packaging: Apply ASLR feature
[platform/upstream/btrfs-progs.git] / cmds-receive.c
index fc5094e..68123a3 100644 (file)
  * Boston, MA 021110-1307, USA.
  */
 
-#define _GNU_SOURCE
-#define _POSIX_C_SOURCE 200809
-#define _XOPEN_SOURCE 700
-#define _BSD_SOURCE
-
 #include "kerncompat.h"
+#include "androidcompat.h"
 
 #include <unistd.h>
 #include <stdint.h>
 #include <pthread.h>
 #include <math.h>
 #include <ftw.h>
-#include <wait.h>
+#include <sys/wait.h>
+#include <assert.h>
+#include <getopt.h>
+#include <limits.h>
 
 #include <sys/stat.h>
 #include <sys/types.h>
 #include "commands.h"
 #include "utils.h"
 #include "list.h"
+#include "btrfs-list.h"
 
 #include "send.h"
 #include "send-stream.h"
 #include "send-utils.h"
+#include "send-dump.h"
+#include "help.h"
 
 static int g_verbose = 0;
 
 struct btrfs_receive
 {
        int mnt_fd;
+       int dest_dir_fd;
 
        int write_fd;
-       char *write_path;
+       char write_path[PATH_MAX];
 
        char *root_path;
-       char *full_subvol_path;
-
-       struct subvol_info *cur_subvol;
-       struct subvol_info *parent_subvol;
+       char *dest_dir_path; /* relative to root_path */
+       char full_subvol_path[PATH_MAX];
+       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;
+
+       int honor_end_cmd;
+
+       /*
+        * Buffer to store capabilities from security.capabilities xattr,
+        * usually 20 bytes, but make same room for potentially larger
+        * encodings. Must be set only once per file, denoted by length > 0.
+        */
+       char cached_capabilities[64];
+       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;
        struct btrfs_ioctl_received_subvol_args rs_args;
-       char uuid_str[128];
+       char uuid_str[BTRFS_UUID_UNPARSED_SIZE];
        u64 flags;
 
-       if (r->cur_subvol == NULL)
+       if (rctx->cur_subvol_path[0] == 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;
-               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));
-       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);
@@ -101,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;
-               fprintf(stderr, "ERROR: BTRFS_IOC_SET_RECEIVED_SUBVOL failed. %s\n",
+               error("ioctl BTRFS_IOC_SET_RECEIVED_SUBVOL failed: %s",
                                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;
-               fprintf(stderr, "ERROR: BTRFS_IOC_SUBVOL_GETFLAGS failed. %s\n",
+               error("ioctl BTRFS_IOC_SUBVOL_GETFLAGS failed: %s",
                                strerror(-ret));
                goto out;
        }
@@ -120,16 +140,17 @@ static int finish_subvol(struct btrfs_receive *r)
        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;
        }
 
-       subvol_uuid_search_add(&r->sus, r->cur_subvol);
-       r->cur_subvol = NULL;
        ret = 0;
 
 out:
+       if (rctx->cur_subvol_path[0]) {
+               rctx->cur_subvol_path[0] = 0;
+       }
        if (subvol_fd != -1)
                close(subvol_fd);
        return ret;
@@ -139,39 +160,62 @@ static int process_subvol(const char *path, const u8 *uuid, u64 ctransid,
                          void *user)
 {
        int ret;
-       struct btrfs_receive *r = user;
+       struct btrfs_receive *rctx = user;
        struct btrfs_ioctl_vol_args args_v1;
-       char uuid_str[128];
+       char uuid_str[BTRFS_UUID_UNPARSED_SIZE];
 
-       ret = finish_subvol(r);
+       ret = finish_subvol(rctx);
        if (ret < 0)
                goto out;
 
-       r->cur_subvol = calloc(1, sizeof(*r->cur_subvol));
-       r->parent_subvol = NULL;
+       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;
+       }
 
-       r->cur_subvol->path = strdup(path);
-       r->full_subvol_path = path_cat(r->root_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);
 
-       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) {
-               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,
-                               r->cur_subvol->stransid);
+                               rctx->cur_subvol.stransid);
        }
 
        memset(&args_v1, 0, sizeof(args_v1));
        strncpy_null(args_v1.name, path);
-       ret = ioctl(r->mnt_fd, BTRFS_IOC_SUBVOL_CREATE, &args_v1);
+       ret = ioctl(rctx->dest_dir_fd, BTRFS_IOC_SUBVOL_CREATE, &args_v1);
        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;
        }
 
@@ -184,30 +228,55 @@ static int process_snapshot(const char *path, const u8 *uuid, u64 ctransid,
                            void *user)
 {
        int ret;
-       struct btrfs_receive *r = user;
-       char uuid_str[128];
+       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;
 
-       ret = finish_subvol(r);
+       ret = finish_subvol(rctx);
        if (ret < 0)
                goto out;
 
-       r->cur_subvol = calloc(1, sizeof(*r->cur_subvol));
-       r->parent_subvol = NULL;
+       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;
+       }
 
-       r->cur_subvol->path = strdup(path);
-       r->full_subvol_path = path_cat(r->root_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(stderr, "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) {
-               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,
-                               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);
@@ -216,14 +285,63 @@ 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);
 
-       r->parent_subvol = subvol_uuid_search(&r->sus, 0, parent_uuid,
-                       parent_ctransid, NULL, subvol_search_by_received_uuid);
-       if (!r->parent_subvol) {
-               ret = -ENOENT;
-               fprintf(stderr, "ERROR: could not find parent subvolume\n");
+       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 (IS_ERR_OR_NULL(parent_subvol)) {
+               if (!parent_subvol)
+                       ret = -ENOENT;
+               else
+                       ret = PTR_ERR(parent_subvol);
+               error("cannot find parent subvolume");
                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 (rctx->full_root_path) {
+               size_t root_len;
+               size_t sub_len;
+
+               root_len = strlen(rctx->full_root_path);
+               sub_len = strlen(parent_subvol->path);
+
+               /* First make sure the parent subvol is actually in our path */
+               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) {
+                       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;
@@ -234,195 +352,259 @@ static int process_snapshot(const char *path, const u8 *uuid, u64 ctransid,
                }
        }*/
 
-       args_v2.fd = openat(r->mnt_fd, r->parent_subvol->path,
-                       O_RDONLY | O_NOATIME);
+       if (*parent_subvol->path == 0)
+               args_v2.fd = dup(rctx->mnt_fd);
+       else
+               args_v2.fd = openat(rctx->mnt_fd, parent_subvol->path,
+                                   O_RDONLY | O_NOATIME);
        if (args_v2.fd < 0) {
                ret = -errno;
-               fprintf(stderr, "ERROR: open %s failed. %s\n",
-                               r->parent_subvol->path, strerror(-ret));
+               if (errno != ENOENT)
+                       error("cannot open %s: %s",
+                                       parent_subvol->path, strerror(-ret));
+               else
+                       fprintf(stderr,
+                               "It seems that you have changed your default "
+                               "subvolume or you specify other subvolume to\n"
+                               "mount btrfs, try to remount this btrfs filesystem "
+                               "with fs tree, and run btrfs receive again!\n");
                goto out;
        }
 
-       ret = ioctl(r->mnt_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;
-               fprintf(stderr, "ERROR: creating snapshot %s -> %s "
-                               "failed. %s\n", r->parent_subvol->path,
-                               path, strerror(-ret));
+               error("creating snapshot %s -> %s failed: %s",
+                               parent_subvol->path, path, strerror(-ret));
                goto out;
        }
 
 out:
+       if (parent_subvol) {
+               free(parent_subvol->path);
+               free(parent_subvol);
+       }
        return 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 >= 1)
+       if (g_verbose >= 2)
                fprintf(stderr, "mkfile %s\n", path);
 
        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:
-       free(full_path);
        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 >= 1)
+       if (g_verbose >= 2)
                fprintf(stderr, "mkdir %s\n", path);
 
        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;
-       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];
 
-       if (g_verbose >= 1)
+       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",
                                path, mode, dev);
 
        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;
-       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 >= 1)
+       if (g_verbose >= 2)
                fprintf(stderr, "mkfifo %s\n", path);
 
        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;
-       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 >= 1)
+       if (g_verbose >= 2)
                fprintf(stderr, "mksock %s\n", path);
 
        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;
-       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 >= 1)
+       if (g_verbose >= 2)
                fprintf(stderr, "symlink %s -> %s\n", path, lnk);
 
        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));
        }
 
-       free(full_path);
+out:
        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];
 
-       if (g_verbose >= 1)
+       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);
 
        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));
        }
 
-       free(full_from);
-       free(full_to);
+out:
        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];
 
-       if (g_verbose >= 1)
+       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);
 
        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;
 }
 
@@ -430,103 +612,111 @@ static int process_link(const char *path, const char *lnk, void *user)
 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];
 
-       if (g_verbose >= 1)
+       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);
 
        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;
-       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 >= 1)
+       if (g_verbose >= 2)
                fprintf(stderr, "rmdir %s\n", path);
 
        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;
 }
 
-
-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;
 
-       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;
-               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;
-               fprintf(stderr, "ERROR: open %s failed. %s\n", path,
-                               strerror(-ret));
+               error("cannot open %s: %s", path, strerror(-ret));
                goto out;
        }
-       free(r->write_path);
-       r->write_path = strdup(path);
+       strncpy_null(rctx->write_path, path);
 
 out:
        return ret;
 }
 
-static int close_inode_for_write(struct btrfs_receive *r)
+static void close_inode_for_write(struct btrfs_receive *rctx)
 {
-       int ret = 0;
-
-       if(r->write_fd == -1)
-               goto out;
+       if(rctx->write_fd == -1)
+               return;
 
-       close(r->write_fd);
-       r->write_fd = -1;
-       r->write_path[0] = 0;
-
-out:
-       return ret;
+       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;
-       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;
 
-       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) {
-               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;
-                       fprintf(stderr, "ERROR: writing to %s failed. %s\n",
+                       error("writing to %s failed: %s",
                                        path, strerror(-ret));
                        goto out;
                }
@@ -534,7 +724,6 @@ static int process_write(const char *path, const void *data, u64 offset,
        }
 
 out:
-       free(full_path);
        return ret;
 }
 
@@ -543,29 +732,39 @@ static int process_clone(const char *path, u64 offset, u64 len,
                         const char *clone_path, u64 clone_offset,
                         void *user)
 {
-       int ret = 0;
-       struct btrfs_receive *r = user;
+       int ret;
+       struct btrfs_receive *rctx = 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 = 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;
 
-       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,
-                               BTRFS_FSID_SIZE) == 0) {
+       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 */
-                       subvol_path = strdup(r->cur_subvol->path);
+                       subvol_path = strdup(rctx->cur_subvol_path);
                } 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 {
@@ -584,16 +783,36 @@ static int process_clone(const char *path, u64 offset, u64 len,
                                                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_cat3(r->root_path, 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 = open(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;
-               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;
        }
 
@@ -601,17 +820,19 @@ 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;
-       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;
-               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;
        }
 
 out:
-       free(full_path);
-       free(full_clone_path);
+       if (si) {
+               free(si->path);
+               free(si);
+       }
        free(subvol_path);
        if (clone_fd != -1)
                close(clone_fd);
@@ -623,10 +844,32 @@ static int process_set_xattr(const char *path, const char *name,
                             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];
 
-       if (g_verbose >= 1) {
+       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 (rctx->cached_capabilities_len)
+                       warning("capabilities set multiple times per file: %s",
+                               full_path);
+               if (len > sizeof(rctx->cached_capabilities)) {
+                       error("capabilities encoded to %d bytes, buffer too small",
+                               len);
+                       ret = -E2BIG;
+                       goto out;
+               }
+               rctx->cached_capabilities_len = len;
+               memcpy(rctx->cached_capabilities, data, len);
+       }
+
+       if (g_verbose >= 2) {
                fprintf(stderr, "set_xattr %s - name=%s data_len=%d "
                                "data=%.*s\n", path, name, len,
                                len, (char*)data);
@@ -635,23 +878,28 @@ static int process_set_xattr(const char *path, const char *name,
        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:
-       free(full_path);
        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];
 
-       if (g_verbose >= 1) {
+       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",
                                path, name);
        }
@@ -659,80 +907,108 @@ static int process_remove_xattr(const char *path, const char *name, void *user)
        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:
-       free(full_path);
        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];
 
-       if (g_verbose >= 1)
+       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);
 
        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:
-       free(full_path);
        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 >= 1)
+       if (g_verbose >= 2)
                fprintf(stderr, "chmod %s - mode=0%o\n", path, (int)mode);
 
        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:
-       free(full_path);
        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 >= 1)
+       if (g_verbose >= 2)
                fprintf(stderr, "chown %s - uid=%llu, gid=%llu\n", path,
                                uid, gid);
 
        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;
        }
 
+       if (rctx->cached_capabilities_len) {
+               if (g_verbose >= 2)
+                       fprintf(stderr, "chown: restore capabilities\n");
+               ret = lsetxattr(full_path, "security.capability",
+                               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;
+                       error("restoring capabilities %s: %s",
+                                       path, strerror(-ret));
+                       goto out;
+               }
+       }
+
 out:
-       free(full_path);
        return ret;
 }
 
@@ -741,30 +1017,49 @@ static int process_utimes(const char *path, struct timespec *at,
                          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];
 
-       if (g_verbose >= 1)
+       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);
 
        tv[0] = *at;
        tv[1] = *mt;
-       ret = utimensat(-1, full_path, tv, AT_SYMLINK_NOFOLLOW);
+       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:
-       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;
+}
 
-struct btrfs_send_ops send_ops = {
+static struct btrfs_send_ops send_ops = {
        .subvol = process_subvol,
        .snapshot = process_snapshot,
        .mkfile = process_mkfile,
@@ -785,129 +1080,311 @@ struct btrfs_send_ops send_ops = {
        .chmod = process_chmod,
        .chown = process_chown,
        .utimes = process_utimes,
+       .update_extent = process_update_extent,
 };
 
-int do_receive(struct btrfs_receive *r, const char *tomnt, int r_fd)
+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 root_subvol_path[PATH_MAX];
        int end = 0;
+       int iterations = 0;
 
-       r->root_path = strdup(tomnt);
-       r->mnt_fd = open(tomnt, O_RDONLY | O_NOATIME);
-       if (r->mnt_fd < 0) {
+       dest_dir_full_path = realpath(tomnt, NULL);
+       if (!dest_dir_full_path) {
                ret = -errno;
-               fprintf(stderr, "ERROR: failed to open %s. %s\n", tomnt,
-                               strerror(-ret));
+               error("realpath(%s) failed: %s", tomnt, strerror(-ret));
+               goto out;
+       }
+       rctx->dest_dir_fd = open(dest_dir_full_path, O_RDONLY | O_NOATIME);
+       if (rctx->dest_dir_fd < 0) {
+               ret = -errno;
+               error("cannot open destination directory %s: %s",
+                       dest_dir_full_path, strerror(-ret));
                goto out;
        }
 
-       ret = subvol_uuid_search_init(r->mnt_fd, &r->sus);
-       if (ret < 0)
-               return ret;
+       if (realmnt[0]) {
+               rctx->root_path = realmnt;
+       } else {
+               ret = find_mount_root(dest_dir_full_path, &rctx->root_path);
+               if (ret < 0) {
+                       error("failed to determine mount point for %s: %s",
+                               dest_dir_full_path, strerror(-ret));
+                       ret = -EINVAL;
+                       goto out;
+               }
+               if (ret > 0) {
+                       error("%s doesn't belong to btrfs mount point",
+                               dest_dir_full_path);
+                       ret = -EINVAL;
+                       goto out;
+               }
+       }
+       rctx->mnt_fd = open(rctx->root_path, O_RDONLY | O_NOATIME);
+       if (rctx->mnt_fd < 0) {
+               ret = -errno;
+               error("cannot open %s: %s", rctx->root_path, strerror(-ret));
+               goto out;
+       }
 
-       r->write_fd = -1;
+       /*
+        * 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(rctx->mnt_fd, &subvol_id);
+       if (ret)
+               goto out;
 
-       while (!end) {
-               ret = btrfs_read_and_process_send_stream(r_fd, &send_ops, r);
-               if (ret < 0)
+       root_subvol_path[0] = 0;
+       ret = btrfs_subvolid_resolve(rctx->mnt_fd, root_subvol_path,
+                                    PATH_MAX, subvol_id);
+       if (ret) {
+               error("cannot resolve our subvol path");
+               goto out;
+       }
+
+       /*
+        * Ok we're inside of a subvol off of the root subvol, we need to
+        * actually set full_root_path.
+        */
+       if (*root_subvol_path)
+               rctx->full_root_path = root_subvol_path;
+
+       if (rctx->dest_dir_chroot) {
+               if (chroot(dest_dir_full_path)) {
+                       ret = -errno;
+                       error("failed to chroot to %s: %s",
+                               dest_dir_full_path, strerror(-ret));
+                       goto out;
+               }
+               if (chdir("/")) {
+                       ret = -errno;
+                       error("failed to chdir to / after chroot: %s",
+                               strerror(-ret));
                        goto out;
-               if (ret)
+               }
+               fprintf(stderr, "Chroot to %s\n", dest_dir_full_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.
+                */
+               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(rctx->mnt_fd, &rctx->sus);
+       if (ret < 0)
+               goto out;
+
+       while (!end) {
+               if (rctx->cached_capabilities_len) {
+                       if (g_verbose >= 3)
+                               fprintf(stderr, "clear cached capabilities\n");
+                       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,
+                                                        rctx,
+                                                        rctx->honor_end_cmd,
+                                                        max_errors);
+               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;
 
-               ret = close_inode_for_write(r);
-               if (ret < 0)
-                       goto out;
-               ret = finish_subvol(r);
+               close_inode_for_write(rctx);
+               ret = finish_subvol(rctx);
                if (ret < 0)
                        goto out;
+
+               iterations++;
        }
        ret = 0;
 
 out:
-       subvol_uuid_search_finit(&r->sus);
+       if (rctx->write_fd != -1) {
+               close(rctx->write_fd);
+               rctx->write_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 (rctx->dest_dir_fd != -1) {
+               close(rctx->dest_dir_fd);
+               rctx->dest_dir_fd = -1;
+       }
+
        return ret;
 }
 
-static int do_cmd_receive(int argc, char **argv)
+int cmd_receive(int argc, char **argv)
 {
-       int c;
        char *tomnt = NULL;
-       char *fromfile = NULL;
-       struct btrfs_receive r;
+       char fromfile[PATH_MAX];
+       char realmnt[PATH_MAX];
+       struct btrfs_receive rctx;
        int receive_fd = fileno(stdin);
+       u64 max_errors = 1;
+       int dump = 0;
+       int ret = 0;
 
-       int ret;
-
-       memset(&r, 0, sizeof(r));
+       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;
+       fromfile[0] = 0;
+
+       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' },
+                       { "dump", no_argument, NULL, GETOPT_VAL_DUMP },
+                       { NULL, 0, NULL, 0 }
+               };
+
+               c = getopt_long(argc, argv, "Cevf:m:E:", long_opts, NULL);
+               if (c < 0)
+                       break;
 
-       while ((c = getopt(argc, argv, "vf:")) != -1) {
                switch (c) {
                case 'v':
                        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':
+                       rctx.honor_end_cmd = 1;
+                       break;
+               case 'C':
+                       rctx.dest_dir_chroot = 1;
+                       break;
+               case 'E':
+                       max_errors = arg_strtou64(optarg);
+                       break;
+               case 'm':
+                       if (arg_copy_path(realmnt, optarg, sizeof(realmnt))) {
+                               error("mount point path too long (%zu)",
+                                       strlen(optarg));
+                               ret = 1;
+                               goto out;
+                       }
+                       break;
+               case GETOPT_VAL_DUMP:
+                       dump = 1;
                        break;
                case '?':
                default:
-                       fprintf(stderr, "ERROR: receive args invalid.\n");
+                       error("receive args invalid");
                        return 1;
                }
        }
 
-       if (optind + 1 != argc) {
-               fprintf(stderr, "ERROR: receive needs path to subvolume\n");
-               return 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];
 
-       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 -errno;
+                       error("cannot open %s: %m", fromfile);
+                       goto out;
                }
        }
 
-       ret = do_receive(&r, tomnt, receive_fd);
+       if (dump) {
+               struct btrfs_dump_send_args dump_args;
 
-       return ret;
-}
+               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);
+       }
 
-static const char * const receive_cmd_group_usage[] = {
-       "btrfs receive <command> <args>",
-       NULL
-};
+       if (receive_fd != fileno(stdin))
+               close(receive_fd);
+out:
+
+       return !!ret;
+}
 
 const char * const cmd_receive_usage[] = {
-       "btrfs receive [-v] [-f <infile>] <mount>",
-       "Receive subvolumes from stdin.",
-       "Receives one or more subvolumes that were previously ",
+       "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",
-       "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",
-       "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",
-       "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.",
+       "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",
+       "-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.",
+       "-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.",
+       "--dump           dump stream metadata, one line per operation,",
+       "                 does not require the MOUNT parameter",
        NULL
 };
-
-const struct cmd_group receive_cmd_group = {
-       receive_cmd_group_usage, NULL, {
-               { "receive", do_cmd_receive, cmd_receive_usage, NULL, 0 },
-               { 0, 0, 0, 0, 0 },
-        },
-};
-
-int cmd_receive(int argc, char **argv)
-{
-       return do_cmd_receive(argc, argv);
-}