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 <sys/mount.h>
29#include <errno.h>
30#include <unistd.h>
31#include "tst_test.h"
32
33#ifdef HAVE_SYS_FANOTIFY_H
34#include "fanotify.h"
35
36#define PATH_LEN 128
37#define BUF_SIZE 256
38#define DIR_ONE "dir_one"
39#define FILE_ONE "file_one"
40#define FILE_TWO "file_two"
41#define MOUNT_PATH "tstmnt"
42#define EVENT_MAX ARRAY_SIZE(objects)
43#define DIR_PATH_ONE MOUNT_PATH"/"DIR_ONE
44#define FILE_PATH_ONE MOUNT_PATH"/"FILE_ONE
45#define FILE_PATH_TWO MOUNT_PATH"/"FILE_TWO
46
47#if defined(HAVE_NAME_TO_HANDLE_AT)
48struct event_t {
49	unsigned long long expected_mask;
50};
51
52static struct object_t {
53	const char *path;
54	int is_dir;
55	struct fanotify_fid_t fid;
56} objects[] = {
57	{FILE_PATH_ONE, 0, {}},
58	{FILE_PATH_TWO, 0, {}},
59	{DIR_PATH_ONE, 1, {}}
60};
61
62static struct test_case_t {
63	struct fanotify_mark_type mark;
64	unsigned long long mask;
65} test_cases[] = {
66	{
67		INIT_FANOTIFY_MARK_TYPE(INODE),
68		FAN_OPEN | FAN_CLOSE_NOWRITE
69	},
70	{
71		INIT_FANOTIFY_MARK_TYPE(INODE),
72		FAN_OPEN | FAN_CLOSE_NOWRITE | FAN_ONDIR
73	},
74	{
75		INIT_FANOTIFY_MARK_TYPE(MOUNT),
76		FAN_OPEN | FAN_CLOSE_NOWRITE
77	},
78	{
79		INIT_FANOTIFY_MARK_TYPE(MOUNT),
80		FAN_OPEN | FAN_CLOSE_NOWRITE | FAN_ONDIR
81	},
82	{
83		INIT_FANOTIFY_MARK_TYPE(FILESYSTEM),
84		FAN_OPEN | FAN_CLOSE_NOWRITE
85	},
86	{
87		INIT_FANOTIFY_MARK_TYPE(FILESYSTEM),
88		FAN_OPEN | FAN_CLOSE_NOWRITE | FAN_ONDIR
89	}
90};
91
92static int ovl_mounted;
93static int bind_mounted;
94static int nofid_fd;
95static int fanotify_fd;
96static int filesystem_mark_unsupported;
97static char events_buf[BUF_SIZE];
98static struct event_t event_set[EVENT_MAX];
99
100static void create_objects(void)
101{
102	unsigned int i;
103
104	for (i = 0; i < ARRAY_SIZE(objects); i++) {
105		if (objects[i].is_dir)
106			SAFE_MKDIR(objects[i].path, 0755);
107		else
108			SAFE_FILE_PRINTF(objects[i].path, "0");
109	}
110}
111
112static void get_object_stats(void)
113{
114	unsigned int i;
115
116	for (i = 0; i < ARRAY_SIZE(objects); i++)
117		fanotify_save_fid(objects[i].path, &objects[i].fid);
118}
119
120static int setup_marks(unsigned int fd, struct test_case_t *tc)
121{
122	unsigned int i;
123	struct fanotify_mark_type *mark = &tc->mark;
124
125	for (i = 0; i < ARRAY_SIZE(objects); i++) {
126		SAFE_FANOTIFY_MARK(fd, FAN_MARK_ADD | mark->flag, tc->mask,
127				   AT_FDCWD, objects[i].path);
128
129		/* Setup the expected mask for each generated event */
130		event_set[i].expected_mask = tc->mask;
131		if (!objects[i].is_dir)
132			event_set[i].expected_mask &= ~FAN_ONDIR;
133	}
134	return 0;
135}
136
137static void do_test(unsigned int number)
138{
139	unsigned int i;
140	int len, fds[ARRAY_SIZE(objects)];
141
142	struct file_handle *event_file_handle;
143	struct fanotify_event_metadata *metadata;
144	struct fanotify_event_info_fid *event_fid;
145	struct test_case_t *tc = &test_cases[number];
146	struct fanotify_mark_type *mark = &tc->mark;
147
148	tst_res(TINFO,
149		"Test #%d.%d: FAN_REPORT_FID with mark flag: %s",
150		number, tst_variant, mark->name);
151
152	if (tst_variant && !ovl_mounted) {
153		tst_res(TCONF, "overlayfs not supported on %s", tst_device->fs_type);
154		return;
155	}
156
157	if (filesystem_mark_unsupported && mark->flag & FAN_MARK_FILESYSTEM) {
158		tst_res(TCONF, "FAN_MARK_FILESYSTEM not supported in kernel?");
159		return;
160	}
161
162	fanotify_fd = SAFE_FANOTIFY_INIT(FAN_CLASS_NOTIF | FAN_REPORT_FID, O_RDONLY);
163
164	/*
165	 * Place marks on a set of objects and setup the expected masks
166	 * for each event that is expected to be generated.
167	 */
168	if (setup_marks(fanotify_fd, tc) != 0)
169		goto out;
170
171	/* Variant #1: watching upper fs - open files on overlayfs */
172	if (tst_variant == 1) {
173		if (mark->flag & FAN_MARK_MOUNT) {
174			tst_res(TCONF, "overlayfs upper fs cannot be watched with mount mark");
175			goto out;
176		}
177		SAFE_MOUNT(OVL_MNT, MOUNT_PATH, "none", MS_BIND, NULL);
178	}
179
180	/* Generate sequence of FAN_OPEN events on objects */
181	for (i = 0; i < ARRAY_SIZE(objects); i++)
182		fds[i] = SAFE_OPEN(objects[i].path, O_RDONLY);
183
184	/*
185	 * Generate sequence of FAN_CLOSE_NOWRITE events on objects. Each
186	 * FAN_CLOSE_NOWRITE event is expected to be merged with its
187	 * respective FAN_OPEN event that was performed on the same object.
188	 */
189	for (i = 0; i < ARRAY_SIZE(objects); i++) {
190		if (fds[i] > 0)
191			SAFE_CLOSE(fds[i]);
192	}
193
194	if (tst_variant == 1)
195		SAFE_UMOUNT(MOUNT_PATH);
196
197	/* Read events from event queue */
198	len = SAFE_READ(0, fanotify_fd, events_buf, BUF_SIZE);
199
200	/* Iterate over event queue */
201	for (i = 0, metadata = (struct fanotify_event_metadata *) events_buf;
202		FAN_EVENT_OK(metadata, len);
203		metadata = FAN_EVENT_NEXT(metadata, len), i++) {
204		struct fanotify_fid_t *expected_fid = &objects[i].fid;
205
206		event_fid = (struct fanotify_event_info_fid *) (metadata + 1);
207		event_file_handle = (struct file_handle *) event_fid->handle;
208
209		/* File descriptor is redundant with FAN_REPORT_FID */
210		if (metadata->fd != FAN_NOFD)
211			tst_res(TFAIL,
212				"Unexpectedly received file descriptor %d in "
213				"event. Expected to get FAN_NOFD(%d)",
214				metadata->fd, FAN_NOFD);
215
216		/* Ensure that the correct mask has been reported in event */
217		if (metadata->mask != event_set[i].expected_mask)
218			tst_res(TFAIL,
219				"Unexpected mask received: %llx (expected: "
220				"%llx) in event",
221				metadata->mask,
222				event_set[i].expected_mask);
223
224		/* Verify handle_bytes returned in event */
225		if (event_file_handle->handle_bytes !=
226		    expected_fid->handle.handle_bytes) {
227			tst_res(TFAIL,
228				"handle_bytes (%x) returned in event does not "
229				"equal to handle_bytes (%x) returned in "
230				"name_to_handle_at(2)",
231				event_file_handle->handle_bytes,
232				expected_fid->handle.handle_bytes);
233			continue;
234		}
235
236		/* Verify handle_type returned in event */
237		if (event_file_handle->handle_type !=
238		    expected_fid->handle.handle_type) {
239			tst_res(TFAIL,
240				"handle_type (%x) returned in event does not "
241				"equal to handle_type (%x) returned in "
242				"name_to_handle_at(2)",
243				event_file_handle->handle_type,
244				expected_fid->handle.handle_type);
245			continue;
246		}
247
248		/* Verify file identifier f_handle returned in event */
249		if (memcmp(event_file_handle->f_handle,
250			   expected_fid->handle.f_handle,
251			   expected_fid->handle.handle_bytes) != 0) {
252			tst_res(TFAIL,
253				"file_handle returned in event does not match "
254				"the file_handle returned in "
255				"name_to_handle_at(2)");
256			continue;
257		}
258
259		/* Verify filesystem ID fsid  returned in event */
260		if (memcmp(&event_fid->fsid, &expected_fid->fsid,
261			   sizeof(expected_fid->fsid)) != 0) {
262			tst_res(TFAIL,
263				"event_fid.fsid != stat.f_fsid that was "
264				"obtained via statfs(2)");
265			continue;
266		}
267
268		tst_res(TPASS,
269			"got event: mask=%llx, pid=%d, fid=%x.%x.%lx values "
270			"returned in event match those returned in statfs(2) "
271			"and name_to_handle_at(2)",
272			metadata->mask,
273			getpid(),
274			FSID_VAL_MEMBER(event_fid->fsid, 0),
275			FSID_VAL_MEMBER(event_fid->fsid, 1),
276			*(unsigned long *) event_file_handle->f_handle);
277	}
278out:
279	SAFE_CLOSE(fanotify_fd);
280}
281
282static void do_setup(void)
283{
284	const char *mnt;
285
286	/*
287	 * Bind mount to either base fs or to overlayfs over base fs:
288	 * Variant #0: watch base fs - open files on base fs
289	 * Variant #1: watch upper fs - open files on overlayfs
290	 *
291	 * Variant #1 tests a bug whose fix bc2473c90fca ("ovl: enable fsnotify
292	 * events on underlying real files") in kernel 6.5 is not likely to be
293	 * backported to older kernels.
294	 * To avoid waiting for events that won't arrive when testing old kernels,
295	 * require that kernel supports encoding fid with new flag AT_HADNLE_FID,
296	 * also merged to 6.5 and not likely to be backported to older kernels.
297	 */
298	if (tst_variant) {
299		REQUIRE_HANDLE_TYPE_SUPPORTED_BY_KERNEL(AT_HANDLE_FID);
300		ovl_mounted = TST_MOUNT_OVERLAY();
301		mnt = OVL_UPPER;
302	} else {
303		mnt = OVL_BASE_MNTPOINT;
304
305	}
306	REQUIRE_FANOTIFY_INIT_FLAGS_SUPPORTED_ON_FS(FAN_REPORT_FID, mnt);
307	SAFE_MKDIR(MOUNT_PATH, 0755);
308	SAFE_MOUNT(mnt, MOUNT_PATH, "none", MS_BIND, NULL);
309	bind_mounted = 1;
310
311	filesystem_mark_unsupported = fanotify_mark_supported_by_kernel(FAN_MARK_FILESYSTEM);
312
313	nofid_fd = SAFE_FANOTIFY_INIT(FAN_CLASS_NOTIF, O_RDONLY);
314
315	/* Create file and directory objects for testing */
316	create_objects();
317
318	/*
319	 * Create a mark on first inode without FAN_REPORT_FID, to test
320	 * uninitialized connector->fsid cache. This mark remains for all test
321	 * cases and is not expected to get any events (no writes in this test).
322	 */
323	SAFE_FANOTIFY_MARK(nofid_fd, FAN_MARK_ADD, FAN_CLOSE_WRITE, AT_FDCWD,
324			  FILE_PATH_ONE);
325
326	/* Get the filesystem fsid and file handle for each created object */
327	get_object_stats();
328}
329
330static void do_cleanup(void)
331{
332	SAFE_CLOSE(nofid_fd);
333	if (fanotify_fd > 0)
334		SAFE_CLOSE(fanotify_fd);
335	if (bind_mounted) {
336		SAFE_UMOUNT(MOUNT_PATH);
337		SAFE_RMDIR(MOUNT_PATH);
338	}
339	if (ovl_mounted)
340		SAFE_UMOUNT(OVL_MNT);
341}
342
343static struct tst_test test = {
344	.test = do_test,
345	.tcnt = ARRAY_SIZE(test_cases),
346	.test_variants = 2,
347	.setup = do_setup,
348	.cleanup = do_cleanup,
349	.needs_root = 1,
350	.mount_device = 1,
351	.mntpoint = OVL_BASE_MNTPOINT,
352	.all_filesystems = 1,
353	.tags = (const struct tst_tag[]) {
354		{"linux-git", "c285a2f01d69"},
355		{"linux-git", "bc2473c90fca"},
356		{}
357	}
358};
359
360#else
361	TST_TEST_TCONF("System does not have required name_to_handle_at() support");
362#endif
363#else
364	TST_TEST_TCONF("System does not have required fanotify support");
365#endif
366