xref: /linux/tools/testing/selftests/filesystems/statmount/statmount_test.c (revision e4ada0bf878ad3d1f348e2fe544ffad25ed8be2b)
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 <sched.h>
8 #include <fcntl.h>
9 #include <sys/param.h>
10 #include <sys/mman.h>
11 #include <sys/mount.h>
12 #include <sys/stat.h>
13 #include <sys/statfs.h>
14 #include <linux/stat.h>
15 
16 #include "statmount.h"
17 #include "kselftest.h"
18 
19 static const char *const known_fs[] = {
20 	"9p", "adfs", "affs", "afs", "aio", "anon_inodefs", "apparmorfs",
21 	"autofs", "bcachefs", "bdev", "befs", "binder", "binfmt_misc",
22 	"bpf", "btrfs", "btrfs_test_fs", "ceph", "cgroup", "cgroup2", "cifs",
23 	"coda", "configfs", "cpuset", "cramfs", "cxl", "dax", "debugfs",
24 	"devpts", "devtmpfs", "dmabuf", "drm", "ecryptfs", "efivarfs", "efs",
25 	"erofs", "exfat", "ext2", "ext3", "ext4", "f2fs", "functionfs",
26 	"fuse", "fuseblk", "fusectl", "gadgetfs", "gfs2", "gfs2meta", "hfs",
27 	"hfsplus", "hostfs", "hpfs", "hugetlbfs", "ibmasmfs", "iomem",
28 	"ipathfs", "iso9660", "jffs2", "jfs", "minix", "mqueue", "msdos",
29 	"nfs", "nfs4", "nfsd", "nilfs2", "nsfs", "ntfs", "ntfs3", "ocfs2",
30 	"ocfs2_dlmfs", "omfs", "openpromfs", "overlay", "pipefs", "proc",
31 	"pstore", "pvfs2", "qnx4", "qnx6", "ramfs", "resctrl", "romfs",
32 	"rootfs", "rpc_pipefs", "s390_hypfs", "secretmem", "securityfs",
33 	"selinuxfs", "smackfs", "smb3", "sockfs", "spufs", "squashfs", "sysfs",
34 	"sysv", "tmpfs", "tracefs", "ubifs", "udf", "ufs", "v7", "vboxsf",
35 	"vfat", "virtiofs", "vxfs", "xenfs", "xfs", "zonefs", NULL };
36 
37 static void write_file(const char *path, const char *val)
38 {
39 	int fd = open(path, O_WRONLY);
40 	size_t len = strlen(val);
41 	int ret;
42 
43 	if (fd == -1)
44 		ksft_exit_fail_msg("opening %s for write: %s\n", path, strerror(errno));
45 
46 	ret = write(fd, val, len);
47 	if (ret == -1)
48 		ksft_exit_fail_msg("writing to %s: %s\n", path, strerror(errno));
49 	if (ret != len)
50 		ksft_exit_fail_msg("short write to %s\n", path);
51 
52 	ret = close(fd);
53 	if (ret == -1)
54 		ksft_exit_fail_msg("closing %s\n", path);
55 }
56 
57 static uint64_t get_mnt_id(const char *name, const char *path, uint64_t mask)
58 {
59 	struct statx sx;
60 	int ret;
61 
62 	ret = statx(AT_FDCWD, path, 0, mask, &sx);
63 	if (ret == -1)
64 		ksft_exit_fail_msg("retrieving %s mount ID for %s: %s\n",
65 				   mask & STATX_MNT_ID_UNIQUE ? "unique" : "old",
66 				   name, strerror(errno));
67 	if (!(sx.stx_mask & mask))
68 		ksft_exit_fail_msg("no %s mount ID available for %s\n",
69 				   mask & STATX_MNT_ID_UNIQUE ? "unique" : "old",
70 				   name);
71 
72 	return sx.stx_mnt_id;
73 }
74 
75 
76 static char root_mntpoint[] = "/tmp/statmount_test_root.XXXXXX";
77 static int orig_root;
78 static uint64_t root_id, parent_id;
79 static uint32_t old_root_id, old_parent_id;
80 static FILE *f_mountinfo;
81 
82 static void cleanup_namespace(void)
83 {
84 	int ret;
85 
86 	if (f_mountinfo)
87 		fclose(f_mountinfo);
88 
89 	ret = fchdir(orig_root);
90 	if (ret == -1)
91 		ksft_perror("fchdir to original root");
92 
93 	ret = chroot(".");
94 	if (ret == -1)
95 		ksft_perror("chroot to original root");
96 
97 	umount2(root_mntpoint, MNT_DETACH);
98 	rmdir(root_mntpoint);
99 }
100 
101 static void setup_namespace(void)
102 {
103 	int ret;
104 	char buf[32];
105 	uid_t uid = getuid();
106 	gid_t gid = getgid();
107 
108 	ret = unshare(CLONE_NEWNS|CLONE_NEWUSER|CLONE_NEWPID);
109 	if (ret == -1)
110 		ksft_exit_fail_msg("unsharing mountns and userns: %s\n",
111 				   strerror(errno));
112 
113 	sprintf(buf, "0 %d 1", uid);
114 	write_file("/proc/self/uid_map", buf);
115 	write_file("/proc/self/setgroups", "deny");
116 	sprintf(buf, "0 %d 1", gid);
117 	write_file("/proc/self/gid_map", buf);
118 
119 	f_mountinfo = fopen("/proc/self/mountinfo", "re");
120 	if (!f_mountinfo)
121 		ksft_exit_fail_msg("failed to open mountinfo: %s\n",
122 				   strerror(errno));
123 
124 	ret = mount("", "/", NULL, MS_REC|MS_PRIVATE, NULL);
125 	if (ret == -1)
126 		ksft_exit_fail_msg("making mount tree private: %s\n",
127 				   strerror(errno));
128 
129 	if (!mkdtemp(root_mntpoint))
130 		ksft_exit_fail_msg("creating temporary directory %s: %s\n",
131 				   root_mntpoint, strerror(errno));
132 
133 	old_parent_id = get_mnt_id("parent", root_mntpoint, STATX_MNT_ID);
134 	parent_id = get_mnt_id("parent", root_mntpoint, STATX_MNT_ID_UNIQUE);
135 
136 	orig_root = open("/", O_PATH);
137 	if (orig_root == -1)
138 		ksft_exit_fail_msg("opening root directory: %s",
139 				   strerror(errno));
140 
141 	atexit(cleanup_namespace);
142 
143 	ret = mount(root_mntpoint, root_mntpoint, NULL, MS_BIND, NULL);
144 	if (ret == -1)
145 		ksft_exit_fail_msg("mounting temp root %s: %s\n",
146 				   root_mntpoint, strerror(errno));
147 
148 	ret = chroot(root_mntpoint);
149 	if (ret == -1)
150 		ksft_exit_fail_msg("chroot to temp root %s: %s\n",
151 				   root_mntpoint, strerror(errno));
152 
153 	ret = chdir("/");
154 	if (ret == -1)
155 		ksft_exit_fail_msg("chdir to root: %s\n", strerror(errno));
156 
157 	old_root_id = get_mnt_id("root", "/", STATX_MNT_ID);
158 	root_id = get_mnt_id("root", "/", STATX_MNT_ID_UNIQUE);
159 }
160 
161 static int setup_mount_tree(int log2_num)
162 {
163 	int ret, i;
164 
165 	ret = mount("", "/", NULL, MS_REC|MS_SHARED, NULL);
166 	if (ret == -1) {
167 		ksft_test_result_fail("making mount tree shared: %s\n",
168 				   strerror(errno));
169 		return -1;
170 	}
171 
172 	for (i = 0; i < log2_num; i++) {
173 		ret = mount("/", "/", NULL, MS_BIND, NULL);
174 		if (ret == -1) {
175 			ksft_test_result_fail("mounting submount %s: %s\n",
176 					      root_mntpoint, strerror(errno));
177 			return -1;
178 		}
179 	}
180 	return 0;
181 }
182 
183 static void test_listmount_empty_root(void)
184 {
185 	ssize_t res;
186 	const unsigned int size = 32;
187 	uint64_t list[size];
188 
189 	res = listmount(LSMT_ROOT, 0, 0, list, size, 0);
190 	if (res == -1) {
191 		ksft_test_result_fail("listmount: %s\n", strerror(errno));
192 		return;
193 	}
194 	if (res != 1) {
195 		ksft_test_result_fail("listmount result is %zi != 1\n", res);
196 		return;
197 	}
198 
199 	if (list[0] != root_id) {
200 		ksft_test_result_fail("listmount ID doesn't match 0x%llx != 0x%llx\n",
201 				      (unsigned long long) list[0],
202 				      (unsigned long long) root_id);
203 		return;
204 	}
205 
206 	ksft_test_result_pass("listmount empty root\n");
207 }
208 
209 static void test_statmount_zero_mask(void)
210 {
211 	struct statmount sm;
212 	int ret;
213 
214 	ret = statmount(root_id, 0, 0, 0, &sm, sizeof(sm), 0);
215 	if (ret == -1) {
216 		ksft_test_result_fail("statmount zero mask: %s\n",
217 				      strerror(errno));
218 		return;
219 	}
220 	if (sm.size != sizeof(sm)) {
221 		ksft_test_result_fail("unexpected size: %u != %u\n",
222 				      sm.size, (uint32_t) sizeof(sm));
223 		return;
224 	}
225 	if (sm.mask != 0) {
226 		ksft_test_result_fail("unexpected mask: 0x%llx != 0x0\n",
227 				      (unsigned long long) sm.mask);
228 		return;
229 	}
230 
231 	ksft_test_result_pass("statmount zero mask\n");
232 }
233 
234 static void test_statmount_mnt_basic(void)
235 {
236 	struct statmount sm;
237 	int ret;
238 	uint64_t mask = STATMOUNT_MNT_BASIC;
239 
240 	ret = statmount(root_id, 0, 0, mask, &sm, sizeof(sm), 0);
241 	if (ret == -1) {
242 		ksft_test_result_fail("statmount mnt basic: %s\n",
243 				      strerror(errno));
244 		return;
245 	}
246 	if (sm.size != sizeof(sm)) {
247 		ksft_test_result_fail("unexpected size: %u != %u\n",
248 				      sm.size, (uint32_t) sizeof(sm));
249 		return;
250 	}
251 	if (sm.mask != mask) {
252 		ksft_test_result_skip("statmount mnt basic unavailable\n");
253 		return;
254 	}
255 
256 	if (sm.mnt_id != root_id) {
257 		ksft_test_result_fail("unexpected root ID: 0x%llx != 0x%llx\n",
258 				      (unsigned long long) sm.mnt_id,
259 				      (unsigned long long) root_id);
260 		return;
261 	}
262 
263 	if (sm.mnt_id_old != old_root_id) {
264 		ksft_test_result_fail("unexpected old root ID: %u != %u\n",
265 				      sm.mnt_id_old, old_root_id);
266 		return;
267 	}
268 
269 	if (sm.mnt_parent_id != parent_id) {
270 		ksft_test_result_fail("unexpected parent ID: 0x%llx != 0x%llx\n",
271 				      (unsigned long long) sm.mnt_parent_id,
272 				      (unsigned long long) parent_id);
273 		return;
274 	}
275 
276 	if (sm.mnt_parent_id_old != old_parent_id) {
277 		ksft_test_result_fail("unexpected old parent ID: %u != %u\n",
278 				      sm.mnt_parent_id_old, old_parent_id);
279 		return;
280 	}
281 
282 	if (sm.mnt_propagation != MS_PRIVATE) {
283 		ksft_test_result_fail("unexpected propagation: 0x%llx\n",
284 				      (unsigned long long) sm.mnt_propagation);
285 		return;
286 	}
287 
288 	ksft_test_result_pass("statmount mnt basic\n");
289 }
290 
291 
292 static void test_statmount_sb_basic(void)
293 {
294 	struct statmount sm;
295 	int ret;
296 	uint64_t mask = STATMOUNT_SB_BASIC;
297 	struct statx sx;
298 	struct statfs sf;
299 
300 	ret = statmount(root_id, 0, 0, mask, &sm, sizeof(sm), 0);
301 	if (ret == -1) {
302 		ksft_test_result_fail("statmount sb basic: %s\n",
303 				      strerror(errno));
304 		return;
305 	}
306 	if (sm.size != sizeof(sm)) {
307 		ksft_test_result_fail("unexpected size: %u != %u\n",
308 				      sm.size, (uint32_t) sizeof(sm));
309 		return;
310 	}
311 	if (sm.mask != mask) {
312 		ksft_test_result_skip("statmount sb basic unavailable\n");
313 		return;
314 	}
315 
316 	ret = statx(AT_FDCWD, "/", 0, 0, &sx);
317 	if (ret == -1) {
318 		ksft_test_result_fail("stat root failed: %s\n",
319 				      strerror(errno));
320 		return;
321 	}
322 
323 	if (sm.sb_dev_major != sx.stx_dev_major ||
324 	    sm.sb_dev_minor != sx.stx_dev_minor) {
325 		ksft_test_result_fail("unexpected sb dev %u:%u != %u:%u\n",
326 				      sm.sb_dev_major, sm.sb_dev_minor,
327 				      sx.stx_dev_major, sx.stx_dev_minor);
328 		return;
329 	}
330 
331 	ret = statfs("/", &sf);
332 	if (ret == -1) {
333 		ksft_test_result_fail("statfs root failed: %s\n",
334 				      strerror(errno));
335 		return;
336 	}
337 
338 	if (sm.sb_magic != sf.f_type) {
339 		ksft_test_result_fail("unexpected sb magic: 0x%llx != 0x%lx\n",
340 				      (unsigned long long) sm.sb_magic,
341 				      sf.f_type);
342 		return;
343 	}
344 
345 	ksft_test_result_pass("statmount sb basic\n");
346 }
347 
348 static void test_statmount_mnt_point(void)
349 {
350 	struct statmount *sm;
351 
352 	sm = statmount_alloc(root_id, 0, STATMOUNT_MNT_POINT, 0);
353 	if (!sm) {
354 		ksft_test_result_fail("statmount mount point: %s\n",
355 				      strerror(errno));
356 		return;
357 	}
358 
359 	if (!(sm->mask & STATMOUNT_MNT_POINT)) {
360 		ksft_test_result_fail("missing STATMOUNT_MNT_POINT in mask\n");
361 		return;
362 	}
363 	if (strcmp(sm->str + sm->mnt_point, "/") != 0) {
364 		ksft_test_result_fail("unexpected mount point: '%s' != '/'\n",
365 				      sm->str + sm->mnt_point);
366 		goto out;
367 	}
368 	ksft_test_result_pass("statmount mount point\n");
369 out:
370 	free(sm);
371 }
372 
373 static void test_statmount_mnt_root(void)
374 {
375 	struct statmount *sm;
376 	const char *mnt_root, *last_dir, *last_root;
377 
378 	last_dir = strrchr(root_mntpoint, '/');
379 	assert(last_dir);
380 	last_dir++;
381 
382 	sm = statmount_alloc(root_id, 0, STATMOUNT_MNT_ROOT, 0);
383 	if (!sm) {
384 		ksft_test_result_fail("statmount mount root: %s\n",
385 				      strerror(errno));
386 		return;
387 	}
388 	if (!(sm->mask & STATMOUNT_MNT_ROOT)) {
389 		ksft_test_result_fail("missing STATMOUNT_MNT_ROOT in mask\n");
390 		return;
391 	}
392 	mnt_root = sm->str + sm->mnt_root;
393 	last_root = strrchr(mnt_root, '/');
394 	if (last_root)
395 		last_root++;
396 	else
397 		last_root = mnt_root;
398 
399 	if (strcmp(last_dir, last_root) != 0) {
400 		ksft_test_result_fail("unexpected mount root last component: '%s' != '%s'\n",
401 				      last_root, last_dir);
402 		goto out;
403 	}
404 	ksft_test_result_pass("statmount mount root\n");
405 out:
406 	free(sm);
407 }
408 
409 static void test_statmount_fs_type(void)
410 {
411 	struct statmount *sm;
412 	const char *fs_type;
413 	const char *const *s;
414 
415 	sm = statmount_alloc(root_id, 0, STATMOUNT_FS_TYPE, 0);
416 	if (!sm) {
417 		ksft_test_result_fail("statmount fs type: %s\n",
418 				      strerror(errno));
419 		return;
420 	}
421 	if (!(sm->mask & STATMOUNT_FS_TYPE)) {
422 		ksft_test_result_fail("missing STATMOUNT_FS_TYPE in mask\n");
423 		return;
424 	}
425 	fs_type = sm->str + sm->fs_type;
426 	for (s = known_fs; s != NULL; s++) {
427 		if (strcmp(fs_type, *s) == 0)
428 			break;
429 	}
430 	if (!s)
431 		ksft_print_msg("unknown filesystem type: %s\n", fs_type);
432 
433 	ksft_test_result_pass("statmount fs type\n");
434 	free(sm);
435 }
436 
437 static void test_statmount_mnt_opts(void)
438 {
439 	struct statmount *sm;
440 	const char *statmount_opts;
441 	char *line = NULL;
442 	size_t len = 0;
443 
444 	sm = statmount_alloc(root_id, 0, STATMOUNT_MNT_BASIC | STATMOUNT_MNT_OPTS,
445 			     0);
446 	if (!sm) {
447 		ksft_test_result_fail("statmount mnt opts: %s\n",
448 				      strerror(errno));
449 		return;
450 	}
451 
452 	if (!(sm->mask & STATMOUNT_MNT_BASIC)) {
453 		ksft_test_result_fail("missing STATMOUNT_MNT_BASIC in mask\n");
454 		return;
455 	}
456 
457 	while (getline(&line, &len, f_mountinfo) != -1) {
458 		int i;
459 		char *p, *p2;
460 		unsigned int old_mnt_id;
461 
462 		old_mnt_id = atoi(line);
463 		if (old_mnt_id != sm->mnt_id_old)
464 			continue;
465 
466 		for (p = line, i = 0; p && i < 5; i++)
467 			p = strchr(p + 1, ' ');
468 		if (!p)
469 			continue;
470 
471 		p2 = strchr(p + 1, ' ');
472 		if (!p2)
473 			continue;
474 		*p2 = '\0';
475 		p = strchr(p2 + 1, '-');
476 		if (!p)
477 			continue;
478 		for (p++, i = 0; p && i < 2; i++)
479 			p = strchr(p + 1, ' ');
480 		if (!p)
481 			continue;
482 		p++;
483 
484 		/* skip generic superblock options */
485 		if (strncmp(p, "ro", 2) == 0)
486 			p += 2;
487 		else if (strncmp(p, "rw", 2) == 0)
488 			p += 2;
489 		if (*p == ',')
490 			p++;
491 		if (strncmp(p, "sync", 4) == 0)
492 			p += 4;
493 		if (*p == ',')
494 			p++;
495 		if (strncmp(p, "dirsync", 7) == 0)
496 			p += 7;
497 		if (*p == ',')
498 			p++;
499 		if (strncmp(p, "lazytime", 8) == 0)
500 			p += 8;
501 		if (*p == ',')
502 			p++;
503 		p2 = strrchr(p, '\n');
504 		if (p2)
505 			*p2 = '\0';
506 
507 		if (sm->mask & STATMOUNT_MNT_OPTS)
508 			statmount_opts = sm->str + sm->mnt_opts;
509 		else
510 			statmount_opts = "";
511 		if (strcmp(statmount_opts, p) != 0)
512 			ksft_test_result_fail(
513 				"unexpected mount options: '%s' != '%s'\n",
514 				statmount_opts, p);
515 		else
516 			ksft_test_result_pass("statmount mount options\n");
517 		free(sm);
518 		free(line);
519 		return;
520 	}
521 
522 	ksft_test_result_fail("didn't find mount entry\n");
523 	free(sm);
524 	free(line);
525 }
526 
527 static void test_statmount_string(uint64_t mask, size_t off, const char *name)
528 {
529 	struct statmount *sm;
530 	size_t len, shortsize, exactsize;
531 	uint32_t start, i;
532 	int ret;
533 
534 	sm = statmount_alloc(root_id, 0, mask, 0);
535 	if (!sm) {
536 		ksft_test_result_fail("statmount %s: %s\n", name,
537 				      strerror(errno));
538 		goto out;
539 	}
540 	if (sm->size < sizeof(*sm)) {
541 		ksft_test_result_fail("unexpected size: %u < %u\n",
542 				      sm->size, (uint32_t) sizeof(*sm));
543 		goto out;
544 	}
545 	if (sm->mask != mask) {
546 		ksft_test_result_skip("statmount %s unavailable\n", name);
547 		goto out;
548 	}
549 	len = sm->size - sizeof(*sm);
550 	start = ((uint32_t *) sm)[off];
551 
552 	for (i = start;; i++) {
553 		if (i >= len) {
554 			ksft_test_result_fail("string out of bounds\n");
555 			goto out;
556 		}
557 		if (!sm->str[i])
558 			break;
559 	}
560 	exactsize = sm->size;
561 	shortsize = sizeof(*sm) + i;
562 
563 	ret = statmount(root_id, 0, 0, mask, sm, exactsize, 0);
564 	if (ret == -1) {
565 		ksft_test_result_fail("statmount exact size: %s\n",
566 				      strerror(errno));
567 		goto out;
568 	}
569 	errno = 0;
570 	ret = statmount(root_id, 0, 0, mask, sm, shortsize, 0);
571 	if (ret != -1 || errno != EOVERFLOW) {
572 		ksft_test_result_fail("should have failed with EOVERFLOW: %s\n",
573 				      strerror(errno));
574 		goto out;
575 	}
576 
577 	ksft_test_result_pass("statmount string %s\n", name);
578 out:
579 	free(sm);
580 }
581 
582 /*
583  * A string request needs a buffer that is larger than the fixed part of
584  * struct statmount. That's true even if the string turns out to be empty
585  * since the empty string itself is placed past the fixed part.
586  */
587 static void test_statmount_string_short_buffer(void)
588 {
589 	const struct {
590 		uint64_t mask;
591 		const char *name;
592 	} reqs[] = {
593 		{ STATMOUNT_FS_TYPE,	"fs type" },
594 		{ STATMOUNT_FS_SUBTYPE,	"fs subtype" },
595 		{ STATMOUNT_MNT_UIDMAP,	"mount uidmap" },
596 	};
597 	const size_t sizes[] = { 0, 1, 100, sizeof(struct statmount) - 1,
598 				 sizeof(struct statmount) };
599 	long page = sysconf(_SC_PAGESIZE);
600 	unsigned char *map;
601 	size_t i, j;
602 	int ret;
603 
604 	map = mmap(NULL, 2 * page, PROT_READ | PROT_WRITE,
605 		   MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
606 	if (map == MAP_FAILED) {
607 		ksft_test_result_fail("mmap: %s\n", strerror(errno));
608 		return;
609 	}
610 
611 	/* Anything written past the end of the buffer faults. */
612 	if (mprotect(map + page, page, PROT_NONE)) {
613 		ksft_test_result_fail("mprotect: %s\n", strerror(errno));
614 		goto out;
615 	}
616 
617 	for (i = 0; i < ARRAY_SIZE(reqs); i++) {
618 		for (j = 0; j < ARRAY_SIZE(sizes); j++) {
619 			/* The buffer ends right at the guard page. */
620 			struct statmount *sm = (void *)(map + page - sizes[j]);
621 
622 			errno = 0;
623 			ret = statmount(root_id, 0, 0, reqs[i].mask, sm,
624 					sizes[j], 0);
625 			if (ret != -1 || errno != EOVERFLOW) {
626 				ksft_test_result_fail("statmount %s with %zu byte buffer: %s\n",
627 						      reqs[i].name, sizes[j],
628 						      ret ? strerror(errno) : "success");
629 				goto out;
630 			}
631 		}
632 	}
633 
634 	ksft_test_result_pass("statmount string short buffer\n");
635 out:
636 	munmap(map, 2 * page);
637 }
638 
639 static void test_listmount_tree(void)
640 {
641 	ssize_t res;
642 	const unsigned int log2_num = 4;
643 	const unsigned int step = 3;
644 	const unsigned int size = (1 << log2_num) + step + 1;
645 	size_t num, expect = 1 << log2_num;
646 	uint64_t list[size];
647 	uint64_t list2[size];
648 	size_t i;
649 
650 
651 	res = setup_mount_tree(log2_num);
652 	if (res == -1)
653 		return;
654 
655 	num = res = listmount(LSMT_ROOT, 0, 0, list, size, 0);
656 	if (res == -1) {
657 		ksft_test_result_fail("listmount: %s\n", strerror(errno));
658 		return;
659 	}
660 	if (num != expect) {
661 		ksft_test_result_fail("listmount result is %zi != %zi\n",
662 				      res, expect);
663 		return;
664 	}
665 
666 	for (i = 0; i < size - step;) {
667 		res = listmount(LSMT_ROOT, 0, i ? list2[i - 1] : 0, list2 + i, step, 0);
668 		if (res == -1)
669 			ksft_test_result_fail("short listmount: %s\n",
670 					      strerror(errno));
671 		i += res;
672 		if (res < step)
673 			break;
674 	}
675 	if (i != num) {
676 		ksft_test_result_fail("different number of entries: %zu != %zu\n",
677 				      i, num);
678 		return;
679 	}
680 	for (i = 0; i < num; i++) {
681 		if (list2[i] != list[i]) {
682 			ksft_test_result_fail("different value for entry %zu: 0x%llx != 0x%llx\n",
683 					      i,
684 					      (unsigned long long) list2[i],
685 					      (unsigned long long) list[i]);
686 		}
687 	}
688 
689 	ksft_test_result_pass("listmount tree\n");
690 }
691 
692 static void test_statmount_by_fd(void)
693 {
694 	struct statmount *sm = NULL;
695 	char tmpdir[] = "/statmount.fd.XXXXXX";
696 	const char root[] = "/test";
697 	char subdir[PATH_MAX], tmproot[PATH_MAX];
698 	int fd;
699 
700 	if (!mkdtemp(tmpdir)) {
701 		ksft_perror("mkdtemp");
702 		return;
703 	}
704 
705 	if (mount("statmount.test", tmpdir, "tmpfs", 0, NULL)) {
706 		ksft_perror("mount");
707 		rmdir(tmpdir);
708 		return;
709 	}
710 
711 	snprintf(subdir, PATH_MAX, "%s%s", tmpdir, root);
712 	snprintf(tmproot, PATH_MAX, "%s/%s", tmpdir, "chroot");
713 
714 	if (mkdir(subdir, 0755)) {
715 		ksft_perror("mkdir");
716 		goto err_tmpdir;
717 	}
718 
719 	if (mount(subdir, subdir, NULL, MS_BIND, 0)) {
720 		ksft_perror("mount");
721 		goto err_subdir;
722 	}
723 
724 	if (mkdir(tmproot, 0755)) {
725 		ksft_perror("mkdir");
726 		goto err_subdir;
727 	}
728 
729 	fd = open(subdir, O_PATH);
730 	if (fd < 0) {
731 		ksft_perror("open");
732 		goto err_tmproot;
733 	}
734 
735 	if (chroot(tmproot)) {
736 		ksft_perror("chroot");
737 		goto err_fd;
738 	}
739 
740 	sm = statmount_alloc_by_fd(fd, STATMOUNT_MNT_ROOT | STATMOUNT_MNT_POINT);
741 	if (!sm) {
742 		ksft_test_result_fail("statmount by fd failed: %s\n", strerror(errno));
743 		goto err_chroot;
744 	}
745 
746 	if (sm->size < sizeof(*sm)) {
747 		ksft_test_result_fail("unexpected size: %u < %u\n",
748 				      sm->size, (uint32_t) sizeof(*sm));
749 		goto err_chroot;
750 	}
751 
752 	if (sm->mask & STATMOUNT_MNT_POINT) {
753 		ksft_test_result_fail("STATMOUNT_MNT_POINT unexpectedly set in statmount\n");
754 		goto err_chroot;
755 	}
756 
757 	if (!(sm->mask & STATMOUNT_MNT_ROOT)) {
758 		ksft_test_result_fail("STATMOUNT_MNT_ROOT not set in statmount\n");
759 		goto err_chroot;
760 	}
761 
762 	if (strcmp(root, sm->str + sm->mnt_root) != 0) {
763 		ksft_test_result_fail("statmount returned incorrect mnt_root,"
764 			"statmount mnt_root: %s != %s\n",
765 			sm->str + sm->mnt_root, root);
766 		goto err_chroot;
767 	}
768 
769 	if (chroot(".")) {
770 		ksft_perror("chroot");
771 		goto out;
772 	}
773 
774 	free(sm);
775 	sm = statmount_alloc_by_fd(fd, STATMOUNT_MNT_ROOT | STATMOUNT_MNT_POINT);
776 	if (!sm) {
777 		ksft_test_result_fail("statmount by fd failed: %s\n", strerror(errno));
778 		goto err_fd;
779 	}
780 
781 	if (sm->size < sizeof(*sm)) {
782 		ksft_test_result_fail("unexpected size: %u < %u\n",
783 				      sm->size, (uint32_t) sizeof(*sm));
784 		goto out;
785 	}
786 
787 	if (!(sm->mask & STATMOUNT_MNT_POINT)) {
788 		ksft_test_result_fail("STATMOUNT_MNT_POINT not set in statmount\n");
789 		goto out;
790 	}
791 
792 	if (!(sm->mask & STATMOUNT_MNT_ROOT)) {
793 		ksft_test_result_fail("STATMOUNT_MNT_ROOT not set in statmount\n");
794 		goto out;
795 	}
796 
797 	if (strcmp(subdir, sm->str + sm->mnt_point) != 0) {
798 		ksft_test_result_fail("statmount returned incorrect mnt_point,"
799 			"statmount mnt_point: %s != %s\n", sm->str + sm->mnt_point, subdir);
800 		goto out;
801 	}
802 
803 	if (strcmp(root, sm->str + sm->mnt_root) != 0) {
804 		ksft_test_result_fail("statmount returned incorrect mnt_root,"
805 			"statmount mnt_root: %s != %s\n", sm->str + sm->mnt_root, root);
806 		goto out;
807 	}
808 
809 	ksft_test_result_pass("statmount by fd\n");
810 	goto out;
811 err_chroot:
812 	chroot(".");
813 out:
814 	free(sm);
815 err_fd:
816 	close(fd);
817 err_tmproot:
818 	rmdir(tmproot);
819 err_subdir:
820 	umount2(subdir, MNT_DETACH);
821 	rmdir(subdir);
822 err_tmpdir:
823 	umount2(tmpdir, MNT_DETACH);
824 	rmdir(tmpdir);
825 }
826 
827 static void test_statmount_by_fd_unmounted(void)
828 {
829 	const char root[] = "/test.unmounted";
830 	char tmpdir[] = "/statmount.fd.XXXXXX";
831 	char subdir[PATH_MAX];
832 	int fd;
833 	struct statmount *sm = NULL;
834 
835 	if (!mkdtemp(tmpdir)) {
836 		ksft_perror("mkdtemp");
837 		return;
838 	}
839 
840 	if (mount("statmount.test", tmpdir, "tmpfs", 0, NULL)) {
841 		ksft_perror("mount");
842 		rmdir(tmpdir);
843 		return;
844 	}
845 
846 	snprintf(subdir, PATH_MAX, "%s%s", tmpdir, root);
847 
848 	if (mkdir(subdir, 0755)) {
849 		ksft_perror("mkdir");
850 		goto err_tmpdir;
851 	}
852 
853 	if (mount(subdir, subdir, 0, MS_BIND, NULL)) {
854 		ksft_perror("mount");
855 		goto err_subdir;
856 	}
857 
858 	fd = open(subdir, O_PATH);
859 	if (fd < 0) {
860 		ksft_perror("open");
861 		goto err_subdir;
862 	}
863 
864 	if (umount2(tmpdir, MNT_DETACH)) {
865 		ksft_perror("umount2");
866 		goto err_fd;
867 	}
868 
869 	sm = statmount_alloc_by_fd(fd, STATMOUNT_MNT_POINT | STATMOUNT_MNT_ROOT);
870 	if (!sm) {
871 		ksft_test_result_fail("statmount by fd unmounted: %s\n",
872 				      strerror(errno));
873 		goto err_sm;
874 	}
875 
876 	if (sm->size < sizeof(*sm)) {
877 		ksft_test_result_fail("unexpected size: %u < %u\n",
878 				      sm->size, (uint32_t) sizeof(*sm));
879 		goto err_sm;
880 	}
881 
882 	if (sm->mask & STATMOUNT_MNT_POINT) {
883 		ksft_test_result_fail("STATMOUNT_MNT_POINT unexpectedly set in mask\n");
884 		goto err_sm;
885 	}
886 
887 	if (!(sm->mask & STATMOUNT_MNT_ROOT)) {
888 		ksft_test_result_fail("STATMOUNT_MNT_ROOT not set in mask\n");
889 		goto err_sm;
890 	}
891 
892 	if (strcmp(sm->str + sm->mnt_root, root) != 0) {
893 		ksft_test_result_fail("statmount returned incorrect mnt_root,"
894 			"statmount mnt_root: %s != %s\n",
895 			sm->str + sm->mnt_root, root);
896 		goto err_sm;
897 	}
898 
899 	ksft_test_result_pass("statmount by fd on unmounted mount\n");
900 err_sm:
901 	free(sm);
902 err_fd:
903 	close(fd);
904 err_subdir:
905 	umount2(subdir, MNT_DETACH);
906 	rmdir(subdir);
907 err_tmpdir:
908 	umount2(tmpdir, MNT_DETACH);
909 	rmdir(tmpdir);
910 }
911 
912 #define str_off(memb) (offsetof(struct statmount, memb) / sizeof(uint32_t))
913 
914 int main(void)
915 {
916 	int ret;
917 	uint64_t all_mask = STATMOUNT_SB_BASIC | STATMOUNT_MNT_BASIC |
918 		STATMOUNT_PROPAGATE_FROM | STATMOUNT_MNT_ROOT |
919 		STATMOUNT_MNT_POINT | STATMOUNT_FS_TYPE | STATMOUNT_MNT_NS_ID;
920 
921 	ksft_print_header();
922 
923 	ret = statmount(0, 0, 0, 0, NULL, 0, 0);
924 	assert(ret == -1);
925 	if (errno == ENOSYS)
926 		ksft_exit_skip("statmount() syscall not supported\n");
927 
928 	setup_namespace();
929 
930 	ksft_set_plan(18);
931 	test_listmount_empty_root();
932 	test_statmount_zero_mask();
933 	test_statmount_mnt_basic();
934 	test_statmount_sb_basic();
935 	test_statmount_mnt_root();
936 	test_statmount_mnt_point();
937 	test_statmount_fs_type();
938 	test_statmount_mnt_opts();
939 	test_statmount_string(STATMOUNT_MNT_ROOT, str_off(mnt_root), "mount root");
940 	test_statmount_string(STATMOUNT_MNT_POINT, str_off(mnt_point), "mount point");
941 	test_statmount_string(STATMOUNT_FS_TYPE, str_off(fs_type), "fs type");
942 	test_statmount_string(all_mask, str_off(mnt_root), "mount root & all");
943 	test_statmount_string(all_mask, str_off(mnt_point), "mount point & all");
944 	test_statmount_string(all_mask, str_off(fs_type), "fs type & all");
945 	test_statmount_string_short_buffer();
946 
947 	test_listmount_tree();
948 	test_statmount_by_fd_unmounted();
949 	test_statmount_by_fd();
950 
951 
952 	if (ksft_get_fail_cnt() + ksft_get_error_cnt() > 0)
953 		ksft_exit_fail();
954 	else
955 		ksft_exit_pass();
956 }
957