From e7bd1b45dc6efb47864c6d03e352a80d62355150 Mon Sep 17 00:00:00 2001 From: Zach Brown Date: Tue, 14 Mar 2023 11:32:22 -0700 Subject: [PATCH] Add prepare-empty-data-device scoutfs command Add a command for writing a super block to a new data device after reading the metadata device to ensure that there's no existing data on the old data device. Signed-off-by: Zach Brown --- utils/man/scoutfs.8 | 40 +++++ utils/src/prepare_empty_data_device.c | 247 ++++++++++++++++++++++++++ 2 files changed, 287 insertions(+) create mode 100644 utils/src/prepare_empty_data_device.c diff --git a/utils/man/scoutfs.8 b/utils/man/scoutfs.8 index ccf54c26..8cfd2af1 100644 --- a/utils/man/scoutfs.8 +++ b/utils/man/scoutfs.8 @@ -328,6 +328,46 @@ The range of supported versions is visible in the output of .RE .PD +.TP +.BI "prepare-empty-data-device {-c|--check} META-DEVICE DATA-DEVICE" +.sp +Prepare an unused device for use as the data device for an existing file +system. This will write an initialized super block to the specified +data device, destroying any existing contents. The specified metadata +device will not be modified. The file system must be fully unmounted +and any client mount recovery must be complete. +.sp +The existing metadata device is read to ensure that it's safe to stop +using the old data device. The data block allocators must indicate that +all data blocks are free. If there are still data blocks referenced by +files then the command will fail. The contents of these files must be +freed for the command to proceed. +.sp +A new super block is written to the new data device. The device can +then be used as the data device to mount the file system. As this +switch is made all client mounts must refer to the new device. The old +device is not modified and still contains a valid data super block that +could be mounted, creating data device writes that wouldn't be read by +mounts using the new device. +.sp +The number of data blocks available to the file system will not change +as the new data device is used. The new device must be large enough to +store all the data blocks that were available on the old device. If the +new device is larger then its added capacity can be used by growing the +new data device with the resize-devices command once it is mounted. +.RS 1.0i +.PD 0 +.TP +.sp +.B "-c, --check" +Only check for errors that would prevent a new empty data device from +being used. No changes will be made to the data device. If the data +device is provided then its size will be checked to make sure that it is +large enough. This can be used to test the metadata for data references +before destroying an old empty data device. +.RE +.PD + .TP .BI "print {-S|--skip-likely-huge} META-DEVICE" .sp diff --git a/utils/src/prepare_empty_data_device.c b/utils/src/prepare_empty_data_device.c new file mode 100644 index 00000000..b9732d1a --- /dev/null +++ b/utils/src/prepare_empty_data_device.c @@ -0,0 +1,247 @@ +#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 prepare_empty_data_dev_args { + char *meta_device; + char *data_device; + bool check; +}; + +static int do_prepare_empty_data_dev(struct prepare_empty_data_dev_args *args) +{ + struct scoutfs_super_block *meta_super = NULL; + struct scoutfs_super_block *data_super = NULL; + char uuid_str[37]; + int meta_fd = -1; + int data_fd = -1; + u64 data_blocks; + u64 data_size; + u64 in_use; + int ret; + + ret = posix_memalign((void **)&data_super, SCOUTFS_BLOCK_SM_SIZE, SCOUTFS_BLOCK_SM_SIZE); + if (ret < 0) { + ret = -errno; + fprintf(stderr, "failed to allocate data super block: %s (%d)\n", + strerror(errno), errno); + goto out; + } + + meta_fd = open(args->meta_device, O_DIRECT | O_SYNC | O_RDONLY | 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; + } + + 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 = meta_super_in_use(meta_fd, meta_super); + if (ret < 0) { + if (ret == -EBUSY) + fprintf(stderr, "The filesystem must be fully recovered and cleanly unmounted to determine if the data device is empty.\n"); + goto out; + } + + in_use = (le64_to_cpu(meta_super->total_data_blocks) - SCOUTFS_DATA_DEV_START_BLKNO) - + le64_to_cpu(meta_super->data_alloc.total_len); + if (in_use) { + fprintf(stderr, "Data block allocator metadata shows "SIZE_FMT" data blocks used by files. They must be removed, truncated, or released before a new empty data device can be used.\n", + SIZE_ARGS(in_use, SCOUTFS_BLOCK_SM_SIZE)); + ret = -EINVAL; + goto out; + } + + if (args->data_device) { + data_fd = open(args->data_device, O_DIRECT | O_EXCL | + (args->check ? O_RDONLY : O_RDWR | O_SYNC)); + 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 = get_device_size(args->data_device, data_fd, &data_size); + if (ret < 0) + goto out; + + data_blocks = data_size >> SCOUTFS_BLOCK_SM_SHIFT; + + if (data_blocks < le64_to_cpu(meta_super->total_data_blocks)) { + fprintf(stderr, "new data device %s of size "BASE_SIZE_FMT" has %llu 4KiB blocks, it needs at least "SIZE_FMT" blocks.\n", + args->data_device, + BASE_SIZE_ARGS(data_size), + data_blocks, + SIZE_ARGS(le64_to_cpu(meta_super->total_data_blocks), + SCOUTFS_BLOCK_SM_SIZE)); + ret = -EINVAL; + goto out; + } + } + + if (args->check) { + ret = 0; + goto out; + } + + /* the data device superblock only needs fs identifying fields */ + memset(data_super, 0, sizeof(struct scoutfs_super_block)); + data_super->id = meta_super->id; + data_super->fmt_vers = meta_super->fmt_vers; + data_super->flags = meta_super->flags &~ cpu_to_le64(SCOUTFS_FLAG_IS_META_BDEV); + memcpy(data_super->uuid, meta_super->uuid,sizeof(data_super->uuid)); + data_super->seq = meta_super->seq; + data_super->total_meta_blocks = meta_super->total_meta_blocks; + data_super->total_data_blocks = meta_super->total_data_blocks; + + ret = write_block(data_fd, SCOUTFS_BLOCK_MAGIC_SUPER, meta_super->hdr.fsid, 1, + SCOUTFS_SUPER_BLKNO, SCOUTFS_BLOCK_SM_SHIFT, &data_super->hdr); + if (ret < 0) { + ret = -errno; + fprintf(stderr, "Error writing super block to new data device '%s': %s (%d)\n", + args->data_device, strerror(errno), errno); + goto out; + } + + uuid_unparse(meta_super->uuid, uuid_str); + + printf("Successfully initialized empty data device for scoutfs filesystem:\n" + " meta device path: %s\n" + " data device path: %s\n" + " fsid: %llx\n" + " uuid: %s\n" + " format version: %llu\n" + " 64KB metadata blocks: "SIZE_FMT"\n" + " 4KB data blocks: "SIZE_FMT"\n", + args->meta_device, + args->data_device, + le64_to_cpu(meta_super->hdr.fsid), + uuid_str, + le64_to_cpu(meta_super->fmt_vers), + SIZE_ARGS(le64_to_cpu(meta_super->total_meta_blocks), + SCOUTFS_BLOCK_LG_SIZE), + SIZE_ARGS(le64_to_cpu(meta_super->total_data_blocks), + SCOUTFS_BLOCK_SM_SIZE)); + + ret = 0; +out: + if (args->check) { + if (ret == 0) + printf("All checks passed.\n"); + else + printf("Errors were found that must be addressed before a new empty data device could be prepared and used.\n"); + } + + 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 prepare_empty_data_dev_args *args = state->input; + + switch (key) { + case 'c': + args->check = true; + 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->meta_device) + argp_error(state, "no metadata device argument given"); + if (!args->data_device && !args->check) + argp_error(state, "no data device argument given"); + break; + default: + break; + } + + return 0; +} + +static struct argp_option options[] = { + { "check", 'c', NULL, 0, "Only check for errors and do not write", }, + { NULL } +}; + +static struct argp argp = { + options, + parse_opt, + "META-DEVICE DATA-DEVICE", + "Prepare empty data device for use with an existing ScoutFS filesystem" +}; + +static int prepare_empty_data_dev_cmd(int argc, char *argv[]) +{ + struct prepare_empty_data_dev_args prepare_empty_data_dev_args = { + .check = false, + }; + int ret; + + ret = argp_parse(&argp, argc, argv, 0, NULL, &prepare_empty_data_dev_args); + if (ret) + return ret; + + return do_prepare_empty_data_dev(&prepare_empty_data_dev_args); +} + +static void __attribute__((constructor)) prepare_empty_data_dev_ctor(void) +{ + cmd_register_argp("prepare-empty-data-device", &argp, GROUP_CORE, + prepare_empty_data_dev_cmd); +}