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
37struct 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
46static 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
77struct event_t event_set[EVENT_MAX];
78
79char event_buf[EVENT_BUF_LEN];
80
81int fd_notify, fd_notify_other;
82
83static 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
191static void setup(void)
192{
193	SAFE_MKDIR(TEST_DIR, 00700);
194	SAFE_FILE_PRINTF(TEST_FILE, "1");
195}
196
197static 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
205static 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