public inbox for linux-btrfs@vger.kernel.org
 help / color / mirror / Atom feed
From: David Sterba <dsterba@suse.com>
To: linux-btrfs@vger.kernel.org
Cc: nborisov@suse.com, osandov@osandov.com, David Sterba <dsterba@suse.com>
Subject: [PATCH RFC] btrfs-progs: send protocol v2 stub, UTIMES2, OTIME
Date: Mon, 18 Oct 2021 16:47:17 +0200	[thread overview]
Message-ID: <20211018144717.20275-1-dsterba@suse.com> (raw)
In-Reply-To: <20211018144109.18442-1-dsterba@suse.com>

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-dump.c b/cmds/receive-dump.c
index 47a0a30e47db..7250228e6c42 100644
--- a/cmds/receive-dump.c
+++ b/cmds/receive-dump.c
@@ -312,6 +312,33 @@ static int print_utimes(const char *path, struct timespec *at,
 			  at_str, mt_str, ct_str);
 }
 
+static int print_utimes2(const char *path, struct timespec *at,
+			struct timespec *mt, struct timespec *ct,
+			struct timespec *ot, void *user)
+{
+	char at_str[TIME_STRING_MAX];
+	char mt_str[TIME_STRING_MAX];
+	char ct_str[TIME_STRING_MAX];
+	char ot_str[TIME_STRING_MAX];
+
+	if (sprintf_timespec(at, at_str, TIME_STRING_MAX - 1) < 0 ||
+	    sprintf_timespec(mt, mt_str, TIME_STRING_MAX - 1) < 0 ||
+	    sprintf_timespec(ct, ct_str, TIME_STRING_MAX - 1) < 0 ||
+	    sprintf_timespec(ot, ot_str, TIME_STRING_MAX - 1) < 0)
+		return -EINVAL;
+	return PRINT_DUMP(user, path, "utimes2", "atime=%s mtime=%s ctime=%s otime=%s",
+			  at_str, mt_str, ct_str, ot_str);
+}
+
+static int print_otime(const char *path, struct timespec *ot, void *user)
+{
+	char ot_str[TIME_STRING_MAX];
+
+	if (sprintf_timespec(ot, ot_str, TIME_STRING_MAX - 1) < 0)
+		return -EINVAL;
+	return PRINT_DUMP(user, path, "otime", "otime=%s", ot_str);
+}
+
 static int print_update_extent(const char *path, u64 offset, u64 len,
 			       void *user)
 {
@@ -340,5 +367,7 @@ struct btrfs_send_ops btrfs_print_send_ops = {
 	.chmod = print_chmod,
 	.chown = print_chown,
 	.utimes = print_utimes,
-	.update_extent = print_update_extent
+	.update_extent = print_update_extent,
+	.utimes2 = print_utimes2,
+	.otime = print_otime,
 };
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;
+}
+
 static int process_update_extent(const char *path, u64 offset, u64 len,
 		void *user)
 {
@@ -1004,6 +1081,8 @@ static struct btrfs_send_ops send_ops = {
 	.chown = process_chown,
 	.utimes = process_utimes,
 	.update_extent = process_update_extent,
+	.utimes2 = process_utimes2,
+	.otime = process_otime,
 };
 
 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)",
 	"-v|--verbose     deprecated, alias for global -v option",
 	"-q|--quiet       deprecated, alias for global -q option",
 	HELPINFO_INSERT_GLOBALS,
@@ -472,6 +509,7 @@ static int cmd_send(const struct cmd_struct *cmd, int argc, char **argv)
 
 	memset(&send, 0, sizeof(send));
 	send.dump_fd = fileno(stdout);
+	send.proto = 0;
 	outname[0] = 0;
 
 	/*
@@ -487,11 +525,12 @@ static int cmd_send(const struct cmd_struct *cmd, int argc, char **argv)
 
 	optind = 0;
 	while (1) {
-		enum { GETOPT_VAL_SEND_NO_DATA = 256 };
+		enum { GETOPT_VAL_SEND_NO_DATA = 256, GETOPT_VAL_PROTO };
 		static const struct option long_options[] = {
 			{ "verbose", no_argument, NULL, 'v' },
 			{ "quiet", no_argument, NULL, 'q' },
 			{ "no-data", no_argument, NULL, GETOPT_VAL_SEND_NO_DATA },
+			{ "proto" , required_argument, NULL, GETOPT_VAL_PROTO },
 			{ NULL, 0, NULL, 0 }
 		};
 		int c = getopt_long(argc, argv, "vqec:f:i:p:", long_options, NULL);
@@ -580,6 +619,17 @@ static int cmd_send(const struct cmd_struct *cmd, int argc, char **argv)
 		case GETOPT_VAL_SEND_NO_DATA:
 			send_flags |= BTRFS_SEND_FLAG_NO_FILE_DATA;
 			break;
+		case GETOPT_VAL_PROTO: {
+			u64 tmp;
+
+			tmp = arg_strtou64(optarg);
+			if (tmp > U32_MAX) {
+				error("protocol version number too big %llu", tmp);
+				ret = 1;
+				goto out;
+			}
+			send.proto = tmp;
+			break; }
 		default:
 			usage_unknown_option(cmd, argv);
 		}
@@ -687,6 +737,18 @@ static int cmd_send(const struct cmd_struct *cmd, int argc, char **argv)
 	if ((send_flags & BTRFS_SEND_FLAG_NO_FILE_DATA) && bconf.verbose > 1)
 		if (bconf.verbose > 1)
 			fprintf(stderr, "Mode NO_FILE_DATA enabled\n");
+	send.proto_supported = get_sysfs_proto_supported();
+	if (send.proto_supported == 1) {
+		if (send.proto > send.proto_supported) {
+			error("requested version %u but kernel supports only %u",
+				send.proto, send.proto_supported);
+			ret = -EPROTO;
+			goto out;
+		}
+	}
+	if (bconf.verbose > 1)
+		fprintf(stderr, "Protocol version requested: %u (supported %u)\n",
+				send.proto, send.proto_supported);
 
 	for (i = optind; i < argc; i++) {
 		int is_first_subvol;
diff --git a/common/send-stream.c b/common/send-stream.c
index d07748ced1fe..0b0558cec671 100644
--- a/common/send-stream.c
+++ b/common/send-stream.c
@@ -314,6 +314,7 @@ static int read_and_process_cmd(struct btrfs_send_stream *sctx)
 	struct timespec at;
 	struct timespec ct;
 	struct timespec mt;
+	struct timespec ot;
 	u8 uuid[BTRFS_UUID_SIZE];
 	u8 clone_uuid[BTRFS_UUID_SIZE];
 	u64 tmp;
@@ -451,6 +452,19 @@ static int read_and_process_cmd(struct btrfs_send_stream *sctx)
 		TLV_GET_U64(sctx, BTRFS_SEND_A_SIZE, &tmp);
 		ret = sctx->ops->update_extent(path, offset, tmp, sctx->user);
 		break;
+	case BTRFS_SEND_C_UTIMES2:
+		TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path);
+		TLV_GET_TIMESPEC(sctx, BTRFS_SEND_A_ATIME, &at);
+		TLV_GET_TIMESPEC(sctx, BTRFS_SEND_A_MTIME, &mt);
+		TLV_GET_TIMESPEC(sctx, BTRFS_SEND_A_CTIME, &ct);
+		TLV_GET_TIMESPEC(sctx, BTRFS_SEND_A_OTIME, &ot);
+		ret = sctx->ops->utimes2(path, &at, &mt, &ct, &ot, sctx->user);
+		break;
+	case BTRFS_SEND_C_OTIME:
+		TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path);
+		TLV_GET_TIMESPEC(sctx, BTRFS_SEND_A_OTIME, &ot);
+		ret = sctx->ops->otime(path, &ot, sctx->user);
+		break;
 	case BTRFS_SEND_C_END:
 		ret = 1;
 		break;
diff --git a/common/send-stream.h b/common/send-stream.h
index 2de51eac5548..be6c71031db0 100644
--- a/common/send-stream.h
+++ b/common/send-stream.h
@@ -53,6 +53,11 @@ struct btrfs_send_ops {
 		      struct timespec *mt, struct timespec *ct,
 		      void *user);
 	int (*update_extent)(const char *path, u64 offset, u64 len, void *user);
+	/* V2 */
+	int (*utimes2)(const char *path, struct timespec *at,
+		      struct timespec *mt, struct timespec *ct,
+		      struct timespec *ot, void *user);
+	int (*otime)(const char *path, struct timespec *ot, void *user);
 };
 
 int btrfs_read_and_process_send_stream(int fd,
diff --git a/ioctl.h b/ioctl.h
index 368a87b27711..2901d92b64e8 100644
--- a/ioctl.h
+++ b/ioctl.h
@@ -655,10 +655,16 @@ BUILD_ASSERT(sizeof(struct btrfs_ioctl_received_subvol_args_32) == 192);
  */
 #define BTRFS_SEND_FLAG_OMIT_END_CMD		0x4
 
+/*
+ * Read the protocol version in the structure
+ */
+#define BTRFS_SEND_FLAG_VERSION			0x8
+
 #define BTRFS_SEND_FLAG_MASK \
 	(BTRFS_SEND_FLAG_NO_FILE_DATA | \
 	 BTRFS_SEND_FLAG_OMIT_STREAM_HEADER | \
-	 BTRFS_SEND_FLAG_OMIT_END_CMD)
+	 BTRFS_SEND_FLAG_OMIT_END_CMD | \
+	 BTRFS_SEND_FLAG_VERSION)
 
 struct btrfs_ioctl_send_args {
 	__s64 send_fd;			/* in */
@@ -666,7 +672,9 @@ struct btrfs_ioctl_send_args {
 	__u64 __user *clone_sources;	/* in */
 	__u64 parent_root;		/* in */
 	__u64 flags;			/* in */
-	__u64 reserved[4];		/* in */
+	__u32 version;			/* in */
+	__u32 reserved32;
+	__u64 reserved[3];		/* in */
 };
 /*
  * Size of structure depends on pointer width, was not caught in the early
diff --git a/kernel-shared/send.h b/kernel-shared/send.h
index e73f09dfe5d7..8b927313ff76 100644
--- a/kernel-shared/send.h
+++ b/kernel-shared/send.h
@@ -31,7 +31,7 @@ extern "C" {
 #endif
 
 #define BTRFS_SEND_STREAM_MAGIC "btrfs-stream"
-#define BTRFS_SEND_STREAM_VERSION 1
+#define BTRFS_SEND_STREAM_VERSION 2
 
 #define BTRFS_SEND_BUF_SIZE  (64 * 1024)
 #define BTRFS_SEND_READ_SIZE (1024 * 48)
@@ -70,6 +70,7 @@ struct btrfs_tlv_header {
 enum btrfs_send_cmd {
 	BTRFS_SEND_C_UNSPEC,
 
+	/* Version 1 */
 	BTRFS_SEND_C_SUBVOL,
 	BTRFS_SEND_C_SNAPSHOT,
 
@@ -98,6 +99,14 @@ enum btrfs_send_cmd {
 
 	BTRFS_SEND_C_END,
 	BTRFS_SEND_C_UPDATE_EXTENT,
+	__BTRFS_SEND_C_MAX_V1,
+
+	/* Version 2 */
+	BTRFS_SEND_C_UTIMES2,
+	BTRFS_SEND_C_OTIME,
+	__BTRFS_SEND_C_MAX_V2,
+
+	/* End */
 	__BTRFS_SEND_C_MAX,
 };
 #define BTRFS_SEND_C_MAX (__BTRFS_SEND_C_MAX - 1)
-- 
2.33.0


  reply	other threads:[~2021-10-18 14:47 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-10-18 14:41 [PATCH RFC] btrfs: send: v2 protocol and example OTIME changes David Sterba
2021-10-18 14:47 ` David Sterba [this message]
2021-10-18 21:42   ` [PATCH RFC] btrfs-progs: send protocol v2 stub, UTIMES2, OTIME Omar Sandoval
2021-10-19 12:53     ` David Sterba
2021-10-19 14:11       ` Josef Bacik
2021-10-19 17:27       ` Omar Sandoval
2021-10-18 15:36 ` [PATCH RFC] btrfs: send: v2 protocol and example OTIME changes Filipe Manana
2021-10-18 21:26 ` Omar Sandoval
2021-10-19 10:51   ` David Sterba
2021-10-19  7:30 ` Nikolay Borisov
2021-10-19 17:38 ` Omar Sandoval
2021-10-19 20:25   ` David Sterba

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20211018144717.20275-1-dsterba@suse.com \
    --to=dsterba@suse.com \
    --cc=linux-btrfs@vger.kernel.org \
    --cc=nborisov@suse.com \
    --cc=osandov@osandov.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox