• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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