All of lore.kernel.org
 help / color / mirror / Atom feed
From: Luis Henriques <luis@igalia.com>
To: Miklos Szeredi <miklos@szeredi.hu>,
	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 v1 4/5] selftests/filesystems: add fuse symlink caching test
Date: Wed,  8 Jul 2026 14:11:21 +0100	[thread overview]
Message-ID: <20260708131122.2917-5-luis@igalia.com> (raw)
In-Reply-To: <20260708131122.2917-1-luis@igalia.com>

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       |   6 +-
 .../selftests/filesystems/fuse/symlink_fs.c   | 115 ++++++++++++++++++
 .../filesystems/fuse/symlink_test.sh          |  52 ++++++++
 4 files changed, 173 insertions(+), 1 deletion(-)
 create mode 100644 tools/testing/selftests/filesystems/fuse/symlink_fs.c
 create mode 100755 tools/testing/selftests/filesystems/fuse/symlink_test.sh

diff --git a/tools/testing/selftests/filesystems/fuse/.gitignore b/tools/testing/selftests/filesystems/fuse/.gitignore
index 3e72e742d08e..cfdc5ca3ded4 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
+symlink_fs
diff --git a/tools/testing/selftests/filesystems/fuse/Makefile b/tools/testing/selftests/filesystems/fuse/Makefile
index 422cd1b1688d..342da0878006 100644
--- a/tools/testing/selftests/filesystems/fuse/Makefile
+++ b/tools/testing/selftests/filesystems/fuse/Makefile
@@ -3,7 +3,8 @@
 CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES)
 
 TEST_GEN_PROGS := fusectl_test
-TEST_GEN_FILES := fuse_mnt
+TEST_PROGS := symlink_test.sh
+TEST_GEN_FILES := fuse_mnt symlink_fs
 
 include ../../lib.mk
 
@@ -19,3 +20,6 @@ endif
 
 $(OUTPUT)/fuse_mnt: CFLAGS += $(VAR_CFLAGS)
 $(OUTPUT)/fuse_mnt: LDLIBS += $(VAR_LDLIBS)
+
+$(OUTPUT)/symlink_fs: CFLAGS += $(VAR_CFLAGS)
+$(OUTPUT)/symlink_fs: LDLIBS += $(VAR_LDLIBS)
diff --git a/tools/testing/selftests/filesystems/fuse/symlink_fs.c b/tools/testing/selftests/filesystems/fuse/symlink_fs.c
new file mode 100644
index 000000000000..1aee26c91b3f
--- /dev/null
+++ b/tools/testing/selftests/filesystems/fuse/symlink_fs.c
@@ -0,0 +1,115 @@
+// 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'.  Whenever the ->readlink() is
+ * executed to resolve 'link' a counter will be incremented.  A ->read() to any
+ * filesystem object will return the value in this counter.
+ *
+ * A '--cache' argument will allow to enable symlink caching (disabled by
+ * default).  This means that, if caching is enabled, resolving a symlink will
+ * only call into user-space the first time.
+ */
+
+#define FUSE_USE_VERSION 31
+
+#include <fuse.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <assert.h>
+
+#define FILE	"file"
+#define LINK	"link"
+
+static struct options {
+	int cache_symlinks;
+} options;
+
+static const struct fuse_opt option_spec[] = {
+	{ "--cache", offsetof(struct options, cache_symlinks), 1 },
+	FUSE_OPT_END
+};
+
+static int readlink_counter = 0;
+
+static void *symlink_init(struct fuse_conn_info *conn, struct fuse_config *cfg)
+{
+	if (options.cache_symlinks)
+		fuse_set_feature_flag(conn, FUSE_CAP_CACHE_SYMLINKS);
+
+	return NULL;
+}
+
+static int symlink_getattr(const char *path, struct stat *stbuf,
+			   struct fuse_file_info *fi)
+{
+	int res = 0;
+
+	memset(stbuf, 0, sizeof(struct stat));
+	if (strcmp(path, "/") == 0) {
+		stbuf->st_mode = S_IFDIR | 0755;
+		stbuf->st_nlink = 2;
+	} else if (strcmp(path + 1, FILE) == 0) {
+		char data[64];
+
+		stbuf->st_mode = S_IFREG | 0444;
+		stbuf->st_nlink = 1;
+		stbuf->st_size = sprintf(data, "%d\n", readlink_counter);
+	} else if (strcmp(path + 1, LINK) == 0) {
+		stbuf->st_mode = S_IFLNK | 0444;
+		stbuf->st_nlink = 1;
+		stbuf->st_size = strlen(FILE);
+	} else
+		res = -ENOENT;
+
+	return res;
+}
+
+static int symlink_readlink(const char *path, char *buf, size_t size)
+{
+	if (strcmp(path + 1, LINK) != 0)
+		return -ENOENT;
+
+	memcpy(buf, FILE, strlen(FILE));
+	readlink_counter++;
+
+	return 0;
+}
+
+static int symlink_read(const char *path, char *buf, size_t sz, off_t off,
+			struct fuse_file_info *fi)
+{
+	char data[64];
+	int len;
+
+	len = sprintf(data, "%d\n", readlink_counter);
+	memcpy(buf, data, len);
+
+	return len;
+}
+
+static const struct fuse_operations symlink_oper = {
+	.init           = symlink_init,
+	.getattr	= symlink_getattr,
+	.readlink	= symlink_readlink,
+	.read		= symlink_read,
+};
+
+int main(int argc, char *argv[])
+{
+	int ret;
+	struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
+	options.cache_symlinks = 0;
+	if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+		return 1;
+
+	ret = fuse_main(args.argc, args.argv, &symlink_oper, NULL);
+	fuse_opt_free_args(&args);
+
+	return ret;
+}
diff --git a/tools/testing/selftests/filesystems/fuse/symlink_test.sh b/tools/testing/selftests/filesystems/fuse/symlink_test.sh
new file mode 100755
index 000000000000..546541c1920e
--- /dev/null
+++ b/tools/testing/selftests/filesystems/fuse/symlink_test.sh
@@ -0,0 +1,52 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+
+exit_cleanup()
+{
+	fusermount -u ./mnt
+	rmdir ./mnt
+}
+
+assert_value ()
+{
+	if [ $1 -ne $2 ]; then
+		echo "FAILED"
+		echo $3
+		exit 1
+	fi
+}
+
+set -e
+
+trap exit_cleanup EXIT
+
+mkdir -p mnt
+
+./symlink_fs ./mnt
+
+echo -n "Testing symlink without cached: "
+
+# When symlink caching is disabled every access to a symlink is expected to
+# result in a call to user-space
+for i in $(seq 1 10); do
+	readlink ./mnt/link > /dev/null
+done
+
+res=$(cat ./mnt/file)
+assert_value $res $i "Got $res, expected $i"
+echo "PASSED"
+
+fusermount -u ./mnt
+
+./symlink_fs --cache ./mnt
+
+echo -n "Testing symlink with cache: "
+
+# With caching enabled, there will only be a single call into user-space
+for i in $(seq 0 10); do
+	readlink ./mnt/link > /dev/null
+done
+
+res=$(cat ./mnt/file)
+assert_value 1 $res "Got $ res, expected 1"
+echo "PASSED"

  parent reply	other threads:[~2026-07-08 13:11 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-08 13:11 [RFC PATCH v1 0/5] fuse: caches documentation and testing Luis Henriques
2026-07-08 13:11 ` [RFC PATCH v1 1/5] Documentation: fuse: add document on caches being used by FUSE Luis Henriques
2026-07-21 10:34   ` Amir Goldstein
2026-07-21 12:55     ` Luis Henriques
2026-07-08 13:11 ` [RFC PATCH v1 2/5] selftests/filesystems: convert fusectl test to fuse3 Luis Henriques
2026-07-21 10:35   ` Amir Goldstein
2026-07-08 13:11 ` [RFC PATCH v1 3/5] selftests/filesystems: check that fusectlfs is mounted Luis Henriques
2026-07-21 10:36   ` Amir Goldstein
2026-07-08 13:11 ` Luis Henriques [this message]
2026-07-21 10:39   ` [RFC PATCH v1 4/5] selftests/filesystems: add fuse symlink caching test Amir Goldstein
2026-07-21 12:58     ` Luis Henriques
2026-07-08 13:11 ` [RFC PATCH v1 5/5] selftests/filesystems: add fuse ACLs " Luis Henriques
2026-07-21 10:43   ` Amir Goldstein
2026-07-21 13:06     ` Luis Henriques
2026-07-21 13:33       ` 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=20260708131122.2917-5-luis@igalia.com \
    --to=luis@igalia.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=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.