This adds some extra tests to ACL caching: - Verify that reading ACLs results in the expected number of requests being sent user-space, depending on whether cache is enabled or disabled - Verify caching behaviour on some caching invalidation scenarios
While there, add test binary to .gitignore. Signed-off-by: Luis Henriques <[email protected]> --- .../selftests/filesystems/fuse/.gitignore | 1 + .../filesystems/fuse/fuse_acl_cache_test.c | 177 ++++++++++++++++++ 2 files changed, 178 insertions(+) diff --git a/tools/testing/selftests/filesystems/fuse/.gitignore b/tools/testing/selftests/filesystems/fuse/.gitignore index 873304f8d1a1..d5061752e2b0 100644 --- a/tools/testing/selftests/filesystems/fuse/.gitignore +++ b/tools/testing/selftests/filesystems/fuse/.gitignore @@ -2,3 +2,4 @@ fuse_mnt fusectl_test fuse_symlink_cache_test +fuse_acl_cache_test diff --git a/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c b/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c index bf8b3807e603..c1291fb1c8b9 100644 --- a/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c +++ b/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c @@ -93,6 +93,7 @@ struct daemon_state { uint8_t *acl; size_t acl_size; int getxattr_count; + bool cache; }; /* @@ -101,9 +102,17 @@ struct daemon_state { */ static struct daemon_state g_ds = { .lock = PTHREAD_MUTEX_INITIALIZER, + .cache = false, }; /* ---- FUSE lowlevel callbacks -------------------------------------------- */ +static void fs_init(void *userdata, struct fuse_conn_info *conn) +{ + pthread_mutex_lock(&g_ds.lock); + if (g_ds.cache) + fuse_set_feature_flag(conn, FUSE_CAP_POSIX_ACL); + pthread_mutex_unlock(&g_ds.lock); +} static void fs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) { @@ -125,6 +134,8 @@ static void fs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) e.attr.st_ino = FILE_INO; e.attr.st_mode = S_IFREG | 0644; e.attr.st_nlink = 1; + e.attr.st_uid = getuid(); + e.attr.st_gid = getgid(); fuse_reply_entry(req, &e); } @@ -185,10 +196,38 @@ static void fs_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name, free(acl); } +static void fs_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name, + const char *value, size_t size, int flags) +{ + int ret = 0; + uint8_t *acl; + + if (ino != FILE_INO) + ret = ENOENT; + else if (!strcmp(name, "system.posix_acl_access")) { + acl = malloc(size); + if (acl) { + memcpy(acl, value, size); + pthread_mutex_lock(&g_ds.lock); + if (g_ds.acl) + free(g_ds.acl); + g_ds.acl = acl; + g_ds.acl_size = size; + pthread_mutex_unlock(&g_ds.lock); + } else + ret = ENOMEM; + } else + ret = ENOTSUP; + + fuse_reply_err(req, ret); +} + static const struct fuse_lowlevel_ops fs_ops = { + .init = fs_init, .lookup = fs_lookup, .getattr = fs_getattr, .getxattr = fs_getxattr, + .setxattr = fs_setxattr, }; /* ---- Daemon thread ------------------------------------------------------- */ @@ -269,6 +308,7 @@ FIXTURE_SETUP(acl_cache) ASSERT_NE(g_ds.acl, NULL); memcpy(g_ds.acl, acl_a, g_ds.acl_size); g_ds.getxattr_count = 0; + g_ds.cache = false; if (fs_setup(&self->se, self->mountpoint, self->file_path, &self->thread, err)) @@ -390,4 +430,141 @@ TEST_F(acl_cache, stale_after_force_sync) EXPECT_EQ(count, 4); } +FIXTURE(acl_cache_onoff) +{ + struct fuse_session *se; + char mountpoint[PATH_MAX]; + char pathname[PATH_MAX]; + pthread_t thread; +}; + +FIXTURE_VARIANT(acl_cache_onoff) { bool cache; }; +FIXTURE_VARIANT_ADD(acl_cache_onoff, nocache) { .cache = false, }; +FIXTURE_VARIANT_ADD(acl_cache_onoff, docache) { .cache = true, }; + +FIXTURE_SETUP(acl_cache_onoff) +{ + char err[MAX_ERR_MSG]; + + g_ds.acl = NULL; + g_ds.acl_size = 0; + g_ds.getxattr_count = 0; + g_ds.cache = variant->cache; + + if (fs_setup(&self->se, self->mountpoint, self->pathname, + &self->thread, err)) + SKIP(return, err); +} + +FIXTURE_TEARDOWN(acl_cache_onoff) +{ + fs_teardown(self->se, self->thread, self->mountpoint); + free(g_ds.acl); +} + +/* + * This is the most basic ACL caching test: verify that, when reading ACLs for + * an inode, user-space is called: + * - Only once if ACLs cache is enabled, or + * - Once per access if cache i disabled. + */ +TEST_F(acl_cache_onoff, test_acl_cache_enable_disable) +{ + char buf[512]; + ssize_t sz; + bool cache; + int counter; + int i; + + ASSERT_EQ(lsetxattr(self->pathname, "system.posix_acl_access", + acl_a, sizeof(acl_a), 0), 0); + + for (i = 0; i < 100; i++) { + sz = lgetxattr(self->pathname, "system.posix_acl_access", + buf, sizeof(buf)); + ASSERT_EQ(sz, sizeof(acl_a)); + ASSERT_EQ(memcmp(buf, acl_a, sz), 0); + } + + pthread_mutex_lock(&g_ds.lock); + counter = g_ds.getxattr_count; + cache = g_ds.cache; + pthread_mutex_unlock(&g_ds.lock); + + if (cache) { + ASSERT_EQ(counter, 1); + } else { + ASSERT_EQ(counter, 100); + } + + TH_LOG("User-space called %d time(s) with ACL caching %s", + counter, cache ? "enabled" : "disabled"); +} + +/* + * Test caching invalidation for several scenarios: + * 1. When a new ACL is set + * 2. When invalidating an inode (NOTIFY_INODE_INVAL) + */ +TEST_F(acl_cache_onoff, test_acl_cache_invalidation) +{ + char buf[512]; + ssize_t sz; + int counter; + bool cache; + int i; + + /* Set an ACL */ + ASSERT_EQ(lsetxattr(self->pathname, "system.posix_acl_access", + acl_a, sizeof(acl_a), 0), 0); + + for (i = 0; i < 100; i++) { + sz = lgetxattr(self->pathname, "system.posix_acl_access", + buf, sizeof(buf)); + ASSERT_EQ(sz, sizeof(acl_a)); + ASSERT_EQ(memcmp(buf, acl_a, sz), 0); + } + + /* 1. force cache invalidation by setting a new ACL */ + ASSERT_EQ(lsetxattr(self->pathname, "system.posix_acl_access", + acl_b, sizeof(acl_b), 0), 0); + + sz = lgetxattr(self->pathname, "system.posix_acl_access", + buf, sizeof(buf)); + ASSERT_EQ(sz, sizeof(acl_b)); + ASSERT_EQ(memcmp(buf, acl_b, sz), 0); + + pthread_mutex_lock(&g_ds.lock); + counter = g_ds.getxattr_count; + cache = g_ds.cache; + pthread_mutex_unlock(&g_ds.lock); + + if (cache) { + ASSERT_EQ(counter, 2); + } else { + ASSERT_EQ(counter, 101); + } + TH_LOG("Invalidation by setting new ACL: OK"); + + /* 2. send FUSE_NOTIFY_INVAL_INODE */ + fuse_lowlevel_notify_inval_inode(self->se, FILE_INO, 0, 0); + + sz = lgetxattr(self->pathname, "system.posix_acl_access", + buf, sizeof(buf)); + ASSERT_EQ(sz, sizeof(acl_b)); + ASSERT_EQ(memcmp(buf, acl_b, sz), 0); + + pthread_mutex_lock(&g_ds.lock); + counter = g_ds.getxattr_count; + cache = g_ds.cache; + pthread_mutex_unlock(&g_ds.lock); + + if (cache) { + ASSERT_EQ(counter, 3); + } else { + ASSERT_EQ(counter, 102); + } + TH_LOG("Invalidation through FUSE_NOTIFY_INVAL_INODE: OK"); +} + TEST_HARNESS_MAIN

