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
next prev parent 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