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 | 203 +++++++++++++++++- 2 files changed, 202 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/filesystems/fuse/.gitignore b/tools/testing/selftests/filesystems/fuse/.gitignore index fb51603fe419..f7f3dd345a50 100644 --- a/tools/testing/selftests/filesystems/fuse/.gitignore +++ b/tools/testing/selftests/filesystems/fuse/.gitignore @@ -2,3 +2,4 @@ fuse_mnt fusectl_test write_extend_eof_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 fb4e3554e3c9..173063efeee8 100644 --- a/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c +++ b/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c @@ -84,6 +84,9 @@ struct daemon_state { uint8_t *acl; size_t acl_size; int getxattr_count; + uid_t uid; + uid_t gid; + bool cache; }; /* @@ -92,9 +95,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) { @@ -116,6 +127,10 @@ 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; + pthread_mutex_lock(&g_ds.lock); + e.attr.st_uid = g_ds.uid; + e.attr.st_gid = g_ds.gid; + pthread_mutex_unlock(&g_ds.lock); fuse_reply_entry(req, &e); } @@ -133,6 +148,10 @@ static void fs_getattr(fuse_req_t req, fuse_ino_t ino, st.st_ino = FILE_INO; st.st_mode = S_IFREG | 0644; st.st_nlink = 1; + pthread_mutex_lock(&g_ds.lock); + st.st_uid = g_ds.uid; + st.st_gid = g_ds.gid; + pthread_mutex_unlock(&g_ds.lock); } else { fuse_reply_err(req, ENOENT); return; @@ -161,12 +180,14 @@ static void fs_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name, } pthread_mutex_lock(&g_ds.lock); acl_size = g_ds.acl_size; - if (acl && (size >= acl_size)) + if (acl && acl_size && (size >= acl_size)) memcpy(acl, g_ds.acl, acl_size); g_ds.getxattr_count++; pthread_mutex_unlock(&g_ds.lock); - if (size == 0) + if (acl_size == 0) + fuse_reply_err(req, ENODATA); + else if (size == 0) fuse_reply_xattr(req, acl_size); else if (size < acl_size) fuse_reply_err(req, ERANGE); @@ -176,10 +197,42 @@ 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; + goto out; + } else if (strcmp(name, "system.posix_acl_access") != 0) { + ret = ENOTSUP; + goto out; + } + acl = malloc(size); + if (!acl) { + ret = ENOMEM; + goto out; + } + 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); + +out: + 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, }; /* ---- kselftest harness --------------------------------------------------- */ @@ -198,6 +251,9 @@ 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.uid = getuid(); + g_ds.gid = getgid(); + g_ds.cache = false; if (fs_setup(&fs_ops, &g_ds.ctx, err)) SKIP(goto out, err); @@ -325,4 +381,147 @@ TEST_F(acl_cache, stale_after_force_sync) EXPECT_EQ(count, 4); } +FIXTURE(acl_cache_onoff) +{ + char pathname[PATH_MAX]; +}; + +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]; + + pthread_mutex_lock(&g_ds.lock); + g_ds.acl = NULL; + g_ds.acl_size = 0; + g_ds.getxattr_count = 0; + g_ds.cache = variant->cache; + g_ds.uid = getuid(); + g_ds.gid = getgid(); + + if (fs_setup(&fs_ops, &g_ds.ctx, err)) + SKIP(goto out, err); + + snprintf(self->pathname, sizeof(self->pathname), + "%s/" FILE_NAME, g_ds.ctx.mountpoint); +out: + pthread_mutex_unlock(&g_ds.lock); +} + +FIXTURE_TEARDOWN(acl_cache_onoff) +{ + pthread_mutex_lock(&g_ds.lock); + fs_teardown(&g_ds.ctx); + pthread_mutex_unlock(&g_ds.lock); + 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(g_ds.ctx.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

