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