1*9acb1025SAmir Goldstein // SPDX-License-Identifier: GPL-2.0
2*9acb1025SAmir Goldstein /*
3*9acb1025SAmir Goldstein * Test: FUSE ACL caching bug triggered by AT_STATX_FORCE_SYNC
4*9acb1025SAmir Goldstein *
5*9acb1025SAmir Goldstein * A FUSE mount that does not negotiate FUSE_POSIX_ACL initialises every inode
6*9acb1025SAmir Goldstein * with i_acl = i_default_acl = ACL_DONT_CACHE. When a fresh stat is needed
7*9acb1025SAmir Goldstein * (e.g. AT_STATX_FORCE_SYNC), fuse_update_get_attr() calls
8*9acb1025SAmir Goldstein * forget_all_cached_acls() before issuing FUSE_GETATTR. On an unfixed kernel,
9*9acb1025SAmir Goldstein * __forget_cached_acl() replaces ACL_DONT_CACHE with ACL_NOT_CACHED,
10*9acb1025SAmir Goldstein * inadvertently enabling the kernel ACL cache for that inode. The next
11*9acb1025SAmir Goldstein * getxattr populates the cache. Because fuse_set_acl() skips
12*9acb1025SAmir Goldstein * forget_all_cached_acls() for !fc->posix_acl mounts, any subsequent change to
13*9acb1025SAmir Goldstein * the ACL leaves the stale kernel entry in place, and the next getxattr returns
14*9acb1025SAmir Goldstein * wrong data without ever reaching the FUSE daemon.
15*9acb1025SAmir Goldstein *
16*9acb1025SAmir Goldstein * Fix (fs/posix_acl.c): __forget_cached_acl() returns early when *p is
17*9acb1025SAmir Goldstein * ACL_DONT_CACHE, preserving the "never cache" invariant for the inode's
18*9acb1025SAmir Goldstein * lifetime.
19*9acb1025SAmir Goldstein *
20*9acb1025SAmir Goldstein * Test outline:
21*9acb1025SAmir Goldstein * 1. Mount a minimal FUSE fs (no FUSE_POSIX_ACL negotiated).
22*9acb1025SAmir Goldstein * 2. lgetxattr -> daemon called, ACL_A returned, NOT cached (ACL_DONT_CACHE).
23*9acb1025SAmir Goldstein * 3. statx(AT_STATX_FORCE_SYNC) -> forget_all_cached_acls() called.
24*9acb1025SAmir Goldstein * Buggy: ACL_DONT_CACHE -> ACL_NOT_CACHED (cache enabled).
25*9acb1025SAmir Goldstein * Fixed: ACL_DONT_CACHE preserved.
26*9acb1025SAmir Goldstein * 4. lgetxattr -> daemon called, ACL_A returned.
27*9acb1025SAmir Goldstein * Buggy: result now cached (ACL_NOT_CACHED -> cached).
28*9acb1025SAmir Goldstein * Fixed: result still not cached.
29*9acb1025SAmir Goldstein * 5. Daemon switches to ACL_B internally (different size).
30*9acb1025SAmir Goldstein * 6. lgetxattr -> should return ACL_B (44 bytes).
31*9acb1025SAmir Goldstein * Buggy: cache hit, returns stale ACL_A (28 bytes). FAIL.
32*9acb1025SAmir Goldstein * Fixed: no cache, daemon called, returns ACL_B (44 bytes). PASS.
33*9acb1025SAmir Goldstein */
34*9acb1025SAmir Goldstein
35*9acb1025SAmir Goldstein #define _GNU_SOURCE
36*9acb1025SAmir Goldstein #include <errno.h>
37*9acb1025SAmir Goldstein #include <fcntl.h>
38*9acb1025SAmir Goldstein #include <linux/limits.h>
39*9acb1025SAmir Goldstein #include <pthread.h>
40*9acb1025SAmir Goldstein #include <stdint.h>
41*9acb1025SAmir Goldstein #include <stdio.h>
42*9acb1025SAmir Goldstein #include <stdlib.h>
43*9acb1025SAmir Goldstein #include <string.h>
44*9acb1025SAmir Goldstein #include <sys/stat.h>
45*9acb1025SAmir Goldstein #include <sys/xattr.h>
46*9acb1025SAmir Goldstein #include <unistd.h>
47*9acb1025SAmir Goldstein
48*9acb1025SAmir Goldstein #define FUSE_USE_VERSION 31
49*9acb1025SAmir Goldstein #include <fuse_lowlevel.h>
50*9acb1025SAmir Goldstein
51*9acb1025SAmir Goldstein #include "kselftest_harness.h"
52*9acb1025SAmir Goldstein
53*9acb1025SAmir Goldstein /* ---- ACL binary encoding ------------------------------------------------ */
54*9acb1025SAmir Goldstein /*
55*9acb1025SAmir Goldstein * POSIX ACL v2 xattr format (little-endian):
56*9acb1025SAmir Goldstein * header: u32 version (= 0x00000002)
57*9acb1025SAmir Goldstein * entry: u16 tag | u16 perm | u32 id
58*9acb1025SAmir Goldstein *
59*9acb1025SAmir Goldstein * Entries must appear in tag-ascending order; named USER/GROUP entries
60*9acb1025SAmir Goldstein * require a MASK entry. Both ACLs pass posix_acl_from_xattr() validation.
61*9acb1025SAmir Goldstein */
62*9acb1025SAmir Goldstein
63*9acb1025SAmir Goldstein /* ACL_A: 3 entries (USER_OBJ:rwx, GROUP_OBJ:r-x, OTHER:r-x) = 28 bytes */
64*9acb1025SAmir Goldstein static const uint8_t acl_a[] = {
65*9acb1025SAmir Goldstein 0x02, 0x00, 0x00, 0x00, /* v2 header */
66*9acb1025SAmir Goldstein 0x01, 0x00, 0x07, 0x00, 0xff, 0xff, 0xff, 0xff, /* USER_OBJ rwx */
67*9acb1025SAmir Goldstein 0x04, 0x00, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff, /* GROUP_OBJ r-x */
68*9acb1025SAmir Goldstein 0x20, 0x00, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff, /* OTHER r-x */
69*9acb1025SAmir Goldstein };
70*9acb1025SAmir Goldstein
71*9acb1025SAmir Goldstein /*
72*9acb1025SAmir Goldstein * ACL_B: 5 entries — adds USER uid=1 and MASK = 44 bytes.
73*9acb1025SAmir Goldstein * A named USER entry requires a MASK; all tags in ascending order.
74*9acb1025SAmir Goldstein */
75*9acb1025SAmir Goldstein static const uint8_t acl_b[] = {
76*9acb1025SAmir Goldstein 0x02, 0x00, 0x00, 0x00, /* v2 header */
77*9acb1025SAmir Goldstein 0x01, 0x00, 0x07, 0x00, 0xff, 0xff, 0xff, 0xff, /* USER_OBJ rwx */
78*9acb1025SAmir Goldstein 0x02, 0x00, 0x07, 0x00, 0x01, 0x00, 0x00, 0x00, /* USER uid=1 rwx */
79*9acb1025SAmir Goldstein 0x04, 0x00, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff, /* GROUP_OBJ r-x */
80*9acb1025SAmir Goldstein 0x10, 0x00, 0x07, 0x00, 0xff, 0xff, 0xff, 0xff, /* MASK rwx */
81*9acb1025SAmir Goldstein 0x20, 0x00, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff, /* OTHER r-x */
82*9acb1025SAmir Goldstein };
83*9acb1025SAmir Goldstein
84*9acb1025SAmir Goldstein /* ---- Shared state (daemon thread <-> test thread) ----------------------- */
85*9acb1025SAmir Goldstein
86*9acb1025SAmir Goldstein #define FILE_INO 2
87*9acb1025SAmir Goldstein #define FILE_NAME "testfile"
88*9acb1025SAmir Goldstein
89*9acb1025SAmir Goldstein struct daemon_state {
90*9acb1025SAmir Goldstein pthread_mutex_t lock;
91*9acb1025SAmir Goldstein const uint8_t *acl;
92*9acb1025SAmir Goldstein size_t acl_size;
93*9acb1025SAmir Goldstein int getxattr_count;
94*9acb1025SAmir Goldstein };
95*9acb1025SAmir Goldstein
96*9acb1025SAmir Goldstein /*
97*9acb1025SAmir Goldstein * Global: callbacks are stateless fns so we use a single global.
98*9acb1025SAmir Goldstein * Safe because only one test instance runs at a time.
99*9acb1025SAmir Goldstein */
100*9acb1025SAmir Goldstein static struct daemon_state g_ds = {
101*9acb1025SAmir Goldstein .lock = PTHREAD_MUTEX_INITIALIZER,
102*9acb1025SAmir Goldstein };
103*9acb1025SAmir Goldstein
104*9acb1025SAmir Goldstein /* ---- FUSE lowlevel callbacks -------------------------------------------- */
105*9acb1025SAmir Goldstein
fs_lookup(fuse_req_t req,fuse_ino_t parent,const char * name)106*9acb1025SAmir Goldstein static void fs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
107*9acb1025SAmir Goldstein {
108*9acb1025SAmir Goldstein if (parent != FUSE_ROOT_ID || strcmp(name, FILE_NAME)) {
109*9acb1025SAmir Goldstein fuse_reply_err(req, ENOENT);
110*9acb1025SAmir Goldstein return;
111*9acb1025SAmir Goldstein }
112*9acb1025SAmir Goldstein struct fuse_entry_param e = {};
113*9acb1025SAmir Goldstein
114*9acb1025SAmir Goldstein /*
115*9acb1025SAmir Goldstein * Long attr/entry timeouts so that normal stat() calls do not
116*9acb1025SAmir Goldstein * expire and trigger forget_all_cached_acls() on their own;
117*9acb1025SAmir Goldstein * only the explicit AT_STATX_FORCE_SYNC should trigger it.
118*9acb1025SAmir Goldstein */
119*9acb1025SAmir Goldstein e.ino = FILE_INO;
120*9acb1025SAmir Goldstein e.generation = 1;
121*9acb1025SAmir Goldstein e.attr_timeout = 10.0;
122*9acb1025SAmir Goldstein e.entry_timeout = 10.0;
123*9acb1025SAmir Goldstein e.attr.st_ino = FILE_INO;
124*9acb1025SAmir Goldstein e.attr.st_mode = S_IFREG | 0644;
125*9acb1025SAmir Goldstein e.attr.st_nlink = 1;
126*9acb1025SAmir Goldstein fuse_reply_entry(req, &e);
127*9acb1025SAmir Goldstein }
128*9acb1025SAmir Goldstein
fs_getattr(fuse_req_t req,fuse_ino_t ino,struct fuse_file_info * fi)129*9acb1025SAmir Goldstein static void fs_getattr(fuse_req_t req, fuse_ino_t ino,
130*9acb1025SAmir Goldstein struct fuse_file_info *fi)
131*9acb1025SAmir Goldstein {
132*9acb1025SAmir Goldstein struct stat st = {};
133*9acb1025SAmir Goldstein
134*9acb1025SAmir Goldstein (void)fi;
135*9acb1025SAmir Goldstein if (ino == FUSE_ROOT_ID) {
136*9acb1025SAmir Goldstein st.st_ino = FUSE_ROOT_ID;
137*9acb1025SAmir Goldstein st.st_mode = S_IFDIR | 0755;
138*9acb1025SAmir Goldstein st.st_nlink = 2;
139*9acb1025SAmir Goldstein } else if (ino == FILE_INO) {
140*9acb1025SAmir Goldstein st.st_ino = FILE_INO;
141*9acb1025SAmir Goldstein st.st_mode = S_IFREG | 0644;
142*9acb1025SAmir Goldstein st.st_nlink = 1;
143*9acb1025SAmir Goldstein } else {
144*9acb1025SAmir Goldstein fuse_reply_err(req, ENOENT);
145*9acb1025SAmir Goldstein return;
146*9acb1025SAmir Goldstein }
147*9acb1025SAmir Goldstein fuse_reply_attr(req, &st, 10);
148*9acb1025SAmir Goldstein }
149*9acb1025SAmir Goldstein
fs_getxattr(fuse_req_t req,fuse_ino_t ino,const char * name,size_t size)150*9acb1025SAmir Goldstein static void fs_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
151*9acb1025SAmir Goldstein size_t size)
152*9acb1025SAmir Goldstein {
153*9acb1025SAmir Goldstein if (ino != FILE_INO ||
154*9acb1025SAmir Goldstein strcmp(name, "system.posix_acl_access") != 0) {
155*9acb1025SAmir Goldstein fuse_reply_err(req, ENODATA);
156*9acb1025SAmir Goldstein return;
157*9acb1025SAmir Goldstein }
158*9acb1025SAmir Goldstein
159*9acb1025SAmir Goldstein pthread_mutex_lock(&g_ds.lock);
160*9acb1025SAmir Goldstein const uint8_t *acl = g_ds.acl;
161*9acb1025SAmir Goldstein size_t acl_size = g_ds.acl_size;
162*9acb1025SAmir Goldstein g_ds.getxattr_count++;
163*9acb1025SAmir Goldstein pthread_mutex_unlock(&g_ds.lock);
164*9acb1025SAmir Goldstein
165*9acb1025SAmir Goldstein if (size == 0)
166*9acb1025SAmir Goldstein fuse_reply_xattr(req, acl_size);
167*9acb1025SAmir Goldstein else if (size < acl_size)
168*9acb1025SAmir Goldstein fuse_reply_err(req, ERANGE);
169*9acb1025SAmir Goldstein else
170*9acb1025SAmir Goldstein fuse_reply_buf(req, (const char *)acl, acl_size);
171*9acb1025SAmir Goldstein }
172*9acb1025SAmir Goldstein
173*9acb1025SAmir Goldstein static const struct fuse_lowlevel_ops fs_ops = {
174*9acb1025SAmir Goldstein .lookup = fs_lookup,
175*9acb1025SAmir Goldstein .getattr = fs_getattr,
176*9acb1025SAmir Goldstein .getxattr = fs_getxattr,
177*9acb1025SAmir Goldstein };
178*9acb1025SAmir Goldstein
179*9acb1025SAmir Goldstein /* ---- Daemon thread ------------------------------------------------------- */
180*9acb1025SAmir Goldstein
run_daemon(void * arg)181*9acb1025SAmir Goldstein static void *run_daemon(void *arg)
182*9acb1025SAmir Goldstein {
183*9acb1025SAmir Goldstein fuse_session_loop((struct fuse_session *)arg);
184*9acb1025SAmir Goldstein return NULL;
185*9acb1025SAmir Goldstein }
186*9acb1025SAmir Goldstein
187*9acb1025SAmir Goldstein /* ---- kselftest harness --------------------------------------------------- */
188*9acb1025SAmir Goldstein
FIXTURE(acl_cache)189*9acb1025SAmir Goldstein FIXTURE(acl_cache) {
190*9acb1025SAmir Goldstein struct fuse_session *se;
191*9acb1025SAmir Goldstein char mountpoint[PATH_MAX];
192*9acb1025SAmir Goldstein char file_path[PATH_MAX];
193*9acb1025SAmir Goldstein pthread_t thread;
194*9acb1025SAmir Goldstein };
195*9acb1025SAmir Goldstein
FIXTURE_SETUP(acl_cache)196*9acb1025SAmir Goldstein FIXTURE_SETUP(acl_cache)
197*9acb1025SAmir Goldstein {
198*9acb1025SAmir Goldstein char *fuse_argv[] = { "fuse_acl_cache_test", NULL };
199*9acb1025SAmir Goldstein struct fuse_args args = FUSE_ARGS_INIT(1, fuse_argv);
200*9acb1025SAmir Goldstein
201*9acb1025SAmir Goldstein g_ds.acl = acl_a;
202*9acb1025SAmir Goldstein g_ds.acl_size = sizeof(acl_a);
203*9acb1025SAmir Goldstein g_ds.getxattr_count = 0;
204*9acb1025SAmir Goldstein
205*9acb1025SAmir Goldstein strcpy(self->mountpoint, "/tmp/acl_cache_test_XXXXXX");
206*9acb1025SAmir Goldstein if (!mkdtemp(self->mountpoint))
207*9acb1025SAmir Goldstein SKIP(return, "mkdtemp: %s", strerror(errno));
208*9acb1025SAmir Goldstein
209*9acb1025SAmir Goldstein snprintf(self->file_path, sizeof(self->file_path),
210*9acb1025SAmir Goldstein "%s/" FILE_NAME, self->mountpoint);
211*9acb1025SAmir Goldstein
212*9acb1025SAmir Goldstein self->se = fuse_session_new(&args, &fs_ops, sizeof(fs_ops), NULL);
213*9acb1025SAmir Goldstein if (!self->se) {
214*9acb1025SAmir Goldstein rmdir(self->mountpoint);
215*9acb1025SAmir Goldstein SKIP(return, "fuse_session_new failed");
216*9acb1025SAmir Goldstein }
217*9acb1025SAmir Goldstein
218*9acb1025SAmir Goldstein if (fuse_session_mount(self->se, self->mountpoint)) {
219*9acb1025SAmir Goldstein fuse_session_destroy(self->se);
220*9acb1025SAmir Goldstein rmdir(self->mountpoint);
221*9acb1025SAmir Goldstein SKIP(return, "fuse_session_mount failed "
222*9acb1025SAmir Goldstein "(missing fusermount3 or insufficient privileges)");
223*9acb1025SAmir Goldstein }
224*9acb1025SAmir Goldstein
225*9acb1025SAmir Goldstein if (pthread_create(&self->thread, NULL, run_daemon, self->se)) {
226*9acb1025SAmir Goldstein fuse_session_unmount(self->se);
227*9acb1025SAmir Goldstein fuse_session_destroy(self->se);
228*9acb1025SAmir Goldstein rmdir(self->mountpoint);
229*9acb1025SAmir Goldstein SKIP(return, "pthread_create: %s", strerror(errno));
230*9acb1025SAmir Goldstein }
231*9acb1025SAmir Goldstein
232*9acb1025SAmir Goldstein fuse_opt_free_args(&args);
233*9acb1025SAmir Goldstein }
234*9acb1025SAmir Goldstein
FIXTURE_TEARDOWN(acl_cache)235*9acb1025SAmir Goldstein FIXTURE_TEARDOWN(acl_cache)
236*9acb1025SAmir Goldstein {
237*9acb1025SAmir Goldstein fuse_session_exit(self->se);
238*9acb1025SAmir Goldstein fuse_session_unmount(self->se);
239*9acb1025SAmir Goldstein pthread_join(self->thread, NULL);
240*9acb1025SAmir Goldstein fuse_session_destroy(self->se);
241*9acb1025SAmir Goldstein rmdir(self->mountpoint);
242*9acb1025SAmir Goldstein }
243*9acb1025SAmir Goldstein
do_force_statx(const char * path)244*9acb1025SAmir Goldstein static int do_force_statx(const char *path)
245*9acb1025SAmir Goldstein {
246*9acb1025SAmir Goldstein struct statx stx;
247*9acb1025SAmir Goldstein
248*9acb1025SAmir Goldstein return statx(AT_FDCWD, path, AT_STATX_FORCE_SYNC, STATX_BASIC_STATS,
249*9acb1025SAmir Goldstein &stx);
250*9acb1025SAmir Goldstein }
251*9acb1025SAmir Goldstein
TEST_F(acl_cache,stale_after_force_sync)252*9acb1025SAmir Goldstein TEST_F(acl_cache, stale_after_force_sync)
253*9acb1025SAmir Goldstein {
254*9acb1025SAmir Goldstein char buf[512];
255*9acb1025SAmir Goldstein ssize_t sz;
256*9acb1025SAmir Goldstein int count;
257*9acb1025SAmir Goldstein
258*9acb1025SAmir Goldstein /*
259*9acb1025SAmir Goldstein * Step 1: two getxattr calls before any statx(FORCE_SYNC).
260*9acb1025SAmir Goldstein * i_acl == ACL_DONT_CACHE. __get_acl's cmpxchg(p, ACL_NOT_CACHED,
261*9acb1025SAmir Goldstein * sentinel) finds *p != ACL_NOT_CACHED on every call, so the sentinel
262*9acb1025SAmir Goldstein * is never placed and the result is never cached. Both calls must
263*9acb1025SAmir Goldstein * reach the daemon, proving ACL_DONT_CACHE suppresses caching.
264*9acb1025SAmir Goldstein */
265*9acb1025SAmir Goldstein sz = lgetxattr(self->file_path, "system.posix_acl_access",
266*9acb1025SAmir Goldstein buf, sizeof(buf));
267*9acb1025SAmir Goldstein ASSERT_EQ(sz, (ssize_t)sizeof(acl_a));
268*9acb1025SAmir Goldstein
269*9acb1025SAmir Goldstein sz = lgetxattr(self->file_path, "system.posix_acl_access",
270*9acb1025SAmir Goldstein buf, sizeof(buf));
271*9acb1025SAmir Goldstein ASSERT_EQ(sz, (ssize_t)sizeof(acl_a));
272*9acb1025SAmir Goldstein
273*9acb1025SAmir Goldstein pthread_mutex_lock(&g_ds.lock);
274*9acb1025SAmir Goldstein count = g_ds.getxattr_count;
275*9acb1025SAmir Goldstein pthread_mutex_unlock(&g_ds.lock);
276*9acb1025SAmir Goldstein
277*9acb1025SAmir Goldstein ASSERT_EQ(count, 2);
278*9acb1025SAmir Goldstein TH_LOG("step 1 OK: both pre-trigger getxattrs reached daemon (count=%d), "
279*9acb1025SAmir Goldstein "ACL_DONT_CACHE is working", count);
280*9acb1025SAmir Goldstein
281*9acb1025SAmir Goldstein /*
282*9acb1025SAmir Goldstein * Step 2: statx(AT_STATX_FORCE_SYNC).
283*9acb1025SAmir Goldstein * fuse_update_get_attr() calls forget_all_cached_acls() before sending
284*9acb1025SAmir Goldstein * FUSE_GETATTR.
285*9acb1025SAmir Goldstein * Buggy kernel: ACL_DONT_CACHE -> ACL_NOT_CACHED (cache enabled)
286*9acb1025SAmir Goldstein * Fixed kernel: ACL_DONT_CACHE preserved (no effect)
287*9acb1025SAmir Goldstein */
288*9acb1025SAmir Goldstein ASSERT_EQ(do_force_statx(self->file_path), 0);
289*9acb1025SAmir Goldstein TH_LOG("step 2 OK: statx(AT_STATX_FORCE_SYNC) succeeded");
290*9acb1025SAmir Goldstein
291*9acb1025SAmir Goldstein /*
292*9acb1025SAmir Goldstein * Step 3: getxattr — cache population attempt after the trigger.
293*9acb1025SAmir Goldstein * Buggy: *p == ACL_NOT_CACHED -> sentinel placed -> fuse_get_inode_acl
294*9acb1025SAmir Goldstein * called -> ACL_A parsed and stored in the kernel cache.
295*9acb1025SAmir Goldstein * Fixed: *p == ACL_DONT_CACHE -> sentinel placement skipped ->
296*9acb1025SAmir Goldstein * fuse_get_inode_acl called but result not cached.
297*9acb1025SAmir Goldstein * Either way the correct ACL_A is returned here.
298*9acb1025SAmir Goldstein */
299*9acb1025SAmir Goldstein sz = lgetxattr(self->file_path, "system.posix_acl_access",
300*9acb1025SAmir Goldstein buf, sizeof(buf));
301*9acb1025SAmir Goldstein ASSERT_EQ(sz, (ssize_t)sizeof(acl_a));
302*9acb1025SAmir Goldstein
303*9acb1025SAmir Goldstein pthread_mutex_lock(&g_ds.lock);
304*9acb1025SAmir Goldstein count = g_ds.getxattr_count;
305*9acb1025SAmir Goldstein pthread_mutex_unlock(&g_ds.lock);
306*9acb1025SAmir Goldstein
307*9acb1025SAmir Goldstein ASSERT_EQ(count, 3);
308*9acb1025SAmir Goldstein TH_LOG("step 3 OK: post-trigger getxattr reached daemon (count=%d), "
309*9acb1025SAmir Goldstein "returned correct ACL_A (%zd bytes)", count, sz);
310*9acb1025SAmir Goldstein
311*9acb1025SAmir Goldstein /*
312*9acb1025SAmir Goldstein * Step 4: switch daemon to ACL_B (different size: 44 vs 28 bytes).
313*9acb1025SAmir Goldstein * Simulates an ACL change that fuse_set_acl() would NOT invalidate for
314*9acb1025SAmir Goldstein * !fc->posix_acl mounts (it skips forget_all_cached_acls in that case).
315*9acb1025SAmir Goldstein * On a fixed kernel the ACL was never cached, so this is moot.
316*9acb1025SAmir Goldstein */
317*9acb1025SAmir Goldstein pthread_mutex_lock(&g_ds.lock);
318*9acb1025SAmir Goldstein g_ds.acl = acl_b;
319*9acb1025SAmir Goldstein g_ds.acl_size = sizeof(acl_b);
320*9acb1025SAmir Goldstein pthread_mutex_unlock(&g_ds.lock);
321*9acb1025SAmir Goldstein TH_LOG("step 4: daemon switched to ACL_B (%zu bytes)", sizeof(acl_b));
322*9acb1025SAmir Goldstein
323*9acb1025SAmir Goldstein /*
324*9acb1025SAmir Goldstein * Step 5: getxattr — the decisive check.
325*9acb1025SAmir Goldstein * Buggy kernel: cache hit -> stale ACL_A (28 bytes), count stays 3.
326*9acb1025SAmir Goldstein * Fixed kernel: no cache -> daemon called -> ACL_B (44 bytes), count 4.
327*9acb1025SAmir Goldstein */
328*9acb1025SAmir Goldstein sz = lgetxattr(self->file_path, "system.posix_acl_access",
329*9acb1025SAmir Goldstein buf, sizeof(buf));
330*9acb1025SAmir Goldstein
331*9acb1025SAmir Goldstein pthread_mutex_lock(&g_ds.lock);
332*9acb1025SAmir Goldstein count = g_ds.getxattr_count;
333*9acb1025SAmir Goldstein pthread_mutex_unlock(&g_ds.lock);
334*9acb1025SAmir Goldstein
335*9acb1025SAmir Goldstein if (sz == (ssize_t)sizeof(acl_a))
336*9acb1025SAmir Goldstein TH_LOG("step 5 BUG: stale ACL_A (%zd bytes) from kernel cache "
337*9acb1025SAmir Goldstein "(count=%d); ACL_DONT_CACHE corrupted by "
338*9acb1025SAmir Goldstein "forget_all_cached_acls()", sz, count);
339*9acb1025SAmir Goldstein else
340*9acb1025SAmir Goldstein TH_LOG("step 5 OK: daemon reached (count=%d), "
341*9acb1025SAmir Goldstein "fresh ACL_B (%zd bytes)", count, sz);
342*9acb1025SAmir Goldstein
343*9acb1025SAmir Goldstein EXPECT_EQ(sz, (ssize_t)sizeof(acl_b));
344*9acb1025SAmir Goldstein EXPECT_EQ(count, 4);
345*9acb1025SAmir Goldstein }
346*9acb1025SAmir Goldstein
347*9acb1025SAmir Goldstein TEST_HARNESS_MAIN
348