On Wed, Sep 16, 2026 at 5:55 PM Luis Henriques <[email protected]> wrote:
>
> 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.

Typo "is 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)

Typo NOTIFY_INVAL_INODE


With that fix, feel free to add

Reviewed-by: Amir Goldstein <[email protected]>

Thanks,
Amir.

> + */
> +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

Reply via email to