diff --git a/kmod/src/alloc.c b/kmod/src/alloc.c index 1adbcd37..d62b2f58 100644 --- a/kmod/src/alloc.c +++ b/kmod/src/alloc.c @@ -977,6 +977,39 @@ int scoutfs_alloc_move(struct super_block *sb, struct scoutfs_alloc *alloc, return ret; } +/* + * Add new free space to an allocator. _ext_insert will make sure that it doesn't + * overlap with any existing extents. This is done by the server in a transaction that + * also updates total_*_blocks in the super so we don't verify. + */ +int scoutfs_alloc_insert(struct super_block *sb, struct scoutfs_alloc *alloc, + struct scoutfs_block_writer *wri, struct scoutfs_alloc_root *root, + u64 start, u64 len) +{ + struct alloc_ext_args args = { + .alloc = alloc, + .wri = wri, + .root = root, + .zone = SCOUTFS_FREE_EXTENT_BLKNO_ZONE, + }; + + return scoutfs_ext_insert(sb, &alloc_ext_ops, &args, start, len, 0, 0); +} + +int scoutfs_alloc_remove(struct super_block *sb, struct scoutfs_alloc *alloc, + struct scoutfs_block_writer *wri, struct scoutfs_alloc_root *root, + u64 start, u64 len) +{ + struct alloc_ext_args args = { + .alloc = alloc, + .wri = wri, + .root = root, + .zone = SCOUTFS_FREE_EXTENT_BLKNO_ZONE, + }; + + return scoutfs_ext_remove(sb, &alloc_ext_ops, &args, start, len); +} + /* * We only trim one block, instead of looping trimming all, because the * caller is assuming that we do a fixed amount of work when they check diff --git a/kmod/src/alloc.h b/kmod/src/alloc.h index 5a95d98c..9dcbd94c 100644 --- a/kmod/src/alloc.h +++ b/kmod/src/alloc.h @@ -132,6 +132,12 @@ int scoutfs_alloc_move(struct super_block *sb, struct scoutfs_alloc *alloc, struct scoutfs_alloc_root *dst, struct scoutfs_alloc_root *src, u64 total, __le64 *exclusive, __le64 *vacant, u64 zone_blocks); +int scoutfs_alloc_insert(struct super_block *sb, struct scoutfs_alloc *alloc, + struct scoutfs_block_writer *wri, struct scoutfs_alloc_root *root, + u64 start, u64 len); +int scoutfs_alloc_remove(struct super_block *sb, struct scoutfs_alloc *alloc, + struct scoutfs_block_writer *wri, struct scoutfs_alloc_root *root, + u64 start, u64 len); int scoutfs_alloc_fill_list(struct super_block *sb, struct scoutfs_alloc *alloc, diff --git a/kmod/src/client.c b/kmod/src/client.c index 9fcc2a2d..20dad9e0 100644 --- a/kmod/src/client.c +++ b/kmod/src/client.c @@ -297,6 +297,14 @@ int scoutfs_client_clear_volopt(struct super_block *sb, struct scoutfs_volume_op volopt, sizeof(*volopt), NULL, 0); } +int scoutfs_client_resize_devices(struct super_block *sb, struct scoutfs_net_resize_devices *nrd) +{ + struct client_info *client = SCOUTFS_SB(sb)->client_info; + + return scoutfs_net_sync_request(sb, client->conn, SCOUTFS_NET_CMD_RESIZE_DEVICES, + nrd, sizeof(*nrd), NULL, 0); +} + /* The client is receiving a invalidation request from the server */ static int client_lock(struct super_block *sb, struct scoutfs_net_connection *conn, u8 cmd, u64 id, diff --git a/kmod/src/client.h b/kmod/src/client.h index 1cbcbc1d..e62eccae 100644 --- a/kmod/src/client.h +++ b/kmod/src/client.h @@ -33,6 +33,7 @@ int scoutfs_client_open_ino_map(struct super_block *sb, u64 group_nr, int scoutfs_client_get_volopt(struct super_block *sb, struct scoutfs_volume_options *volopt); int scoutfs_client_set_volopt(struct super_block *sb, struct scoutfs_volume_options *volopt); int scoutfs_client_clear_volopt(struct super_block *sb, struct scoutfs_volume_options *volopt); +int scoutfs_client_resize_devices(struct super_block *sb, struct scoutfs_net_resize_devices *nrd); int scoutfs_client_setup(struct super_block *sb); void scoutfs_client_destroy(struct super_block *sb); diff --git a/kmod/src/format.h b/kmod/src/format.h index 39fdddd2..3f0fff1a 100644 --- a/kmod/src/format.h +++ b/kmod/src/format.h @@ -986,6 +986,7 @@ enum scoutfs_net_cmd { SCOUTFS_NET_CMD_GET_VOLOPT, SCOUTFS_NET_CMD_SET_VOLOPT, SCOUTFS_NET_CMD_CLEAR_VOLOPT, + SCOUTFS_NET_CMD_RESIZE_DEVICES, SCOUTFS_NET_CMD_FAREWELL, SCOUTFS_NET_CMD_UNKNOWN, }; @@ -1028,6 +1029,11 @@ struct scoutfs_net_roots { struct scoutfs_btree_root srch_root; }; +struct scoutfs_net_resize_devices { + __le64 new_total_meta_blocks; + __le64 new_total_data_blocks; +}; + struct scoutfs_net_lock { struct scoutfs_key key; __le64 write_seq; diff --git a/kmod/src/ioctl.c b/kmod/src/ioctl.c index cb3f4a4e..59092d89 100644 --- a/kmod/src/ioctl.c +++ b/kmod/src/ioctl.c @@ -867,13 +867,21 @@ static long scoutfs_ioc_statfs_more(struct file *file, unsigned long arg) { struct super_block *sb = file_inode(file)->i_sb; struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb); - struct scoutfs_super_block *super = &sbi->super; + struct scoutfs_super_block *super; struct scoutfs_ioctl_statfs_more sfm; int ret; if (get_user(sfm.valid_bytes, (__u64 __user *)arg)) return -EFAULT; + super = kzalloc(sizeof(struct scoutfs_super_block), GFP_NOFS); + if (!super) + return -ENOMEM; + + ret = scoutfs_read_super(sb, super); + if (ret) + goto out; + sfm.valid_bytes = min_t(u64, sfm.valid_bytes, sizeof(struct scoutfs_ioctl_statfs_more)); sfm.fsid = le64_to_cpu(super->hdr.fsid); @@ -884,12 +892,15 @@ static long scoutfs_ioc_statfs_more(struct file *file, unsigned long arg) ret = scoutfs_client_get_last_seq(sb, &sfm.committed_seq); if (ret) - return ret; + goto out; if (copy_to_user((void __user *)arg, &sfm, sfm.valid_bytes)) - return -EFAULT; - - return 0; + ret = -EFAULT; + else + ret = 0; +out: + kfree(super); + return ret; } struct copy_alloc_detail_args { @@ -993,6 +1004,37 @@ out: return ret; } +static long scoutfs_ioc_resize_devices(struct file *file, unsigned long arg) +{ + struct super_block *sb = file_inode(file)->i_sb; + struct scoutfs_ioctl_resize_devices __user *urd = (void __user *)arg; + struct scoutfs_ioctl_resize_devices rd; + struct scoutfs_net_resize_devices nrd; + int ret; + + if (!(file->f_mode & FMODE_READ)) { + ret = -EBADF; + goto out; + } + + if (!capable(CAP_SYS_ADMIN)) { + ret = -EPERM; + goto out; + } + + if (copy_from_user(&rd, urd, sizeof(rd))) { + ret = -EFAULT; + goto out; + } + + nrd.new_total_meta_blocks = cpu_to_le64(rd.new_total_meta_blocks); + nrd.new_total_data_blocks = cpu_to_le64(rd.new_total_data_blocks); + + ret = scoutfs_client_resize_devices(sb, &nrd); +out: + return ret; +} + long scoutfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { switch (cmd) { @@ -1022,6 +1064,8 @@ long scoutfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg) return scoutfs_ioc_alloc_detail(file, arg); case SCOUTFS_IOC_MOVE_BLOCKS: return scoutfs_ioc_move_blocks(file, arg); + case SCOUTFS_IOC_RESIZE_DEVICES: + return scoutfs_ioc_resize_devices(file, arg); } return -ENOTTY; diff --git a/kmod/src/ioctl.h b/kmod/src/ioctl.h index 446611e9..469551d9 100644 --- a/kmod/src/ioctl.h +++ b/kmod/src/ioctl.h @@ -477,4 +477,12 @@ struct scoutfs_ioctl_move_blocks { #define SCOUTFS_IOC_MOVE_BLOCKS _IOR(SCOUTFS_IOCTL_MAGIC, 13, \ struct scoutfs_ioctl_move_blocks) +struct scoutfs_ioctl_resize_devices { + __u64 new_total_meta_blocks; + __u64 new_total_data_blocks; +}; + +#define SCOUTFS_IOC_RESIZE_DEVICES \ + _IOR(SCOUTFS_IOCTL_MAGIC, 14, struct scoutfs_ioctl_resize_devices) + #endif diff --git a/kmod/src/server.c b/kmod/src/server.c index 93480a36..d605d0ca 100644 --- a/kmod/src/server.c +++ b/kmod/src/server.c @@ -2563,6 +2563,103 @@ out: return scoutfs_net_response(sb, conn, cmd, id, ret, NULL, 0); } +static u64 device_blocks(struct block_device *bdev, int shift) +{ + return i_size_read(bdev->bd_inode) >> shift; +} + +static int server_resize_devices(struct super_block *sb, struct scoutfs_net_connection *conn, + u8 cmd, u64 id, void *arg, u16 arg_len) +{ + DECLARE_SERVER_INFO(sb, server); + struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb); + struct scoutfs_super_block *super = &SCOUTFS_SB(sb)->super; + struct scoutfs_net_resize_devices *nrd; + u64 meta_tot; + u64 meta_start; + u64 meta_len; + u64 data_tot; + u64 data_start; + u64 data_len; + int ret; + int err; + + if (arg_len != sizeof(struct scoutfs_net_resize_devices)) { + ret = -EINVAL; + goto out; + } + nrd = arg; + + meta_tot = le64_to_cpu(nrd->new_total_meta_blocks); + data_tot = le64_to_cpu(nrd->new_total_data_blocks); + + scoutfs_server_hold_commit(sb); + mutex_lock(&server->alloc_mutex); + + if (meta_tot == le64_to_cpu(super->total_meta_blocks)) + meta_tot = 0; + if (data_tot == le64_to_cpu(super->total_data_blocks)) + data_tot = 0; + + if (!meta_tot && !data_tot) { + ret = 0; + goto unlock; + } + + /* we don't support shrinking */ + if ((meta_tot && (meta_tot < le64_to_cpu(super->total_meta_blocks))) || + (data_tot && (data_tot < le64_to_cpu(super->total_data_blocks)))) { + ret = -EINVAL; + goto unlock; + } + + /* must be within devices */ + if ((meta_tot > device_blocks(sbi->meta_bdev, SCOUTFS_BLOCK_LG_SHIFT)) || + (data_tot > device_blocks(sb->s_bdev, SCOUTFS_BLOCK_SM_SHIFT))) { + ret = -EINVAL; + goto unlock; + } + + /* extents are only used if _tot is set */ + meta_start = le64_to_cpu(super->total_meta_blocks); + meta_len = meta_tot - meta_start; + data_start = le64_to_cpu(super->total_data_blocks); + data_len = data_tot - data_start; + + if (meta_tot) { + ret = scoutfs_alloc_insert(sb, &server->alloc, &server->wri, + server->meta_avail, meta_start, meta_len); + if (ret < 0) + goto unlock; + } + + if (data_tot) { + ret = scoutfs_alloc_insert(sb, &server->alloc, &server->wri, + &super->data_alloc, data_start, data_len); + if (ret < 0) { + if (meta_tot) { + err = scoutfs_alloc_remove(sb, &server->alloc, &server->wri, + server->meta_avail, meta_start, + meta_len); + WARN_ON_ONCE(err); /* btree blocks are dirty.. really unlikely? */ + } + goto unlock; + } + } + + if (meta_tot) + super->total_meta_blocks = cpu_to_le64(meta_tot); + if (data_tot) + super->total_data_blocks = cpu_to_le64(data_tot); + + ret = 0; +unlock: + mutex_unlock(&server->alloc_mutex); + ret = scoutfs_server_apply_commit(sb, ret); +out: + return scoutfs_net_response(sb, conn, cmd, id, ret, NULL, 0); +}; + static void init_mounted_client_key(struct scoutfs_key *key, u64 rid) { *key = (struct scoutfs_key) { @@ -3199,6 +3296,7 @@ static scoutfs_net_request_t server_req_funcs[] = { [SCOUTFS_NET_CMD_GET_VOLOPT] = server_get_volopt, [SCOUTFS_NET_CMD_SET_VOLOPT] = server_set_volopt, [SCOUTFS_NET_CMD_CLEAR_VOLOPT] = server_clear_volopt, + [SCOUTFS_NET_CMD_RESIZE_DEVICES] = server_resize_devices, [SCOUTFS_NET_CMD_FAREWELL] = server_farewell, }; diff --git a/tests/golden/resize-devices b/tests/golden/resize-devices new file mode 100644 index 00000000..3b2529fe --- /dev/null +++ b/tests/golden/resize-devices @@ -0,0 +1,27 @@ +== make initial small fs +== 0s do nothing +== shrinking fails +resize_devices ioctl failed: Invalid argument (22) +scoutfs: resize-devices failed: Invalid argument (22) +resize_devices ioctl failed: Invalid argument (22) +scoutfs: resize-devices failed: Invalid argument (22) +resize_devices ioctl failed: Invalid argument (22) +scoutfs: resize-devices failed: Invalid argument (22) +== existing sizes do nothing +== growing outside device fails +resize_devices ioctl failed: Invalid argument (22) +scoutfs: resize-devices failed: Invalid argument (22) +resize_devices ioctl failed: Invalid argument (22) +scoutfs: resize-devices failed: Invalid argument (22) +resize_devices ioctl failed: Invalid argument (22) +scoutfs: resize-devices failed: Invalid argument (22) +== resizing meta works +== resizing data works +== shrinking back fails +resize_devices ioctl failed: Invalid argument (22) +scoutfs: resize-devices failed: Invalid argument (22) +resize_devices ioctl failed: Invalid argument (22) +scoutfs: resize-devices failed: Invalid argument (22) +== resizing again does nothing +== resizing to full works +== cleanup extra fs diff --git a/tests/sequence b/tests/sequence index b97e4847..17955e28 100644 --- a/tests/sequence +++ b/tests/sequence @@ -29,6 +29,7 @@ lock-conflicting-batch-commit.sh cross-mount-data-free.sh persistent-item-vers.sh setup-error-teardown.sh +resize-devices.sh fence-and-reclaim.sh orphan-inodes.sh mount-unmount-race.sh diff --git a/tests/tests/resize-devices.sh b/tests/tests/resize-devices.sh new file mode 100644 index 00000000..9bd91476 --- /dev/null +++ b/tests/tests/resize-devices.sh @@ -0,0 +1,149 @@ +# +# Some basic tests of online resizing metadata and data devices. +# + +statfs_total() { + local single="total_$1_blocks" + local mnt="$2" + + scoutfs statfs -s $single -p "$mnt" +} + +df_free() { + local md="$1" + local mnt="$2" + + scoutfs df -p "$mnt" | awk '($1 == "'$md'") { print $5; exit }' +} + +same_totals() { + cur_meta_tot=$(statfs_total meta "$SCR") + cur_data_tot=$(statfs_total data "$SCR") + + test "$cur_meta_tot" == "$exp_meta_tot" || \ + t_fail "cur total_meta_blocks $cur_meta_tot != expected $exp_meta_tot" + test "$cur_data_tot" == "$exp_data_tot" || \ + t_fail "cur total_data_blocks $cur_data_tot != expected $exp_data_tot" +} + +# +# make sure that the specified devices have grown by doubling. The +# total blocks can be tested exactly but the df reported total needs +# some slop to account for reserved blocks and concurrent allocation. +# +devices_grew() { + cur_meta_tot=$(statfs_total meta "$SCR") + cur_data_tot=$(statfs_total data "$SCR") + cur_meta_df=$(df_free MetaData "$SCR") + cur_data_df=$(df_free Data "$SCR") + + local grow_meta_tot=$(echo "$exp_meta_tot * 2" | bc) + local grow_data_tot=$(echo "$exp_data_tot * 2" | bc) + local grow_meta_df=$(echo "($exp_meta_df * 1.95)/1" | bc) + local grow_data_df=$(echo "($exp_data_df * 1.95)/1" | bc) + + if [ "$1" == "meta" ]; then + test "$cur_meta_tot" == "$grow_meta_tot" || \ + t_fail "cur total_meta_blocks $cur_meta_tot != grown $grow_meta_tot" + test "$cur_meta_df" -lt "$grow_meta_df" && \ + t_fail "cur meta df total $cur_meta_df < grown $grow_meta_df" + exp_meta_tot=$cur_meta_tot + exp_meta_df=$cur_meta_df + shift + fi + + if [ "$1" == "data" ]; then + test "$cur_data_tot" == "$grow_data_tot" || \ + t_fail "cur total_data_blocks $cur_data_tot != grown $grow_data_tot" + test "$cur_data_df" -lt "$grow_data_df" && \ + t_fail "cur data df total $cur_data_df < grown $grow_data_df" + exp_data_tot=$cur_data_tot + exp_data_df=$cur_data_df + fi +} + +# first calculate small mkfs based on device size +size_meta=$(blockdev --getsize64 "$T_EX_META_DEV") +size_data=$(blockdev --getsize64 "$T_EX_DATA_DEV") +quarter_meta=$(echo "$size_meta / 4" | bc) +quarter_data=$(echo "$size_data / 4" | bc) + +# XXX this is all pretty manual, would be nice to have helpers +echo "== make initial small fs" +scoutfs mkfs -A -f -Q 0,127.0.0.1,53000 -m $quarter_meta -d $quarter_data \ + "$T_EX_META_DEV" "$T_EX_DATA_DEV" > $T_TMP.mkfs.out 2>&1 || \ + t_fail "mkfs failed" +SCR="/mnt/scoutfs.enospc" +mkdir -p "$SCR" +mount -t scoutfs -o metadev_path=$T_EX_META_DEV,quorum_slot_nr=0 \ + "$T_EX_DATA_DEV" "$SCR" + +# then calculate sizes based on blocks that mkfs used +quarter_meta=$(echo "$(statfs_total meta "$SCR") * 64 * 1024" | bc) +quarter_data=$(echo "$(statfs_total data "$SCR") * 4 * 1024" | bc) +whole_meta=$(echo "$quarter_meta * 4" | bc) +whole_data=$(echo "$quarter_data * 4" | bc) +outsize_meta=$(echo "$whole_meta * 2" | bc) +outsize_data=$(echo "$whole_data * 2" | bc) +half_meta=$(echo "$whole_meta / 2" | bc) +half_data=$(echo "$whole_data / 2" | bc) +shrink_meta=$(echo "$quarter_meta / 2" | bc) +shrink_data=$(echo "$quarter_data / 2" | bc) + +# and save expected values for checks +exp_meta_tot=$(statfs_total meta "$SCR") +exp_meta_df=$(df_free MetaData "$SCR") +exp_data_tot=$(statfs_total data "$SCR") +exp_data_df=$(df_free Data "$SCR") + +echo "== 0s do nothing" +scoutfs resize-devices -p "$SCR" +scoutfs resize-devices -p "$SCR" -m 0 +scoutfs resize-devices -p "$SCR" -d 0 +scoutfs resize-devices -p "$SCR" -m 0 -d 0 + +echo "== shrinking fails" +scoutfs resize-devices -p "$SCR" -m $shrink_meta +scoutfs resize-devices -p "$SCR" -d $shrink_data +scoutfs resize-devices -p "$SCR" -m $shrink_meta -d $shrink_data +same_totals + +echo "== existing sizes do nothing" +scoutfs resize-devices -p "$SCR" -m $quarter_meta +scoutfs resize-devices -p "$SCR" -d $quarter_data +scoutfs resize-devices -p "$SCR" -m $quarter_meta -d $quarter_data +same_totals + +echo "== growing outside device fails" +scoutfs resize-devices -p "$SCR" -m $outsize_meta +scoutfs resize-devices -p "$SCR" -d $outsize_data +scoutfs resize-devices -p "$SCR" -m $outsize_meta -d $outsize_data +same_totals + +echo "== resizing meta works" +scoutfs resize-devices -p "$SCR" -m $half_meta +devices_grew meta + +echo "== resizing data works" +scoutfs resize-devices -p "$SCR" -d $half_data +devices_grew data + +echo "== shrinking back fails" +scoutfs resize-devices -p "$SCR" -m $quarter_meta +scoutfs resize-devices -p "$SCR" -m $quarter_data +same_totals + +echo "== resizing again does nothing" +scoutfs resize-devices -p "$SCR" -m $half_meta +scoutfs resize-devices -p "$SCR" -m $half_data +same_totals + +echo "== resizing to full works" +scoutfs resize-devices -p "$SCR" -m $whole_meta -d $whole_data +devices_grew meta data + +echo "== cleanup extra fs" +umount "$SCR" +rmdir "$SCR" + +t_pass diff --git a/utils/man/scoutfs.8 b/utils/man/scoutfs.8 index d7723302..25ee53c7 100644 --- a/utils/man/scoutfs.8 +++ b/utils/man/scoutfs.8 @@ -103,6 +103,63 @@ Ignore presence of existing data on the data and metadata devices. .PD .TP +.BI "resize-devices [-p|--path PATH] [-m|--meta-size SIZE] [-d|--data-size SIZE]" +.sp +Resize the metadata or data devices of a mounted ScoutFS filesystem. +.sp +ScoutFS metadata has free extent records and fields in the super block +that reflect the size of the devices in use. This command sends a +request to the server to change the size of the device that can be used +by updating free extents and setting the super block fields. +.sp +The specified sizes are in bytes and are translated into block counts. +If the specified sizes are not a multiple of the metadata or data block +sizes then a message is output and the resized size is truncated down to +the next whole block. Specifying either a size of 0 or the current +device size makes no change. The current size of the devices can be +seen, in units of their respective block sizes, in the total_meta_blocks +and total_data_blocks fields returned by the scoutfs statfs command (via +the statfs_more ioctl). +.sp +Shrinking is not supported. Specifying a smaller size for either device +will return an error and neither device will be resized. +.sp +Specifying a larger size will expand the initial size of the device that +will be used. Free space records are added for the expanded region and +can be used once the resizing transaction is complete. +.sp +The resizing action is performed in a transaction on the server. This +command will hang until a server is elected and running and can service +the reqeust. The server serializes any concurrent requests to resize. +.sp +The new sizes must fit within the current sizes of the mounted devices. +Presumably this command is being performed as part of a larger +coordinated resize of the underlying devices. The device must be +expanded before ScoutFS can use the larger device and ScoutFS must stop +using a region to shrink before it could be removed from the device +(which is not currently supported). +.sp +The resize will be committed by the server before the response is sent +to the client. The system can be using the new device size before the +result is communicated through the client and this command completes. +The client could crash and the server could still have performed the +resize. +.RS 1.0i +.PD 0 +.TP +.sp +.B "-p, --path PATH" +A path in the mounted ScoutFS filesystem which will have its devices +resized. +.TP +.B "-m, --meta-size SIZE" +.B "-d, --data-size SIZE" +The new size of the metadata or data device to use, in bytes. Size is given as +an integer followed by a units digit: "K", "M", "G", "T", "P", to denote +kibibytes, mebibytes, etc. +.RE +.PD + .BI "stat FILE [-s|--single-field FIELD-NAME]" .sp Display ScoutFS-specific metadata fields for the given file. diff --git a/utils/src/resize_devices.c b/utils/src/resize_devices.c new file mode 100644 index 00000000..bdf6659b --- /dev/null +++ b/utils/src/resize_devices.c @@ -0,0 +1,120 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sparse.h" +#include "parse.h" +#include "util.h" +#include "format.h" +#include "ioctl.h" +#include "cmd.h" + +struct resize_args { + char *path; + u64 meta_size; + u64 data_size; +}; + +static int do_resize_devices(struct resize_args *args) +{ + struct scoutfs_ioctl_resize_devices rd; + int ret; + int fd; + + if (args->meta_size & SCOUTFS_BLOCK_LG_MASK) { + printf("metadata device size %llu is not a multiple of %u metadata block size, truncating down to %llu byte size\n", + args->meta_size, SCOUTFS_BLOCK_LG_SIZE, + args->meta_size & ~(u64)SCOUTFS_BLOCK_LG_MASK); + } + + if (args->data_size & SCOUTFS_BLOCK_SM_MASK) { + printf("data device size %llu is not a multiple of %u data block size, truncating down to %llu byte size\n", + args->data_size, SCOUTFS_BLOCK_SM_SIZE, + args->data_size & ~(u64)SCOUTFS_BLOCK_SM_MASK); + } + + fd = get_path(args->path, O_RDONLY); + if (fd < 0) + return fd; + + rd.new_total_meta_blocks = args->meta_size >> SCOUTFS_BLOCK_LG_SHIFT; + rd.new_total_data_blocks = args->data_size >> SCOUTFS_BLOCK_SM_SHIFT; + + ret = ioctl(fd, SCOUTFS_IOC_RESIZE_DEVICES, &rd); + if (ret < 0) { + ret = -errno; + fprintf(stderr, "resize_devices ioctl failed: %s (%d)\n", strerror(errno), errno); + } + + close(fd); + return ret; +}; + +static int parse_opt(int key, char *arg, struct argp_state *state) +{ + struct resize_args *args = state->input; + int ret; + + switch (key) { + case 'm': /* meta-size */ + { + ret = parse_human(arg, &args->meta_size); + if (ret) + return ret; + break; + } + case 'd': /* data-size */ + { + ret = parse_human(arg, &args->data_size); + if (ret) + return ret; + break; + } + case 'p': + args->path = strdup_or_error(state, arg); + break; + default: + break; + } + + return 0; +} + +static struct argp_option options[] = { + { "path", 'p', "PATH", 0, "Path to ScoutFS filesystem"}, + { "meta-size", 'm', "SIZE", 0, "New metadata device size (bytes or KMGTP units)"}, + { "data-size", 'd', "SIZE", 0, "New data device size (bytes or KMGTP units)"}, + { NULL } +}; + +static struct argp argp = { + options, + parse_opt, + "", + "Online resize of metadata and/or data devices", +}; + +static int resize_devices_cmd(int argc, char **argv) +{ + + struct resize_args resize_args = {NULL,}; + int ret; + + ret = argp_parse(&argp, argc, argv, 0, NULL, &resize_args); + if (ret) + return ret; + + return do_resize_devices(&resize_args); +} + +static void __attribute__((constructor)) read_xattr_totals_ctor(void) +{ + cmd_register_argp("resize-devices", &argp, GROUP_CORE, resize_devices_cmd); +}