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