All of lore.kernel.org
 help / color / mirror / Atom feed
From: Luis Henriques <luis@igalia.com>
To: Miklos Szeredi <miklos@szeredi.hu>,
	Amir Goldstein <amir73il@gmail.com>,
	Chen Linxuan <me@black-desk.cn>, Jonathan Corbet <corbet@lwn.net>,
	Shuah Khan <skhan@linuxfoundation.org>
Cc: fuse-devel@lists.linux.dev, linux-kernel@vger.kernel.org,
	linux-kselftest@vger.kernel.org,
	Matt Harvey <mharvey@jumptrading.com>,
	kernel-dev@igalia.com, Luis Henriques <luis@igalia.com>
Subject: [RFC PATCH v4 6/8] selftests/fuse: add some extra ACL caching tests
Date: Wed, 16 Sep 2026 16:56:27 +0100	[thread overview]
Message-ID: <20260916155629.32421-7-luis@igalia.com> (raw)
In-Reply-To: <20260916155629.32421-1-luis@igalia.com>

This adds some extra tests to ACL caching:
- Verify that reading ACLs results in the expected number of requests
  being sent user-space, depending on whether cache is enabled or disabled
- Verify caching behaviour on some caching invalidation scenarios

While there, add test binary to .gitignore.

Signed-off-by: Luis Henriques <luis@igalia.com>
---
 .../selftests/filesystems/fuse/.gitignore     |   1 +
 .../filesystems/fuse/fuse_acl_cache_test.c    | 203 +++++++++++++++++-
 2 files changed, 202 insertions(+), 2 deletions(-)

diff --git a/tools/testing/selftests/filesystems/fuse/.gitignore b/tools/testing/selftests/filesystems/fuse/.gitignore
index fb51603fe419..f7f3dd345a50 100644
--- a/tools/testing/selftests/filesystems/fuse/.gitignore
+++ b/tools/testing/selftests/filesystems/fuse/.gitignore
@@ -2,3 +2,4 @@
 fuse_mnt
 fusectl_test
 write_extend_eof_test
+fuse_acl_cache_test
diff --git a/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c b/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c
index fb4e3554e3c9..173063efeee8 100644
--- a/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c
+++ b/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c
@@ -84,6 +84,9 @@ struct daemon_state {
 	uint8_t		*acl;
 	size_t          acl_size;
 	int             getxattr_count;
+	uid_t           uid;
+	uid_t           gid;
+	bool            cache;
 };
 
 /*
@@ -92,9 +95,17 @@ struct daemon_state {
  */
 static struct daemon_state g_ds = {
 	.lock = PTHREAD_MUTEX_INITIALIZER,
+	.cache = false,
 };
 
 /* ---- FUSE lowlevel callbacks -------------------------------------------- */
+static void fs_init(void *userdata, struct fuse_conn_info *conn)
+{
+	pthread_mutex_lock(&g_ds.lock);
+	if (g_ds.cache)
+		fuse_set_feature_flag(conn, FUSE_CAP_POSIX_ACL);
+	pthread_mutex_unlock(&g_ds.lock);
+}
 
 static void fs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
 {
@@ -116,6 +127,10 @@ static void fs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
 	e.attr.st_ino     = FILE_INO;
 	e.attr.st_mode    = S_IFREG | 0644;
 	e.attr.st_nlink   = 1;
+	pthread_mutex_lock(&g_ds.lock);
+	e.attr.st_uid = g_ds.uid;
+	e.attr.st_gid = g_ds.gid;
+	pthread_mutex_unlock(&g_ds.lock);
 	fuse_reply_entry(req, &e);
 }
 
@@ -133,6 +148,10 @@ static void fs_getattr(fuse_req_t req, fuse_ino_t ino,
 		st.st_ino   = FILE_INO;
 		st.st_mode  = S_IFREG | 0644;
 		st.st_nlink = 1;
+		pthread_mutex_lock(&g_ds.lock);
+		st.st_uid = g_ds.uid;
+		st.st_gid = g_ds.gid;
+		pthread_mutex_unlock(&g_ds.lock);
 	} else {
 		fuse_reply_err(req, ENOENT);
 		return;
@@ -161,12 +180,14 @@ static void fs_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
 	}
 	pthread_mutex_lock(&g_ds.lock);
 	acl_size = g_ds.acl_size;
-	if (acl && (size >= acl_size))
+	if (acl && acl_size && (size >= acl_size))
 		memcpy(acl, g_ds.acl, acl_size);
 	g_ds.getxattr_count++;
 	pthread_mutex_unlock(&g_ds.lock);
 
-	if (size == 0)
+	if (acl_size == 0)
+		fuse_reply_err(req, ENODATA);
+	else if (size == 0)
 		fuse_reply_xattr(req, acl_size);
 	else if (size < acl_size)
 		fuse_reply_err(req, ERANGE);
@@ -176,10 +197,42 @@ static void fs_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
 	free(acl);
 }
 
+static void fs_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+			    const char *value, size_t size, int flags)
+{
+	int ret = 0;
+	uint8_t *acl;
+
+	if (ino != FILE_INO) {
+		ret = ENOENT;
+		goto out;
+	} else if (strcmp(name, "system.posix_acl_access") != 0) {
+		ret = ENOTSUP;
+		goto out;
+	}
+	acl = malloc(size);
+	if (!acl) {
+		ret = ENOMEM;
+		goto out;
+	}
+	memcpy(acl, value, size);
+	pthread_mutex_lock(&g_ds.lock);
+	if (g_ds.acl)
+		free(g_ds.acl);
+	g_ds.acl = acl;
+	g_ds.acl_size = size;
+	pthread_mutex_unlock(&g_ds.lock);
+
+out:
+	fuse_reply_err(req, ret);
+}
+
 static const struct fuse_lowlevel_ops fs_ops = {
+	.init     = fs_init,
 	.lookup   = fs_lookup,
 	.getattr  = fs_getattr,
 	.getxattr = fs_getxattr,
+	.setxattr = fs_setxattr,
 };
 
 /* ---- kselftest harness --------------------------------------------------- */
@@ -198,6 +251,9 @@ FIXTURE_SETUP(acl_cache)
 	ASSERT_NE(g_ds.acl, NULL);
 	memcpy(g_ds.acl, acl_a, g_ds.acl_size);
 	g_ds.getxattr_count = 0;
+	g_ds.uid = getuid();
+	g_ds.gid = getgid();
+	g_ds.cache = false;
 
 	if (fs_setup(&fs_ops, &g_ds.ctx, err))
 		SKIP(goto out, err);
@@ -325,4 +381,147 @@ TEST_F(acl_cache, stale_after_force_sync)
 	EXPECT_EQ(count, 4);
 }
 
+FIXTURE(acl_cache_onoff)
+{
+	char pathname[PATH_MAX];
+};
+
+FIXTURE_VARIANT(acl_cache_onoff) { bool cache; };
+FIXTURE_VARIANT_ADD(acl_cache_onoff, nocache) { .cache = false, };
+FIXTURE_VARIANT_ADD(acl_cache_onoff, docache) { .cache = true, };
+
+FIXTURE_SETUP(acl_cache_onoff)
+{
+	char err[MAX_ERR_MSG];
+
+	pthread_mutex_lock(&g_ds.lock);
+	g_ds.acl = NULL;
+	g_ds.acl_size = 0;
+	g_ds.getxattr_count = 0;
+	g_ds.cache = variant->cache;
+	g_ds.uid = getuid();
+	g_ds.gid = getgid();
+
+	if (fs_setup(&fs_ops, &g_ds.ctx, err))
+		SKIP(goto out, err);
+
+	snprintf(self->pathname, sizeof(self->pathname),
+		 "%s/" FILE_NAME, g_ds.ctx.mountpoint);
+out:
+	pthread_mutex_unlock(&g_ds.lock);
+}
+
+FIXTURE_TEARDOWN(acl_cache_onoff)
+{
+	pthread_mutex_lock(&g_ds.lock);
+	fs_teardown(&g_ds.ctx);
+	pthread_mutex_unlock(&g_ds.lock);
+	free(g_ds.acl);
+}
+
+/*
+ * This is the most basic ACL caching test: verify that, when reading ACLs for
+ * an inode, user-space is called:
+ * - Only once if ACLs cache is enabled, or
+ * - Once per access if cache i disabled.
+ */
+TEST_F(acl_cache_onoff, test_acl_cache_enable_disable)
+{
+	char buf[512];
+	ssize_t sz;
+	bool cache;
+	int counter;
+	int i;
+
+	ASSERT_EQ(lsetxattr(self->pathname, "system.posix_acl_access",
+			    acl_a, sizeof(acl_a), 0), 0);
+
+	for (i = 0; i < 100; i++) {
+		sz = lgetxattr(self->pathname, "system.posix_acl_access",
+			       buf, sizeof(buf));
+		ASSERT_EQ(sz, sizeof(acl_a));
+		ASSERT_EQ(memcmp(buf, acl_a, sz), 0);
+	}
+
+	pthread_mutex_lock(&g_ds.lock);
+	counter = g_ds.getxattr_count;
+	cache = g_ds.cache;
+	pthread_mutex_unlock(&g_ds.lock);
+
+	if (cache) {
+		ASSERT_EQ(counter, 1);
+	} else {
+		ASSERT_EQ(counter, 100);
+	}
+
+	TH_LOG("User-space called %d time(s) with ACL caching %s",
+	       counter, cache ? "enabled" : "disabled");
+}
+
+/*
+ * Test caching invalidation for several scenarios:
+ * 1. When a new ACL is set
+ * 2. When invalidating an inode (NOTIFY_INODE_INVAL)
+ */
+TEST_F(acl_cache_onoff, test_acl_cache_invalidation)
+{
+	char buf[512];
+	ssize_t sz;
+	int counter;
+	bool cache;
+	int i;
+
+	/* Set an ACL */
+	ASSERT_EQ(lsetxattr(self->pathname, "system.posix_acl_access",
+			    acl_a, sizeof(acl_a), 0), 0);
+
+	for (i = 0; i < 100; i++) {
+		sz = lgetxattr(self->pathname, "system.posix_acl_access",
+			       buf, sizeof(buf));
+		ASSERT_EQ(sz, sizeof(acl_a));
+		ASSERT_EQ(memcmp(buf, acl_a, sz), 0);
+	}
+
+	/* 1. force cache invalidation by setting a new ACL */
+	ASSERT_EQ(lsetxattr(self->pathname, "system.posix_acl_access",
+			    acl_b, sizeof(acl_b), 0), 0);
+
+	sz = lgetxattr(self->pathname, "system.posix_acl_access",
+		       buf, sizeof(buf));
+	ASSERT_EQ(sz, sizeof(acl_b));
+	ASSERT_EQ(memcmp(buf, acl_b, sz), 0);
+
+	pthread_mutex_lock(&g_ds.lock);
+	counter = g_ds.getxattr_count;
+	cache = g_ds.cache;
+	pthread_mutex_unlock(&g_ds.lock);
+
+	if (cache) {
+		ASSERT_EQ(counter, 2);
+	} else {
+		ASSERT_EQ(counter, 101);
+	}
+	TH_LOG("Invalidation by setting new ACL: OK");
+
+	/* 2. send FUSE_NOTIFY_INVAL_INODE */
+	fuse_lowlevel_notify_inval_inode(g_ds.ctx.se, FILE_INO, 0, 0);
+
+	sz = lgetxattr(self->pathname, "system.posix_acl_access",
+		       buf, sizeof(buf));
+	ASSERT_EQ(sz, sizeof(acl_b));
+	ASSERT_EQ(memcmp(buf, acl_b, sz), 0);
+
+	pthread_mutex_lock(&g_ds.lock);
+	counter = g_ds.getxattr_count;
+	cache = g_ds.cache;
+	pthread_mutex_unlock(&g_ds.lock);
+
+	if (cache) {
+		ASSERT_EQ(counter, 3);
+	} else {
+		ASSERT_EQ(counter, 102);
+	}
+	TH_LOG("Invalidation through FUSE_NOTIFY_INVAL_INODE: OK");
+}
+
 TEST_HARNESS_MAIN

  parent reply	other threads:[~2026-09-16 15:55 UTC|newest]

Thread overview: 16+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-16 15:56 [RFC PATCH v4 0/8] fuse: caches documentation and testing Luis Henriques
2026-09-16 15:56 ` [RFC PATCH v4 1/8] Documentation: fuse: add document on caches being used by FUSE Luis Henriques
2026-09-17 15:47   ` Amir Goldstein
2026-09-18  9:16     ` Luis Henriques
2026-09-16 15:56 ` [RFC PATCH v4 2/8] selftests/fuse: convert fusectl test to fuse3 Luis Henriques
2026-09-16 15:56 ` [RFC PATCH v4 3/8] selftests/fuse: check that fusectlfs is mounted Luis Henriques
2026-09-16 15:56 ` [RFC PATCH v4 4/8] selftests/fuse: factor-out test fixture setup/teardown Luis Henriques
2026-09-17 15:11   ` Amir Goldstein
2026-09-18  9:28     ` Luis Henriques
2026-09-16 15:56 ` [RFC PATCH v4 5/8] selftests/fuse: use dynamically allocated memory to store ACLs Luis Henriques
2026-09-16 15:56 ` Luis Henriques [this message]
2026-09-17 15:14   ` [RFC PATCH v4 6/8] selftests/fuse: add some extra ACL caching tests Amir Goldstein
2026-09-16 15:56 ` [RFC PATCH v4 7/8] selftests/fuse: add fuse symlink caching test Luis Henriques
2026-09-17 15:18   ` Amir Goldstein
2026-09-16 15:56 ` [RFC PATCH v4 8/8] selftests/fuse: add fuse readdir " Luis Henriques
2026-09-17 15:21   ` Amir Goldstein

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260916155629.32421-7-luis@igalia.com \
    --to=luis@igalia.com \
    --cc=amir73il@gmail.com \
    --cc=corbet@lwn.net \
    --cc=fuse-devel@lists.linux.dev \
    --cc=kernel-dev@igalia.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=me@black-desk.cn \
    --cc=mharvey@jumptrading.com \
    --cc=miklos@szeredi.hu \
    --cc=skhan@linuxfoundation.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.