• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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:
16  *
17  *      54a307ba8d3c fanotify: fix logic of events on child
18  *
19  * Test case #1 is a regression test for commit:
20  *
21  *      b469e7e47c8a fanotify: fix handling of events on child sub-directory
22  *
23  * Test case #2 is a regression test for commit:
24  *
25  *      55bf882c7f13 fanotify: fix merging marks masks with FAN_ONDIR
26  *
27  * Test case #5 is a regression test for commit:
28  *
29  *      7372e79c9eb9 fanotify: fix logic of reporting name info with watched parent
30  *
31  * Test cases #6-#7 are regression tests for commit:
32  * (from v5.19, unlikely to be backported thus not in .tags):
33  *
34  *      e730558adffb fanotify: consistent behavior for parent not watching children
35  */
36 
37 #define _GNU_SOURCE
38 #include "config.h"
39 
40 #include <stdio.h>
41 #include <sys/stat.h>
42 #include <sys/types.h>
43 #include <errno.h>
44 #include <string.h>
45 #include <sys/mount.h>
46 #include <sys/syscall.h>
47 #include <stdint.h>
48 #include "tst_test.h"
49 
50 #ifdef HAVE_SYS_FANOTIFY_H
51 #include "fanotify.h"
52 
53 #define EVENT_MAX 1024
54 /* size of the event structure, not counting name */
55 #define EVENT_SIZE  (sizeof(struct fanotify_event_metadata))
56 /* reasonable guess as to size of 1024 events */
57 #define EVENT_BUF_LEN        (EVENT_MAX * EVENT_SIZE)
58 
59 #define NUM_GROUPS 3
60 
61 #define BUF_SIZE 256
62 static char fname[BUF_SIZE];
63 static char symlnk[BUF_SIZE];
64 static char fdpath[BUF_SIZE];
65 static int fd_notify[NUM_GROUPS];
66 
67 static char event_buf[EVENT_BUF_LEN];
68 
69 #define MOUNT_PATH "fs_mnt"
70 #define MOUNT_NAME "mntpoint"
71 #define DIR_NAME "testdir"
72 #define FILE2_NAME "testfile"
73 static int mount_created;
74 
75 static int fan_report_dfid_unsupported;
76 static int ignore_mark_unsupported;
77 
78 static struct tcase {
79 	const char *tname;
80 	struct fanotify_mark_type mark;
81 	unsigned int ondir;
82 	unsigned int ignore;
83 	unsigned int ignore_flags;
84 	unsigned int report_name;
85 	const char *event_path;
86 	int nevents;
87 	unsigned int nonfirst_event;
88 } tcases[] = {
89 	{
90 		.tname = "Events on non-dir child with both parent and mount marks",
91 		.mark = INIT_FANOTIFY_MARK_TYPE(MOUNT),
92 		.event_path = DIR_NAME,
93 		.nevents = 1,
94 	},
95 	{
96 		.tname = "Events on non-dir child and subdir with both parent and mount marks",
97 		.mark = INIT_FANOTIFY_MARK_TYPE(MOUNT),
98 		.ondir = FAN_ONDIR,
99 		.event_path = DIR_NAME,
100 		.nevents = 2,
101 	},
102 	{
103 		.tname = "Events on non-dir child and parent with both parent and mount marks",
104 		.mark = INIT_FANOTIFY_MARK_TYPE(MOUNT),
105 		.ondir = FAN_ONDIR,
106 		.event_path = ".",
107 		.nevents = 2,
108 	},
109 	{
110 		.tname = "Events on non-dir child and subdir with both parent and subdir marks",
111 		.mark = INIT_FANOTIFY_MARK_TYPE(INODE),
112 		.ondir = FAN_ONDIR,
113 		.event_path = DIR_NAME,
114 		.nevents = 2,
115 	},
116 	{
117 		.tname = "Events on non-dir children with both parent and mount marks",
118 		.mark = INIT_FANOTIFY_MARK_TYPE(MOUNT),
119 		.event_path = FILE2_NAME,
120 		.nevents = 2,
121 		.nonfirst_event = FAN_CLOSE_NOWRITE,
122 	},
123 	{
124 		.tname = "Events on non-dir child with both parent and mount marks and filename info",
125 		.mark = INIT_FANOTIFY_MARK_TYPE(MOUNT),
126 		.report_name = FAN_REPORT_DFID_NAME,
127 		.event_path = FILE2_NAME,
128 		.nevents = 2,
129 		.nonfirst_event = FAN_CLOSE_NOWRITE,
130 	},
131 	{
132 		.tname = "Events on non-dir child with ignore mask on parent",
133 		.mark = INIT_FANOTIFY_MARK_TYPE(MOUNT),
134 		.ignore = FAN_MARK_IGNORED_MASK,
135 		.event_path = DIR_NAME,
136 		.nevents = 1,
137 	},
138 	{
139 		.tname = "Events on non-dir children with surviving ignore mask on parent",
140 		.mark = INIT_FANOTIFY_MARK_TYPE(MOUNT),
141 		.ignore = FAN_MARK_IGNORED_MASK | FAN_MARK_IGNORED_SURV_MODIFY,
142 		.event_path = FILE2_NAME,
143 		.nevents = 2,
144 		.nonfirst_event = FAN_CLOSE_NOWRITE,
145 	},
146 	/* FAN_MARK_IGNORE test cases: */
147 	{
148 		.tname = "Events on dir with ignore mask that does not apply to dirs",
149 		.mark = INIT_FANOTIFY_MARK_TYPE(MOUNT),
150 		.ondir = FAN_ONDIR,
151 		.ignore = FAN_MARK_IGNORE_SURV,
152 		.event_path = ".",
153 		.nevents = 2,
154 		.nonfirst_event = FAN_CLOSE_NOWRITE,
155 	},
156 	{
157 		.tname = "Events on dir with ignore mask that does apply to dirs",
158 		.mark = INIT_FANOTIFY_MARK_TYPE(MOUNT),
159 		.ondir = FAN_ONDIR,
160 		.ignore = FAN_MARK_IGNORE_SURV,
161 		.ignore_flags = FAN_ONDIR,
162 		.event_path = ".",
163 		.nevents = 2,
164 	},
165 	{
166 		.tname = "Events on child with ignore mask on parent that does not apply to children",
167 		.mark = INIT_FANOTIFY_MARK_TYPE(MOUNT),
168 		.ignore = FAN_MARK_IGNORE_SURV,
169 		.event_path = FILE2_NAME,
170 		.nevents = 2,
171 		.nonfirst_event = FAN_CLOSE_NOWRITE,
172 	},
173 	{
174 		.tname = "Events on child with ignore mask on parent that does apply to children",
175 		.mark = INIT_FANOTIFY_MARK_TYPE(MOUNT),
176 		.ignore = FAN_MARK_IGNORE_SURV,
177 		.ignore_flags = FAN_EVENT_ON_CHILD,
178 		.event_path = FILE2_NAME,
179 		.nevents = 2,
180 	},
181 	{
182 		.tname = "Events on subdir with ignore mask on parent that does not apply to children",
183 		.mark = INIT_FANOTIFY_MARK_TYPE(MOUNT),
184 		.ondir = FAN_ONDIR,
185 		.ignore = FAN_MARK_IGNORE_SURV,
186 		.ignore_flags = FAN_ONDIR,
187 		.event_path = DIR_NAME,
188 		.nevents = 2,
189 		.nonfirst_event = FAN_CLOSE_NOWRITE,
190 	},
191 	{
192 		.tname = "Events on subdir with ignore mask on parent that does not apply to dirs",
193 		.mark = INIT_FANOTIFY_MARK_TYPE(MOUNT),
194 		.ondir = FAN_ONDIR,
195 		.ignore = FAN_MARK_IGNORE_SURV,
196 		.ignore_flags = FAN_EVENT_ON_CHILD,
197 		.event_path = DIR_NAME,
198 		.nevents = 2,
199 		.nonfirst_event = FAN_CLOSE_NOWRITE,
200 	},
201 	{
202 		.tname = "Events on subdir with ignore mask on parent that does apply to subdirs",
203 		.mark = INIT_FANOTIFY_MARK_TYPE(MOUNT),
204 		.ondir = FAN_ONDIR,
205 		.ignore = FAN_MARK_IGNORE_SURV,
206 		.ignore_flags = FAN_EVENT_ON_CHILD | FAN_ONDIR,
207 		.event_path = DIR_NAME,
208 		.nevents = 2,
209 	},
210 };
211 
create_fanotify_groups(struct tcase * tc)212 static void create_fanotify_groups(struct tcase *tc)
213 {
214 	struct fanotify_mark_type *mark = &tc->mark;
215 	int i;
216 
217 	for (i = 0; i < NUM_GROUPS; i++) {
218 		/*
219 		 * The first group may request events with filename info and
220 		 * events on subdirs and always request events on children.
221 		 */
222 		unsigned int report_name = tc->report_name;
223 		unsigned int mask_flags = tc->ondir | FAN_EVENT_ON_CHILD;
224 		unsigned int parent_mask, ignore_mask, ignore = 0;
225 
226 		/*
227 		 * The non-first groups may request events on children and
228 		 * subdirs only when setting an ignore mask on parent dir.
229 		 * The parent ignore mask may request to ignore events on
230 		 * children or subdirs.
231 		 */
232 		if (i > 0) {
233 			ignore = tc->ignore;
234 			report_name = 0;
235 			if (!ignore)
236 				mask_flags = 0;
237 		}
238 
239 		fd_notify[i] = SAFE_FANOTIFY_INIT(FAN_CLASS_NOTIF | report_name |
240 						  FAN_NONBLOCK, O_RDONLY);
241 
242 		/*
243 		 * Add subdir or mount mark for each group with CLOSE event,
244 		 * but only the first group requests events on dir.
245 		 */
246 		SAFE_FANOTIFY_MARK(fd_notify[i],
247 				    FAN_MARK_ADD | mark->flag,
248 				    FAN_CLOSE_NOWRITE | mask_flags,
249 				    AT_FDCWD, tc->event_path);
250 
251 		/*
252 		 * Add inode mark on parent for each group with MODIFY event,
253 		 * but only the first group requests events on child.
254 		 * The one mark with FAN_EVENT_ON_CHILD is needed for
255 		 * setting the DCACHE_FSNOTIFY_PARENT_WATCHED dentry flag.
256 		 *
257 		 * The inode mark on non-first group is either with FAN_MODIFY
258 		 * in mask or FAN_CLOSE_NOWRITE in ignore mask. In either case,
259 		 * it is not expected to get the modify event on a child, nor
260 		 * the close event on dir.
261 		 */
262 		parent_mask = FAN_MODIFY | tc->ondir | mask_flags;
263 		ignore_mask = FAN_CLOSE_NOWRITE | tc->ignore_flags;
264 		SAFE_FANOTIFY_MARK(fd_notify[i], FAN_MARK_ADD | ignore,
265 				    ignore ? ignore_mask : parent_mask,
266 				    AT_FDCWD, ".");
267 	}
268 }
269 
cleanup_fanotify_groups(void)270 static void cleanup_fanotify_groups(void)
271 {
272 	unsigned int i;
273 
274 	for (i = 0; i < NUM_GROUPS; i++) {
275 		if (fd_notify[i] > 0)
276 			SAFE_CLOSE(fd_notify[i]);
277 	}
278 }
279 
check_ignore_mask(int fd)280 static void check_ignore_mask(int fd)
281 {
282 	unsigned int ignored_mask, mflags;
283 	char procfdinfo[100];
284 
285 	sprintf(procfdinfo, "/proc/%d/fdinfo/%d", (int)getpid(), fd);
286 	if (FILE_LINES_SCANF(procfdinfo, "fanotify ino:%*x sdev:%*x mflags: %x mask:0 ignored_mask:%x",
287 				&mflags, &ignored_mask) || !ignored_mask) {
288 		tst_res(TFAIL, "The ignore mask did not survive");
289 	} else {
290 		tst_res(TPASS, "Found mark with ignore mask (ignored_mask=%x, mflags=%x) in %s",
291 				ignored_mask, mflags, procfdinfo);
292 	}
293 }
294 
event_res(int ttype,int group,struct fanotify_event_metadata * event,const char * filename)295 static void event_res(int ttype, int group,
296 		      struct fanotify_event_metadata *event,
297 		      const char *filename)
298 {
299 	if (event->fd != FAN_NOFD) {
300 		int len = 0;
301 
302 		sprintf(symlnk, "/proc/self/fd/%d", event->fd);
303 		len = readlink(symlnk, fdpath, sizeof(fdpath));
304 		if (len < 0)
305 			len = 0;
306 		fdpath[len] = 0;
307 		filename = fdpath;
308 	}
309 
310 	tst_res(ttype, "group %d got event: mask %llx pid=%u fd=%d filename=%s",
311 		group, (unsigned long long)event->mask,
312 		(unsigned int)event->pid, event->fd, filename);
313 }
314 
event_filename(struct fanotify_event_metadata * event)315 static const char *event_filename(struct fanotify_event_metadata *event)
316 {
317 	struct fanotify_event_info_fid *event_fid;
318 	struct file_handle *file_handle;
319 	const char *filename, *end;
320 
321 	if (event->event_len <= FAN_EVENT_METADATA_LEN)
322 		return "";
323 
324 	event_fid = (struct fanotify_event_info_fid *)(event + 1);
325 	file_handle = (struct file_handle *)event_fid->handle;
326 	filename = (char *)file_handle->f_handle + file_handle->handle_bytes;
327 	end = (char *)event_fid + event_fid->hdr.len;
328 
329 	/* End of event_fid could have name, zero padding, both or none */
330 	return (filename == end) ? "" : filename;
331 }
332 
verify_event(int group,struct fanotify_event_metadata * event,uint32_t expect,const char * expect_filename)333 static void verify_event(int group, struct fanotify_event_metadata *event,
334 			 uint32_t expect, const char *expect_filename)
335 {
336 	const char *filename = event_filename(event);
337 
338 	if (event->mask != expect) {
339 		tst_res(TFAIL, "group %d got event: mask %llx (expected %llx) "
340 			"pid=%u fd=%d filename=%s", group, (unsigned long long)event->mask,
341 			(unsigned long long)expect,
342 			(unsigned int)event->pid, event->fd, filename);
343 	} else if (event->pid != getpid()) {
344 		tst_res(TFAIL, "group %d got event: mask %llx pid=%u "
345 			"(expected %u) fd=%d filename=%s", group,
346 			(unsigned long long)event->mask, (unsigned int)event->pid,
347 			(unsigned int)getpid(), event->fd, filename);
348 	} else if (strcmp(filename, expect_filename)) {
349 		tst_res(TFAIL, "group %d got event: mask %llx pid=%u "
350 			"fd=%d filename='%s' (expected '%s')", group,
351 			(unsigned long long)event->mask, (unsigned int)event->pid,
352 			event->fd, filename, expect_filename);
353 	} else {
354 		event_res(TPASS, group, event, filename);
355 	}
356 	if (event->fd != FAN_NOFD)
357 		SAFE_CLOSE(event->fd);
358 }
359 
close_event_fds(struct fanotify_event_metadata * event,int buflen)360 static void close_event_fds(struct fanotify_event_metadata *event, int buflen)
361 {
362 	/* Close all file descriptors of read events */
363 	for (; FAN_EVENT_OK(event, buflen); FAN_EVENT_NEXT(event, buflen)) {
364 		if (event->fd != FAN_NOFD)
365 			SAFE_CLOSE(event->fd);
366 	}
367 }
368 
test_fanotify(unsigned int n)369 static void test_fanotify(unsigned int n)
370 {
371 	int ret, dirfd;
372 	unsigned int i;
373 	struct fanotify_event_metadata *event;
374 	struct tcase *tc = &tcases[n];
375 
376 	tst_res(TINFO, "Test #%d: %s", n, tc->tname);
377 
378 	if (fan_report_dfid_unsupported && tc->report_name) {
379 		FANOTIFY_INIT_FLAGS_ERR_MSG(FAN_REPORT_DFID_NAME, fan_report_dfid_unsupported);
380 		return;
381 	}
382 
383 	if (tc->ignore && tst_kvercmp(5, 19, 0) < 0) {
384 		tst_res(TCONF, "ignored mask on parent dir has undefined "
385 				"behavior on kernel < 5.19");
386 		return;
387 	}
388 
389 	if (ignore_mark_unsupported && tc->ignore & FAN_MARK_IGNORE) {
390 		tst_res(TCONF, "FAN_MARK_IGNORE not supported in kernel?");
391 		return;
392 	}
393 
394 	create_fanotify_groups(tc);
395 
396 	/*
397 	 * generate MODIFY event and no FAN_CLOSE_NOWRITE event.
398 	 */
399 	SAFE_FILE_PRINTF(fname, "1");
400 	/*
401 	 * generate FAN_CLOSE_NOWRITE event on a child, subdir or "."
402 	 */
403 	dirfd = SAFE_OPEN(tc->event_path, O_RDONLY);
404 	SAFE_CLOSE(dirfd);
405 
406 	/*
407 	 * First verify the first group got the file MODIFY event and got just
408 	 * one FAN_CLOSE_NOWRITE event.
409 	 */
410 	ret = read(fd_notify[0], event_buf, EVENT_BUF_LEN);
411 	if (ret < 0) {
412 		if (errno == EAGAIN) {
413 			tst_res(TFAIL, "first group did not get event");
414 		} else {
415 			tst_brk(TBROK | TERRNO,
416 				"reading fanotify events failed");
417 		}
418 	}
419 	event = (struct fanotify_event_metadata *)event_buf;
420 	if (ret < tc->nevents * (int)FAN_EVENT_METADATA_LEN) {
421 		tst_res(TFAIL,
422 			"short read when reading fanotify events (%d < %d)",
423 			ret, tc->nevents * (int)FAN_EVENT_METADATA_LEN);
424 	}
425 	if (FAN_EVENT_OK(event, ret)) {
426 		verify_event(0, event, FAN_MODIFY, tc->report_name ? fname : "");
427 		event = FAN_EVENT_NEXT(event, ret);
428 	}
429 	if (tc->nevents > 1 && FAN_EVENT_OK(event, ret)) {
430 		verify_event(0, event, FAN_CLOSE_NOWRITE,
431 			     tc->report_name ? (tc->ondir ? "." : tc->event_path) : "");
432 		event = FAN_EVENT_NEXT(event, ret);
433 	}
434 	if (ret > 0) {
435 		tst_res(TFAIL,
436 			"first group got more than %d events (%d bytes)",
437 			tc->nevents, ret);
438 	}
439 	close_event_fds(event, ret);
440 
441 	/*
442 	 * Then verify the rest of the groups did not get the MODIFY event and
443 	 * got the FAN_CLOSE_NOWRITE event only on a non-directory.
444 	 */
445 	for (i = 1; i < NUM_GROUPS; i++) {
446 		/*
447 		 * Verify that ignore mask survived the modify event on child,
448 		 * which was not supposed to be sent to this group.
449 		 */
450 		if (tc->ignore)
451 			check_ignore_mask(fd_notify[i]);
452 
453 		ret = read(fd_notify[i], event_buf, EVENT_BUF_LEN);
454 		if (ret > 0) {
455 			event = (struct fanotify_event_metadata *)event_buf;
456 			verify_event(i, event, tc->nonfirst_event, "");
457 			event = FAN_EVENT_NEXT(event, ret);
458 
459 			close_event_fds(event, ret);
460 			continue;
461 		}
462 
463 		if (ret == 0) {
464 			tst_res(TFAIL, "group %d zero length read from fanotify fd", i);
465 			continue;
466 		}
467 
468 		if (errno != EAGAIN) {
469 			tst_brk(TBROK | TERRNO,
470 				"reading fanotify events failed");
471 		}
472 
473 		if (tc->nonfirst_event)
474 			tst_res(TFAIL, "group %d expected and got no event", i);
475 		else
476 			tst_res(TPASS, "group %d got no event as expected", i);
477 	}
478 	cleanup_fanotify_groups();
479 }
480 
setup(void)481 static void setup(void)
482 {
483 	fan_report_dfid_unsupported = fanotify_init_flags_supported_on_fs(FAN_REPORT_DFID_NAME,
484 									  MOUNT_PATH);
485 	ignore_mark_unsupported = fanotify_mark_supported_by_kernel(FAN_MARK_IGNORE_SURV);
486 
487 	SAFE_MKDIR(MOUNT_NAME, 0755);
488 	SAFE_MOUNT(MOUNT_PATH, MOUNT_NAME, "none", MS_BIND, NULL);
489 	mount_created = 1;
490 	SAFE_CHDIR(MOUNT_NAME);
491 	SAFE_MKDIR(DIR_NAME, 0755);
492 
493 	sprintf(fname, "tfile_%d", getpid());
494 	SAFE_FILE_PRINTF(fname, "1");
495 	SAFE_FILE_PRINTF(FILE2_NAME, "1");
496 }
497 
cleanup(void)498 static void cleanup(void)
499 {
500 	cleanup_fanotify_groups();
501 
502 	SAFE_CHDIR("../");
503 
504 	if (mount_created)
505 		SAFE_UMOUNT(MOUNT_NAME);
506 }
507 
508 static struct tst_test test = {
509 	.test = test_fanotify,
510 	.tcnt = ARRAY_SIZE(tcases),
511 	.setup = setup,
512 	.cleanup = cleanup,
513 	.mount_device = 1,
514 	.mntpoint = MOUNT_PATH,
515 	.needs_root = 1,
516 	.tags = (const struct tst_tag[]) {
517 		{"linux-git", "54a307ba8d3c"},
518 		{"linux-git", "b469e7e47c8a"},
519 		{"linux-git", "55bf882c7f13"},
520 		{"linux-git", "7372e79c9eb9"},
521 		{}
522 	}
523 };
524 
525 #else
526 	TST_TEST_TCONF("system doesn't have required fanotify support");
527 #endif
528