1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * Copyright (c) 2018 Matthew Bobrowski. All Rights Reserved.
4 *
5 * Started by Matthew Bobrowski <mbobrowski@mbobrowski.org>
6 */
7
8 /*\
9 * [Description]
10 * Validate that the values returned within an event when FAN_REPORT_FID is
11 * specified matches those that are obtained via explicit invocation to system
12 * calls statfs(2) and name_to_handle_at(2).
13 */
14
15 /*
16 * This is also regression test for:
17 * c285a2f01d69 ("fanotify: update connector fsid cache on add mark")
18 */
19
20 #define _GNU_SOURCE
21 #include "config.h"
22
23 #include <stdio.h>
24 #include <string.h>
25 #include <sys/statfs.h>
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <sys/mount.h>
29 #include <errno.h>
30 #include <unistd.h>
31 #include "tst_test.h"
32
33 #ifdef HAVE_SYS_FANOTIFY_H
34 #include "fanotify.h"
35
36 #define PATH_LEN 128
37 #define BUF_SIZE 256
38 #define DIR_ONE "dir_one"
39 #define FILE_ONE "file_one"
40 #define FILE_TWO "file_two"
41 #define MOUNT_PATH "tstmnt"
42 #define EVENT_MAX ARRAY_SIZE(objects)
43 #define DIR_PATH_ONE MOUNT_PATH"/"DIR_ONE
44 #define FILE_PATH_ONE MOUNT_PATH"/"FILE_ONE
45 #define FILE_PATH_TWO MOUNT_PATH"/"FILE_TWO
46
47 #if defined(HAVE_NAME_TO_HANDLE_AT)
48 struct event_t {
49 unsigned long long expected_mask;
50 };
51
52 static struct object_t {
53 const char *path;
54 int is_dir;
55 struct fanotify_fid_t fid;
56 } objects[] = {
57 {FILE_PATH_ONE, 0, {}},
58 {FILE_PATH_TWO, 0, {}},
59 {DIR_PATH_ONE, 1, {}}
60 };
61
62 static struct test_case_t {
63 struct fanotify_mark_type mark;
64 unsigned long long mask;
65 } test_cases[] = {
66 {
67 INIT_FANOTIFY_MARK_TYPE(INODE),
68 FAN_OPEN | FAN_CLOSE_NOWRITE
69 },
70 {
71 INIT_FANOTIFY_MARK_TYPE(INODE),
72 FAN_OPEN | FAN_CLOSE_NOWRITE | FAN_ONDIR
73 },
74 {
75 INIT_FANOTIFY_MARK_TYPE(MOUNT),
76 FAN_OPEN | FAN_CLOSE_NOWRITE
77 },
78 {
79 INIT_FANOTIFY_MARK_TYPE(MOUNT),
80 FAN_OPEN | FAN_CLOSE_NOWRITE | FAN_ONDIR
81 },
82 {
83 INIT_FANOTIFY_MARK_TYPE(FILESYSTEM),
84 FAN_OPEN | FAN_CLOSE_NOWRITE
85 },
86 {
87 INIT_FANOTIFY_MARK_TYPE(FILESYSTEM),
88 FAN_OPEN | FAN_CLOSE_NOWRITE | FAN_ONDIR
89 }
90 };
91
92 static int ovl_mounted;
93 static int bind_mounted;
94 static int nofid_fd;
95 static int fanotify_fd;
96 static int filesystem_mark_unsupported;
97 static char events_buf[BUF_SIZE];
98 static struct event_t event_set[EVENT_MAX];
99
create_objects(void)100 static void create_objects(void)
101 {
102 unsigned int i;
103
104 for (i = 0; i < ARRAY_SIZE(objects); i++) {
105 if (objects[i].is_dir)
106 SAFE_MKDIR(objects[i].path, 0755);
107 else
108 SAFE_FILE_PRINTF(objects[i].path, "0");
109 }
110 }
111
get_object_stats(void)112 static void get_object_stats(void)
113 {
114 unsigned int i;
115
116 for (i = 0; i < ARRAY_SIZE(objects); i++)
117 fanotify_save_fid(objects[i].path, &objects[i].fid);
118 }
119
setup_marks(unsigned int fd,struct test_case_t * tc)120 static int setup_marks(unsigned int fd, struct test_case_t *tc)
121 {
122 unsigned int i;
123 struct fanotify_mark_type *mark = &tc->mark;
124
125 for (i = 0; i < ARRAY_SIZE(objects); i++) {
126 SAFE_FANOTIFY_MARK(fd, FAN_MARK_ADD | mark->flag, tc->mask,
127 AT_FDCWD, objects[i].path);
128
129 /* Setup the expected mask for each generated event */
130 event_set[i].expected_mask = tc->mask;
131 if (!objects[i].is_dir)
132 event_set[i].expected_mask &= ~FAN_ONDIR;
133 }
134 return 0;
135 }
136
do_test(unsigned int number)137 static void do_test(unsigned int number)
138 {
139 unsigned int i;
140 int len, fds[ARRAY_SIZE(objects)];
141
142 struct file_handle *event_file_handle;
143 struct fanotify_event_metadata *metadata;
144 struct fanotify_event_info_fid *event_fid;
145 struct test_case_t *tc = &test_cases[number];
146 struct fanotify_mark_type *mark = &tc->mark;
147
148 tst_res(TINFO,
149 "Test #%d.%d: FAN_REPORT_FID with mark flag: %s",
150 number, tst_variant, mark->name);
151
152 if (tst_variant && !ovl_mounted) {
153 tst_res(TCONF, "overlayfs not supported on %s", tst_device->fs_type);
154 return;
155 }
156
157 if (filesystem_mark_unsupported && mark->flag & FAN_MARK_FILESYSTEM) {
158 tst_res(TCONF, "FAN_MARK_FILESYSTEM not supported in kernel?");
159 return;
160 }
161
162 fanotify_fd = SAFE_FANOTIFY_INIT(FAN_CLASS_NOTIF | FAN_REPORT_FID, O_RDONLY);
163
164 /*
165 * Place marks on a set of objects and setup the expected masks
166 * for each event that is expected to be generated.
167 */
168 if (setup_marks(fanotify_fd, tc) != 0)
169 goto out;
170
171 /* Variant #1: watching upper fs - open files on overlayfs */
172 if (tst_variant == 1) {
173 if (mark->flag & FAN_MARK_MOUNT) {
174 tst_res(TCONF, "overlayfs upper fs cannot be watched with mount mark");
175 goto out;
176 }
177 SAFE_MOUNT(OVL_MNT, MOUNT_PATH, "none", MS_BIND, NULL);
178 }
179
180 /* Generate sequence of FAN_OPEN events on objects */
181 for (i = 0; i < ARRAY_SIZE(objects); i++)
182 fds[i] = SAFE_OPEN(objects[i].path, O_RDONLY);
183
184 /*
185 * Generate sequence of FAN_CLOSE_NOWRITE events on objects. Each
186 * FAN_CLOSE_NOWRITE event is expected to be merged with its
187 * respective FAN_OPEN event that was performed on the same object.
188 */
189 for (i = 0; i < ARRAY_SIZE(objects); i++) {
190 if (fds[i] > 0)
191 SAFE_CLOSE(fds[i]);
192 }
193
194 if (tst_variant == 1)
195 SAFE_UMOUNT(MOUNT_PATH);
196
197 /* Read events from event queue */
198 len = SAFE_READ(0, fanotify_fd, events_buf, BUF_SIZE);
199
200 /* Iterate over event queue */
201 for (i = 0, metadata = (struct fanotify_event_metadata *) events_buf;
202 FAN_EVENT_OK(metadata, len);
203 metadata = FAN_EVENT_NEXT(metadata, len), i++) {
204 struct fanotify_fid_t *expected_fid = &objects[i].fid;
205
206 event_fid = (struct fanotify_event_info_fid *) (metadata + 1);
207 event_file_handle = (struct file_handle *) event_fid->handle;
208
209 /* File descriptor is redundant with FAN_REPORT_FID */
210 if (metadata->fd != FAN_NOFD)
211 tst_res(TFAIL,
212 "Unexpectedly received file descriptor %d in "
213 "event. Expected to get FAN_NOFD(%d)",
214 metadata->fd, FAN_NOFD);
215
216 /* Ensure that the correct mask has been reported in event */
217 if (metadata->mask != event_set[i].expected_mask)
218 tst_res(TFAIL,
219 "Unexpected mask received: %llx (expected: "
220 "%llx) in event",
221 metadata->mask,
222 event_set[i].expected_mask);
223
224 /* Verify handle_bytes returned in event */
225 if (event_file_handle->handle_bytes !=
226 expected_fid->handle.handle_bytes) {
227 tst_res(TFAIL,
228 "handle_bytes (%x) returned in event does not "
229 "equal to handle_bytes (%x) returned in "
230 "name_to_handle_at(2)",
231 event_file_handle->handle_bytes,
232 expected_fid->handle.handle_bytes);
233 continue;
234 }
235
236 /* Verify handle_type returned in event */
237 if (event_file_handle->handle_type !=
238 expected_fid->handle.handle_type) {
239 tst_res(TFAIL,
240 "handle_type (%x) returned in event does not "
241 "equal to handle_type (%x) returned in "
242 "name_to_handle_at(2)",
243 event_file_handle->handle_type,
244 expected_fid->handle.handle_type);
245 continue;
246 }
247
248 /* Verify file identifier f_handle returned in event */
249 if (memcmp(event_file_handle->f_handle,
250 expected_fid->handle.f_handle,
251 expected_fid->handle.handle_bytes) != 0) {
252 tst_res(TFAIL,
253 "file_handle returned in event does not match "
254 "the file_handle returned in "
255 "name_to_handle_at(2)");
256 continue;
257 }
258
259 /* Verify filesystem ID fsid returned in event */
260 if (memcmp(&event_fid->fsid, &expected_fid->fsid,
261 sizeof(expected_fid->fsid)) != 0) {
262 tst_res(TFAIL,
263 "event_fid.fsid != stat.f_fsid that was "
264 "obtained via statfs(2)");
265 continue;
266 }
267
268 tst_res(TPASS,
269 "got event: mask=%llx, pid=%d, fid=%x.%x.%lx values "
270 "returned in event match those returned in statfs(2) "
271 "and name_to_handle_at(2)",
272 metadata->mask,
273 getpid(),
274 FSID_VAL_MEMBER(event_fid->fsid, 0),
275 FSID_VAL_MEMBER(event_fid->fsid, 1),
276 *(unsigned long *) event_file_handle->f_handle);
277 }
278 out:
279 SAFE_CLOSE(fanotify_fd);
280 }
281
do_setup(void)282 static void do_setup(void)
283 {
284 const char *mnt;
285
286 /*
287 * Bind mount to either base fs or to overlayfs over base fs:
288 * Variant #0: watch base fs - open files on base fs
289 * Variant #1: watch upper fs - open files on overlayfs
290 *
291 * Variant #1 tests a bug whose fix bc2473c90fca ("ovl: enable fsnotify
292 * events on underlying real files") in kernel 6.5 is not likely to be
293 * backported to older kernels.
294 * To avoid waiting for events that won't arrive when testing old kernels,
295 * require that kernel supports encoding fid with new flag AT_HADNLE_FID,
296 * also merged to 6.5 and not likely to be backported to older kernels.
297 */
298 if (tst_variant) {
299 REQUIRE_HANDLE_TYPE_SUPPORTED_BY_KERNEL(AT_HANDLE_FID);
300 ovl_mounted = TST_MOUNT_OVERLAY();
301 mnt = OVL_UPPER;
302 } else {
303 mnt = OVL_BASE_MNTPOINT;
304
305 }
306 REQUIRE_FANOTIFY_INIT_FLAGS_SUPPORTED_ON_FS(FAN_REPORT_FID, mnt);
307 SAFE_MKDIR(MOUNT_PATH, 0755);
308 SAFE_MOUNT(mnt, MOUNT_PATH, "none", MS_BIND, NULL);
309 bind_mounted = 1;
310
311 filesystem_mark_unsupported = fanotify_mark_supported_by_kernel(FAN_MARK_FILESYSTEM);
312
313 nofid_fd = SAFE_FANOTIFY_INIT(FAN_CLASS_NOTIF, O_RDONLY);
314
315 /* Create file and directory objects for testing */
316 create_objects();
317
318 /*
319 * Create a mark on first inode without FAN_REPORT_FID, to test
320 * uninitialized connector->fsid cache. This mark remains for all test
321 * cases and is not expected to get any events (no writes in this test).
322 */
323 SAFE_FANOTIFY_MARK(nofid_fd, FAN_MARK_ADD, FAN_CLOSE_WRITE, AT_FDCWD,
324 FILE_PATH_ONE);
325
326 /* Get the filesystem fsid and file handle for each created object */
327 get_object_stats();
328 }
329
do_cleanup(void)330 static void do_cleanup(void)
331 {
332 SAFE_CLOSE(nofid_fd);
333 if (fanotify_fd > 0)
334 SAFE_CLOSE(fanotify_fd);
335 if (bind_mounted) {
336 SAFE_UMOUNT(MOUNT_PATH);
337 SAFE_RMDIR(MOUNT_PATH);
338 }
339 if (ovl_mounted)
340 SAFE_UMOUNT(OVL_MNT);
341 }
342
343 static struct tst_test test = {
344 .test = do_test,
345 .tcnt = ARRAY_SIZE(test_cases),
346 .test_variants = 2,
347 .setup = do_setup,
348 .cleanup = do_cleanup,
349 .needs_root = 1,
350 .mount_device = 1,
351 .mntpoint = OVL_BASE_MNTPOINT,
352 .all_filesystems = 1,
353 .tags = (const struct tst_tag[]) {
354 {"linux-git", "c285a2f01d69"},
355 {"linux-git", "bc2473c90fca"},
356 {}
357 }
358 };
359
360 #else
361 TST_TEST_TCONF("System does not have required name_to_handle_at() support");
362 #endif
363 #else
364 TST_TEST_TCONF("System does not have required fanotify support");
365 #endif
366