1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (c) 2018 CTERA Networks. All Rights Reserved.
4 *
5 * Started by Amir Goldstein <amir73il@gmail.com>
6 */
7
8 /*\
9 * [Description]
10 * Check that fanotify handles events on children correctly when both parent and
11 * subdir or mountpoint marks exist.
12 */
13
14 /*
15 * This is a regression test for commit 54a307ba8d3c:
16 *
17 * fanotify: fix logic of events on child
18 *
19 * Test case #1 is a regression test for commit b469e7e47c8a:
20 *
21 * fanotify: fix handling of events on child sub-directory
22 *
23 * Test case #2 is a regression test for commit 55bf882c7f13:
24 *
25 * fanotify: fix merging marks masks with FAN_ONDIR
26 *
27 * Test case #5 is a regression test for commit 7372e79c9eb9:
28 *
29 * fanotify: fix logic of reporting name info with watched parent
30 */
31
32 #define _GNU_SOURCE
33 #include "config.h"
34
35 #include <stdio.h>
36 #include <sys/stat.h>
37 #include <sys/types.h>
38 #include <errno.h>
39 #include <string.h>
40 #include <sys/mount.h>
41 #include <sys/syscall.h>
42 #include <stdint.h>
43 #include "tst_test.h"
44
45 #ifdef HAVE_SYS_FANOTIFY_H
46 #include "fanotify.h"
47
48 #define EVENT_MAX 1024
49 /* size of the event structure, not counting name */
50 #define EVENT_SIZE (sizeof (struct fanotify_event_metadata))
51 /* reasonable guess as to size of 1024 events */
52 #define EVENT_BUF_LEN (EVENT_MAX * EVENT_SIZE)
53
54 #define NUM_GROUPS 3
55
56 #define BUF_SIZE 256
57 static char fname[BUF_SIZE];
58 static char symlnk[BUF_SIZE];
59 static char fdpath[BUF_SIZE];
60 static int fd_notify[NUM_GROUPS];
61
62 static char event_buf[EVENT_BUF_LEN];
63
64 #define MOUNT_PATH "fs_mnt"
65 #define MOUNT_NAME "mntpoint"
66 #define DIR_NAME "testdir"
67 #define FILE2_NAME "testfile"
68 static int mount_created;
69
70 static int fan_report_dfid_unsupported;
71
72 static struct tcase {
73 const char *tname;
74 struct fanotify_mark_type mark;
75 unsigned int ondir;
76 unsigned int report_name;
77 const char *close_nowrite;
78 int nevents;
79 } tcases[] = {
80 {
81 "Events on non-dir child with both parent and mount marks",
82 INIT_FANOTIFY_MARK_TYPE(MOUNT),
83 0,
84 0,
85 DIR_NAME,
86 1,
87 },
88 {
89 "Events on non-dir child and subdir with both parent and mount marks",
90 INIT_FANOTIFY_MARK_TYPE(MOUNT),
91 FAN_ONDIR,
92 0,
93 DIR_NAME,
94 2,
95 },
96 {
97 "Events on non-dir child and parent with both parent and mount marks",
98 INIT_FANOTIFY_MARK_TYPE(MOUNT),
99 FAN_ONDIR,
100 0,
101 ".",
102 2,
103 },
104 {
105 "Events on non-dir child and subdir with both parent and subdir marks",
106 INIT_FANOTIFY_MARK_TYPE(INODE),
107 FAN_ONDIR,
108 0,
109 DIR_NAME,
110 2,
111 },
112 {
113 "Events on non-dir children with both parent and mount marks",
114 INIT_FANOTIFY_MARK_TYPE(MOUNT),
115 0,
116 0,
117 FILE2_NAME,
118 2,
119 },
120 {
121 "Events on non-dir child with both parent and mount marks and filename info",
122 INIT_FANOTIFY_MARK_TYPE(MOUNT),
123 0,
124 FAN_REPORT_DFID_NAME,
125 FILE2_NAME,
126 2,
127 },
128 };
129
create_fanotify_groups(struct tcase * tc)130 static void create_fanotify_groups(struct tcase *tc)
131 {
132 struct fanotify_mark_type *mark = &tc->mark;
133 unsigned int i, onchild, report_name, ondir = tc->ondir;
134
135 for (i = 0; i < NUM_GROUPS; i++) {
136 /*
137 * The first group may request events with filename info.
138 */
139 report_name = (i == 0) ? tc->report_name : 0;
140 fd_notify[i] = SAFE_FANOTIFY_INIT(FAN_CLASS_NOTIF | report_name |
141 FAN_NONBLOCK, O_RDONLY);
142
143 /*
144 * Add subdir or mount mark for each group with CLOSE event,
145 * but only the first group requests events on dir.
146 */
147 onchild = (i == 0) ? FAN_EVENT_ON_CHILD | ondir : 0;
148 SAFE_FANOTIFY_MARK(fd_notify[i],
149 FAN_MARK_ADD | mark->flag,
150 FAN_CLOSE_NOWRITE | onchild,
151 AT_FDCWD, tc->close_nowrite);
152
153 /*
154 * Add inode mark on parent for each group with MODIFY event,
155 * but only the first group requests events on child.
156 * The one mark with FAN_EVENT_ON_CHILD is needed for
157 * setting the DCACHE_FSNOTIFY_PARENT_WATCHED dentry
158 * flag.
159 */
160 SAFE_FANOTIFY_MARK(fd_notify[i], FAN_MARK_ADD,
161 FAN_MODIFY | ondir | onchild,
162 AT_FDCWD, ".");
163 }
164 }
165
cleanup_fanotify_groups(void)166 static void cleanup_fanotify_groups(void)
167 {
168 unsigned int i;
169
170 for (i = 0; i < NUM_GROUPS; i++) {
171 if (fd_notify[i] > 0)
172 SAFE_CLOSE(fd_notify[i]);
173 }
174 }
175
event_res(int ttype,int group,struct fanotify_event_metadata * event,const char * filename)176 static void event_res(int ttype, int group,
177 struct fanotify_event_metadata *event,
178 const char *filename)
179 {
180 if (event->fd != FAN_NOFD) {
181 int len = 0;
182 sprintf(symlnk, "/proc/self/fd/%d", event->fd);
183 len = readlink(symlnk, fdpath, sizeof(fdpath));
184 if (len < 0)
185 len = 0;
186 fdpath[len] = 0;
187 filename = fdpath;
188 }
189 tst_res(ttype, "group %d got event: mask %llx pid=%u fd=%d filename=%s",
190 group, (unsigned long long)event->mask,
191 (unsigned)event->pid, event->fd, filename);
192 }
193
event_filename(struct fanotify_event_metadata * event)194 static const char *event_filename(struct fanotify_event_metadata *event)
195 {
196 struct fanotify_event_info_fid *event_fid;
197 struct file_handle *file_handle;
198 const char *filename, *end;
199
200 if (event->event_len <= FAN_EVENT_METADATA_LEN)
201 return "";
202
203 event_fid = (struct fanotify_event_info_fid *)(event + 1);
204 file_handle = (struct file_handle *)event_fid->handle;
205 filename = (char *)file_handle->f_handle + file_handle->handle_bytes;
206 end = (char *)event_fid + event_fid->hdr.len;
207
208 /* End of event_fid could have name, zero padding, both or none */
209 return (filename == end) ? "" : filename;
210 }
211
verify_event(int group,struct fanotify_event_metadata * event,uint32_t expect,const char * expect_filename)212 static void verify_event(int group, struct fanotify_event_metadata *event,
213 uint32_t expect, const char *expect_filename)
214 {
215 const char *filename = event_filename(event);
216
217 if (event->mask != expect) {
218 tst_res(TFAIL, "group %d got event: mask %llx (expected %llx) "
219 "pid=%u fd=%d filename=%s", group, (unsigned long long)event->mask,
220 (unsigned long long)expect,
221 (unsigned)event->pid, event->fd, filename);
222 } else if (event->pid != getpid()) {
223 tst_res(TFAIL, "group %d got event: mask %llx pid=%u "
224 "(expected %u) fd=%d filename=%s", group,
225 (unsigned long long)event->mask, (unsigned)event->pid,
226 (unsigned)getpid(), event->fd, filename);
227 } else if (strcmp(filename, expect_filename)) {
228 tst_res(TFAIL, "group %d got event: mask %llx pid=%u "
229 "fd=%d filename='%s' (expected '%s')", group,
230 (unsigned long long)event->mask, (unsigned)event->pid,
231 event->fd, filename, expect_filename);
232 } else {
233 event_res(TPASS, group, event, filename);
234 }
235 if (event->fd != FAN_NOFD)
236 SAFE_CLOSE(event->fd);
237 }
238
test_fanotify(unsigned int n)239 static void test_fanotify(unsigned int n)
240 {
241 int ret, dirfd;
242 unsigned int i;
243 struct fanotify_event_metadata *event;
244 struct tcase *tc = &tcases[n];
245
246 tst_res(TINFO, "Test #%d: %s", n, tc->tname);
247
248 if (fan_report_dfid_unsupported && tc->report_name) {
249 FANOTIFY_INIT_FLAGS_ERR_MSG(FAN_REPORT_DFID_NAME, fan_report_dfid_unsupported);
250 return;
251 }
252
253 create_fanotify_groups(tc);
254
255 /*
256 * generate MODIFY event and no FAN_CLOSE_NOWRITE event.
257 */
258 SAFE_FILE_PRINTF(fname, "1");
259 /*
260 * generate FAN_CLOSE_NOWRITE event on a child, subdir or "."
261 */
262 dirfd = SAFE_OPEN(tc->close_nowrite, O_RDONLY);
263 if (dirfd >= 0)
264 SAFE_CLOSE(dirfd);
265
266 /*
267 * First verify the first group got the file MODIFY event and got just
268 * one FAN_CLOSE_NOWRITE event.
269 */
270 ret = read(fd_notify[0], event_buf, EVENT_BUF_LEN);
271 if (ret < 0) {
272 if (errno == EAGAIN) {
273 tst_res(TFAIL, "first group did not get event");
274 } else {
275 tst_brk(TBROK | TERRNO,
276 "reading fanotify events failed");
277 }
278 }
279 if (ret < tc->nevents * (int)FAN_EVENT_METADATA_LEN) {
280 tst_brk(TBROK,
281 "short read when reading fanotify events (%d < %d)",
282 ret, tc->nevents * (int)FAN_EVENT_METADATA_LEN);
283 }
284 event = (struct fanotify_event_metadata *)event_buf;
285 verify_event(0, event, FAN_MODIFY, tc->report_name ? fname : "");
286 event = FAN_EVENT_NEXT(event, ret);
287 if (tc->nevents > 1) {
288 verify_event(0, event, FAN_CLOSE_NOWRITE,
289 tc->report_name ? (tc->ondir ? "." : tc->close_nowrite) : "");
290 event = FAN_EVENT_NEXT(event, ret);
291 }
292 if (ret > 0) {
293 tst_res(TFAIL,
294 "first group got more than %d events (%d bytes)",
295 tc->nevents, ret);
296 }
297 /* Close all file descriptors of read events */
298 for (; FAN_EVENT_OK(event, ret); FAN_EVENT_NEXT(event, ret)) {
299 if (event->fd != FAN_NOFD)
300 SAFE_CLOSE(event->fd);
301 }
302
303 /*
304 * Then verify the rest of the groups did not get the MODIFY event and
305 * got the FAN_CLOSE_NOWRITE event only on a non-directory.
306 */
307 for (i = 1; i < NUM_GROUPS; i++) {
308 ret = read(fd_notify[i], event_buf, EVENT_BUF_LEN);
309 if (ret > 0) {
310 uint32_t expect = 0;
311
312 if (tc->nevents > 1 && !tc->ondir)
313 expect = FAN_CLOSE_NOWRITE;
314
315 event = (struct fanotify_event_metadata *)event_buf;
316 verify_event(i, event, expect, "");
317 event = FAN_EVENT_NEXT(event, ret);
318
319 for (; FAN_EVENT_OK(event, ret); FAN_EVENT_NEXT(event, ret)) {
320 if (event->fd != FAN_NOFD)
321 SAFE_CLOSE(event->fd);
322 }
323 continue;
324 }
325
326 if (ret == 0) {
327 tst_brk(TBROK, "zero length read from fanotify fd");
328 }
329
330 if (errno != EAGAIN) {
331 tst_brk(TBROK | TERRNO,
332 "reading fanotify events failed");
333 }
334
335 tst_res(TPASS, "group %d got no event", i);
336 }
337 cleanup_fanotify_groups();
338 }
339
setup(void)340 static void setup(void)
341 {
342 fan_report_dfid_unsupported = fanotify_init_flags_supported_on_fs(FAN_REPORT_DFID_NAME,
343 MOUNT_PATH);
344
345 SAFE_MKDIR(MOUNT_NAME, 0755);
346 SAFE_MOUNT(MOUNT_PATH, MOUNT_NAME, "none", MS_BIND, NULL);
347 mount_created = 1;
348 SAFE_CHDIR(MOUNT_NAME);
349 SAFE_MKDIR(DIR_NAME, 0755);
350
351 sprintf(fname, "tfile_%d", getpid());
352 SAFE_FILE_PRINTF(fname, "1");
353 SAFE_FILE_PRINTF(FILE2_NAME, "1");
354 }
355
cleanup(void)356 static void cleanup(void)
357 {
358 cleanup_fanotify_groups();
359
360 SAFE_CHDIR("../");
361
362 if (mount_created && tst_umount(MOUNT_NAME) < 0)
363 tst_brk(TBROK | TERRNO, "umount failed");
364 }
365
366 static struct tst_test test = {
367 .test = test_fanotify,
368 .tcnt = ARRAY_SIZE(tcases),
369 .setup = setup,
370 .cleanup = cleanup,
371 .mount_device = 1,
372 .mntpoint = MOUNT_PATH,
373 .needs_root = 1,
374 .tags = (const struct tst_tag[]) {
375 {"linux-git", "54a307ba8d3c"},
376 {"linux-git", "b469e7e47c8a"},
377 {"linux-git", "55bf882c7f13"},
378 {"linux-git", "7372e79c9eb9"},
379 {}
380 }
381 };
382
383 #else
384 TST_TEST_TCONF("system doesn't have required fanotify support");
385 #endif
386