]> git.ipfire.org Git - thirdparty/shadow.git/commitdiff
Add support for btrfs subvolumes for user homes
authorAdam Majer <amajer@suse.de>
Mon, 21 Jan 2019 08:32:36 +0000 (09:32 +0100)
committerSerge Hallyn <shallyn@cisco.com>
Sat, 4 May 2019 05:38:23 +0000 (22:38 -0700)
new switch added to useradd command, --btrfs-subvolume-home. When
specified *and* the filesystem is detected as btrfs, it will create a
subvolume for user's home instead of a plain directory. This is done via
`btrfs subvolume` command.  Specifying the new switch while trying to
create home on non-btrfs will result in an error.

userdel -r will handle and remove this subvolume transparently via
`btrfs subvolume` command. Previosuly this failed as you can't rmdir a
subvolume.

usermod, when moving user's home across devices, will detect if the home
is a subvolume and issue an error messages instead of copying it. Moving
user's home (as subvolume) on same btrfs works transparently.

lib/prototypes.h
libmisc/Makefile.am
libmisc/btrfs.c [new file with mode: 0644]
src/useradd.c
src/userdel.c
src/usermod.c

index d9e7f6f4c4929ce4260eb34b961420d45f1e7c96..9e4f73cd05b56639fb761f6551860c66a44f4603 100644 (file)
@@ -72,6 +72,12 @@ extern int expire (const struct passwd *, /*@null@*/const struct spwd *);
 /* isexpired.c */
 extern int isexpired (const struct passwd *, /*@null@*/const struct spwd *);
 
+/* btrfs.c */
+extern int btrfs_create_subvolume(const char *path);
+extern int btrfs_remove_subvolume(const char *path);
+extern int btrfs_is_subvolume(const char *path);
+extern int is_btrfs(const char *path);
+
 /* basename() renamed to Basename() to avoid libc name space confusion */
 /* basename.c */
 extern /*@observer@*/const char *Basename (const char *str);
index eb44a569e244c8dbe8902f0baced1051b135b64e..a95392eae2669bcba1ae82f2c9facc63842cfe8f 100644 (file)
@@ -10,6 +10,7 @@ libmisc_a_SOURCES = \
        age.c \
        audit_help.c \
        basename.c \
+       btrfs.c \
        chkname.c \
        chkname.h \
        chowndir.c \
diff --git a/libmisc/btrfs.c b/libmisc/btrfs.c
new file mode 100644 (file)
index 0000000..7236454
--- /dev/null
@@ -0,0 +1,94 @@
+#include <linux/btrfs_tree.h>
+#include <linux/magic.h>
+#include <sys/statfs.h>
+
+#include "prototypes.h"
+
+
+static int run_btrfs_subvolume_cmd(const char *subcmd, const char *arg1, const char *arg2)
+{
+       int status = 0;
+       const char *cmd = "/sbin/btrfs";
+       const char *argv[] = {
+               strrchr(cmd, '/'),
+               "subvolume",
+               subcmd,
+               arg1,
+               arg2,
+               NULL
+       };
+
+       if (argv[0] == NULL)
+               argv[0] = cmd;
+       else
+               argv[0] = argv[0] + 1;
+
+       if (access(cmd, X_OK)) {
+               return 1;
+       }
+
+       if (run_command(cmd, argv, NULL, &status))
+               return -1;
+       return status;
+}
+
+
+int btrfs_create_subvolume(const char *path)
+{
+       return run_btrfs_subvolume_cmd("create", path, NULL);
+}
+
+
+int btrfs_remove_subvolume(const char *path)
+{
+       return run_btrfs_subvolume_cmd("delete", "-C", path);
+}
+
+
+/* Adapted from btrfsprogs */
+/*
+ * This intentionally duplicates btrfs_util_is_subvolume_fd() instead of opening
+ * a file descriptor and calling it, because fstat() and fstatfs() don't accept
+ * file descriptors opened with O_PATH on old kernels (before v3.6 and before
+ * v3.12, respectively), but stat() and statfs() can be called on a path that
+ * the user doesn't have read or write permissions to.
+ *
+ * returns:
+ *   1 - btrfs subvolume
+ *   0 - not btrfs subvolume
+ *  -1 - error
+ */
+int btrfs_is_subvolume(const char *path)
+{
+       struct stat st;
+       int ret;
+
+       ret = is_btrfs(path);
+       if (ret <= 0)
+               return ret;
+
+       ret = stat(path, &st);
+       if (ret == -1)
+               return -1;
+
+       if (st.st_ino != BTRFS_FIRST_FREE_OBJECTID || !S_ISDIR(st.st_mode)) {
+               return 0;
+       }
+
+       return 1;
+}
+
+
+/* Adapted from btrfsprogs */
+int is_btrfs(const char *path)
+{
+       struct statfs sfs;
+       int ret;
+
+       ret = statfs(path, &sfs);
+       if (ret == -1)
+               return -1;
+
+       return sfs.f_type == BTRFS_SUPER_MAGIC;
+}
+
index 41e2ace4a5e1188ef3c7b2df45af3a5eaa0c9f89..bb426bf43895bba40ad953628f438650dd7af54c 100644 (file)
@@ -165,6 +165,7 @@ static bool
     oflg = false,              /* permit non-unique user ID to be specified with -u */
     rflg = false,              /* create a system account */
     sflg = false,              /* shell program for new account */
+    subvolflg = false,         /* create subvolume home on BTRFS */
     uflg = false,              /* specify user ID for new account */
     Uflg = false;              /* create a group having the same name as the user */
 
@@ -822,6 +823,7 @@ static void usage (int status)
                        Prog, Prog, Prog);
        (void) fputs (_("  -b, --base-dir BASE_DIR       base directory for the home directory of the\n"
                        "                                new account\n"), usageout);
+       (void) fputs (_("      --btrfs-subvolume-home    use BTRFS subvolume for home directory\n"), usageout);
        (void) fputs (_("  -c, --comment COMMENT         GECOS field of the new account\n"), usageout);
        (void) fputs (_("  -d, --home-dir HOME_DIR       home directory of the new account\n"), usageout);
        (void) fputs (_("  -D, --defaults                print or change default useradd configuration\n"), usageout);
@@ -1102,6 +1104,7 @@ static void process_flags (int argc, char **argv)
                int c;
                static struct option long_options[] = {
                        {"base-dir",       required_argument, NULL, 'b'},
+                       {"btrfs-subvolume-home", no_argument, NULL, 200},
                        {"comment",        required_argument, NULL, 'c'},
                        {"home-dir",       required_argument, NULL, 'd'},
                        {"defaults",       no_argument,       NULL, 'D'},
@@ -1148,6 +1151,9 @@ static void process_flags (int argc, char **argv)
                                def_home = optarg;
                                bflg = true;
                                break;
+                       case 200:
+                               subvolflg = true;
+                               break;
                        case 'c':
                                if (!VALID (optarg)) {
                                        fprintf (stderr,
@@ -2073,7 +2079,35 @@ static void create_home (void)
                        strcat (path, "/");
                        strcat (path, cp);
                        if (access (path, F_OK) != 0) {
-                               if (mkdir (path, 0) != 0) {
+                               /* Check if parent directory is BTRFS, fail if requesting
+                                  subvolume but no BTRFS. The paths cound be different by the
+                                  trailing slash
+                                */
+                               if (subvolflg && (strlen(prefix_user_home) - (int)strlen(path)) <= 1) {
+                                       char *btrfs_check = strdup(path);
+
+                                       if (!btrfs_check) {
+                                               fprintf (stderr,
+                                                        _("%s: error while duplicating string in BTRFS check %s\n"),
+                                                        Prog, path);
+                                               fail_exit (E_HOMEDIR);
+                                       }
+                                       btrfs_check[strlen(path) - strlen(cp) - 1] = '\0';
+                                       if (is_btrfs(btrfs_check) <= 0) {
+                                               fprintf (stderr,
+                                                        _("%s: home directory \"%s\" must be mounted on BTRFS\n"),
+                                                        Prog, path);
+                                               fail_exit (E_HOMEDIR);
+                                       }
+                                       // make subvolume to mount for user instead of directory
+                                       if (btrfs_create_subvolume(path)) {
+                                               fprintf (stderr,
+                                                        _("%s: failed to create BTRFS subvolume: %s\n"),
+                                                        Prog, path);
+                                               fail_exit (E_HOMEDIR);
+                                       }
+                               }
+                               else if (mkdir (path, 0) != 0) {
                        fprintf (stderr,
                                                        _("%s: cannot create directory %s\n"),
                                                        Prog, path);
index 0715e4fe9043542221037f9d96252051cb49902f..003691338a18c302f8fb8dc1ec0de4600da5b735 100644 (file)
@@ -1272,7 +1272,21 @@ int main (int argc, char **argv)
 #endif                         /* EXTRA_CHECK_HOME_DIR */
 
        if (rflg) {
-               if (remove_tree (user_home, true) != 0) {
+               int is_subvolume = btrfs_is_subvolume (user_home);
+               if (is_subvolume < 0) {
+                   errors++;
+                   /* continue */
+               }
+               else if (is_subvolume > 0) {
+                       if (btrfs_remove_subvolume (user_home)) {
+                               fprintf (stderr,
+                                        _("%s: error removing subvolume %s\n"),
+                                        Prog, user_home);
+                               errors++;
+                               /* continue */
+                       }
+               }
+               else if (remove_tree (user_home, true) != 0) {
                        fprintf (stderr,
                                 _("%s: error removing directory %s\n"),
                                 Prog, user_home);
index cc77c0a8cacdd2101f8e6302e2d8d721ad8c5ca7..3c2a08441fa1f9c20b8828ba55c746b70d648718 100644 (file)
@@ -1819,6 +1819,13 @@ static void move_home (void)
                        return;
                } else {
                        if (EXDEV == errno) {
+                               if (btrfs_is_subvolume (prefix_user_home) > 0) {
+                                       fprintf (stderr,
+                                               _("%s: error: cannot move subvolume from %s to %s - different device\n"),
+                                               Prog, prefix_user_home, prefix_user_newhome);
+                                       fail_exit (E_HOMEDIR);
+                               }
+
                                if (copy_tree (prefix_user_home, prefix_user_newhome, true,
                                               true,
                                               user_id,