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