borisb / rpms / btrfs-progs

Forked from rpms/btrfs-progs 2 years ago
Clone
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