xref: /linux/tools/testing/selftests/filesystems/statmount/statmount_test.c (revision ab0f4cedc3554f921691ce5b63d59e258154e799)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 
3 #define _GNU_SOURCE
4 
5 #include <assert.h>
6 #include <stddef.h>
7 #include <stdint.h>
8 #include <sched.h>
9 #include <fcntl.h>
10 #include <sys/param.h>
11 #include <sys/mount.h>
12 #include <sys/stat.h>
13 #include <sys/statfs.h>
14 #include <linux/mount.h>
15 #include <linux/stat.h>
16 #include <asm/unistd.h>
17 
18 #include "../../kselftest.h"
19 
20 static const char *const known_fs[] = {
21 	"9p", "adfs", "affs", "afs", "aio", "anon_inodefs", "apparmorfs",
22 	"autofs", "bcachefs", "bdev", "befs", "bfs", "binder", "binfmt_misc",
23 	"bpf", "btrfs", "btrfs_test_fs", "ceph", "cgroup", "cgroup2", "cifs",
24 	"coda", "configfs", "cpuset", "cramfs", "cxl", "dax", "debugfs",
25 	"devpts", "devtmpfs", "dmabuf", "drm", "ecryptfs", "efivarfs", "efs",
26 	"erofs", "exfat", "ext2", "ext3", "ext4", "f2fs", "functionfs",
27 	"fuse", "fuseblk", "fusectl", "gadgetfs", "gfs2", "gfs2meta", "hfs",
28 	"hfsplus", "hostfs", "hpfs", "hugetlbfs", "ibmasmfs", "iomem",
29 	"ipathfs", "iso9660", "jffs2", "jfs", "minix", "mqueue", "msdos",
30 	"nfs", "nfs4", "nfsd", "nilfs2", "nsfs", "ntfs", "ntfs3", "ocfs2",
31 	"ocfs2_dlmfs", "ocxlflash", "omfs", "openpromfs", "overlay", "pipefs",
32 	"proc", "pstore", "pvfs2", "qnx4", "qnx6", "ramfs", "reiserfs",
33 	"resctrl", "romfs", "rootfs", "rpc_pipefs", "s390_hypfs", "secretmem",
34 	"securityfs", "selinuxfs", "smackfs", "smb3", "sockfs", "spufs",
35 	"squashfs", "sysfs", "sysv", "tmpfs", "tracefs", "ubifs", "udf",
36 	"ufs", "v7", "vboxsf", "vfat", "virtiofs", "vxfs", "xenfs", "xfs",
37 	"zonefs", NULL };
38 
39 static int statmount(uint64_t mnt_id, uint64_t mask, struct statmount *buf,
40 		     size_t bufsize, unsigned int flags)
41 {
42 	struct mnt_id_req req = {
43 		.size = MNT_ID_REQ_SIZE_VER0,
44 		.mnt_id = mnt_id,
45 		.param = mask,
46 	};
47 
48 	return syscall(__NR_statmount, &req, buf, bufsize, flags);
49 }
50 
51 static struct statmount *statmount_alloc(uint64_t mnt_id, uint64_t mask, unsigned int flags)
52 {
53 	size_t bufsize = 1 << 15;
54 	struct statmount *buf = NULL, *tmp = alloca(bufsize);
55 	int tofree = 0;
56 	int ret;
57 
58 	for (;;) {
59 		ret = statmount(mnt_id, mask, tmp, bufsize, flags);
60 		if (ret != -1)
61 			break;
62 		if (tofree)
63 			free(tmp);
64 		if (errno != EOVERFLOW)
65 			return NULL;
66 		bufsize <<= 1;
67 		tofree = 1;
68 		tmp = malloc(bufsize);
69 		if (!tmp)
70 			return NULL;
71 	}
72 	buf = malloc(tmp->size);
73 	if (buf)
74 		memcpy(buf, tmp, tmp->size);
75 	if (tofree)
76 		free(tmp);
77 
78 	return buf;
79 }
80 
81 static void write_file(const char *path, const char *val)
82 {
83 	int fd = open(path, O_WRONLY);
84 	size_t len = strlen(val);
85 	int ret;
86 
87 	if (fd == -1)
88 		ksft_exit_fail_msg("opening %s for write: %s\n", path, strerror(errno));
89 
90 	ret = write(fd, val, len);
91 	if (ret == -1)
92 		ksft_exit_fail_msg("writing to %s: %s\n", path, strerror(errno));
93 	if (ret != len)
94 		ksft_exit_fail_msg("short write to %s\n", path);
95 
96 	ret = close(fd);
97 	if (ret == -1)
98 		ksft_exit_fail_msg("closing %s\n", path);
99 }
100 
101 static uint64_t get_mnt_id(const char *name, const char *path, uint64_t mask)
102 {
103 	struct statx sx;
104 	int ret;
105 
106 	ret = statx(AT_FDCWD, path, 0, mask, &sx);
107 	if (ret == -1)
108 		ksft_exit_fail_msg("retrieving %s mount ID for %s: %s\n",
109 				   mask & STATX_MNT_ID_UNIQUE ? "unique" : "old",
110 				   name, strerror(errno));
111 	if (!(sx.stx_mask & mask))
112 		ksft_exit_fail_msg("no %s mount ID available for %s\n",
113 				   mask & STATX_MNT_ID_UNIQUE ? "unique" : "old",
114 				   name);
115 
116 	return sx.stx_mnt_id;
117 }
118 
119 
120 static char root_mntpoint[] = "/tmp/statmount_test_root.XXXXXX";
121 static int orig_root;
122 static uint64_t root_id, parent_id;
123 static uint32_t old_root_id, old_parent_id;
124 
125 
126 static void cleanup_namespace(void)
127 {
128 	int ret;
129 
130 	ret = fchdir(orig_root);
131 	if (ret == -1)
132 		ksft_perror("fchdir to original root");
133 
134 	ret = chroot(".");
135 	if (ret == -1)
136 		ksft_perror("chroot to original root");
137 
138 	umount2(root_mntpoint, MNT_DETACH);
139 	rmdir(root_mntpoint);
140 }
141 
142 static void setup_namespace(void)
143 {
144 	int ret;
145 	char buf[32];
146 	uid_t uid = getuid();
147 	gid_t gid = getgid();
148 
149 	ret = unshare(CLONE_NEWNS|CLONE_NEWUSER);
150 	if (ret == -1)
151 		ksft_exit_fail_msg("unsharing mountns and userns: %s\n",
152 				   strerror(errno));
153 
154 	sprintf(buf, "0 %d 1", uid);
155 	write_file("/proc/self/uid_map", buf);
156 	write_file("/proc/self/setgroups", "deny");
157 	sprintf(buf, "0 %d 1", gid);
158 	write_file("/proc/self/gid_map", buf);
159 
160 	ret = mount("", "/", NULL, MS_REC|MS_PRIVATE, NULL);
161 	if (ret == -1)
162 		ksft_exit_fail_msg("making mount tree private: %s\n",
163 				   strerror(errno));
164 
165 	if (!mkdtemp(root_mntpoint))
166 		ksft_exit_fail_msg("creating temporary directory %s: %s\n",
167 				   root_mntpoint, strerror(errno));
168 
169 	old_parent_id = get_mnt_id("parent", root_mntpoint, STATX_MNT_ID);
170 	parent_id = get_mnt_id("parent", root_mntpoint, STATX_MNT_ID_UNIQUE);
171 
172 	orig_root = open("/", O_PATH);
173 	if (orig_root == -1)
174 		ksft_exit_fail_msg("opening root directory: %s",
175 				   strerror(errno));
176 
177 	atexit(cleanup_namespace);
178 
179 	ret = mount(root_mntpoint, root_mntpoint, NULL, MS_BIND, NULL);
180 	if (ret == -1)
181 		ksft_exit_fail_msg("mounting temp root %s: %s\n",
182 				   root_mntpoint, strerror(errno));
183 
184 	ret = chroot(root_mntpoint);
185 	if (ret == -1)
186 		ksft_exit_fail_msg("chroot to temp root %s: %s\n",
187 				   root_mntpoint, strerror(errno));
188 
189 	ret = chdir("/");
190 	if (ret == -1)
191 		ksft_exit_fail_msg("chdir to root: %s\n", strerror(errno));
192 
193 	old_root_id = get_mnt_id("root", "/", STATX_MNT_ID);
194 	root_id = get_mnt_id("root", "/", STATX_MNT_ID_UNIQUE);
195 }
196 
197 static int setup_mount_tree(int log2_num)
198 {
199 	int ret, i;
200 
201 	ret = mount("", "/", NULL, MS_REC|MS_SHARED, NULL);
202 	if (ret == -1) {
203 		ksft_test_result_fail("making mount tree shared: %s\n",
204 				   strerror(errno));
205 		return -1;
206 	}
207 
208 	for (i = 0; i < log2_num; i++) {
209 		ret = mount("/", "/", NULL, MS_BIND, NULL);
210 		if (ret == -1) {
211 			ksft_test_result_fail("mounting submount %s: %s\n",
212 					      root_mntpoint, strerror(errno));
213 			return -1;
214 		}
215 	}
216 	return 0;
217 }
218 
219 static ssize_t listmount(uint64_t mnt_id, uint64_t last_mnt_id,
220 			 uint64_t list[], size_t num, unsigned int flags)
221 {
222 	struct mnt_id_req req = {
223 		.size = MNT_ID_REQ_SIZE_VER0,
224 		.mnt_id = mnt_id,
225 		.param = last_mnt_id,
226 	};
227 
228 	return syscall(__NR_listmount, &req, list, num, flags);
229 }
230 
231 static void test_listmount_empty_root(void)
232 {
233 	ssize_t res;
234 	const unsigned int size = 32;
235 	uint64_t list[size];
236 
237 	res = listmount(LSMT_ROOT, 0, list, size, 0);
238 	if (res == -1) {
239 		ksft_test_result_fail("listmount: %s\n", strerror(errno));
240 		return;
241 	}
242 	if (res != 1) {
243 		ksft_test_result_fail("listmount result is %zi != 1\n", res);
244 		return;
245 	}
246 
247 	if (list[0] != root_id) {
248 		ksft_test_result_fail("listmount ID doesn't match 0x%llx != 0x%llx\n",
249 				      (unsigned long long) list[0],
250 				      (unsigned long long) root_id);
251 		return;
252 	}
253 
254 	ksft_test_result_pass("listmount empty root\n");
255 }
256 
257 static void test_statmount_zero_mask(void)
258 {
259 	struct statmount sm;
260 	int ret;
261 
262 	ret = statmount(root_id, 0, &sm, sizeof(sm), 0);
263 	if (ret == -1) {
264 		ksft_test_result_fail("statmount zero mask: %s\n",
265 				      strerror(errno));
266 		return;
267 	}
268 	if (sm.size != sizeof(sm)) {
269 		ksft_test_result_fail("unexpected size: %u != %u\n",
270 				      sm.size, (uint32_t) sizeof(sm));
271 		return;
272 	}
273 	if (sm.mask != 0) {
274 		ksft_test_result_fail("unexpected mask: 0x%llx != 0x0\n",
275 				      (unsigned long long) sm.mask);
276 		return;
277 	}
278 
279 	ksft_test_result_pass("statmount zero mask\n");
280 }
281 
282 static void test_statmount_mnt_basic(void)
283 {
284 	struct statmount sm;
285 	int ret;
286 	uint64_t mask = STATMOUNT_MNT_BASIC;
287 
288 	ret = statmount(root_id, mask, &sm, sizeof(sm), 0);
289 	if (ret == -1) {
290 		ksft_test_result_fail("statmount mnt basic: %s\n",
291 				      strerror(errno));
292 		return;
293 	}
294 	if (sm.size != sizeof(sm)) {
295 		ksft_test_result_fail("unexpected size: %u != %u\n",
296 				      sm.size, (uint32_t) sizeof(sm));
297 		return;
298 	}
299 	if (sm.mask != mask) {
300 		ksft_test_result_skip("statmount mnt basic unavailable\n");
301 		return;
302 	}
303 
304 	if (sm.mnt_id != root_id) {
305 		ksft_test_result_fail("unexpected root ID: 0x%llx != 0x%llx\n",
306 				      (unsigned long long) sm.mnt_id,
307 				      (unsigned long long) root_id);
308 		return;
309 	}
310 
311 	if (sm.mnt_id_old != old_root_id) {
312 		ksft_test_result_fail("unexpected old root ID: %u != %u\n",
313 				      sm.mnt_id_old, old_root_id);
314 		return;
315 	}
316 
317 	if (sm.mnt_parent_id != parent_id) {
318 		ksft_test_result_fail("unexpected parent ID: 0x%llx != 0x%llx\n",
319 				      (unsigned long long) sm.mnt_parent_id,
320 				      (unsigned long long) parent_id);
321 		return;
322 	}
323 
324 	if (sm.mnt_parent_id_old != old_parent_id) {
325 		ksft_test_result_fail("unexpected old parent ID: %u != %u\n",
326 				      sm.mnt_parent_id_old, old_parent_id);
327 		return;
328 	}
329 
330 	if (sm.mnt_propagation != MS_PRIVATE) {
331 		ksft_test_result_fail("unexpected propagation: 0x%llx\n",
332 				      (unsigned long long) sm.mnt_propagation);
333 		return;
334 	}
335 
336 	ksft_test_result_pass("statmount mnt basic\n");
337 }
338 
339 
340 static void test_statmount_sb_basic(void)
341 {
342 	struct statmount sm;
343 	int ret;
344 	uint64_t mask = STATMOUNT_SB_BASIC;
345 	struct statx sx;
346 	struct statfs sf;
347 
348 	ret = statmount(root_id, mask, &sm, sizeof(sm), 0);
349 	if (ret == -1) {
350 		ksft_test_result_fail("statmount sb basic: %s\n",
351 				      strerror(errno));
352 		return;
353 	}
354 	if (sm.size != sizeof(sm)) {
355 		ksft_test_result_fail("unexpected size: %u != %u\n",
356 				      sm.size, (uint32_t) sizeof(sm));
357 		return;
358 	}
359 	if (sm.mask != mask) {
360 		ksft_test_result_skip("statmount sb basic unavailable\n");
361 		return;
362 	}
363 
364 	ret = statx(AT_FDCWD, "/", 0, 0, &sx);
365 	if (ret == -1) {
366 		ksft_test_result_fail("stat root failed: %s\n",
367 				      strerror(errno));
368 		return;
369 	}
370 
371 	if (sm.sb_dev_major != sx.stx_dev_major ||
372 	    sm.sb_dev_minor != sx.stx_dev_minor) {
373 		ksft_test_result_fail("unexpected sb dev %u:%u != %u:%u\n",
374 				      sm.sb_dev_major, sm.sb_dev_minor,
375 				      sx.stx_dev_major, sx.stx_dev_minor);
376 		return;
377 	}
378 
379 	ret = statfs("/", &sf);
380 	if (ret == -1) {
381 		ksft_test_result_fail("statfs root failed: %s\n",
382 				      strerror(errno));
383 		return;
384 	}
385 
386 	if (sm.sb_magic != sf.f_type) {
387 		ksft_test_result_fail("unexpected sb magic: 0x%llx != 0x%lx\n",
388 				      (unsigned long long) sm.sb_magic,
389 				      sf.f_type);
390 		return;
391 	}
392 
393 	ksft_test_result_pass("statmount sb basic\n");
394 }
395 
396 static void test_statmount_mnt_point(void)
397 {
398 	struct statmount *sm;
399 
400 	sm = statmount_alloc(root_id, STATMOUNT_MNT_POINT, 0);
401 	if (!sm) {
402 		ksft_test_result_fail("statmount mount point: %s\n",
403 				      strerror(errno));
404 		return;
405 	}
406 
407 	if (strcmp(sm->str + sm->mnt_point, "/") != 0) {
408 		ksft_test_result_fail("unexpected mount point: '%s' != '/'\n",
409 				      sm->str + sm->mnt_point);
410 		goto out;
411 	}
412 	ksft_test_result_pass("statmount mount point\n");
413 out:
414 	free(sm);
415 }
416 
417 static void test_statmount_mnt_root(void)
418 {
419 	struct statmount *sm;
420 	const char *mnt_root, *last_dir, *last_root;
421 
422 	last_dir = strrchr(root_mntpoint, '/');
423 	assert(last_dir);
424 	last_dir++;
425 
426 	sm = statmount_alloc(root_id, STATMOUNT_MNT_ROOT, 0);
427 	if (!sm) {
428 		ksft_test_result_fail("statmount mount root: %s\n",
429 				      strerror(errno));
430 		return;
431 	}
432 	mnt_root = sm->str + sm->mnt_root;
433 	last_root = strrchr(mnt_root, '/');
434 	if (last_root)
435 		last_root++;
436 	else
437 		last_root = mnt_root;
438 
439 	if (strcmp(last_dir, last_root) != 0) {
440 		ksft_test_result_fail("unexpected mount root last component: '%s' != '%s'\n",
441 				      last_root, last_dir);
442 		goto out;
443 	}
444 	ksft_test_result_pass("statmount mount root\n");
445 out:
446 	free(sm);
447 }
448 
449 static void test_statmount_fs_type(void)
450 {
451 	struct statmount *sm;
452 	const char *fs_type;
453 	const char *const *s;
454 
455 	sm = statmount_alloc(root_id, STATMOUNT_FS_TYPE, 0);
456 	if (!sm) {
457 		ksft_test_result_fail("statmount fs type: %s\n",
458 				      strerror(errno));
459 		return;
460 	}
461 	fs_type = sm->str + sm->fs_type;
462 	for (s = known_fs; s != NULL; s++) {
463 		if (strcmp(fs_type, *s) == 0)
464 			break;
465 	}
466 	if (!s)
467 		ksft_print_msg("unknown filesystem type: %s\n", fs_type);
468 
469 	ksft_test_result_pass("statmount fs type\n");
470 	free(sm);
471 }
472 
473 static void test_statmount_string(uint64_t mask, size_t off, const char *name)
474 {
475 	struct statmount *sm;
476 	size_t len, shortsize, exactsize;
477 	uint32_t start, i;
478 	int ret;
479 
480 	sm = statmount_alloc(root_id, mask, 0);
481 	if (!sm) {
482 		ksft_test_result_fail("statmount %s: %s\n", name,
483 				      strerror(errno));
484 		goto out;
485 	}
486 	if (sm->size < sizeof(*sm)) {
487 		ksft_test_result_fail("unexpected size: %u < %u\n",
488 				      sm->size, (uint32_t) sizeof(*sm));
489 		goto out;
490 	}
491 	if (sm->mask != mask) {
492 		ksft_test_result_skip("statmount %s unavailable\n", name);
493 		goto out;
494 	}
495 	len = sm->size - sizeof(*sm);
496 	start = ((uint32_t *) sm)[off];
497 
498 	for (i = start;; i++) {
499 		if (i >= len) {
500 			ksft_test_result_fail("string out of bounds\n");
501 			goto out;
502 		}
503 		if (!sm->str[i])
504 			break;
505 	}
506 	exactsize = sm->size;
507 	shortsize = sizeof(*sm) + i;
508 
509 	ret = statmount(root_id, mask, sm, exactsize, 0);
510 	if (ret == -1) {
511 		ksft_test_result_fail("statmount exact size: %s\n",
512 				      strerror(errno));
513 		goto out;
514 	}
515 	errno = 0;
516 	ret = statmount(root_id, mask, sm, shortsize, 0);
517 	if (ret != -1 || errno != EOVERFLOW) {
518 		ksft_test_result_fail("should have failed with EOVERFLOW: %s\n",
519 				      strerror(errno));
520 		goto out;
521 	}
522 
523 	ksft_test_result_pass("statmount string %s\n", name);
524 out:
525 	free(sm);
526 }
527 
528 static void test_listmount_tree(void)
529 {
530 	ssize_t res;
531 	const unsigned int log2_num = 4;
532 	const unsigned int step = 3;
533 	const unsigned int size = (1 << log2_num) + step + 1;
534 	size_t num, expect = 1 << log2_num;
535 	uint64_t list[size];
536 	uint64_t list2[size];
537 	size_t i;
538 
539 
540 	res = setup_mount_tree(log2_num);
541 	if (res == -1)
542 		return;
543 
544 	num = res = listmount(LSMT_ROOT, 0, list, size, 0);
545 	if (res == -1) {
546 		ksft_test_result_fail("listmount: %s\n", strerror(errno));
547 		return;
548 	}
549 	if (num != expect) {
550 		ksft_test_result_fail("listmount result is %zi != %zi\n",
551 				      res, expect);
552 		return;
553 	}
554 
555 	for (i = 0; i < size - step;) {
556 		res = listmount(LSMT_ROOT, i ? list2[i - 1] : 0, list2 + i, step, 0);
557 		if (res == -1)
558 			ksft_test_result_fail("short listmount: %s\n",
559 					      strerror(errno));
560 		i += res;
561 		if (res < step)
562 			break;
563 	}
564 	if (i != num) {
565 		ksft_test_result_fail("different number of entries: %zu != %zu\n",
566 				      i, num);
567 		return;
568 	}
569 	for (i = 0; i < num; i++) {
570 		if (list2[i] != list[i]) {
571 			ksft_test_result_fail("different value for entry %zu: 0x%llx != 0x%llx\n",
572 					      i,
573 					      (unsigned long long) list2[i],
574 					      (unsigned long long) list[i]);
575 		}
576 	}
577 
578 	ksft_test_result_pass("listmount tree\n");
579 }
580 
581 #define str_off(memb) (offsetof(struct statmount, memb) / sizeof(uint32_t))
582 
583 int main(void)
584 {
585 	int ret;
586 	uint64_t all_mask = STATMOUNT_SB_BASIC | STATMOUNT_MNT_BASIC |
587 		STATMOUNT_PROPAGATE_FROM | STATMOUNT_MNT_ROOT |
588 		STATMOUNT_MNT_POINT | STATMOUNT_FS_TYPE;
589 
590 	ksft_print_header();
591 
592 	ret = statmount(0, 0, NULL, 0, 0);
593 	assert(ret == -1);
594 	if (errno == ENOSYS)
595 		ksft_exit_skip("statmount() syscall not supported\n");
596 
597 	setup_namespace();
598 
599 	ksft_set_plan(14);
600 	test_listmount_empty_root();
601 	test_statmount_zero_mask();
602 	test_statmount_mnt_basic();
603 	test_statmount_sb_basic();
604 	test_statmount_mnt_root();
605 	test_statmount_mnt_point();
606 	test_statmount_fs_type();
607 	test_statmount_string(STATMOUNT_MNT_ROOT, str_off(mnt_root), "mount root");
608 	test_statmount_string(STATMOUNT_MNT_POINT, str_off(mnt_point), "mount point");
609 	test_statmount_string(STATMOUNT_FS_TYPE, str_off(fs_type), "fs type");
610 	test_statmount_string(all_mask, str_off(mnt_root), "mount root & all");
611 	test_statmount_string(all_mask, str_off(mnt_point), "mount point & all");
612 	test_statmount_string(all_mask, str_off(fs_type), "fs type & all");
613 
614 	test_listmount_tree();
615 
616 
617 	if (ksft_get_fail_cnt() + ksft_get_error_cnt() > 0)
618 		ksft_exit_fail();
619 	else
620 		ksft_exit_pass();
621 }
622