btrfs-progs: Beautify owner when printing leaf/nodes
[platform/upstream/btrfs-progs.git] / cmds-receive.c
index 8908f8f..68123a3 100644 (file)
@@ -17,6 +17,7 @@
  */
 
 #include "kerncompat.h"
  */
 
 #include "kerncompat.h"
+#include "androidcompat.h"
 
 #include <unistd.h>
 #include <stdint.h>
 
 #include <unistd.h>
 #include <stdint.h>
 #include <pthread.h>
 #include <math.h>
 #include <ftw.h>
 #include <pthread.h>
 #include <math.h>
 #include <ftw.h>
-#include <wait.h>
+#include <sys/wait.h>
 #include <assert.h>
 #include <getopt.h>
 #include <assert.h>
 #include <getopt.h>
+#include <limits.h>
 
 #include <sys/stat.h>
 #include <sys/types.h>
 
 #include <sys/stat.h>
 #include <sys/types.h>
@@ -47,6 +49,8 @@
 #include "send.h"
 #include "send-stream.h"
 #include "send-utils.h"
 #include "send.h"
 #include "send-stream.h"
 #include "send-utils.h"
+#include "send-dump.h"
+#include "help.h"
 
 static int g_verbose = 0;
 
 
 static int g_verbose = 0;
 
@@ -56,15 +60,20 @@ struct btrfs_receive
        int dest_dir_fd;
 
        int write_fd;
        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 *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;
        char *full_root_path;
        int dest_dir_chroot;
 
        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;
 
 
        struct subvol_uuid_search sus;
 
@@ -79,7 +88,7 @@ struct btrfs_receive
        int cached_capabilities_len;
 };
 
        int cached_capabilities_len;
 };
 
-static int finish_subvol(struct btrfs_receive *r)
+static int finish_subvol(struct btrfs_receive *rctx)
 {
        int ret;
        int subvol_fd = -1;
 {
        int ret;
        int subvol_fd = -1;
@@ -87,21 +96,21 @@ static int finish_subvol(struct btrfs_receive *r)
        char uuid_str[BTRFS_UUID_UNPARSED_SIZE];
        u64 flags;
 
        char uuid_str[BTRFS_UUID_UNPARSED_SIZE];
        u64 flags;
 
-       if (r->cur_subvol.path == NULL)
+       if (rctx->cur_subvol_path[0] == 0)
                return 0;
 
                return 0;
 
-       subvol_fd = openat(r->mnt_fd, r->cur_subvol.path,
-                       O_RDONLY | O_NOATIME);
+       subvol_fd = openat(rctx->mnt_fd, rctx->cur_subvol_path,
+                          O_RDONLY | O_NOATIME);
        if (subvol_fd < 0) {
                ret = -errno;
        if (subvol_fd < 0) {
                ret = -errno;
-               fprintf(stderr, "ERROR: open %s failed. %s\n",
-                               r->cur_subvol.path, strerror(-ret));
+               error("cannot open %s: %s",
+                               rctx->cur_subvol_path, strerror(-ret));
                goto out;
        }
 
        memset(&rs_args, 0, sizeof(rs_args));
                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, rctx->cur_subvol.received_uuid, BTRFS_UUID_SIZE);
+       rs_args.stransid = rctx->cur_subvol.stransid;
 
        if (g_verbose >= 1) {
                uuid_unparse((u8*)rs_args.uuid, uuid_str);
 
        if (g_verbose >= 1) {
                uuid_unparse((u8*)rs_args.uuid, uuid_str);
@@ -112,16 +121,16 @@ static int finish_subvol(struct btrfs_receive *r)
        ret = ioctl(subvol_fd, BTRFS_IOC_SET_RECEIVED_SUBVOL, &rs_args);
        if (ret < 0) {
                ret = -errno;
        ret = ioctl(subvol_fd, BTRFS_IOC_SET_RECEIVED_SUBVOL, &rs_args);
        if (ret < 0) {
                ret = -errno;
-               fprintf(stderr, "ERROR: BTRFS_IOC_SET_RECEIVED_SUBVOL failed. %s\n",
+               error("ioctl BTRFS_IOC_SET_RECEIVED_SUBVOL failed: %s",
                                strerror(-ret));
                goto out;
        }
                                strerror(-ret));
                goto out;
        }
-       r->cur_subvol.rtransid = rs_args.rtransid;
+       rctx->cur_subvol.rtransid = rs_args.rtransid;
 
        ret = ioctl(subvol_fd, BTRFS_IOC_SUBVOL_GETFLAGS, &flags);
        if (ret < 0) {
                ret = -errno;
 
        ret = ioctl(subvol_fd, BTRFS_IOC_SUBVOL_GETFLAGS, &flags);
        if (ret < 0) {
                ret = -errno;
-               fprintf(stderr, "ERROR: BTRFS_IOC_SUBVOL_GETFLAGS failed. %s\n",
+               error("ioctl BTRFS_IOC_SUBVOL_GETFLAGS failed: %s",
                                strerror(-ret));
                goto out;
        }
                                strerror(-ret));
                goto out;
        }
@@ -131,17 +140,16 @@ static int finish_subvol(struct btrfs_receive *r)
        ret = ioctl(subvol_fd, BTRFS_IOC_SUBVOL_SETFLAGS, &flags);
        if (ret < 0) {
                ret = -errno;
        ret = ioctl(subvol_fd, BTRFS_IOC_SUBVOL_SETFLAGS, &flags);
        if (ret < 0) {
                ret = -errno;
-               fprintf(stderr, "ERROR: failed to make subvolume read only. "
-                               "%s\n", strerror(-ret));
+               error("failed to make subvolume read only: %s",
+                               strerror(-ret));
                goto out;
        }
 
        ret = 0;
 
 out:
                goto out;
        }
 
        ret = 0;
 
 out:
-       if (r->cur_subvol.path) {
-               free(r->cur_subvol.path);
-               r->cur_subvol.path = NULL;
+       if (rctx->cur_subvol_path[0]) {
+               rctx->cur_subvol_path[0] = 0;
        }
        if (subvol_fd != -1)
                close(subvol_fd);
        }
        if (subvol_fd != -1)
                close(subvol_fd);
@@ -152,42 +160,62 @@ static int process_subvol(const char *path, const u8 *uuid, u64 ctransid,
                          void *user)
 {
        int ret;
                          void *user)
 {
        int ret;
-       struct btrfs_receive *r = user;
+       struct btrfs_receive *rctx = user;
        struct btrfs_ioctl_vol_args args_v1;
        char uuid_str[BTRFS_UUID_UNPARSED_SIZE];
 
        struct btrfs_ioctl_vol_args args_v1;
        char uuid_str[BTRFS_UUID_UNPARSED_SIZE];
 
-       ret = finish_subvol(r);
+       ret = finish_subvol(rctx);
        if (ret < 0)
                goto out;
 
        if (ret < 0)
                goto out;
 
-       BUG_ON(r->cur_subvol.path);
+       if (rctx->cur_subvol.path) {
+               error("subvol: another one already started, path ptr: %s",
+                               rctx->cur_subvol.path);
+               ret = -EINVAL;
+               goto out;
+       }
+       if (rctx->cur_subvol_path[0]) {
+               error("subvol: another one already started, path buf: %s",
+                               rctx->cur_subvol.path);
+               ret = -EINVAL;
+               goto out;
+       }
 
 
-       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 (*rctx->dest_dir_path == 0) {
+               strncpy_null(rctx->cur_subvol_path, path);
+       } else {
+               ret = path_cat_out(rctx->cur_subvol_path, rctx->dest_dir_path,
+                                  path);
+               if (ret < 0) {
+                       error("subvol: path invalid: %s", path);
+                       goto out;
+               }
+       }
+       ret = path_cat3_out(rctx->full_subvol_path, rctx->root_path,
+                           rctx->dest_dir_path, path);
+       if (ret < 0) {
+               error("subvol: path invalid: %s", path);
+               goto out;
+       }
 
        fprintf(stderr, "At subvol %s\n", path);
 
 
        fprintf(stderr, "At subvol %s\n", path);
 
-       memcpy(r->cur_subvol.received_uuid, uuid, BTRFS_UUID_SIZE);
-       r->cur_subvol.stransid = ctransid;
+       memcpy(rctx->cur_subvol.received_uuid, uuid, BTRFS_UUID_SIZE);
+       rctx->cur_subvol.stransid = ctransid;
 
        if (g_verbose) {
 
        if (g_verbose) {
-               uuid_unparse((u8*)r->cur_subvol.received_uuid, uuid_str);
+               uuid_unparse((u8*)rctx->cur_subvol.received_uuid, uuid_str);
                fprintf(stderr, "receiving subvol %s uuid=%s, stransid=%llu\n",
                                path, uuid_str,
                fprintf(stderr, "receiving subvol %s uuid=%s, stransid=%llu\n",
                                path, uuid_str,
-                               r->cur_subvol.stransid);
+                               rctx->cur_subvol.stransid);
        }
 
        memset(&args_v1, 0, sizeof(args_v1));
        strncpy_null(args_v1.name, path);
        }
 
        memset(&args_v1, 0, sizeof(args_v1));
        strncpy_null(args_v1.name, path);
-       ret = ioctl(r->dest_dir_fd, BTRFS_IOC_SUBVOL_CREATE, &args_v1);
+       ret = ioctl(rctx->dest_dir_fd, BTRFS_IOC_SUBVOL_CREATE, &args_v1);
        if (ret < 0) {
                ret = -errno;
        if (ret < 0) {
                ret = -errno;
-               fprintf(stderr, "ERROR: creating subvolume %s failed. "
-                               "%s\n", path, strerror(-ret));
+               error("creating subvolume %s failed: %s", path, strerror(-ret));
                goto out;
        }
 
                goto out;
        }
 
@@ -200,34 +228,55 @@ static int process_snapshot(const char *path, const u8 *uuid, u64 ctransid,
                            void *user)
 {
        int ret;
                            void *user)
 {
        int ret;
-       struct btrfs_receive *r = user;
+       struct btrfs_receive *rctx = user;
        char uuid_str[BTRFS_UUID_UNPARSED_SIZE];
        struct btrfs_ioctl_vol_args_v2 args_v2;
        struct subvol_info *parent_subvol = NULL;
 
        char uuid_str[BTRFS_UUID_UNPARSED_SIZE];
        struct btrfs_ioctl_vol_args_v2 args_v2;
        struct subvol_info *parent_subvol = NULL;
 
-       ret = finish_subvol(r);
+       ret = finish_subvol(rctx);
        if (ret < 0)
                goto out;
 
        if (ret < 0)
                goto out;
 
-       BUG_ON(r->cur_subvol.path);
+       if (rctx->cur_subvol.path) {
+               error("snapshot: another one already started, path ptr: %s",
+                               rctx->cur_subvol.path);
+               ret = -EINVAL;
+               goto out;
+       }
+       if (rctx->cur_subvol_path[0]) {
+               error("snapshot: another one already started, path buf: %s",
+                               rctx->cur_subvol.path);
+               ret = -EINVAL;
+               goto out;
+       }
 
 
-       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 (*rctx->dest_dir_path == 0) {
+               strncpy_null(rctx->cur_subvol_path, path);
+       } else {
+               ret = path_cat_out(rctx->cur_subvol_path, rctx->dest_dir_path,
+                                  path);
+               if (ret < 0) {
+                       error("snapshot: path invalid: %s", path);
+                       goto out;
+               }
+       }
+       ret = path_cat3_out(rctx->full_subvol_path, rctx->root_path,
+                           rctx->dest_dir_path, path);
+       if (ret < 0) {
+               error("snapshot: path invalid: %s", path);
+               goto out;
+       }
 
        fprintf(stdout, "At snapshot %s\n", path);
 
 
        fprintf(stdout, "At snapshot %s\n", path);
 
-       memcpy(r->cur_subvol.received_uuid, uuid, BTRFS_UUID_SIZE);
-       r->cur_subvol.stransid = ctransid;
+       memcpy(rctx->cur_subvol.received_uuid, uuid, BTRFS_UUID_SIZE);
+       rctx->cur_subvol.stransid = ctransid;
 
        if (g_verbose) {
 
        if (g_verbose) {
-               uuid_unparse((u8*)r->cur_subvol.received_uuid, uuid_str);
+               uuid_unparse((u8*)rctx->cur_subvol.received_uuid, uuid_str);
                fprintf(stderr, "receiving snapshot %s uuid=%s, "
                                "ctransid=%llu ", path, uuid_str,
                fprintf(stderr, "receiving snapshot %s uuid=%s, "
                                "ctransid=%llu ", path, uuid_str,
-                               r->cur_subvol.stransid);
+                               rctx->cur_subvol.stransid);
                uuid_unparse(parent_uuid, uuid_str);
                fprintf(stderr, "parent_uuid=%s, parent_ctransid=%llu\n",
                                uuid_str, parent_ctransid);
                uuid_unparse(parent_uuid, uuid_str);
                fprintf(stderr, "parent_uuid=%s, parent_ctransid=%llu\n",
                                uuid_str, parent_ctransid);
@@ -236,15 +285,20 @@ static int process_snapshot(const char *path, const u8 *uuid, u64 ctransid,
        memset(&args_v2, 0, sizeof(args_v2));
        strncpy_null(args_v2.name, path);
 
        memset(&args_v2, 0, sizeof(args_v2));
        strncpy_null(args_v2.name, path);
 
-       parent_subvol = subvol_uuid_search(&r->sus, 0, parent_uuid,
-                       parent_ctransid, NULL, subvol_search_by_received_uuid);
-       if (!parent_subvol) {
-               parent_subvol = subvol_uuid_search(&r->sus, 0, parent_uuid,
-                               parent_ctransid, NULL, subvol_search_by_uuid);
+       parent_subvol = subvol_uuid_search(&rctx->sus, 0, parent_uuid,
+                                          parent_ctransid, NULL,
+                                          subvol_search_by_received_uuid);
+       if (IS_ERR_OR_NULL(parent_subvol)) {
+               parent_subvol = subvol_uuid_search(&rctx->sus, 0, parent_uuid,
+                                                  parent_ctransid, NULL,
+                                                  subvol_search_by_uuid);
        }
        }
-       if (!parent_subvol) {
-               ret = -ENOENT;
-               fprintf(stderr, "ERROR: could not find parent subvolume\n");
+       if (IS_ERR_OR_NULL(parent_subvol)) {
+               if (!parent_subvol)
+                       ret = -ENOENT;
+               else
+                       ret = PTR_ERR(parent_subvol);
+               error("cannot find parent subvolume");
                goto out;
        }
 
                goto out;
        }
 
@@ -253,24 +307,24 @@ static int process_snapshot(const char *path, const u8 *uuid, u64 ctransid,
         * subvolume under the root subvolume, so try and adjust the path to be
         * relative to our root path.
         */
         * subvolume under the root subvolume, so try and adjust the path to be
         * relative to our root path.
         */
-       if (r->full_root_path) {
+       if (rctx->full_root_path) {
                size_t root_len;
                size_t sub_len;
 
                size_t root_len;
                size_t sub_len;
 
-               root_len = strlen(r->full_root_path);
+               root_len = strlen(rctx->full_root_path);
                sub_len = strlen(parent_subvol->path);
 
                /* First make sure the parent subvol is actually in our 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");
+               if (strstr(parent_subvol->path, rctx->full_root_path) != parent_subvol->path ||
+                   (sub_len > root_len && parent_subvol->path[root_len] != '/')) {
+                       error(
+               "parent subvol is not reachable from inside the root subvol");
                        ret = -ENOENT;
                        goto out;
                }
 
                if (sub_len == root_len) {
                        ret = -ENOENT;
                        goto out;
                }
 
                if (sub_len == root_len) {
-                       parent_subvol->path[0] = '/';
+                       parent_subvol->path[0] = '.';
                        parent_subvol->path[1] = '\0';
                } else {
                        /*
                        parent_subvol->path[1] = '\0';
                } else {
                        /*
@@ -298,15 +352,15 @@ static int process_snapshot(const char *path, const u8 *uuid, u64 ctransid,
                }
        }*/
 
                }
        }*/
 
-       if (strlen(parent_subvol->path) == 0)
-               args_v2.fd = dup(r->mnt_fd);
+       if (*parent_subvol->path == 0)
+               args_v2.fd = dup(rctx->mnt_fd);
        else
        else
-               args_v2.fd = openat(r->mnt_fd, parent_subvol->path,
-                               O_RDONLY | O_NOATIME);
+               args_v2.fd = openat(rctx->mnt_fd, parent_subvol->path,
+                                   O_RDONLY | O_NOATIME);
        if (args_v2.fd < 0) {
                ret = -errno;
                if (errno != ENOENT)
        if (args_v2.fd < 0) {
                ret = -errno;
                if (errno != ENOENT)
-                       fprintf(stderr, "ERROR: open %s failed. %s\n",
+                       error("cannot open %s: %s",
                                        parent_subvol->path, strerror(-ret));
                else
                        fprintf(stderr,
                                        parent_subvol->path, strerror(-ret));
                else
                        fprintf(stderr,
@@ -317,13 +371,12 @@ static int process_snapshot(const char *path, const u8 *uuid, u64 ctransid,
                goto out;
        }
 
                goto out;
        }
 
-       ret = ioctl(r->dest_dir_fd, BTRFS_IOC_SNAP_CREATE_V2, &args_v2);
+       ret = ioctl(rctx->dest_dir_fd, BTRFS_IOC_SNAP_CREATE_V2, &args_v2);
        close(args_v2.fd);
        if (ret < 0) {
                ret = -errno;
        close(args_v2.fd);
        if (ret < 0) {
                ret = -errno;
-               fprintf(stderr, "ERROR: creating snapshot %s -> %s "
-                               "failed. %s\n", parent_subvol->path,
-                               path, strerror(-ret));
+               error("creating snapshot %s -> %s failed: %s",
+                               parent_subvol->path, path, strerror(-ret));
                goto out;
        }
 
                goto out;
        }
 
@@ -338,8 +391,14 @@ out:
 static int process_mkfile(const char *path, void *user)
 {
        int ret;
 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);
+       struct btrfs_receive *rctx = user;
+       char full_path[PATH_MAX];
+
+       ret = path_cat_out(full_path, rctx->full_subvol_path, path);
+       if (ret < 0) {
+               error("mkfile: path invalid: %s", path);
+               goto out;
+       }
 
        if (g_verbose >= 2)
                fprintf(stderr, "mkfile %s\n", path);
 
        if (g_verbose >= 2)
                fprintf(stderr, "mkfile %s\n", path);
@@ -347,23 +406,27 @@ static int process_mkfile(const char *path, void *user)
        ret = creat(full_path, 0600);
        if (ret < 0) {
                ret = -errno;
        ret = creat(full_path, 0600);
        if (ret < 0) {
                ret = -errno;
-               fprintf(stderr, "ERROR: mkfile %s failed. %s\n", path,
-                               strerror(-ret));
+               error("mkfile %s failed: %s", path, strerror(-ret));
                goto out;
        }
        close(ret);
        ret = 0;
 
 out:
                goto out;
        }
        close(ret);
        ret = 0;
 
 out:
-       free(full_path);
        return ret;
 }
 
 static int process_mkdir(const char *path, void *user)
 {
        int ret;
        return ret;
 }
 
 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);
+       struct btrfs_receive *rctx = user;
+       char full_path[PATH_MAX];
+
+       ret = path_cat_out(full_path, rctx->full_subvol_path, path);
+       if (ret < 0) {
+               error("mkdir: path invalid: %s", path);
+               goto out;
+       }
 
        if (g_verbose >= 2)
                fprintf(stderr, "mkdir %s\n", path);
 
        if (g_verbose >= 2)
                fprintf(stderr, "mkdir %s\n", path);
@@ -371,19 +434,24 @@ static int process_mkdir(const char *path, void *user)
        ret = mkdir(full_path, 0700);
        if (ret < 0) {
                ret = -errno;
        ret = mkdir(full_path, 0700);
        if (ret < 0) {
                ret = -errno;
-               fprintf(stderr, "ERROR: mkdir %s failed. %s\n", path,
-                               strerror(-ret));
+               error("mkdir %s failed: %s", path, strerror(-ret));
        }
 
        }
 
-       free(full_path);
+out:
        return ret;
 }
 
 static int process_mknod(const char *path, u64 mode, u64 dev, void *user)
 {
        int ret;
        return ret;
 }
 
 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);
+       struct btrfs_receive *rctx = user;
+       char full_path[PATH_MAX];
+
+       ret = path_cat_out(full_path, rctx->full_subvol_path, path);
+       if (ret < 0) {
+               error("mknod: path invalid: %s", path);
+               goto out;
+       }
 
        if (g_verbose >= 2)
                fprintf(stderr, "mknod %s mode=%llu, dev=%llu\n",
 
        if (g_verbose >= 2)
                fprintf(stderr, "mknod %s mode=%llu, dev=%llu\n",
@@ -392,19 +460,24 @@ static int process_mknod(const char *path, u64 mode, u64 dev, void *user)
        ret = mknod(full_path, mode & S_IFMT, dev);
        if (ret < 0) {
                ret = -errno;
        ret = mknod(full_path, mode & S_IFMT, dev);
        if (ret < 0) {
                ret = -errno;
-               fprintf(stderr, "ERROR: mknod %s failed. %s\n", path,
-                               strerror(-ret));
+               error("mknod %s failed: %s", path, strerror(-ret));
        }
 
        }
 
-       free(full_path);
+out:
        return ret;
 }
 
 static int process_mkfifo(const char *path, void *user)
 {
        int ret;
        return ret;
 }
 
 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);
+       struct btrfs_receive *rctx = user;
+       char full_path[PATH_MAX];
+
+       ret = path_cat_out(full_path, rctx->full_subvol_path, path);
+       if (ret < 0) {
+               error("mkfifo: path invalid: %s", path);
+               goto out;
+       }
 
        if (g_verbose >= 2)
                fprintf(stderr, "mkfifo %s\n", path);
 
        if (g_verbose >= 2)
                fprintf(stderr, "mkfifo %s\n", path);
@@ -412,19 +485,24 @@ static int process_mkfifo(const char *path, void *user)
        ret = mkfifo(full_path, 0600);
        if (ret < 0) {
                ret = -errno;
        ret = mkfifo(full_path, 0600);
        if (ret < 0) {
                ret = -errno;
-               fprintf(stderr, "ERROR: mkfifo %s failed. %s\n", path,
-                               strerror(-ret));
+               error("mkfifo %s failed: %s", path, strerror(-ret));
        }
 
        }
 
-       free(full_path);
+out:
        return ret;
 }
 
 static int process_mksock(const char *path, void *user)
 {
        int ret;
        return ret;
 }
 
 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);
+       struct btrfs_receive *rctx = user;
+       char full_path[PATH_MAX];
+
+       ret = path_cat_out(full_path, rctx->full_subvol_path, path);
+       if (ret < 0) {
+               error("mksock: path invalid: %s", path);
+               goto out;
+       }
 
        if (g_verbose >= 2)
                fprintf(stderr, "mksock %s\n", path);
 
        if (g_verbose >= 2)
                fprintf(stderr, "mksock %s\n", path);
@@ -432,19 +510,24 @@ static int process_mksock(const char *path, void *user)
        ret = mknod(full_path, 0600 | S_IFSOCK, 0);
        if (ret < 0) {
                ret = -errno;
        ret = mknod(full_path, 0600 | S_IFSOCK, 0);
        if (ret < 0) {
                ret = -errno;
-               fprintf(stderr, "ERROR: mknod %s failed. %s\n", path,
-                               strerror(-ret));
+               error("mknod %s failed: %s", path, strerror(-ret));
        }
 
        }
 
-       free(full_path);
+out:
        return ret;
 }
 
 static int process_symlink(const char *path, const char *lnk, void *user)
 {
        int ret;
        return ret;
 }
 
 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);
+       struct btrfs_receive *rctx = user;
+       char full_path[PATH_MAX];
+
+       ret = path_cat_out(full_path, rctx->full_subvol_path, path);
+       if (ret < 0) {
+               error("symlink: path invalid: %s", path);
+               goto out;
+       }
 
        if (g_verbose >= 2)
                fprintf(stderr, "symlink %s -> %s\n", path, lnk);
 
        if (g_verbose >= 2)
                fprintf(stderr, "symlink %s -> %s\n", path, lnk);
@@ -452,20 +535,32 @@ static int process_symlink(const char *path, const char *lnk, void *user)
        ret = symlink(lnk, full_path);
        if (ret < 0) {
                ret = -errno;
        ret = symlink(lnk, full_path);
        if (ret < 0) {
                ret = -errno;
-               fprintf(stderr, "ERROR: symlink %s -> %s failed. %s\n", path,
+               error("symlink %s -> %s failed: %s", path,
                                lnk, strerror(-ret));
        }
 
                                lnk, strerror(-ret));
        }
 
-       free(full_path);
+out:
        return ret;
 }
 
 static int process_rename(const char *from, const char *to, void *user)
 {
        int ret;
        return ret;
 }
 
 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);
+       struct btrfs_receive *rctx = user;
+       char full_from[PATH_MAX];
+       char full_to[PATH_MAX];
+
+       ret = path_cat_out(full_from, rctx->full_subvol_path, from);
+       if (ret < 0) {
+               error("rename: source path invalid: %s", from);
+               goto out;
+       }
+
+       ret = path_cat_out(full_to, rctx->full_subvol_path, to);
+       if (ret < 0) {
+               error("rename: target path invalid: %s", to);
+               goto out;
+       }
 
        if (g_verbose >= 2)
                fprintf(stderr, "rename %s -> %s\n", from, to);
 
        if (g_verbose >= 2)
                fprintf(stderr, "rename %s -> %s\n", from, to);
@@ -473,21 +568,32 @@ static int process_rename(const char *from, const char *to, void *user)
        ret = rename(full_from, full_to);
        if (ret < 0) {
                ret = -errno;
        ret = rename(full_from, full_to);
        if (ret < 0) {
                ret = -errno;
-               fprintf(stderr, "ERROR: rename %s -> %s failed. %s\n", from,
+               error("rename %s -> %s failed: %s", from,
                                to, strerror(-ret));
        }
 
                                to, strerror(-ret));
        }
 
-       free(full_from);
-       free(full_to);
+out:
        return ret;
 }
 
 static int process_link(const char *path, const char *lnk, void *user)
 {
        int ret;
        return ret;
 }
 
 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);
+       struct btrfs_receive *rctx = user;
+       char full_path[PATH_MAX];
+       char full_link_path[PATH_MAX];
+
+       ret = path_cat_out(full_path, rctx->full_subvol_path, path);
+       if (ret < 0) {
+               error("link: source path invalid: %s", full_path);
+               goto out;
+       }
+
+       ret = path_cat_out(full_link_path, rctx->full_subvol_path, lnk);
+       if (ret < 0) {
+               error("link: target path invalid: %s", full_link_path);
+               goto out;
+       }
 
        if (g_verbose >= 2)
                fprintf(stderr, "link %s -> %s\n", path, lnk);
 
        if (g_verbose >= 2)
                fprintf(stderr, "link %s -> %s\n", path, lnk);
@@ -495,12 +601,10 @@ static int process_link(const char *path, const char *lnk, void *user)
        ret = link(full_link_path, full_path);
        if (ret < 0) {
                ret = -errno;
        ret = link(full_link_path, full_path);
        if (ret < 0) {
                ret = -errno;
-               fprintf(stderr, "ERROR: link %s -> %s failed. %s\n", path,
-                               lnk, strerror(-ret));
+               error("link %s -> %s failed: %s", path, lnk, strerror(-ret));
        }
 
        }
 
-       free(full_path);
-       free(full_link_path);
+out:
        return ret;
 }
 
        return ret;
 }
 
@@ -508,8 +612,14 @@ static int process_link(const char *path, const char *lnk, void *user)
 static int process_unlink(const char *path, void *user)
 {
        int ret;
 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);
+       struct btrfs_receive *rctx = user;
+       char full_path[PATH_MAX];
+
+       ret = path_cat_out(full_path, rctx->full_subvol_path, path);
+       if (ret < 0) {
+               error("unlink: path invalid: %s", path);
+               goto out;
+       }
 
        if (g_verbose >= 2)
                fprintf(stderr, "unlink %s\n", path);
 
        if (g_verbose >= 2)
                fprintf(stderr, "unlink %s\n", path);
@@ -517,19 +627,24 @@ static int process_unlink(const char *path, void *user)
        ret = unlink(full_path);
        if (ret < 0) {
                ret = -errno;
        ret = unlink(full_path);
        if (ret < 0) {
                ret = -errno;
-               fprintf(stderr, "ERROR: unlink %s failed. %s\n", path,
-                               strerror(-ret));
+               error("unlink %s failed. %s", path, strerror(-ret));
        }
 
        }
 
-       free(full_path);
+out:
        return ret;
 }
 
 static int process_rmdir(const char *path, void *user)
 {
        int ret;
        return ret;
 }
 
 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);
+       struct btrfs_receive *rctx = user;
+       char full_path[PATH_MAX];
+
+       ret = path_cat_out(full_path, rctx->full_subvol_path, path);
+       if (ret < 0) {
+               error("rmdir: path invalid: %s", path);
+               goto out;
+       }
 
        if (g_verbose >= 2)
                fprintf(stderr, "rmdir %s\n", path);
 
        if (g_verbose >= 2)
                fprintf(stderr, "rmdir %s\n", path);
@@ -537,69 +652,71 @@ static int process_rmdir(const char *path, void *user)
        ret = rmdir(full_path);
        if (ret < 0) {
                ret = -errno;
        ret = rmdir(full_path);
        if (ret < 0) {
                ret = -errno;
-               fprintf(stderr, "ERROR: rmdir %s failed. %s\n", path,
-                               strerror(-ret));
+               error("rmdir %s failed: %s", path, strerror(-ret));
        }
 
        }
 
-       free(full_path);
+out:
        return ret;
 }
 
        return ret;
 }
 
-
-static int open_inode_for_write(struct btrfs_receive *r, const char *path)
+static int open_inode_for_write(struct btrfs_receive *rctx, const char *path)
 {
        int ret = 0;
 
 {
        int ret = 0;
 
-       if (r->write_fd != -1) {
-               if (strcmp(r->write_path, path) == 0)
+       if (rctx->write_fd != -1) {
+               if (strcmp(rctx->write_path, path) == 0)
                        goto out;
                        goto out;
-               close(r->write_fd);
-               r->write_fd = -1;
+               close(rctx->write_fd);
+               rctx->write_fd = -1;
        }
 
        }
 
-       r->write_fd = open(path, O_RDWR);
-       if (r->write_fd < 0) {
+       rctx->write_fd = open(path, O_RDWR);
+       if (rctx->write_fd < 0) {
                ret = -errno;
                ret = -errno;
-               fprintf(stderr, "ERROR: open %s failed. %s\n", path,
-                               strerror(-ret));
+               error("cannot open %s: %s", path, strerror(-ret));
                goto out;
        }
                goto out;
        }
-       free(r->write_path);
-       r->write_path = strdup(path);
+       strncpy_null(rctx->write_path, path);
 
 out:
        return ret;
 }
 
 
 out:
        return ret;
 }
 
-static void close_inode_for_write(struct btrfs_receive *r)
+static void close_inode_for_write(struct btrfs_receive *rctx)
 {
 {
-       if(r->write_fd == -1)
+       if(rctx->write_fd == -1)
                return;
 
                return;
 
-       close(r->write_fd);
-       r->write_fd = -1;
-       r->write_path[0] = 0;
+       close(rctx->write_fd);
+       rctx->write_fd = -1;
+       rctx->write_path[0] = 0;
 }
 
 static int process_write(const char *path, const void *data, u64 offset,
                         u64 len, void *user)
 {
        int ret = 0;
 }
 
 static int process_write(const char *path, const void *data, u64 offset,
                         u64 len, void *user)
 {
        int ret = 0;
-       struct btrfs_receive *r = user;
-       char *full_path = path_cat(r->full_subvol_path, path);
+       struct btrfs_receive *rctx = user;
+       char full_path[PATH_MAX];
        u64 pos = 0;
        int w;
 
        u64 pos = 0;
        int w;
 
-       ret = open_inode_for_write(r, full_path);
+       ret = path_cat_out(full_path, rctx->full_subvol_path, path);
+       if (ret < 0) {
+               error("write: path invalid: %s", path);
+               goto out;
+       }
+
+       ret = open_inode_for_write(rctx, full_path);
        if (ret < 0)
                goto out;
 
        while (pos < len) {
        if (ret < 0)
                goto out;
 
        while (pos < len) {
-               w = pwrite(r->write_fd, (char*)data + pos, len - pos,
+               w = pwrite(rctx->write_fd, (char*)data + pos, len - pos,
                                offset + pos);
                if (w < 0) {
                        ret = -errno;
                                offset + pos);
                if (w < 0) {
                        ret = -errno;
-                       fprintf(stderr, "ERROR: writing to %s failed. %s\n",
+                       error("writing to %s failed: %s",
                                        path, strerror(-ret));
                        goto out;
                }
                                        path, strerror(-ret));
                        goto out;
                }
@@ -607,7 +724,6 @@ static int process_write(const char *path, const void *data, u64 offset,
        }
 
 out:
        }
 
 out:
-       free(full_path);
        return ret;
 }
 
        return ret;
 }
 
@@ -617,28 +733,38 @@ static int process_clone(const char *path, u64 offset, u64 len,
                         void *user)
 {
        int ret;
                         void *user)
 {
        int ret;
-       struct btrfs_receive *r = user;
+       struct btrfs_receive *rctx = user;
        struct btrfs_ioctl_clone_range_args clone_args;
        struct subvol_info *si = NULL;
        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 *subvol_path = NULL;
-       char *full_clone_path = NULL;
+       char full_clone_path[PATH_MAX];
        int clone_fd = -1;
 
        int clone_fd = -1;
 
-       ret = open_inode_for_write(r, full_path);
+       ret = path_cat_out(full_path, rctx->full_subvol_path, path);
+       if (ret < 0) {
+               error("clone: source path invalid: %s", path);
+               goto out;
+       }
+
+       ret = open_inode_for_write(rctx, full_path);
        if (ret < 0)
                goto out;
 
        if (ret < 0)
                goto out;
 
-       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,
+       si = subvol_uuid_search(&rctx->sus, 0, clone_uuid, clone_ctransid,
+                               NULL,
+                               subvol_search_by_received_uuid);
+       if (IS_ERR_OR_NULL(si)) {
+               if (memcmp(clone_uuid, rctx->cur_subvol.received_uuid,
                                BTRFS_UUID_SIZE) == 0) {
                        /* TODO check generation of extent */
                                BTRFS_UUID_SIZE) == 0) {
                        /* TODO check generation of extent */
-                       subvol_path = strdup(r->cur_subvol.path);
+                       subvol_path = strdup(rctx->cur_subvol_path);
                } else {
                } else {
-                       ret = -ENOENT;
-                       fprintf(stderr, "ERROR: did not find source subvol.\n");
+                       if (!si)
+                               ret = -ENOENT;
+                       else
+                               ret = PTR_ERR(si);
+                       error("clone: did not find source subvol");
                        goto out;
                }
        } else {
                        goto out;
                }
        } else {
@@ -657,16 +783,36 @@ static int process_clone(const char *path, u64 offset, u64 len,
                                                r->subvol_parent_name);
                        }
                }*/
                                                r->subvol_parent_name);
                        }
                }*/
-               subvol_path = strdup(si->path);
+
+               /* strip the subvolume that we are receiving to from the start of subvol_path */
+               if (rctx->full_root_path) {
+                       size_t root_len = strlen(rctx->full_root_path);
+                       size_t sub_len = strlen(si->path);
+
+                       if (sub_len > root_len &&
+                           strstr(si->path, rctx->full_root_path) == si->path &&
+                           si->path[root_len] == '/') {
+                               subvol_path = strdup(si->path + root_len + 1);
+                       } else {
+                               error("clone: source subvol path %s unreachable from %s",
+                                       si->path, rctx->full_root_path);
+                               goto out;
+                       }
+               } else {
+                       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) {
+               error("clone: target path invalid: %s", clone_path);
+               goto out;
+       }
 
 
-       clone_fd = openat(r->mnt_fd, full_clone_path, O_RDONLY | O_NOATIME);
+       clone_fd = openat(rctx->mnt_fd, full_clone_path, O_RDONLY | O_NOATIME);
        if (clone_fd < 0) {
                ret = -errno;
        if (clone_fd < 0) {
                ret = -errno;
-               fprintf(stderr, "ERROR: failed to open %s. %s\n",
-                               full_clone_path, strerror(-ret));
+               error("cannot open %s: %s", full_clone_path, strerror(-ret));
                goto out;
        }
 
                goto out;
        }
 
@@ -674,10 +820,10 @@ static int process_clone(const char *path, u64 offset, u64 len,
        clone_args.src_offset = clone_offset;
        clone_args.src_length = len;
        clone_args.dest_offset = offset;
        clone_args.src_offset = clone_offset;
        clone_args.src_length = len;
        clone_args.dest_offset = offset;
-       ret = ioctl(r->write_fd, BTRFS_IOC_CLONE_RANGE, &clone_args);
-       if (ret) {
+       ret = ioctl(rctx->write_fd, BTRFS_IOC_CLONE_RANGE, &clone_args);
+       if (ret < 0) {
                ret = -errno;
                ret = -errno;
-               fprintf(stderr, "ERROR: failed to clone extents to %s\n%s\n",
+               error("failed to clone extents to %s\n%s",
                                path, strerror(-ret));
                goto out;
        }
                                path, strerror(-ret));
                goto out;
        }
@@ -687,8 +833,6 @@ out:
                free(si->path);
                free(si);
        }
                free(si->path);
                free(si);
        }
-       free(full_path);
-       free(full_clone_path);
        free(subvol_path);
        if (clone_fd != -1)
                close(clone_fd);
        free(subvol_path);
        if (clone_fd != -1)
                close(clone_fd);
@@ -700,25 +844,29 @@ static int process_set_xattr(const char *path, const char *name,
                             const void *data, int len, void *user)
 {
        int ret = 0;
                             const void *data, int len, void *user)
 {
        int ret = 0;
-       struct btrfs_receive *r = user;
-       char *full_path = path_cat(r->full_subvol_path, path);
+       struct btrfs_receive *rctx = user;
+       char full_path[PATH_MAX];
+
+       ret = path_cat_out(full_path, rctx->full_subvol_path, path);
+       if (ret < 0) {
+               error("set_xattr: path invalid: %s", path);
+               goto out;
+       }
 
        if (strcmp("security.capability", name) == 0) {
                if (g_verbose >= 3)
                        fprintf(stderr, "set_xattr: cache capabilities\n");
 
        if (strcmp("security.capability", name) == 0) {
                if (g_verbose >= 3)
                        fprintf(stderr, "set_xattr: cache capabilities\n");
-               if (r->cached_capabilities_len)
-                       fprintf(stderr,
-                         "WARNING: capabilities set multiple times per file: %s\n",
+               if (rctx->cached_capabilities_len)
+                       warning("capabilities set multiple times per file: %s",
                                full_path);
                                full_path);
-               if (len > sizeof(r->cached_capabilities)) {
-                       fprintf(stderr,
-                         "ERROR: capabilities encoded to %d bytes, buffer too small\n",
+               if (len > sizeof(rctx->cached_capabilities)) {
+                       error("capabilities encoded to %d bytes, buffer too small",
                                len);
                        ret = -E2BIG;
                        goto out;
                }
                                len);
                        ret = -E2BIG;
                        goto out;
                }
-               r->cached_capabilities_len = len;
-               memcpy(r->cached_capabilities, data, len);
+               rctx->cached_capabilities_len = len;
+               memcpy(rctx->cached_capabilities, data, len);
        }
 
        if (g_verbose >= 2) {
        }
 
        if (g_verbose >= 2) {
@@ -730,21 +878,26 @@ static int process_set_xattr(const char *path, const char *name,
        ret = lsetxattr(full_path, name, data, len, 0);
        if (ret < 0) {
                ret = -errno;
        ret = lsetxattr(full_path, name, data, len, 0);
        if (ret < 0) {
                ret = -errno;
-               fprintf(stderr, "ERROR: lsetxattr %s %s=%.*s failed. %s\n",
+               error("lsetxattr %s %s=%.*s failed: %s",
                                path, name, len, (char*)data, strerror(-ret));
                goto out;
        }
 
 out:
                                path, name, len, (char*)data, strerror(-ret));
                goto out;
        }
 
 out:
-       free(full_path);
        return ret;
 }
 
 static int process_remove_xattr(const char *path, const char *name, void *user)
 {
        int ret = 0;
        return ret;
 }
 
 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);
+       struct btrfs_receive *rctx = user;
+       char full_path[PATH_MAX];
+
+       ret = path_cat_out(full_path, rctx->full_subvol_path, path);
+       if (ret < 0) {
+               error("remove_xattr: path invalid: %s", path);
+               goto out;
+       }
 
        if (g_verbose >= 2) {
                fprintf(stderr, "remove_xattr %s - name=%s\n",
 
        if (g_verbose >= 2) {
                fprintf(stderr, "remove_xattr %s - name=%s\n",
@@ -754,21 +907,26 @@ static int process_remove_xattr(const char *path, const char *name, void *user)
        ret = lremovexattr(full_path, name);
        if (ret < 0) {
                ret = -errno;
        ret = lremovexattr(full_path, name);
        if (ret < 0) {
                ret = -errno;
-               fprintf(stderr, "ERROR: lremovexattr %s %s failed. %s\n",
+               error("lremovexattr %s %s failed: %s",
                                path, name, strerror(-ret));
                goto out;
        }
 
 out:
                                path, name, strerror(-ret));
                goto out;
        }
 
 out:
-       free(full_path);
        return ret;
 }
 
 static int process_truncate(const char *path, u64 size, void *user)
 {
        int ret = 0;
        return ret;
 }
 
 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);
+       struct btrfs_receive *rctx = user;
+       char full_path[PATH_MAX];
+
+       ret = path_cat_out(full_path, rctx->full_subvol_path, path);
+       if (ret < 0) {
+               error("truncate: path invalid: %s", path);
+               goto out;
+       }
 
        if (g_verbose >= 2)
                fprintf(stderr, "truncate %s size=%llu\n", path, size);
 
        if (g_verbose >= 2)
                fprintf(stderr, "truncate %s size=%llu\n", path, size);
@@ -776,21 +934,25 @@ static int process_truncate(const char *path, u64 size, void *user)
        ret = truncate(full_path, size);
        if (ret < 0) {
                ret = -errno;
        ret = truncate(full_path, size);
        if (ret < 0) {
                ret = -errno;
-               fprintf(stderr, "ERROR: truncate %s failed. %s\n",
-                               path, strerror(-ret));
+               error("truncate %s failed: %s", path, strerror(-ret));
                goto out;
        }
 
 out:
                goto out;
        }
 
 out:
-       free(full_path);
        return ret;
 }
 
 static int process_chmod(const char *path, u64 mode, void *user)
 {
        int ret = 0;
        return ret;
 }
 
 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);
+       struct btrfs_receive *rctx = user;
+       char full_path[PATH_MAX];
+
+       ret = path_cat_out(full_path, rctx->full_subvol_path, path);
+       if (ret < 0) {
+               error("chmod: path invalid: %s", path);
+               goto out;
+       }
 
        if (g_verbose >= 2)
                fprintf(stderr, "chmod %s - mode=0%o\n", path, (int)mode);
 
        if (g_verbose >= 2)
                fprintf(stderr, "chmod %s - mode=0%o\n", path, (int)mode);
@@ -798,21 +960,25 @@ static int process_chmod(const char *path, u64 mode, void *user)
        ret = chmod(full_path, mode);
        if (ret < 0) {
                ret = -errno;
        ret = chmod(full_path, mode);
        if (ret < 0) {
                ret = -errno;
-               fprintf(stderr, "ERROR: chmod %s failed. %s\n",
-                               path, strerror(-ret));
+               error("chmod %s failed: %s", path, strerror(-ret));
                goto out;
        }
 
 out:
                goto out;
        }
 
 out:
-       free(full_path);
        return ret;
 }
 
 static int process_chown(const char *path, u64 uid, u64 gid, void *user)
 {
        int ret = 0;
        return ret;
 }
 
 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);
+       struct btrfs_receive *rctx = user;
+       char full_path[PATH_MAX];
+
+       ret = path_cat_out(full_path, rctx->full_subvol_path, path);
+       if (ret < 0) {
+               error("chown: path invalid: %s", path);
+               goto out;
+       }
 
        if (g_verbose >= 2)
                fprintf(stderr, "chown %s - uid=%llu, gid=%llu\n", path,
 
        if (g_verbose >= 2)
                fprintf(stderr, "chown %s - uid=%llu, gid=%llu\n", path,
@@ -821,30 +987,28 @@ static int process_chown(const char *path, u64 uid, u64 gid, void *user)
        ret = lchown(full_path, uid, gid);
        if (ret < 0) {
                ret = -errno;
        ret = lchown(full_path, uid, gid);
        if (ret < 0) {
                ret = -errno;
-               fprintf(stderr, "ERROR: chown %s failed. %s\n",
-                               path, strerror(-ret));
+               error("chown %s failed: %s", path, strerror(-ret));
                goto out;
        }
 
                goto out;
        }
 
-       if (r->cached_capabilities_len) {
+       if (rctx->cached_capabilities_len) {
                if (g_verbose >= 2)
                        fprintf(stderr, "chown: restore capabilities\n");
                ret = lsetxattr(full_path, "security.capability",
                if (g_verbose >= 2)
                        fprintf(stderr, "chown: restore capabilities\n");
                ret = lsetxattr(full_path, "security.capability",
-                               r->cached_capabilities,
-                               r->cached_capabilities_len, 0);
-               memset(r->cached_capabilities, 0,
-                               sizeof(r->cached_capabilities));
-               r->cached_capabilities_len = 0;
+                               rctx->cached_capabilities,
+                               rctx->cached_capabilities_len, 0);
+               memset(rctx->cached_capabilities, 0,
+                               sizeof(rctx->cached_capabilities));
+               rctx->cached_capabilities_len = 0;
                if (ret < 0) {
                        ret = -errno;
                if (ret < 0) {
                        ret = -errno;
-                       fprintf(stderr, "ERROR: restoring capabilities %s: %s\n",
+                       error("restoring capabilities %s: %s",
                                        path, strerror(-ret));
                        goto out;
                }
        }
 
 out:
                                        path, strerror(-ret));
                        goto out;
                }
        }
 
 out:
-       free(full_path);
        return ret;
 }
 
        return ret;
 }
 
@@ -853,10 +1017,16 @@ static int process_utimes(const char *path, struct timespec *at,
                          void *user)
 {
        int ret = 0;
                          void *user)
 {
        int ret = 0;
-       struct btrfs_receive *r = user;
-       char *full_path = path_cat(r->full_subvol_path, path);
+       struct btrfs_receive *rctx = user;
+       char full_path[PATH_MAX];
        struct timespec tv[2];
 
        struct timespec tv[2];
 
+       ret = path_cat_out(full_path, rctx->full_subvol_path, path);
+       if (ret < 0) {
+               error("utimes: path invalid: %s", path);
+               goto out;
+       }
+
        if (g_verbose >= 2)
                fprintf(stderr, "utimes %s\n", path);
 
        if (g_verbose >= 2)
                fprintf(stderr, "utimes %s\n", path);
 
@@ -865,13 +1035,12 @@ static int process_utimes(const char *path, struct timespec *at,
        ret = utimensat(AT_FDCWD, full_path, tv, AT_SYMLINK_NOFOLLOW);
        if (ret < 0) {
                ret = -errno;
        ret = utimensat(AT_FDCWD, full_path, tv, AT_SYMLINK_NOFOLLOW);
        if (ret < 0) {
                ret = -errno;
-               fprintf(stderr, "ERROR: utimes %s failed. %s\n",
+               error("utimes %s failed: %s",
                                path, strerror(-ret));
                goto out;
        }
 
 out:
                                path, strerror(-ret));
                goto out;
        }
 
 out:
-       free(full_path);
        return ret;
 }
 
        return ret;
 }
 
@@ -914,55 +1083,51 @@ static struct btrfs_send_ops send_ops = {
        .update_extent = process_update_extent,
 };
 
        .update_extent = process_update_extent,
 };
 
-static int do_receive(struct btrfs_receive *r, const char *tomnt,
+static int do_receive(struct btrfs_receive *rctx, const char *tomnt,
                      char *realmnt, int r_fd, u64 max_errors)
 {
        u64 subvol_id;
        int ret;
        char *dest_dir_full_path;
                      char *realmnt, int r_fd, u64 max_errors)
 {
        u64 subvol_id;
        int ret;
        char *dest_dir_full_path;
-       char *root_subvol_path;
+       char root_subvol_path[PATH_MAX];
        int end = 0;
        int end = 0;
+       int iterations = 0;
 
        dest_dir_full_path = realpath(tomnt, NULL);
        if (!dest_dir_full_path) {
                ret = -errno;
 
        dest_dir_full_path = realpath(tomnt, NULL);
        if (!dest_dir_full_path) {
                ret = -errno;
-               fprintf(stderr, "ERROR: realpath(%s) failed. %s\n", tomnt,
-                       strerror(-ret));
+               error("realpath(%s) failed: %s", tomnt, strerror(-ret));
                goto out;
        }
                goto out;
        }
-       r->dest_dir_fd = open(dest_dir_full_path, O_RDONLY | O_NOATIME);
-       if (r->dest_dir_fd < 0) {
+       rctx->dest_dir_fd = open(dest_dir_full_path, O_RDONLY | O_NOATIME);
+       if (rctx->dest_dir_fd < 0) {
                ret = -errno;
                ret = -errno;
-               fprintf(stderr,
-                       "ERROR: failed to open destination directory %s. %s\n",
+               error("cannot open destination directory %s: %s",
                        dest_dir_full_path, strerror(-ret));
                goto out;
        }
 
        if (realmnt[0]) {
                        dest_dir_full_path, strerror(-ret));
                goto out;
        }
 
        if (realmnt[0]) {
-               r->root_path = realmnt;
+               rctx->root_path = realmnt;
        } else {
        } else {
-               ret = find_mount_root(dest_dir_full_path, &r->root_path);
+               ret = find_mount_root(dest_dir_full_path, &rctx->root_path);
                if (ret < 0) {
                if (ret < 0) {
-                       fprintf(stderr,
-                               "ERROR: failed to determine mount point for %s: %s\n",
+                       error("failed to determine mount point for %s: %s",
                                dest_dir_full_path, strerror(-ret));
                        ret = -EINVAL;
                        goto out;
                }
                if (ret > 0) {
                                dest_dir_full_path, strerror(-ret));
                        ret = -EINVAL;
                        goto out;
                }
                if (ret > 0) {
-                       fprintf(stderr,
-                       "ERROR: %s doesn't belong to btrfs mount point\n",
-                       dest_dir_full_path);
+                       error("%s doesn't belong to btrfs mount point",
+                               dest_dir_full_path);
                        ret = -EINVAL;
                        goto out;
                }
        }
                        ret = -EINVAL;
                        goto out;
                }
        }
-       r->mnt_fd = open(r->root_path, O_RDONLY | O_NOATIME);
-       if (r->mnt_fd < 0) {
+       rctx->mnt_fd = open(rctx->root_path, O_RDONLY | O_NOATIME);
+       if (rctx->mnt_fd < 0) {
                ret = -errno;
                ret = -errno;
-               fprintf(stderr, "ERROR: failed to open %s. %s\n", r->root_path,
-                       strerror(-ret));
+               error("cannot open %s: %s", rctx->root_path, strerror(-ret));
                goto out;
        }
 
                goto out;
        }
 
@@ -971,25 +1136,15 @@ static int do_receive(struct btrfs_receive *r, const char *tomnt,
         * subvolume we're sitting in so that we can adjust the paths of any
         * subvols we want to receive in.
         */
         * 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);
+       ret = btrfs_list_get_path_rootid(rctx->mnt_fd, &subvol_id);
+       if (ret)
                goto out;
                goto out;
-       }
-
-       root_subvol_path = malloc(PATH_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,
+       root_subvol_path[0] = 0;
+       ret = btrfs_subvolid_resolve(rctx->mnt_fd, root_subvol_path,
                                     PATH_MAX, subvol_id);
        if (ret) {
                                     PATH_MAX, subvol_id);
        if (ret) {
-               fprintf(stderr, "ERROR: couldn't resolve our subvol path\n");
+               error("cannot resolve our subvol path");
                goto out;
        }
 
                goto out;
        }
 
@@ -997,128 +1152,132 @@ static int do_receive(struct btrfs_receive *r, const char *tomnt,
         * Ok we're inside of a subvol off of the root subvol, we need to
         * actually set full_root_path.
         */
         * 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 (*root_subvol_path)
+               rctx->full_root_path = root_subvol_path;
 
 
-       if (r->dest_dir_chroot) {
+       if (rctx->dest_dir_chroot) {
                if (chroot(dest_dir_full_path)) {
                        ret = -errno;
                if (chroot(dest_dir_full_path)) {
                        ret = -errno;
-                       fprintf(stderr,
-                               "ERROR: failed to chroot to %s, %s\n",
-                               dest_dir_full_path,
-                               strerror(-ret));
+                       error("failed to chroot to %s: %s",
+                               dest_dir_full_path, strerror(-ret));
                        goto out;
                }
                if (chdir("/")) {
                        ret = -errno;
                        goto out;
                }
                if (chdir("/")) {
                        ret = -errno;
-                       fprintf(stderr,
-                               "ERROR: failed to chdir to /, %s\n",
+                       error("failed to chdir to / after chroot: %s",
                                strerror(-ret));
                        goto out;
                }
                fprintf(stderr, "Chroot to %s\n", dest_dir_full_path);
                                strerror(-ret));
                        goto out;
                }
                fprintf(stderr, "Chroot to %s\n", dest_dir_full_path);
-               r->root_path = strdup("/");
-               r->dest_dir_path = r->root_path;
+               rctx->root_path = strdup("/");
+               rctx->dest_dir_path = rctx->root_path;
        } else {
                /*
                 * find_mount_root returns a root_path that is a subpath of
                 * dest_dir_full_path. Now get the other part of root_path,
                 * which is the destination dir relative to root_path.
                 */
        } else {
                /*
                 * find_mount_root returns a root_path that is a subpath of
                 * dest_dir_full_path. Now get the other part of root_path,
                 * which is the destination dir relative to root_path.
                 */
-               r->dest_dir_path = dest_dir_full_path + strlen(r->root_path);
-               while (r->dest_dir_path[0] == '/')
-                       r->dest_dir_path++;
+               rctx->dest_dir_path = dest_dir_full_path + strlen(rctx->root_path);
+               while (rctx->dest_dir_path[0] == '/')
+                       rctx->dest_dir_path++;
        }
 
        }
 
-       ret = subvol_uuid_search_init(r->mnt_fd, &r->sus);
+       ret = subvol_uuid_search_init(rctx->mnt_fd, &rctx->sus);
        if (ret < 0)
                goto out;
 
        while (!end) {
        if (ret < 0)
                goto out;
 
        while (!end) {
-               if (r->cached_capabilities_len) {
+               if (rctx->cached_capabilities_len) {
                        if (g_verbose >= 3)
                                fprintf(stderr, "clear cached capabilities\n");
                        if (g_verbose >= 3)
                                fprintf(stderr, "clear cached capabilities\n");
-                       memset(r->cached_capabilities, 0,
-                                       sizeof(r->cached_capabilities));
-                       r->cached_capabilities_len = 0;
+                       memset(rctx->cached_capabilities, 0,
+                                       sizeof(rctx->cached_capabilities));
+                       rctx->cached_capabilities_len = 0;
                }
 
                }
 
-               ret = btrfs_read_and_process_send_stream(r_fd, &send_ops, r,
-                                                        r->honor_end_cmd,
+               ret = btrfs_read_and_process_send_stream(r_fd, &send_ops,
+                                                        rctx,
+                                                        rctx->honor_end_cmd,
                                                         max_errors);
                                                         max_errors);
-               if (ret < 0)
-                       goto out;
-               if (ret)
+               if (ret < 0) {
+                       if (ret != -ENODATA)
+                               goto out;
+
+                       /* Empty stream is invalid */
+                       if (iterations == 0) {
+                               error("empty stream is not considered valid");
+                               ret = -EINVAL;
+                               goto out;
+                       }
+
+                       ret = 1;
+               }
+               if (ret > 0)
                        end = 1;
 
                        end = 1;
 
-               close_inode_for_write(r);
-               ret = finish_subvol(r);
+               close_inode_for_write(rctx);
+               ret = finish_subvol(rctx);
                if (ret < 0)
                        goto out;
                if (ret < 0)
                        goto out;
+
+               iterations++;
        }
        ret = 0;
 
 out:
        }
        ret = 0;
 
 out:
-       if (r->write_fd != -1) {
-               close(r->write_fd);
-               r->write_fd = -1;
-       }
-       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.path) {
-               free(r->cur_subvol.path);
-               r->cur_subvol.path = NULL;
-       }
-       subvol_uuid_search_finit(&r->sus);
-       if (r->mnt_fd != -1) {
-               close(r->mnt_fd);
-               r->mnt_fd = -1;
+       if (rctx->write_fd != -1) {
+               close(rctx->write_fd);
+               rctx->write_fd = -1;
        }
        }
-       if (r->dest_dir_fd != -1) {
-               close(r->dest_dir_fd);
-               r->dest_dir_fd = -1;
+
+       if (rctx->root_path != realmnt)
+               free(rctx->root_path);
+       rctx->root_path = NULL;
+       rctx->dest_dir_path = NULL;
+       free(dest_dir_full_path);
+       subvol_uuid_search_finit(&rctx->sus);
+       if (rctx->mnt_fd != -1) {
+               close(rctx->mnt_fd);
+               rctx->mnt_fd = -1;
        }
        }
-       if (r->full_root_path) {
-               free(r->full_root_path);
-               r->full_root_path = NULL;
+       if (rctx->dest_dir_fd != -1) {
+               close(rctx->dest_dir_fd);
+               rctx->dest_dir_fd = -1;
        }
        }
+
        return ret;
 }
 
 int cmd_receive(int argc, char **argv)
 {
        char *tomnt = NULL;
        return ret;
 }
 
 int cmd_receive(int argc, char **argv)
 {
        char *tomnt = NULL;
-       char *fromfile = NULL;
+       char fromfile[PATH_MAX];
        char realmnt[PATH_MAX];
        char realmnt[PATH_MAX];
-       struct btrfs_receive r;
+       struct btrfs_receive rctx;
        int receive_fd = fileno(stdin);
        u64 max_errors = 1;
        int receive_fd = fileno(stdin);
        u64 max_errors = 1;
+       int dump = 0;
        int ret = 0;
 
        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;
+       memset(&rctx, 0, sizeof(rctx));
+       rctx.mnt_fd = -1;
+       rctx.write_fd = -1;
+       rctx.dest_dir_fd = -1;
+       rctx.dest_dir_chroot = 0;
        realmnt[0] = 0;
        realmnt[0] = 0;
+       fromfile[0] = 0;
 
        while (1) {
                int c;
 
        while (1) {
                int c;
+               enum { GETOPT_VAL_DUMP = 257 };
                static const struct option long_opts[] = {
                        { "max-errors", required_argument, NULL, 'E' },
                        { "chroot", no_argument, NULL, 'C' },
                static const struct option long_opts[] = {
                        { "max-errors", required_argument, NULL, 'E' },
                        { "chroot", no_argument, NULL, 'C' },
+                       { "dump", no_argument, NULL, GETOPT_VAL_DUMP },
                        { NULL, 0, NULL, 0 }
                };
 
                        { NULL, 0, NULL, 0 }
                };
 
-               c = getopt_long(argc, argv, "Cevf:m:", long_opts, NULL);
+               c = getopt_long(argc, argv, "Cevf:m:E:", long_opts, NULL);
                if (c < 0)
                        break;
 
                if (c < 0)
                        break;
 
@@ -1127,80 +1286,105 @@ int cmd_receive(int argc, char **argv)
                        g_verbose++;
                        break;
                case 'f':
                        g_verbose++;
                        break;
                case 'f':
-                       fromfile = optarg;
+                       if (arg_copy_path(fromfile, optarg, sizeof(fromfile))) {
+                               error("input file path too long (%zu)",
+                                       strlen(optarg));
+                               ret = 1;
+                               goto out;
+                       }
                        break;
                case 'e':
                        break;
                case 'e':
-                       r.honor_end_cmd = 1;
+                       rctx.honor_end_cmd = 1;
                        break;
                case 'C':
                        break;
                case 'C':
-                       r.dest_dir_chroot = 1;
+                       rctx.dest_dir_chroot = 1;
                        break;
                case 'E':
                        max_errors = arg_strtou64(optarg);
                        break;
                case 'm':
                        if (arg_copy_path(realmnt, optarg, sizeof(realmnt))) {
                        break;
                case 'E':
                        max_errors = arg_strtou64(optarg);
                        break;
                case 'm':
                        if (arg_copy_path(realmnt, optarg, sizeof(realmnt))) {
-                               fprintf(stderr,
-                                   "ERROR: mount point path too long (%zu)\n",
-                                   strlen(optarg));
+                               error("mount point path too long (%zu)",
+                                       strlen(optarg));
                                ret = 1;
                                goto out;
                        }
                        break;
                                ret = 1;
                                goto out;
                        }
                        break;
+               case GETOPT_VAL_DUMP:
+                       dump = 1;
+                       break;
                case '?':
                default:
                case '?':
                default:
-                       fprintf(stderr, "ERROR: receive args invalid.\n");
+                       error("receive args invalid");
                        return 1;
                }
        }
 
                        return 1;
                }
        }
 
-       if (check_argc_exact(argc - optind, 1))
+       if (dump && check_argc_exact(argc - optind, 0))
+               usage(cmd_receive_usage);
+       if (!dump && check_argc_exact(argc - optind, 1))
                usage(cmd_receive_usage);
 
        tomnt = argv[optind];
 
                usage(cmd_receive_usage);
 
        tomnt = argv[optind];
 
-       if (fromfile) {
+       if (fromfile[0]) {
                receive_fd = open(fromfile, O_RDONLY | O_NOATIME);
                if (receive_fd < 0) {
                receive_fd = open(fromfile, O_RDONLY | O_NOATIME);
                if (receive_fd < 0) {
-                       fprintf(stderr, "ERROR: failed to open %s\n", fromfile);
+                       error("cannot open %s: %m", fromfile);
                        goto out;
                }
        }
 
                        goto out;
                }
        }
 
-       ret = do_receive(&r, tomnt, realmnt, receive_fd, max_errors);
+       if (dump) {
+               struct btrfs_dump_send_args dump_args;
+
+               dump_args.root_path[0] = '.';
+               dump_args.root_path[1] = '\0';
+               dump_args.full_subvol_path[0] = '.';
+               dump_args.full_subvol_path[1] = '\0';
+               ret = btrfs_read_and_process_send_stream(receive_fd,
+                               &btrfs_print_send_ops, &dump_args, 0, 0);
+               if (ret < 0)
+                       error("failed to dump the send stream: %s",
+                             strerror(-ret));
+       } else {
+               ret = do_receive(&rctx, tomnt, realmnt, receive_fd, max_errors);
+       }
 
 
+       if (receive_fd != fileno(stdin))
+               close(receive_fd);
 out:
 
        return !!ret;
 }
 
 const char * const cmd_receive_usage[] = {
 out:
 
        return !!ret;
 }
 
 const char * const cmd_receive_usage[] = {
-       "btrfs receive [-ve] [-f <infile>] [--max-errors <N>] <mount>",
-       "Receive subvolumes from stdin.",
+       "btrfs receive [options] <mount>\n"
+       "btrfs receive --dump [options]",
+       "Receive subvolumes from a stream",
        "Receives one or more subvolumes that were previously",
        "sent with btrfs send. The received subvolumes are stored",
        "Receives one or more subvolumes that were previously",
        "sent with btrfs send. The received subvolumes are stored",
-       "into <mount>.",
-       "btrfs receive will fail in case a receiving subvolume",
+       "into MOUNT.",
+       "The receive will fail in case the receiving subvolume",
        "already exists. It will also fail in case a previously",
        "already exists. It will also fail in case a previously",
-       "received subvolume was changed after it was received.",
+       "received subvolume has been changed after it was received.",
        "After receiving a subvolume, it is immediately set to",
        "After receiving a subvolume, it is immediately set to",
-       "read only.\n",
-       "-v               Enable verbose debug output. Each",
-       "                 occurrence of this option increases the",
-       "                 verbose level more.",
-       "-f <infile>      By default, btrfs receive uses stdin",
-       "                 to receive the subvolumes. Use this",
-       "                 option to specify a file to use instead.",
-       "-e               Terminate after receiving an <end cmd>",
-       "                 in the data stream. Without this option,",
-       "                 the receiver terminates only if an error",
-       "                 is recognized or on EOF.",
+       "read-only.",
+       "",
+       "-v               increase verbosity about performed actions",
+       "-f FILE          read the stream from FILE instead of stdin",
+       "-e               terminate after receiving an <end cmd> marker in the stream.",
+       "                 Without this option the receiver side terminates only in case",
+       "                 of an error on end of file.",
        "-C|--chroot      confine the process to <mount> using chroot",
        "-C|--chroot      confine the process to <mount> using chroot",
-       "--max-errors <N> Terminate as soon as N errors happened while",
-       "                 processing commands from the send stream.",
+       "-E|--max-errors NERR",
+       "                 terminate as soon as NERR errors occur while",
+       "                 stream processing commands from the stream.",
        "                 Default value is 1. A value of 0 means no limit.",
        "                 Default value is 1. A value of 0 means no limit.",
-       "-m <mountpoint>  The root mount point of the destination fs.",
-       "                 If you do not have /proc use this to tell us where ",
+       "-m ROOTMOUNT     the root mount point of the destination filesystem.",
+       "                 If /proc is not accessible, use this to tell us where",
        "                 this file system is mounted.",
        "                 this file system is mounted.",
+       "--dump           dump stream metadata, one line per operation,",
+       "                 does not require the MOUNT parameter",
        NULL
 };
        NULL
 };