From fa560016d4afb7bddbd842bffd7e81254432f524 Mon Sep 17 00:00:00 2001 From: Auke Kok Date: Wed, 27 May 2026 14:24:51 -0700 Subject: [PATCH 1/2] Register .set_acl unconditionally to fix POSIX ACL writes over NFS Scoutfs has supported posix ACLs through the xattr handler table, which allowed NFS to fetch them through this sideband, which worked for older kernels. With recent changes we've pulled in .get_acl because the mainline kernel is changing how ACL ops are called. But we still left .set_acl unreachable. This meant that on el9.7 nfs clients could now reach .get_acl, but still not set them. With this change, we're finally exposing .set_acl consistently across all el releases and allowing nfs clients to both get and set posix ACLs. Signed-off-by: Auke Kok --- kmod/src/Makefile.kernelcompat | 10 ++++++++++ kmod/src/acl.c | 5 +++-- kmod/src/acl.h | 3 ++- kmod/src/dir.c | 2 +- kmod/src/inode.c | 4 ++-- 5 files changed, 18 insertions(+), 6 deletions(-) diff --git a/kmod/src/Makefile.kernelcompat b/kmod/src/Makefile.kernelcompat index 81dc8a58..46825b3e 100644 --- a/kmod/src/Makefile.kernelcompat +++ b/kmod/src/Makefile.kernelcompat @@ -479,6 +479,16 @@ ifneq (,$(shell grep '^unsigned int stack_trace_save' include/linux/stacktrace.h ccflags-y += -DKC_STACK_TRACE_SAVE endif +# +# v3.14-rc1-7-g4e34e719e457 +# +# .set_acl callback added to struct inode_operations. Most kernels +# we target have it, but el7 (3.10 base) does not, so detect. +# +ifneq (,$(shell grep 'int ..set_acl..struct' include/linux/fs.h)) +ccflags-y += -DKC_HAS_SET_ACL +endif + # # v6.1-rc1-2-g138060ba92b3 # diff --git a/kmod/src/acl.c b/kmod/src/acl.c index 356e3107..c836df19 100644 --- a/kmod/src/acl.c +++ b/kmod/src/acl.c @@ -216,7 +216,8 @@ int scoutfs_set_acl(KC_VFS_NS_DEF { struct inode *inode = dentry->d_inode; #else -int scoutfs_set_acl(struct inode *inode, struct posix_acl *acl, int type) +int scoutfs_set_acl(KC_VFS_NS_DEF + struct inode *inode, struct posix_acl *acl, int type) { #endif struct super_block *sb = inode->i_sb; @@ -309,7 +310,7 @@ int scoutfs_acl_set_xattr(struct dentry *dentry, const char *name, const void *v #ifdef KC_SET_ACL_DENTRY ret = scoutfs_set_acl(KC_VFS_INIT_NS dentry, acl, type); #else - ret = scoutfs_set_acl(dentry->d_inode, acl, type); + ret = scoutfs_set_acl(KC_VFS_INIT_NS dentry->d_inode, acl, type); #endif out: posix_acl_release(acl); diff --git a/kmod/src/acl.h b/kmod/src/acl.h index a5bf21d6..2b64fafa 100644 --- a/kmod/src/acl.h +++ b/kmod/src/acl.h @@ -5,7 +5,8 @@ int scoutfs_set_acl(KC_VFS_NS_DEF struct dentry *dentry, struct posix_acl *acl, int type); #else -int scoutfs_set_acl(struct inode *inode, struct posix_acl *acl, int type); +int scoutfs_set_acl(KC_VFS_NS_DEF + struct inode *inode, struct posix_acl *acl, int type); #endif #ifdef KC_GET_INODE_ACL struct posix_acl *scoutfs_get_acl(struct inode *inode, int type, bool rcu); diff --git a/kmod/src/dir.c b/kmod/src/dir.c index efcdef24..f9729d7d 100644 --- a/kmod/src/dir.c +++ b/kmod/src/dir.c @@ -2063,7 +2063,7 @@ const struct inode_operations scoutfs_dir_iops = { #else .get_acl = scoutfs_get_acl, #endif -#ifdef KC_SET_ACL_DENTRY +#ifdef KC_HAS_SET_ACL .set_acl = scoutfs_set_acl, #endif .symlink = scoutfs_symlink, diff --git a/kmod/src/inode.c b/kmod/src/inode.c index 96376dc0..621833d2 100644 --- a/kmod/src/inode.c +++ b/kmod/src/inode.c @@ -154,7 +154,7 @@ static const struct inode_operations scoutfs_file_iops = { #else .get_acl = scoutfs_get_acl, #endif -#ifdef KC_SET_ACL_DENTRY +#ifdef KC_HAS_SET_ACL .set_acl = scoutfs_set_acl, #endif .fiemap = scoutfs_data_fiemap, @@ -174,7 +174,7 @@ static const struct inode_operations scoutfs_special_iops = { #else .get_acl = scoutfs_get_acl, #endif -#ifdef KC_SET_ACL_DENTRY +#ifdef KC_HAS_SET_ACL .set_acl = scoutfs_set_acl, #endif }; From f8f661d79cfb1ff9f458799bd4caea07733f0406 Mon Sep 17 00:00:00 2001 From: Auke Kok Date: Wed, 27 May 2026 14:25:03 -0700 Subject: [PATCH 2/2] Add basic NFS tests. This depends on nfs-utils being installed on the host. Without it it will skip, and count as a failure. It starts nfs-server and does a bare exportfs. - Tests basic read/write/stage/release/data wait. - Tests setfacl/getfacl. Signed-off-by: Auke Kok --- tests/funcs/filter.sh | 7 ++++ tests/golden/basic-nfs | 32 +++++++++++++++ tests/sequence | 1 + tests/tests/basic-nfs.sh | 86 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 126 insertions(+) create mode 100644 tests/golden/basic-nfs create mode 100644 tests/tests/basic-nfs.sh diff --git a/tests/funcs/filter.sh b/tests/funcs/filter.sh index 1016b8ef..3d89e855 100644 --- a/tests/funcs/filter.sh +++ b/tests/funcs/filter.sh @@ -171,6 +171,13 @@ t_filter_dmesg() # orphan log trees reclaim is handled, not an error re="$re|scoutfs .* reclaiming orphan log trees" + # nfs can emit a whole range of messages we can ignore + re="$re|Installing knfsd .*" + re="$re|nfsd: .*" + re="$re|NFSD: .*" + re="$re|RPC: .*" + re="$re|FS-Cache: .*" + # fencing tests force unmounts and trigger timeouts re="$re|scoutfs .* forcing unmount" re="$re|scoutfs .* reconnect timed out" diff --git a/tests/golden/basic-nfs b/tests/golden/basic-nfs new file mode 100644 index 00000000..4e722fb4 --- /dev/null +++ b/tests/golden/basic-nfs @@ -0,0 +1,32 @@ +== write via NFS, read both sides +== POSIX ACL set via NFS, read both sides +user::rw- +user:22222:rw- +group::r-- +mask::rw- +other::r-- + +user::rw- +user:22222:rw- +group::r-- +mask::rw- +other::r-- + +== POSIX ACL set on scoutfs, read via NFS +user::rw- +user:22222:rw- +group::r-- +group:44444:r-- +mask::rw- +other::r-- + +== default ACL inheritance via NFS +user::rw- +user:22222:rwx #effective:rw- +group::r-x #effective:r-- +mask::rw- +other::r-- + +== NFS read demand-stages a released file +1 +== cleanup diff --git a/tests/sequence b/tests/sequence index 909e665a..32a5cf4f 100644 --- a/tests/sequence +++ b/tests/sequence @@ -3,6 +3,7 @@ basic-block-counts.sh basic-bad-mounts.sh basic-posix-acl.sh basic-acl-consistency.sh +basic-nfs.sh inode-items-updated.sh simple-inode-index.sh simple-staging.sh diff --git a/tests/tests/basic-nfs.sh b/tests/tests/basic-nfs.sh new file mode 100644 index 00000000..a347178d --- /dev/null +++ b/tests/tests/basic-nfs.sh @@ -0,0 +1,86 @@ +# +# Test basic scoutfs-nfs interactions: +# - read/write +# - stage/release and data wait +# - nfs setacl/getacl mapping +# + +t_require_commands scoutfs setfacl getfacl exportfs mount.nfs umount \ + stat dd cmp systemctl + +systemctl start nfs-server >> "$T_TMPDIR/nfs.log" 2>&1 || \ + t_skip "nfs-server not available" + +# Keep file creation modes deterministic for the ACL golden output. +umask 022 + +EXPORT_OPTS="rw,async,no_root_squash,no_subtree_check,fsid=42" +NFS_MNT="$T_TMP.nfs" +NFS_DIR="$NFS_MNT/test/basic-nfs" + +filter() { sed "s@$T_TMPDIR@T_TMPDIR@g" | t_filter_fs; } +gf() { getfacl -n --omit-header "$@" 2>/dev/null; } + +teardown_nfs() +{ + ( + umount "$NFS_MNT" + exportfs -u "127.0.0.1:$T_M0" + exportfs -f + systemctl stop nfs-server + rmdir "$NFS_MNT" + ) >> "$T_TMPDIR/nfs.log" 2>&1 +} +trap teardown_nfs EXIT + +exportfs -u "127.0.0.1:$T_M0" >> "$T_TMPDIR/nfs.log" 2>&1 || true +t_quiet mkdir -p "$NFS_MNT" +exportfs -o "$EXPORT_OPTS" "127.0.0.1:$T_M0" >> "$T_TMPDIR/nfs.log" 2>&1 +mount.nfs -o vers=3,noac,actimeo=0 "127.0.0.1:$T_M0" "$NFS_MNT" >> "$T_TMPDIR/nfs.log" 2>&1 + +test -d "$NFS_DIR" || t_fail "test dir $NFS_DIR not visible over NFS" + +echo "== write via NFS, read both sides" +dd if=/dev/urandom bs=4096 count=1 of="$T_TMP.data" status=none +cp "$T_TMP.data" "$NFS_DIR/file" +cmp "$T_TMP.data" "$T_D0/file" +cmp "$T_TMP.data" "$NFS_DIR/file" + +echo "== POSIX ACL set via NFS, read both sides" +setfacl -m u:22222:rw "$NFS_DIR/file" 2>&1 | filter +gf "$NFS_DIR/file" +gf "$T_D0/file" + +echo "== POSIX ACL set on scoutfs, read via NFS" +setfacl -m g:44444:r "$T_D0/file" 2>&1 | filter +gf "$NFS_DIR/file" + +echo "== default ACL inheritance via NFS" +mkdir "$NFS_DIR/d" +setfacl -d -m u:22222:rwx "$NFS_DIR/d" 2>&1 | filter +touch "$NFS_DIR/d/child" +gf "$NFS_DIR/d/child" + +echo "== NFS read demand-stages a released file" +dd if=/dev/urandom bs=4096 count=1 of="$T_TMP.big" status=none +cp "$T_TMP.big" "$T_D0/big" +sync +vers=$(scoutfs stat -s data_version "$T_D0/big") +t_quiet scoutfs release "$T_D0/big" -V "$vers" -o 0 -l 4K + +# NFS read against the offline file blocks in scoutfs_read waiting +# for the data to come back online. +cat "$NFS_DIR/big" > "$T_TMP.read" & +read_pid=$! +sleep 1 +scoutfs data-waiting -B 0 -I 0 -p "$T_D0" | wc -l + +t_quiet scoutfs stage "$T_TMP.big" "$T_D0/big" -V "$vers" -o 0 -l 4096 +wait "$read_pid" +cmp "$T_TMP.big" "$T_TMP.read" + +echo "== cleanup" +rm -f "$T_D0/file" "$T_D0/big" +rm -rf "$T_D0/d" + +t_pass