diff --git a/kmod/src/dir.c b/kmod/src/dir.c index f380a5d8..7470244c 100644 --- a/kmod/src/dir.c +++ b/kmod/src/dir.c @@ -1615,8 +1615,9 @@ static int verify_ancestors(struct super_block *sb, u64 p1, u64 p2, * from using parent/child locking orders as two groups can have both * parent and child relationships to each other. */ -static int scoutfs_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry) +static int scoutfs_rename_common(struct inode *old_dir, + struct dentry *old_dentry, struct inode *new_dir, + struct dentry *new_dentry, unsigned int flags) { struct super_block *sb = old_dir->i_sb; struct inode *old_inode = old_dentry->d_inode; @@ -1688,6 +1689,11 @@ static int scoutfs_rename(struct inode *old_dir, struct dentry *old_dentry, if (ret) goto out_unlock; + if ((flags & RENAME_NOREPLACE) && (new_inode != NULL)) { + ret = -EEXIST; + goto out_unlock; + } + if (should_orphan(new_inode)) { ret = scoutfs_lock_orphan(sb, SCOUTFS_LOCK_WRITE_ONLY, 0, scoutfs_ino(new_inode), &orph_lock); @@ -1870,6 +1876,23 @@ out_unlock: return ret; } +static int scoutfs_rename(struct inode *old_dir, + struct dentry *old_dentry, struct inode *new_dir, + struct dentry *new_dentry) +{ + return scoutfs_rename_common(old_dir, old_dentry, new_dir, new_dentry, 0); +} + +static int scoutfs_rename2(struct inode *old_dir, + struct dentry *old_dentry, struct inode *new_dir, + struct dentry *new_dentry, unsigned int flags) +{ + if (flags & ~RENAME_NOREPLACE) + return -EINVAL; + + return scoutfs_rename_common(old_dir, old_dentry, new_dir, new_dentry, flags); +} + #ifdef KC_FMODE_KABI_ITERATE /* we only need this to set the iterate flag for kabi :/ */ static int scoutfs_dir_open(struct inode *inode, struct file *file) @@ -1960,6 +1983,7 @@ const struct inode_operations_wrapper scoutfs_dir_iops = { .permission = scoutfs_permission, }, .tmpfile = scoutfs_tmpfile, + .rename2 = scoutfs_rename2, }; void scoutfs_dir_exit(void) diff --git a/tests/.gitignore b/tests/.gitignore index f9edc55f..7603c209 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -1,5 +1,6 @@ src/*.d src/createmany +src/dumb_renameat2 src/dumb_setxattr src/handle_cat src/bulk_create_paths diff --git a/tests/Makefile b/tests/Makefile index 81e358a5..ec507401 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -3,6 +3,7 @@ SHELL := /usr/bin/bash # each binary command is built from a single .c file BIN := src/createmany \ + src/dumb_renameat2 \ src/dumb_setxattr \ src/handle_cat \ src/bulk_create_paths \ diff --git a/tests/golden/renameat2-noreplace b/tests/golden/renameat2-noreplace new file mode 100644 index 00000000..0cf6e932 --- /dev/null +++ b/tests/golden/renameat2-noreplace @@ -0,0 +1,2 @@ +=== renameat2 noreplace flag test +=== run two asynchronous calls to renameat2 NOREPLACE diff --git a/tests/golden/xfstests b/tests/golden/xfstests index bd40dd55..9a818f51 100644 --- a/tests/golden/xfstests +++ b/tests/golden/xfstests @@ -9,6 +9,8 @@ generic/011 generic/013 generic/014 generic/020 +generic/023 +generic/024 generic/028 generic/032 generic/034 @@ -82,6 +84,7 @@ generic/016 generic/018 generic/021 generic/022 +generic/025 generic/026 generic/031 generic/033 @@ -93,6 +96,7 @@ generic/060 generic/061 generic/063 generic/064 +generic/078 generic/079 generic/081 generic/082 @@ -278,4 +282,4 @@ shared/004 shared/032 shared/051 shared/289 -Passed all 73 tests +Passed all 75 tests diff --git a/tests/sequence b/tests/sequence index 146fa047..a168e287 100644 --- a/tests/sequence +++ b/tests/sequence @@ -37,4 +37,5 @@ createmany-parallel-mounts.sh archive-light-cycle.sh block-stale-reads.sh inode-deletion.sh +renameat2-noreplace.sh xfstests.sh diff --git a/tests/src/dumb_renameat2.c b/tests/src/dumb_renameat2.c new file mode 100644 index 00000000..b8b38f38 --- /dev/null +++ b/tests/src/dumb_renameat2.c @@ -0,0 +1,93 @@ +#include +#include +#include +#include + +#ifndef RENAMEAT2_EXIST +#include +#include + +#if !defined(SYS_renameat2) && defined(__x86_64__) +#define SYS_renameat2 316 /* from arch/x86/entry/syscalls/syscall_64.tbl */ +#endif + +static int renameat2(int olddfd, const char *old_dir, + int newdfd, const char *new_dir, + unsigned int flags) +{ +#ifdef SYS_renameat2 + return syscall(SYS_renameat2, olddfd, old_dir, newdfd, new_dir, flags); +#else + errno = ENOSYS; + return -1; +#endif +} +#endif + +#ifndef RENAME_NOREPLACE +#define RENAME_NOREPLACE (1 << 0) /* Don't overwrite newpath of rename */ +#endif +#ifndef RENAME_EXCHANGE +#define RENAME_EXCHANGE (1 << 1) /* Exchange oldpath and newpath */ +#endif +#ifndef RENAME_WHITEOUT +#define RENAME_WHITEOUT (1 << 2) /* Whiteout oldpath */ +#endif + +static void exit_usage(char **argv) +{ + fprintf(stderr, + "usage: %s [-n|-x|-w] old_path new_path\n" + " -n noreplace\n" + " -x exchange\n" + " -w whiteout\n", argv[0]); + + exit(1); +} + +int main(int argc, char **argv) +{ + const char *old_path = NULL; + const char *new_path = NULL; + unsigned int flags = 0; + int ret; + int c; + + for (c = 1; c < argc; c++) { + if (argv[c][0] == '-') { + switch (argv[c][1]) { + case 'n': + flags |= RENAME_NOREPLACE; + break; + case 'x': + flags |= RENAME_EXCHANGE; + break; + case 'w': + flags |= RENAME_WHITEOUT; + break; + default: + exit_usage(argv); + } + } else if (!old_path) { + old_path = argv[c]; + } else if (!new_path) { + new_path = argv[c]; + } else { + exit_usage(argv); + } + } + + if (!old_path || !new_path) { + printf("specify the correct directory path\n"); + errno = ENOENT; + return 1; + } + + ret = renameat2(AT_FDCWD, old_path, AT_FDCWD, new_path, flags); + if (ret == -1) { + perror("Error"); + return 1; + } + + return 0; +} diff --git a/tests/tests/renameat2-noreplace.sh b/tests/tests/renameat2-noreplace.sh new file mode 100644 index 00000000..78045e8a --- /dev/null +++ b/tests/tests/renameat2-noreplace.sh @@ -0,0 +1,37 @@ +# +# simple renameat2 NOREPLACE unit test +# + +t_require_commands dumb_renameat2 +t_require_mounts 2 + +echo "=== renameat2 noreplace flag test" + +# give each mount their own dir (lock group) to minimize create contention +mkdir $T_M0/dir0 +mkdir $T_M1/dir1 + +echo "=== run two asynchronous calls to renameat2 NOREPLACE" +for i in $(seq 0 100); do + # prepare inputs in isolation + touch "$T_M0/dir0/old0" + touch "$T_M1/dir1/old1" + + # race doing noreplace renames, both can't succeed + dumb_renameat2 -n "$T_M0/dir0/old0" "$T_M0/dir0/sharednew" 2> /dev/null & + pid0=$! + dumb_renameat2 -n "$T_M1/dir1/old1" "$T_M1/dir0/sharednew" 2> /dev/null & + pid1=$! + + wait $pid0 + rc0=$? + wait $pid1 + rc1=$? + + test "$rc0" == 0 -a "$rc1" == 0 && t_fail "both renames succeeded" + + # blow away possible files for either race outcome + rm -f "$T_M0/dir0/old0" "$T_M1/dir1/old1" "$T_M0/dir0/sharednew" "$T_M1/dir1/sharednew" +done + +t_pass diff --git a/tests/tests/xfstests.sh b/tests/tests/xfstests.sh index 5bcf67e0..140a3819 100644 --- a/tests/tests/xfstests.sh +++ b/tests/tests/xfstests.sh @@ -60,13 +60,9 @@ EOF cat << EOF > local.exclude generic/003 # missing atime update in buffered read -generic/023 # renameat2 not implemented -generic/024 # renameat2 not implemented -generic/025 # renameat2 not implemented generic/029 # mmap missing generic/030 # mmap missing generic/075 # file content mismatch failures (fds, etc) -generic/078 # renameat2 not implemented generic/080 # mmap missing generic/103 # enospc causes trans commit failures generic/105 # needs trigage: something about acls