diff --git a/kmod/src/data.c b/kmod/src/data.c index 6e110e3a..52a42f38 100644 --- a/kmod/src/data.c +++ b/kmod/src/data.c @@ -39,6 +39,7 @@ #include "msg.h" #include "count.h" #include "ext.h" +#include "util.h" /* * We want to amortize work done after dirtying the shared transaction @@ -1103,6 +1104,241 @@ out: return ret; } +/* + * We're using truncate_inode_pages_range to maintain consistency + * between the page cache and extents that just changed. We have to + * call with full aligned page offsets or it thinks that it should leave + * behind a zeroed partial page. + */ +static void truncate_inode_pages_extent(struct inode *inode, u64 start, u64 len) +{ + truncate_inode_pages_range(&inode->i_data, + start << SCOUTFS_BLOCK_SM_SHIFT, + ((start + len) << SCOUTFS_BLOCK_SM_SHIFT) - 1); +} + +/* + * Move extents from one file to another. The behaviour is more fully + * explained above the move_blocks ioctl argument structure definition. + * + * The caller has processed the ioctl args and performed the most basic + * inode checks, but we perform more detailed inode checks once we have + * the inode lock and refreshed inodes. Our job is to safely lock the + * two files and move the extents. + */ +#define MOVE_DATA_EXTENTS_PER_HOLD 16 +int scoutfs_data_move_blocks(struct inode *from, u64 from_off, + u64 byte_len, struct inode *to, u64 to_off) +{ + struct scoutfs_inode_info *from_si = SCOUTFS_I(from); + struct scoutfs_inode_info *to_si = SCOUTFS_I(to); + struct super_block *sb = from->i_sb; + struct scoutfs_lock *from_lock = NULL; + struct scoutfs_lock *to_lock = NULL; + struct data_ext_args from_args; + struct data_ext_args to_args; + struct scoutfs_extent ext; + LIST_HEAD(locks); + bool done = false; + loff_t from_size; + loff_t to_size; + u64 from_offline; + u64 to_offline; + u64 from_start; + u64 to_start; + u64 from_iblock; + u64 to_iblock; + u64 count; + u64 junk; + u64 seq; + u64 map; + u64 len; + int ret; + int err; + int i; + + lock_two_nondirectories(from, to); + + ret = scoutfs_lock_inodes(sb, SCOUTFS_LOCK_WRITE, + SCOUTFS_LKF_REFRESH_INODE, from, &from_lock, + to, &to_lock, NULL, NULL, NULL, NULL); + if (ret) + goto out; + + if ((from_off & SCOUTFS_BLOCK_SM_MASK) || + (to_off & SCOUTFS_BLOCK_SM_MASK) || + ((byte_len & SCOUTFS_BLOCK_SM_MASK) && + (from_off + byte_len != i_size_read(from)))) { + ret = -EINVAL; + goto out; + } + + from_iblock = from_off >> SCOUTFS_BLOCK_SM_SHIFT; + count = (byte_len + SCOUTFS_BLOCK_SM_MASK) >> SCOUTFS_BLOCK_SM_SHIFT; + to_iblock = to_off >> SCOUTFS_BLOCK_SM_SHIFT; + + if (S_ISDIR(from->i_mode) || S_ISDIR(to->i_mode)) { + ret = -EISDIR; + goto out; + } + + if (!S_ISREG(from->i_mode) || !S_ISREG(to->i_mode)) { + ret = -EINVAL; + goto out; + } + + ret = inode_permission(from, MAY_WRITE) ?: + inode_permission(to, MAY_WRITE); + if (ret < 0) + goto out; + + /* can't stage once data_version changes */ + scoutfs_inode_get_onoff(from, &junk, &from_offline); + scoutfs_inode_get_onoff(to, &junk, &to_offline); + if (from_offline || to_offline) { + ret = -ENODATA; + goto out; + } + + from_args = (struct data_ext_args) { + .ino = scoutfs_ino(from), + .inode = from, + .lock = from_lock, + }; + + to_args = (struct data_ext_args) { + .ino = scoutfs_ino(to), + .inode = to, + .lock = to_lock, + }; + + inode_dio_wait(from); + inode_dio_wait(to); + + ret = filemap_write_and_wait_range(&from->i_data, from_off, + from_off + byte_len - 1); + if (ret < 0) + goto out; + + for (;;) { + ret = scoutfs_inode_index_start(sb, &seq) ?: + scoutfs_inode_index_prepare(sb, &locks, from, true) ?: + scoutfs_inode_index_prepare(sb, &locks, to, true) ?: + scoutfs_inode_index_try_lock_hold(sb, &locks, seq, + SIC_EXACT(1, 1)); + if (ret > 0) + continue; + if (ret < 0) + goto out; + + ret = scoutfs_dirty_inode_item(from, from_lock) ?: + scoutfs_dirty_inode_item(to, to_lock); + if (ret < 0) + goto out; + + down_write_two(&from_si->extent_sem, &to_si->extent_sem); + + /* arbitrarily limit the number of extents per trans hold */ + for (i = 0; i < MOVE_DATA_EXTENTS_PER_HOLD; i++) { + /* find the next extent to move */ + ret = scoutfs_ext_next(sb, &data_ext_ops, &from_args, + from_iblock, 1, &ext); + if (ret < 0) { + if (ret == -ENOENT) { + done = true; + ret = 0; + } + break; + } + + /* only move extents within count and i_size */ + if (ext.start >= from_iblock + count || + ext.start >= i_size_read(from)) { + done = true; + ret = 0; + break; + } + + from_start = max(ext.start, from_iblock); + map = ext.map + (from_start - ext.start); + len = min3(from_iblock + count, + round_up((u64)i_size_read(from), + SCOUTFS_BLOCK_SM_SIZE), + ext.start + ext.len) - from_start; + + to_start = to_iblock + (from_start - from_iblock); + + /* insert the new, fails if it overlaps */ + ret = scoutfs_ext_insert(sb, &data_ext_ops, &to_args, + to_start, len, + map, ext.flags); + if (ret < 0) + break; + + /* remove the old, possibly splitting */ + ret = scoutfs_ext_set(sb, &data_ext_ops, &from_args, + from_start, len, 0, 0); + if (ret < 0) { + /* remove inserted new on err */ + err = scoutfs_ext_remove(sb, &data_ext_ops, + &to_args, to_start, + len); + BUG_ON(err); /* XXX inconsistent */ + break; + } + + trace_scoutfs_data_move_blocks(sb, scoutfs_ino(from), + from_start, len, map, + ext.flags, + scoutfs_ino(to), + to_start); + + /* moved extent might extend i_size */ + to_size = (to_start + len) << SCOUTFS_BLOCK_SM_SHIFT; + if (to_size > i_size_read(to)) { + /* while maintaining final partial */ + from_size = (from_start + len) << + SCOUTFS_BLOCK_SM_SHIFT; + if (from_size > i_size_read(from)) + to_size -= from_size - + i_size_read(from); + i_size_write(to, to_size); + } + } + + + up_write(&from_si->extent_sem); + up_write(&to_si->extent_sem); + + from->i_ctime = from->i_mtime = + to->i_ctime = to->i_mtime = CURRENT_TIME; + scoutfs_inode_inc_data_version(from); + scoutfs_inode_inc_data_version(to); + scoutfs_inode_set_data_seq(from); + scoutfs_inode_set_data_seq(to); + + scoutfs_update_inode_item(from, from_lock, &locks); + scoutfs_update_inode_item(to, to_lock, &locks); + scoutfs_release_trans(sb); + scoutfs_inode_index_unlock(sb, &locks); + + if (ret < 0 || done) + break; + } + + /* remove any cached pages from old extents */ + truncate_inode_pages_extent(from, from_iblock, count); + truncate_inode_pages_extent(to, to_iblock, count); + +out: + scoutfs_unlock(sb, from_lock, SCOUTFS_LOCK_WRITE); + scoutfs_unlock(sb, to_lock, SCOUTFS_LOCK_WRITE); + + unlock_two_nondirectories(from, to); + + return ret; +} + /* * This copies to userspace :/ */ diff --git a/kmod/src/data.h b/kmod/src/data.h index 09a64fe7..4668eca3 100644 --- a/kmod/src/data.h +++ b/kmod/src/data.h @@ -58,6 +58,8 @@ int scoutfs_data_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo, long scoutfs_fallocate(struct file *file, int mode, loff_t offset, loff_t len); int scoutfs_data_init_offline_extent(struct inode *inode, u64 size, struct scoutfs_lock *lock); +int scoutfs_data_move_blocks(struct inode *from, u64 from_off, + u64 byte_len, struct inode *to, u64 to_off); int scoutfs_data_wait_check(struct inode *inode, loff_t pos, loff_t len, u8 sef, u8 op, struct scoutfs_data_wait *ow, diff --git a/kmod/src/ioctl.c b/kmod/src/ioctl.c index 3fcaae34..96d02787 100644 --- a/kmod/src/ioctl.c +++ b/kmod/src/ioctl.c @@ -12,6 +12,7 @@ */ #include #include +#include #include #include #include @@ -937,6 +938,54 @@ static long scoutfs_ioc_alloc_detail(struct file *file, unsigned long arg) args.copied; } +static long scoutfs_ioc_move_blocks(struct file *file, unsigned long arg) +{ + struct inode *to = file_inode(file); + struct super_block *sb = to->i_sb; + struct scoutfs_ioctl_move_blocks __user *umb = (void __user *)arg; + struct scoutfs_ioctl_move_blocks mb; + struct file *from_file; + struct inode *from; + int ret; + + if (copy_from_user(&mb, umb, sizeof(mb))) + return -EFAULT; + + if (mb.len == 0) + return 0; + + if (mb.from_off + mb.len < mb.from_off || + mb.to_off + mb.len < mb.to_off) + return -EOVERFLOW; + + from_file = fget(mb.from_fd); + if (!from_file) + return -EBADF; + from = file_inode(from_file); + + if (from == to) { + ret = -EINVAL; + goto out; + } + + if (from->i_sb != sb) { + ret = -EXDEV; + goto out; + } + + ret = mnt_want_write_file(file); + if (ret < 0) + goto out; + + ret = scoutfs_data_move_blocks(from, mb.from_off, mb.len, + to, mb.to_off); + mnt_drop_write_file(file); +out: + fput(from_file); + + return ret; +} + long scoutfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { switch (cmd) { @@ -964,6 +1013,8 @@ long scoutfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg) return scoutfs_ioc_data_wait_err(file, arg); case SCOUTFS_IOC_ALLOC_DETAIL: return scoutfs_ioc_alloc_detail(file, arg); + case SCOUTFS_IOC_MOVE_BLOCKS: + return scoutfs_ioc_move_blocks(file, arg); } return -ENOTTY; diff --git a/kmod/src/ioctl.h b/kmod/src/ioctl.h index 081baa3e..3eaa36f0 100644 --- a/kmod/src/ioctl.h +++ b/kmod/src/ioctl.h @@ -395,9 +395,6 @@ struct scoutfs_ioctl_data_wait_err { struct scoutfs_ioctl_data_wait_err) -#define SCOUTFS_IOC_ALLOC_DETAIL _IOR(SCOUTFS_IOCTL_MAGIC, 12, \ - struct scoutfs_ioctl_alloc_detail) - struct scoutfs_ioctl_alloc_detail { __u64 entries_ptr; __u64 entries_nr; @@ -413,4 +410,58 @@ struct scoutfs_ioctl_alloc_detail_entry { __u8 __pad[6]; }; +#define SCOUTFS_IOC_ALLOC_DETAIL _IOR(SCOUTFS_IOCTL_MAGIC, 12, \ + struct scoutfs_ioctl_alloc_detail) + +/* + * Move extents from one regular file to another at a different offset, + * on the same file system. + * + * from_fd specifies the source file and the ioctl is called on the + * destination file. Both files must have write access. from_off + * specifies the byte offset in the source, to_off is the byte offset in + * the destination, and len is the number of bytes in the region to + * move. All of the offsets and lengths must be in multiples of 4KB, + * except in the case where the from_off + len ends at the i_size of the + * source file. + * + * This interface only moves extents which are block granular, it does + * not perform RMW of sub-block byte extents and it does not overwrite + * existing extents in the destination. It will split extents in the + * source. + * + * Only extents within i_size on the source are moved. The destination + * i_size will be updated if extents are moved beyond its current + * i_size. The i_size update will maintain final partial blocks in the + * source. + * + * It will return an error if either of the files have offline extents. + * It will return 0 when all of the extents in the source region have + * been moved to the destination. Moving extents updates the ctime, + * mtime, meta_seq, data_seq, and data_version fields of both the source + * and destination inodes. If an error is returned then partial + * progress may have been made and inode fields may have been updated. + * + * Errors specific to this interface include: + * + * EINVAL: from_off, len, or to_off aren't a multiple of 4KB; the source + * and destination files are the same inode; either the source or + * destination is not a regular file; the destination file has + * an existing overlapping extent. + * EOVERFLOW: either from_off + len or to_off + len exceeded 64bits. + * EBADF: from_fd isn't a valid open file descriptor. + * EXDEV: the source and destination files are in different filesystems. + * EISDIR: either the source or destination is a directory. + * ENODATA: either the source or destination file have offline extents. + */ +struct scoutfs_ioctl_move_blocks { + __u64 from_fd; + __u64 from_off; + __u64 len; + __u64 to_off; +}; + +#define SCOUTFS_IOC_MOVE_BLOCKS _IOR(SCOUTFS_IOCTL_MAGIC, 13, \ + struct scoutfs_ioctl_move_blocks) + #endif diff --git a/kmod/src/scoutfs_trace.h b/kmod/src/scoutfs_trace.h index a2e1cae8..5262126b 100644 --- a/kmod/src/scoutfs_trace.h +++ b/kmod/src/scoutfs_trace.h @@ -169,6 +169,40 @@ TRACE_EVENT(scoutfs_data_fallocate, __entry->len, __entry->ret) ); +TRACE_EVENT(scoutfs_data_move_blocks, + TP_PROTO(struct super_block *sb, u64 from_ino, u64 from_start, u64 len, + u64 map, u8 flags, u64 to_ino, u64 to_start), + + TP_ARGS(sb, from_ino, from_start, len, map, flags, to_ino, to_start), + + TP_STRUCT__entry( + SCSB_TRACE_FIELDS + __field(__u64, from_ino) + __field(__u64, from_start) + __field(__u64, len) + __field(__u64, map) + __field(__u8, flags) + __field(__u64, to_ino) + __field(__u64, to_start) + ), + + TP_fast_assign( + SCSB_TRACE_ASSIGN(sb); + __entry->from_ino = from_ino; + __entry->from_start = from_start; + __entry->len = len; + __entry->map = map; + __entry->flags = flags; + __entry->to_ino = to_ino; + __entry->to_start = to_start; + ), + + TP_printk(SCSBF" from_ino %llu from_start %llu len %llu map %llu flags 0x%x to_ino %llu to_start %llu\n", + SCSB_TRACE_ARGS, __entry->from_ino, __entry->from_start, + __entry->len, __entry->map, __entry->flags, __entry->to_ino, + __entry->to_start) +); + TRACE_EVENT(scoutfs_data_fiemap, TP_PROTO(struct super_block *sb, __u64 start, __u64 len, int ret), diff --git a/kmod/src/util.h b/kmod/src/util.h new file mode 100644 index 00000000..20d9db79 --- /dev/null +++ b/kmod/src/util.h @@ -0,0 +1,20 @@ +#ifndef _SCOUTFS_UTIL_H_ +#define _SCOUTFS_UTIL_H_ + +/* + * Little utility helpers that probably belong upstream. + */ + +static inline void down_write_two(struct rw_semaphore *a, + struct rw_semaphore *b) +{ + BUG_ON(a == b); + + if (a > b) + swap(a, b); + + down_write(a); + down_write_nested(b, SINGLE_DEPTH_NESTING); +} + +#endif diff --git a/tests/golden/move-blocks b/tests/golden/move-blocks new file mode 100644 index 00000000..ad04c62d --- /dev/null +++ b/tests/golden/move-blocks @@ -0,0 +1,33 @@ +== build test files +== wrapped offsets should fail +ioctl failed on '/mnt/test/test/move-blocks/to': Value too large for defined data type (75) +scoutfs: move-blocks failed: Value too large for defined data type (75) +ioctl failed on '/mnt/test/test/move-blocks/to': Value too large for defined data type (75) +scoutfs: move-blocks failed: Value too large for defined data type (75) +== specifying same file fails +ioctl failed on '/mnt/test/test/move-blocks/hardlink': Invalid argument (22) +scoutfs: move-blocks failed: Invalid argument (22) +== specifying files in other file systems fails +ioctl failed on '/mnt/test/test/move-blocks/to': Invalid cross-device link (18) +scoutfs: move-blocks failed: Invalid cross-device link (18) +== offsets must be multiples of 4KB +ioctl failed on '/mnt/test/test/move-blocks/to': Invalid argument (22) +scoutfs: move-blocks failed: Invalid argument (22) +ioctl failed on '/mnt/test/test/move-blocks/to': Invalid argument (22) +scoutfs: move-blocks failed: Invalid argument (22) +ioctl failed on '/mnt/test/test/move-blocks/to': Invalid argument (22) +scoutfs: move-blocks failed: Invalid argument (22) +== can't move onto existing extent +ioctl failed on '/mnt/test/test/move-blocks/to': Invalid argument (22) +scoutfs: move-blocks failed: Invalid argument (22) +== can't move between files with offline extents +ioctl failed on '/mnt/test/test/move-blocks/to': No data available (61) +scoutfs: move-blocks failed: No data available (61) +ioctl failed on '/mnt/test/test/move-blocks/to': No data available (61) +scoutfs: move-blocks failed: No data available (61) +== basic moves work +== moving final partial block sets partial i_size +123 +== moving updates inode fields +== moving blocks backwards works +== combine many files into one diff --git a/tests/sequence b/tests/sequence index 4c7d0cdb..8cd44c87 100644 --- a/tests/sequence +++ b/tests/sequence @@ -6,6 +6,7 @@ simple-staging.sh simple-release-extents.sh setattr_more.sh offline-extent-waiting.sh +move-blocks.sh srch-basic-functionality.sh simple-xattr-unit.sh lock-refleak.sh diff --git a/tests/tests/move-blocks.sh b/tests/tests/move-blocks.sh new file mode 100644 index 00000000..c930589c --- /dev/null +++ b/tests/tests/move-blocks.sh @@ -0,0 +1,169 @@ +# +# test MOVE_BLOCKS ioctl, mostly basic error testing and functionality, +# but a bit of expected use. +# + +t_require_commands scoutfs dd + +FROM="$T_D0/from" +TO="$T_D0/to" +HARD="$T_D0/hardlink" +OTHER="$T_TMP.other" + +BLOCKS=8 +BS=4096 +PART=123 +LEN=$(((BS * BLOCKS) + PART)) +PIECES=8 + +regenerate_files() { + rm -f "$FROM" + rm -f "$TO" + dd if=/dev/urandom of="$FROM" bs=$LEN count=1 status=none + touch "$TO" +} + +set_updated_fields() { + local arr="$1" + local path="$2" + + eval $arr["ctime"]="$(stat -c '%Z' "$path")" + eval $arr["mtime"]="$(stat -c '%Y' "$path")" + eval $arr["data_version"]="$(scoutfs stat -s data_version "$path")" + eval $arr["meta_seq"]="$(scoutfs stat -s meta_seq "$path")" + eval $arr["data_seq"]="$(scoutfs stat -s data_seq "$path")" +} + +# +# before moving extents manually copy the byte regions so that we have +# expected good file contents to compare to. We know that the byte +# regions are 4KB block aligned (with an allowance for a len that ends +# on from i_size). +# +move_and_compare() { + local from="$1" + local from_off="$2" + local from_blk="$((from_off / BS))" + local len="$3" + local blocks="$(((len + BS - 1) / BS))" + local to="$4" + local to_off="$5" + local to_blk="$((to_off / BS))" + + local right_start=$((from_blk + blocks)) + local from_size=$(stat -c "%s" "$from") + local from_blocks=$(( (from_size + BS - 1) / BS )) + local right_len=$((from_blocks - right_start)) + + # copying around instead of punching hole + dd if="$from" of="$from.expected" bs="$BS" \ + skip=0 seek=0 count="$from_blk" \ + status=none + dd if="$from" of="$from.expected" bs="$BS" \ + skip="$right_start" seek="$right_start" count="$right_len" \ + status=none conv=notrunc + # moving doesn't truncate, expect full size when no data + truncate -s "$from_size" "$from.expected" + + cp "$to" "$to.expected" + dd if="$from" of="$to.expected" bs="$BS" \ + skip="$from_blk" seek="$to_blk" count="$blocks" \ + status=none conv=notrunc + + scoutfs move-blocks "$from" -f "$from_off" -l "$len" "$to" -t "$to_off" \ + 2>&1 | t_filter_fs + + cmp "$from" "$from.expected" + cmp "$to" "$to.expected" +} + +echo "== build test files" +regenerate_files +touch "$OTHER" +ln "$FROM" "$HARD" + +echo "== wrapped offsets should fail" +HUGE=0x8000000000000000 +scoutfs move-blocks "$FROM" -f "$HUGE" -l "$HUGE" "$TO" -t 0 2>&1 | t_filter_fs +scoutfs move-blocks "$FROM" -f 0 -l "$HUGE" "$TO" -t "$HUGE" 2>&1 | t_filter_fs + +echo "== specifying same file fails" +scoutfs move-blocks "$FROM" -f 0 -l "$BS" "$HARD" -t 0 2>&1 | t_filter_fs + +echo "== specifying files in other file systems fails" +scoutfs move-blocks "$OTHER" -f 0 -l "$BS" "$TO" -t 0 2>&1 | t_filter_fs + +echo "== offsets must be multiples of 4KB" +scoutfs move-blocks "$FROM" -f 1 -l "$BS" "$TO" -t 0 2>&1 | t_filter_fs +scoutfs move-blocks "$FROM" -f 0 -l 1 "$TO" -t 0 2>&1 | t_filter_fs +scoutfs move-blocks "$FROM" -f 0 -l "$BS" "$TO" -t 1 2>&1 | t_filter_fs + +echo "== can't move onto existing extent" +dd if=/dev/urandom of="$TO" bs=$BS count=1 status=none +scoutfs move-blocks "$FROM" -f 0 -l "$BS" "$TO" -t 0 2>&1 | t_filter_fs + +echo "== can't move between files with offline extents" +dd if=/dev/zero of="$TO" bs=$BS count=1 status=none +vers=$(scoutfs stat -s data_version "$TO") +scoutfs release "$TO" -V "$vers" -o 0 -l $BS +scoutfs move-blocks "$FROM" -f 0 -l "$BS" "$TO" -t 0 2>&1 | t_filter_fs +regenerate_files +vers=$(scoutfs stat -s data_version "$FROM") +scoutfs release "$FROM" -V "$vers" -o 0 -l $BS +scoutfs move-blocks "$FROM" -f 0 -l "$BS" "$TO" -t 0 2>&1 | t_filter_fs +regenerate_files + +echo "== basic moves work" +move_and_compare "$FROM" 0 "$BS" "$TO" 0 +regenerate_files +move_and_compare "$FROM" 0 "$BS" "$TO" "$BS" +regenerate_files +move_and_compare "$FROM" 0 "$LEN" "$TO" 0 +regenerate_files + +echo "== moving final partial block sets partial i_size" +move_and_compare "$FROM" $((LEN - PART)) "$PART" "$TO" 0 +stat -c '%s' "$TO" +regenerate_files + +echo "== moving updates inode fields" +declare -A from_before from_after to_before to_after +set_updated_fields from_before "$FROM" +set_updated_fields to_before "$TO" +t_quiet sync +sleep 1 +move_and_compare "$FROM" 0 "$BS" "$TO" 0 +set_updated_fields from_after "$FROM" +set_updated_fields to_after "$TO" +for k in ${!from_after[@]}; do + if [ "${from_before[$k]}" == "${from_after[$k]}" ]; then + echo "move didn't change from $k ${from_before[$k]}" + fi + if [ "${to_before[$k]}" == "${to_after[$k]}" ]; then + echo "move didn't change to $k ${to_before[$k]}" + fi +done +regenerate_files + +echo "== moving blocks backwards works" +cp "$FROM" "$FROM.orig" +move_and_compare "$FROM" $((LEN - PART)) "$PART" "$TO" $((LEN - PART)) +for i in $(seq $((BLOCKS - 1)) -1 0); do + move_and_compare "$FROM" $((i * BS)) "$BS" "$TO" $((i * BS)) +done +cmp "$TO" "$FROM.orig" +regenerate_files + +echo "== combine many files into one" +for i in $(seq 0 $((PIECES - 1))); do + dd if=/dev/urandom of="$FROM.$i" bs=$BS count=$BLOCKS status=none + cat "$FROM.$i" >> "$TO.large" + move_and_compare "$FROM.$i" 0 "$((BS * BLOCKS))" \ + "$TO" $((i * BS * BLOCKS)) +done +((i++)) +cat "$FROM" >> "$TO.large" +move_and_compare "$FROM" 0 "$LEN" "$TO" $((i * BS * BLOCKS)) +cmp "$TO.large" "$TO" + +t_pass diff --git a/utils/src/move_blocks.c b/utils/src/move_blocks.c new file mode 100644 index 00000000..9cc85d96 --- /dev/null +++ b/utils/src/move_blocks.c @@ -0,0 +1,161 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sparse.h" +#include "util.h" +#include "format.h" +#include "ioctl.h" +#include "cmd.h" +#include "parse.h" + +struct move_blocks_args { + char *from_path; + u64 from_offset; + u64 length; + char *to_path; + u64 to_offset; + + unsigned from_off_set:1, + len_set:1, + to_off_set:1; +}; + +static int do_move_blocks(struct move_blocks_args *args) +{ + struct scoutfs_ioctl_move_blocks mb; + int from_fd = -1; + int to_fd = -1; + int ret; + + from_fd = open(args->from_path, O_RDWR); + if (from_fd < 0) { + ret = -errno; + fprintf(stderr, "failed to open '%s': %s (%d)\n", + args->from_path, strerror(errno), errno); + goto out; + } + + to_fd = open(args->to_path, O_RDWR); + if (to_fd < 0) { + ret = -errno; + fprintf(stderr, "failed to open '%s': %s (%d)\n", + args->to_path, strerror(errno), errno); + goto out; + } + + mb.from_fd = from_fd; + mb.from_off = args->from_offset; + mb.len = args->length; + mb.to_off = args->to_offset; + + ret = ioctl(to_fd, SCOUTFS_IOC_MOVE_BLOCKS, &mb); + if (ret < 0) { + ret = -errno; + fprintf(stderr, "ioctl failed on '%s': %s (%d)\n", + args->to_path, strerror(errno), errno); + } + +out: + if (from_fd >= 0) + close(from_fd); + if (to_fd >= 0) + close(to_fd); + + return ret; +} + +static int parse_move_blocks_opts(int key, char *arg, struct argp_state *state) +{ + struct move_blocks_args *args = state->input; + int ret; + + switch (key) { + case 'f': + ret = parse_u64(arg, &args->from_offset); + if (ret) + return ret; + args->from_off_set = 1; + break; + case 'l': + ret = parse_human(arg, &args->length); + if (ret) + return ret; + args->len_set = 1; + break; + case 't': + ret = parse_human(arg, &args->to_offset); + if (ret) + return ret; + args->to_off_set = 1; + break; + case ARGP_KEY_ARG: + if (args->to_path) + argp_error(state, "more than two file path arguments given"); + if (args->from_path) + args->to_path = strdup_or_error(state, arg); + else + args->from_path = strdup_or_error(state, arg); + break; + case ARGP_KEY_FINI: + if (!args->from_path) + argp_error(state, "must provide from file path"); + if (!args->to_path) + argp_error(state, "must provide to file path"); + if (!args->from_off_set) + argp_error(state, "must provide from file offset --from-offset"); + if (!args->len_set) + argp_error(state, "must provide region length --length"); + if (!args->to_off_set) + argp_error(state, "must provide to file offset --to-offset"); + break; + default: + break; + } + + return 0; +} + +static struct argp_option move_blocks_options[] = { + { "from-offset", 'f', "OFFSET", 0, + "Byte offset in from file of region to move [Required]"}, + { "length", 'l', "LENGTH", 0, + "Length in bytes of region to move between files [Required]"}, + { "to-offset", 't', "OFFSET", 0, + "Byte offset in to file where region will be moved to [Required]"}, + { NULL } +}; + +static struct argp move_blocks_argp = { + move_blocks_options, + parse_move_blocks_opts, + "FROM_FILE --from-offset OFFSET --length LENGTH TO_FILE --to-offset OFFSET", + "Move a fixed-size region of extents from one regular file to another", +}; + +static int move_blocks_cmd(int argc, char **argv) +{ + struct move_blocks_args args = {NULL}; + int ret; + + ret = argp_parse(&move_blocks_argp, argc, argv, 0, NULL, &args); + if (ret) + return ret; + + return do_move_blocks(&args); +} + +static void __attribute__((constructor)) move_blocks_ctor(void) +{ + cmd_register_argp("move-blocks", &move_blocks_argp, GROUP_AGENT, + move_blocks_cmd); +}