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