]> git.ipfire.org Git - thirdparty/shadow.git/commitdiff
useradd(8): fallback to regular dir for BTRFS home on non-BTRFS parent
authorHadi Chokr <hadichokr@icloud.com>
Tue, 21 Apr 2026 07:18:19 +0000 (09:18 +0200)
committerAlejandro Colomar <foss+github@alejandro-colomar.es>
Thu, 23 Apr 2026 12:17:18 +0000 (14:17 +0200)
When the --btrfs-subvolume-home option is used but the parent directory
is not on a BTRFS filesystem, useradd previously failed with an error.
This is too strict; instead, fall back to creating a regular directory
and issue a warning.  The subvolume creation is attempted only when the
parent is BTRFS.  Otherwise, a regular directory is created and a
syslog(3) warning is logged.

Fixes: 3e8c105 (2026-01-02; "src/useradd: Support config for creating home dirs as Btrfs subvolumes")
Co-authored-by: Hadi Chokr <hadichokr@icloud.com>
Co-authored-by: Alejandro Colomar <alx@kernel.org>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
man/useradd.8.xml
src/useradd.c

index 8a087e13ac69c236aaeb05389d2d909ba9fedcf6..0bde6683d46c90869127d5bd2d9cb747f8d2f527 100644 (file)
@@ -2,6 +2,7 @@
 <!--
    SPDX-FileCopyrightText: 1991 - 1994, Julianne Frances Haugh
    SPDX-FileCopyrightText: 2007 - 2011, Nicolas François
+   SPDX-FileCopyrightText: 2025 - 2026, Hadi Chokr
    SPDX-License-Identifier: BSD-3-Clause
 -->
 <!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook V4.5//EN"
                regardless of any configuration file settings.
          </para>
          <para>
-           Note: this feature works only if the underlying filesystem supports
-           Btrfs subvolumes.
+           If the parent directory of the user's home directory is
+           <emphasis>not</emphasis> on a Btrfs filesystem,
+           <command>useradd</command> will <emphasis>not</emphasis> create a
+           subvolume.
+           Instead, it creates a regular directory,
+           prints a warning to standard error,
+           and logs the event via syslog at level
+           <constant>LOG_WARN</constant>.
+           The user account is still created successfully.
+         </para>
+         <para>
+           If the filesystem type cannot be determined (e.g., because of
+           insufficient permissions, an I/O error,
+           or a
+           <citerefentry>
+             <refentrytitle>statfs</refentrytitle>
+             <manvolnum>2</manvolnum>
+           </citerefentry>
+           failure),
+           <command>useradd</command> treats this as a fatal error:
+           the home directory is not created,
+           the command exits with a non‑zero status
+           (<literal>E_HOMEDIR</literal>, 12),
+           and an error message is printed.
          </para>
        </listitem>
       </varlistentry>
index 9bd32b11a104ba5ab50d04b3dc1539753fa73de9..9210379c8b970a380a90911bc10db32639d4a0a6 100644 (file)
@@ -1,11 +1,11 @@
-/*
- * SPDX-FileCopyrightText: 1991 - 1994, Julianne Frances Haugh
- * SPDX-FileCopyrightText: 1996 - 2000, Marek Michałkiewicz
- * SPDX-FileCopyrightText: 2000 - 2006, Tomasz Kłoczko
- * SPDX-FileCopyrightText: 2007 - 2012, Nicolas François
- *
- * SPDX-License-Identifier: BSD-3-Clause
- */
+// SPDX-FileCopyrightText: 1991-1994, Julianne Frances Haugh
+// SPDX-FileCopyrightText: 1996-2000, Marek Michałkiewicz
+// SPDX-FileCopyrightText: 2000-2006, Tomasz Kłoczko
+// SPDX-FileCopyrightText: 2007-2012, Nicolas François
+// SPDX-FileCopyrightText: 2025-2026, Hadi Chokr
+// SPDX-FileCopyrightText: 2026, Alejandro Colomar <alx@kernel.org>
+// SPDX-License-Identifier: BSD-3-Clause
+
 
 #include "config.h"
 
@@ -2251,6 +2251,8 @@ static void create_home(const struct option_flags *flags)
           owner root:root.
         */
        for (cp = strtok(bhome, "/"); cp != NULL; cp = strtok(NULL, "/")) {
+               bool  dir_created;
+
                /* Avoid turning a relative path into an absolute path. */
                if (strprefix(bhome, "/") || !streq(path, ""))
                        strcat(path, "/");
@@ -2260,10 +2262,7 @@ static void create_home(const struct option_flags *flags)
                        continue;
                }
 
-               /* Check if parent directory is BTRFS, fail if requesting
-                  subvolume but no BTRFS. The paths could be different by the
-                  trailing slash
-                */
+               dir_created = false;
 #if WITH_BTRFS
                if (subvolflg && (strlen(prefix_user_home) - (int)strlen(path)) <= 1) {
                        char *btrfs_check = strdup(path);
@@ -2284,25 +2283,26 @@ static void create_home(const struct option_flags *flags)
                        free(btrfs_check);
                        if (!is_btrfs(&sfs)) {
                                fprintf(stderr,
-                                       _("%s: home directory \"%s\" must be mounted on BTRFS\n"),
-                                       Prog, path);
-                               fail_exit(E_HOMEDIR, process_selinux);
+                                       _("%s: warning: \"%s\" is not on BTRFS; creating regular directory instead of subvolume\n"),
+                                       Prog, prefix_user_home);
+                       } else {
+                               if (btrfs_create_subvolume(path)) {
+                                       fprintf(stderr,
+                                               _("%s: failed to create BTRFS subvolume: %s\n"),
+                                               Prog, path);
+                                       fail_exit(E_HOMEDIR, process_selinux);
+                               }
+                               dir_created = true;
                        }
-                       // make subvolume to mount for user instead of directory
-                       if (btrfs_create_subvolume(path)) {
-                               fprintf(stderr,
-                                       _("%s: failed to create BTRFS subvolume: %s\n"),
+               }
+#endif
+               if (!dir_created) {
+                       if (mkdir(path, 0) != 0) {
+                               fprintf(stderr, _("%s: cannot create directory %s\n"),
                                        Prog, path);
                                fail_exit(E_HOMEDIR, process_selinux);
                        }
                }
-               else
-#endif
-               if (mkdir(path, 0) != 0) {
-                       fprintf(stderr, _("%s: cannot create directory %s\n"),
-                               Prog, path);
-                       fail_exit(E_HOMEDIR, process_selinux);
-               }
                if (chown(path, 0, 0) < 0) {
                        fprintf(stderr,
                                _("%s: warning: chown on `%s' failed: %m\n"),