File Index
Files and directories
Sign in
FILE

mount-notify_test.c

File preview with syntax highlighting.

linux-6.19.14/tools/testing/selftests/filesystems/mount-notify/mount-notify_test.c C
  1// SPDX-License-Identifier: GPL-2.0-or-later
  2// Copyright (c) 2025 Miklos Szeredi <miklos@szeredi.hu>
  3
  4#define _GNU_SOURCE
  5
  6// Needed for linux/fanotify.h
  7typedef struct {
  8	int	val[2];
  9} __kernel_fsid_t;
 10#define __kernel_fsid_t __kernel_fsid_t
 11
 12#include <fcntl.h>
 13#include <sched.h>
 14#include <stdio.h>
 15#include <string.h>
 16#include <sys/stat.h>
 17#include <sys/mount.h>
 18#include <unistd.h>
 19#include <sys/syscall.h>
 20#include <sys/fanotify.h>
 21
 22#include "kselftest_harness.h"
 23#include "../statmount/statmount.h"
 24#include "../utils.h"
 25
 26static const char root_mntpoint_templ[] = "/tmp/mount-notify_test_root.XXXXXX";
 27
 28static const int mark_cmds[] = {
 29	FAN_MARK_ADD,
 30	FAN_MARK_REMOVE,
 31	FAN_MARK_FLUSH
 32};
 33
 34#define NUM_FAN_FDS ARRAY_SIZE(mark_cmds)
 35
 36FIXTURE(fanotify) {
 37	int fan_fd[NUM_FAN_FDS];
 38	char buf[256];
 39	unsigned int rem;
 40	void *next;
 41	char root_mntpoint[sizeof(root_mntpoint_templ)];
 42	int orig_root;
 43	int ns_fd;
 44	uint64_t root_id;
 45};
 46
 47FIXTURE_SETUP(fanotify)
 48{
 49	int i, ret;
 50
 51	ASSERT_EQ(unshare(CLONE_NEWNS), 0);
 52
 53	self->ns_fd = open("/proc/self/ns/mnt", O_RDONLY);
 54	ASSERT_GE(self->ns_fd, 0);
 55
 56	ASSERT_EQ(mount("", "/", NULL, MS_REC|MS_PRIVATE, NULL), 0);
 57
 58	strcpy(self->root_mntpoint, root_mntpoint_templ);
 59	ASSERT_NE(mkdtemp(self->root_mntpoint), NULL);
 60
 61	self->orig_root = open("/", O_PATH | O_CLOEXEC);
 62	ASSERT_GE(self->orig_root, 0);
 63
 64	ASSERT_EQ(mount("tmpfs", self->root_mntpoint, "tmpfs", 0, NULL), 0);
 65
 66	ASSERT_EQ(chroot(self->root_mntpoint), 0);
 67
 68	ASSERT_EQ(chdir("/"), 0);
 69
 70	ASSERT_EQ(mkdir("a", 0700), 0);
 71
 72	ASSERT_EQ(mkdir("b", 0700), 0);
 73
 74	self->root_id = get_unique_mnt_id("/");
 75	ASSERT_NE(self->root_id, 0);
 76
 77	for (i = 0; i < NUM_FAN_FDS; i++) {
 78		self->fan_fd[i] = fanotify_init(FAN_REPORT_MNT | FAN_NONBLOCK,
 79						0);
 80		ASSERT_GE(self->fan_fd[i], 0);
 81		ret = fanotify_mark(self->fan_fd[i], FAN_MARK_ADD |
 82				    FAN_MARK_MNTNS,
 83				    FAN_MNT_ATTACH | FAN_MNT_DETACH,
 84				    self->ns_fd, NULL);
 85		ASSERT_EQ(ret, 0);
 86		// On fd[0] we do an extra ADD that changes nothing.
 87		// On fd[1]/fd[2] we REMOVE/FLUSH which removes the mark.
 88		ret = fanotify_mark(self->fan_fd[i], mark_cmds[i] |
 89				    FAN_MARK_MNTNS,
 90				    FAN_MNT_ATTACH | FAN_MNT_DETACH,
 91				    self->ns_fd, NULL);
 92		ASSERT_EQ(ret, 0);
 93	}
 94
 95	self->rem = 0;
 96}
 97
 98FIXTURE_TEARDOWN(fanotify)
 99{
100	int i;
101
102	ASSERT_EQ(self->rem, 0);
103	for (i = 0; i < NUM_FAN_FDS; i++)
104		close(self->fan_fd[i]);
105
106	ASSERT_EQ(fchdir(self->orig_root), 0);
107
108	ASSERT_EQ(chroot("."), 0);
109
110	EXPECT_EQ(umount2(self->root_mntpoint, MNT_DETACH), 0);
111	EXPECT_EQ(chdir(self->root_mntpoint), 0);
112	EXPECT_EQ(chdir("/"), 0);
113	EXPECT_EQ(rmdir(self->root_mntpoint), 0);
114}
115
116static uint64_t expect_notify(struct __test_metadata *const _metadata,
117			      FIXTURE_DATA(fanotify) *self,
118			      uint64_t *mask)
119{
120	struct fanotify_event_metadata *meta;
121	struct fanotify_event_info_mnt *mnt;
122	unsigned int thislen;
123
124	if (!self->rem) {
125		ssize_t len;
126		int i;
127
128		for (i = NUM_FAN_FDS - 1; i >= 0; i--) {
129			len = read(self->fan_fd[i], self->buf,
130				   sizeof(self->buf));
131			if (i > 0) {
132				// Groups 1,2 should get EAGAIN
133				ASSERT_EQ(len, -1);
134				ASSERT_EQ(errno, EAGAIN);
135			} else {
136				// Group 0 should get events
137				ASSERT_GT(len, 0);
138			}
139		}
140
141		self->rem = len;
142		self->next = (void *) self->buf;
143	}
144
145	meta = self->next;
146	ASSERT_TRUE(FAN_EVENT_OK(meta, self->rem));
147
148	thislen = meta->event_len;
149	self->rem -= thislen;
150	self->next += thislen;
151
152	*mask = meta->mask;
153	thislen -= sizeof(*meta);
154
155	mnt = ((void *) meta) + meta->event_len - thislen;
156
157	ASSERT_EQ(thislen, sizeof(*mnt));
158
159	return mnt->mnt_id;
160}
161
162static void expect_notify_n(struct __test_metadata *const _metadata,
163				 FIXTURE_DATA(fanotify) *self,
164				 unsigned int n, uint64_t mask[], uint64_t mnts[])
165{
166	unsigned int i;
167
168	for (i = 0; i < n; i++)
169		mnts[i] = expect_notify(_metadata, self, &mask[i]);
170}
171
172static uint64_t expect_notify_mask(struct __test_metadata *const _metadata,
173				   FIXTURE_DATA(fanotify) *self,
174				   uint64_t expect_mask)
175{
176	uint64_t mntid, mask;
177
178	mntid = expect_notify(_metadata, self, &mask);
179	ASSERT_EQ(expect_mask, mask);
180
181	return mntid;
182}
183
184
185static void expect_notify_mask_n(struct __test_metadata *const _metadata,
186				 FIXTURE_DATA(fanotify) *self,
187				 uint64_t mask, unsigned int n, uint64_t mnts[])
188{
189	unsigned int i;
190
191	for (i = 0; i < n; i++)
192		mnts[i] = expect_notify_mask(_metadata, self, mask);
193}
194
195static void verify_mount_ids(struct __test_metadata *const _metadata,
196			     const uint64_t list1[], const uint64_t list2[],
197			     size_t num)
198{
199	unsigned int i, j;
200
201	// Check that neither list has any duplicates
202	for (i = 0; i < num; i++) {
203		for (j = 0; j < num; j++) {
204			if (i != j) {
205				ASSERT_NE(list1[i], list1[j]);
206				ASSERT_NE(list2[i], list2[j]);
207			}
208		}
209	}
210	// Check that all list1 memebers can be found in list2. Together with
211	// the above it means that the list1 and list2 represent the same sets.
212	for (i = 0; i < num; i++) {
213		for (j = 0; j < num; j++) {
214			if (list1[i] == list2[j])
215				break;
216		}
217		ASSERT_NE(j, num);
218	}
219}
220
221static void check_mounted(struct __test_metadata *const _metadata,
222			  const uint64_t mnts[], size_t num)
223{
224	ssize_t ret;
225	uint64_t *list;
226
227	list = malloc((num + 1) * sizeof(list[0]));
228	ASSERT_NE(list, NULL);
229
230	ret = listmount(LSMT_ROOT, 0, 0, list, num + 1, 0);
231	ASSERT_EQ(ret, num);
232
233	verify_mount_ids(_metadata, mnts, list, num);
234
235	free(list);
236}
237
238static void setup_mount_tree(struct __test_metadata *const _metadata,
239			    int log2_num)
240{
241	int ret, i;
242
243	ret = mount("", "/", NULL, MS_SHARED, NULL);
244	ASSERT_EQ(ret, 0);
245
246	for (i = 0; i < log2_num; i++) {
247		ret = mount("/", "/", NULL, MS_BIND, NULL);
248		ASSERT_EQ(ret, 0);
249	}
250}
251
252TEST_F(fanotify, bind)
253{
254	int ret;
255	uint64_t mnts[2] = { self->root_id };
256
257	ret = mount("/", "/", NULL, MS_BIND, NULL);
258	ASSERT_EQ(ret, 0);
259
260	mnts[1] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);
261	ASSERT_NE(mnts[0], mnts[1]);
262
263	check_mounted(_metadata, mnts, 2);
264
265	// Cleanup
266	uint64_t detach_id;
267	ret = umount("/");
268	ASSERT_EQ(ret, 0);
269
270	detach_id = expect_notify_mask(_metadata, self, FAN_MNT_DETACH);
271	ASSERT_EQ(detach_id, mnts[1]);
272
273	check_mounted(_metadata, mnts, 1);
274}
275
276TEST_F(fanotify, move)
277{
278	int ret;
279	uint64_t mnts[2] = { self->root_id };
280	uint64_t move_id;
281
282	ret = mount("/", "/a", NULL, MS_BIND, NULL);
283	ASSERT_EQ(ret, 0);
284
285	mnts[1] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);
286	ASSERT_NE(mnts[0], mnts[1]);
287
288	check_mounted(_metadata, mnts, 2);
289
290	ret = move_mount(AT_FDCWD, "/a", AT_FDCWD, "/b", 0);
291	ASSERT_EQ(ret, 0);
292
293	move_id = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH | FAN_MNT_DETACH);
294	ASSERT_EQ(move_id, mnts[1]);
295
296	// Cleanup
297	ret = umount("/b");
298	ASSERT_EQ(ret, 0);
299
300	check_mounted(_metadata, mnts, 1);
301}
302
303TEST_F(fanotify, propagate)
304{
305	const unsigned int log2_num = 4;
306	const unsigned int num = (1 << log2_num);
307	uint64_t mnts[num];
308
309	setup_mount_tree(_metadata, log2_num);
310
311	expect_notify_mask_n(_metadata, self, FAN_MNT_ATTACH, num - 1, mnts + 1);
312
313	mnts[0] = self->root_id;
314	check_mounted(_metadata, mnts, num);
315
316	// Cleanup
317	int ret;
318	uint64_t mnts2[num];
319	ret = umount2("/", MNT_DETACH);
320	ASSERT_EQ(ret, 0);
321
322	ret = mount("", "/", NULL, MS_PRIVATE, NULL);
323	ASSERT_EQ(ret, 0);
324
325	mnts2[0] = self->root_id;
326	expect_notify_mask_n(_metadata, self, FAN_MNT_DETACH, num - 1, mnts2 + 1);
327	verify_mount_ids(_metadata, mnts, mnts2, num);
328
329	check_mounted(_metadata, mnts, 1);
330}
331
332TEST_F(fanotify, fsmount)
333{
334	int ret, fs, mnt;
335	uint64_t mnts[2] = { self->root_id };
336
337	fs = fsopen("tmpfs", 0);
338	ASSERT_GE(fs, 0);
339
340        ret = fsconfig(fs, FSCONFIG_CMD_CREATE, 0, 0, 0);
341	ASSERT_EQ(ret, 0);
342
343        mnt = fsmount(fs, 0, 0);
344	ASSERT_GE(mnt, 0);
345
346        close(fs);
347
348	ret = move_mount(mnt, "", AT_FDCWD, "/a", MOVE_MOUNT_F_EMPTY_PATH);
349	ASSERT_EQ(ret, 0);
350
351        close(mnt);
352
353	mnts[1] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);
354	ASSERT_NE(mnts[0], mnts[1]);
355
356	check_mounted(_metadata, mnts, 2);
357
358	// Cleanup
359	uint64_t detach_id;
360	ret = umount("/a");
361	ASSERT_EQ(ret, 0);
362
363	detach_id = expect_notify_mask(_metadata, self, FAN_MNT_DETACH);
364	ASSERT_EQ(detach_id, mnts[1]);
365
366	check_mounted(_metadata, mnts, 1);
367}
368
369TEST_F(fanotify, reparent)
370{
371	uint64_t mnts[6] = { self->root_id };
372	uint64_t dmnts[3];
373	uint64_t masks[3];
374	unsigned int i;
375	int ret;
376
377	// Create setup with a[1] -> b[2] propagation
378	ret = mount("/", "/a", NULL, MS_BIND, NULL);
379	ASSERT_EQ(ret, 0);
380
381	ret = mount("", "/a", NULL, MS_SHARED, NULL);
382	ASSERT_EQ(ret, 0);
383
384	ret = mount("/a", "/b", NULL, MS_BIND, NULL);
385	ASSERT_EQ(ret, 0);
386
387	ret = mount("", "/b", NULL, MS_SLAVE, NULL);
388	ASSERT_EQ(ret, 0);
389
390	expect_notify_mask_n(_metadata, self, FAN_MNT_ATTACH, 2, mnts + 1);
391
392	check_mounted(_metadata, mnts, 3);
393
394	// Mount on a[3], which is propagated to b[4]
395	ret = mount("/", "/a", NULL, MS_BIND, NULL);
396	ASSERT_EQ(ret, 0);
397
398	expect_notify_mask_n(_metadata, self, FAN_MNT_ATTACH, 2, mnts + 3);
399
400	check_mounted(_metadata, mnts, 5);
401
402	// Mount on b[5], not propagated
403	ret = mount("/", "/b", NULL, MS_BIND, NULL);
404	ASSERT_EQ(ret, 0);
405
406	mnts[5] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);
407
408	check_mounted(_metadata, mnts, 6);
409
410	// Umount a[3], which is propagated to b[4], but not b[5]
411	// This will result in b[5] "falling" on b[2]
412	ret = umount("/a");
413	ASSERT_EQ(ret, 0);
414
415	expect_notify_n(_metadata, self, 3, masks, dmnts);
416	verify_mount_ids(_metadata, mnts + 3, dmnts, 3);
417
418	for (i = 0; i < 3; i++) {
419		if (dmnts[i] == mnts[5]) {
420			ASSERT_EQ(masks[i], FAN_MNT_ATTACH | FAN_MNT_DETACH);
421		} else {
422			ASSERT_EQ(masks[i], FAN_MNT_DETACH);
423		}
424	}
425
426	mnts[3] = mnts[5];
427	check_mounted(_metadata, mnts, 4);
428
429	// Cleanup
430	ret = umount("/b");
431	ASSERT_EQ(ret, 0);
432
433	ret = umount("/a");
434	ASSERT_EQ(ret, 0);
435
436	ret = umount("/b");
437	ASSERT_EQ(ret, 0);
438
439	expect_notify_mask_n(_metadata, self, FAN_MNT_DETACH, 3, dmnts);
440	verify_mount_ids(_metadata, mnts + 1, dmnts, 3);
441
442	check_mounted(_metadata, mnts, 1);
443}
444
445TEST_F(fanotify, rmdir)
446{
447	uint64_t mnts[3] = { self->root_id };
448	int ret;
449
450	ret = mount("/", "/a", NULL, MS_BIND, NULL);
451	ASSERT_EQ(ret, 0);
452
453	ret = mount("/", "/a/b", NULL, MS_BIND, NULL);
454	ASSERT_EQ(ret, 0);
455
456	expect_notify_mask_n(_metadata, self, FAN_MNT_ATTACH, 2, mnts + 1);
457
458	check_mounted(_metadata, mnts, 3);
459
460	ret = chdir("/a");
461	ASSERT_EQ(ret, 0);
462
463	ret = fork();
464	ASSERT_GE(ret, 0);
465
466	if (ret == 0) {
467		chdir("/");
468		unshare(CLONE_NEWNS);
469		mount("", "/", NULL, MS_REC|MS_PRIVATE, NULL);
470		umount2("/a", MNT_DETACH);
471		// This triggers a detach in the other namespace
472		rmdir("/a");
473		exit(0);
474	}
475	wait(NULL);
476
477	expect_notify_mask_n(_metadata, self, FAN_MNT_DETACH, 2, mnts + 1);
478	check_mounted(_metadata, mnts, 1);
479
480	// Cleanup
481	ret = chdir("/");
482	ASSERT_EQ(ret, 0);
483}
484
485TEST_F(fanotify, pivot_root)
486{
487	uint64_t mnts[3] = { self->root_id };
488	uint64_t mnts2[3];
489	int ret;
490
491	ret = mount("tmpfs", "/a", "tmpfs", 0, NULL);
492	ASSERT_EQ(ret, 0);
493
494	mnts[2] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);
495
496	ret = mkdir("/a/new", 0700);
497	ASSERT_EQ(ret, 0);
498
499	ret = mkdir("/a/old", 0700);
500	ASSERT_EQ(ret, 0);
501
502	ret = mount("/a", "/a/new", NULL, MS_BIND, NULL);
503	ASSERT_EQ(ret, 0);
504
505	mnts[1] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);
506	check_mounted(_metadata, mnts, 3);
507
508	ret = syscall(SYS_pivot_root, "/a/new", "/a/new/old");
509	ASSERT_EQ(ret, 0);
510
511	expect_notify_mask_n(_metadata, self, FAN_MNT_ATTACH | FAN_MNT_DETACH, 2, mnts2);
512	verify_mount_ids(_metadata, mnts, mnts2, 2);
513	check_mounted(_metadata, mnts, 3);
514
515	// Cleanup
516	ret = syscall(SYS_pivot_root, "/old", "/old/a/new");
517	ASSERT_EQ(ret, 0);
518
519	ret = umount("/a/new");
520	ASSERT_EQ(ret, 0);
521
522	ret = umount("/a");
523	ASSERT_EQ(ret, 0);
524
525	check_mounted(_metadata, mnts, 1);
526}
527
528TEST_HARNESS_MAIN
~/ Read-only file preview.