From 0deb232d3f15676b461fd73191cca2829c78cc26 Mon Sep 17 00:00:00 2001 From: Andy Grover Date: Sat, 20 Mar 2021 11:37:55 -0700 Subject: [PATCH] Support O_TMPFILE and allow MOVE_BLOCKS into released extents Support O_TMPFILE: Create an unlinked file and put it on the orphan list. If it ever gains a link, take it off the orphan list. Change MOVE_BLOCKS ioctl to allow moving blocks into offline extent ranges. Ioctl callers must set a new flag to enable this operation mode. RH-compat: tmpfile support it actually backported by RH into 3.10 kernel. We need to use some of their kabi-maintaining wrappers to use it: use a struct inode_operations_wrapper instead of base struct inode_operations, set S_IOPS_WRAPPER flag in i_flags. This lets RH's modified vfs_tmpfile() find our tmpfile fn pointer. Add a test that tests both creating tmpfiles as well as moving their contents into a destination file via MOVE_BLOCKS. xfstests common/004 now runs because tmpfile is supported. Signed-off-by: Andy Grover --- kmod/src/data.c | 65 ++++++++++++---- kmod/src/data.h | 3 +- kmod/src/dir.c | 57 +++++++++++++- kmod/src/dir.h | 2 +- kmod/src/ext.c | 6 +- kmod/src/ext.h | 1 + kmod/src/inode.c | 18 ++++- kmod/src/inode.h | 2 + kmod/src/ioctl.c | 8 +- kmod/src/ioctl.h | 42 ++++++---- tests/Makefile | 3 +- tests/golden/stage-tmpfile | 18 +++++ tests/golden/xfstests | 4 +- tests/sequence | 1 + tests/src/stage_tmpfile.c | 145 +++++++++++++++++++++++++++++++++++ tests/tests/stage-tmpfile.sh | 15 ++++ utils/src/move_blocks.c | 2 +- 17 files changed, 349 insertions(+), 43 deletions(-) create mode 100644 tests/golden/stage-tmpfile create mode 100644 tests/src/stage_tmpfile.c create mode 100644 tests/tests/stage-tmpfile.sh diff --git a/kmod/src/data.c b/kmod/src/data.c index 33061d8d..7a1d8585 100644 --- a/kmod/src/data.c +++ b/kmod/src/data.c @@ -1135,7 +1135,8 @@ static void truncate_inode_pages_extent(struct inode *inode, u64 start, u64 len) */ #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) + u64 byte_len, struct inode *to, u64 to_off, bool is_stage, + u64 data_version) { struct scoutfs_inode_info *from_si = SCOUTFS_I(from); struct scoutfs_inode_info *to_si = SCOUTFS_I(to); @@ -1145,6 +1146,7 @@ int scoutfs_data_move_blocks(struct inode *from, u64 from_off, struct data_ext_args from_args; struct data_ext_args to_args; struct scoutfs_extent ext; + struct timespec cur_time; LIST_HEAD(locks); bool done = false; loff_t from_size; @@ -1180,6 +1182,11 @@ int scoutfs_data_move_blocks(struct inode *from, u64 from_off, goto out; } + if (is_stage && (data_version != SCOUTFS_I(to)->data_version)) { + ret = -ESTALE; + 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; @@ -1202,7 +1209,7 @@ int scoutfs_data_move_blocks(struct inode *from, u64 from_off, /* 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) { + if (from_offline || (to_offline && !is_stage)) { ret = -ENODATA; goto out; } @@ -1246,6 +1253,8 @@ int scoutfs_data_move_blocks(struct inode *from, u64 from_off, /* arbitrarily limit the number of extents per trans hold */ for (i = 0; i < MOVE_DATA_EXTENTS_PER_HOLD; i++) { + struct scoutfs_extent off_ext; + /* find the next extent to move */ ret = scoutfs_ext_next(sb, &data_ext_ops, &from_args, from_iblock, 1, &ext); @@ -1274,10 +1283,27 @@ int scoutfs_data_move_blocks(struct inode *from, u64 from_off, 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 (is_stage) { + ret = scoutfs_ext_next(sb, &data_ext_ops, &to_args, + to_iblock, 1, &off_ext); + if (ret) + break; + + if (!scoutfs_ext_inside(to_start, len, &off_ext) || + !(off_ext.flags & SEF_OFFLINE)) { + ret = -EINVAL; + break; + } + + ret = scoutfs_ext_set(sb, &data_ext_ops, &to_args, + to_start, len, + map, ext.flags); + } else { + /* 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; @@ -1285,10 +1311,18 @@ int scoutfs_data_move_blocks(struct inode *from, u64 from_off, 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); + if (is_stage) { + /* re-mark dest range as offline */ + WARN_ON_ONCE(!(off_ext.flags & SEF_OFFLINE)); + err = scoutfs_ext_set(sb, &data_ext_ops, &to_args, + to_start, len, + 0, off_ext.flags); + } else { + /* remove inserted new on err */ + err = scoutfs_ext_remove(sb, &data_ext_ops, + &to_args, to_start, + len); + } BUG_ON(err); /* XXX inconsistent */ break; } @@ -1316,12 +1350,15 @@ int scoutfs_data_move_blocks(struct inode *from, u64 from_off, 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; + cur_time = CURRENT_TIME; + if (!is_stage) { + to->i_ctime = to->i_mtime = cur_time; + scoutfs_inode_inc_data_version(to); + scoutfs_inode_set_data_seq(to); + } + from->i_ctime = from->i_mtime = cur_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); diff --git a/kmod/src/data.h b/kmod/src/data.h index 4668eca3..4f51a8c2 100644 --- a/kmod/src/data.h +++ b/kmod/src/data.h @@ -59,7 +59,8 @@ 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); + u64 byte_len, struct inode *to, u64 to_off, bool to_stage, + u64 data_version); 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/dir.c b/kmod/src/dir.c index 0bfc0e7b..42c70a4a 100644 --- a/kmod/src/dir.c +++ b/kmod/src/dir.c @@ -813,6 +813,7 @@ static int scoutfs_link(struct dentry *old_dentry, struct scoutfs_lock *dir_lock; struct scoutfs_lock *inode_lock = NULL; LIST_HEAD(ind_locks); + bool del_orphan; u64 dir_size; u64 ind_seq; u64 hash; @@ -841,6 +842,8 @@ static int scoutfs_link(struct dentry *old_dentry, goto out_unlock; dir_size = i_size_read(dir) + dentry->d_name.len; + del_orphan = (inode->i_nlink == 0); + retry: ret = scoutfs_inode_index_start(sb, &ind_seq) ?: scoutfs_inode_index_prepare(sb, &ind_locks, dir, false) ?: @@ -855,6 +858,12 @@ retry: if (ret) goto out; + if (del_orphan) { + ret = scoutfs_orphan_dirty(sb, scoutfs_ino(inode)); + if (ret) + goto out; + } + pos = SCOUTFS_I(dir)->next_readdir_pos++; ret = add_entry_items(sb, scoutfs_ino(dir), hash, pos, @@ -870,6 +879,11 @@ retry: inode->i_ctime = dir->i_mtime; inc_nlink(inode); + if (del_orphan) { + ret = scoutfs_orphan_delete(sb, scoutfs_ino(inode)); + WARN_ON_ONCE(ret); + } + scoutfs_update_inode_item(inode, inode_lock, &ind_locks); scoutfs_update_inode_item(dir, dir_lock, &ind_locks); @@ -1760,6 +1774,42 @@ static int scoutfs_dir_open(struct inode *inode, struct file *file) } #endif +static int scoutfs_tmpfile(struct inode *dir, struct dentry *dentry, umode_t mode) +{ + struct super_block *sb = dir->i_sb; + struct inode *inode = NULL; + struct scoutfs_lock *dir_lock = NULL; + struct scoutfs_lock *inode_lock = NULL; + LIST_HEAD(ind_locks); + int ret; + + if (dentry->d_name.len > SCOUTFS_NAME_LEN) + return -ENAMETOOLONG; + + inode = lock_hold_create(dir, dentry, mode, 0, + &dir_lock, &inode_lock, &ind_locks); + if (IS_ERR(inode)) + return PTR_ERR(inode); + + inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME; + insert_inode_hash(inode); + d_tmpfile(dentry, inode); + + scoutfs_update_inode_item(inode, inode_lock, &ind_locks); + scoutfs_update_inode_item(dir, dir_lock, &ind_locks); + scoutfs_inode_index_unlock(sb, &ind_locks); + + ret = scoutfs_orphan_inode(inode); + WARN_ON_ONCE(ret); /* XXX returning error but items deleted */ + + scoutfs_release_trans(sb); + scoutfs_inode_index_unlock(sb, &ind_locks); + scoutfs_unlock(sb, dir_lock, SCOUTFS_LOCK_WRITE); + scoutfs_unlock(sb, inode_lock, SCOUTFS_LOCK_WRITE); + + return ret; +} + const struct file_operations scoutfs_dir_fops = { .KC_FOP_READDIR = scoutfs_readdir, #ifdef KC_FMODE_KABI_ITERATE @@ -1770,7 +1820,10 @@ const struct file_operations scoutfs_dir_fops = { .llseek = generic_file_llseek, }; -const struct inode_operations scoutfs_dir_iops = { + + +const struct inode_operations_wrapper scoutfs_dir_iops = { + .ops = { .lookup = scoutfs_lookup, .mknod = scoutfs_mknod, .create = scoutfs_create, @@ -1787,6 +1840,8 @@ const struct inode_operations scoutfs_dir_iops = { .removexattr = scoutfs_removexattr, .symlink = scoutfs_symlink, .permission = scoutfs_permission, + }, + .tmpfile = scoutfs_tmpfile, }; void scoutfs_dir_exit(void) diff --git a/kmod/src/dir.h b/kmod/src/dir.h index ee43930e..1f3bd44c 100644 --- a/kmod/src/dir.h +++ b/kmod/src/dir.h @@ -5,7 +5,7 @@ #include "lock.h" extern const struct file_operations scoutfs_dir_fops; -extern const struct inode_operations scoutfs_dir_iops; +extern const struct inode_operations_wrapper scoutfs_dir_iops; extern const struct inode_operations scoutfs_symlink_iops; struct scoutfs_link_backref_entry { diff --git a/kmod/src/ext.c b/kmod/src/ext.c index f87c064d..fdb1198a 100644 --- a/kmod/src/ext.c +++ b/kmod/src/ext.c @@ -38,7 +38,7 @@ static bool ext_overlap(struct scoutfs_extent *ext, u64 start, u64 len) return !(e_end < start || ext->start > end); } -static bool ext_inside(u64 start, u64 len, struct scoutfs_extent *out) +bool scoutfs_ext_inside(u64 start, u64 len, struct scoutfs_extent *out) { u64 in_end = start + len - 1; u64 out_end = out->start + out->len - 1; @@ -241,7 +241,7 @@ int scoutfs_ext_remove(struct super_block *sb, struct scoutfs_ext_ops *ops, goto out; /* removed extent must be entirely within found */ - if (!ext_inside(start, len, &found)) { + if (!scoutfs_ext_inside(start, len, &found)) { ret = -EINVAL; goto out; } @@ -341,7 +341,7 @@ int scoutfs_ext_set(struct super_block *sb, struct scoutfs_ext_ops *ops, if (ret == 0 && ext_overlap(&found, start, len)) { /* set extent must be entirely within found */ - if (!ext_inside(start, len, &found)) { + if (!scoutfs_ext_inside(start, len, &found)) { ret = -EINVAL; goto out; } diff --git a/kmod/src/ext.h b/kmod/src/ext.h index 31dbd57a..d826d21a 100644 --- a/kmod/src/ext.h +++ b/kmod/src/ext.h @@ -31,5 +31,6 @@ int scoutfs_ext_alloc(struct super_block *sb, struct scoutfs_ext_ops *ops, struct scoutfs_extent *ext); int scoutfs_ext_set(struct super_block *sb, struct scoutfs_ext_ops *ops, void *arg, u64 start, u64 len, u64 map, u8 flags); +bool scoutfs_ext_inside(u64 start, u64 len, struct scoutfs_extent *out); #endif diff --git a/kmod/src/inode.c b/kmod/src/inode.c index 8f16da36..42761a3f 100644 --- a/kmod/src/inode.c +++ b/kmod/src/inode.c @@ -182,7 +182,8 @@ static void set_inode_ops(struct inode *inode) inode->i_fop = &scoutfs_file_fops; break; case S_IFDIR: - inode->i_op = &scoutfs_dir_iops; + inode->i_op = &scoutfs_dir_iops.ops; + inode->i_flags |= S_IOPS_WRAPPER; inode->i_fop = &scoutfs_dir_fops; break; case S_IFLNK: @@ -1417,7 +1418,18 @@ static void init_orphan_key(struct scoutfs_key *key, u64 rid, u64 ino) }; } -static int remove_orphan_item(struct super_block *sb, u64 ino) +int scoutfs_orphan_dirty(struct super_block *sb, u64 ino) +{ + struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb); + struct scoutfs_lock *lock = sbi->rid_lock; + struct scoutfs_key key; + + init_orphan_key(&key, sbi->rid, ino); + + return scoutfs_item_dirty(sb, &key, lock); +} + +int scoutfs_orphan_delete(struct super_block *sb, u64 ino) { struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb); struct scoutfs_lock *lock = sbi->rid_lock; @@ -1516,7 +1528,7 @@ retry: if (ret) goto out; - ret = remove_orphan_item(sb, ino); + ret = scoutfs_orphan_delete(sb, ino); out: if (release) scoutfs_release_trans(sb); diff --git a/kmod/src/inode.h b/kmod/src/inode.h index e37382c4..ad517752 100644 --- a/kmod/src/inode.h +++ b/kmod/src/inode.h @@ -114,6 +114,8 @@ int scoutfs_getattr(struct vfsmount *mnt, struct dentry *dentry, int scoutfs_setattr(struct dentry *dentry, struct iattr *attr); int scoutfs_scan_orphans(struct super_block *sb); +int scoutfs_orphan_dirty(struct super_block *sb, u64 ino); +int scoutfs_orphan_delete(struct super_block *sb, u64 ino); void scoutfs_inode_queue_writeback(struct inode *inode); int scoutfs_inode_walk_writeback(struct super_block *sb, bool write); diff --git a/kmod/src/ioctl.c b/kmod/src/ioctl.c index 013778cf..b323b9a1 100644 --- a/kmod/src/ioctl.c +++ b/kmod/src/ioctl.c @@ -972,12 +972,18 @@ static long scoutfs_ioc_move_blocks(struct file *file, unsigned long arg) goto out; } + if (mb.flags & SCOUTFS_IOC_MB_UNKNOWN) { + ret = -EINVAL; + 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); + to, mb.to_off, !!(mb.flags & SCOUTFS_IOC_MB_STAGE), + mb.data_version); mnt_drop_write_file(file); out: fput(from_file); diff --git a/kmod/src/ioctl.h b/kmod/src/ioctl.h index 3eaa36f0..ebdc8d6d 100644 --- a/kmod/src/ioctl.h +++ b/kmod/src/ioctl.h @@ -259,7 +259,7 @@ struct scoutfs_ioctl_data_waiting { __u8 _pad[6]; }; -#define SCOUTFS_IOC_DATA_WAITING_FLAGS_UNKNOWN (U8_MAX << 0) +#define SCOUTFS_IOC_DATA_WAITING_FLAGS_UNKNOWN (U64_MAX << 0) #define SCOUTFS_IOC_DATA_WAITING _IOR(SCOUTFS_IOCTL_MAGIC, 6, \ struct scoutfs_ioctl_data_waiting) @@ -279,7 +279,7 @@ struct scoutfs_ioctl_setattr_more { }; #define SCOUTFS_IOC_SETATTR_MORE_OFFLINE (1 << 0) -#define SCOUTFS_IOC_SETATTR_MORE_UNKNOWN (U8_MAX << 1) +#define SCOUTFS_IOC_SETATTR_MORE_UNKNOWN (U64_MAX << 1) #define SCOUTFS_IOC_SETATTR_MORE _IOW(SCOUTFS_IOCTL_MAGIC, 7, \ struct scoutfs_ioctl_setattr_more) @@ -418,12 +418,13 @@ struct scoutfs_ioctl_alloc_detail_entry { * 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. + * 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. data_version is only used when STAGE flag is set (see below). flags + * field is currently only used to optionally specify STAGE behavior. * * This interface only moves extents which are block granular, it does * not perform RMW of sub-block byte extents and it does not overwrite @@ -435,30 +436,41 @@ struct scoutfs_ioctl_alloc_detail_entry { * 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 + * If STAGE flag is not set, 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. * + * If STAGE flag is set, as above except destination range must be in an + * offline extent. Fields are updated only for source inode. + * * 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. + * an existing overlapping extent (if STAGE flag not set); the + * destination range is not in an offline extent (if STAGE set). * 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. + * ENODATA: either the source or destination file have offline extents and + * STAGE flag is not set. + * ESTALE: data_version does not match destination data_version. */ +#define SCOUTFS_IOC_MB_STAGE (1 << 0) +#define SCOUTFS_IOC_MB_UNKNOWN (U64_MAX << 1) + struct scoutfs_ioctl_move_blocks { __u64 from_fd; __u64 from_off; __u64 len; __u64 to_off; + __u64 data_version; + __u64 flags; }; #define SCOUTFS_IOC_MOVE_BLOCKS _IOR(SCOUTFS_IOCTL_MAGIC, 13, \ diff --git a/tests/Makefile b/tests/Makefile index 16f88e6e..9a640bc4 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,4 +1,4 @@ -CFLAGS := -Wall -O2 -Werror -D_FILE_OFFSET_BITS=64 -fno-strict-aliasing +CFLAGS := -Wall -O2 -Werror -D_FILE_OFFSET_BITS=64 -fno-strict-aliasing -I ../kmod/src SHELL := /usr/bin/bash # each binary command is built from a single .c file @@ -6,6 +6,7 @@ BIN := src/createmany \ src/dumb_setxattr \ src/handle_cat \ src/bulk_create_paths \ + src/stage_tmpfile \ src/find_xattrs DEPS := $(wildcard src/*.d) diff --git a/tests/golden/stage-tmpfile b/tests/golden/stage-tmpfile new file mode 100644 index 00000000..71496330 --- /dev/null +++ b/tests/golden/stage-tmpfile @@ -0,0 +1,18 @@ +total file size 33669120 +00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA| +* +00400000 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 |BBBBBBBBBBBBBBBB| +* +00801000 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 |CCCCCCCCCCCCCCCC| +* +00c03000 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 |DDDDDDDDDDDDDDDD| +* +01006000 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 |EEEEEEEEEEEEEEEE| +* +0140a000 46 46 46 46 46 46 46 46 46 46 46 46 46 46 46 46 |FFFFFFFFFFFFFFFF| +* +0180f000 47 47 47 47 47 47 47 47 47 47 47 47 47 47 47 47 |GGGGGGGGGGGGGGGG| +* +01c15000 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 |HHHHHHHHHHHHHHHH| +* +0201c000 diff --git a/tests/golden/xfstests b/tests/golden/xfstests index bd09d504..bd40dd55 100644 --- a/tests/golden/xfstests +++ b/tests/golden/xfstests @@ -1,6 +1,7 @@ Ran: generic/001 generic/002 +generic/004 generic/005 generic/006 generic/007 @@ -73,7 +74,6 @@ generic/376 generic/377 Not run: -generic/004 generic/008 generic/009 generic/012 @@ -278,4 +278,4 @@ shared/004 shared/032 shared/051 shared/289 -Passed all 72 tests +Passed all 73 tests diff --git a/tests/sequence b/tests/sequence index 764ec501..fe988b3a 100644 --- a/tests/sequence +++ b/tests/sequence @@ -18,6 +18,7 @@ createmany-large-names.sh createmany-rename-large-dir.sh stage-release-race-alloc.sh stage-multi-part.sh +stage-tmpfile.sh basic-posix-consistency.sh dirent-consistency.sh lock-ex-race-processes.sh diff --git a/tests/src/stage_tmpfile.c b/tests/src/stage_tmpfile.c new file mode 100644 index 00000000..be98c731 --- /dev/null +++ b/tests/src/stage_tmpfile.c @@ -0,0 +1,145 @@ +/* + * Exercise O_TMPFILE creation as well as staging from tmpfiles into + * a released destination file. + * + * Copyright (C) 2021 Versity Software, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License v2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ioctl.h" + +#define array_size(arr) (sizeof(arr) / sizeof(arr[0])) + +/* + * Write known data into 8 tmpfiles. + * Make a new file X and release it + * Move contents of 8 tmpfiles into X. + */ + +struct sub_tmp_info { + int fd; + unsigned int offset; + unsigned int length; +}; + +#define SZ 4096 +char buf[SZ]; + +int main(int argc, char **argv) +{ + struct scoutfs_ioctl_release ioctl_args = {0}; + struct scoutfs_ioctl_move_blocks mb; + struct sub_tmp_info sub_tmps[8]; + int tot_size = 0; + char *dest_file; + int dest_fd; + char *mnt; + int ret; + int i; + + if (argc < 3) { + printf("%s \n", argv[0]); + return 1; + } + + mnt = argv[1]; + dest_file = argv[2]; + + for (i = 0; i < array_size(sub_tmps); i++) { + struct sub_tmp_info *sub_tmp = &sub_tmps[i]; + int remaining; + + sub_tmp->fd = open(mnt, O_RDWR | O_TMPFILE, S_IRUSR | S_IWUSR); + if (sub_tmp->fd < 0) { + perror("error"); + exit(1); + } + + sub_tmp->offset = tot_size; + + /* First tmp file is 4MB */ + /* Each is 4k bigger than last */ + sub_tmp->length = (i + 1024) * sizeof(buf); + + remaining = sub_tmp->length; + + /* Each sub tmpfile written with 'A', 'B', etc. */ + memset(buf, 'A' + i, sizeof(buf)); + while (remaining) { + int written; + + written = write(sub_tmp->fd, buf, sizeof(buf)); + assert(written == sizeof(buf)); + tot_size += sizeof(buf); + remaining -= written; + } + } + + printf("total file size %d\n", tot_size); + + dest_fd = open(dest_file, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); + if (dest_fd == -1) { + perror("error"); + exit(1); + } + + // make dest file big + ret = posix_fallocate(dest_fd, 0, tot_size); + if (ret) { + perror("error"); + exit(1); + } + + // release everything in dest file + ioctl_args.offset = 0; + ioctl_args.length = tot_size; + ioctl_args.data_version = 0; + + ret = ioctl(dest_fd, SCOUTFS_IOC_RELEASE, &ioctl_args); + if (ret < 0) { + perror("error"); + exit(1); + } + + // move contents into dest in reverse order + for (i = array_size(sub_tmps) - 1; i >= 0 ; i--) { + struct sub_tmp_info *sub_tmp = &sub_tmps[i]; + + mb.from_fd = sub_tmp->fd; + mb.from_off = 0; + mb.len = sub_tmp->length; + mb.to_off = sub_tmp->offset; + mb.data_version = 0; + mb.flags = SCOUTFS_IOC_MB_STAGE; + + ret = ioctl(dest_fd, SCOUTFS_IOC_MOVE_BLOCKS, &mb); + if (ret < 0) { + perror("error"); + exit(1); + } + + } + + return 0; +} diff --git a/tests/tests/stage-tmpfile.sh b/tests/tests/stage-tmpfile.sh new file mode 100644 index 00000000..5a77ed8f --- /dev/null +++ b/tests/tests/stage-tmpfile.sh @@ -0,0 +1,15 @@ +# +# Run tmpfile_stage and check the output with hexdump. +# + +t_require_commands stage_tmpfile hexdump + +DEST_FILE="$T_D0/dest_file" + +stage_tmpfile $T_D0 $DEST_FILE + +hexdump -C "$DEST_FILE" + +rm -fr "$DEST_FILE" + +t_pass diff --git a/utils/src/move_blocks.c b/utils/src/move_blocks.c index 9cc85d96..01669adf 100644 --- a/utils/src/move_blocks.c +++ b/utils/src/move_blocks.c @@ -32,7 +32,7 @@ struct move_blocks_args { static int do_move_blocks(struct move_blocks_args *args) { - struct scoutfs_ioctl_move_blocks mb; + struct scoutfs_ioctl_move_blocks mb = {0}; int from_fd = -1; int to_fd = -1; int ret;