btrfs-progs/cmds-receive.c

1391 lines
32 KiB
C
Raw Normal View History

/*
* Copyright (C) 2012 Alexander Block. All rights reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License v2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 021110-1307, USA.
*/
#include "kerncompat.h"
#include "androidcompat.h"
#include <unistd.h>
#include <stdint.h>
#include <dirent.h>
#include <fcntl.h>
#include <pthread.h>
#include <math.h>
#include <ftw.h>
#include <sys/wait.h>
Btrfs-progs: use UUID tree for send/receive This commit changes the btrfs send/receive commands to use the UUID tree to map UUIDs to subvolumes, and to use the root tree to map subvolume IDs to paths. Now these tools start fast and are independent on the number of subvolules/snapshot that exist. Before this commit, mapping UUIDs to subvolume IDs was an operation with a high effort. The algorithm even had quadratic effort (based on the number of existing subvolumes). E.g. with 15,000 subvolumes it took much more than 5 minutes on a state of the art XEON CPU to start btrfs send or receive before these tools were able to send or receive the first byte). Even linear effort instead of the current quadratic effort would be too much since it would be a waste. And these data structures to allow mapping UUIDs to subvolume IDs had been created every time a btrfs send/receive instance was started. It is much more efficient to maintain a searchable persistent data structure in the filesystem, one that is updated whenever a subvolume/snapshot is created and deleted, and when the received subvolume UUID is set by the btrfs-receive tool. Therefore kernel code was added that is able to maintain data structures in the filesystem that allow to quickly search for a given UUID and to retrieve data that is assigned to this UUID, like which subvolume ID is related to this UUID. Signed-off-by: Stefan Behrens <sbehrens@giantdisaster.de> Signed-off-by: David Sterba <dsterba@suse.cz> Signed-off-by: Chris Mason <chris.mason@fusionio.com>
2013-06-26 15:17:57 +00:00
#include <assert.h>
#include <getopt.h>
#include <limits.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/xattr.h>
#include <uuid/uuid.h>
#include "ctree.h"
#include "ioctl.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[PATH_MAX];
char *root_path;
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;
Btrfs-progs: btrfs-receive optionally honors the end-cmd A new option is added to btrfs-receive to change the behavior when an <end cmd> is received in the Btrfs send stream. The traditional behavior (which still is the default) is to continue to read the stream until an EOF condition is encountered. If an <end cmd> is received, afterwards either an EOF or a new <stream header> is expected. The new behavior (if the -e option is set on the command line) is to terminate after an <end cmd> is read without the need for an EOF. This allows the stream (e.g. a single TCP stream) to carry additional data or even multiple Btrfs send streams. Old btrfs-send tools used to encode multiple snapshots like this (with 2 snapshots in this example): <stream header> + <sequence of commands> + <end cmd> + <stream header> + <sequence of commands> + <end cmd> + EOF If the new -e option is set, the expected format is like this: <stream header> + <sequence of commands> + <sequence of commands> + <end cmd> The btrfs-send tool is changed in a seperate commit to always use the new format, i.e. to send an <end cmd> only at the end. Note that the currently existing receivers treat <end cmd> only as an indication that a new <stream header> is following. This means, you can just skip the sequence <end cmd> <stream header> without loosing compatibility. As long as an EOF is following, the currently existing receivers handle the new format (if the two new flags are used) exactly as the old one. The goal of changing the semantic of <end cmd> is to be able to use a single stream (one TCP connection) to multiplex a request/response handshake plus Btrfs send streams, all in the same stream. In this case you cannot evaluate an EOF condition as an end of the Btrfs send stream. You need something else, and the <end cmd> is just perfect for this purpose. Signed-off-by: Stefan Behrens <sbehrens@giantdisaster.de>
2013-04-09 17:08:40 +00:00
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 *rctx)
{
int ret;
int subvol_fd = -1;
struct btrfs_ioctl_received_subvol_args rs_args;
char uuid_str[BTRFS_UUID_UNPARSED_SIZE];
u64 flags;
if (rctx->cur_subvol_path[0] == 0)
return 0;
subvol_fd = openat(rctx->mnt_fd, rctx->cur_subvol_path,
O_RDONLY | O_NOATIME);
if (subvol_fd < 0) {
ret = -errno;
error("cannot open %s: %s",
rctx->cur_subvol_path, strerror(-ret));
goto out;
}
memset(&rs_args, 0, sizeof(rs_args));
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);
fprintf(stderr, "BTRFS_IOC_SET_RECEIVED_SUBVOL uuid=%s, "
"stransid=%llu\n", uuid_str, rs_args.stransid);
}
ret = ioctl(subvol_fd, BTRFS_IOC_SET_RECEIVED_SUBVOL, &rs_args);
if (ret < 0) {
ret = -errno;
error("ioctl BTRFS_IOC_SET_RECEIVED_SUBVOL failed: %s",
strerror(-ret));
goto out;
}
rctx->cur_subvol.rtransid = rs_args.rtransid;
ret = ioctl(subvol_fd, BTRFS_IOC_SUBVOL_GETFLAGS, &flags);
if (ret < 0) {
ret = -errno;
error("ioctl BTRFS_IOC_SUBVOL_GETFLAGS failed: %s",
strerror(-ret));
goto out;
}
flags |= BTRFS_SUBVOL_RDONLY;
ret = ioctl(subvol_fd, BTRFS_IOC_SUBVOL_SETFLAGS, &flags);
if (ret < 0) {
ret = -errno;
error("failed to make subvolume read only: %s",
strerror(-ret));
goto out;
}
ret = 0;
out:
if (rctx->cur_subvol_path[0]) {
rctx->cur_subvol_path[0] = 0;
Btrfs-progs: use UUID tree for send/receive This commit changes the btrfs send/receive commands to use the UUID tree to map UUIDs to subvolumes, and to use the root tree to map subvolume IDs to paths. Now these tools start fast and are independent on the number of subvolules/snapshot that exist. Before this commit, mapping UUIDs to subvolume IDs was an operation with a high effort. The algorithm even had quadratic effort (based on the number of existing subvolumes). E.g. with 15,000 subvolumes it took much more than 5 minutes on a state of the art XEON CPU to start btrfs send or receive before these tools were able to send or receive the first byte). Even linear effort instead of the current quadratic effort would be too much since it would be a waste. And these data structures to allow mapping UUIDs to subvolume IDs had been created every time a btrfs send/receive instance was started. It is much more efficient to maintain a searchable persistent data structure in the filesystem, one that is updated whenever a subvolume/snapshot is created and deleted, and when the received subvolume UUID is set by the btrfs-receive tool. Therefore kernel code was added that is able to maintain data structures in the filesystem that allow to quickly search for a given UUID and to retrieve data that is assigned to this UUID, like which subvolume ID is related to this UUID. Signed-off-by: Stefan Behrens <sbehrens@giantdisaster.de> Signed-off-by: David Sterba <dsterba@suse.cz> Signed-off-by: Chris Mason <chris.mason@fusionio.com>
2013-06-26 15:17:57 +00:00
}
if (subvol_fd != -1)
close(subvol_fd);
return ret;
}
static int process_subvol(const char *path, const u8 *uuid, u64 ctransid,
void *user)
{
int ret;
struct btrfs_receive *rctx = user;
struct btrfs_ioctl_vol_args args_v1;
char uuid_str[BTRFS_UUID_UNPARSED_SIZE];
ret = finish_subvol(rctx);
if (ret < 0)
goto out;
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 (*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(rctx->cur_subvol.received_uuid, uuid, BTRFS_UUID_SIZE);
rctx->cur_subvol.stransid = ctransid;
if (g_verbose) {
uuid_unparse((u8*)rctx->cur_subvol.received_uuid, uuid_str);
fprintf(stderr, "receiving subvol %s uuid=%s, stransid=%llu\n",
path, uuid_str,
rctx->cur_subvol.stransid);
}
memset(&args_v1, 0, sizeof(args_v1));
strncpy_null(args_v1.name, path);
ret = ioctl(rctx->dest_dir_fd, BTRFS_IOC_SUBVOL_CREATE, &args_v1);
if (ret < 0) {
ret = -errno;
error("creating subvolume %s failed: %s", path, strerror(-ret));
goto out;
}
out:
return ret;
}
static int process_snapshot(const char *path, const u8 *uuid, u64 ctransid,
const u8 *parent_uuid, u64 parent_ctransid,
void *user)
{
int ret;
struct btrfs_receive *rctx = user;
char uuid_str[BTRFS_UUID_UNPARSED_SIZE];
struct btrfs_ioctl_vol_args_v2 args_v2;
Btrfs-progs: use UUID tree for send/receive This commit changes the btrfs send/receive commands to use the UUID tree to map UUIDs to subvolumes, and to use the root tree to map subvolume IDs to paths. Now these tools start fast and are independent on the number of subvolules/snapshot that exist. Before this commit, mapping UUIDs to subvolume IDs was an operation with a high effort. The algorithm even had quadratic effort (based on the number of existing subvolumes). E.g. with 15,000 subvolumes it took much more than 5 minutes on a state of the art XEON CPU to start btrfs send or receive before these tools were able to send or receive the first byte). Even linear effort instead of the current quadratic effort would be too much since it would be a waste. And these data structures to allow mapping UUIDs to subvolume IDs had been created every time a btrfs send/receive instance was started. It is much more efficient to maintain a searchable persistent data structure in the filesystem, one that is updated whenever a subvolume/snapshot is created and deleted, and when the received subvolume UUID is set by the btrfs-receive tool. Therefore kernel code was added that is able to maintain data structures in the filesystem that allow to quickly search for a given UUID and to retrieve data that is assigned to this UUID, like which subvolume ID is related to this UUID. Signed-off-by: Stefan Behrens <sbehrens@giantdisaster.de> Signed-off-by: David Sterba <dsterba@suse.cz> Signed-off-by: Chris Mason <chris.mason@fusionio.com>
2013-06-26 15:17:57 +00:00
struct subvol_info *parent_subvol = NULL;
ret = finish_subvol(rctx);
if (ret < 0)
goto out;
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 (*rctx->dest_dir_path == 0) {
strncpy_null(rctx->cur_subvol_path, path);
} else {
ret = path_cat_out(rctx->cur_subvol_path, rctx->dest_dir_path,
path);
if (ret < 0) {
error("snapshot: path invalid: %s", path);
goto out;
}
}
ret = path_cat3_out(rctx->full_subvol_path, rctx->root_path,
rctx->dest_dir_path, path);
if (ret < 0) {
error("snapshot: path invalid: %s", path);
goto out;
}
fprintf(stdout, "At snapshot %s\n", path);
memcpy(rctx->cur_subvol.received_uuid, uuid, BTRFS_UUID_SIZE);
rctx->cur_subvol.stransid = ctransid;
if (g_verbose) {
uuid_unparse((u8*)rctx->cur_subvol.received_uuid, uuid_str);
fprintf(stderr, "receiving snapshot %s uuid=%s, "
"ctransid=%llu ", path, uuid_str,
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(&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;
fprintf(stderr, "ERROR: subvolume %s was modified after it was received.\n", r->subvol_parent_name);
goto out;
} else {
fprintf(stderr, "WARNING: subvolume %s was modified after it was received.\n", r->subvol_parent_name);
}
}*/
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;
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(rctx->dest_dir_fd, BTRFS_IOC_SNAP_CREATE_V2, &args_v2);
close(args_v2.fd);
if (ret < 0) {
ret = -errno;
error("creating snapshot %s -> %s failed: %s",
parent_subvol->path, path, strerror(-ret));
goto out;
}
out:
Btrfs-progs: use UUID tree for send/receive This commit changes the btrfs send/receive commands to use the UUID tree to map UUIDs to subvolumes, and to use the root tree to map subvolume IDs to paths. Now these tools start fast and are independent on the number of subvolules/snapshot that exist. Before this commit, mapping UUIDs to subvolume IDs was an operation with a high effort. The algorithm even had quadratic effort (based on the number of existing subvolumes). E.g. with 15,000 subvolumes it took much more than 5 minutes on a state of the art XEON CPU to start btrfs send or receive before these tools were able to send or receive the first byte). Even linear effort instead of the current quadratic effort would be too much since it would be a waste. And these data structures to allow mapping UUIDs to subvolume IDs had been created every time a btrfs send/receive instance was started. It is much more efficient to maintain a searchable persistent data structure in the filesystem, one that is updated whenever a subvolume/snapshot is created and deleted, and when the received subvolume UUID is set by the btrfs-receive tool. Therefore kernel code was added that is able to maintain data structures in the filesystem that allow to quickly search for a given UUID and to retrieve data that is assigned to this UUID, like which subvolume ID is related to this UUID. Signed-off-by: Stefan Behrens <sbehrens@giantdisaster.de> Signed-off-by: David Sterba <dsterba@suse.cz> Signed-off-by: Chris Mason <chris.mason@fusionio.com>
2013-06-26 15:17:57 +00:00
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 *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;
error("mkfile %s failed: %s", path, strerror(-ret));
goto out;
}
close(ret);
ret = 0;
out:
return ret;
}
static int process_mkdir(const char *path, void *user)
{
int ret;
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;
error("mkdir %s failed: %s", path, strerror(-ret));
}
out:
return ret;
}
static int process_mknod(const char *path, u64 mode, u64 dev, void *user)
{
int ret;
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",
path, mode, dev);
ret = mknod(full_path, mode & S_IFMT, dev);
if (ret < 0) {
ret = -errno;
error("mknod %s failed: %s", path, strerror(-ret));
}
out:
return ret;
}
static int process_mkfifo(const char *path, void *user)
{
int ret;
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;
error("mkfifo %s failed: %s", path, strerror(-ret));
}
out:
return ret;
}
static int process_mksock(const char *path, void *user)
{
int ret;
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;
error("mknod %s failed: %s", path, strerror(-ret));
}
out:
return ret;
}
static int process_symlink(const char *path, const char *lnk, void *user)
{
int ret;
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;
error("symlink %s -> %s failed: %s", path,
lnk, strerror(-ret));
}
out:
return ret;
}
static int process_rename(const char *from, const char *to, void *user)
{
int ret;
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;
error("rename %s -> %s failed: %s", from,
to, strerror(-ret));
}
out:
return ret;
}
static int process_link(const char *path, const char *lnk, void *user)
{
int ret;
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;
error("link %s -> %s failed: %s", path, lnk, strerror(-ret));
}
out:
return ret;
}
static int process_unlink(const char *path, void *user)
{
int ret;
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;
error("unlink %s failed. %s", path, strerror(-ret));
}
out:
return ret;
}
static int process_rmdir(const char *path, void *user)
{
int ret;
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;
error("rmdir %s failed: %s", path, strerror(-ret));
}
out:
return ret;
}
static int open_inode_for_write(struct btrfs_receive *rctx, const char *path)
{
int ret = 0;
if (rctx->write_fd != -1) {
if (strcmp(rctx->write_path, path) == 0)
goto out;
close(rctx->write_fd);
rctx->write_fd = -1;
}
rctx->write_fd = open(path, O_RDWR);
if (rctx->write_fd < 0) {
ret = -errno;
error("cannot open %s: %s", path, strerror(-ret));
goto out;
}
strncpy_null(rctx->write_path, path);
out:
return ret;
}
static void close_inode_for_write(struct btrfs_receive *rctx)
{
if(rctx->write_fd == -1)
return;
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 *rctx = user;
char full_path[PATH_MAX];
u64 pos = 0;
int w;
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(rctx->write_fd, (char*)data + pos, len - pos,
offset + pos);
if (w < 0) {
ret = -errno;
error("writing to %s failed: %s",
path, strerror(-ret));
goto out;
}
pos += w;
}
out:
return ret;
}
static int process_clone(const char *path, u64 offset, u64 len,
const u8 *clone_uuid, u64 clone_ctransid,
const char *clone_path, u64 clone_offset,
void *user)
{
Btrfs-progs: use UUID tree for send/receive This commit changes the btrfs send/receive commands to use the UUID tree to map UUIDs to subvolumes, and to use the root tree to map subvolume IDs to paths. Now these tools start fast and are independent on the number of subvolules/snapshot that exist. Before this commit, mapping UUIDs to subvolume IDs was an operation with a high effort. The algorithm even had quadratic effort (based on the number of existing subvolumes). E.g. with 15,000 subvolumes it took much more than 5 minutes on a state of the art XEON CPU to start btrfs send or receive before these tools were able to send or receive the first byte). Even linear effort instead of the current quadratic effort would be too much since it would be a waste. And these data structures to allow mapping UUIDs to subvolume IDs had been created every time a btrfs send/receive instance was started. It is much more efficient to maintain a searchable persistent data structure in the filesystem, one that is updated whenever a subvolume/snapshot is created and deleted, and when the received subvolume UUID is set by the btrfs-receive tool. Therefore kernel code was added that is able to maintain data structures in the filesystem that allow to quickly search for a given UUID and to retrieve data that is assigned to this UUID, like which subvolume ID is related to this UUID. Signed-off-by: Stefan Behrens <sbehrens@giantdisaster.de> Signed-off-by: David Sterba <dsterba@suse.cz> Signed-off-by: Chris Mason <chris.mason@fusionio.com>
2013-06-26 15:17:57 +00:00
int ret;
struct btrfs_receive *rctx = user;
struct btrfs_ioctl_clone_range_args clone_args;
struct subvol_info *si = NULL;
char full_path[PATH_MAX];
char *subvol_path = NULL;
char full_clone_path[PATH_MAX];
int clone_fd = -1;
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(&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(rctx->cur_subvol_path);
} else {
if (!si)
ret = -ENOENT;
else
ret = PTR_ERR(si);
error("clone: did not find source subvol");
goto out;
}
} else {
/*if (rs_args.ctransid > rs_args.rtransid) {
if (!r->force) {
ret = -EINVAL;
fprintf(stderr, "ERROR: subvolume %s was "
"modified after it was "
"received.\n",
r->subvol_parent_name);
goto out;
} else {
fprintf(stderr, "WARNING: subvolume %s was "
"modified after it was "
"received.\n",
r->subvol_parent_name);
}
}*/
/* 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);
}
}
ret = path_cat_out(full_clone_path, subvol_path, clone_path);
if (ret < 0) {
error("clone: target path invalid: %s", clone_path);
goto out;
}
clone_fd = openat(rctx->mnt_fd, full_clone_path, O_RDONLY | O_NOATIME);
if (clone_fd < 0) {
ret = -errno;
error("cannot open %s: %s", full_clone_path, strerror(-ret));
goto out;
}
clone_args.src_fd = clone_fd;
clone_args.src_offset = clone_offset;
clone_args.src_length = len;
clone_args.dest_offset = offset;
ret = ioctl(rctx->write_fd, BTRFS_IOC_CLONE_RANGE, &clone_args);
if (ret < 0) {
ret = -errno;
error("failed to clone extents to %s\n%s",
path, strerror(-ret));
goto out;
}
out:
Btrfs-progs: use UUID tree for send/receive This commit changes the btrfs send/receive commands to use the UUID tree to map UUIDs to subvolumes, and to use the root tree to map subvolume IDs to paths. Now these tools start fast and are independent on the number of subvolules/snapshot that exist. Before this commit, mapping UUIDs to subvolume IDs was an operation with a high effort. The algorithm even had quadratic effort (based on the number of existing subvolumes). E.g. with 15,000 subvolumes it took much more than 5 minutes on a state of the art XEON CPU to start btrfs send or receive before these tools were able to send or receive the first byte). Even linear effort instead of the current quadratic effort would be too much since it would be a waste. And these data structures to allow mapping UUIDs to subvolume IDs had been created every time a btrfs send/receive instance was started. It is much more efficient to maintain a searchable persistent data structure in the filesystem, one that is updated whenever a subvolume/snapshot is created and deleted, and when the received subvolume UUID is set by the btrfs-receive tool. Therefore kernel code was added that is able to maintain data structures in the filesystem that allow to quickly search for a given UUID and to retrieve data that is assigned to this UUID, like which subvolume ID is related to this UUID. Signed-off-by: Stefan Behrens <sbehrens@giantdisaster.de> Signed-off-by: David Sterba <dsterba@suse.cz> Signed-off-by: Chris Mason <chris.mason@fusionio.com>
2013-06-26 15:17:57 +00:00
if (si) {
free(si->path);
free(si);
}
free(subvol_path);
if (clone_fd != -1)
close(clone_fd);
return ret;
}
static int process_set_xattr(const char *path, const char *name,
const void *data, int len, void *user)
{
int ret = 0;
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 "
"data=%.*s\n", path, name, len,
len, (char*)data);
}
ret = lsetxattr(full_path, name, data, len, 0);
if (ret < 0) {
ret = -errno;
error("lsetxattr %s %s=%.*s failed: %s",
path, name, len, (char*)data, strerror(-ret));
goto out;
}
out:
return ret;
}
static int process_remove_xattr(const char *path, const char *name, void *user)
{
int ret = 0;
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",
path, name);
}
ret = lremovexattr(full_path, name);
if (ret < 0) {
ret = -errno;
error("lremovexattr %s %s failed: %s",
path, name, strerror(-ret));
goto out;
}
out:
return ret;
}
static int process_truncate(const char *path, u64 size, void *user)
{
int ret = 0;
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;
error("truncate %s failed: %s", path, strerror(-ret));
goto out;
}
out:
return ret;
}
static int process_chmod(const char *path, u64 mode, void *user)
{
int ret = 0;
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;
error("chmod %s failed: %s", path, strerror(-ret));
goto out;
}
out:
return ret;
}
static int process_chown(const char *path, u64 uid, u64 gid, void *user)
{
int ret = 0;
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,
uid, gid);
ret = lchown(full_path, uid, gid);
if (ret < 0) {
ret = -errno;
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:
return ret;
}
static int process_utimes(const char *path, struct timespec *at,
struct timespec *mt, struct timespec *ct,
void *user)
{
int ret = 0;
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);
tv[0] = *at;
tv[1] = *mt;
ret = utimensat(AT_FDCWD, full_path, tv, AT_SYMLINK_NOFOLLOW);
if (ret < 0) {
ret = -errno;
error("utimes %s failed: %s",
path, strerror(-ret));
goto out;
}
out:
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,
.snapshot = process_snapshot,
.mkfile = process_mkfile,
.mkdir = process_mkdir,
.mknod = process_mknod,
.mkfifo = process_mkfifo,
.mksock = process_mksock,
.symlink = process_symlink,
.rename = process_rename,
.link = process_link,
.unlink = process_unlink,
.rmdir = process_rmdir,
.write = process_write,
.clone = process_clone,
.set_xattr = process_set_xattr,
.remove_xattr = process_remove_xattr,
.truncate = process_truncate,
.chmod = process_chmod,
.chown = process_chown,
.utimes = process_utimes,
.update_extent = process_update_extent,
};
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;
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;
}
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;
}
/*
* 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;
}
/*
* 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;
}
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;
close_inode_for_write(rctx);
ret = finish_subvol(rctx);
if (ret < 0)
goto out;
iterations++;
}
ret = 0;
out:
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;
}
int cmd_receive(int argc, char **argv)
{
char *tomnt = NULL;
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;
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;
switch (c) {
case 'v':
g_verbose++;
break;
case 'f':
if (arg_copy_path(fromfile, optarg, sizeof(fromfile))) {
error("input file path too long (%zu)",
strlen(optarg));
ret = 1;
goto out;
}
break;
Btrfs-progs: btrfs-receive optionally honors the end-cmd A new option is added to btrfs-receive to change the behavior when an <end cmd> is received in the Btrfs send stream. The traditional behavior (which still is the default) is to continue to read the stream until an EOF condition is encountered. If an <end cmd> is received, afterwards either an EOF or a new <stream header> is expected. The new behavior (if the -e option is set on the command line) is to terminate after an <end cmd> is read without the need for an EOF. This allows the stream (e.g. a single TCP stream) to carry additional data or even multiple Btrfs send streams. Old btrfs-send tools used to encode multiple snapshots like this (with 2 snapshots in this example): <stream header> + <sequence of commands> + <end cmd> + <stream header> + <sequence of commands> + <end cmd> + EOF If the new -e option is set, the expected format is like this: <stream header> + <sequence of commands> + <sequence of commands> + <end cmd> The btrfs-send tool is changed in a seperate commit to always use the new format, i.e. to send an <end cmd> only at the end. Note that the currently existing receivers treat <end cmd> only as an indication that a new <stream header> is following. This means, you can just skip the sequence <end cmd> <stream header> without loosing compatibility. As long as an EOF is following, the currently existing receivers handle the new format (if the two new flags are used) exactly as the old one. The goal of changing the semantic of <end cmd> is to be able to use a single stream (one TCP connection) to multiplex a request/response handshake plus Btrfs send streams, all in the same stream. In this case you cannot evaluate an EOF condition as an end of the Btrfs send stream. You need something else, and the <end cmd> is just perfect for this purpose. Signed-off-by: Stefan Behrens <sbehrens@giantdisaster.de>
2013-04-09 17:08:40 +00:00
case 'e':
rctx.honor_end_cmd = 1;
Btrfs-progs: btrfs-receive optionally honors the end-cmd A new option is added to btrfs-receive to change the behavior when an <end cmd> is received in the Btrfs send stream. The traditional behavior (which still is the default) is to continue to read the stream until an EOF condition is encountered. If an <end cmd> is received, afterwards either an EOF or a new <stream header> is expected. The new behavior (if the -e option is set on the command line) is to terminate after an <end cmd> is read without the need for an EOF. This allows the stream (e.g. a single TCP stream) to carry additional data or even multiple Btrfs send streams. Old btrfs-send tools used to encode multiple snapshots like this (with 2 snapshots in this example): <stream header> + <sequence of commands> + <end cmd> + <stream header> + <sequence of commands> + <end cmd> + EOF If the new -e option is set, the expected format is like this: <stream header> + <sequence of commands> + <sequence of commands> + <end cmd> The btrfs-send tool is changed in a seperate commit to always use the new format, i.e. to send an <end cmd> only at the end. Note that the currently existing receivers treat <end cmd> only as an indication that a new <stream header> is following. This means, you can just skip the sequence <end cmd> <stream header> without loosing compatibility. As long as an EOF is following, the currently existing receivers handle the new format (if the two new flags are used) exactly as the old one. The goal of changing the semantic of <end cmd> is to be able to use a single stream (one TCP connection) to multiplex a request/response handshake plus Btrfs send streams, all in the same stream. In this case you cannot evaluate an EOF condition as an end of the Btrfs send stream. You need something else, and the <end cmd> is just perfect for this purpose. Signed-off-by: Stefan Behrens <sbehrens@giantdisaster.de>
2013-04-09 17:08:40 +00:00
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:
error("receive args invalid");
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[0]) {
receive_fd = open(fromfile, O_RDONLY | O_NOATIME);
if (receive_fd < 0) {
error("cannot open %s: %s", fromfile, strerror(errno));
goto out;
}
}
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 [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.",
"The receive will fail in case the receiving subvolume",
"already exists. It will also fail in case a previously",
"received subvolume has been changed after it was received.",
"After receiving a subvolume, it is immediately set to",
"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
};