* [RFC PATCH v2 0/8] fuse: caches documentation and testing
@ 2026-08-17 14:11 Luis Henriques
2026-08-17 14:11 ` [RFC PATCH v2 1/8] Documentation: fuse: add document on caches being used by FUSE Luis Henriques
` (8 more replies)
0 siblings, 9 replies; 15+ messages in thread
From: Luis Henriques @ 2026-08-17 14:11 UTC (permalink / raw)
To: Miklos Szeredi, Amir Goldstein, Chen Linxuan, Jonathan Corbet,
Shuah Khan
Cc: fuse-devel, linux-kernel, linux-kselftest, Matt Harvey,
kernel-dev, Luis Henriques
Hi!
I'm (finally!) sending v2 of this patchset. It tries to document (and add
a few kselftests) to the different types of caches that are currently in use
within FUSE. As I mentioned in v1 the idea for documenting this came from
Miklos during this year's LSFMM, where he mentioned he would like to see
caches usage documented before he could merge an initial version of fusex.
This version documents symlinks, attributes, ACLs and readdir caches. Still
missing: dentries and data caching.
I'm still sending it as an RFC as I'm still not sure if it fulfils Miklos'
initial goal.
As usual feedback is welcome, as I'll (slowly) continue looking into other
cache types.
Major changes since v1:
- Added more caches to the document
- Changed tests format to single self-contained binaries
- Since Amir added a new fuse3-based test, the conversion to fuse3 of the
fusectl has minor changes to the Makefile
- NOTE: I've kept Amir's Reviewed-by anyway (should I drop it?)
- Added the ACL test-cases to the new ACL test implemented by Amir
- This required some re-work of the existing test (patches 5 & 6)
- Added more tests
Luis Henriques (8):
Documentation: fuse: add document on caches being used by FUSE
selftests/fuse: convert fusectl test to fuse3
selftests/fuse: check that fusectlfs is mounted
selftests/fuse: add fuse symlink caching test
selftests/fuse: factor-out test fixture setup/teardown
selftests/fuse: use dynamically allocated memory to store ACLs
selftests/fuse: add some extra ACL caching tests
selftests/fuse: add fuse readdir caching test
.../filesystems/fuse/fuse-caches.rst | 142 ++++++++
.../selftests/filesystems/fuse/.gitignore | 3 +
.../selftests/filesystems/fuse/Makefile | 28 +-
.../filesystems/fuse/fuse_acl_cache_test.c | 289 ++++++++++++++--
.../selftests/filesystems/fuse/fuse_mnt.c | 17 +-
.../fuse/fuse_readdir_cache_test.c | 314 ++++++++++++++++++
.../fuse/fuse_symlink_cache_test.c | 204 ++++++++++++
.../selftests/filesystems/fuse/fusectl_test.c | 7 +
8 files changed, 950 insertions(+), 54 deletions(-)
create mode 100644 Documentation/filesystems/fuse/fuse-caches.rst
create mode 100644 tools/testing/selftests/filesystems/fuse/fuse_readdir_cache_test.c
create mode 100644 tools/testing/selftests/filesystems/fuse/fuse_symlink_cache_test.c
^ permalink raw reply [flat|nested] 15+ messages in thread
* [RFC PATCH v2 1/8] Documentation: fuse: add document on caches being used by FUSE
2026-08-17 14:11 [RFC PATCH v2 0/8] fuse: caches documentation and testing Luis Henriques
@ 2026-08-17 14:11 ` Luis Henriques
2026-08-18 12:40 ` Amir Goldstein
2026-08-17 14:11 ` [RFC PATCH v2 2/8] selftests/fuse: convert fusectl test to fuse3 Luis Henriques
` (7 subsequent siblings)
8 siblings, 1 reply; 15+ messages in thread
From: Luis Henriques @ 2026-08-17 14:11 UTC (permalink / raw)
To: Miklos Szeredi, Amir Goldstein, Chen Linxuan, Jonathan Corbet,
Shuah Khan
Cc: fuse-devel, linux-kernel, linux-kselftest, Matt Harvey,
kernel-dev, Luis Henriques
This new file aims at documenting the caches that are used by FUSE. At
the moment only symlink, attributes, ACLs and readdir caches are described.
Signed-off-by: Luis Henriques <luis@igalia.com>
---
.../filesystems/fuse/fuse-caches.rst | 142 ++++++++++++++++++
1 file changed, 142 insertions(+)
create mode 100644 Documentation/filesystems/fuse/fuse-caches.rst
diff --git a/Documentation/filesystems/fuse/fuse-caches.rst b/Documentation/filesystems/fuse/fuse-caches.rst
new file mode 100644
index 000000000000..071febf45d00
--- /dev/null
+++ b/Documentation/filesystems/fuse/fuse-caches.rst
@@ -0,0 +1,142 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+===========
+FUSE Caches
+===========
+
+Introduction
+============
+
+This document summarises the different types of caches that are used in FUSE.
+For each cache type, it attempts to document the rules that are followed to
+insert, validate and invalidate data into the cache.
+
+symlink caching
+===============
+
+Whenever there's a link resolution request, the VFS will call into
+``fuse_get_link()`` which will then send a ``FUSE_READLINK`` request to the
+user-space FUSE server. However, the server can ask the kernel to cache all
+links resolutions by setting the ``FUSE_CACHE_SYMLINKS`` flag during the
+``FUSE_INIT`` negotiation.
+
+If this flag is set, FUSE will immediately call into the VFS
+``__page_get_link()`` from the ``->get_link()`` inode operation. The first time
+this is done for a specific link, it will end-up sending the ``FUSE_READLINK``
+to user-space but the link contents will then be added into page-cache. The next
+time the link needs to be resolved, it will use the link content that is already
+cached, and will only fallback into sending the request to use-space if the
+folio isn't up-to-date.
+
+Attributes caching
+==================
+
+Attributes obtained from user-space, for example when an inode is first
+looked-up, are cached in the kernel. However, these attributes have a timeout
+associated and once expired they are invalidated.
+
+Thus, the ``FUSE_GETATTR`` operation will be sent to user-space only if the
+attributes aren't yet available, the attributes aren't valid (timeout), or if
+there is an explicit request for doing so (for example, by using the
+``AT_STATX_FORCE_SYNC`` flag in ``statx``). This may happen in the following
+situations:
+
+#. An explicit request from VFS to get the attributes for an inode (through the
+ ``->getattr()`` callback).
+#. When an ``->llseek()`` is requested to FUSE with a type of request
+ (``whence``):
+
+ - ``SEEK_{HOLE,DATA}`` and the user-space doesn't implement the
+ ``FUSE_LSEEK`` operation (it has returned ``ENOSYS``), or
+ - ``SEEK_END``
+
+#. When doing a buffered read past EOF or automatic page cache invalidation mode
+ is enabled (``FUSE_AUTO_INVAL_DATA``).
+#. When doing a buffered write with write-back cache enabled
+ (``FUSE_CAP_WRITEBACK_CACHE``).
+
+ACL caching
+===========
+
+FUSE has allowed the usage of POSIX ACLs for a long time as they could be set
+and accessed simply as extended attributes. However, it was only with the
+addition of the ``FUSE_POSIX_ACL`` flag that ACLs started to be fully supported.
+Without this flag, ACLs can still be set, but the VFS won't use them for
+performing permission checks - that would be the user-space server's
+responsibility.
+
+Also, without setting ``FUSE_POSIX_ACL``, ACLs will not be cached by the kernel.
+In this case, new inodes ``i_acl`` and ``i_default_acl`` fields will be set to
+``ACL_DONT_CACHE``.
+
+On the other hand, if ``FUSE_POSIX_ACL`` is set during ``FUSE_INIT``, when an
+ACL is accessed the VFS layer will first check if it's already cached. If it is
+not, FUSE ``->get_acl`` operation is called, which will eventually send a
+user-space request. Future accesses to this inode ACL will then use the cached
+data.
+
+Setting an ACL in an inode, however, won't cache it immediately. It will send
+user-space a request with the new ACL, and the FUSE server may perform some
+modifications before storing it.
+
+On the other hand, ACLs will be removed for the cache in the following
+situations:
+
+- When setting an ACL in an inode and the user-space server has set the
+ ``FUSE_POSIX_ACL`` flag, all previously cached ACLs for this inode will be
+ invalidated.
+- When invalidating an inode through the ``FUSE_NOTIFY_INVAL_INODE`` operation.
+- When ``->d_revalidate()`` is called for a dentry that requires a lookup (e.g.
+ it has expired) and that lookup operation is successful.
+- When the VFS needs to check access rights for an inode (by calling
+ ``->permission()``), attributes may need to be refreshed. If that happens,
+ any cached ACLs for that inode will be invalidated.
+- After setting an inode attribute (i.e. operation ``FUSE_SETATTR`` is sent to
+ user-space), the user-space server may have also updated the ACLs, so any
+ cached ACLs for this inode are also invalidated.
+- While processing ``FUSE_READDIRPLUS`` and a new dentry is added (unless this
+ dentry is already being looked up (``DCACHE_PAR_LOOKUP``))
+- In general, when there is the need to sent a ``FUSE_STATX`` or
+ ``FUSE_GETATTR`` to user-space (e.g. because the attributes have expired).
+ This may happen in the following cases:
+
+ - When doing an ``->llseek()`` on a file with ``SEEK_END``, ``SEEK_HOLE`` or
+ ``SEEK_DATA``.
+ - When the ``FUSE_AUTO_INVAL_DATA`` flag is set at ``INIT`` time (to
+ automatically invalidate cached pages), and a buffered read
+ (``->read_iter()``) past EOF is done on a non-passthrough file.
+ - When the ``FUSE_WRITEBACK_CACHE`` flag is set at ``INIT`` time, and a
+ buffered write (``->write_iter()``) past EOF is done on a non-passthrough
+ file.
+ - When the ``FUSE_AUTO_INVAL_DATA`` flag is set at ``INIT`` time and the VFS
+ needs to read a directory contents (``->iterate_shared()``) for a
+ directory that is allowed to be cached.
+
+readdir caching
+===============
+
+When opening a directory for doing a readdir, a ``FUSE_OPENDIR`` will be sent
+and the user-space server will be responsible for setting the open flags related
+with caching, namely ``FOPEN_KEEP_CACHE`` and ``FOPEN_CACHE_DIR``.
+
+If neither flags are set by the user-space FUSE server, then every ``readdir``
+will result in a ``FUSE_READDIR`` (or ``FUSE_READDIRPLUS``) request being sent.
+If ``FOPEN_CACHE_DIR`` is set by the server, then the result of a ``readdir``
+will be cached by the kernel and reused. However, if ``FOPEN_KEEP_CACHE`` isn't
+also set, the cache will be invalidated next time the directory is open.
+
+The readdir cache will also expire and resetted in the following situations if:
+
+- The inode ``mtime`` doesn't match the cache ``mtime``,
+- The inode ``iversion`` doesn't match the cache ``iversion``,
+- The FUSE connection ``epoch`` doesn't match the cache ``epoch``.
+
+dentry caching
+==============
+
+TBD
+
+data caching
+============
+
+TBD
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH v2 2/8] selftests/fuse: convert fusectl test to fuse3
2026-08-17 14:11 [RFC PATCH v2 0/8] fuse: caches documentation and testing Luis Henriques
2026-08-17 14:11 ` [RFC PATCH v2 1/8] Documentation: fuse: add document on caches being used by FUSE Luis Henriques
@ 2026-08-17 14:11 ` Luis Henriques
2026-08-17 14:11 ` [RFC PATCH v2 3/8] selftests/fuse: check that fusectlfs is mounted Luis Henriques
` (6 subsequent siblings)
8 siblings, 0 replies; 15+ messages in thread
From: Luis Henriques @ 2026-08-17 14:11 UTC (permalink / raw)
To: Miklos Szeredi, Amir Goldstein, Chen Linxuan, Jonathan Corbet,
Shuah Khan
Cc: fuse-devel, linux-kernel, linux-kselftest, Matt Harvey,
kernel-dev, Luis Henriques
Since it is probably not worth adding new fuse kselftests based on fuse2,
it is a good idea to convert the single existing test to fuse3. The
conversion is trivial, as it only requires some changes to function
signatures (the gettattr and truncate fuse operations), and to the filler()
helper.
Signed-off-by: Luis Henriques <luis@igalia.com>
Reviewed-by: Amir Goldstein <amir73il@gmail.com>
---
.../selftests/filesystems/fuse/Makefile | 21 +++++++------------
.../selftests/filesystems/fuse/fuse_mnt.c | 17 ++++++++-------
2 files changed, 17 insertions(+), 21 deletions(-)
diff --git a/tools/testing/selftests/filesystems/fuse/Makefile b/tools/testing/selftests/filesystems/fuse/Makefile
index f47141484275..54411bc349d2 100644
--- a/tools/testing/selftests/filesystems/fuse/Makefile
+++ b/tools/testing/selftests/filesystems/fuse/Makefile
@@ -2,30 +2,23 @@
CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES)
-TEST_GEN_PROGS := fusectl_test
+TEST_GEN_PROGS := fusectl_test fuse_acl_cache_test
TEST_GEN_FILES := fuse_mnt
-# fuse_acl_cache_test requires libfuse3; add it only when the library is present.
-ACL_CFLAGS := $(shell pkg-config fuse3 --cflags 2>/dev/null)
-ACL_LDLIBS := $(shell pkg-config fuse3 --libs 2>/dev/null)
-ifneq ($(ACL_CFLAGS),)
-TEST_GEN_PROGS += fuse_acl_cache_test
-endif
-
include ../../lib.mk
-VAR_CFLAGS := $(shell pkg-config fuse --cflags 2>/dev/null)
+VAR_CFLAGS := $(shell pkg-config fuse3 --cflags 2>/dev/null)
ifeq ($(VAR_CFLAGS),)
-VAR_CFLAGS := -D_FILE_OFFSET_BITS=64 -I/usr/include/fuse
+VAR_CFLAGS := -D_FILE_OFFSET_BITS=64 -I/usr/include/fuse3
endif
-VAR_LDLIBS := $(shell pkg-config fuse --libs 2>/dev/null)
+VAR_LDLIBS := $(shell pkg-config fuse3 --libs 2>/dev/null)
ifeq ($(VAR_LDLIBS),)
-VAR_LDLIBS := -lfuse -pthread
+VAR_LDLIBS := -lfuse3 -pthread
endif
$(OUTPUT)/fuse_mnt: CFLAGS += $(VAR_CFLAGS)
$(OUTPUT)/fuse_mnt: LDLIBS += $(VAR_LDLIBS)
-$(OUTPUT)/fuse_acl_cache_test: CFLAGS += $(ACL_CFLAGS)
-$(OUTPUT)/fuse_acl_cache_test: LDLIBS += $(ACL_LDLIBS)
+$(OUTPUT)/fuse_acl_cache_test: CFLAGS += $(VAR_CFLAGS)
+$(OUTPUT)/fuse_acl_cache_test: LDLIBS += $(VAR_LDLIBS)
diff --git a/tools/testing/selftests/filesystems/fuse/fuse_mnt.c b/tools/testing/selftests/filesystems/fuse/fuse_mnt.c
index d12b17f30fad..5d335fa5cf05 100644
--- a/tools/testing/selftests/filesystems/fuse/fuse_mnt.c
+++ b/tools/testing/selftests/filesystems/fuse/fuse_mnt.c
@@ -4,7 +4,7 @@
* Creates a simple FUSE filesystem with a single read-write file (/test)
*/
-#define FUSE_USE_VERSION 26
+#define FUSE_USE_VERSION 31
#include <fuse.h>
#include <stdio.h>
@@ -20,7 +20,8 @@ static char *content;
static size_t content_size = 0;
static const char test_path[] = "/test";
-static int test_getattr(const char *path, struct stat *st)
+static int test_getattr(const char *path, struct stat *st,
+ struct fuse_file_info *fi)
{
memset(st, 0, sizeof(*st));
@@ -41,14 +42,15 @@ static int test_getattr(const char *path, struct stat *st)
}
static int test_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
- off_t offset, struct fuse_file_info *fi)
+ off_t offset, struct fuse_file_info *fi,
+ enum fuse_readdir_flags flags)
{
if (strcmp(path, "/"))
return -ENOENT;
- filler(buf, ".", NULL, 0);
- filler(buf, "..", NULL, 0);
- filler(buf, test_path + 1, NULL, 0);
+ filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+ filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+ filler(buf, test_path + 1, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
return 0;
}
@@ -107,7 +109,8 @@ static int test_write(const char *path, const char *buf, size_t size,
return size;
}
-static int test_truncate(const char *path, off_t size)
+static int test_truncate(const char *path, off_t size,
+ struct fuse_file_info *fi)
{
if (strcmp(path, test_path) != 0)
return -ENOENT;
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH v2 3/8] selftests/fuse: check that fusectlfs is mounted
2026-08-17 14:11 [RFC PATCH v2 0/8] fuse: caches documentation and testing Luis Henriques
2026-08-17 14:11 ` [RFC PATCH v2 1/8] Documentation: fuse: add document on caches being used by FUSE Luis Henriques
2026-08-17 14:11 ` [RFC PATCH v2 2/8] selftests/fuse: convert fusectl test to fuse3 Luis Henriques
@ 2026-08-17 14:11 ` Luis Henriques
2026-08-17 14:11 ` [RFC PATCH v2 4/8] selftests/fuse: add fuse symlink caching test Luis Henriques
` (5 subsequent siblings)
8 siblings, 0 replies; 15+ messages in thread
From: Luis Henriques @ 2026-08-17 14:11 UTC (permalink / raw)
To: Miklos Szeredi, Amir Goldstein, Chen Linxuan, Jonathan Corbet,
Shuah Khan
Cc: fuse-devel, linux-kernel, linux-kselftest, Matt Harvey,
kernel-dev, Luis Henriques
The control filesystem for FUSE needs to be mounted for the fusectl_test to
be successfully run. Skip the test is that is not the case.
Signed-off-by: Luis Henriques <luis@igalia.com>
Reviewed-by: Amir Goldstein <amir73il@gmail.com>
---
tools/testing/selftests/filesystems/fuse/fusectl_test.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/tools/testing/selftests/filesystems/fuse/fusectl_test.c b/tools/testing/selftests/filesystems/fuse/fusectl_test.c
index 0d1d012c35ed..2dcfd3ebe0d5 100644
--- a/tools/testing/selftests/filesystems/fuse/fusectl_test.c
+++ b/tools/testing/selftests/filesystems/fuse/fusectl_test.c
@@ -48,6 +48,7 @@ FIXTURE_SETUP(fusectl)
uid_t uid = getuid();
gid_t gid = getgid();
char buf[32];
+ char path_buf[PATH_MAX];
/* Setup userns */
ASSERT_EQ(unshare(CLONE_NEWNS|CLONE_NEWUSER), 0);
@@ -93,6 +94,12 @@ FIXTURE_SETUP(fusectl)
strerror(errno));
self->connection = statbuf.st_dev;
+
+ sprintf(path_buf, "/sys/fs/fuse/connections/%d", self->connection);
+ if (access(path_buf, F_OK) != 0)
+ SKIP(return,
+ "fusectl doesn't seem to be mounted: %s\n",
+ strerror(errno));
}
FIXTURE_TEARDOWN(fusectl)
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH v2 4/8] selftests/fuse: add fuse symlink caching test
2026-08-17 14:11 [RFC PATCH v2 0/8] fuse: caches documentation and testing Luis Henriques
` (2 preceding siblings ...)
2026-08-17 14:11 ` [RFC PATCH v2 3/8] selftests/fuse: check that fusectlfs is mounted Luis Henriques
@ 2026-08-17 14:11 ` Luis Henriques
2026-08-17 14:11 ` [RFC PATCH v2 5/8] selftests/fuse: factor-out test fixture setup/teardown Luis Henriques
` (4 subsequent siblings)
8 siblings, 0 replies; 15+ messages in thread
From: Luis Henriques @ 2026-08-17 14:11 UTC (permalink / raw)
To: Miklos Szeredi, Amir Goldstein, Chen Linxuan, Jonathan Corbet,
Shuah Khan
Cc: fuse-devel, linux-kernel, linux-kselftest, Matt Harvey,
kernel-dev, Luis Henriques
This patch adds a simple test that allows to verify that, when resolving a
symlink, user-space is called only the first time when caching is enabled
or, if caching is disabled, every time the symlink resolution is requested.
Signed-off-by: Luis Henriques <luis@igalia.com>
---
.../selftests/filesystems/fuse/.gitignore | 1 +
.../selftests/filesystems/fuse/Makefile | 5 +-
.../fuse/fuse_symlink_cache_test.c | 204 ++++++++++++++++++
3 files changed, 209 insertions(+), 1 deletion(-)
create mode 100644 tools/testing/selftests/filesystems/fuse/fuse_symlink_cache_test.c
diff --git a/tools/testing/selftests/filesystems/fuse/.gitignore b/tools/testing/selftests/filesystems/fuse/.gitignore
index 3e72e742d08e..873304f8d1a1 100644
--- a/tools/testing/selftests/filesystems/fuse/.gitignore
+++ b/tools/testing/selftests/filesystems/fuse/.gitignore
@@ -1,3 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
fuse_mnt
fusectl_test
+fuse_symlink_cache_test
diff --git a/tools/testing/selftests/filesystems/fuse/Makefile b/tools/testing/selftests/filesystems/fuse/Makefile
index 54411bc349d2..4091b1cc939e 100644
--- a/tools/testing/selftests/filesystems/fuse/Makefile
+++ b/tools/testing/selftests/filesystems/fuse/Makefile
@@ -2,7 +2,7 @@
CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES)
-TEST_GEN_PROGS := fusectl_test fuse_acl_cache_test
+TEST_GEN_PROGS := fusectl_test fuse_acl_cache_test fuse_symlink_cache_test
TEST_GEN_FILES := fuse_mnt
include ../../lib.mk
@@ -22,3 +22,6 @@ $(OUTPUT)/fuse_mnt: LDLIBS += $(VAR_LDLIBS)
$(OUTPUT)/fuse_acl_cache_test: CFLAGS += $(VAR_CFLAGS)
$(OUTPUT)/fuse_acl_cache_test: LDLIBS += $(VAR_LDLIBS)
+
+$(OUTPUT)/fuse_symlink_cache_test: CFLAGS += $(VAR_CFLAGS)
+$(OUTPUT)/fuse_symlink_cache_test: LDLIBS += $(VAR_LDLIBS)
diff --git a/tools/testing/selftests/filesystems/fuse/fuse_symlink_cache_test.c b/tools/testing/selftests/filesystems/fuse/fuse_symlink_cache_test.c
new file mode 100644
index 000000000000..c922db832c68
--- /dev/null
+++ b/tools/testing/selftests/filesystems/fuse/fuse_symlink_cache_test.c
@@ -0,0 +1,204 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Simple filesystem to test FUSE symlink cache
+ *
+ * This is a simple FUSE filesystem that contains two objects: a file named
+ * 'file' and a symlink to that file named 'link'. If symlink caching is
+ * disabled (i.e. FUSE_CAP_CACHE_SYMLINKS is reset during FUSE_INIT), whenever
+ * the ->readlink() is executed to resolve 'link' a counter will be incremented.
+ *
+ * If symlink caching is enabled (i.e. FUSE_CAP_CACHE_SYMLINKS is set during
+ * FUSE_INIT), resolving a symlink will only call into user-space the first
+ * time.
+ */
+
+#define FUSE_USE_VERSION 31
+
+#include <stdio.h>
+#include <limits.h>
+#include <string.h>
+#include <pthread.h>
+#include <fuse_lowlevel.h>
+
+#include "kselftest_harness.h"
+
+#define FILENAME "file"
+#define FILE_INO 42
+
+#define LINKNAME "link"
+#define LINK_INO 43
+
+#define TIMEOUT 86400.0f
+
+#define SYMLINK_MOUNTPOINT "/tmp/symlink_cache_test_XXXXXX"
+
+struct test_state {
+ pthread_mutex_t lock;
+ bool cache;
+ int readlink_counter;
+} test_state = {
+ .lock = PTHREAD_MUTEX_INITIALIZER,
+};
+
+static void fs_init(void *userdata, struct fuse_conn_info *conn)
+{
+ pthread_mutex_lock(&test_state.lock);
+ if (test_state.cache)
+ fuse_set_feature_flag(conn, FUSE_CAP_CACHE_SYMLINKS);
+ else
+ fuse_unset_feature_flag(conn, FUSE_CAP_CACHE_SYMLINKS);
+ pthread_mutex_unlock(&test_state.lock);
+}
+
+static void fs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+{
+ struct fuse_entry_param e = {};
+
+ if (parent != FUSE_ROOT_ID ||
+ (!strcmp(name, FILENAME) && !(strcmp(name, LINKNAME))))
+ fuse_reply_err(req, ENOENT);
+ else {
+ if (!strcmp(name, FILENAME)) {
+ e.ino = FILE_INO;
+ e.attr.st_mode = S_IFREG | 0444;
+ e.attr.st_nlink = 2;
+ } else if (!strcmp(name, LINKNAME)) {
+ e.ino = LINK_INO;
+ e.attr.st_mode = S_IFLNK | 0444;
+ e.attr.st_nlink = 1;
+ e.attr.st_size = strlen(FILENAME);
+ } else {
+ e.ino = FUSE_ROOT_ID;
+ e.attr.st_mode = S_IFDIR | 0755;
+ e.attr.st_nlink = 2;
+ }
+ e.attr_timeout = TIMEOUT;
+ e.entry_timeout = TIMEOUT;
+ fuse_reply_entry(req, &e);
+ }
+}
+
+static void fs_readlink(fuse_req_t req, fuse_ino_t ino)
+{
+ char buf[PATH_MAX];
+ size_t sz = strlen(FILENAME);
+
+ if (ino != LINK_INO) {
+ fuse_reply_err(req, ENOENT);
+ return;
+ }
+
+ memcpy(buf, FILENAME, sz);
+ buf[sz] = '\0';
+ pthread_mutex_lock(&test_state.lock);
+ test_state.readlink_counter++;
+ pthread_mutex_unlock(&test_state.lock);
+
+ fuse_reply_readlink(req, buf);
+}
+
+static const struct fuse_lowlevel_ops symlink_ops = {
+ .init = fs_init,
+ .lookup = fs_lookup,
+ .readlink = fs_readlink,
+};
+
+static void *run_daemon(void *arg)
+{
+ struct fuse_session *se = (struct fuse_session *)arg;
+
+ fuse_session_loop(se);
+
+ return NULL;
+}
+
+FIXTURE(symlink_cache)
+{
+ struct fuse_session *se;
+ char mountpoint[PATH_MAX];
+ pthread_t thread;
+};
+FIXTURE_VARIANT(symlink_cache)
+{
+ const bool cache;
+};
+FIXTURE_VARIANT_ADD(symlink_cache, symlinks_nocache)
+{
+ /* Variant with symlink cache disabled */
+ .cache = false,
+};
+FIXTURE_VARIANT_ADD(symlink_cache, symlinks_cache)
+{
+ /* Variant with symlink cache enabled */
+ .cache = true,
+};
+
+FIXTURE_SETUP(symlink_cache)
+{
+ char *fuse_argv[] = { "fuse_symlink_cache_test", NULL };
+ struct fuse_args args = FUSE_ARGS_INIT(1, fuse_argv);
+
+ pthread_mutex_lock(&test_state.lock);
+ test_state.readlink_counter = 0;
+ test_state.cache = variant->cache;
+ pthread_mutex_unlock(&test_state.lock);
+
+ strcpy(self->mountpoint, SYMLINK_MOUNTPOINT);
+ if (!mkdtemp(self->mountpoint))
+ SKIP(return, "mkdtemp: %s", strerror(errno));
+
+ self->se = fuse_session_new(&args, &symlink_ops,
+ sizeof(symlink_ops), NULL);
+ if (!self->se) {
+ rmdir(self->mountpoint);
+ SKIP(return, "Failed to created FUSE session");
+ }
+ if (fuse_session_mount(self->se, self->mountpoint)) {
+ fuse_session_destroy(self->se);
+ rmdir(self->mountpoint);
+ SKIP(return, "Failed to mount FUSE session");
+ }
+ if (pthread_create(&self->thread, NULL, run_daemon, self->se)) {
+ fuse_session_unmount(self->se);
+ fuse_session_destroy(self->se);
+ rmdir(self->mountpoint);
+ SKIP(return, "pthread_create: %s", strerror(errno));
+ }
+
+ fuse_opt_free_args(&args);
+}
+
+FIXTURE_TEARDOWN(symlink_cache)
+{
+ fuse_session_exit(self->se);
+ fuse_session_unmount(self->se);
+ pthread_join(self->thread, NULL);
+ fuse_session_destroy(self->se);
+ rmdir(self->mountpoint);
+}
+
+TEST_F(symlink_cache, test_symlink_cache)
+{
+ char pathname[PATH_MAX];
+ char buf[PATH_MAX];
+ ssize_t sz;
+ int counter;
+ int i;
+
+ sprintf(pathname, "%s/%s", self->mountpoint, LINKNAME);
+ for (i = 0; i < 100; i++) {
+ sz = readlink(pathname, buf, PATH_MAX);
+ ASSERT_NE(sz, -1);
+ }
+ pthread_mutex_lock(&test_state.lock);
+ counter = test_state.readlink_counter;
+ pthread_mutex_unlock(&test_state.lock);
+
+ if (variant->cache) {
+ ASSERT_EQ(counter, 1);
+ } else {
+ ASSERT_EQ(counter, 100);
+ }
+}
+
+TEST_HARNESS_MAIN
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH v2 5/8] selftests/fuse: factor-out test fixture setup/teardown
2026-08-17 14:11 [RFC PATCH v2 0/8] fuse: caches documentation and testing Luis Henriques
` (3 preceding siblings ...)
2026-08-17 14:11 ` [RFC PATCH v2 4/8] selftests/fuse: add fuse symlink caching test Luis Henriques
@ 2026-08-17 14:11 ` Luis Henriques
2026-08-18 13:09 ` Amir Goldstein
2026-08-17 14:11 ` [RFC PATCH v2 6/8] selftests/fuse: use dynamically allocated memory to store ACLs Luis Henriques
` (3 subsequent siblings)
8 siblings, 1 reply; 15+ messages in thread
From: Luis Henriques @ 2026-08-17 14:11 UTC (permalink / raw)
To: Miklos Szeredi, Amir Goldstein, Chen Linxuan, Jonathan Corbet,
Shuah Khan
Cc: fuse-devel, linux-kernel, linux-kselftest, Matt Harvey,
kernel-dev, Luis Henriques
In order to reduce new tests setup/teardown code duplication, factor-out
these functions from the existing acl_cache test.
Signed-off-by: Luis Henriques <luis@igalia.com>
---
.../filesystems/fuse/fuse_acl_cache_test.c | 82 ++++++++++++-------
1 file changed, 53 insertions(+), 29 deletions(-)
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 2411a6e285f1..8bdc90572be2 100644
--- a/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c
+++ b/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c
@@ -50,6 +50,8 @@
#include "kselftest_harness.h"
+#define MAX_ERR_MSG 256
+
/* ---- ACL binary encoding ------------------------------------------------ */
/*
* POSIX ACL v2 xattr format (little-endian):
@@ -193,52 +195,74 @@ FIXTURE(acl_cache) {
pthread_t thread;
};
-FIXTURE_SETUP(acl_cache)
+int fs_setup(struct fuse_session **se, char *mountpoint, char *file_path,
+ pthread_t *thread, char *err)
{
char *fuse_argv[] = { "fuse_acl_cache_test", NULL };
struct fuse_args args = FUSE_ARGS_INIT(1, fuse_argv);
- g_ds.acl = acl_a;
- g_ds.acl_size = sizeof(acl_a);
- g_ds.getxattr_count = 0;
-
- strcpy(self->mountpoint, "/tmp/acl_cache_test_XXXXXX");
- if (!mkdtemp(self->mountpoint))
- SKIP(return, "mkdtemp: %s", strerror(errno));
+ strcpy(mountpoint, "/tmp/acl_cache_test_XXXXXX");
+ if (!mkdtemp(mountpoint)) {
+ snprintf(err, MAX_ERR_MSG, "mkdtemp: %s", strerror(errno));
+ return -1;
+ }
- snprintf(self->file_path, sizeof(self->file_path),
- "%s/" FILE_NAME, self->mountpoint);
+ snprintf(file_path, PATH_MAX, "%s/" FILE_NAME, mountpoint);
- self->se = fuse_session_new(&args, &fs_ops, sizeof(fs_ops), NULL);
- if (!self->se) {
- rmdir(self->mountpoint);
- SKIP(return, "fuse_session_new failed");
+ *se = fuse_session_new(&args, &fs_ops, sizeof(fs_ops), NULL);
+ if (!*se) {
+ rmdir(mountpoint);
+ snprintf(err, MAX_ERR_MSG, "fuse_session_new failed");
+ return -1;
}
- if (fuse_session_mount(self->se, self->mountpoint)) {
- fuse_session_destroy(self->se);
- rmdir(self->mountpoint);
- SKIP(return, "fuse_session_mount failed "
- "(missing fusermount3 or insufficient privileges)");
+ if (fuse_session_mount(*se, mountpoint)) {
+ fuse_session_destroy(*se);
+ rmdir(mountpoint);
+ snprintf(err, MAX_ERR_MSG, "fuse_session_mount failed "
+ "(missing fusermount3 or insufficient privileges)");
+ return -1;
}
- if (pthread_create(&self->thread, NULL, run_daemon, self->se)) {
- fuse_session_unmount(self->se);
- fuse_session_destroy(self->se);
- rmdir(self->mountpoint);
- SKIP(return, "pthread_create: %s", strerror(errno));
+ if (pthread_create(thread, NULL, run_daemon, *se)) {
+ fuse_session_unmount(*se);
+ fuse_session_destroy(*se);
+ rmdir(mountpoint);
+ snprintf(err, MAX_ERR_MSG, "pthread_create: %s", strerror(errno));
+ return -1;
}
fuse_opt_free_args(&args);
+
+ return 0;
+}
+
+static void fs_teardown(struct fuse_session *se, pthread_t thread,
+ char *mountpoint)
+{
+ fuse_session_exit(se);
+ fuse_session_unmount(se);
+ pthread_join(thread, NULL);
+ fuse_session_destroy(se);
+ rmdir(mountpoint);
+}
+
+FIXTURE_SETUP(acl_cache)
+{
+ char err[MAX_ERR_MSG];
+
+ g_ds.acl = acl_a;
+ g_ds.acl_size = sizeof(acl_a);
+ g_ds.getxattr_count = 0;
+
+ if (fs_setup(&self->se, self->mountpoint, self->file_path,
+ &self->thread, err))
+ SKIP(return, err);
}
FIXTURE_TEARDOWN(acl_cache)
{
- fuse_session_exit(self->se);
- fuse_session_unmount(self->se);
- pthread_join(self->thread, NULL);
- fuse_session_destroy(self->se);
- rmdir(self->mountpoint);
+ fs_teardown(self->se, self->thread, self->mountpoint);
}
static int do_force_statx(const char *path)
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH v2 6/8] selftests/fuse: use dynamically allocated memory to store ACLs
2026-08-17 14:11 [RFC PATCH v2 0/8] fuse: caches documentation and testing Luis Henriques
` (4 preceding siblings ...)
2026-08-17 14:11 ` [RFC PATCH v2 5/8] selftests/fuse: factor-out test fixture setup/teardown Luis Henriques
@ 2026-08-17 14:11 ` Luis Henriques
2026-08-17 14:11 ` [RFC PATCH v2 7/8] selftests/fuse: add some extra ACL caching tests Luis Henriques
` (2 subsequent siblings)
8 siblings, 0 replies; 15+ messages in thread
From: Luis Henriques @ 2026-08-17 14:11 UTC (permalink / raw)
To: Miklos Szeredi, Amir Goldstein, Chen Linxuan, Jonathan Corbet,
Shuah Khan
Cc: fuse-devel, linux-kernel, linux-kselftest, Matt Harvey,
kernel-dev, Luis Henriques
Instead of directly using static arrays for the ACL value, allocate memory
for storing it. This will make it easier to implement ACL tests that also
set the xattr dynamically.
Signed-off-by: Luis Henriques <luis@igalia.com>
---
.../filesystems/fuse/fuse_acl_cache_test.c | 34 +++++++++++++++----
1 file changed, 28 insertions(+), 6 deletions(-)
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 8bdc90572be2..bf8b3807e603 100644
--- a/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c
+++ b/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c
@@ -90,7 +90,7 @@ static const uint8_t acl_b[] = {
struct daemon_state {
pthread_mutex_t lock;
- const uint8_t *acl;
+ uint8_t *acl;
size_t acl_size;
int getxattr_count;
};
@@ -152,15 +152,26 @@ static void fs_getattr(fuse_req_t req, fuse_ino_t ino,
static void fs_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
size_t size)
{
+ uint8_t *acl = NULL;
+ size_t acl_size;
+
if (ino != FILE_INO ||
strcmp(name, "system.posix_acl_access") != 0) {
fuse_reply_err(req, ENODATA);
return;
}
+ if (size) {
+ acl = malloc(size);
+ if (!acl) {
+ fuse_reply_err(req, ENOMEM);
+ return;
+ }
+ }
pthread_mutex_lock(&g_ds.lock);
- const uint8_t *acl = g_ds.acl;
- size_t acl_size = g_ds.acl_size;
+ acl_size = g_ds.acl_size;
+ if (acl && (size >= acl_size))
+ memcpy(acl, g_ds.acl, acl_size);
g_ds.getxattr_count++;
pthread_mutex_unlock(&g_ds.lock);
@@ -170,6 +181,8 @@ static void fs_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
fuse_reply_err(req, ERANGE);
else
fuse_reply_buf(req, (const char *)acl, acl_size);
+
+ free(acl);
}
static const struct fuse_lowlevel_ops fs_ops = {
@@ -251,8 +264,10 @@ FIXTURE_SETUP(acl_cache)
{
char err[MAX_ERR_MSG];
- g_ds.acl = acl_a;
- g_ds.acl_size = sizeof(acl_a);
+ g_ds.acl_size = sizeof(acl_a);
+ g_ds.acl = malloc(g_ds.acl_size);
+ ASSERT_NE(g_ds.acl, NULL);
+ memcpy(g_ds.acl, acl_a, g_ds.acl_size);
g_ds.getxattr_count = 0;
if (fs_setup(&self->se, self->mountpoint, self->file_path,
@@ -263,6 +278,7 @@ FIXTURE_SETUP(acl_cache)
FIXTURE_TEARDOWN(acl_cache)
{
fs_teardown(self->se, self->thread, self->mountpoint);
+ free(g_ds.acl);
}
static int do_force_statx(const char *path)
@@ -278,6 +294,7 @@ TEST_F(acl_cache, stale_after_force_sync)
char buf[512];
ssize_t sz;
int count;
+ uint8_t *acl;
/*
* Step 1: two getxattr calls before any statx(FORCE_SYNC).
@@ -338,8 +355,13 @@ TEST_F(acl_cache, stale_after_force_sync)
* !fc->posix_acl mounts (it skips forget_all_cached_acls in that case).
* On a fixed kernel the ACL was never cached, so this is moot.
*/
+ acl = malloc(sizeof(acl_b));
+ ASSERT_NE(acl, NULL);
+ memcpy(acl, acl_b, sizeof(acl_b));
+
pthread_mutex_lock(&g_ds.lock);
- g_ds.acl = acl_b;
+ free(g_ds.acl);
+ g_ds.acl = acl;
g_ds.acl_size = sizeof(acl_b);
pthread_mutex_unlock(&g_ds.lock);
TH_LOG("step 4: daemon switched to ACL_B (%zu bytes)", sizeof(acl_b));
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH v2 7/8] selftests/fuse: add some extra ACL caching tests
2026-08-17 14:11 [RFC PATCH v2 0/8] fuse: caches documentation and testing Luis Henriques
` (5 preceding siblings ...)
2026-08-17 14:11 ` [RFC PATCH v2 6/8] selftests/fuse: use dynamically allocated memory to store ACLs Luis Henriques
@ 2026-08-17 14:11 ` Luis Henriques
2026-08-17 14:11 ` [RFC PATCH v2 8/8] selftests/fuse: add fuse readdir caching test Luis Henriques
2026-08-18 13:13 ` [RFC PATCH v2 0/8] fuse: caches documentation and testing Amir Goldstein
8 siblings, 0 replies; 15+ messages in thread
From: Luis Henriques @ 2026-08-17 14:11 UTC (permalink / raw)
To: Miklos Szeredi, Amir Goldstein, Chen Linxuan, Jonathan Corbet,
Shuah Khan
Cc: fuse-devel, linux-kernel, linux-kselftest, Matt Harvey,
kernel-dev, Luis Henriques
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 | 177 ++++++++++++++++++
2 files changed, 178 insertions(+)
diff --git a/tools/testing/selftests/filesystems/fuse/.gitignore b/tools/testing/selftests/filesystems/fuse/.gitignore
index 873304f8d1a1..d5061752e2b0 100644
--- a/tools/testing/selftests/filesystems/fuse/.gitignore
+++ b/tools/testing/selftests/filesystems/fuse/.gitignore
@@ -2,3 +2,4 @@
fuse_mnt
fusectl_test
fuse_symlink_cache_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 bf8b3807e603..c1291fb1c8b9 100644
--- a/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c
+++ b/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c
@@ -93,6 +93,7 @@ struct daemon_state {
uint8_t *acl;
size_t acl_size;
int getxattr_count;
+ bool cache;
};
/*
@@ -101,9 +102,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)
{
@@ -125,6 +134,8 @@ 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;
+ e.attr.st_uid = getuid();
+ e.attr.st_gid = getgid();
fuse_reply_entry(req, &e);
}
@@ -185,10 +196,38 @@ 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;
+ else if (!strcmp(name, "system.posix_acl_access")) {
+ acl = malloc(size);
+ if (acl) {
+ 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);
+ } else
+ ret = ENOMEM;
+ } else
+ ret = ENOTSUP;
+
+ 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,
};
/* ---- Daemon thread ------------------------------------------------------- */
@@ -269,6 +308,7 @@ 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.cache = false;
if (fs_setup(&self->se, self->mountpoint, self->file_path,
&self->thread, err))
@@ -390,4 +430,141 @@ TEST_F(acl_cache, stale_after_force_sync)
EXPECT_EQ(count, 4);
}
+FIXTURE(acl_cache_onoff)
+{
+ struct fuse_session *se;
+ char mountpoint[PATH_MAX];
+ char pathname[PATH_MAX];
+ pthread_t thread;
+};
+
+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];
+
+ g_ds.acl = NULL;
+ g_ds.acl_size = 0;
+ g_ds.getxattr_count = 0;
+ g_ds.cache = variant->cache;
+
+ if (fs_setup(&self->se, self->mountpoint, self->pathname,
+ &self->thread, err))
+ SKIP(return, err);
+}
+
+FIXTURE_TEARDOWN(acl_cache_onoff)
+{
+ fs_teardown(self->se, self->thread, self->mountpoint);
+ 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(self->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
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH v2 8/8] selftests/fuse: add fuse readdir caching test
2026-08-17 14:11 [RFC PATCH v2 0/8] fuse: caches documentation and testing Luis Henriques
` (6 preceding siblings ...)
2026-08-17 14:11 ` [RFC PATCH v2 7/8] selftests/fuse: add some extra ACL caching tests Luis Henriques
@ 2026-08-17 14:11 ` Luis Henriques
2026-08-18 13:13 ` [RFC PATCH v2 0/8] fuse: caches documentation and testing Amir Goldstein
8 siblings, 0 replies; 15+ messages in thread
From: Luis Henriques @ 2026-08-17 14:11 UTC (permalink / raw)
To: Miklos Szeredi, Amir Goldstein, Chen Linxuan, Jonathan Corbet,
Shuah Khan
Cc: fuse-devel, linux-kernel, linux-kselftest, Matt Harvey,
kernel-dev, Luis Henriques
This new test will check the caching behaviour using combinations of two
opendir flags: FOPEN_KEEP_CACHE and FOPEN_CACHE_DIR.
Signed-off-by: Luis Henriques <luis@igalia.com>
---
.../selftests/filesystems/fuse/.gitignore | 1 +
.../selftests/filesystems/fuse/Makefile | 6 +-
.../fuse/fuse_readdir_cache_test.c | 314 ++++++++++++++++++
3 files changed, 320 insertions(+), 1 deletion(-)
create mode 100644 tools/testing/selftests/filesystems/fuse/fuse_readdir_cache_test.c
diff --git a/tools/testing/selftests/filesystems/fuse/.gitignore b/tools/testing/selftests/filesystems/fuse/.gitignore
index d5061752e2b0..7c9fc24a0b61 100644
--- a/tools/testing/selftests/filesystems/fuse/.gitignore
+++ b/tools/testing/selftests/filesystems/fuse/.gitignore
@@ -3,3 +3,4 @@ fuse_mnt
fusectl_test
fuse_symlink_cache_test
fuse_acl_cache_test
+fuse_readdir_cache_test
diff --git a/tools/testing/selftests/filesystems/fuse/Makefile b/tools/testing/selftests/filesystems/fuse/Makefile
index 4091b1cc939e..6edcd25169de 100644
--- a/tools/testing/selftests/filesystems/fuse/Makefile
+++ b/tools/testing/selftests/filesystems/fuse/Makefile
@@ -2,7 +2,8 @@
CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES)
-TEST_GEN_PROGS := fusectl_test fuse_acl_cache_test fuse_symlink_cache_test
+TEST_GEN_PROGS := fusectl_test fuse_acl_cache_test fuse_symlink_cache_test \
+ fuse_readdir_cache_test
TEST_GEN_FILES := fuse_mnt
include ../../lib.mk
@@ -25,3 +26,6 @@ $(OUTPUT)/fuse_acl_cache_test: LDLIBS += $(VAR_LDLIBS)
$(OUTPUT)/fuse_symlink_cache_test: CFLAGS += $(VAR_CFLAGS)
$(OUTPUT)/fuse_symlink_cache_test: LDLIBS += $(VAR_LDLIBS)
+
+$(OUTPUT)/fuse_readdir_cache_test: CFLAGS += $(VAR_CFLAGS)
+$(OUTPUT)/fuse_readdir_cache_test: LDLIBS += $(VAR_LDLIBS)
diff --git a/tools/testing/selftests/filesystems/fuse/fuse_readdir_cache_test.c b/tools/testing/selftests/filesystems/fuse/fuse_readdir_cache_test.c
new file mode 100644
index 000000000000..181c08d9391a
--- /dev/null
+++ b/tools/testing/selftests/filesystems/fuse/fuse_readdir_cache_test.c
@@ -0,0 +1,314 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Simple filesystem to test FUSE readdir cache
+ *
+ * It will simply perform readdir operations on a directory checking how many
+ * times a request is sent to user-space using all the possible caching
+ * combination setting (FOPEN_KEEP_CACHE and FOPEN_CACHE_DIR flags).
+ */
+
+#define FUSE_USE_VERSION 31
+
+#include <stdio.h>
+#include <limits.h>
+#include <string.h>
+#include <pthread.h>
+#include <dirent.h>
+#include <fuse_lowlevel.h>
+
+#include "kselftest_harness.h"
+
+#define DIRNAME "mydir"
+#define FILENAME "myfile"
+
+#define DIR_INO 42
+#define FILE_INO 43
+#define DOT_INO 40
+#define DOTDOT_INO 41
+
+#define TIMEOUT 86400.0f
+
+#define FS_MOUNTPOINT "/tmp/readdir_cache_test_XXXXXX"
+
+struct test_state {
+ pthread_mutex_t lock;
+ bool cache_readdir;
+ bool keep_cache;
+ int readdir_counter;
+} test_state = {
+ .lock = PTHREAD_MUTEX_INITIALIZER,
+};
+
+static void fs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+{
+ struct fuse_entry_param e = {};
+
+ if (parent != FUSE_ROOT_ID || strcmp(name, DIRNAME) != 0)
+ fuse_reply_err(req, ENOENT);
+ else {
+ if (!strcmp(name, DIRNAME)) {
+ e.ino = DIR_INO;
+ e.attr.st_mode = S_IFDIR | 0755;
+ e.attr.st_nlink = 1;
+ } else {
+ e.ino = FUSE_ROOT_ID;
+ e.attr.st_mode = S_IFDIR | 0755;
+ e.attr.st_nlink = 2;
+ }
+ e.attr.st_mtime = time(NULL);
+ e.attr_timeout = TIMEOUT;
+ e.entry_timeout = TIMEOUT;
+ fuse_reply_entry(req, &e);
+ }
+}
+
+static int fill_stat(fuse_ino_t ino, struct stat *st)
+{
+ int ret = 0;
+
+ st->st_ino = ino;
+ st->st_mtime = time(NULL);
+
+ switch (ino) {
+ case FUSE_ROOT_ID:
+ st->st_mode = S_IFDIR | 0755;
+ st->st_nlink = 2;
+ break;
+ case DOT_INO:
+ case DOTDOT_INO:
+ case DIR_INO:
+ st->st_mode = S_IFDIR | 0755;
+ st->st_nlink = 1;
+ break;
+ case FILE_INO:
+ st->st_mode = S_IFREG | 0444;
+ st->st_nlink = 1;
+ break;
+ default:
+ ret = -1;
+ break;
+ }
+
+ return ret;
+}
+
+static void fs_getattr(fuse_req_t req, fuse_ino_t ino,
+ struct fuse_file_info *fi)
+{
+ struct stat st = {};
+
+ if (fill_stat(ino, &st) < 0)
+ fuse_reply_err(req, ENOENT);
+ else
+ fuse_reply_attr(req, &st, TIMEOUT);
+}
+
+static void fs_opendir(fuse_req_t req, fuse_ino_t ino,
+ struct fuse_file_info *fi)
+{
+ pthread_mutex_lock(&test_state.lock);
+ fi->keep_cache = test_state.keep_cache;
+ fi->cache_readdir = test_state.cache_readdir;
+ pthread_mutex_unlock(&test_state.lock);
+ fuse_reply_open(req, fi);
+}
+
+static void fs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+ off_t offset, struct fuse_file_info *fi)
+{
+ struct stat st = {};
+ char buf[1024];
+ char *pbuf;
+ size_t rem = size;
+ size_t sz;
+ int nextoff = 0;
+
+ if (ino != DIR_INO) {
+ fuse_reply_err(req, ENOTDIR);
+ return;
+ }
+ if (offset) {
+ fuse_reply_buf(req, NULL, 0);
+ return;
+ }
+ pbuf = buf;
+ fill_stat(DOT_INO, &st);
+ sz = fuse_add_direntry(req, pbuf, rem, ".", &st, nextoff++);
+ rem -= sz;
+ pbuf += sz;
+ fill_stat(DOTDOT_INO, &st);
+ sz = fuse_add_direntry(req, pbuf, rem, "..", &st, nextoff++);
+ rem -= sz;
+ pbuf += sz;
+ fill_stat(FILE_INO, &st);
+ sz = fuse_add_direntry(req, pbuf, rem, FILENAME, &st, nextoff++);
+ rem -= sz;
+
+ fuse_reply_buf(req, buf, size - rem);
+
+ pthread_mutex_lock(&test_state.lock);
+ test_state.readdir_counter++;
+ pthread_mutex_unlock(&test_state.lock);
+}
+
+static const struct fuse_lowlevel_ops fs_ops = {
+ .lookup = fs_lookup,
+ .getattr = fs_getattr,
+ .opendir = fs_opendir,
+ .readdir = fs_readdir,
+};
+
+static void *run_daemon(void *arg)
+{
+ struct fuse_session *se = (struct fuse_session *)arg;
+
+ fuse_session_loop(se);
+
+ return NULL;
+}
+
+FIXTURE(readdir_cache)
+{
+ struct fuse_session *se;
+ char mountpoint[PATH_MAX];
+ pthread_t thread;
+};
+
+FIXTURE_VARIANT(readdir_cache)
+{
+ bool cache_readdir;
+ bool keep_cache;
+};
+FIXTURE_VARIANT_ADD(readdir_cache, nocache)
+{
+ .cache_readdir = false,
+ .keep_cache = false,
+};
+FIXTURE_VARIANT_ADD(readdir_cache, cache_readdir)
+{
+ .cache_readdir = true,
+ .keep_cache = false,
+};
+FIXTURE_VARIANT_ADD(readdir_cache, keep_cache)
+{
+ .cache_readdir = false,
+ .keep_cache = true,
+};
+FIXTURE_VARIANT_ADD(readdir_cache, cache)
+{
+ .cache_readdir = true,
+ .keep_cache = true,
+};
+
+FIXTURE_SETUP(readdir_cache)
+{
+ char *fuse_argv[] = { "fuse_readdir_cache_test", NULL };
+ struct fuse_args args = FUSE_ARGS_INIT(1, fuse_argv);
+
+ pthread_mutex_lock(&test_state.lock);
+ test_state.readdir_counter = 0;
+ test_state.cache_readdir = variant->cache_readdir;
+ test_state.keep_cache = variant->keep_cache;
+ pthread_mutex_unlock(&test_state.lock);
+
+ strcpy(self->mountpoint, FS_MOUNTPOINT);
+ if (!mkdtemp(self->mountpoint))
+ SKIP(return, "mkdtemp: %s", strerror(errno));
+
+ self->se = fuse_session_new(&args, &fs_ops,
+ sizeof(fs_ops), NULL);
+ if (!self->se) {
+ rmdir(self->mountpoint);
+ SKIP(return, "Failed to created FUSE session");
+ }
+ if (fuse_session_mount(self->se, self->mountpoint)) {
+ fuse_session_destroy(self->se);
+ rmdir(self->mountpoint);
+ SKIP(return, "Failed to mount FUSE session");
+ }
+ if (pthread_create(&self->thread, NULL, run_daemon, self->se)) {
+ fuse_session_unmount(self->se);
+ fuse_session_destroy(self->se);
+ rmdir(self->mountpoint);
+ SKIP(return, "pthread_create: %s", strerror(errno));
+ }
+
+ fuse_opt_free_args(&args);
+}
+
+FIXTURE_TEARDOWN(readdir_cache)
+{
+ fuse_session_exit(self->se);
+ fuse_session_unmount(self->se);
+ pthread_join(self->thread, NULL);
+ fuse_session_destroy(self->se);
+ rmdir(self->mountpoint);
+}
+
+TEST_F(readdir_cache, test_readdir_cache)
+{
+ struct dirent *dentry;
+ DIR *dir;
+ char pathname[PATH_MAX];
+ int total_counter, rewind_counter;
+ int dentrycount;
+
+ sprintf(pathname, "%s/%s", self->mountpoint, DIRNAME);
+
+ dir = opendir(pathname);
+ if (dir == NULL)
+ TH_LOG("opendir(): %s", strerror(errno));
+ ASSERT_NE(dir, NULL);
+
+ errno = 0;
+ dentrycount = 0;
+ while ((dentry = readdir(dir)))
+ dentrycount++;
+ ASSERT_EQ(errno, 0);
+ ASSERT_EQ(dentrycount, 3);
+
+ rewinddir(dir);
+ errno = 0;
+ dentrycount = 0;
+ while ((dentry = readdir(dir)))
+ dentrycount++;
+ ASSERT_EQ(errno, 0);
+ ASSERT_EQ(dentrycount, 3);
+
+ ASSERT_EQ(closedir(dir), 0);
+
+ pthread_mutex_lock(&test_state.lock);
+ rewind_counter = test_state.readdir_counter;
+ pthread_mutex_unlock(&test_state.lock);
+
+ dir = opendir(pathname);
+ if (dir == NULL)
+ TH_LOG("opendir(): %s", strerror(errno));
+ ASSERT_NE(dir, NULL);
+
+ errno = 0;
+ dentrycount = 0;
+ while ((dentry = readdir(dir)))
+ dentrycount++;
+ ASSERT_EQ(errno, 0);
+ ASSERT_EQ(dentrycount, 3);
+
+ ASSERT_EQ(closedir(dir), 0);
+
+ pthread_mutex_lock(&test_state.lock);
+ total_counter = test_state.readdir_counter;
+ pthread_mutex_unlock(&test_state.lock);
+
+ if (!variant->cache_readdir) {
+ ASSERT_EQ(rewind_counter, 2);
+ ASSERT_EQ(total_counter, 3);
+ } else if (!variant->keep_cache) {
+ ASSERT_EQ(rewind_counter, 1);
+ ASSERT_EQ(total_counter, 2);
+ } else {
+ ASSERT_EQ(rewind_counter, 1);
+ ASSERT_EQ(total_counter, 1);
+ }
+}
+
+TEST_HARNESS_MAIN
^ permalink raw reply related [flat|nested] 15+ messages in thread
* Re: [RFC PATCH v2 1/8] Documentation: fuse: add document on caches being used by FUSE
2026-08-17 14:11 ` [RFC PATCH v2 1/8] Documentation: fuse: add document on caches being used by FUSE Luis Henriques
@ 2026-08-18 12:40 ` Amir Goldstein
2026-08-18 15:51 ` Luis Henriques
0 siblings, 1 reply; 15+ messages in thread
From: Amir Goldstein @ 2026-08-18 12:40 UTC (permalink / raw)
To: Luis Henriques
Cc: Miklos Szeredi, Chen Linxuan, Jonathan Corbet, Shuah Khan,
fuse-devel, linux-kernel, linux-kselftest, Matt Harvey,
kernel-dev
On Mon, Aug 17, 2026 at 4:11 PM Luis Henriques <luis@igalia.com> wrote:
>
> This new file aims at documenting the caches that are used by FUSE. At
> the moment only symlink, attributes, ACLs and readdir caches are described.
>
> Signed-off-by: Luis Henriques <luis@igalia.com>
> ---
> .../filesystems/fuse/fuse-caches.rst | 142 ++++++++++++++++++
> 1 file changed, 142 insertions(+)
> create mode 100644 Documentation/filesystems/fuse/fuse-caches.rst
>
> diff --git a/Documentation/filesystems/fuse/fuse-caches.rst b/Documentation/filesystems/fuse/fuse-caches.rst
> new file mode 100644
> index 000000000000..071febf45d00
> --- /dev/null
> +++ b/Documentation/filesystems/fuse/fuse-caches.rst
> @@ -0,0 +1,142 @@
> +.. SPDX-License-Identifier: GPL-2.0
> +
> +===========
> +FUSE Caches
> +===========
> +
> +Introduction
> +============
> +
> +This document summarises the different types of caches that are used in FUSE.
> +For each cache type, it attempts to document the rules that are followed to
> +insert, validate and invalidate data into the cache.
> +
> +symlink caching
> +===============
> +
> +Whenever there's a link resolution request, the VFS will call into
> +``fuse_get_link()`` which will then send a ``FUSE_READLINK`` request to the
> +user-space FUSE server. However, the server can ask the kernel to cache all
> +links resolutions by setting the ``FUSE_CACHE_SYMLINKS`` flag during the
> +``FUSE_INIT`` negotiation.
> +
> +If this flag is set, FUSE will immediately call into the VFS
> +``__page_get_link()`` from the ``->get_link()`` inode operation. The first time
> +this is done for a specific link, it will end-up sending the ``FUSE_READLINK``
> +to user-space but the link contents will then be added into page-cache. The next
> +time the link needs to be resolved, it will use the link content that is already
> +cached, and will only fallback into sending the request to use-space if the
> +folio isn't up-to-date.
> +
> +Attributes caching
> +==================
> +
> +Attributes obtained from user-space, for example when an inode is first
> +looked-up, are cached in the kernel. However, these attributes have a timeout
> +associated and once expired they are invalidated.
> +
> +Thus, the ``FUSE_GETATTR`` operation will be sent to user-space only if the
> +attributes aren't yet available, the attributes aren't valid (timeout), or if
> +there is an explicit request for doing so (for example, by using the
> +``AT_STATX_FORCE_SYNC`` flag in ``statx``). This may happen in the following
> +situations:
"This may happen" what may happen? I don't see it referring to anything.
> +
> +#. An explicit request from VFS to get the attributes for an inode (through the
> + ``->getattr()`` callback).
> +#. When an ``->llseek()`` is requested to FUSE with a type of request
> + (``whence``):
> +
> + - ``SEEK_{HOLE,DATA}`` and the user-space doesn't implement the
> + ``FUSE_LSEEK`` operation (it has returned ``ENOSYS``), or
> + - ``SEEK_END``
> +
> +#. When doing a buffered read past EOF or automatic page cache invalidation mode
> + is enabled (``FUSE_AUTO_INVAL_DATA``).
> +#. When doing a buffered write with write-back cache enabled
> + (``FUSE_CAP_WRITEBACK_CACHE``).
This list is incomplete and strange. it has post EOF write for
writeback which is the exception
and leaves out every non writeback write.
If you composed this list yourself I highly recommend an LLM for this task
if you used LLM I suggest a stronger model.
Generally speaking, I find that today's robots are much better at writing these
sorts of docs than I am - as long as I sit at the helm and guide them
about where to expand on and where to keep it concise.
> +
> +ACL caching
> +===========
> +
> +FUSE has allowed the usage of POSIX ACLs for a long time as they could be set
> +and accessed simply as extended attributes. However, it was only with the
> +addition of the ``FUSE_POSIX_ACL`` flag that ACLs started to be fully supported.
> +Without this flag, ACLs can still be set, but the VFS won't use them for
> +performing permission checks - that would be the user-space server's
> +responsibility.
> +
> +Also, without setting ``FUSE_POSIX_ACL``, ACLs will not be cached by the kernel.
> +In this case, new inodes ``i_acl`` and ``i_default_acl`` fields will be set to
> +``ACL_DONT_CACHE``.
> +
> +On the other hand, if ``FUSE_POSIX_ACL`` is set during ``FUSE_INIT``, when an
> +ACL is accessed the VFS layer will first check if it's already cached. If it is
> +not, FUSE ``->get_acl`` operation is called, which will eventually send a
> +user-space request. Future accesses to this inode ACL will then use the cached
> +data.
> +
> +Setting an ACL in an inode, however, won't cache it immediately. It will send
> +user-space a request with the new ACL, and the FUSE server may perform some
> +modifications before storing it.
Do not encourage this by documenting it please.
It reinforces that this was by design, rather than an oversight which
we don't know.
> +
> +On the other hand, ACLs will be removed for the cache in the following
> +situations:
> +
> +- When setting an ACL in an inode and the user-space server has set the
> + ``FUSE_POSIX_ACL`` flag, all previously cached ACLs for this inode will be
> + invalidated.
> +- When invalidating an inode through the ``FUSE_NOTIFY_INVAL_INODE`` operation.
> +- When ``->d_revalidate()`` is called for a dentry that requires a lookup (e.g.
> + it has expired) and that lookup operation is successful.
> +- When the VFS needs to check access rights for an inode (by calling
> + ``->permission()``), attributes may need to be refreshed. If that happens,
> + any cached ACLs for that inode will be invalidated.
> +- After setting an inode attribute (i.e. operation ``FUSE_SETATTR`` is sent to
> + user-space), the user-space server may have also updated the ACLs, so any
> + cached ACLs for this inode are also invalidated.
> +- While processing ``FUSE_READDIRPLUS`` and a new dentry is added (unless this
> + dentry is already being looked up (``DCACHE_PAR_LOOKUP``))
> +- In general, when there is the need to sent a ``FUSE_STATX`` or
> + ``FUSE_GETATTR`` to user-space (e.g. because the attributes have expired).
> + This may happen in the following cases:
> +
> + - When doing an ``->llseek()`` on a file with ``SEEK_END``, ``SEEK_HOLE`` or
> + ``SEEK_DATA``.
> + - When the ``FUSE_AUTO_INVAL_DATA`` flag is set at ``INIT`` time (to
> + automatically invalidate cached pages), and a buffered read
> + (``->read_iter()``) past EOF is done on a non-passthrough file.
> + - When the ``FUSE_WRITEBACK_CACHE`` flag is set at ``INIT`` time, and a
> + buffered write (``->write_iter()``) past EOF is done on a non-passthrough
> + file.
> + - When the ``FUSE_AUTO_INVAL_DATA`` flag is set at ``INIT`` time and the VFS
> + needs to read a directory contents (``->iterate_shared()``) for a
> + directory that is allowed to be cached.
No reason to repeat the reasons for attr cache invalidation that were
just listed above
> +
> +readdir caching
> +===============
> +
> +When opening a directory for doing a readdir, a ``FUSE_OPENDIR`` will be sent
> +and the user-space server will be responsible for setting the open flags related
> +with caching, namely ``FOPEN_KEEP_CACHE`` and ``FOPEN_CACHE_DIR``.
> +
> +If neither flags are set by the user-space FUSE server, then every ``readdir``
> +will result in a ``FUSE_READDIR`` (or ``FUSE_READDIRPLUS``) request being sent.
> +If ``FOPEN_CACHE_DIR`` is set by the server, then the result of a ``readdir``
> +will be cached by the kernel and reused. However, if ``FOPEN_KEEP_CACHE`` isn't
> +also set, the cache will be invalidated next time the directory is open.
Confusing.
FOPEN_KEEP_CACHE is about keeping the cache on THIS open not on
some NEXT open.
Thanks,
Amir.
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [RFC PATCH v2 5/8] selftests/fuse: factor-out test fixture setup/teardown
2026-08-17 14:11 ` [RFC PATCH v2 5/8] selftests/fuse: factor-out test fixture setup/teardown Luis Henriques
@ 2026-08-18 13:09 ` Amir Goldstein
2026-08-18 15:51 ` Luis Henriques
0 siblings, 1 reply; 15+ messages in thread
From: Amir Goldstein @ 2026-08-18 13:09 UTC (permalink / raw)
To: Luis Henriques
Cc: Miklos Szeredi, Chen Linxuan, Jonathan Corbet, Shuah Khan,
fuse-devel, linux-kernel, linux-kselftest, Matt Harvey,
kernel-dev
On Mon, Aug 17, 2026 at 4:11 PM Luis Henriques <luis@igalia.com> wrote:
>
> In order to reduce new tests setup/teardown code duplication, factor-out
> these functions from the existing acl_cache test.
When I read this I thought you were going to share these helpers with the
new symlink and readdir cache tests, but you did not.
Maybe a fuse_common.c would make sense to reduce boiler plate
in new fuse tests.
These helpers and fixture look pretty similar in all three tests.
Thanks,
Amir.
>
> Signed-off-by: Luis Henriques <luis@igalia.com>
> ---
> .../filesystems/fuse/fuse_acl_cache_test.c | 82 ++++++++++++-------
> 1 file changed, 53 insertions(+), 29 deletions(-)
>
> 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 2411a6e285f1..8bdc90572be2 100644
> --- a/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c
> +++ b/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c
> @@ -50,6 +50,8 @@
>
> #include "kselftest_harness.h"
>
> +#define MAX_ERR_MSG 256
> +
> /* ---- ACL binary encoding ------------------------------------------------ */
> /*
> * POSIX ACL v2 xattr format (little-endian):
> @@ -193,52 +195,74 @@ FIXTURE(acl_cache) {
> pthread_t thread;
> };
>
> -FIXTURE_SETUP(acl_cache)
> +int fs_setup(struct fuse_session **se, char *mountpoint, char *file_path,
> + pthread_t *thread, char *err)
> {
> char *fuse_argv[] = { "fuse_acl_cache_test", NULL };
> struct fuse_args args = FUSE_ARGS_INIT(1, fuse_argv);
>
> - g_ds.acl = acl_a;
> - g_ds.acl_size = sizeof(acl_a);
> - g_ds.getxattr_count = 0;
> -
> - strcpy(self->mountpoint, "/tmp/acl_cache_test_XXXXXX");
> - if (!mkdtemp(self->mountpoint))
> - SKIP(return, "mkdtemp: %s", strerror(errno));
> + strcpy(mountpoint, "/tmp/acl_cache_test_XXXXXX");
> + if (!mkdtemp(mountpoint)) {
> + snprintf(err, MAX_ERR_MSG, "mkdtemp: %s", strerror(errno));
> + return -1;
> + }
>
> - snprintf(self->file_path, sizeof(self->file_path),
> - "%s/" FILE_NAME, self->mountpoint);
> + snprintf(file_path, PATH_MAX, "%s/" FILE_NAME, mountpoint);
>
> - self->se = fuse_session_new(&args, &fs_ops, sizeof(fs_ops), NULL);
> - if (!self->se) {
> - rmdir(self->mountpoint);
> - SKIP(return, "fuse_session_new failed");
> + *se = fuse_session_new(&args, &fs_ops, sizeof(fs_ops), NULL);
> + if (!*se) {
> + rmdir(mountpoint);
> + snprintf(err, MAX_ERR_MSG, "fuse_session_new failed");
> + return -1;
> }
>
> - if (fuse_session_mount(self->se, self->mountpoint)) {
> - fuse_session_destroy(self->se);
> - rmdir(self->mountpoint);
> - SKIP(return, "fuse_session_mount failed "
> - "(missing fusermount3 or insufficient privileges)");
> + if (fuse_session_mount(*se, mountpoint)) {
> + fuse_session_destroy(*se);
> + rmdir(mountpoint);
> + snprintf(err, MAX_ERR_MSG, "fuse_session_mount failed "
> + "(missing fusermount3 or insufficient privileges)");
> + return -1;
> }
>
> - if (pthread_create(&self->thread, NULL, run_daemon, self->se)) {
> - fuse_session_unmount(self->se);
> - fuse_session_destroy(self->se);
> - rmdir(self->mountpoint);
> - SKIP(return, "pthread_create: %s", strerror(errno));
> + if (pthread_create(thread, NULL, run_daemon, *se)) {
> + fuse_session_unmount(*se);
> + fuse_session_destroy(*se);
> + rmdir(mountpoint);
> + snprintf(err, MAX_ERR_MSG, "pthread_create: %s", strerror(errno));
> + return -1;
> }
>
> fuse_opt_free_args(&args);
> +
> + return 0;
> +}
> +
> +static void fs_teardown(struct fuse_session *se, pthread_t thread,
> + char *mountpoint)
> +{
> + fuse_session_exit(se);
> + fuse_session_unmount(se);
> + pthread_join(thread, NULL);
> + fuse_session_destroy(se);
> + rmdir(mountpoint);
> +}
> +
> +FIXTURE_SETUP(acl_cache)
> +{
> + char err[MAX_ERR_MSG];
> +
> + g_ds.acl = acl_a;
> + g_ds.acl_size = sizeof(acl_a);
> + g_ds.getxattr_count = 0;
> +
> + if (fs_setup(&self->se, self->mountpoint, self->file_path,
> + &self->thread, err))
> + SKIP(return, err);
> }
>
> FIXTURE_TEARDOWN(acl_cache)
> {
> - fuse_session_exit(self->se);
> - fuse_session_unmount(self->se);
> - pthread_join(self->thread, NULL);
> - fuse_session_destroy(self->se);
> - rmdir(self->mountpoint);
> + fs_teardown(self->se, self->thread, self->mountpoint);
> }
>
> static int do_force_statx(const char *path)
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [RFC PATCH v2 0/8] fuse: caches documentation and testing
2026-08-17 14:11 [RFC PATCH v2 0/8] fuse: caches documentation and testing Luis Henriques
` (7 preceding siblings ...)
2026-08-17 14:11 ` [RFC PATCH v2 8/8] selftests/fuse: add fuse readdir caching test Luis Henriques
@ 2026-08-18 13:13 ` Amir Goldstein
2026-08-18 15:52 ` Luis Henriques
8 siblings, 1 reply; 15+ messages in thread
From: Amir Goldstein @ 2026-08-18 13:13 UTC (permalink / raw)
To: Luis Henriques
Cc: Miklos Szeredi, Chen Linxuan, Jonathan Corbet, Shuah Khan,
fuse-devel, linux-kernel, linux-kselftest, Matt Harvey,
kernel-dev
On Mon, Aug 17, 2026 at 4:11 PM Luis Henriques <luis@igalia.com> wrote:
>
> Hi!
>
> I'm (finally!) sending v2 of this patchset. It tries to document (and add
> a few kselftests) to the different types of caches that are currently in use
> within FUSE. As I mentioned in v1 the idea for documenting this came from
> Miklos during this year's LSFMM, where he mentioned he would like to see
> caches usage documented before he could merge an initial version of fusex.
>
> This version documents symlinks, attributes, ACLs and readdir caches. Still
> missing: dentries and data caching.
>
> I'm still sending it as an RFC as I'm still not sure if it fulfils Miklos'
> initial goal.
>
> As usual feedback is welcome, as I'll (slowly) continue looking into other
> cache types.
>
> Major changes since v1:
> - Added more caches to the document
> - Changed tests format to single self-contained binaries
> - Since Amir added a new fuse3-based test, the conversion to fuse3 of the
> fusectl has minor changes to the Makefile
> - NOTE: I've kept Amir's Reviewed-by anyway (should I drop it?)
It's fine. I trust your judgement.
Please see Sashiko review comments.
My main concern is with the doc - it's not an easy read
and I would have liked it to be and I found many correctness error
which is not a good sign.
Thanks,
Amir.
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [RFC PATCH v2 1/8] Documentation: fuse: add document on caches being used by FUSE
2026-08-18 12:40 ` Amir Goldstein
@ 2026-08-18 15:51 ` Luis Henriques
0 siblings, 0 replies; 15+ messages in thread
From: Luis Henriques @ 2026-08-18 15:51 UTC (permalink / raw)
To: Amir Goldstein
Cc: Miklos Szeredi, Chen Linxuan, Jonathan Corbet, Shuah Khan,
fuse-devel, linux-kernel, linux-kselftest, Matt Harvey,
kernel-dev
On Tue, Aug 18 2026, Amir Goldstein wrote:
> On Mon, Aug 17, 2026 at 4:11 PM Luis Henriques <luis@igalia.com> wrote:
>>
>> This new file aims at documenting the caches that are used by FUSE. At
>> the moment only symlink, attributes, ACLs and readdir caches are described.
>>
>> Signed-off-by: Luis Henriques <luis@igalia.com>
>> ---
>> .../filesystems/fuse/fuse-caches.rst | 142 ++++++++++++++++++
>> 1 file changed, 142 insertions(+)
>> create mode 100644 Documentation/filesystems/fuse/fuse-caches.rst
>>
>> diff --git a/Documentation/filesystems/fuse/fuse-caches.rst b/Documentation/filesystems/fuse/fuse-caches.rst
>> new file mode 100644
>> index 000000000000..071febf45d00
>> --- /dev/null
>> +++ b/Documentation/filesystems/fuse/fuse-caches.rst
>> @@ -0,0 +1,142 @@
>> +.. SPDX-License-Identifier: GPL-2.0
>> +
>> +===========
>> +FUSE Caches
>> +===========
>> +
>> +Introduction
>> +============
>> +
>> +This document summarises the different types of caches that are used in FUSE.
>> +For each cache type, it attempts to document the rules that are followed to
>> +insert, validate and invalidate data into the cache.
>> +
>> +symlink caching
>> +===============
>> +
>> +Whenever there's a link resolution request, the VFS will call into
>> +``fuse_get_link()`` which will then send a ``FUSE_READLINK`` request to the
>> +user-space FUSE server. However, the server can ask the kernel to cache all
>> +links resolutions by setting the ``FUSE_CACHE_SYMLINKS`` flag during the
>> +``FUSE_INIT`` negotiation.
>> +
>> +If this flag is set, FUSE will immediately call into the VFS
>> +``__page_get_link()`` from the ``->get_link()`` inode operation. The first time
>> +this is done for a specific link, it will end-up sending the ``FUSE_READLINK``
>> +to user-space but the link contents will then be added into page-cache. The next
>> +time the link needs to be resolved, it will use the link content that is already
>> +cached, and will only fallback into sending the request to use-space if the
>> +folio isn't up-to-date.
>> +
>> +Attributes caching
>> +==================
>> +
>> +Attributes obtained from user-space, for example when an inode is first
>> +looked-up, are cached in the kernel. However, these attributes have a timeout
>> +associated and once expired they are invalidated.
>> +
>> +Thus, the ``FUSE_GETATTR`` operation will be sent to user-space only if the
>> +attributes aren't yet available, the attributes aren't valid (timeout), or if
>> +there is an explicit request for doing so (for example, by using the
>> +``AT_STATX_FORCE_SYNC`` flag in ``statx``). This may happen in the following
>> +situations:
>
> "This may happen" what may happen? I don't see it referring to anything.
Yeah, that sentence doesn't really make a lot of sense. I'll rephrase.
>> +
>> +#. An explicit request from VFS to get the attributes for an inode (through the
>> + ``->getattr()`` callback).
>> +#. When an ``->llseek()`` is requested to FUSE with a type of request
>> + (``whence``):
>> +
>> + - ``SEEK_{HOLE,DATA}`` and the user-space doesn't implement the
>> + ``FUSE_LSEEK`` operation (it has returned ``ENOSYS``), or
>> + - ``SEEK_END``
>> +
>> +#. When doing a buffered read past EOF or automatic page cache invalidation mode
>> + is enabled (``FUSE_AUTO_INVAL_DATA``).
>> +#. When doing a buffered write with write-back cache enabled
>> + (``FUSE_CAP_WRITEBACK_CACHE``).
>
> This list is incomplete and strange. it has post EOF write for
> writeback which is the exception
> and leaves out every non writeback write.
This list was meant to list the scenarios where the FUSE_GETATTR is sent
(the "this may happen" above). But I'll review the list again.
> If you composed this list yourself I highly recommend an LLM for this task
> if you used LLM I suggest a stronger model.
>
> Generally speaking, I find that today's robots are much better at writing these
> sorts of docs than I am - as long as I sit at the helm and guide them
> about where to expand on and where to keep it concise.
Thank you for the suggestion (I did not use an LLM btw). In fact, do you
think this document is really useful, given that everyone will be running
an LLM anyway? (I've been asking this question myself...)
>> +
>> +ACL caching
>> +===========
>> +
>> +FUSE has allowed the usage of POSIX ACLs for a long time as they could be set
>> +and accessed simply as extended attributes. However, it was only with the
>> +addition of the ``FUSE_POSIX_ACL`` flag that ACLs started to be fully supported.
>> +Without this flag, ACLs can still be set, but the VFS won't use them for
>> +performing permission checks - that would be the user-space server's
>> +responsibility.
>> +
>> +Also, without setting ``FUSE_POSIX_ACL``, ACLs will not be cached by the kernel.
>> +In this case, new inodes ``i_acl`` and ``i_default_acl`` fields will be set to
>> +``ACL_DONT_CACHE``.
>> +
>> +On the other hand, if ``FUSE_POSIX_ACL`` is set during ``FUSE_INIT``, when an
>> +ACL is accessed the VFS layer will first check if it's already cached. If it is
>> +not, FUSE ``->get_acl`` operation is called, which will eventually send a
>> +user-space request. Future accesses to this inode ACL will then use the cached
>> +data.
>> +
>> +Setting an ACL in an inode, however, won't cache it immediately. It will send
>> +user-space a request with the new ACL, and the FUSE server may perform some
>> +modifications before storing it.
>
> Do not encourage this by documenting it please.
> It reinforces that this was by design, rather than an oversight which
> we don't know.
Eh! OK, I'll stop that sentence after the comma :-)
>> +
>> +On the other hand, ACLs will be removed for the cache in the following
>> +situations:
>> +
>> +- When setting an ACL in an inode and the user-space server has set the
>> + ``FUSE_POSIX_ACL`` flag, all previously cached ACLs for this inode will be
>> + invalidated.
>> +- When invalidating an inode through the ``FUSE_NOTIFY_INVAL_INODE`` operation.
>> +- When ``->d_revalidate()`` is called for a dentry that requires a lookup (e.g.
>> + it has expired) and that lookup operation is successful.
>> +- When the VFS needs to check access rights for an inode (by calling
>> + ``->permission()``), attributes may need to be refreshed. If that happens,
>> + any cached ACLs for that inode will be invalidated.
>> +- After setting an inode attribute (i.e. operation ``FUSE_SETATTR`` is sent to
>> + user-space), the user-space server may have also updated the ACLs, so any
>> + cached ACLs for this inode are also invalidated.
>> +- While processing ``FUSE_READDIRPLUS`` and a new dentry is added (unless this
>> + dentry is already being looked up (``DCACHE_PAR_LOOKUP``))
>> +- In general, when there is the need to sent a ``FUSE_STATX`` or
>> + ``FUSE_GETATTR`` to user-space (e.g. because the attributes have expired).
>> + This may happen in the following cases:
>> +
>> + - When doing an ``->llseek()`` on a file with ``SEEK_END``, ``SEEK_HOLE`` or
>> + ``SEEK_DATA``.
>> + - When the ``FUSE_AUTO_INVAL_DATA`` flag is set at ``INIT`` time (to
>> + automatically invalidate cached pages), and a buffered read
>> + (``->read_iter()``) past EOF is done on a non-passthrough file.
>> + - When the ``FUSE_WRITEBACK_CACHE`` flag is set at ``INIT`` time, and a
>> + buffered write (``->write_iter()``) past EOF is done on a non-passthrough
>> + file.
>> + - When the ``FUSE_AUTO_INVAL_DATA`` flag is set at ``INIT`` time and the VFS
>> + needs to read a directory contents (``->iterate_shared()``) for a
>> + directory that is allowed to be cached.
>
> No reason to repeat the reasons for attr cache invalidation that were
> just listed above
>
>> +
>> +readdir caching
>> +===============
>> +
>> +When opening a directory for doing a readdir, a ``FUSE_OPENDIR`` will be sent
>> +and the user-space server will be responsible for setting the open flags related
>> +with caching, namely ``FOPEN_KEEP_CACHE`` and ``FOPEN_CACHE_DIR``.
>> +
>> +If neither flags are set by the user-space FUSE server, then every ``readdir``
>> +will result in a ``FUSE_READDIR`` (or ``FUSE_READDIRPLUS``) request being sent.
>> +If ``FOPEN_CACHE_DIR`` is set by the server, then the result of a ``readdir``
>> +will be cached by the kernel and reused. However, if ``FOPEN_KEEP_CACHE`` isn't
>> +also set, the cache will be invalidated next time the directory is open.
>
> Confusing.
> FOPEN_KEEP_CACHE is about keeping the cache on THIS open not on
> some NEXT open.
Right, that's true. I just wanted to emphasise that the invalidation
doesn't happen on a close, but on the next open. But I'll rephrase,
thanks.
Cheers,
--
Luís
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [RFC PATCH v2 5/8] selftests/fuse: factor-out test fixture setup/teardown
2026-08-18 13:09 ` Amir Goldstein
@ 2026-08-18 15:51 ` Luis Henriques
0 siblings, 0 replies; 15+ messages in thread
From: Luis Henriques @ 2026-08-18 15:51 UTC (permalink / raw)
To: Amir Goldstein
Cc: Miklos Szeredi, Chen Linxuan, Jonathan Corbet, Shuah Khan,
fuse-devel, linux-kernel, linux-kselftest, Matt Harvey,
kernel-dev
On Tue, Aug 18 2026, Amir Goldstein wrote:
> On Mon, Aug 17, 2026 at 4:11 PM Luis Henriques <luis@igalia.com> wrote:
>>
>> In order to reduce new tests setup/teardown code duplication, factor-out
>> these functions from the existing acl_cache test.
>
> When I read this I thought you were going to share these helpers with the
> new symlink and readdir cache tests, but you did not.
>
> Maybe a fuse_common.c would make sense to reduce boiler plate
> in new fuse tests.
>
> These helpers and fixture look pretty similar in all three tests.
True. I'll try to reduce the duplication by moving the common place.
Cheers,
--
Luís
> Thanks,
> Amir.
>
>
>>
>> Signed-off-by: Luis Henriques <luis@igalia.com>
>> ---
>> .../filesystems/fuse/fuse_acl_cache_test.c | 82 ++++++++++++-------
>> 1 file changed, 53 insertions(+), 29 deletions(-)
>>
>> 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 2411a6e285f1..8bdc90572be2 100644
>> --- a/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c
>> +++ b/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c
>> @@ -50,6 +50,8 @@
>>
>> #include "kselftest_harness.h"
>>
>> +#define MAX_ERR_MSG 256
>> +
>> /* ---- ACL binary encoding ------------------------------------------------ */
>> /*
>> * POSIX ACL v2 xattr format (little-endian):
>> @@ -193,52 +195,74 @@ FIXTURE(acl_cache) {
>> pthread_t thread;
>> };
>>
>> -FIXTURE_SETUP(acl_cache)
>> +int fs_setup(struct fuse_session **se, char *mountpoint, char *file_path,
>> + pthread_t *thread, char *err)
>> {
>> char *fuse_argv[] = { "fuse_acl_cache_test", NULL };
>> struct fuse_args args = FUSE_ARGS_INIT(1, fuse_argv);
>>
>> - g_ds.acl = acl_a;
>> - g_ds.acl_size = sizeof(acl_a);
>> - g_ds.getxattr_count = 0;
>> -
>> - strcpy(self->mountpoint, "/tmp/acl_cache_test_XXXXXX");
>> - if (!mkdtemp(self->mountpoint))
>> - SKIP(return, "mkdtemp: %s", strerror(errno));
>> + strcpy(mountpoint, "/tmp/acl_cache_test_XXXXXX");
>> + if (!mkdtemp(mountpoint)) {
>> + snprintf(err, MAX_ERR_MSG, "mkdtemp: %s", strerror(errno));
>> + return -1;
>> + }
>>
>> - snprintf(self->file_path, sizeof(self->file_path),
>> - "%s/" FILE_NAME, self->mountpoint);
>> + snprintf(file_path, PATH_MAX, "%s/" FILE_NAME, mountpoint);
>>
>> - self->se = fuse_session_new(&args, &fs_ops, sizeof(fs_ops), NULL);
>> - if (!self->se) {
>> - rmdir(self->mountpoint);
>> - SKIP(return, "fuse_session_new failed");
>> + *se = fuse_session_new(&args, &fs_ops, sizeof(fs_ops), NULL);
>> + if (!*se) {
>> + rmdir(mountpoint);
>> + snprintf(err, MAX_ERR_MSG, "fuse_session_new failed");
>> + return -1;
>> }
>>
>> - if (fuse_session_mount(self->se, self->mountpoint)) {
>> - fuse_session_destroy(self->se);
>> - rmdir(self->mountpoint);
>> - SKIP(return, "fuse_session_mount failed "
>> - "(missing fusermount3 or insufficient privileges)");
>> + if (fuse_session_mount(*se, mountpoint)) {
>> + fuse_session_destroy(*se);
>> + rmdir(mountpoint);
>> + snprintf(err, MAX_ERR_MSG, "fuse_session_mount failed "
>> + "(missing fusermount3 or insufficient privileges)");
>> + return -1;
>> }
>>
>> - if (pthread_create(&self->thread, NULL, run_daemon, self->se)) {
>> - fuse_session_unmount(self->se);
>> - fuse_session_destroy(self->se);
>> - rmdir(self->mountpoint);
>> - SKIP(return, "pthread_create: %s", strerror(errno));
>> + if (pthread_create(thread, NULL, run_daemon, *se)) {
>> + fuse_session_unmount(*se);
>> + fuse_session_destroy(*se);
>> + rmdir(mountpoint);
>> + snprintf(err, MAX_ERR_MSG, "pthread_create: %s", strerror(errno));
>> + return -1;
>> }
>>
>> fuse_opt_free_args(&args);
>> +
>> + return 0;
>> +}
>> +
>> +static void fs_teardown(struct fuse_session *se, pthread_t thread,
>> + char *mountpoint)
>> +{
>> + fuse_session_exit(se);
>> + fuse_session_unmount(se);
>> + pthread_join(thread, NULL);
>> + fuse_session_destroy(se);
>> + rmdir(mountpoint);
>> +}
>> +
>> +FIXTURE_SETUP(acl_cache)
>> +{
>> + char err[MAX_ERR_MSG];
>> +
>> + g_ds.acl = acl_a;
>> + g_ds.acl_size = sizeof(acl_a);
>> + g_ds.getxattr_count = 0;
>> +
>> + if (fs_setup(&self->se, self->mountpoint, self->file_path,
>> + &self->thread, err))
>> + SKIP(return, err);
>> }
>>
>> FIXTURE_TEARDOWN(acl_cache)
>> {
>> - fuse_session_exit(self->se);
>> - fuse_session_unmount(self->se);
>> - pthread_join(self->thread, NULL);
>> - fuse_session_destroy(self->se);
>> - rmdir(self->mountpoint);
>> + fs_teardown(self->se, self->thread, self->mountpoint);
>> }
>>
>> static int do_force_statx(const char *path)
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [RFC PATCH v2 0/8] fuse: caches documentation and testing
2026-08-18 13:13 ` [RFC PATCH v2 0/8] fuse: caches documentation and testing Amir Goldstein
@ 2026-08-18 15:52 ` Luis Henriques
0 siblings, 0 replies; 15+ messages in thread
From: Luis Henriques @ 2026-08-18 15:52 UTC (permalink / raw)
To: Amir Goldstein
Cc: Miklos Szeredi, Chen Linxuan, Jonathan Corbet, Shuah Khan,
fuse-devel, linux-kernel, linux-kselftest, Matt Harvey,
kernel-dev
On Tue, Aug 18 2026, Amir Goldstein wrote:
> On Mon, Aug 17, 2026 at 4:11 PM Luis Henriques <luis@igalia.com> wrote:
>>
>> Hi!
>>
>> I'm (finally!) sending v2 of this patchset. It tries to document (and add
>> a few kselftests) to the different types of caches that are currently in use
>> within FUSE. As I mentioned in v1 the idea for documenting this came from
>> Miklos during this year's LSFMM, where he mentioned he would like to see
>> caches usage documented before he could merge an initial version of fusex.
>>
>> This version documents symlinks, attributes, ACLs and readdir caches. Still
>> missing: dentries and data caching.
>>
>> I'm still sending it as an RFC as I'm still not sure if it fulfils Miklos'
>> initial goal.
>>
>> As usual feedback is welcome, as I'll (slowly) continue looking into other
>> cache types.
>>
>> Major changes since v1:
>> - Added more caches to the document
>> - Changed tests format to single self-contained binaries
>> - Since Amir added a new fuse3-based test, the conversion to fuse3 of the
>> fusectl has minor changes to the Makefile
>> - NOTE: I've kept Amir's Reviewed-by anyway (should I drop it?)
>
> It's fine. I trust your judgement.
>
> Please see Sashiko review comments.
Sure, I will do that. (For some reason I assumed I would receive an email
if there was a review for my patches. Looks like that's not the case.)
> My main concern is with the doc - it's not an easy read
> and I would have liked it to be and I found many correctness error
> which is not a good sign.
Hmm... OK, I'll need to go through those errors and try my best to see if
I can fix them.
Thanks a lot for your feedback, Amir. Much appreciated.
Cheers,
--
Luís
^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2026-08-18 15:51 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-17 14:11 [RFC PATCH v2 0/8] fuse: caches documentation and testing Luis Henriques
2026-08-17 14:11 ` [RFC PATCH v2 1/8] Documentation: fuse: add document on caches being used by FUSE Luis Henriques
2026-08-18 12:40 ` Amir Goldstein
2026-08-18 15:51 ` Luis Henriques
2026-08-17 14:11 ` [RFC PATCH v2 2/8] selftests/fuse: convert fusectl test to fuse3 Luis Henriques
2026-08-17 14:11 ` [RFC PATCH v2 3/8] selftests/fuse: check that fusectlfs is mounted Luis Henriques
2026-08-17 14:11 ` [RFC PATCH v2 4/8] selftests/fuse: add fuse symlink caching test Luis Henriques
2026-08-17 14:11 ` [RFC PATCH v2 5/8] selftests/fuse: factor-out test fixture setup/teardown Luis Henriques
2026-08-18 13:09 ` Amir Goldstein
2026-08-18 15:51 ` Luis Henriques
2026-08-17 14:11 ` [RFC PATCH v2 6/8] selftests/fuse: use dynamically allocated memory to store ACLs Luis Henriques
2026-08-17 14:11 ` [RFC PATCH v2 7/8] selftests/fuse: add some extra ACL caching tests Luis Henriques
2026-08-17 14:11 ` [RFC PATCH v2 8/8] selftests/fuse: add fuse readdir caching test Luis Henriques
2026-08-18 13:13 ` [RFC PATCH v2 0/8] fuse: caches documentation and testing Amir Goldstein
2026-08-18 15:52 ` Luis Henriques
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox