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.