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"

