Re: [RFC PATCH v1 4/5] selftests/filesystems: add fuse symlink caching test
Amir Goldstein <[email protected]> Tue, 21 Jul 2026 12:39:22 +0200
| Newsgroups | dev.linux.lists.fuse-devel,org.kernel.vger.linux-kernel,org.kernel.vger.linux-kselftest |
|---|---|
| Message-ID | <CAOQ4uxiOqkXfySiFcYhWUR_c1_UeAA62vK2Py-ML=TpQyrUbCw@mail.gmail.com> |
On Wed, Jul 8, 2026 at 3:11=E2=80=AFPM Luis Henriques <[email protected]> wro= te: > > 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 requeste= d. > > 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/te= sting/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 +=3D -Wall -O2 -g $(KHDR_INCLUDES) > > TEST_GEN_PROGS :=3D fusectl_test > -TEST_GEN_FILES :=3D fuse_mnt > +TEST_PROGS :=3D symlink_test.sh > +TEST_GEN_FILES :=3D fuse_mnt symlink_fs > > include ../../lib.mk > > @@ -19,3 +20,6 @@ endif > > $(OUTPUT)/fuse_mnt: CFLAGS +=3D $(VAR_CFLAGS) > $(OUTPUT)/fuse_mnt: LDLIBS +=3D $(VAR_LDLIBS) > + > +$(OUTPUT)/symlink_fs: CFLAGS +=3D $(VAR_CFLAGS) > +$(OUTPUT)/symlink_fs: LDLIBS +=3D $(VAR_LDLIBS) > diff --git a/tools/testing/selftests/filesystems/fuse/symlink_fs.c b/tool= s/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 na= med > + * 'file' and a symlink to that file named 'link'. Whenever the ->readl= ink() 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 b= y > + * default). This means that, if caching is enabled, resolving a symlin= k 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[] =3D { > + { "--cache", offsetof(struct options, cache_symlinks), 1 }, > + FUSE_OPT_END > +}; > + > +static int readlink_counter =3D 0; > + > +static void *symlink_init(struct fuse_conn_info *conn, struct fuse_confi= g *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 =3D 0; > + > + memset(stbuf, 0, sizeof(struct stat)); > + if (strcmp(path, "/") =3D=3D 0) { > + stbuf->st_mode =3D S_IFDIR | 0755; > + stbuf->st_nlink =3D 2; > + } else if (strcmp(path + 1, FILE) =3D=3D 0) { > + char data[64]; > + > + stbuf->st_mode =3D S_IFREG | 0444; > + stbuf->st_nlink =3D 1; > + stbuf->st_size =3D sprintf(data, "%d\n", readlink_counter= ); > + } else if (strcmp(path + 1, LINK) =3D=3D 0) { > + stbuf->st_mode =3D S_IFLNK | 0444; > + stbuf->st_nlink =3D 1; > + stbuf->st_size =3D strlen(FILE); > + } else > + res =3D -ENOENT; > + > + return res; > +} > + > +static int symlink_readlink(const char *path, char *buf, size_t size) > +{ > + if (strcmp(path + 1, LINK) !=3D 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 of= f, > + struct fuse_file_info *fi) > +{ > + char data[64]; > + int len; > + > + len =3D sprintf(data, "%d\n", readlink_counter); > + memcpy(buf, data, len); > + > + return len; > +} > + > +static const struct fuse_operations symlink_oper =3D { > + .init =3D symlink_init, > + .getattr =3D symlink_getattr, > + .readlink =3D symlink_readlink, > + .read =3D symlink_read, > +}; > + > +int main(int argc, char *argv[]) > +{ > + int ret; > + struct fuse_args args =3D FUSE_ARGS_INIT(argc, argv); > + > + options.cache_symlinks =3D 0; > + if (fuse_opt_parse(&args, &options, option_spec, NULL) =3D=3D -1) > + return 1; > + > + ret =3D 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/t= ools/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=3D$(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=3D$(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. Thanks, Amir.