1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * Copyright (c) 2018 Matthew Bobrowski. All Rights Reserved.
4 *
5 * Started by Matthew Bobrowski <mbobrowski@mbobrowski.org>
6 */
7
8 /*\
9 * [Description]
10 * Validate that the values returned within an event when FAN_REPORT_FID is
11 * specified matches those that are obtained via explicit invocation to system
12 * calls statfs(2) and name_to_handle_at(2).
13 */
14
15 /*
16 * This is also regression test for:
17 * c285a2f01d69 ("fanotify: update connector fsid cache on add mark")
18 */
19
20 #define _GNU_SOURCE
21 #include "config.h"
22
23 #include <stdio.h>
24 #include <string.h>
25 #include <sys/statfs.h>
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <errno.h>
29 #include <unistd.h>
30 #include "tst_test.h"
31
32 #ifdef HAVE_SYS_FANOTIFY_H
33 #include "fanotify.h"
34
35 #define PATH_LEN 128
36 #define BUF_SIZE 256
37 #define DIR_ONE "dir_one"
38 #define FILE_ONE "file_one"
39 #define FILE_TWO "file_two"
40 #define MOUNT_PATH "mntpoint"
41 #define EVENT_MAX ARRAY_SIZE(objects)
42 #define DIR_PATH_ONE MOUNT_PATH"/"DIR_ONE
43 #define FILE_PATH_ONE MOUNT_PATH"/"FILE_ONE
44 #define FILE_PATH_TWO MOUNT_PATH"/"FILE_TWO
45
46 #if defined(HAVE_NAME_TO_HANDLE_AT)
47 struct event_t {
48 unsigned long long expected_mask;
49 };
50
51 static struct object_t {
52 const char *path;
53 int is_dir;
54 struct fanotify_fid_t fid;
55 } objects[] = {
56 {FILE_PATH_ONE, 0, {}},
57 {FILE_PATH_TWO, 0, {}},
58 {DIR_PATH_ONE, 1, {}}
59 };
60
61 static struct test_case_t {
62 struct fanotify_mark_type mark;
63 unsigned long long mask;
64 } test_cases[] = {
65 {
66 INIT_FANOTIFY_MARK_TYPE(INODE),
67 FAN_OPEN | FAN_CLOSE_NOWRITE
68 },
69 {
70 INIT_FANOTIFY_MARK_TYPE(INODE),
71 FAN_OPEN | FAN_CLOSE_NOWRITE | FAN_ONDIR
72 },
73 {
74 INIT_FANOTIFY_MARK_TYPE(MOUNT),
75 FAN_OPEN | FAN_CLOSE_NOWRITE
76 },
77 {
78 INIT_FANOTIFY_MARK_TYPE(MOUNT),
79 FAN_OPEN | FAN_CLOSE_NOWRITE | FAN_ONDIR
80 },
81 {
82 INIT_FANOTIFY_MARK_TYPE(FILESYSTEM),
83 FAN_OPEN | FAN_CLOSE_NOWRITE
84 },
85 {
86 INIT_FANOTIFY_MARK_TYPE(FILESYSTEM),
87 FAN_OPEN | FAN_CLOSE_NOWRITE | FAN_ONDIR
88 }
89 };
90
91 static int nofid_fd;
92 static int fanotify_fd;
93 static int filesystem_mark_unsupported;
94 static char events_buf[BUF_SIZE];
95 static struct event_t event_set[EVENT_MAX];
96
create_objects(void)97 static void create_objects(void)
98 {
99 unsigned int i;
100
101 for (i = 0; i < ARRAY_SIZE(objects); i++) {
102 if (objects[i].is_dir)
103 SAFE_MKDIR(objects[i].path, 0755);
104 else
105 SAFE_FILE_PRINTF(objects[i].path, "0");
106 }
107 }
108
get_object_stats(void)109 static void get_object_stats(void)
110 {
111 unsigned int i;
112 for (i = 0; i < ARRAY_SIZE(objects); i++)
113 fanotify_save_fid(objects[i].path, &objects[i].fid);
114 }
115
setup_marks(unsigned int fd,struct test_case_t * tc)116 static int setup_marks(unsigned int fd, struct test_case_t *tc)
117 {
118 unsigned int i;
119 struct fanotify_mark_type *mark = &tc->mark;
120
121 for (i = 0; i < ARRAY_SIZE(objects); i++) {
122 SAFE_FANOTIFY_MARK(fd, FAN_MARK_ADD | mark->flag, tc->mask,
123 AT_FDCWD, objects[i].path);
124
125 /* Setup the expected mask for each generated event */
126 event_set[i].expected_mask = tc->mask;
127 if (!objects[i].is_dir)
128 event_set[i].expected_mask &= ~FAN_ONDIR;
129 }
130 return 0;
131 }
132
do_test(unsigned int number)133 static void do_test(unsigned int number)
134 {
135 unsigned int i;
136 int len, fds[ARRAY_SIZE(objects)];
137
138 struct file_handle *event_file_handle;
139 struct fanotify_event_metadata *metadata;
140 struct fanotify_event_info_fid *event_fid;
141 struct test_case_t *tc = &test_cases[number];
142 struct fanotify_mark_type *mark = &tc->mark;
143
144 tst_res(TINFO,
145 "Test #%d: FAN_REPORT_FID with mark flag: %s",
146 number, mark->name);
147
148 if (filesystem_mark_unsupported && mark->flag & FAN_MARK_FILESYSTEM) {
149 tst_res(TCONF, "FAN_MARK_FILESYSTEM not supported in kernel?");
150 return;
151 }
152
153 fanotify_fd = SAFE_FANOTIFY_INIT(FAN_CLASS_NOTIF | FAN_REPORT_FID, O_RDONLY);
154
155 /*
156 * Place marks on a set of objects and setup the expected masks
157 * for each event that is expected to be generated.
158 */
159 if (setup_marks(fanotify_fd, tc) != 0)
160 goto out;
161
162 /* Generate sequence of FAN_OPEN events on objects */
163 for (i = 0; i < ARRAY_SIZE(objects); i++)
164 fds[i] = SAFE_OPEN(objects[i].path, O_RDONLY);
165
166 /*
167 * Generate sequence of FAN_CLOSE_NOWRITE events on objects. Each
168 * FAN_CLOSE_NOWRITE event is expected to be merged with its
169 * respective FAN_OPEN event that was performed on the same object.
170 */
171 for (i = 0; i < ARRAY_SIZE(objects); i++) {
172 if (fds[i] > 0)
173 SAFE_CLOSE(fds[i]);
174 }
175
176 /* Read events from event queue */
177 len = SAFE_READ(0, fanotify_fd, events_buf, BUF_SIZE);
178
179 /* Iterate over event queue */
180 for (i = 0, metadata = (struct fanotify_event_metadata *) events_buf;
181 FAN_EVENT_OK(metadata, len);
182 metadata = FAN_EVENT_NEXT(metadata, len), i++) {
183 struct fanotify_fid_t *expected_fid = &objects[i].fid;
184 event_fid = (struct fanotify_event_info_fid *) (metadata + 1);
185 event_file_handle = (struct file_handle *) event_fid->handle;
186
187 /* File descriptor is redundant with FAN_REPORT_FID */
188 if (metadata->fd != FAN_NOFD)
189 tst_res(TFAIL,
190 "Unexpectedly received file descriptor %d in "
191 "event. Expected to get FAN_NOFD(%d)",
192 metadata->fd, FAN_NOFD);
193
194 /* Ensure that the correct mask has been reported in event */
195 if (metadata->mask != event_set[i].expected_mask)
196 tst_res(TFAIL,
197 "Unexpected mask received: %llx (expected: "
198 "%llx) in event",
199 metadata->mask,
200 event_set[i].expected_mask);
201
202 /* Verify handle_bytes returned in event */
203 if (event_file_handle->handle_bytes !=
204 expected_fid->handle.handle_bytes) {
205 tst_res(TFAIL,
206 "handle_bytes (%x) returned in event does not "
207 "equal to handle_bytes (%x) returned in "
208 "name_to_handle_at(2)",
209 event_file_handle->handle_bytes,
210 expected_fid->handle.handle_bytes);
211 continue;
212 }
213
214 /* Verify handle_type returned in event */
215 if (event_file_handle->handle_type !=
216 expected_fid->handle.handle_type) {
217 tst_res(TFAIL,
218 "handle_type (%x) returned in event does not "
219 "equal to handle_type (%x) returned in "
220 "name_to_handle_at(2)",
221 event_file_handle->handle_type,
222 expected_fid->handle.handle_type);
223 continue;
224 }
225
226 /* Verify file identifier f_handle returned in event */
227 if (memcmp(event_file_handle->f_handle,
228 expected_fid->handle.f_handle,
229 expected_fid->handle.handle_bytes) != 0) {
230 tst_res(TFAIL,
231 "file_handle returned in event does not match "
232 "the file_handle returned in "
233 "name_to_handle_at(2)");
234 continue;
235 }
236
237 /* Verify filesystem ID fsid returned in event */
238 if (memcmp(&event_fid->fsid, &expected_fid->fsid,
239 sizeof(expected_fid->fsid)) != 0) {
240 tst_res(TFAIL,
241 "event_fid.fsid != stat.f_fsid that was "
242 "obtained via statfs(2)");
243 continue;
244 }
245
246 tst_res(TPASS,
247 "got event: mask=%llx, pid=%d, fid=%x.%x.%lx values "
248 "returned in event match those returned in statfs(2) "
249 "and name_to_handle_at(2)",
250 metadata->mask,
251 getpid(),
252 FSID_VAL_MEMBER(event_fid->fsid, 0),
253 FSID_VAL_MEMBER(event_fid->fsid, 1),
254 *(unsigned long *) event_file_handle->f_handle);
255 }
256 out:
257 SAFE_CLOSE(fanotify_fd);
258 }
259
do_setup(void)260 static void do_setup(void)
261 {
262 REQUIRE_FANOTIFY_INIT_FLAGS_SUPPORTED_ON_FS(FAN_REPORT_FID, MOUNT_PATH);
263
264 filesystem_mark_unsupported = fanotify_mark_supported_by_kernel(FAN_MARK_FILESYSTEM);
265
266 nofid_fd = SAFE_FANOTIFY_INIT(FAN_CLASS_NOTIF, O_RDONLY);
267
268 /* Create file and directory objects for testing */
269 create_objects();
270
271 /*
272 * Create a mark on first inode without FAN_REPORT_FID, to test
273 * uninitialized connector->fsid cache. This mark remains for all test
274 * cases and is not expected to get any events (no writes in this test).
275 */
276 SAFE_FANOTIFY_MARK(nofid_fd, FAN_MARK_ADD, FAN_CLOSE_WRITE, AT_FDCWD,
277 FILE_PATH_ONE);
278
279 /* Get the filesystem fsid and file handle for each created object */
280 get_object_stats();
281 }
282
do_cleanup(void)283 static void do_cleanup(void)
284 {
285 SAFE_CLOSE(nofid_fd);
286 if (fanotify_fd > 0)
287 SAFE_CLOSE(fanotify_fd);
288 }
289
290 static struct tst_test test = {
291 .test = do_test,
292 .tcnt = ARRAY_SIZE(test_cases),
293 .setup = do_setup,
294 .cleanup = do_cleanup,
295 .needs_root = 1,
296 .mount_device = 1,
297 .mntpoint = MOUNT_PATH,
298 .all_filesystems = 1,
299 .tags = (const struct tst_tag[]) {
300 {"linux-git", "c285a2f01d69"},
301 {}
302 }
303 };
304
305 #else
306 TST_TEST_TCONF("System does not have required name_to_handle_at() support");
307 #endif
308 #else
309 TST_TEST_TCONF("System does not have required fanotify support");
310 #endif
311