aboutsummaryrefslogtreecommitdiff
path: root/testcases/kernel/syscalls/fanotify/fanotify13.c
blob: a25a360fde81c12790dc23381834cdab9d0f29cb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2018 Matthew Bobrowski. All Rights Reserved.
 *
 * Started by Matthew Bobrowski <mbobrowski@mbobrowski.org>
 */

/*\
 * [Description]
 * Validate that the values returned within an event when FAN_REPORT_FID is
 * specified matches those that are obtained via explicit invocation to system
 * calls statfs(2) and name_to_handle_at(2).
 */

/*
 * This is also regression test for:
 *     c285a2f01d69 ("fanotify: update connector fsid cache on add mark")
 */

#define _GNU_SOURCE
#include "config.h"

#include <stdio.h>
#include <string.h>
#include <sys/statfs.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <errno.h>
#include <unistd.h>
#include "tst_test.h"

#ifdef HAVE_SYS_FANOTIFY_H
#include "fanotify.h"

#define PATH_LEN 128
#define BUF_SIZE 256
#define DIR_ONE "dir_one"
#define FILE_ONE "file_one"
#define FILE_TWO "file_two"
#define MOUNT_PATH "tstmnt"
#define EVENT_MAX ARRAY_SIZE(objects)
#define DIR_PATH_ONE MOUNT_PATH"/"DIR_ONE
#define FILE_PATH_ONE MOUNT_PATH"/"FILE_ONE
#define FILE_PATH_TWO MOUNT_PATH"/"FILE_TWO

#if defined(HAVE_NAME_TO_HANDLE_AT)
struct event_t {
	unsigned long long expected_mask;
};

static struct object_t {
	const char *path;
	int is_dir;
	struct fanotify_fid_t fid;
} objects[] = {
	{FILE_PATH_ONE, 0, {}},
	{FILE_PATH_TWO, 0, {}},
	{DIR_PATH_ONE, 1, {}}
};

static struct test_case_t {
	struct fanotify_mark_type mark;
	unsigned long long mask;
} test_cases[] = {
	{
		INIT_FANOTIFY_MARK_TYPE(INODE),
		FAN_OPEN | FAN_CLOSE_NOWRITE
	},
	{
		INIT_FANOTIFY_MARK_TYPE(INODE),
		FAN_OPEN | FAN_CLOSE_NOWRITE | FAN_ONDIR
	},
	{
		INIT_FANOTIFY_MARK_TYPE(MOUNT),
		FAN_OPEN | FAN_CLOSE_NOWRITE
	},
	{
		INIT_FANOTIFY_MARK_TYPE(MOUNT),
		FAN_OPEN | FAN_CLOSE_NOWRITE | FAN_ONDIR
	},
	{
		INIT_FANOTIFY_MARK_TYPE(FILESYSTEM),
		FAN_OPEN | FAN_CLOSE_NOWRITE
	},
	{
		INIT_FANOTIFY_MARK_TYPE(FILESYSTEM),
		FAN_OPEN | FAN_CLOSE_NOWRITE | FAN_ONDIR
	}
};

static int ovl_mounted;
static int bind_mounted;
static int nofid_fd;
static int fanotify_fd;
static int filesystem_mark_unsupported;
static char events_buf[BUF_SIZE];
static struct event_t event_set[EVENT_MAX];

static void create_objects(void)
{
	unsigned int i;

	for (i = 0; i < ARRAY_SIZE(objects); i++) {
		if (objects[i].is_dir)
			SAFE_MKDIR(objects[i].path, 0755);
		else
			SAFE_FILE_PRINTF(objects[i].path, "0");
	}
}

static void get_object_stats(void)
{
	unsigned int i;

	for (i = 0; i < ARRAY_SIZE(objects); i++)
		fanotify_save_fid(objects[i].path, &objects[i].fid);
}

static int setup_marks(unsigned int fd, struct test_case_t *tc)
{
	unsigned int i;
	struct fanotify_mark_type *mark = &tc->mark;

	for (i = 0; i < ARRAY_SIZE(objects); i++) {
		SAFE_FANOTIFY_MARK(fd, FAN_MARK_ADD | mark->flag, tc->mask,
				   AT_FDCWD, objects[i].path);

		/* Setup the expected mask for each generated event */
		event_set[i].expected_mask = tc->mask;
		if (!objects[i].is_dir)
			event_set[i].expected_mask &= ~FAN_ONDIR;
	}
	return 0;
}

static void do_test(unsigned int number)
{
	unsigned int i;
	int len, fds[ARRAY_SIZE(objects)];

	struct file_handle *event_file_handle;
	struct fanotify_event_metadata *metadata;
	struct fanotify_event_info_fid *event_fid;
	struct test_case_t *tc = &test_cases[number];
	struct fanotify_mark_type *mark = &tc->mark;

	tst_res(TINFO,
		"Test #%d.%d: FAN_REPORT_FID with mark flag: %s",
		number, tst_variant, mark->name);

	if (tst_variant && !ovl_mounted) {
		tst_res(TCONF, "overlayfs not supported on %s", tst_device->fs_type);
		return;
	}

	if (filesystem_mark_unsupported && mark->flag & FAN_MARK_FILESYSTEM) {
		tst_res(TCONF, "FAN_MARK_FILESYSTEM not supported in kernel?");
		return;
	}

	fanotify_fd = SAFE_FANOTIFY_INIT(FAN_CLASS_NOTIF | FAN_REPORT_FID, O_RDONLY);

	/*
	 * Place marks on a set of objects and setup the expected masks
	 * for each event that is expected to be generated.
	 */
	if (setup_marks(fanotify_fd, tc) != 0)
		goto out;

	/* Variant #1: watching upper fs - open files on overlayfs */
	if (tst_variant == 1) {
		if (mark->flag & FAN_MARK_MOUNT) {
			tst_res(TCONF, "overlayfs upper fs cannot be watched with mount mark");
			goto out;
		}
		SAFE_MOUNT(OVL_MNT, MOUNT_PATH, "none", MS_BIND, NULL);
	}

	/* Generate sequence of FAN_OPEN events on objects */
	for (i = 0; i < ARRAY_SIZE(objects); i++)
		fds[i] = SAFE_OPEN(objects[i].path, O_RDONLY);

	/*
	 * Generate sequence of FAN_CLOSE_NOWRITE events on objects. Each
	 * FAN_CLOSE_NOWRITE event is expected to be merged with its
	 * respective FAN_OPEN event that was performed on the same object.
	 */
	for (i = 0; i < ARRAY_SIZE(objects); i++) {
		if (fds[i] > 0)
			SAFE_CLOSE(fds[i]);
	}

	if (tst_variant == 1)
		SAFE_UMOUNT(MOUNT_PATH);

	/* Read events from event queue */
	len = SAFE_READ(0, fanotify_fd, events_buf, BUF_SIZE);

	/* Iterate over event queue */
	for (i = 0, metadata = (struct fanotify_event_metadata *) events_buf;
		FAN_EVENT_OK(metadata, len);
		metadata = FAN_EVENT_NEXT(metadata, len), i++) {
		struct fanotify_fid_t *expected_fid = &objects[i].fid;

		event_fid = (struct fanotify_event_info_fid *) (metadata + 1);
		event_file_handle = (struct file_handle *) event_fid->handle;

		/* File descriptor is redundant with FAN_REPORT_FID */
		if (metadata->fd != FAN_NOFD)
			tst_res(TFAIL,
				"Unexpectedly received file descriptor %d in "
				"event. Expected to get FAN_NOFD(%d)",
				metadata->fd, FAN_NOFD);

		/* Ensure that the correct mask has been reported in event */
		if (metadata->mask != event_set[i].expected_mask)
			tst_res(TFAIL,
				"Unexpected mask received: %llx (expected: "
				"%llx) in event",
				metadata->mask,
				event_set[i].expected_mask);

		/* Verify handle_bytes returned in event */
		if (event_file_handle->handle_bytes !=
		    expected_fid->handle.handle_bytes) {
			tst_res(TFAIL,
				"handle_bytes (%x) returned in event does not "
				"equal to handle_bytes (%x) returned in "
				"name_to_handle_at(2)",
				event_file_handle->handle_bytes,
				expected_fid->handle.handle_bytes);
			continue;
		}

		/* Verify handle_type returned in event */
		if (event_file_handle->handle_type !=
		    expected_fid->handle.handle_type) {
			tst_res(TFAIL,
				"handle_type (%x) returned in event does not "
				"equal to handle_type (%x) returned in "
				"name_to_handle_at(2)",
				event_file_handle->handle_type,
				expected_fid->handle.handle_type);
			continue;
		}

		/* Verify file identifier f_handle returned in event */
		if (memcmp(event_file_handle->f_handle,
			   expected_fid->handle.f_handle,
			   expected_fid->handle.handle_bytes) != 0) {
			tst_res(TFAIL,
				"file_handle returned in event does not match "
				"the file_handle returned in "
				"name_to_handle_at(2)");
			continue;
		}

		/* Verify filesystem ID fsid  returned in event */
		if (memcmp(&event_fid->fsid, &expected_fid->fsid,
			   sizeof(expected_fid->fsid)) != 0) {
			tst_res(TFAIL,
				"event_fid.fsid != stat.f_fsid that was "
				"obtained via statfs(2)");
			continue;
		}

		tst_res(TPASS,
			"got event: mask=%llx, pid=%d, fid=%x.%x.%lx values "
			"returned in event match those returned in statfs(2) "
			"and name_to_handle_at(2)",
			metadata->mask,
			getpid(),
			FSID_VAL_MEMBER(event_fid->fsid, 0),
			FSID_VAL_MEMBER(event_fid->fsid, 1),
			*(unsigned long *) event_file_handle->f_handle);
	}
out:
	SAFE_CLOSE(fanotify_fd);
}

static void do_setup(void)
{
	const char *mnt;

	/*
	 * Bind mount to either base fs or to overlayfs over base fs:
	 * Variant #0: watch base fs - open files on base fs
	 * Variant #1: watch upper fs - open files on overlayfs
	 *
	 * Variant #1 tests a bug whose fix bc2473c90fca ("ovl: enable fsnotify
	 * events on underlying real files") in kernel 6.5 is not likely to be
	 * backported to older kernels.
	 * To avoid waiting for events that won't arrive when testing old kernels,
	 * require that kernel supports encoding fid with new flag AT_HADNLE_FID,
	 * also merged to 6.5 and not likely to be backported to older kernels.
	 */
	if (tst_variant) {
		REQUIRE_HANDLE_TYPE_SUPPORTED_BY_KERNEL(AT_HANDLE_FID);
		ovl_mounted = TST_MOUNT_OVERLAY();
		mnt = OVL_UPPER;
	} else {
		mnt = OVL_BASE_MNTPOINT;

	}
	REQUIRE_FANOTIFY_INIT_FLAGS_SUPPORTED_ON_FS(FAN_REPORT_FID, mnt);
	SAFE_MKDIR(MOUNT_PATH, 0755);
	SAFE_MOUNT(mnt, MOUNT_PATH, "none", MS_BIND, NULL);
	bind_mounted = 1;

	filesystem_mark_unsupported = fanotify_mark_supported_by_kernel(FAN_MARK_FILESYSTEM);

	nofid_fd = SAFE_FANOTIFY_INIT(FAN_CLASS_NOTIF, O_RDONLY);

	/* Create file and directory objects for testing */
	create_objects();

	/*
	 * Create a mark on first inode without FAN_REPORT_FID, to test
	 * uninitialized connector->fsid cache. This mark remains for all test
	 * cases and is not expected to get any events (no writes in this test).
	 */
	SAFE_FANOTIFY_MARK(nofid_fd, FAN_MARK_ADD, FAN_CLOSE_WRITE, AT_FDCWD,
			  FILE_PATH_ONE);

	/* Get the filesystem fsid and file handle for each created object */
	get_object_stats();
}

static void do_cleanup(void)
{
	SAFE_CLOSE(nofid_fd);
	if (fanotify_fd > 0)
		SAFE_CLOSE(fanotify_fd);
	if (bind_mounted) {
		SAFE_UMOUNT(MOUNT_PATH);
		SAFE_RMDIR(MOUNT_PATH);
	}
	if (ovl_mounted)
		SAFE_UMOUNT(OVL_MNT);
}

static struct tst_test test = {
	.test = do_test,
	.tcnt = ARRAY_SIZE(test_cases),
	.test_variants = 2,
	.setup = do_setup,
	.cleanup = do_cleanup,
	.needs_root = 1,
	.mount_device = 1,
	.mntpoint = OVL_BASE_MNTPOINT,
	.all_filesystems = 1,
	.tags = (const struct tst_tag[]) {
		{"linux-git", "c285a2f01d69"},
		{"linux-git", "bc2473c90fca"},
		{}
	}
};

#else
	TST_TEST_TCONF("System does not have required name_to_handle_at() support");
#endif
#else
	TST_TEST_TCONF("System does not have required fanotify support");
#endif