From 366f615c9fa98ee60760f615c045218a69f37dae Mon Sep 17 00:00:00 2001 From: Zach Brown Date: Mon, 27 Sep 2021 14:39:39 -0700 Subject: [PATCH] Add support for our format version We had previously started on a relatively simple notion of an interoperability version which wasn't quite right. This fleshes out support for a more functional format version. The super blocks have a single version that defines behaviour of the running system. The code supports a range of versions and we add some initial interfaces for updating the version while the system is offline. All of this together should let us safely change the underlying format over time. Signed-off-by: Zach Brown --- kmod/src/client.c | 17 +- kmod/src/format.h | 15 +- kmod/src/quorum.c | 2 +- kmod/src/server.c | 16 +- kmod/src/super.c | 38 +++- kmod/src/super.h | 1 + kmod/src/sysfs.c | 11 ++ utils/man/scoutfs.5 | 81 ++++++++- utils/man/scoutfs.8 | 37 +++- utils/src/change_format_version.c | 287 ++++++++++++++++++++++++++++++ utils/src/cmd.c | 4 + utils/src/mkfs.c | 22 ++- utils/src/print.c | 4 +- utils/src/util.c | 13 +- 14 files changed, 507 insertions(+), 41 deletions(-) create mode 100644 utils/src/change_format_version.c diff --git a/kmod/src/client.c b/kmod/src/client.c index 4ddc54fb..acf9412f 100644 --- a/kmod/src/client.c +++ b/kmod/src/client.c @@ -361,7 +361,8 @@ static int client_greeting(struct super_block *sb, void *resp, unsigned int resp_len, int error, void *data) { - struct client_info *client = SCOUTFS_SB(sb)->client_info; + struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb); + struct client_info *client = sbi->client_info; struct scoutfs_super_block *super = &SCOUTFS_SB(sb)->super; struct scoutfs_net_greeting *gr = resp; bool new_server; @@ -378,17 +379,15 @@ static int client_greeting(struct super_block *sb, } if (gr->fsid != super->hdr.fsid) { - scoutfs_warn(sb, "server sent fsid 0x%llx, client has 0x%llx", - le64_to_cpu(gr->fsid), - le64_to_cpu(super->hdr.fsid)); + scoutfs_warn(sb, "server greeting response fsid 0x%llx did not match client fsid 0x%llx", + le64_to_cpu(gr->fsid), le64_to_cpu(super->hdr.fsid)); ret = -EINVAL; goto out; } - if (gr->version != super->version) { - scoutfs_warn(sb, "server sent format 0x%llx, client has 0x%llx", - le64_to_cpu(gr->version), - le64_to_cpu(super->version)); + if (le64_to_cpu(gr->fmt_vers) != sbi->fmt_vers) { + scoutfs_warn(sb, "server greeting response format version %llu did not match client format version %llu", + le64_to_cpu(gr->fmt_vers), sbi->fmt_vers); ret = -EINVAL; goto out; } @@ -514,7 +513,7 @@ static void scoutfs_client_connect_worker(struct work_struct *work) /* send a greeting to verify endpoints of each connection */ greet.fsid = super->hdr.fsid; - greet.version = super->version; + greet.fmt_vers = cpu_to_le64(sbi->fmt_vers); greet.server_term = cpu_to_le64(client->server_term); greet.rid = cpu_to_le64(sbi->rid); greet.flags = 0; diff --git a/kmod/src/format.h b/kmod/src/format.h index b9275eda..3f20db33 100644 --- a/kmod/src/format.h +++ b/kmod/src/format.h @@ -1,8 +1,15 @@ #ifndef _SCOUTFS_FORMAT_H_ #define _SCOUTFS_FORMAT_H_ -#define SCOUTFS_INTEROP_VERSION 0ULL -#define SCOUTFS_INTEROP_VERSION_STR __stringify(0) +/* + * The format version defines the format of structures on devices, + * structures that are communicated over the wire, and the protocol + * behind the structures. + */ +#define SCOUTFS_FORMAT_VERSION_MIN 0 +#define SCOUTFS_FORMAT_VERSION_MIN_STR __stringify(SCOUTFS_FORMAT_VERSION_MIN) +#define SCOUTFS_FORMAT_VERSION_MAX 0 +#define SCOUTFS_FORMAT_VERSION_MAX_STR __stringify(SCOUTFS_FORMAT_VERSION_MAX) /* statfs(2) f_type */ #define SCOUTFS_SUPER_MAGIC 0x554f4353 /* "SCOU" */ @@ -783,7 +790,7 @@ struct scoutfs_volume_options { struct scoutfs_super_block { struct scoutfs_block_header hdr; __le64 id; - __le64 version; + __le64 fmt_vers; __le64 flags; __u8 uuid[SCOUTFS_UUID_BYTES]; __le64 seq; @@ -928,7 +935,7 @@ enum scoutfs_dentry_type { */ struct scoutfs_net_greeting { __le64 fsid; - __le64 version; + __le64 fmt_vers; __le64 server_term; __le64 rid; __le64 flags; diff --git a/kmod/src/quorum.c b/kmod/src/quorum.c index 34922d78..f8b547d2 100644 --- a/kmod/src/quorum.c +++ b/kmod/src/quorum.c @@ -828,7 +828,7 @@ static void scoutfs_quorum_worker(struct work_struct *work) qst.term); } - /* informational event that we're shutting down, nothing relies on it */ + /* record that this slot no longer has an active quorum */ update_quorum_block(sb, SCOUTFS_QUORUM_EVENT_END, qst.term, true); out: if (ret < 0) { diff --git a/kmod/src/server.c b/kmod/src/server.c index 8624e5e1..2ca229a6 100644 --- a/kmod/src/server.c +++ b/kmod/src/server.c @@ -3199,7 +3199,8 @@ static int server_greeting(struct super_block *sb, struct scoutfs_net_connection *conn, u8 cmd, u64 id, void *arg, u16 arg_len) { - struct scoutfs_super_block *super = &SCOUTFS_SB(sb)->super; + struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb); + struct scoutfs_super_block *super = &sbi->super; struct scoutfs_net_greeting *gr = arg; struct scoutfs_net_greeting greet; DECLARE_SERVER_INFO(sb, server); @@ -3215,17 +3216,16 @@ static int server_greeting(struct super_block *sb, } if (gr->fsid != super->hdr.fsid) { - scoutfs_warn(sb, "client sent fsid 0x%llx, server has 0x%llx", - le64_to_cpu(gr->fsid), + scoutfs_warn(sb, "client rid %016llx greeting fsid 0x%llx did not match server fsid 0x%llx", + le64_to_cpu(gr->rid), le64_to_cpu(gr->fsid), le64_to_cpu(super->hdr.fsid)); ret = -EINVAL; goto send_err; } - if (gr->version != super->version) { - scoutfs_warn(sb, "client sent format 0x%llx, server has 0x%llx", - le64_to_cpu(gr->version), - le64_to_cpu(super->version)); + if (le64_to_cpu(gr->fmt_vers) != sbi->fmt_vers) { + scoutfs_warn(sb, "client rid %016llx greeting format version %llu did not match server format version %llu", + le64_to_cpu(gr->rid), le64_to_cpu(gr->fmt_vers), sbi->fmt_vers); ret = -EINVAL; goto send_err; } @@ -3249,7 +3249,7 @@ send_err: err = ret; greet.fsid = super->hdr.fsid; - greet.version = super->version; + greet.fmt_vers = cpu_to_le64(sbi->fmt_vers); greet.server_term = cpu_to_le64(server->term); greet.rid = gr->rid; greet.flags = 0; diff --git a/kmod/src/super.c b/kmod/src/super.c index 1fb8c209..612c51fa 100644 --- a/kmod/src/super.c +++ b/kmod/src/super.c @@ -403,11 +403,22 @@ static int scoutfs_read_super_from_bdev(struct super_block *sb, goto out; } + if (le64_to_cpu(super->fmt_vers) < SCOUTFS_FORMAT_VERSION_MIN || + le64_to_cpu(super->fmt_vers) > SCOUTFS_FORMAT_VERSION_MAX) { + scoutfs_err(sb, "super block has format version %llu outside of supported version range %u-%u", + le64_to_cpu(super->fmt_vers), SCOUTFS_FORMAT_VERSION_MIN, + SCOUTFS_FORMAT_VERSION_MAX); + ret = -EINVAL; + goto out; + } - if (super->version != cpu_to_le64(SCOUTFS_INTEROP_VERSION)) { - scoutfs_err(sb, "super block has invalid version %llu, expected %llu", - le64_to_cpu(super->version), - SCOUTFS_INTEROP_VERSION); + /* + * fill_supers checks the fmt_vers in both supers and then decides to use it. + * From then on we verify that the supers we read have that version. + */ + if (sbi->fmt_vers != 0 && le64_to_cpu(super->fmt_vers) != sbi->fmt_vers) { + scoutfs_err(sb, "super block has format version %llu than %llu read at mount", + le64_to_cpu(super->fmt_vers), sbi->fmt_vers); ret = -EINVAL; goto out; } @@ -524,6 +535,14 @@ static int scoutfs_read_supers(struct super_block *sb) goto out; } + if (le64_to_cpu(meta_super->fmt_vers) != le64_to_cpu(data_super->fmt_vers)) { + scoutfs_err(sb, "meta device format version %llu != data device format version %llu", + le64_to_cpu(meta_super->fmt_vers), le64_to_cpu(data_super->fmt_vers)); + goto out; + } + + + sbi->fmt_vers = le64_to_cpu(meta_super->fmt_vers); sbi->super = *meta_super; out: kfree(meta_super); @@ -717,8 +736,12 @@ static int __init scoutfs_module_init(void) ".ascii \""SCOUTFS_GIT_DESCRIBE"\\n\"\n" ".previous\n"); __asm__ __volatile__ ( - ".section .note.scoutfs_interop_version,\"a\"\n" - ".ascii \""SCOUTFS_INTEROP_VERSION_STR"\\n\"\n" + ".section .note.scoutfs_format_version_min,\"a\"\n" + ".ascii \""SCOUTFS_FORMAT_VERSION_MIN_STR"\\n\"\n" + ".previous\n"); + __asm__ __volatile__ ( + ".section .note.scoutfs_format_version_max,\"a\"\n" + ".ascii \""SCOUTFS_FORMAT_VERSION_MAX_STR"\\n\"\n" ".previous\n"); scoutfs_init_counters(); @@ -752,4 +775,5 @@ module_exit(scoutfs_module_exit) MODULE_AUTHOR("Zach Brown "); MODULE_LICENSE("GPL"); MODULE_INFO(git_describe, SCOUTFS_GIT_DESCRIBE); -MODULE_INFO(scoutfs_interop_version, SCOUTFS_INTEROP_VERSION_STR); +MODULE_INFO(scoutfs_format_version_min, SCOUTFS_FORMAT_VERSION_MIN_STR); +MODULE_INFO(scoutfs_format_version_max, SCOUTFS_FORMAT_VERSION_MAX_STR); diff --git a/kmod/src/super.h b/kmod/src/super.h index 92106b63..32fba8d6 100644 --- a/kmod/src/super.h +++ b/kmod/src/super.h @@ -36,6 +36,7 @@ struct scoutfs_sb_info { /* assigned once at the start of each mount, read-only */ u64 rid; + u64 fmt_vers; struct scoutfs_super_block super; diff --git a/kmod/src/sysfs.c b/kmod/src/sysfs.c index 5f2f024c..9bf19e48 100644 --- a/kmod/src/sysfs.c +++ b/kmod/src/sysfs.c @@ -37,6 +37,16 @@ struct attr_funcs { #define ATTR_FUNCS_RO(_name) \ static struct attr_funcs _name##_attr_funcs = __ATTR_RO(_name) +static ssize_t format_version_show(struct kobject *kobj, struct attribute *attr, + char *buf) +{ + struct super_block *sb = KOBJ_TO_SB(kobj, sb_id_kobj); + struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb); + + return snprintf(buf, PAGE_SIZE, "%llu\n", sbi->fmt_vers); +} +ATTR_FUNCS_RO(format_version); + static ssize_t fsid_show(struct kobject *kobj, struct attribute *attr, char *buf) { @@ -91,6 +101,7 @@ static ssize_t attr_funcs_show(struct kobject *kobj, struct attribute *attr, static struct attribute *sb_id_attrs[] = { + &format_version_attr_funcs.attr, &fsid_attr_funcs.attr, &rid_attr_funcs.attr, NULL, diff --git a/utils/man/scoutfs.5 b/utils/man/scoutfs.5 index 63962735..a9303c9e 100644 --- a/utils/man/scoutfs.5 +++ b/utils/man/scoutfs.5 @@ -198,7 +198,86 @@ with the .IB READ_XATTR_TOTALS ioctl. .RE - + +.SH FORMAT VERSION +The format version defines the layout and use of structures stored on +devices and passed over the network. The version is incremented for +every change in structures that is not backwards compatible with +previous versions. A single version implies all changes, individual +changes can't be selectively adopted. +.sp +As a new file system is created the format version is stored in both of +the super blocks written to the metadata and data devices. By default +the greatest supported version is written while an older supported +version may be specified. +.sp +During mount the kernel module verifies that the format versions stored +in both of the super blocks match and are supported. That version +defines the set of features and behavior of all the mounts using the +file system, including the network protocol that is communicated over +the wire. +.sp +Any combination of software release versions that support the current +format version of the file system can safely be used concurrently. This +allows for rolling software updates of multiple mounts using a shared +file system. +.sp +To use new incompatible features added in newer format versions the super blocks must +be updated. This can currently only be safely performed on a +completely and cleanly unmounted file system. The +.BR scoutfs (8) +.I change-format-version +command can be used with the +.I --offline +option to write a newer supported version into the super blocks. It +will fail if it sees any indication of unresolved mounts that may be +using the devices: either active quorum members working with their +quorum blocks or persistent records of mounted clients that haven't been +resolved. Like creating a new file system, there is no protection +against multiple invocations of the change command corrupting the +system. Once the version is updated older software can no longer use +the file system so this change should be performed with care. Once the +newer format version is successfully written it can be mounted and newer +features can be used. +.sp +Each layer of the system can show its supported format versions: +.RS +.TP +.B Userspace utilities +.B scoutfs --help +includes the range of supported format versions for a given release +of the userspace utilities. +.TP +.B Kernel module +.I modinfo MODULE +shows the range of supproted versions for a kernel module file in the +.I scoutfs_format_version_min +and +.I scoutfs_format_version_min +fields. +.TP +.B Inserted module +The supported version range of an inserted module can be found in +.I .note.scoutfs_format_version_min +and +.I .note.scoutfs_format_version_max +notes files in the sysfs notes directory for the inserted module, +typically +.I /sys/module/scoutfs/notes/ +.TP +.B Metadata and data devices +.I scoutfs print DEVICE +shows the +.I fmt_vers +field in the initial output of the super block on the device. +.TP +.B Mounted filesystem +The version that a mount is using is shown in the +.I format_version +file in the mount's sysfs directory, typically +.I /sys/fs/scoutfs/f.FSID.r.RID/ +.RE + .SH CORRUPTION DETECTION A .B scoutfs diff --git a/utils/man/scoutfs.8 b/utils/man/scoutfs.8 index 25ee53c7..5c36b4f8 100644 --- a/utils/man/scoutfs.8 +++ b/utils/man/scoutfs.8 @@ -14,6 +14,34 @@ option will, when the option is omitted, fall back to using the value of the environment variable. If that variable is also absent the current working directory will be used. +.TP +.BI "change-format-version [-V, --format-version VERS] [-F|--offline META-DEVICE DATA-DEVICE]" +.sp +Change the format version of an existing file system. The maxmimum +supported version is used by default. A specific version in the range +can be specified. The range of supported versions in shown in the +output of --help. +.RS 1.0i +.PD 0 +.TP +.sp +.B "-F, --offline META-DEVICE DATA-DEVICE" +Change the format version by writing directly to the metadata and data +devices. Like mkfs, this writes directly to the devices without +protection and must only be used on completely unmounted devices. The +command will fail if it sees evidence of active quorum use of the device +or of previously connected clients which haven't been reclaimed. The +only way to avoid these checks is to fully mount and cleanly unmount the +file system. +.sp +This is not an atomic operation because it writes to blocks on two +devices. Write failure can result in the versions becoming out of sync +which will prevent the system from mouting. To recover the error must +be resolved so the command can be repeated and successfully write to +the super blocks on both devices. +.RE +.PD + .TP .BI "df [-h|--human-readable] [-p|--path PATH]" .sp @@ -32,7 +60,7 @@ A path within a ScoutFS filesystem. .PD .TP -.BI "mkfs META-DEVICE DATA-DEVICE {-Q|--quorum-slot} NR,ADDR,PORT [-m|--max-meta-size SIZE] [-d|--max-data-size SIZE] [-z|--data-alloc-zone-blocks BLOCKS] [-f|--force] [-A|--allow-small-size]" +.BI "mkfs META-DEVICE DATA-DEVICE {-Q|--quorum-slot} NR,ADDR,PORT [-m|--max-meta-size SIZE] [-d|--max-data-size SIZE] [-z|--data-alloc-zone-blocks BLOCKS] [-f|--force] [-A|--allow-small-size] [-V|--format-version VERS]" .sp Initialize a new ScoutFS filesystem on the target devices. Since ScoutFS uses separate block devices for its metadata and data storage, two are required. @@ -99,6 +127,13 @@ Set the data_alloc_zone_blocks volume option, as described in .TP .B "-f, --force" Ignore presence of existing data on the data and metadata devices. +.TP +.B "-V, --format-verson" +Specify the format version to use in the newly created file system. +The range of supported versions is visible in the output of ++.BR scoutfs (8) ++.I --help +. .RE .PD diff --git a/utils/src/change_format_version.c b/utils/src/change_format_version.c new file mode 100644 index 00000000..1447302b --- /dev/null +++ b/utils/src/change_format_version.c @@ -0,0 +1,287 @@ +#define _GNU_SOURCE /* O_DIRECT */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sparse.h" +#include "cmd.h" +#include "util.h" +#include "format.h" +#include "parse.h" +#include "crc.h" +#include "rand.h" +#include "dev.h" +#include "key.h" +#include "bitops.h" +#include "btree.h" +#include "leaf_item_hash.h" +#include "blkid.h" +#include "quorum.h" + +struct change_fmt_vers_args { + char *meta_device; + char *data_device; + u64 fmt_vers; + bool offline; +}; + +static int do_change_fmt_vers(struct change_fmt_vers_args *args) +{ + struct scoutfs_super_block *meta_super = NULL; + struct scoutfs_super_block *data_super = NULL; + struct scoutfs_quorum_block *qblk = NULL; + struct scoutfs_quorum_block_event *beg; + struct scoutfs_quorum_block_event *end; + bool wrote_meta = false; + bool in_use = false; + char uuid_str[37]; + int meta_fd = -1; + int data_fd = -1; + int ret; + int i; + + meta_fd = open(args->meta_device, O_DIRECT | O_SYNC | O_RDWR | O_EXCL); + if (meta_fd < 0) { + ret = -errno; + fprintf(stderr, "failed to open meta device '%s': %s (%d)\n", + args->meta_device, strerror(errno), errno); + goto out; + } + + data_fd = open(args->data_device, O_DIRECT | O_SYNC | O_RDWR | O_EXCL); + if (data_fd < 0) { + ret = -errno; + fprintf(stderr, "failed to open data device '%s': %s (%d)\n", + args->data_device, strerror(errno), errno); + goto out; + } + + ret = read_block_verify(meta_fd, SCOUTFS_BLOCK_MAGIC_SUPER, 0, SCOUTFS_SUPER_BLKNO, + SCOUTFS_BLOCK_SM_SHIFT, (void **)&meta_super); + if (ret) { + ret = -errno; + fprintf(stderr, "failed to read meta super block: %s (%d)\n", + strerror(errno), errno); + goto out; + } + + ret = read_block_verify(data_fd, SCOUTFS_BLOCK_MAGIC_SUPER, + le64_to_cpu(meta_super->hdr.fsid), SCOUTFS_SUPER_BLKNO, + SCOUTFS_BLOCK_SM_SHIFT, (void **)&data_super); + if (ret) { + ret = -errno; + fprintf(stderr, "failed to read data super block: %s (%d)\n", + strerror(errno), errno); + goto out; + } + + if (le64_to_cpu(meta_super->fmt_vers) == args->fmt_vers && + meta_super->fmt_vers == data_super->fmt_vers) { + printf("both metadata and data device format version are already %llu, nothing to do.\n", + args->fmt_vers); + ret = 0; + goto out; + } + + if (le64_to_cpu(meta_super->fmt_vers) < SCOUTFS_FORMAT_VERSION_MIN || + le64_to_cpu(meta_super->fmt_vers) > SCOUTFS_FORMAT_VERSION_MAX) { + fprintf(stderr, "meta super block has format version %llu outside of supported version range %u-%u", + le64_to_cpu(meta_super->fmt_vers), SCOUTFS_FORMAT_VERSION_MIN, + SCOUTFS_FORMAT_VERSION_MAX); + ret = -EINVAL; + goto out; + } + + if (le64_to_cpu(data_super->fmt_vers) < SCOUTFS_FORMAT_VERSION_MIN || + le64_to_cpu(data_super->fmt_vers) > SCOUTFS_FORMAT_VERSION_MAX) { + fprintf(stderr, "data super block has format version %llu outside of supported version range %u-%u", + le64_to_cpu(data_super->fmt_vers), SCOUTFS_FORMAT_VERSION_MIN, + SCOUTFS_FORMAT_VERSION_MAX); + ret = -EINVAL; + goto out; + } + + if (meta_super->mounted_clients.ref.blkno != 0) { + fprintf(stderr, "meta superblock mounted clients btree is not empty.\n"); + ret = -EBUSY; + in_use = true; + goto out; + } + + /* check for active quorum slots */ + for (i = 0; i < SCOUTFS_QUORUM_BLOCKS; i++) { + if (!quorum_slot_present(meta_super, i)) + continue; + ret = read_block(meta_fd, SCOUTFS_QUORUM_BLKNO + i, SCOUTFS_BLOCK_SM_SHIFT, + (void **)&qblk); + if (ret < 0) { + fprintf(stderr, "error reading quorum block for slot %u\n", i); + goto out; + } + + beg = &qblk->events[SCOUTFS_QUORUM_EVENT_BEGIN]; + end = &qblk->events[SCOUTFS_QUORUM_EVENT_END]; + + if (le64_to_cpu(beg->write_nr) > le64_to_cpu(end->write_nr)) { + fprintf(stderr, "mount in quorum slot %u could still be running.\n" + " begin event: write_nr %llu timestamp %llu.%08u\n" + " end event: write_nr %llu timestamp %llu.%08u\n", + i, le64_to_cpu(beg->write_nr), le64_to_cpu(beg->ts.sec), + le32_to_cpu(beg->ts.nsec), + le64_to_cpu(end->write_nr), le64_to_cpu(end->ts.sec), + le32_to_cpu(end->ts.nsec)); + ret = -EBUSY; + in_use = true; + goto out; + } + + free(qblk); + qblk = NULL; + } + + if (le64_to_cpu(meta_super->fmt_vers) != args->fmt_vers) { + meta_super->fmt_vers = cpu_to_le64(args->fmt_vers); + + ret = write_block(meta_fd, SCOUTFS_BLOCK_MAGIC_SUPER, meta_super->hdr.fsid, 1, + SCOUTFS_SUPER_BLKNO, SCOUTFS_BLOCK_SM_SHIFT, &meta_super->hdr); + if (ret) + goto out; + + wrote_meta = true; + } + + if (le64_to_cpu(data_super->fmt_vers) != args->fmt_vers) { + data_super->fmt_vers = cpu_to_le64(args->fmt_vers); + + ret = write_block(data_fd, SCOUTFS_BLOCK_MAGIC_SUPER, data_super->hdr.fsid, 1, + SCOUTFS_SUPER_BLKNO, SCOUTFS_BLOCK_SM_SHIFT, &data_super->hdr); + if (ret < 0 && wrote_meta) { + fprintf(stderr, "Error writing data super block after writing the meta\n" + "super block. The two super blocks may now be out of sync which\n" + "would prevent mounting. Correct the source of the write error\n" + "and retry changing the version to write both super blocks.\n"); + goto out; + } + } + + uuid_unparse(meta_super->uuid, uuid_str); + + printf("Successfully updated format version for scoutfs filesystem:\n" + " meta device path: %s\n" + " data device path: %s\n" + " fsid: %llx\n" + " uuid: %s\n" + " format version: %llu\n", + args->meta_device, + args->data_device, + le64_to_cpu(meta_super->hdr.fsid), + uuid_str, + le64_to_cpu(meta_super->fmt_vers)); + +out: + if (in_use) + fprintf(stderr, "The filesystem must be fully recovered and cleanly unmounted to change the format version\n"); + + if (qblk) + free(qblk); + if (meta_super) + free(meta_super); + if (data_super) + free(data_super); + if (meta_fd != -1) + close(meta_fd); + if (data_fd != -1) + close(data_fd); + return ret; +} + +static int parse_opt(int key, char *arg, struct argp_state *state) +{ + struct change_fmt_vers_args *args = state->input; + int ret; + + switch (key) { + case 'F': + args->offline = true; + break; + case 'V': + ret = parse_u64(arg, &args->fmt_vers); + if (ret) + return ret; + if (args->fmt_vers < SCOUTFS_FORMAT_VERSION_MIN || + args->fmt_vers > SCOUTFS_FORMAT_VERSION_MAX) + argp_error(state, "format-version %llu is outside supported range of %u-%u", + args->fmt_vers, SCOUTFS_FORMAT_VERSION_MIN, + SCOUTFS_FORMAT_VERSION_MAX); + break; + case ARGP_KEY_ARG: + if (!args->meta_device) + args->meta_device = strdup_or_error(state, arg); + else if (!args->data_device) + args->data_device = strdup_or_error(state, arg); + else + argp_error(state, "more than two device arguments given"); + break; + case ARGP_KEY_FINI: + if (!args->offline) + argp_error(state, "must specify --offline"); + if (!args->meta_device) + argp_error(state, "no metadata device argument given"); + if (!args->data_device) + argp_error(state, "no data device argument given"); + break; + default: + break; + } + + return 0; +} + +static struct argp_option options[] = { + { "offline", 'F', NULL, 0, "Write format version in offline device super blocks"}, + { "format-version", 'V', "VERS", 0, "Specify a format version within supported range ("SCOUTFS_FORMAT_VERSION_MIN_STR"-"SCOUTFS_FORMAT_VERSION_MAX_STR", default "SCOUTFS_FORMAT_VERSION_MAX_STR")"}, + { NULL } +}; + +static struct argp argp = { + options, + parse_opt, + "", + "Change format version of an existing ScoutFS filesystem" +}; + +static int change_fmt_vers_cmd(int argc, char *argv[]) +{ + struct change_fmt_vers_args change_fmt_vers_args = { + .offline = false, + .fmt_vers = SCOUTFS_FORMAT_VERSION_MAX, + }; + int ret; + + ret = argp_parse(&argp, argc, argv, 0, NULL, &change_fmt_vers_args); + if (ret) + return ret; + + return do_change_fmt_vers(&change_fmt_vers_args); +} + +static void __attribute__((constructor)) change_fmt_vers_ctor(void) +{ + cmd_register_argp("change-format-version", &argp, GROUP_CORE, change_fmt_vers_cmd); +} diff --git a/utils/src/cmd.c b/utils/src/cmd.c index ddd49710..10e10ca7 100644 --- a/utils/src/cmd.c +++ b/utils/src/cmd.c @@ -8,6 +8,7 @@ #include "cmd.h" #include "util.h" +#include "format.h" static struct argp_command { char *name; @@ -69,6 +70,9 @@ static void usage(void) fprintf(stderr, "Selected fs defaults to current working directory.\n"); fprintf(stderr, "See --help for more details.\n"); + fprintf(stderr, "\nSupported format version: %u-%u\n", + SCOUTFS_FORMAT_VERSION_MIN, SCOUTFS_FORMAT_VERSION_MAX); + fprintf(stderr, "\nCore admin:\n"); print_cmds_for_group(GROUP_CORE); fprintf(stderr, "\nAdditional Information:\n"); diff --git a/utils/src/mkfs.c b/utils/src/mkfs.c index 69999c16..cb8eb21e 100644 --- a/utils/src/mkfs.c +++ b/utils/src/mkfs.c @@ -110,6 +110,7 @@ struct mkfs_args { unsigned long long max_meta_size; unsigned long long max_data_size; u64 data_alloc_zone_blocks; + u64 fmt_vers; bool force; bool allow_small_size; int nr_slots; @@ -212,7 +213,7 @@ static int do_mkfs(struct mkfs_args *args) /* partially initialize the super so we can use it to init others */ memset(super, 0, SCOUTFS_BLOCK_SM_SIZE); - super->version = cpu_to_le64(SCOUTFS_INTEROP_VERSION); + super->fmt_vers = cpu_to_le64(args->fmt_vers); uuid_generate(super->uuid); super->next_ino = cpu_to_le64(round_up(SCOUTFS_ROOT_INO + 1, SCOUTFS_LOCK_INODE_GROUP_NR)); super->seq = cpu_to_le64(1); @@ -351,16 +352,16 @@ static int do_mkfs(struct mkfs_args *args) " meta device path: %s\n" " data device path: %s\n" " fsid: %llx\n" - " version: %llx\n" " uuid: %s\n" + " format version: %llu\n" " 64KB metadata blocks: "SIZE_FMT"\n" " 4KB data blocks: "SIZE_FMT"\n" " quorum slots: ", args->meta_device, args->data_device, le64_to_cpu(super->hdr.fsid), - le64_to_cpu(super->version), uuid_str, + le64_to_cpu(super->fmt_vers), SIZE_ARGS(le64_to_cpu(super->total_meta_blocks), SCOUTFS_BLOCK_LG_SIZE), SIZE_ARGS(le64_to_cpu(super->total_data_blocks), @@ -484,6 +485,16 @@ static int parse_opt(int key, char *arg, struct argp_state *state) case 'A': args->allow_small_size = true; break; + case 'V': + ret = parse_u64(arg, &args->fmt_vers); + if (ret) + return ret; + if (args->fmt_vers < SCOUTFS_FORMAT_VERSION_MIN || + args->fmt_vers > SCOUTFS_FORMAT_VERSION_MAX) + argp_error(state, "format-version %llu is outside supported range of %u-%u", + args->fmt_vers, SCOUTFS_FORMAT_VERSION_MIN, + SCOUTFS_FORMAT_VERSION_MAX); + break; case 'z': /* data-alloc-zone-blocks */ { ret = parse_u64(arg, &args->data_alloc_zone_blocks); @@ -527,6 +538,7 @@ static struct argp_option options[] = { { "max-meta-size", 'm', "SIZE", 0, "Use a size less than the base metadata device size (bytes or KMGTP units)"}, { "max-data-size", 'd', "SIZE", 0, "Use a size less than the base data device size (bytes or KMGTP units)"}, { "data-alloc-zone-blocks", 'z', "BLOCKS", 0, "Divide data device into block zones so each mounts writes to a zone (4KB blocks)"}, + { "format-version", 'V', "version", 0, "Specify a format version within supported range, ("SCOUTFS_FORMAT_VERSION_MIN_STR"-"SCOUTFS_FORMAT_VERSION_MAX_STR", default "SCOUTFS_FORMAT_VERSION_MAX_STR")"}, { NULL } }; @@ -539,7 +551,9 @@ static struct argp argp = { static int mkfs_cmd(int argc, char *argv[]) { - struct mkfs_args mkfs_args = {NULL,}; + struct mkfs_args mkfs_args = { + .fmt_vers = SCOUTFS_FORMAT_VERSION_MAX, + }; int ret; ret = argp_parse(&argp, argc, argv, 0, NULL, &mkfs_args); diff --git a/utils/src/print.c b/utils/src/print.c index bb566730..ba6195b8 100644 --- a/utils/src/print.c +++ b/utils/src/print.c @@ -935,8 +935,8 @@ static void print_super_block(struct scoutfs_super_block *super, u64 blkno) printf("super blkno %llu\n", blkno); print_block_header(&super->hdr, SCOUTFS_BLOCK_SM_SIZE); - printf(" version %llx uuid %s\n", - le64_to_cpu(super->version), uuid_str); + printf(" fmt_vers %llu uuid %s\n", + le64_to_cpu(super->fmt_vers), uuid_str); printf(" flags: 0x%016llx\n", le64_to_cpu(super->flags)); /* XXX these are all in a crazy order */ diff --git a/utils/src/util.c b/utils/src/util.c index 53a45ed2..0dc4a437 100644 --- a/utils/src/util.c +++ b/utils/src/util.c @@ -79,11 +79,16 @@ int read_block(int fd, u64 blkno, int shift, void **ret_val) void *buf; int ret; + buf = NULL; *ret_val = NULL; - buf = malloc(size); - if (!buf) - return -ENOMEM; + ret = posix_memalign(&buf, size, size); + if (ret != 0) { + ret = -errno; + fprintf(stderr, "%zu byte aligned buffer allocation failed: %s (%d)\n", + size, strerror(errno), errno); + return ret; + } ret = pread(fd, buf, size, blkno << shift); if (ret == -1) { @@ -136,7 +141,7 @@ int read_block_verify(int fd, u32 magic, u64 fsid, u64 blkno, int shift, void ** if (le32_to_cpu(hdr->magic) != magic) fprintf(stderr, "read blkno %llu has bad magic %08x != expected %08x\n", blkno, le32_to_cpu(hdr->magic), magic); - else if (fsid != 0 && le32_to_cpu(hdr->fsid) != fsid) + else if (fsid != 0 && le64_to_cpu(hdr->fsid) != fsid) fprintf(stderr, "read blkno %llu has bad fsid %016llx != expected %016llx\n", blkno, le64_to_cpu(hdr->fsid), fsid); else if (le32_to_cpu(hdr->blkno) != blkno)