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