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 };
48
49 static struct object_t {
50 const char *path;
51 int is_dir;
52 struct fanotify_fid_t fid;
53 } objects[] = {
54 {FILE_PATH_ONE, 0, {}},
55 {FILE_PATH_TWO, 0, {}},
56 {DIR_PATH_ONE, 1, {}}
57 };
58
59 static struct test_case_t {
60 struct fanotify_mark_type mark;
61 unsigned long long mask;
62 } test_cases[] = {
63 {
64 INIT_FANOTIFY_MARK_TYPE(INODE),
65 FAN_OPEN | FAN_CLOSE_NOWRITE
66 },
67 {
68 INIT_FANOTIFY_MARK_TYPE(INODE),
69 FAN_OPEN | FAN_CLOSE_NOWRITE | FAN_ONDIR
70 },
71 {
72 INIT_FANOTIFY_MARK_TYPE(MOUNT),
73 FAN_OPEN | FAN_CLOSE_NOWRITE
74 },
75 {
76 INIT_FANOTIFY_MARK_TYPE(MOUNT),
77 FAN_OPEN | FAN_CLOSE_NOWRITE | FAN_ONDIR
78 },
79 {
80 INIT_FANOTIFY_MARK_TYPE(FILESYSTEM),
81 FAN_OPEN | FAN_CLOSE_NOWRITE
82 },
83 {
84 INIT_FANOTIFY_MARK_TYPE(FILESYSTEM),
85 FAN_OPEN | FAN_CLOSE_NOWRITE | FAN_ONDIR
86 }
87 };
88
89 static int nofid_fd;
90 static int fanotify_fd;
91 static char events_buf[BUF_SIZE];
92 static struct event_t event_set[EVENT_MAX];
93
create_objects(void)94 static void create_objects(void)
95 {
96 unsigned int i;
97
98 for (i = 0; i < ARRAY_SIZE(objects); i++) {
99 if (objects[i].is_dir)
100 SAFE_MKDIR(objects[i].path, 0755);
101 else
102 SAFE_FILE_PRINTF(objects[i].path, "0");
103 }
104 }
105
get_object_stats(void)106 static void get_object_stats(void)
107 {
108 unsigned int i;
109 for (i = 0; i < ARRAY_SIZE(objects); i++)
110 fanotify_save_fid(objects[i].path, &objects[i].fid);
111 }
112
setup_marks(unsigned int fd,struct test_case_t * tc)113 static int setup_marks(unsigned int fd, struct test_case_t *tc)
114 {
115 unsigned int i;
116 struct fanotify_mark_type *mark = &tc->mark;
117
118 for (i = 0; i < ARRAY_SIZE(objects); i++) {
119 if (fanotify_mark(fd, FAN_MARK_ADD | mark->flag, tc->mask,
120 AT_FDCWD, objects[i].path) == -1) {
121 if (errno == EINVAL &&
122 mark->flag & FAN_MARK_FILESYSTEM) {
123 tst_res(TCONF,
124 "FAN_MARK_FILESYSTEM not supported by "
125 "kernel");
126 return 1;
127 } else if (errno == ENODEV &&
128 !objects[i].fid.fsid.val[0] &&
129 !objects[i].fid.fsid.val[1]) {
130 tst_res(TCONF,
131 "FAN_REPORT_FID not supported on "
132 "filesystem type %s",
133 tst_device->fs_type);
134 return 1;
135 }
136 tst_brk(TBROK | TERRNO,
137 "fanotify_mark(%d, FAN_MARK_ADD, FAN_OPEN, "
138 "AT_FDCWD, %s) failed",
139 fanotify_fd, objects[i].path);
140 }
141
142 /* Setup the expected mask for each generated event */
143 event_set[i].expected_mask = tc->mask;
144 if (!objects[i].is_dir)
145 event_set[i].expected_mask &= ~FAN_ONDIR;
146 }
147 return 0;
148 }
149
do_test(unsigned int number)150 static void do_test(unsigned int number)
151 {
152 unsigned int i;
153 int len, fds[ARRAY_SIZE(objects)];
154
155 struct file_handle *event_file_handle;
156 struct fanotify_event_metadata *metadata;
157 struct fanotify_event_info_fid *event_fid;
158 struct test_case_t *tc = &test_cases[number];
159 struct fanotify_mark_type *mark = &tc->mark;
160
161 tst_res(TINFO,
162 "Test #%d: FAN_REPORT_FID with mark flag: %s",
163 number, mark->name);
164
165 fanotify_fd = fanotify_init(FAN_CLASS_NOTIF | FAN_REPORT_FID, O_RDONLY);
166 if (fanotify_fd == -1) {
167 if (errno == EINVAL) {
168 tst_res(TCONF,
169 "FAN_REPORT_FID not supported by kernel");
170 return;
171 }
172 tst_brk(TBROK | TERRNO,
173 "fanotify_init(FAN_CLASS_NOTIF | FAN_REPORT_FID, "
174 "O_RDONLY) failed");
175 }
176
177 /*
178 * Place marks on a set of objects and setup the expected masks
179 * for each event that is expected to be generated.
180 */
181 if (setup_marks(fanotify_fd, tc) != 0)
182 goto out;
183
184 /* Generate sequence of FAN_OPEN events on objects */
185 for (i = 0; i < ARRAY_SIZE(objects); i++)
186 fds[i] = SAFE_OPEN(objects[i].path, O_RDONLY);
187
188 /*
189 * Generate sequence of FAN_CLOSE_NOWRITE events on objects. Each
190 * FAN_CLOSE_NOWRITE event is expected to be merged with its
191 * respective FAN_OPEN event that was performed on the same object.
192 */
193 for (i = 0; i < ARRAY_SIZE(objects); i++) {
194 if (fds[i] > 0)
195 SAFE_CLOSE(fds[i]);
196 }
197
198 /* Read events from event queue */
199 len = SAFE_READ(0, fanotify_fd, events_buf, BUF_SIZE);
200
201 /* Iterate over event queue */
202 for (i = 0, metadata = (struct fanotify_event_metadata *) events_buf;
203 FAN_EVENT_OK(metadata, len);
204 metadata = FAN_EVENT_NEXT(metadata, len), i++) {
205 struct fanotify_fid_t *expected_fid = &objects[i].fid;
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 /* Check for kernel fanotify support */
285 nofid_fd = SAFE_FANOTIFY_INIT(FAN_CLASS_NOTIF, O_RDONLY);
286
287 /* Create file and directory objects for testing */
288 create_objects();
289
290 /*
291 * Create a mark on first inode without FAN_REPORT_FID, to test
292 * uninitialized connector->fsid cache. This mark remains for all test
293 * cases and is not expected to get any events (no writes in this test).
294 */
295 if (fanotify_mark(nofid_fd, FAN_MARK_ADD, FAN_CLOSE_WRITE, AT_FDCWD,
296 FILE_PATH_ONE) == -1) {
297 tst_brk(TBROK | TERRNO,
298 "fanotify_mark(%d, FAN_MARK_ADD, FAN_CLOSE_WRITE, "
299 "AT_FDCWD, "FILE_PATH_ONE") failed",
300 nofid_fd);
301 }
302
303 /* Get the filesystem fsid and file handle for each created object */
304 get_object_stats();
305 }
306
do_cleanup(void)307 static void do_cleanup(void)
308 {
309 SAFE_CLOSE(nofid_fd);
310 if (fanotify_fd > 0)
311 SAFE_CLOSE(fanotify_fd);
312 }
313
314 static struct tst_test test = {
315 .test = do_test,
316 .tcnt = ARRAY_SIZE(test_cases),
317 .setup = do_setup,
318 .cleanup = do_cleanup,
319 .needs_root = 1,
320 .needs_tmpdir = 1,
321 .mount_device = 1,
322 .mntpoint = MOUNT_PATH,
323 .all_filesystems = 1,
324 .tags = (const struct tst_tag[]) {
325 {"linux-git", "c285a2f01d69"},
326 {}
327 }
328 };
329
330 #else
331 TST_TEST_TCONF("System does not have required name_to_handle_at() support");
332 #endif
333 #else
334 TST_TEST_TCONF("System does not have required fanotify support");
335 #endif
336