mirror of
https://github.com/versity/scoutfs.git
synced 2026-01-08 13:01:23 +00:00
Merge pull request #5 from versity/zab/move_blocks_ioctl
Zab/move blocks ioctl
This commit is contained in:
236
kmod/src/data.c
236
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 :/
|
||||
*/
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/file.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/compiler.h>
|
||||
#include <linux/uio.h>
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
|
||||
20
kmod/src/util.h
Normal file
20
kmod/src/util.h
Normal file
@@ -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
|
||||
33
tests/golden/move-blocks
Normal file
33
tests/golden/move-blocks
Normal file
@@ -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
|
||||
@@ -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
|
||||
|
||||
169
tests/tests/move-blocks.sh
Normal file
169
tests/tests/move-blocks.sh
Normal file
@@ -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
|
||||
161
utils/src/move_blocks.c
Normal file
161
utils/src/move_blocks.c
Normal file
@@ -0,0 +1,161 @@
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <getopt.h>
|
||||
#include <assert.h>
|
||||
#include <argp.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
Reference in New Issue
Block a user