Something went wrong. Try again.
(I'm so sorry!) Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
Something went wrong. Try again.
C
at master
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274// SPDX-License-Identifier: GPL-2.0/* * Simple filesystem to test FUSE readdir cache * * It will simply perform readdir operations on a directory checking how many * times a request is sent to user-space using all the possible caching * combination setting (FOPEN_KEEP_CACHE and FOPEN_CACHE_DIR flags). */
#include <stdio.h>#include <dirent.h>
#include "kselftest_harness.h"
#include "fuse_common.h"
#define DIRNAME "mydir"#define FILENAME "myfile"
#define DIR_INO 42#define FILE_INO 43#define DOT_INO 40#define DOTDOT_INO 41
#define TIMEOUT 86400.0f
struct test_state { pthread_mutex_t lock; time_t mtime; int readdir_counter; bool cache_readdir; bool keep_cache;} test_state = { .lock = PTHREAD_MUTEX_INITIALIZER,};
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, DIRNAME) != 0) { fuse_reply_err(req, ENOENT); return; }
e.ino = DIR_INO; e.attr.st_mode = S_IFDIR | 0755; e.attr.st_nlink = 1; pthread_mutex_lock(&test_state.lock); e.attr.st_mtime = test_state.mtime; pthread_mutex_unlock(&test_state.lock); e.attr_timeout = TIMEOUT; e.entry_timeout = TIMEOUT;
fuse_reply_entry(req, &e);}
static int fill_stat(fuse_ino_t ino, struct stat *st){ int ret = 0;
st->st_ino = ino; pthread_mutex_lock(&test_state.lock); st->st_mtime = test_state.mtime; pthread_mutex_unlock(&test_state.lock);
switch (ino) { case FUSE_ROOT_ID: st->st_mode = S_IFDIR | 0755; st->st_nlink = 2; break; case DOT_INO: case DOTDOT_INO: case DIR_INO: st->st_mode = S_IFDIR | 0755; st->st_nlink = 1; break; case FILE_INO: st->st_mode = S_IFREG | 0444; st->st_nlink = 1; break; default: ret = -1; break; }
return ret;}
static void fs_getattr(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi){ struct stat st = {};
if (fill_stat(ino, &st) < 0) fuse_reply_err(req, ENOENT); else fuse_reply_attr(req, &st, TIMEOUT);}
static void fs_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi){ pthread_mutex_lock(&test_state.lock); fi->keep_cache = test_state.keep_cache; fi->cache_readdir = test_state.cache_readdir; pthread_mutex_unlock(&test_state.lock); fuse_reply_open(req, fi);}
static void fs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t offset, struct fuse_file_info *fi){ struct stat st = {}; char buf[1024]; char *pbuf; size_t rem = size; size_t sz; int nextoff = 0;
if (ino != DIR_INO) { fuse_reply_err(req, ENOTDIR); return; } if (offset) { fuse_reply_buf(req, NULL, 0); return; } pbuf = buf; fill_stat(DOT_INO, &st); sz = fuse_add_direntry(req, pbuf, rem, ".", &st, nextoff++); rem -= sz; pbuf += sz; fill_stat(DOTDOT_INO, &st); sz = fuse_add_direntry(req, pbuf, rem, "..", &st, nextoff++); rem -= sz; pbuf += sz; fill_stat(FILE_INO, &st); sz = fuse_add_direntry(req, pbuf, rem, FILENAME, &st, nextoff++); rem -= sz;
fuse_reply_buf(req, buf, size - rem);
pthread_mutex_lock(&test_state.lock); test_state.readdir_counter++; pthread_mutex_unlock(&test_state.lock);}
static const struct fuse_lowlevel_ops fs_ops = { .lookup = fs_lookup, .getattr = fs_getattr, .opendir = fs_opendir, .readdir = fs_readdir,};
FIXTURE(readdir_cache){ struct fuse_common_ctx ctx;};
FIXTURE_VARIANT(readdir_cache){ bool cache_readdir; bool keep_cache;};FIXTURE_VARIANT_ADD(readdir_cache, nocache){ .cache_readdir = false, .keep_cache = false,};FIXTURE_VARIANT_ADD(readdir_cache, cache_readdir){ .cache_readdir = true, .keep_cache = false,};FIXTURE_VARIANT_ADD(readdir_cache, keep_cache){ .cache_readdir = false, .keep_cache = true,};FIXTURE_VARIANT_ADD(readdir_cache, cache){ .cache_readdir = true, .keep_cache = true,};
FIXTURE_SETUP(readdir_cache){ char err[MAX_ERR_MSG];
pthread_mutex_lock(&test_state.lock); test_state.mtime = time(NULL); test_state.readdir_counter = 0; test_state.cache_readdir = variant->cache_readdir; test_state.keep_cache = variant->keep_cache; pthread_mutex_unlock(&test_state.lock);
if (fs_setup(&fs_ops, &self->ctx, err)) SKIP(return, err);}
FIXTURE_TEARDOWN(readdir_cache){ fs_teardown(&self->ctx);}
TEST_F(readdir_cache, test_readdir_cache){ struct dirent *dentry; DIR *dir; char pathname[PATH_MAX]; int total_counter, rewind_counter; int dentrycount;
sprintf(pathname, "%s/%s", self->ctx.mountpoint, DIRNAME);
dir = opendir(pathname); if (dir == NULL) TH_LOG("opendir(): %s", strerror(errno)); ASSERT_NE(dir, NULL);
errno = 0; dentrycount = 0; while ((dentry = readdir(dir))) dentrycount++; ASSERT_EQ(errno, 0); ASSERT_EQ(dentrycount, 3);
rewinddir(dir); errno = 0; dentrycount = 0; while ((dentry = readdir(dir))) dentrycount++; ASSERT_EQ(errno, 0); ASSERT_EQ(dentrycount, 3);
ASSERT_EQ(closedir(dir), 0);
pthread_mutex_lock(&test_state.lock); rewind_counter = test_state.readdir_counter; pthread_mutex_unlock(&test_state.lock);
dir = opendir(pathname); if (dir == NULL) TH_LOG("opendir(): %s", strerror(errno)); ASSERT_NE(dir, NULL);
errno = 0; dentrycount = 0; while ((dentry = readdir(dir))) dentrycount++; ASSERT_EQ(errno, 0); ASSERT_EQ(dentrycount, 3);
ASSERT_EQ(closedir(dir), 0);
pthread_mutex_lock(&test_state.lock); total_counter = test_state.readdir_counter; pthread_mutex_unlock(&test_state.lock);
if (!variant->cache_readdir) { ASSERT_EQ(rewind_counter, 2); ASSERT_EQ(total_counter, 3); } else if (!variant->keep_cache) { ASSERT_EQ(rewind_counter, 1); ASSERT_EQ(total_counter, 2); } else { ASSERT_EQ(rewind_counter, 1); ASSERT_EQ(total_counter, 1); }}
TEST_HARNESS_MAIN