Thread (12 messages) 12 messages, 6 authors, 2021-10-19

Re: [PATCH RFC] btrfs-progs: send protocol v2 stub, UTIMES2, OTIME

From: Omar Sandoval <osandov@osandov.com>
Date: 2021-10-18 21:42:42

On Mon, Oct 18, 2021 at 04:47:17PM +0200, David Sterba wrote:
quoted hunk ↗ jump to hunk
This is counterpart for the protocol version update.

- version 2 protocol
- new protocol command UTIMES2, same as utimes with additional otime
  data
- send: add command line options to specify version, compare against
  current running kernel supported version
- receive: parse UTIMES2
- receive: parse OTIME

TODO:

- libbtrfs compatibility is missing, ie. this will break anything that
  uses send stream (snapper), this needs library version update and
  maybe some ifdefs in the headers

Signed-off-by: David Sterba <dsterba@suse.com>
---
 cmds/receive-dump.c  | 31 ++++++++++++++++-
 cmds/receive.c       | 79 ++++++++++++++++++++++++++++++++++++++++++++
 cmds/send.c          | 66 ++++++++++++++++++++++++++++++++++--
 common/send-stream.c | 14 ++++++++
 common/send-stream.h |  5 +++
 ioctl.h              | 12 +++++--
 kernel-shared/send.h | 11 +++++-
 7 files changed, 212 insertions(+), 6 deletions(-)
diff --git a/cmds/receive.c b/cmds/receive.c
index 4d123a1f8782..dfb37a502598 100644
--- a/cmds/receive.c
+++ b/cmds/receive.c
@@ -967,6 +967,83 @@ static int process_utimes(const char *path, struct timespec *at,
 	return ret;
 }
 
+static int process_utimes2(const char *path, struct timespec *at,
+			  struct timespec *mt, struct timespec *ct,
+			  struct timespec *ot, 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("utimes2: path invalid: %s", path);
+		goto out;
+	}
+
+	if (bconf.verbose >= 3)
+		fprintf(stderr, "utimes2 %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("utimes2 %s failed: %m", path);
+		goto out;
+	}
+
+out:
+	return ret;
+}
+
+/* TODO: Copied from receive-dump.c */
+static int sprintf_timespec(struct timespec *ts, char *dest, int max_size)
+{
+	struct tm tm;
+	int ret;
+
+	if (!localtime_r(&ts->tv_sec, &tm)) {
+		error("failed to convert time %lld.%.9ld to local time",
+		      (long long)ts->tv_sec, ts->tv_nsec);
+		return -EINVAL;
+	}
+	ret = strftime(dest, max_size, "%FT%T%z", &tm);
+	if (ret == 0) {
+		error(
+		"time %lld.%ld is too long to convert into readable string",
+		      (long long)ts->tv_sec, ts->tv_nsec);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+
+static int process_otime(const char *path, struct timespec *ot, 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("otime: path invalid: %s", path);
+		goto out;
+	}
+
+	if (bconf.verbose >= 3) {
+		char ot_str[128];
+
+		if (sprintf_timespec(ot, ot_str, sizeof(ot_str) - 1) < 0)
+			goto out;
+		fprintf(stderr, "otime %s\n", ot_str);
+	}
+
+out:
+	return 0;
+}
Are you planning to do anything with otime (e.g., storing it in an
xattr) in the future?
quoted hunk ↗ jump to hunk
 static int do_receive(struct btrfs_receive *rctx, const char *tomnt,
diff --git a/cmds/send.c b/cmds/send.c
index 1810233137aa..f529f0b0a6a0 100644
--- a/cmds/send.c
+++ b/cmds/send.c
@@ -57,6 +57,8 @@ struct btrfs_send {
 	u64 clone_sources_count;
 
 	char *root_path;
+	u32 proto;
+	u32 proto_supported;
 };
 
 static int get_root_id(struct btrfs_send *sctx, const char *path, u64 *root_id)
@@ -257,6 +259,13 @@ static int do_send(struct btrfs_send *send, u64 parent_root_id,
 	memset(&io_send, 0, sizeof(io_send));
 	io_send.send_fd = pipefd[1];
 	send->send_fd = pipefd[0];
+	io_send.flags = flags;
+
+	if (send->proto_supported > 1) {
+		/* Versioned stream supported, requesting default or specific number */
+		io_send.version = send->proto;
+		io_send.flags |= BTRFS_SEND_FLAG_VERSION;
+	}
 
 	if (!ret)
 		ret = pthread_create(&t_read, NULL, read_sent_data, send);
@@ -267,7 +276,6 @@ static int do_send(struct btrfs_send *send, u64 parent_root_id,
 		goto out;
 	}
 
-	io_send.flags = flags;
 	io_send.clone_sources = (__u64*)send->clone_sources;
 	io_send.clone_sources_count = send->clone_sources_count;
 	io_send.parent_root = parent_root_id;
@@ -275,6 +283,7 @@ static int do_send(struct btrfs_send *send, u64 parent_root_id,
 		io_send.flags |= BTRFS_SEND_FLAG_OMIT_STREAM_HEADER;
 	if (!is_last_subvol)
 		io_send.flags |= BTRFS_SEND_FLAG_OMIT_END_CMD;
+
 	ret = ioctl(subvol_fd, BTRFS_IOC_SEND, &io_send);
 	if (ret < 0) {
 		ret = -errno;
@@ -419,6 +428,33 @@ static void free_send_info(struct btrfs_send *sctx)
 	sctx->root_path = NULL;
 }
 
+static u32 get_sysfs_proto_supported(void)
+{
+	int fd;
+	int ret;
+	char buf[32] = {};
+	char *end = NULL;
+	u64 version;
+
+	fd = sysfs_open_file("features/send_stream_version");
+	if (fd < 0) {
+		/* No file is either no version support or old kernel with just v1 */
+		return 1;
+	}
+	ret = sysfs_read_file(fd, buf, sizeof(buf));
+	close(fd);
+	if (ret <= 0)
+		return 1;
+	version = strtoull(buf, &end, 10);
+	if (version == ULLONG_MAX && errno == ERANGE)
+		return 1;
+	if (version > U32_MAX) {
+		warning("sysfs/send_stream_version too big: %llu", version);
+		version = 1;
+	}
+	return version;
+}
+
 static const char * const cmd_send_usage[] = {
 	"btrfs send [-ve] [-p <parent>] [-c <clone-src>] [-f <outfile>] <subvol> [<subvol>...]",
 	"Send the subvolume(s) to stdout.",
@@ -447,6 +483,7 @@ static const char * const cmd_send_usage[] = {
 	"                 does not contain any file data and thus cannot be used",
 	"                 to transfer changes. This mode is faster and useful to",
 	"                 show the differences in metadata.",
+	"--proto N        request maximum protocol version N (default: highest supported by running kernel)",
Can we default to version 1 and provide a way to opt in to the latest
version? I'm concerned with a kernel upgrade suddenly creating a send
stream that the receiving side can't handle. Making this opt-in rather
than opt-out seems safer.
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help