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