• 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 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