|
|
dbfe2d |
From caaa44c482a3aae46d8a883cc60b6caf2d47bc75 Mon Sep 17 00:00:00 2001
|
|
|
dbfe2d |
From: Omar Sandoval <osandov@fb.com>
|
|
|
dbfe2d |
Date: Wed, 9 Feb 2022 17:10:59 -0800
|
|
|
dbfe2d |
Subject: [PATCH] btrfs-progs: send: stream v2 ioctl flags
|
|
|
dbfe2d |
|
|
|
dbfe2d |
First, add a --proto option to allow specifying the desired send
|
|
|
dbfe2d |
protocol version. It defaults to one, the original version. In a couple
|
|
|
dbfe2d |
of releases once people are aware that protocol revisions are happening,
|
|
|
dbfe2d |
we can change it to default to zero, which means the latest version
|
|
|
dbfe2d |
supported by the kernel. This is based on Dave Sterba's patch.
|
|
|
dbfe2d |
|
|
|
dbfe2d |
Also add a --compressed-data flag to instruct the kernel to use
|
|
|
dbfe2d |
encoded_write commands for compressed extents. This requires an explicit
|
|
|
dbfe2d |
opt in separate from the protocol version because:
|
|
|
dbfe2d |
|
|
|
dbfe2d |
1. The user may not want compression on the receiving side, or may want
|
|
|
dbfe2d |
a different compression algorithm/level on the receiving side.
|
|
|
dbfe2d |
2. It has a soft requirement for kernel support on the receiving side
|
|
|
dbfe2d |
(btrfs-progs can fall back to decompressing and writing if the kernel
|
|
|
dbfe2d |
doesn't support BTRFS_IOC_ENCODED_WRITE, but the user may not be
|
|
|
dbfe2d |
prepared to pay that CPU cost). Going forward, since it's easier to
|
|
|
dbfe2d |
update progs than the kernel, I think we'll want to make new send
|
|
|
dbfe2d |
features that require kernel support opt-in, whereas anything that
|
|
|
dbfe2d |
only requires a progs update can happen automatically.
|
|
|
dbfe2d |
|
|
|
dbfe2d |
Signed-off-by: Boris Burkov <boris@bur.io>
|
|
|
dbfe2d |
Signed-off-by: Omar Sandoval <osandov@fb.com>
|
|
|
dbfe2d |
---
|
|
|
dbfe2d |
Documentation/btrfs-send.rst | 22 ++++++++
|
|
|
dbfe2d |
cmds/send.c | 100 ++++++++++++++++++++++++++++++++++-
|
|
|
dbfe2d |
ioctl.h | 19 ++++++-
|
|
|
dbfe2d |
kernel-shared/send.h | 2 +-
|
|
|
dbfe2d |
4 files changed, 138 insertions(+), 5 deletions(-)
|
|
|
dbfe2d |
|
|
|
dbfe2d |
diff --git a/Documentation/btrfs-send.rst b/Documentation/btrfs-send.rst
|
|
|
dbfe2d |
index 4526532e..291c537e 100644
|
|
|
dbfe2d |
--- a/Documentation/btrfs-send.rst
|
|
|
dbfe2d |
+++ b/Documentation/btrfs-send.rst
|
|
|
dbfe2d |
@@ -60,6 +60,28 @@ please see section *SUBVOLUME FLAGS* in ``btrfs-subvolume(8)``.
|
|
|
dbfe2d |
used to transfer changes. This mode is faster and is useful to show the
|
|
|
dbfe2d |
differences in metadata.
|
|
|
dbfe2d |
|
|
|
dbfe2d |
+--proto <N>
|
|
|
dbfe2d |
+ use send protocol version N
|
|
|
dbfe2d |
+
|
|
|
dbfe2d |
+ The default is 1, which was the original protocol version. Version 2
|
|
|
dbfe2d |
+ encodes file data slightly more efficiently; it is also required for
|
|
|
dbfe2d |
+ sending compressed data directly (see *--compressed-data*). Version 2
|
|
|
dbfe2d |
+ requires at least btrfs-progs 5.18 on both the sender and receiver and
|
|
|
dbfe2d |
+ at least Linux 5.18 on the sender. Passing 0 means to use the highest
|
|
|
dbfe2d |
+ version supported by the running kernel.
|
|
|
dbfe2d |
+
|
|
|
dbfe2d |
+--compressed-data
|
|
|
dbfe2d |
+ send data that is compressed on the filesystem directly without
|
|
|
dbfe2d |
+ decompressing it
|
|
|
dbfe2d |
+
|
|
|
dbfe2d |
+ If the receiver supports the *BTRFS_IOC_ENCODED_WRITE* ioctl (added in
|
|
|
dbfe2d |
+ Linux 5.18), it can also write it directly without decompressing it.
|
|
|
dbfe2d |
+ Otherwise, the receiver will fall back to decompressing it and writing
|
|
|
dbfe2d |
+ it normally.
|
|
|
dbfe2d |
+
|
|
|
dbfe2d |
+ This requires protocol version 2 or higher. If *--proto* was not used,
|
|
|
dbfe2d |
+ then *--compressed-data* implies *--proto 2*.
|
|
|
dbfe2d |
+
|
|
|
dbfe2d |
-q|--quiet
|
|
|
dbfe2d |
(deprecated) alias for global *-q* option
|
|
|
dbfe2d |
|
|
|
dbfe2d |
diff --git a/cmds/send.c b/cmds/send.c
|
|
|
dbfe2d |
index 087af05c..b1adfeca 100644
|
|
|
dbfe2d |
--- a/cmds/send.c
|
|
|
dbfe2d |
+++ b/cmds/send.c
|
|
|
dbfe2d |
@@ -57,6 +57,8 @@ struct btrfs_send {
|
|
|
dbfe2d |
u64 clone_sources_count;
|
|
|
dbfe2d |
|
|
|
dbfe2d |
char *root_path;
|
|
|
dbfe2d |
+ u32 proto;
|
|
|
dbfe2d |
+ u32 proto_supported;
|
|
|
dbfe2d |
};
|
|
|
dbfe2d |
|
|
|
dbfe2d |
static int get_root_id(struct btrfs_send *sctx, const char *path, u64 *root_id)
|
|
|
dbfe2d |
@@ -259,6 +261,16 @@ static int do_send(struct btrfs_send *send, u64 parent_root_id,
|
|
|
dbfe2d |
memset(&io_send, 0, sizeof(io_send));
|
|
|
dbfe2d |
io_send.send_fd = pipefd[1];
|
|
|
dbfe2d |
send->send_fd = pipefd[0];
|
|
|
dbfe2d |
+ io_send.flags = flags;
|
|
|
dbfe2d |
+
|
|
|
dbfe2d |
+ if (send->proto_supported > 1) {
|
|
|
dbfe2d |
+ /*
|
|
|
dbfe2d |
+ * Versioned stream supported, requesting default or specific
|
|
|
dbfe2d |
+ * number.
|
|
|
dbfe2d |
+ */
|
|
|
dbfe2d |
+ io_send.version = send->proto;
|
|
|
dbfe2d |
+ io_send.flags |= BTRFS_SEND_FLAG_VERSION;
|
|
|
dbfe2d |
+ }
|
|
|
dbfe2d |
|
|
|
dbfe2d |
if (!ret)
|
|
|
dbfe2d |
ret = pthread_create(&t_read, NULL, read_sent_data, send);
|
|
|
dbfe2d |
@@ -269,7 +281,6 @@ static int do_send(struct btrfs_send *send, u64 parent_root_id,
|
|
|
dbfe2d |
goto out;
|
|
|
dbfe2d |
}
|
|
|
dbfe2d |
|
|
|
dbfe2d |
- io_send.flags = flags;
|
|
|
dbfe2d |
io_send.clone_sources = (__u64*)send->clone_sources;
|
|
|
dbfe2d |
io_send.clone_sources_count = send->clone_sources_count;
|
|
|
dbfe2d |
io_send.parent_root = parent_root_id;
|
|
|
dbfe2d |
@@ -421,6 +432,36 @@ static void free_send_info(struct btrfs_send *sctx)
|
|
|
dbfe2d |
sctx->root_path = NULL;
|
|
|
dbfe2d |
}
|
|
|
dbfe2d |
|
|
|
dbfe2d |
+static u32 get_sysfs_proto_supported(void)
|
|
|
dbfe2d |
+{
|
|
|
dbfe2d |
+ int fd;
|
|
|
dbfe2d |
+ int ret;
|
|
|
dbfe2d |
+ char buf[32] = {};
|
|
|
dbfe2d |
+ char *end = NULL;
|
|
|
dbfe2d |
+ u64 version;
|
|
|
dbfe2d |
+
|
|
|
dbfe2d |
+ fd = sysfs_open_file("features/send_stream_version");
|
|
|
dbfe2d |
+ if (fd < 0) {
|
|
|
dbfe2d |
+ /*
|
|
|
dbfe2d |
+ * No file is either no version support or old kernel with just
|
|
|
dbfe2d |
+ * v1.
|
|
|
dbfe2d |
+ */
|
|
|
dbfe2d |
+ return 1;
|
|
|
dbfe2d |
+ }
|
|
|
dbfe2d |
+ ret = sysfs_read_file(fd, buf, sizeof(buf));
|
|
|
dbfe2d |
+ close(fd);
|
|
|
dbfe2d |
+ if (ret <= 0)
|
|
|
dbfe2d |
+ return 1;
|
|
|
dbfe2d |
+ version = strtoull(buf, &end, 10);
|
|
|
dbfe2d |
+ if (version == ULLONG_MAX && errno == ERANGE)
|
|
|
dbfe2d |
+ return 1;
|
|
|
dbfe2d |
+ if (version > U32_MAX) {
|
|
|
dbfe2d |
+ warning("sysfs/send_stream_version too big: %llu", version);
|
|
|
dbfe2d |
+ version = 1;
|
|
|
dbfe2d |
+ }
|
|
|
dbfe2d |
+ return version;
|
|
|
dbfe2d |
+}
|
|
|
dbfe2d |
+
|
|
|
dbfe2d |
static const char * const cmd_send_usage[] = {
|
|
|
dbfe2d |
"btrfs send [-ve] [-p <parent>] [-c <clone-src>] [-f <outfile>] <subvol> [<subvol>...]",
|
|
|
dbfe2d |
"Send the subvolume(s) to stdout.",
|
|
|
dbfe2d |
@@ -449,6 +490,11 @@ static const char * const cmd_send_usage[] = {
|
|
|
dbfe2d |
" does not contain any file data and thus cannot be used",
|
|
|
dbfe2d |
" to transfer changes. This mode is faster and useful to",
|
|
|
dbfe2d |
" show the differences in metadata.",
|
|
|
dbfe2d |
+ "--proto N use protocol version N, or 0 to use the highest version",
|
|
|
dbfe2d |
+ " supported by the sending kernel (default: 1)",
|
|
|
dbfe2d |
+ "--compressed-data",
|
|
|
dbfe2d |
+ " send data that is compressed on the filesystem directly",
|
|
|
dbfe2d |
+ " without decompressing it",
|
|
|
dbfe2d |
"-v|--verbose deprecated, alias for global -v option",
|
|
|
dbfe2d |
"-q|--quiet deprecated, alias for global -q option",
|
|
|
dbfe2d |
HELPINFO_INSERT_GLOBALS,
|
|
|
dbfe2d |
@@ -471,9 +517,11 @@ static int cmd_send(const struct cmd_struct *cmd, int argc, char **argv)
|
|
|
dbfe2d |
int full_send = 1;
|
|
|
dbfe2d |
int new_end_cmd_semantic = 0;
|
|
|
dbfe2d |
u64 send_flags = 0;
|
|
|
dbfe2d |
+ u64 proto = 0;
|
|
|
dbfe2d |
|
|
|
dbfe2d |
memset(&send, 0, sizeof(send));
|
|
|
dbfe2d |
send.dump_fd = fileno(stdout);
|
|
|
dbfe2d |
+ send.proto = 1;
|
|
|
dbfe2d |
outname[0] = 0;
|
|
|
dbfe2d |
|
|
|
dbfe2d |
/*
|
|
|
dbfe2d |
@@ -489,11 +537,17 @@ static int cmd_send(const struct cmd_struct *cmd, int argc, char **argv)
|
|
|
dbfe2d |
|
|
|
dbfe2d |
optind = 0;
|
|
|
dbfe2d |
while (1) {
|
|
|
dbfe2d |
- enum { GETOPT_VAL_SEND_NO_DATA = 256 };
|
|
|
dbfe2d |
+ enum {
|
|
|
dbfe2d |
+ GETOPT_VAL_SEND_NO_DATA = 256,
|
|
|
dbfe2d |
+ GETOPT_VAL_PROTO,
|
|
|
dbfe2d |
+ GETOPT_VAL_COMPRESSED_DATA,
|
|
|
dbfe2d |
+ };
|
|
|
dbfe2d |
static const struct option long_options[] = {
|
|
|
dbfe2d |
{ "verbose", no_argument, NULL, 'v' },
|
|
|
dbfe2d |
{ "quiet", no_argument, NULL, 'q' },
|
|
|
dbfe2d |
{ "no-data", no_argument, NULL, GETOPT_VAL_SEND_NO_DATA },
|
|
|
dbfe2d |
+ { "proto", required_argument, NULL, GETOPT_VAL_PROTO },
|
|
|
dbfe2d |
+ { "compressed-data", no_argument, NULL, GETOPT_VAL_COMPRESSED_DATA },
|
|
|
dbfe2d |
{ NULL, 0, NULL, 0 }
|
|
|
dbfe2d |
};
|
|
|
dbfe2d |
int c = getopt_long(argc, argv, "vqec:f:i:p:", long_options, NULL);
|
|
|
dbfe2d |
@@ -582,6 +636,18 @@ static int cmd_send(const struct cmd_struct *cmd, int argc, char **argv)
|
|
|
dbfe2d |
case GETOPT_VAL_SEND_NO_DATA:
|
|
|
dbfe2d |
send_flags |= BTRFS_SEND_FLAG_NO_FILE_DATA;
|
|
|
dbfe2d |
break;
|
|
|
dbfe2d |
+ case GETOPT_VAL_PROTO:
|
|
|
dbfe2d |
+ proto = arg_strtou64(optarg);
|
|
|
dbfe2d |
+ if (proto > U32_MAX) {
|
|
|
dbfe2d |
+ error("protocol version number too big %llu", proto);
|
|
|
dbfe2d |
+ ret = 1;
|
|
|
dbfe2d |
+ goto out;
|
|
|
dbfe2d |
+ }
|
|
|
dbfe2d |
+ send.proto = proto;
|
|
|
dbfe2d |
+ break;
|
|
|
dbfe2d |
+ case GETOPT_VAL_COMPRESSED_DATA:
|
|
|
dbfe2d |
+ send_flags |= BTRFS_SEND_FLAG_COMPRESSED;
|
|
|
dbfe2d |
+ break;
|
|
|
dbfe2d |
default:
|
|
|
dbfe2d |
usage_unknown_option(cmd, argv);
|
|
|
dbfe2d |
}
|
|
|
dbfe2d |
@@ -689,6 +755,36 @@ static int cmd_send(const struct cmd_struct *cmd, int argc, char **argv)
|
|
|
dbfe2d |
if ((send_flags & BTRFS_SEND_FLAG_NO_FILE_DATA) && bconf.verbose > 1)
|
|
|
dbfe2d |
if (bconf.verbose > 1)
|
|
|
dbfe2d |
fprintf(stderr, "Mode NO_FILE_DATA enabled\n");
|
|
|
dbfe2d |
+ send.proto_supported = get_sysfs_proto_supported();
|
|
|
dbfe2d |
+ if (send.proto_supported == 1) {
|
|
|
dbfe2d |
+ if (send.proto > send.proto_supported) {
|
|
|
dbfe2d |
+ error("requested version %u but kernel supports only %u",
|
|
|
dbfe2d |
+ send.proto, send.proto_supported);
|
|
|
dbfe2d |
+ ret = -EPROTO;
|
|
|
dbfe2d |
+ goto out;
|
|
|
dbfe2d |
+ }
|
|
|
dbfe2d |
+ }
|
|
|
dbfe2d |
+ if (send_flags & BTRFS_SEND_FLAG_COMPRESSED) {
|
|
|
dbfe2d |
+ /*
|
|
|
dbfe2d |
+ * If no protocol version was explicitly requested, then
|
|
|
dbfe2d |
+ * --compressed-data implies --proto 2.
|
|
|
dbfe2d |
+ */
|
|
|
dbfe2d |
+ if (send.proto == 1 && !proto)
|
|
|
dbfe2d |
+ send.proto = 2;
|
|
|
dbfe2d |
+
|
|
|
dbfe2d |
+ if (send.proto == 1) {
|
|
|
dbfe2d |
+ error("--compressed-data requires protocol version >= 2 (requested 1)");
|
|
|
dbfe2d |
+ ret = -EINVAL;
|
|
|
dbfe2d |
+ goto out;
|
|
|
dbfe2d |
+ } else if (send.proto == 0 && send.proto_supported < 2) {
|
|
|
dbfe2d |
+ error("kernel does not support --compressed-data");
|
|
|
dbfe2d |
+ ret = -EINVAL;
|
|
|
dbfe2d |
+ goto out;
|
|
|
dbfe2d |
+ }
|
|
|
dbfe2d |
+ }
|
|
|
dbfe2d |
+ if (bconf.verbose > 1)
|
|
|
dbfe2d |
+ fprintf(stderr, "Protocol version requested: %u (supported %u)\n",
|
|
|
dbfe2d |
+ send.proto, send.proto_supported);
|
|
|
dbfe2d |
|
|
|
dbfe2d |
for (i = optind; i < argc; i++) {
|
|
|
dbfe2d |
int is_first_subvol;
|
|
|
dbfe2d |
diff --git a/ioctl.h b/ioctl.h
|
|
|
dbfe2d |
index 8adf63c2..f19695e3 100644
|
|
|
dbfe2d |
--- a/ioctl.h
|
|
|
dbfe2d |
+++ b/ioctl.h
|
|
|
dbfe2d |
@@ -655,10 +655,24 @@ BUILD_ASSERT(sizeof(struct btrfs_ioctl_received_subvol_args_32) == 192);
|
|
|
dbfe2d |
*/
|
|
|
dbfe2d |
#define BTRFS_SEND_FLAG_OMIT_END_CMD 0x4
|
|
|
dbfe2d |
|
|
|
dbfe2d |
+/*
|
|
|
dbfe2d |
+ * Read the protocol version in the structure
|
|
|
dbfe2d |
+ */
|
|
|
dbfe2d |
+#define BTRFS_SEND_FLAG_VERSION 0x8
|
|
|
dbfe2d |
+
|
|
|
dbfe2d |
+/*
|
|
|
dbfe2d |
+ * Send compressed data using the ENCODED_WRITE command instead of decompressing
|
|
|
dbfe2d |
+ * the data and sending it with the WRITE command. This requires protocol
|
|
|
dbfe2d |
+ * version >= 2.
|
|
|
dbfe2d |
+ */
|
|
|
dbfe2d |
+#define BTRFS_SEND_FLAG_COMPRESSED 0x10
|
|
|
dbfe2d |
+
|
|
|
dbfe2d |
#define BTRFS_SEND_FLAG_MASK \
|
|
|
dbfe2d |
(BTRFS_SEND_FLAG_NO_FILE_DATA | \
|
|
|
dbfe2d |
BTRFS_SEND_FLAG_OMIT_STREAM_HEADER | \
|
|
|
dbfe2d |
- BTRFS_SEND_FLAG_OMIT_END_CMD)
|
|
|
dbfe2d |
+ BTRFS_SEND_FLAG_OMIT_END_CMD | \
|
|
|
dbfe2d |
+ BTRFS_SEND_FLAG_VERSION | \
|
|
|
dbfe2d |
+ BTRFS_SEND_FLAG_COMPRESSED)
|
|
|
dbfe2d |
|
|
|
dbfe2d |
struct btrfs_ioctl_send_args {
|
|
|
dbfe2d |
__s64 send_fd; /* in */
|
|
|
dbfe2d |
@@ -666,7 +680,8 @@ struct btrfs_ioctl_send_args {
|
|
|
dbfe2d |
__u64 __user *clone_sources; /* in */
|
|
|
dbfe2d |
__u64 parent_root; /* in */
|
|
|
dbfe2d |
__u64 flags; /* in */
|
|
|
dbfe2d |
- __u64 reserved[4]; /* in */
|
|
|
dbfe2d |
+ __u32 version; /* in */
|
|
|
dbfe2d |
+ __u8 reserved[28]; /* in */
|
|
|
dbfe2d |
};
|
|
|
dbfe2d |
/*
|
|
|
dbfe2d |
* Size of structure depends on pointer width, was not caught in the early
|
|
|
dbfe2d |
diff --git a/kernel-shared/send.h b/kernel-shared/send.h
|
|
|
dbfe2d |
index b902d054..1f20d01a 100644
|
|
|
dbfe2d |
--- a/kernel-shared/send.h
|
|
|
dbfe2d |
+++ b/kernel-shared/send.h
|
|
|
dbfe2d |
@@ -31,7 +31,7 @@ extern "C" {
|
|
|
dbfe2d |
#endif
|
|
|
dbfe2d |
|
|
|
dbfe2d |
#define BTRFS_SEND_STREAM_MAGIC "btrfs-stream"
|
|
|
dbfe2d |
-#define BTRFS_SEND_STREAM_VERSION 1
|
|
|
dbfe2d |
+#define BTRFS_SEND_STREAM_VERSION 2
|
|
|
dbfe2d |
|
|
|
dbfe2d |
/*
|
|
|
dbfe2d |
* In send stream v1, no command is larger than 64k. In send stream v2, no limit
|
|
|
dbfe2d |
--
|
|
|
dbfe2d |
2.35.1
|
|
|
dbfe2d |
|