* 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 "send.h"
#include "send-stream.h"
#include "send-utils.h"
+#include "send-dump.h"
+#include "help.h"
static int g_verbose = 0;
int dest_dir_fd;
int write_fd;
- char *write_path;
+ char write_path[PATH_MAX];
char *root_path;
char *dest_dir_path; /* relative to root_path */
- char *full_subvol_path;
+ char full_subvol_path[PATH_MAX];
+ char *full_root_path;
+ int dest_dir_chroot;
- struct subvol_info *cur_subvol;
+ struct subvol_info cur_subvol;
+ /*
+ * Substitute for cur_subvol::path which is a pointer and we cannot
+ * change it to an array as it's a public API.
+ */
+ char cur_subvol_path[PATH_MAX];
struct subvol_uuid_search sus;
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);
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;
}
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:
- if (r->cur_subvol) {
- free(r->cur_subvol->path);
- free(r->cur_subvol);
- r->cur_subvol = NULL;
+ if (rctx->cur_subvol_path[0]) {
+ rctx->cur_subvol_path[0] = 0;
}
if (subvol_fd != -1)
close(subvol_fd);
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));
+ 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);
- 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->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;
- fprintf(stderr, "ERROR: creating subvolume %s failed. "
- "%s\n", path, strerror(-ret));
+ error("creating subvolume %s failed: %s", path, strerror(-ret));
goto out;
}
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));
+ 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(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);
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) {
- 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;
}
}*/
- args_v2.fd = openat(r->mnt_fd, 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",
- 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->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;
- 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;
}
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);
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 >= 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];
+
+ 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",
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 >= 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 >= 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 >= 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];
+
+ 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];
+
+ 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;
}
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);
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 >= 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(rctx->write_fd == -1)
+ return;
- if(r->write_fd == -1)
- goto out;
-
- 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;
}
}
out:
- free(full_path);
return 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;
- 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,
+ 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 {
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;
}
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;
}
free(si->path);
free(si);
}
- free(full_path);
- free(full_clone_path);
free(subvol_path);
if (clone_fd != -1)
close(clone_fd);
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 (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 "
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];
+
+ 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",
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];
+
+ 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 >= 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 >= 2)
fprintf(stderr, "chown %s - uid=%llu, gid=%llu\n", path,
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;
}
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];
+ 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);
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;
+}
static struct btrfs_send_ops send_ops = {
.subvol = process_subvol,
.chmod = process_chmod,
.chown = process_chown,
.utimes = process_utimes,
+ .update_extent = process_update_extent,
};
-static int do_receive(struct btrfs_receive *r, const char *tomnt, 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;
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;
}
- 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;
- 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;
}
- ret = find_mount_root(dest_dir_full_path, &r->root_path);
- if (ret < 0) {
- ret = -EINVAL;
- fprintf(stderr, "ERROR: failed to determine mount point "
- "for %s\n", dest_dir_full_path);
- goto out;
+ 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;
+ }
}
- 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;
- 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;
+ }
+
+ /*
+ * 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;
+
+ 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;
}
/*
- * 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.
+ * Ok we're inside of a subvol off of the root subvol, we need to
+ * actually set full_root_path.
*/
- r->dest_dir_path = dest_dir_full_path + strlen(r->root_path);
- while (r->dest_dir_path[0] == '/')
- r->dest_dir_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;
+ }
+ 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(r->mnt_fd, &r->sus);
+ ret = subvol_uuid_search_init(rctx->mnt_fd, &rctx->sus);
if (ret < 0)
goto out;
while (!end) {
- ret = btrfs_read_and_process_send_stream(r_fd, &send_ops, r,
- r->honor_end_cmd);
- if (ret < 0)
- goto out;
- if (ret)
+ 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:
- 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) {
- free(r->cur_subvol->path);
- free(r->cur_subvol);
- r->cur_subvol = NULL;
+ if (rctx->write_fd != -1) {
+ close(rctx->write_fd);
+ rctx->write_fd = -1;
}
- subvol_uuid_search_finit(&r->sus);
- if (r->mnt_fd != -1) {
- close(r->mnt_fd);
- r->mnt_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->dest_dir_fd != -1) {
- close(r->dest_dir_fd);
- r->dest_dir_fd = -1;
+ 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)
{
- 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));
- r.mnt_fd = -1;
- r.write_fd = -1;
- r.dest_dir_fd = -1;
+ 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, "evf:")) != -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':
- r.honor_end_cmd = 1;
+ 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 1;
+ error("cannot open %s: %m", fromfile);
+ goto out;
}
}
- ret = do_receive(&r, tomnt, receive_fd);
+ 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[] = {
- "btrfs receive [-ve] [-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.",
- "-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",
+ "-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
};