• 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  * DESCRIPTION
8  *     Check that fanotify handles events on children correctly when
9  *     both inode and mountpoint marks exist.
10  *
11  * This is a regression test for commit 54a307ba8d3c:
12  *
13  *      fanotify: fix logic of events on child
14  *
15  * Test case #2 is a regression test for commit b469e7e47c8a:
16  *
17  *      fanotify: fix handling of events on child sub-directory
18  *
19  * Test case #3 is a regression test for commit 55bf882c7f13:
20  *
21  *      fanotify: fix merging marks masks with FAN_ONDIR
22  */
23 #define _GNU_SOURCE
24 #include "config.h"
25 
26 #include <stdio.h>
27 #include <sys/stat.h>
28 #include <sys/types.h>
29 #include <fcntl.h>
30 #include <errno.h>
31 #include <string.h>
32 #include <sys/mount.h>
33 #include <sys/syscall.h>
34 #include <stdint.h>
35 #include "tst_test.h"
36 #include "fanotify.h"
37 
38 #if defined(HAVE_SYS_FANOTIFY_H)
39 #include <sys/fanotify.h>
40 
41 #define EVENT_MAX 1024
42 /* size of the event structure, not counting name */
43 #define EVENT_SIZE  (sizeof (struct fanotify_event_metadata))
44 /* reasonable guess as to size of 1024 events */
45 #define EVENT_BUF_LEN        (EVENT_MAX * EVENT_SIZE)
46 
47 #define NUM_GROUPS 3
48 
49 #define BUF_SIZE 256
50 static char fname[BUF_SIZE];
51 static char symlnk[BUF_SIZE];
52 static char fdpath[BUF_SIZE];
53 static int fd_notify[NUM_GROUPS];
54 
55 static char event_buf[EVENT_BUF_LEN];
56 
57 #define MOUNT_NAME "mntpoint"
58 #define DIR_NAME "testdir"
59 static int mount_created;
60 
61 static struct tcase {
62 	const char *tname;
63 	unsigned int ondir;
64 	const char *testdir;
65 	int nevents;
66 } tcases[] = {
67 	{
68 		"Events on children with both inode and mount marks",
69 		0,
70 		DIR_NAME,
71 		1,
72 	},
73 	{
74 		"Events on children and subdirs with both inode and mount marks",
75 		FAN_ONDIR,
76 		DIR_NAME,
77 		2,
78 	},
79 	{
80 		"Events on files and dirs with both inode and mount marks",
81 		FAN_ONDIR,
82 		".",
83 		2,
84 	},
85 };
86 
create_fanotify_groups(unsigned int ondir)87 static void create_fanotify_groups(unsigned int ondir)
88 {
89 	unsigned int i, onchild;
90 	int ret;
91 
92 	for (i = 0; i < NUM_GROUPS; i++) {
93 		fd_notify[i] = SAFE_FANOTIFY_INIT(FAN_CLASS_NOTIF |
94 						  FAN_NONBLOCK,
95 						  O_RDONLY);
96 
97 		/* Add mount mark for each group without MODIFY event */
98 		onchild = (i == 0) ? FAN_EVENT_ON_CHILD | ondir : 0;
99 		ret = fanotify_mark(fd_notify[i],
100 				    FAN_MARK_ADD | FAN_MARK_MOUNT,
101 				    FAN_CLOSE_NOWRITE | onchild,
102 				    AT_FDCWD, ".");
103 		if (ret < 0) {
104 			tst_brk(TBROK | TERRNO,
105 				"fanotify_mark(%d, FAN_MARK_ADD | "
106 				"FAN_MARK_MOUNT, FAN_MODIFY%s, AT_FDCWD,"
107 				" '.') failed", fd_notify[i],
108 				ondir ? " | FAN_ONDIR" : "");
109 		}
110 		/*
111 		 * Add inode mark on parent for each group with MODIFY
112 		 * event, but only one group requests events on child.
113 		 * The one mark with FAN_EVENT_ON_CHILD is needed for
114 		 * setting the DCACHE_FSNOTIFY_PARENT_WATCHED dentry
115 		 * flag.
116 		 */
117 		ret = fanotify_mark(fd_notify[i], FAN_MARK_ADD,
118 				    FAN_MODIFY | ondir | onchild,
119 				    AT_FDCWD, ".");
120 		if (ret < 0) {
121 			tst_brk(TBROK | TERRNO,
122 				"fanotify_mark(%d, FAN_MARK_ADD, "
123 				"FAN_MODIFY%s%s, AT_FDCWD, '.') failed",
124 				fd_notify[i],
125 				ondir ? " | FAN_ONDIR" : "",
126 				onchild ? " | FAN_EVENT_ON_CHILD" : "");
127 		}
128 	}
129 }
130 
cleanup_fanotify_groups(void)131 static void cleanup_fanotify_groups(void)
132 {
133 	unsigned int i;
134 
135 	for (i = 0; i < NUM_GROUPS; i++) {
136 		if (fd_notify[i] > 0)
137 			SAFE_CLOSE(fd_notify[i]);
138 	}
139 }
140 
event_res(int ttype,int group,struct fanotify_event_metadata * event)141 static void event_res(int ttype, int group,
142 		      struct fanotify_event_metadata *event)
143 {
144 	int len;
145 	sprintf(symlnk, "/proc/self/fd/%d", event->fd);
146 	len = readlink(symlnk, fdpath, sizeof(fdpath));
147 	if (len < 0)
148 		len = 0;
149 	fdpath[len] = 0;
150 	tst_res(ttype, "group %d got event: mask %llx pid=%u fd=%d path=%s",
151 		group, (unsigned long long)event->mask,
152 		(unsigned)event->pid, event->fd, fdpath);
153 }
154 
verify_event(int group,struct fanotify_event_metadata * event,uint32_t expect)155 static void verify_event(int group, struct fanotify_event_metadata *event,
156 			 uint32_t expect)
157 {
158 	if (event->mask != expect) {
159 		tst_res(TFAIL, "group %d got event: mask %llx (expected %llx) "
160 			"pid=%u fd=%d", group, (unsigned long long)event->mask,
161 			(unsigned long long)expect,
162 			(unsigned)event->pid, event->fd);
163 	} else if (event->pid != getpid()) {
164 		tst_res(TFAIL, "group %d got event: mask %llx pid=%u "
165 			"(expected %u) fd=%d", group,
166 			(unsigned long long)event->mask, (unsigned)event->pid,
167 			(unsigned)getpid(), event->fd);
168 	} else {
169 		event_res(TPASS, group, event);
170 	}
171 }
172 
test_fanotify(unsigned int n)173 static void test_fanotify(unsigned int n)
174 {
175 	int ret, dirfd;
176 	unsigned int i;
177 	struct fanotify_event_metadata *event, *ev;
178 	struct tcase *tc = &tcases[n];
179 
180 	tst_res(TINFO, "Test #%d: %s", n, tc->tname);
181 
182 	create_fanotify_groups(tc->ondir);
183 
184 	/*
185 	 * generate MODIFY event and no FAN_CLOSE_NOWRITE event.
186 	 */
187 	SAFE_FILE_PRINTF(fname, "1");
188 	/*
189 	 * generate FAN_CLOSE_NOWRITE event on a testdir (subdir or ".")
190 	 */
191 	dirfd = SAFE_OPEN(tc->testdir, O_RDONLY);
192 	if (dirfd >= 0)
193 		SAFE_CLOSE(dirfd);
194 
195 	/*
196 	 * First verify the first group got the file MODIFY event and got just
197 	 * one FAN_CLOSE_NOWRITE event.
198 	 */
199 	ret = read(fd_notify[0], event_buf, EVENT_BUF_LEN);
200 	if (ret < 0) {
201 		if (errno == EAGAIN) {
202 			tst_res(TFAIL, "first group did not get event");
203 		} else {
204 			tst_brk(TBROK | TERRNO,
205 				"reading fanotify events failed");
206 		}
207 	}
208 	if (ret < tc->nevents * (int)FAN_EVENT_METADATA_LEN) {
209 		tst_brk(TBROK,
210 			"short read when reading fanotify events (%d < %d)",
211 			ret, tc->nevents * (int)FAN_EVENT_METADATA_LEN);
212 	}
213 	event = (struct fanotify_event_metadata *)event_buf;
214 	verify_event(0, event, FAN_MODIFY);
215 	if (tc->ondir)
216 		verify_event(0, event + 1, FAN_CLOSE_NOWRITE);
217 	if (ret > tc->nevents * (int)FAN_EVENT_METADATA_LEN) {
218 		tst_res(TFAIL,
219 			"first group got more than %d events (%d > %d)",
220 			tc->nevents, ret,
221 			tc->nevents * (int)FAN_EVENT_METADATA_LEN);
222 	}
223 	/* Close all file descriptors of read events */
224 	for (ev = event; ret >= (int)FAN_EVENT_METADATA_LEN; ev++) {
225 		if (ev->fd != FAN_NOFD)
226 			SAFE_CLOSE(ev->fd);
227 		ret -= (int)FAN_EVENT_METADATA_LEN;
228 	}
229 
230 	/*
231 	 * Then verify the rest of the groups did not get the MODIFY event and
232 	 * did not get the FAN_CLOSE_NOWRITE event on testdir.
233 	 */
234 	for (i = 1; i < NUM_GROUPS; i++) {
235 		ret = read(fd_notify[i], event_buf, FAN_EVENT_METADATA_LEN);
236 		if (ret > 0) {
237 			event_res(TFAIL, i, event);
238 			if (event->fd != FAN_NOFD)
239 				SAFE_CLOSE(event->fd);
240 			continue;
241 		}
242 
243 		if (ret == 0) {
244 			tst_brk(TBROK, "zero length read from fanotify fd");
245 		}
246 
247 		if (errno != EAGAIN) {
248 			tst_brk(TBROK | TERRNO,
249 				"reading fanotify events failed");
250 		}
251 
252 		tst_res(TPASS, "group %d got no event", i);
253 	}
254 	cleanup_fanotify_groups();
255 }
256 
setup(void)257 static void setup(void)
258 {
259 	SAFE_MKDIR(MOUNT_NAME, 0755);
260 	SAFE_MOUNT(MOUNT_NAME, MOUNT_NAME, "none", MS_BIND, NULL);
261 	mount_created = 1;
262 	SAFE_CHDIR(MOUNT_NAME);
263 	SAFE_MKDIR(DIR_NAME, 0755);
264 
265 	sprintf(fname, "tfile_%d", getpid());
266 	SAFE_FILE_PRINTF(fname, "1");
267 }
268 
cleanup(void)269 static void cleanup(void)
270 {
271 	cleanup_fanotify_groups();
272 
273 	SAFE_CHDIR("../");
274 
275 	if (mount_created && tst_umount(MOUNT_NAME) < 0)
276 		tst_brk(TBROK | TERRNO, "umount failed");
277 }
278 
279 static struct tst_test test = {
280 	.test = test_fanotify,
281 	.tcnt = ARRAY_SIZE(tcases),
282 	.setup = setup,
283 	.cleanup = cleanup,
284 	.needs_tmpdir = 1,
285 	.needs_root = 1,
286 	.tags = (const struct tst_tag[]) {
287 		{"linux-git", "54a307ba8d3c"},
288 		{"linux-git", "b469e7e47c8a"},
289 		{"linux-git", "55bf882c7f13"},
290 		{}
291 	}
292 };
293 
294 #else
295 	TST_TEST_TCONF("system doesn't have required fanotify support");
296 #endif
297