btrfs-progs: Add further checks to btrfs replace start command
[platform/upstream/btrfs-progs.git] / cmds-receive.c
index 114b718..d4b3103 100644 (file)
@@ -28,6 +28,7 @@
 #include <wait.h>
 #include <assert.h>
 #include <getopt.h>
+#include <limits.h>
 
 #include <sys/stat.h>
 #include <sys/types.h>
@@ -56,14 +57,20 @@ struct btrfs_receive
        int dest_dir_fd;
 
        int write_fd;
-       char *write_path;
+       char write_path[PATH_MAX];
 
        char *root_path;
        char *dest_dir_path; /* relative to root_path */
-       char *full_subvol_path;
+       char full_subvol_path[PATH_MAX];
+       char *full_root_path;
        int dest_dir_chroot;
 
-       struct subvol_info *cur_subvol;
+       struct subvol_info cur_subvol;
+       /*
+        * Substitute for cur_subvol::path which is a pointer and we cannot
+        * change it to an array as it's a public API.
+        */
+       char cur_subvol_path[PATH_MAX];
 
        struct subvol_uuid_search sus;
 
@@ -86,21 +93,21 @@ static int finish_subvol(struct btrfs_receive *r)
        char uuid_str[BTRFS_UUID_UNPARSED_SIZE];
        u64 flags;
 
-       if (r->cur_subvol == NULL)
+       if (r->cur_subvol_path[0] == 0)
                return 0;
 
-       subvol_fd = openat(r->mnt_fd, r->cur_subvol->path,
+       subvol_fd = openat(r->mnt_fd, r->cur_subvol_path,
                        O_RDONLY | O_NOATIME);
        if (subvol_fd < 0) {
                ret = -errno;
                fprintf(stderr, "ERROR: open %s failed. %s\n",
-                               r->cur_subvol->path, strerror(-ret));
+                               r->cur_subvol_path, strerror(-ret));
                goto out;
        }
 
        memset(&rs_args, 0, sizeof(rs_args));
-       memcpy(rs_args.uuid, r->cur_subvol->received_uuid, BTRFS_UUID_SIZE);
-       rs_args.stransid = r->cur_subvol->stransid;
+       memcpy(rs_args.uuid, r->cur_subvol.received_uuid, BTRFS_UUID_SIZE);
+       rs_args.stransid = r->cur_subvol.stransid;
 
        if (g_verbose >= 1) {
                uuid_unparse((u8*)rs_args.uuid, uuid_str);
@@ -115,7 +122,7 @@ static int finish_subvol(struct btrfs_receive *r)
                                strerror(-ret));
                goto out;
        }
-       r->cur_subvol->rtransid = rs_args.rtransid;
+       r->cur_subvol.rtransid = rs_args.rtransid;
 
        ret = ioctl(subvol_fd, BTRFS_IOC_SUBVOL_GETFLAGS, &flags);
        if (ret < 0) {
@@ -138,10 +145,8 @@ static int finish_subvol(struct btrfs_receive *r)
        ret = 0;
 
 out:
-       if (r->cur_subvol) {
-               free(r->cur_subvol->path);
-               free(r->cur_subvol);
-               r->cur_subvol = NULL;
+       if (r->cur_subvol_path[0]) {
+               r->cur_subvol_path[0] = 0;
        }
        if (subvol_fd != -1)
                close(subvol_fd);
@@ -160,25 +165,36 @@ static int process_subvol(const char *path, const u8 *uuid, u64 ctransid,
        if (ret < 0)
                goto out;
 
-       r->cur_subvol = calloc(1, sizeof(*r->cur_subvol));
+       BUG_ON(r->cur_subvol.path);
+       BUG_ON(r->cur_subvol_path[0]);
 
-       if (strlen(r->dest_dir_path) == 0)
-               r->cur_subvol->path = strdup(path);
-       else
-               r->cur_subvol->path = path_cat(r->dest_dir_path, path);
-       free(r->full_subvol_path);
-       r->full_subvol_path = path_cat3(r->root_path, r->dest_dir_path, path);
+       if (strlen(r->dest_dir_path) == 0) {
+               strncpy_null(r->cur_subvol_path, path);
+       } else {
+               ret = path_cat_out(r->cur_subvol_path, r->dest_dir_path, path);
+               if (ret < 0) {
+                       fprintf(stderr, "ERROR: subvol: path invalid: %s\n",
+                                       path);
+                       goto out;
+               }
+       }
+       ret = path_cat3_out(r->full_subvol_path, r->root_path,
+                       r->dest_dir_path, path);
+       if (ret < 0) {
+               fprintf(stderr, "ERROR: subvol: path invalid: %s\n", path);
+               goto out;
+       }
 
        fprintf(stderr, "At subvol %s\n", path);
 
-       memcpy(r->cur_subvol->received_uuid, uuid, BTRFS_UUID_SIZE);
-       r->cur_subvol->stransid = ctransid;
+       memcpy(r->cur_subvol.received_uuid, uuid, BTRFS_UUID_SIZE);
+       r->cur_subvol.stransid = ctransid;
 
        if (g_verbose) {
-               uuid_unparse((u8*)r->cur_subvol->received_uuid, uuid_str);
+               uuid_unparse((u8*)r->cur_subvol.received_uuid, uuid_str);
                fprintf(stderr, "receiving subvol %s uuid=%s, stransid=%llu\n",
                                path, uuid_str,
-                               r->cur_subvol->stransid);
+                               r->cur_subvol.stransid);
        }
 
        memset(&args_v1, 0, sizeof(args_v1));
@@ -209,25 +225,36 @@ static int process_snapshot(const char *path, const u8 *uuid, u64 ctransid,
        if (ret < 0)
                goto out;
 
-       r->cur_subvol = calloc(1, sizeof(*r->cur_subvol));
+       BUG_ON(r->cur_subvol.path);
+       BUG_ON(r->cur_subvol_path[0]);
 
-       if (strlen(r->dest_dir_path) == 0)
-               r->cur_subvol->path = strdup(path);
-       else
-               r->cur_subvol->path = path_cat(r->dest_dir_path, path);
-       free(r->full_subvol_path);
-       r->full_subvol_path = path_cat3(r->root_path, r->dest_dir_path, path);
+       if (strlen(r->dest_dir_path) == 0) {
+               strncpy_null(r->cur_subvol_path, path);
+       } else {
+               ret = path_cat_out(r->cur_subvol_path, r->dest_dir_path, path);
+               if (ret < 0) {
+                       fprintf(stderr, "ERROR: snapshot: path invalid: %s\n",
+                                       path);
+                       goto out;
+               }
+       }
+       ret = path_cat3_out(r->full_subvol_path, r->root_path,
+                       r->dest_dir_path, path);
+       if (ret < 0) {
+               fprintf(stderr, "ERROR: snapshot: path invalid: %s\n", path);
+               goto out;
+       }
 
        fprintf(stdout, "At snapshot %s\n", path);
 
-       memcpy(r->cur_subvol->received_uuid, uuid, BTRFS_UUID_SIZE);
-       r->cur_subvol->stransid = ctransid;
+       memcpy(r->cur_subvol.received_uuid, uuid, BTRFS_UUID_SIZE);
+       r->cur_subvol.stransid = ctransid;
 
        if (g_verbose) {
-               uuid_unparse((u8*)r->cur_subvol->received_uuid, uuid_str);
+               uuid_unparse((u8*)r->cur_subvol.received_uuid, uuid_str);
                fprintf(stderr, "receiving snapshot %s uuid=%s, "
                                "ctransid=%llu ", path, uuid_str,
-                               r->cur_subvol->stransid);
+                               r->cur_subvol.stransid);
                uuid_unparse(parent_uuid, uuid_str);
                fprintf(stderr, "parent_uuid=%s, parent_ctransid=%llu\n",
                                uuid_str, parent_ctransid);
@@ -248,6 +275,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 +325,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)
@@ -296,7 +366,13 @@ static int process_mkfile(const char *path, void *user)
 {
        int ret;
        struct btrfs_receive *r = user;
-       char *full_path = path_cat(r->full_subvol_path, path);
+       char full_path[PATH_MAX];
+
+       ret = path_cat_out(full_path, r->full_subvol_path, path);
+       if (ret < 0) {
+               fprintf(stderr, "ERROR: mkfile: path invalid: %s\n", path);
+               goto out;
+       }
 
        if (g_verbose >= 2)
                fprintf(stderr, "mkfile %s\n", path);
@@ -312,7 +388,6 @@ static int process_mkfile(const char *path, void *user)
        ret = 0;
 
 out:
-       free(full_path);
        return ret;
 }
 
@@ -320,7 +395,13 @@ static int process_mkdir(const char *path, void *user)
 {
        int ret;
        struct btrfs_receive *r = user;
-       char *full_path = path_cat(r->full_subvol_path, path);
+       char full_path[PATH_MAX];
+
+       ret = path_cat_out(full_path, r->full_subvol_path, path);
+       if (ret < 0) {
+               fprintf(stderr, "ERROR: mkdir: path invalid: %s\n", path);
+               goto out;
+       }
 
        if (g_verbose >= 2)
                fprintf(stderr, "mkdir %s\n", path);
@@ -332,7 +413,7 @@ static int process_mkdir(const char *path, void *user)
                                strerror(-ret));
        }
 
-       free(full_path);
+out:
        return ret;
 }
 
@@ -340,7 +421,13 @@ static int process_mknod(const char *path, u64 mode, u64 dev, void *user)
 {
        int ret;
        struct btrfs_receive *r = user;
-       char *full_path = path_cat(r->full_subvol_path, path);
+       char full_path[PATH_MAX];
+
+       ret = path_cat_out(full_path, r->full_subvol_path, path);
+       if (ret < 0) {
+               fprintf(stderr, "ERROR: mknod: path invalid: %s\n", path);
+               goto out;
+       }
 
        if (g_verbose >= 2)
                fprintf(stderr, "mknod %s mode=%llu, dev=%llu\n",
@@ -353,7 +440,7 @@ static int process_mknod(const char *path, u64 mode, u64 dev, void *user)
                                strerror(-ret));
        }
 
-       free(full_path);
+out:
        return ret;
 }
 
@@ -361,7 +448,13 @@ static int process_mkfifo(const char *path, void *user)
 {
        int ret;
        struct btrfs_receive *r = user;
-       char *full_path = path_cat(r->full_subvol_path, path);
+       char full_path[PATH_MAX];
+
+       ret = path_cat_out(full_path, r->full_subvol_path, path);
+       if (ret < 0) {
+               fprintf(stderr, "ERROR: mkfifo: path invalid: %s\n", path);
+               goto out;
+       }
 
        if (g_verbose >= 2)
                fprintf(stderr, "mkfifo %s\n", path);
@@ -373,7 +466,7 @@ static int process_mkfifo(const char *path, void *user)
                                strerror(-ret));
        }
 
-       free(full_path);
+out:
        return ret;
 }
 
@@ -381,7 +474,13 @@ static int process_mksock(const char *path, void *user)
 {
        int ret;
        struct btrfs_receive *r = user;
-       char *full_path = path_cat(r->full_subvol_path, path);
+       char full_path[PATH_MAX];
+
+       ret = path_cat_out(full_path, r->full_subvol_path, path);
+       if (ret < 0) {
+               fprintf(stderr, "ERROR: mksock: path invalid: %s\n", path);
+               goto out;
+       }
 
        if (g_verbose >= 2)
                fprintf(stderr, "mksock %s\n", path);
@@ -393,7 +492,7 @@ static int process_mksock(const char *path, void *user)
                                strerror(-ret));
        }
 
-       free(full_path);
+out:
        return ret;
 }
 
@@ -401,7 +500,13 @@ static int process_symlink(const char *path, const char *lnk, void *user)
 {
        int ret;
        struct btrfs_receive *r = user;
-       char *full_path = path_cat(r->full_subvol_path, path);
+       char full_path[PATH_MAX];
+
+       ret = path_cat_out(full_path, r->full_subvol_path, path);
+       if (ret < 0) {
+               fprintf(stderr, "ERROR: symlink: path invalid: %s\n", path);
+               goto out;
+       }
 
        if (g_verbose >= 2)
                fprintf(stderr, "symlink %s -> %s\n", path, lnk);
@@ -413,7 +518,7 @@ static int process_symlink(const char *path, const char *lnk, void *user)
                                lnk, strerror(-ret));
        }
 
-       free(full_path);
+out:
        return ret;
 }
 
@@ -421,8 +526,22 @@ static int process_rename(const char *from, const char *to, void *user)
 {
        int ret;
        struct btrfs_receive *r = user;
-       char *full_from = path_cat(r->full_subvol_path, from);
-       char *full_to = path_cat(r->full_subvol_path, to);
+       char full_from[PATH_MAX];
+       char full_to[PATH_MAX];
+
+       ret = path_cat_out(full_from, r->full_subvol_path, from);
+       if (ret < 0) {
+               fprintf(stderr, "ERROR: rename: source path invalid: %s\n",
+                               from);
+               goto out;
+       }
+
+       ret = path_cat_out(full_to, r->full_subvol_path, to);
+       if (ret < 0) {
+               fprintf(stderr, "ERROR: rename: target path invalid: %s\n",
+                               to);
+               goto out;
+       }
 
        if (g_verbose >= 2)
                fprintf(stderr, "rename %s -> %s\n", from, to);
@@ -434,8 +553,7 @@ static int process_rename(const char *from, const char *to, void *user)
                                to, strerror(-ret));
        }
 
-       free(full_from);
-       free(full_to);
+out:
        return ret;
 }
 
@@ -443,8 +561,22 @@ static int process_link(const char *path, const char *lnk, void *user)
 {
        int ret;
        struct btrfs_receive *r = user;
-       char *full_path = path_cat(r->full_subvol_path, path);
-       char *full_link_path = path_cat(r->full_subvol_path, lnk);
+       char full_path[PATH_MAX];
+       char full_link_path[PATH_MAX];
+
+       ret = path_cat_out(full_path, r->full_subvol_path, path);
+       if (ret < 0) {
+               fprintf(stderr, "ERROR: link: source path invalid: %s\n",
+                               full_path);
+               goto out;
+       }
+
+       ret = path_cat_out(full_link_path, r->full_subvol_path, lnk);
+       if (ret < 0) {
+               fprintf(stderr, "ERROR: link: target path invalid: %s\n",
+                               full_link_path);
+               goto out;
+       }
 
        if (g_verbose >= 2)
                fprintf(stderr, "link %s -> %s\n", path, lnk);
@@ -456,8 +588,7 @@ static int process_link(const char *path, const char *lnk, void *user)
                                lnk, strerror(-ret));
        }
 
-       free(full_path);
-       free(full_link_path);
+out:
        return ret;
 }
 
@@ -466,7 +597,13 @@ static int process_unlink(const char *path, void *user)
 {
        int ret;
        struct btrfs_receive *r = user;
-       char *full_path = path_cat(r->full_subvol_path, path);
+       char full_path[PATH_MAX];
+
+       ret = path_cat_out(full_path, r->full_subvol_path, path);
+       if (ret < 0) {
+               fprintf(stderr, "ERROR: unlink: path invalid: %s\n", path);
+               goto out;
+       }
 
        if (g_verbose >= 2)
                fprintf(stderr, "unlink %s\n", path);
@@ -478,7 +615,7 @@ static int process_unlink(const char *path, void *user)
                                strerror(-ret));
        }
 
-       free(full_path);
+out:
        return ret;
 }
 
@@ -486,7 +623,13 @@ static int process_rmdir(const char *path, void *user)
 {
        int ret;
        struct btrfs_receive *r = user;
-       char *full_path = path_cat(r->full_subvol_path, path);
+       char full_path[PATH_MAX];
+
+       ret = path_cat_out(full_path, r->full_subvol_path, path);
+       if (ret < 0) {
+               fprintf(stderr, "ERROR: rmdir: path invalid: %s\n", path);
+               goto out;
+       }
 
        if (g_verbose >= 2)
                fprintf(stderr, "rmdir %s\n", path);
@@ -498,11 +641,10 @@ static int process_rmdir(const char *path, void *user)
                                strerror(-ret));
        }
 
-       free(full_path);
+out:
        return ret;
 }
 
-
 static int open_inode_for_write(struct btrfs_receive *r, const char *path)
 {
        int ret = 0;
@@ -521,8 +663,7 @@ static int open_inode_for_write(struct btrfs_receive *r, const char *path)
                                strerror(-ret));
                goto out;
        }
-       free(r->write_path);
-       r->write_path = strdup(path);
+       strncpy_null(r->write_path, path);
 
 out:
        return ret;
@@ -543,10 +684,16 @@ static int process_write(const char *path, const void *data, u64 offset,
 {
        int ret = 0;
        struct btrfs_receive *r = user;
-       char *full_path = path_cat(r->full_subvol_path, path);
+       char full_path[PATH_MAX];
        u64 pos = 0;
        int w;
 
+       ret = path_cat_out(full_path, r->full_subvol_path, path);
+       if (ret < 0) {
+               fprintf(stderr, "ERROR: write: path invalid: %s\n", path);
+               goto out;
+       }
+
        ret = open_inode_for_write(r, full_path);
        if (ret < 0)
                goto out;
@@ -564,7 +711,6 @@ static int process_write(const char *path, const void *data, u64 offset,
        }
 
 out:
-       free(full_path);
        return ret;
 }
 
@@ -577,11 +723,18 @@ static int process_clone(const char *path, u64 offset, u64 len,
        struct btrfs_receive *r = user;
        struct btrfs_ioctl_clone_range_args clone_args;
        struct subvol_info *si = NULL;
-       char *full_path = path_cat(r->full_subvol_path, path);
+       char full_path[PATH_MAX];
        char *subvol_path = NULL;
-       char *full_clone_path = NULL;
+       char full_clone_path[PATH_MAX];
        int clone_fd = -1;
 
+       ret = path_cat_out(full_path, r->full_subvol_path, path);
+       if (ret < 0) {
+               fprintf(stderr, "ERROR: clone: source path invalid: %s\n",
+                               path);
+               goto out;
+       }
+
        ret = open_inode_for_write(r, full_path);
        if (ret < 0)
                goto out;
@@ -589,10 +742,10 @@ static int process_clone(const char *path, u64 offset, u64 len,
        si = subvol_uuid_search(&r->sus, 0, clone_uuid, clone_ctransid, NULL,
                        subvol_search_by_received_uuid);
        if (!si) {
-               if (memcmp(clone_uuid, r->cur_subvol->received_uuid,
+               if (memcmp(clone_uuid, r->cur_subvol.received_uuid,
                                BTRFS_UUID_SIZE) == 0) {
                        /* TODO check generation of extent */
-                       subvol_path = strdup(r->cur_subvol->path);
+                       subvol_path = strdup(r->cur_subvol_path);
                } else {
                        ret = -ENOENT;
                        fprintf(stderr, "ERROR: did not find source subvol.\n");
@@ -617,7 +770,12 @@ static int process_clone(const char *path, u64 offset, u64 len,
                subvol_path = strdup(si->path);
        }
 
-       full_clone_path = path_cat(subvol_path, clone_path);
+       ret = path_cat_out(full_clone_path, subvol_path, clone_path);
+       if (ret < 0) {
+               fprintf(stderr, "ERROR: clone: target path invalid: %s\n",
+                               clone_path);
+               goto out;
+       }
 
        clone_fd = openat(r->mnt_fd, full_clone_path, O_RDONLY | O_NOATIME);
        if (clone_fd < 0) {
@@ -644,8 +802,6 @@ out:
                free(si->path);
                free(si);
        }
-       free(full_path);
-       free(full_clone_path);
        free(subvol_path);
        if (clone_fd != -1)
                close(clone_fd);
@@ -658,7 +814,13 @@ static int process_set_xattr(const char *path, const char *name,
 {
        int ret = 0;
        struct btrfs_receive *r = user;
-       char *full_path = path_cat(r->full_subvol_path, path);
+       char full_path[PATH_MAX];
+
+       ret = path_cat_out(full_path, r->full_subvol_path, path);
+       if (ret < 0) {
+               fprintf(stderr, "ERROR: set_xattr: path invalid: %s\n", path);
+               goto out;
+       }
 
        if (strcmp("security.capability", name) == 0) {
                if (g_verbose >= 3)
@@ -693,7 +855,6 @@ static int process_set_xattr(const char *path, const char *name,
        }
 
 out:
-       free(full_path);
        return ret;
 }
 
@@ -701,7 +862,14 @@ static int process_remove_xattr(const char *path, const char *name, void *user)
 {
        int ret = 0;
        struct btrfs_receive *r = user;
-       char *full_path = path_cat(r->full_subvol_path, path);
+       char full_path[PATH_MAX];
+
+       ret = path_cat_out(full_path, r->full_subvol_path, path);
+       if (ret < 0) {
+               fprintf(stderr, "ERROR: remove_xattr: path invalid: %s\n",
+                               path);
+               goto out;
+       }
 
        if (g_verbose >= 2) {
                fprintf(stderr, "remove_xattr %s - name=%s\n",
@@ -717,7 +885,6 @@ static int process_remove_xattr(const char *path, const char *name, void *user)
        }
 
 out:
-       free(full_path);
        return ret;
 }
 
@@ -725,7 +892,13 @@ static int process_truncate(const char *path, u64 size, void *user)
 {
        int ret = 0;
        struct btrfs_receive *r = user;
-       char *full_path = path_cat(r->full_subvol_path, path);
+       char full_path[PATH_MAX];
+
+       ret = path_cat_out(full_path, r->full_subvol_path, path);
+       if (ret < 0) {
+               fprintf(stderr, "ERROR: truncate: path invalid: %s\n", path);
+               goto out;
+       }
 
        if (g_verbose >= 2)
                fprintf(stderr, "truncate %s size=%llu\n", path, size);
@@ -739,7 +912,6 @@ static int process_truncate(const char *path, u64 size, void *user)
        }
 
 out:
-       free(full_path);
        return ret;
 }
 
@@ -747,7 +919,13 @@ static int process_chmod(const char *path, u64 mode, void *user)
 {
        int ret = 0;
        struct btrfs_receive *r = user;
-       char *full_path = path_cat(r->full_subvol_path, path);
+       char full_path[PATH_MAX];
+
+       ret = path_cat_out(full_path, r->full_subvol_path, path);
+       if (ret < 0) {
+               fprintf(stderr, "ERROR: chmod: path invalid: %s\n", path);
+               goto out;
+       }
 
        if (g_verbose >= 2)
                fprintf(stderr, "chmod %s - mode=0%o\n", path, (int)mode);
@@ -761,7 +939,6 @@ static int process_chmod(const char *path, u64 mode, void *user)
        }
 
 out:
-       free(full_path);
        return ret;
 }
 
@@ -769,7 +946,13 @@ static int process_chown(const char *path, u64 uid, u64 gid, void *user)
 {
        int ret = 0;
        struct btrfs_receive *r = user;
-       char *full_path = path_cat(r->full_subvol_path, path);
+       char full_path[PATH_MAX];
+
+       ret = path_cat_out(full_path, r->full_subvol_path, path);
+       if (ret < 0) {
+               fprintf(stderr, "ERROR: chown: path invalid: %s\n", path);
+               goto out;
+       }
 
        if (g_verbose >= 2)
                fprintf(stderr, "chown %s - uid=%llu, gid=%llu\n", path,
@@ -801,7 +984,6 @@ static int process_chown(const char *path, u64 uid, u64 gid, void *user)
        }
 
 out:
-       free(full_path);
        return ret;
 }
 
@@ -811,9 +993,15 @@ static int process_utimes(const char *path, struct timespec *at,
 {
        int ret = 0;
        struct btrfs_receive *r = user;
-       char *full_path = path_cat(r->full_subvol_path, path);
+       char full_path[PATH_MAX];
        struct timespec tv[2];
 
+       ret = path_cat_out(full_path, r->full_subvol_path, path);
+       if (ret < 0) {
+               fprintf(stderr, "ERROR: utimes: path invalid: %s\n", path);
+               goto out;
+       }
+
        if (g_verbose >= 2)
                fprintf(stderr, "utimes %s\n", path);
 
@@ -828,10 +1016,23 @@ static int process_utimes(const char *path, struct timespec *at,
        }
 
 out:
-       free(full_path);
        return ret;
 }
 
+static int process_update_extent(const char *path, u64 offset, u64 len,
+               void *user)
+{
+       if (g_verbose >= 2)
+               fprintf(stderr, "update_extent %s: offset=%llu, len=%llu\n",
+                               path, (unsigned long long)offset,
+                               (unsigned long long)len);
+
+       /*
+        * Sent with BTRFS_SEND_FLAG_NO_FILE_DATA, nothing to do.
+        */
+
+       return 0;
+}
 
 static struct btrfs_send_ops send_ops = {
        .subvol = process_subvol,
@@ -854,13 +1055,16 @@ static struct btrfs_send_ops send_ops = {
        .chmod = process_chmod,
        .chown = process_chown,
        .utimes = process_utimes,
+       .update_extent = process_update_extent,
 };
 
 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[PATH_MAX];
        int end = 0;
 
        dest_dir_full_path = realpath(tomnt, NULL);
@@ -879,7 +1083,7 @@ static int do_receive(struct btrfs_receive *r, const char *tomnt,
                goto out;
        }
 
-       if (realmnt) {
+       if (realmnt[0]) {
                r->root_path = realmnt;
        } else {
                ret = find_mount_root(dest_dir_full_path, &r->root_path);
@@ -906,6 +1110,33 @@ 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[0] = 0;
+       ret = btrfs_subvolid_resolve(r->mnt_fd, root_subvol_path,
+                                    PATH_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;
+
        if (r->dest_dir_chroot) {
                if (chroot(dest_dir_full_path)) {
                        ret = -errno;
@@ -971,17 +1202,8 @@ out:
        }
        free(r->root_path);
        r->root_path = NULL;
-       free(r->write_path);
-       r->write_path = NULL;
-       free(r->full_subvol_path);
-       r->full_subvol_path = NULL;
        r->dest_dir_path = NULL;
        free(dest_dir_full_path);
-       if (r->cur_subvol) {
-               free(r->cur_subvol->path);
-               free(r->cur_subvol);
-               r->cur_subvol = NULL;
-       }
        subvol_uuid_search_finit(&r->sus);
        if (r->mnt_fd != -1) {
                close(r->mnt_fd);
@@ -991,24 +1213,27 @@ out:
                close(r->dest_dir_fd);
                r->dest_dir_fd = -1;
        }
+
        return ret;
 }
 
 int cmd_receive(int argc, char **argv)
 {
        char *tomnt = NULL;
-       char *fromfile = NULL;
-       char *realmnt = NULL;
+       char fromfile[PATH_MAX];
+       char realmnt[PATH_MAX];
        struct btrfs_receive r;
        int receive_fd = fileno(stdin);
        u64 max_errors = 1;
-       int ret;
+       int ret = 0;
 
        memset(&r, 0, sizeof(r));
        r.mnt_fd = -1;
        r.write_fd = -1;
        r.dest_dir_fd = -1;
        r.dest_dir_chroot = 0;
+       realmnt[0] = 0;
+       fromfile[0] = 0;
 
        while (1) {
                int c;
@@ -1027,7 +1252,13 @@ int cmd_receive(int argc, char **argv)
                        g_verbose++;
                        break;
                case 'f':
-                       fromfile = optarg;
+                       if (arg_copy_path(fromfile, optarg, sizeof(fromfile))) {
+                               fprintf(stderr,
+                                   "ERROR: input file path too long (%zu)\n",
+                                   strlen(optarg));
+                               ret = 1;
+                               goto out;
+                       }
                        break;
                case 'e':
                        r.honor_end_cmd = 1;
@@ -1039,10 +1270,12 @@ int cmd_receive(int argc, char **argv)
                        max_errors = arg_strtou64(optarg);
                        break;
                case 'm':
-                       realmnt = strdup(optarg);
-                       if (!realmnt) {
-                               fprintf(stderr, "ERROR: couldn't allocate realmnt.\n");
-                               return 1;
+                       if (arg_copy_path(realmnt, optarg, sizeof(realmnt))) {
+                               fprintf(stderr,
+                                   "ERROR: mount point path too long (%zu)\n",
+                                   strlen(optarg));
+                               ret = 1;
+                               goto out;
                        }
                        break;
                case '?':
@@ -1057,16 +1290,18 @@ int cmd_receive(int argc, char **argv)
 
        tomnt = argv[optind];
 
-       if (fromfile) {
+       if (fromfile[0]) {
                receive_fd = open(fromfile, O_RDONLY | O_NOATIME);
                if (receive_fd < 0) {
                        fprintf(stderr, "ERROR: failed to open %s\n", fromfile);
-                       return 1;
+                       goto out;
                }
        }
 
        ret = do_receive(&r, tomnt, realmnt, receive_fd, max_errors);
 
+out:
+
        return !!ret;
 }