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
62static char fname[BUF_SIZE];
63static char symlnk[BUF_SIZE];
64static char fdpath[BUF_SIZE];
65static int fd_notify[NUM_GROUPS];
66
67static 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"
73static int mount_created;
74
75static int fan_report_dfid_unsupported;
76static int ignore_mark_unsupported;
77
78static 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
212static 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
270static 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
280static 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
295static 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
315static 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
333static 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
360static 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
369static 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
481static 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
498static void cleanup(void)
499{
500	cleanup_fanotify_groups();
501
502	SAFE_CHDIR("../");
503
504	if (mount_created)
505		SAFE_UMOUNT(MOUNT_NAME);
506}
507
508static 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