Amir Goldstein <[email protected]> writes: > On Wed, Jul 8, 2026 at 3:11 PM Luis Henriques <[email protected]> wrote: >> >> 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 <[email protected]> >> --- >> .../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" >> > > As we discussed, consider doing this as a single c test program, > but not a must as you wish.
Sure, next iteration will have this test as a stand-alone c program. Cheers, -- Luís

