From 1b8e3f7c05d8c1962eed7ca504f806b359d534a6 Mon Sep 17 00:00:00 2001 From: "Bryant G. Duffy-Ly" Date: Fri, 19 Nov 2021 16:45:41 -0600 Subject: [PATCH 1/4] Add basic renameat2 syscall support Support generic renameat2 syscall then add support for the RENAME_NOREPLACE flag. To suppor the flag we need to check the existance of both entries and return -EXIST. Signed-off-by: Bryant G. Duffy-Ly --- kmod/src/dir.c | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) 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) From 16ea0ef671320e2f7fce1f9d5025ea47cdf99c94 Mon Sep 17 00:00:00 2001 From: "Bryant G. Duffy-Ly" Date: Wed, 17 Nov 2021 08:47:36 -0600 Subject: [PATCH 2/4] Add syscall wrapper for renameat2 Signed-off-by: Bryant G. Duffy-Ly --- tests/.gitignore | 1 + tests/Makefile | 1 + tests/src/dumb_renameat2.c | 93 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 tests/src/dumb_renameat2.c 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/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; +} From 888ad8ec5c7d9570d8604717b2408c5f4b97c54b Mon Sep 17 00:00:00 2001 From: "Bryant G. Duffy-Ly" Date: Wed, 17 Nov 2021 09:39:30 -0600 Subject: [PATCH 3/4] Add renameat2 unit test case The goal of the test case is to have two mount points with two async calls made to do renameat2. This allows for two calls to race to call renameat2 RENAME_NOREPLACE. When this happens you expect one of them to fail with a -EEXIST. This would validate that the new flag works. Essentially one of the two calls to renameat should hit the new RENAME_NOREPLACE code and exit early. Signed-off-by: Bryant G. Duffy-Ly --- tests/golden/renameat2-noreplace | 2 ++ tests/sequence | 1 + tests/tests/renameat2-noreplace.sh | 37 ++++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+) create mode 100644 tests/golden/renameat2-noreplace create mode 100644 tests/tests/renameat2-noreplace.sh 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/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/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 From 0abcd5a00424cec2ab54ed951d16e1f50eb63483 Mon Sep 17 00:00:00 2001 From: "Bryant G. Duffy-Ly" Date: Tue, 16 Nov 2021 13:50:27 -0600 Subject: [PATCH 4/4] Take generic/025/078 off expunge list adding 23/24 We want to enable the test case for: generic/023 - tests that renameat2 syscall exists generic/024 - renameat2 with NOREPLACE flag Move both generic/025 and 078 to the no run list so that we can test the unsupported output if the flags were passed that were not supported. Example output: generic/025 [not run] fs doesn't support RENAME_EXCHANGE generic/078 [not run] fs doesn't support RENAME_WHITEOUT Signed-off-by: Bryant G. Duffy-Ly --- tests/golden/xfstests | 6 +++++- tests/tests/xfstests.sh | 4 ---- 2 files changed, 5 insertions(+), 5 deletions(-) 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/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