selftests/fuse: add the missing symlink caching test

Commit 3e14f079d7 ("selftests/fuse: add fuse symlink caching test")
registered fuse_symlink_cache_test in the fuse selftest Makefile and
.gitignore, but the test source was not part of the applied commit, so
building the fuse selftests fails:

  make[1]: *** No rule to make target 'fuse_symlink_cache_test.c', needed by '.../fuse_symlink_cache_test'.

Add fuse_symlink_cache_test.c as posted in Luis Henriques's v5
submission. The test serves a file and a symlink to it from a libfuse
low-level filesystem, resolves the symlink 100 times, and counts the
readlink requests that reach the server: one when FUSE_CAP_CACHE_SYMLINKS
is set during FUSE_INIT, and all 100 when it is not.

Fixes: 3e14f079d7 ("selftests/fuse: add fuse symlink caching test")
Reported-by: kernel test robot <lkp@intel.com>
Closes: https://lore.kernel.org/r/202610170822.oEJ1Autq-lkp@intel.com/
Link: https://lore.kernel.org/r/20261001164916.15311-8-luis@igalia.com
Originally-by: Luis Henriques <luis@igalia.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
Reviewed-by: Luis Henriques <luis@igalia.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
Sasha Levin 2026-10-26 15:40:08 -04:00 committed by Linus Torvalds
parent 1ea806165e
commit aba9df1806

View File

@ -0,0 +1,162 @@
// 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 <fuse_lowlevel.h>
#include "kselftest_harness.h"
#include "fuse_common.h"
#define FILENAME "file"
#define FILE_INO 42
#define LINKNAME "link"
#define LINK_INO 43
#define TIMEOUT 86400.0f
struct test_state {
pthread_mutex_t lock;
int readlink_counter;
bool cache;
} 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) != 0 && strcmp(name, LINKNAME) != 0)) {
fuse_reply_err(req, ENOENT);
return;
}
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);
}
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 fs_ops = {
.init = fs_init,
.lookup = fs_lookup,
.readlink = fs_readlink,
};
FIXTURE(symlink_cache)
{
struct fuse_common_ctx ctx;
};
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 err[MAX_ERR_MSG];
pthread_mutex_lock(&test_state.lock);
test_state.readlink_counter = 0;
test_state.cache = variant->cache;
pthread_mutex_unlock(&test_state.lock);
if (fs_setup(&fs_ops, &self->ctx, err))
SKIP(return, err);
}
FIXTURE_TEARDOWN(symlink_cache)
{
fs_teardown(&self->ctx);
}
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->ctx.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