btrfs-progs: make receive work inside of subvolumes
authorJosef Bacik <jbacik@fb.com>
Wed, 10 Jun 2015 19:05:51 +0000 (15:05 -0400)
committerDavid Sterba <dsterba@suse.cz>
Fri, 12 Jun 2015 12:39:57 +0000 (14:39 +0200)
Kind of a big feature of btrfs is being able to have a default subvol.  However
the receive code generates the paths to the subvols from the root of the fs,
even in the case of a default subvol.  So instead figure out if we're inside of
a subvol, either because we have a different default or we've chroot'ed and are
using -m.  Then strip this extra path off of the subvol we find so we can look
up our parent properly.  Thanks

Reported-by: Neil Horman <nhorman@redhat.com>
Signed-off-by: Josef Bacik <jbacik@fb.com>
Signed-off-by: David Sterba <dsterba@suse.cz>
cmds-receive.c

index a57f8d9..67e066c 100644 (file)
@@ -61,6 +61,7 @@ struct btrfs_receive
        char *root_path;
        char *dest_dir_path; /* relative to root_path */
        char *full_subvol_path;
+       char *full_root_path;
        int dest_dir_chroot;
 
        struct subvol_info *cur_subvol;
@@ -248,6 +249,46 @@ static int process_snapshot(const char *path, const u8 *uuid, u64 ctransid,
                goto out;
        }
 
+       /*
+        * The path is resolved from the root subvol, but we could be in some
+        * subvolume under the root subvolume, so try and adjust the path to be
+        * relative to our root path.
+        */
+       if (r->full_root_path) {
+               size_t root_len;
+               size_t sub_len;
+
+               root_len = strlen(r->full_root_path);
+               sub_len = strlen(parent_subvol->path);
+
+               /* First make sure the parent subvol is actually in our path */
+               if (sub_len < root_len ||
+                   strstr(parent_subvol->path, r->full_root_path) == NULL) {
+                       fprintf(stderr, "ERROR: parent subvol is not reachable"
+                               " from inside the root subvol.\n");
+                       ret = -ENOENT;
+                       goto out;
+               }
+
+               if (sub_len == root_len) {
+                       parent_subvol->path[0] = '/';
+                       parent_subvol->path[1] = '\0';
+               } else {
+                       /*
+                        * root path is foo/bar
+                        * subvol path is foo/bar/baz
+                        *
+                        * we need to have baz be the path, so we need to move
+                        * the bit after foo/bar/, so path + root_len + 1, and
+                        * move the part we care about, so sub_len - root_len -
+                        * 1.
+                        */
+                       memmove(parent_subvol->path,
+                               parent_subvol->path + root_len + 1,
+                               sub_len - root_len - 1);
+                       parent_subvol->path[sub_len - root_len - 1] = '\0';
+               }
+       }
        /*if (rs_args.ctransid > rs_args.rtransid) {
                if (!r->force) {
                        ret = -EINVAL;
@@ -258,8 +299,11 @@ static int process_snapshot(const char *path, const u8 *uuid, u64 ctransid,
                }
        }*/
 
-       args_v2.fd = openat(r->mnt_fd, parent_subvol->path,
-                       O_RDONLY | O_NOATIME);
+       if (strlen(parent_subvol->path) == 0)
+               args_v2.fd = dup(r->mnt_fd);
+       else
+               args_v2.fd = openat(r->mnt_fd, parent_subvol->path,
+                               O_RDONLY | O_NOATIME);
        if (args_v2.fd < 0) {
                ret = -errno;
                if (errno != ENOENT)
@@ -859,8 +903,10 @@ static struct btrfs_send_ops send_ops = {
 static int do_receive(struct btrfs_receive *r, const char *tomnt,
                      char *realmnt, int r_fd, u64 max_errors)
 {
+       u64 subvol_id;
        int ret;
        char *dest_dir_full_path;
+       char *root_subvol_path;
        int end = 0;
 
        dest_dir_full_path = realpath(tomnt, NULL);
@@ -906,6 +952,42 @@ static int do_receive(struct btrfs_receive *r, const char *tomnt,
                goto out;
        }
 
+       /*
+        * If we use -m or a default subvol we want to resolve the path to the
+        * subvolume we're sitting in so that we can adjust the paths of any
+        * subvols we want to receive in.
+        */
+       ret = btrfs_list_get_path_rootid(r->mnt_fd, &subvol_id);
+       if (ret) {
+               fprintf(stderr, "ERROR: couldn't resolve our subvolid %d\n",
+                       ret);
+               goto out;
+       }
+
+       root_subvol_path = malloc(BTRFS_PATH_NAME_MAX);
+       if (!root_subvol_path) {
+               ret = -ENOMEM;
+               fprintf(stderr, "ERROR: couldn't allocate buffer for the root "
+                       "subvol path\n");
+               goto out;
+       }
+
+       ret = btrfs_subvolid_resolve(r->mnt_fd, root_subvol_path,
+                                    BTRFS_PATH_NAME_MAX, subvol_id);
+       if (ret) {
+               fprintf(stderr, "ERROR: couldn't resolve our subvol path\n");
+               goto out;
+       }
+
+       /*
+        * Ok we're inside of a subvol off of the root subvol, we need to
+        * actually set full_root_path.
+        */
+       if (strlen(root_subvol_path))
+               r->full_root_path = root_subvol_path;
+       else
+               free(root_subvol_path);
+
        if (r->dest_dir_chroot) {
                if (chroot(dest_dir_full_path)) {
                        ret = -errno;
@@ -991,6 +1073,10 @@ out:
                close(r->dest_dir_fd);
                r->dest_dir_fd = -1;
        }
+       if (r->full_root_path) {
+               free(r->full_root_path);
+               r->full_root_path = NULL;
+       }
        return ret;
 }