1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * Copyright (c) 2020 CTERA Networks. All Rights Reserved.
4 *
5 * Started by Amir Goldstein <amir73il@gmail.com>
6 *
7 * DESCRIPTION
8 * Check that event is reported to watching parent and watching child
9 * based on their interest
10 *
11 * Test case #3 is a regression test for commit fecc4559780d that fixes
12 * a bug introduced in kernel v5.9:
13 *
14 * fsnotify: fix events reported to watching parent and child
15 */
16
17 #include "config.h"
18
19 #if defined(HAVE_SYS_INOTIFY_H)
20 # include <sys/inotify.h>
21 #endif
22 #include <errno.h>
23 #include <string.h>
24 #include "tst_test.h"
25 #include "inotify.h"
26
27 #if defined(HAVE_SYS_INOTIFY_H)
28
29 #define EVENT_MAX 10
30 /* Size of the event structure, not including the name */
31 #define EVENT_SIZE (sizeof(struct inotify_event))
32 #define EVENT_BUF_LEN (EVENT_MAX * (EVENT_SIZE + 16))
33
34
35 #define BUF_SIZE 256
36
37 struct event_t {
38 char name[BUF_SIZE];
39 unsigned int mask;
40 int wd;
41 };
42
43 #define TEST_DIR "test_dir"
44 #define TEST_FILE "test_file"
45
46 static struct tcase {
47 const char *tname;
48 unsigned int parent_mask;
49 unsigned int subdir_mask;
50 unsigned int child_mask;
51 unsigned int parent_mask_other;
52 unsigned int subdir_mask_other;
53 unsigned int child_mask_other;
54 } tcases[] = {
55 {
56 "Group with parent and child watches",
57 IN_ATTRIB, IN_ATTRIB, IN_ATTRIB,
58 0, 0, 0,
59 },
60 {
61 "Group with child watches and other group with parent watch",
62 0, IN_ATTRIB, IN_ATTRIB,
63 IN_ATTRIB, 0, 0,
64 },
65 {
66 "Group with parent watch and other group with child watches",
67 IN_ATTRIB, 0, 0,
68 0, IN_ATTRIB, IN_ATTRIB,
69 },
70 {
71 "Two Groups with parent and child watches for different events",
72 IN_ATTRIB, IN_OPEN, IN_OPEN,
73 IN_OPEN, IN_ATTRIB, IN_ATTRIB,
74 },
75 };
76
77 struct event_t event_set[EVENT_MAX];
78
79 char event_buf[EVENT_BUF_LEN];
80
81 int fd_notify, fd_notify_other;
82
verify_inotify(unsigned int n)83 static void verify_inotify(unsigned int n)
84 {
85 struct tcase *tc = &tcases[n];
86 int i = 0, test_num = 0, len;
87 int wd_parent = 0, wd_subdir = 0, wd_child = 0;
88 int test_cnt = 0;
89
90 tst_res(TINFO, "Test #%d: %s", n, tc->tname);
91
92 fd_notify = SAFE_MYINOTIFY_INIT();
93 fd_notify_other = SAFE_MYINOTIFY_INIT();
94
95 /* Setup watches on parent dir and children */
96 if (tc->parent_mask)
97 wd_parent = SAFE_MYINOTIFY_ADD_WATCH(fd_notify, ".", tc->parent_mask);
98 if (tc->subdir_mask)
99 wd_subdir = SAFE_MYINOTIFY_ADD_WATCH(fd_notify, TEST_DIR, tc->subdir_mask);
100 if (tc->child_mask)
101 wd_child = SAFE_MYINOTIFY_ADD_WATCH(fd_notify, TEST_FILE, tc->child_mask);
102 /*
103 * Setup watches on "other" group to verify no intereferecne with our group.
104 * We do not check events reported to the "other" group.
105 */
106 if (tc->parent_mask_other)
107 SAFE_MYINOTIFY_ADD_WATCH(fd_notify_other, ".", tc->parent_mask_other);
108 if (tc->subdir_mask_other)
109 SAFE_MYINOTIFY_ADD_WATCH(fd_notify_other, TEST_DIR, tc->subdir_mask_other);
110 if (tc->child_mask_other)
111 SAFE_MYINOTIFY_ADD_WATCH(fd_notify_other, TEST_FILE, tc->child_mask_other);
112
113 /*
114 * Generate IN_ATTRIB events on file and subdir that should be reported to parent
115 * dir with name and to children without name if they have IN_ATTRIB in their mask.
116 */
117 SAFE_CHMOD(TEST_DIR, 0755);
118 SAFE_CHMOD(TEST_FILE, 0644);
119
120 if (wd_parent && (tc->parent_mask & IN_ATTRIB)) {
121 event_set[test_cnt].wd = wd_parent;
122 event_set[test_cnt].mask = tc->parent_mask | IN_ISDIR;
123 strcpy(event_set[test_cnt].name, TEST_DIR);
124 test_cnt++;
125 }
126 if (wd_subdir && (tc->subdir_mask & IN_ATTRIB)) {
127 event_set[test_cnt].wd = wd_subdir;
128 event_set[test_cnt].mask = tc->subdir_mask | IN_ISDIR;
129 strcpy(event_set[test_cnt].name, "");
130 test_cnt++;
131 }
132 if (wd_parent && (tc->parent_mask & IN_ATTRIB)) {
133 event_set[test_cnt].wd = wd_parent;
134 event_set[test_cnt].mask = tc->parent_mask;
135 strcpy(event_set[test_cnt].name, TEST_FILE);
136 test_cnt++;
137 }
138 if (wd_child && (tc->child_mask & IN_ATTRIB)) {
139 event_set[test_cnt].wd = wd_child;
140 event_set[test_cnt].mask = tc->child_mask;
141 strcpy(event_set[test_cnt].name, "");
142 test_cnt++;
143 }
144
145 len = read(fd_notify, event_buf, EVENT_BUF_LEN);
146 if (len == -1)
147 tst_brk(TBROK | TERRNO, "read failed");
148
149 while (i < len) {
150 struct event_t *expected = &event_set[test_num];
151 struct inotify_event *event;
152 event = (struct inotify_event *)&event_buf[i];
153 if (test_num >= test_cnt) {
154 tst_res(TFAIL,
155 "got unnecessary event: "
156 "wd=%d mask=%04x len=%u "
157 "name=\"%.*s\"", event->wd, event->mask,
158 event->len, event->len, event->name);
159
160 } else if (expected->wd == event->wd &&
161 expected->mask == event->mask &&
162 !strncmp(expected->name, event->name, event->len)) {
163 tst_res(TPASS,
164 "got event: wd=%d mask=%04x "
165 "cookie=%u len=%u name=\"%.*s\"",
166 event->wd, event->mask, event->cookie,
167 event->len, event->len, event->name);
168
169 } else {
170 tst_res(TFAIL, "got event: wd=%d (expected %d) "
171 "mask=%04x (expected %x) len=%u "
172 "name=\"%.*s\" (expected \"%s\")",
173 event->wd, expected->wd,
174 event->mask, expected->mask,
175 event->len, event->len,
176 event->name, expected->name);
177 }
178 test_num++;
179 i += EVENT_SIZE + event->len;
180 }
181
182 for (; test_num < test_cnt; test_num++) {
183 tst_res(TFAIL, "didn't get event: mask=%04x ",
184 event_set[test_num].mask);
185 }
186
187 SAFE_CLOSE(fd_notify);
188 SAFE_CLOSE(fd_notify_other);
189 }
190
setup(void)191 static void setup(void)
192 {
193 SAFE_MKDIR(TEST_DIR, 00700);
194 SAFE_FILE_PRINTF(TEST_FILE, "1");
195 }
196
cleanup(void)197 static void cleanup(void)
198 {
199 if (fd_notify > 0)
200 SAFE_CLOSE(fd_notify);
201 if (fd_notify_other > 0)
202 SAFE_CLOSE(fd_notify_other);
203 }
204
205 static struct tst_test test = {
206 .needs_tmpdir = 1,
207 .setup = setup,
208 .cleanup = cleanup,
209 .test = verify_inotify,
210 .tcnt = ARRAY_SIZE(tcases),
211 .tags = (const struct tst_tag[]) {
212 {"linux-git", "fecc4559780d"},
213 {}
214 }
215 };
216
217 #else
218 TST_TEST_TCONF("system doesn't have required inotify support");
219 #endif /* defined(HAVE_SYS_INOTIFY_H) */
220