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 <zab@versity.com>
This commit is contained in:
Zach Brown
2023-03-14 11:32:22 -07:00
parent 6ded240089
commit e7bd1b45dc
2 changed files with 287 additions and 0 deletions

View File

@@ -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

View File

@@ -0,0 +1,247 @@
#define _GNU_SOURCE /* O_DIRECT */
#include <unistd.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/time.h>
#include <uuid/uuid.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <assert.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <inttypes.h>
#include <argp.h>
#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);
}