Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
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
20static 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
39static 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
51static 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
81static 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
101static 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
120static char root_mntpoint[] = "/tmp/statmount_test_root.XXXXXX";
121static int orig_root;
122static uint64_t root_id, parent_id;
123static uint32_t old_root_id, old_parent_id;
124
125
126static void cleanup_namespace(void)
127{
128 fchdir(orig_root);
129 chroot(".");
130 umount2(root_mntpoint, MNT_DETACH);
131 rmdir(root_mntpoint);
132}
133
134static void setup_namespace(void)
135{
136 int ret;
137 char buf[32];
138 uid_t uid = getuid();
139 gid_t gid = getgid();
140
141 ret = unshare(CLONE_NEWNS|CLONE_NEWUSER);
142 if (ret == -1)
143 ksft_exit_fail_msg("unsharing mountns and userns: %s\n",
144 strerror(errno));
145
146 sprintf(buf, "0 %d 1", uid);
147 write_file("/proc/self/uid_map", buf);
148 write_file("/proc/self/setgroups", "deny");
149 sprintf(buf, "0 %d 1", gid);
150 write_file("/proc/self/gid_map", buf);
151
152 ret = mount("", "/", NULL, MS_REC|MS_PRIVATE, NULL);
153 if (ret == -1)
154 ksft_exit_fail_msg("making mount tree private: %s\n",
155 strerror(errno));
156
157 if (!mkdtemp(root_mntpoint))
158 ksft_exit_fail_msg("creating temporary directory %s: %s\n",
159 root_mntpoint, strerror(errno));
160
161 old_parent_id = get_mnt_id("parent", root_mntpoint, STATX_MNT_ID);
162 parent_id = get_mnt_id("parent", root_mntpoint, STATX_MNT_ID_UNIQUE);
163
164 orig_root = open("/", O_PATH);
165 if (orig_root == -1)
166 ksft_exit_fail_msg("opening root directory: %s",
167 strerror(errno));
168
169 atexit(cleanup_namespace);
170
171 ret = mount(root_mntpoint, root_mntpoint, NULL, MS_BIND, NULL);
172 if (ret == -1)
173 ksft_exit_fail_msg("mounting temp root %s: %s\n",
174 root_mntpoint, strerror(errno));
175
176 ret = chroot(root_mntpoint);
177 if (ret == -1)
178 ksft_exit_fail_msg("chroot to temp root %s: %s\n",
179 root_mntpoint, strerror(errno));
180
181 ret = chdir("/");
182 if (ret == -1)
183 ksft_exit_fail_msg("chdir to root: %s\n", strerror(errno));
184
185 old_root_id = get_mnt_id("root", "/", STATX_MNT_ID);
186 root_id = get_mnt_id("root", "/", STATX_MNT_ID_UNIQUE);
187}
188
189static int setup_mount_tree(int log2_num)
190{
191 int ret, i;
192
193 ret = mount("", "/", NULL, MS_REC|MS_SHARED, NULL);
194 if (ret == -1) {
195 ksft_test_result_fail("making mount tree shared: %s\n",
196 strerror(errno));
197 return -1;
198 }
199
200 for (i = 0; i < log2_num; i++) {
201 ret = mount("/", "/", NULL, MS_BIND, NULL);
202 if (ret == -1) {
203 ksft_test_result_fail("mounting submount %s: %s\n",
204 root_mntpoint, strerror(errno));
205 return -1;
206 }
207 }
208 return 0;
209}
210
211static ssize_t listmount(uint64_t mnt_id, uint64_t last_mnt_id,
212 uint64_t list[], size_t num, unsigned int flags)
213{
214 struct mnt_id_req req = {
215 .size = MNT_ID_REQ_SIZE_VER0,
216 .mnt_id = mnt_id,
217 .param = last_mnt_id,
218 };
219
220 return syscall(__NR_listmount, &req, list, num, flags);
221}
222
223static void test_listmount_empty_root(void)
224{
225 ssize_t res;
226 const unsigned int size = 32;
227 uint64_t list[size];
228
229 res = listmount(LSMT_ROOT, 0, list, size, 0);
230 if (res == -1) {
231 ksft_test_result_fail("listmount: %s\n", strerror(errno));
232 return;
233 }
234 if (res != 1) {
235 ksft_test_result_fail("listmount result is %zi != 1\n", res);
236 return;
237 }
238
239 if (list[0] != root_id) {
240 ksft_test_result_fail("listmount ID doesn't match 0x%llx != 0x%llx\n",
241 (unsigned long long) list[0],
242 (unsigned long long) root_id);
243 return;
244 }
245
246 ksft_test_result_pass("listmount empty root\n");
247}
248
249static void test_statmount_zero_mask(void)
250{
251 struct statmount sm;
252 int ret;
253
254 ret = statmount(root_id, 0, &sm, sizeof(sm), 0);
255 if (ret == -1) {
256 ksft_test_result_fail("statmount zero mask: %s\n",
257 strerror(errno));
258 return;
259 }
260 if (sm.size != sizeof(sm)) {
261 ksft_test_result_fail("unexpected size: %u != %u\n",
262 sm.size, (uint32_t) sizeof(sm));
263 return;
264 }
265 if (sm.mask != 0) {
266 ksft_test_result_fail("unexpected mask: 0x%llx != 0x0\n",
267 (unsigned long long) sm.mask);
268 return;
269 }
270
271 ksft_test_result_pass("statmount zero mask\n");
272}
273
274static void test_statmount_mnt_basic(void)
275{
276 struct statmount sm;
277 int ret;
278 uint64_t mask = STATMOUNT_MNT_BASIC;
279
280 ret = statmount(root_id, mask, &sm, sizeof(sm), 0);
281 if (ret == -1) {
282 ksft_test_result_fail("statmount mnt basic: %s\n",
283 strerror(errno));
284 return;
285 }
286 if (sm.size != sizeof(sm)) {
287 ksft_test_result_fail("unexpected size: %u != %u\n",
288 sm.size, (uint32_t) sizeof(sm));
289 return;
290 }
291 if (sm.mask != mask) {
292 ksft_test_result_skip("statmount mnt basic unavailable\n");
293 return;
294 }
295
296 if (sm.mnt_id != root_id) {
297 ksft_test_result_fail("unexpected root ID: 0x%llx != 0x%llx\n",
298 (unsigned long long) sm.mnt_id,
299 (unsigned long long) root_id);
300 return;
301 }
302
303 if (sm.mnt_id_old != old_root_id) {
304 ksft_test_result_fail("unexpected old root ID: %u != %u\n",
305 sm.mnt_id_old, old_root_id);
306 return;
307 }
308
309 if (sm.mnt_parent_id != parent_id) {
310 ksft_test_result_fail("unexpected parent ID: 0x%llx != 0x%llx\n",
311 (unsigned long long) sm.mnt_parent_id,
312 (unsigned long long) parent_id);
313 return;
314 }
315
316 if (sm.mnt_parent_id_old != old_parent_id) {
317 ksft_test_result_fail("unexpected old parent ID: %u != %u\n",
318 sm.mnt_parent_id_old, old_parent_id);
319 return;
320 }
321
322 if (sm.mnt_propagation != MS_PRIVATE) {
323 ksft_test_result_fail("unexpected propagation: 0x%llx\n",
324 (unsigned long long) sm.mnt_propagation);
325 return;
326 }
327
328 ksft_test_result_pass("statmount mnt basic\n");
329}
330
331
332static void test_statmount_sb_basic(void)
333{
334 struct statmount sm;
335 int ret;
336 uint64_t mask = STATMOUNT_SB_BASIC;
337 struct statx sx;
338 struct statfs sf;
339
340 ret = statmount(root_id, mask, &sm, sizeof(sm), 0);
341 if (ret == -1) {
342 ksft_test_result_fail("statmount sb basic: %s\n",
343 strerror(errno));
344 return;
345 }
346 if (sm.size != sizeof(sm)) {
347 ksft_test_result_fail("unexpected size: %u != %u\n",
348 sm.size, (uint32_t) sizeof(sm));
349 return;
350 }
351 if (sm.mask != mask) {
352 ksft_test_result_skip("statmount sb basic unavailable\n");
353 return;
354 }
355
356 ret = statx(AT_FDCWD, "/", 0, 0, &sx);
357 if (ret == -1) {
358 ksft_test_result_fail("stat root failed: %s\n",
359 strerror(errno));
360 return;
361 }
362
363 if (sm.sb_dev_major != sx.stx_dev_major ||
364 sm.sb_dev_minor != sx.stx_dev_minor) {
365 ksft_test_result_fail("unexpected sb dev %u:%u != %u:%u\n",
366 sm.sb_dev_major, sm.sb_dev_minor,
367 sx.stx_dev_major, sx.stx_dev_minor);
368 return;
369 }
370
371 ret = statfs("/", &sf);
372 if (ret == -1) {
373 ksft_test_result_fail("statfs root failed: %s\n",
374 strerror(errno));
375 return;
376 }
377
378 if (sm.sb_magic != sf.f_type) {
379 ksft_test_result_fail("unexpected sb magic: 0x%llx != 0x%lx\n",
380 (unsigned long long) sm.sb_magic,
381 sf.f_type);
382 return;
383 }
384
385 ksft_test_result_pass("statmount sb basic\n");
386}
387
388static void test_statmount_mnt_point(void)
389{
390 struct statmount *sm;
391
392 sm = statmount_alloc(root_id, STATMOUNT_MNT_POINT, 0);
393 if (!sm) {
394 ksft_test_result_fail("statmount mount point: %s\n",
395 strerror(errno));
396 return;
397 }
398
399 if (strcmp(sm->str + sm->mnt_point, "/") != 0) {
400 ksft_test_result_fail("unexpected mount point: '%s' != '/'\n",
401 sm->str + sm->mnt_point);
402 goto out;
403 }
404 ksft_test_result_pass("statmount mount point\n");
405out:
406 free(sm);
407}
408
409static void test_statmount_mnt_root(void)
410{
411 struct statmount *sm;
412 const char *mnt_root, *last_dir, *last_root;
413
414 last_dir = strrchr(root_mntpoint, '/');
415 assert(last_dir);
416 last_dir++;
417
418 sm = statmount_alloc(root_id, STATMOUNT_MNT_ROOT, 0);
419 if (!sm) {
420 ksft_test_result_fail("statmount mount root: %s\n",
421 strerror(errno));
422 return;
423 }
424 mnt_root = sm->str + sm->mnt_root;
425 last_root = strrchr(mnt_root, '/');
426 if (last_root)
427 last_root++;
428 else
429 last_root = mnt_root;
430
431 if (strcmp(last_dir, last_root) != 0) {
432 ksft_test_result_fail("unexpected mount root last component: '%s' != '%s'\n",
433 last_root, last_dir);
434 goto out;
435 }
436 ksft_test_result_pass("statmount mount root\n");
437out:
438 free(sm);
439}
440
441static void test_statmount_fs_type(void)
442{
443 struct statmount *sm;
444 const char *fs_type;
445 const char *const *s;
446
447 sm = statmount_alloc(root_id, STATMOUNT_FS_TYPE, 0);
448 if (!sm) {
449 ksft_test_result_fail("statmount fs type: %s\n",
450 strerror(errno));
451 return;
452 }
453 fs_type = sm->str + sm->fs_type;
454 for (s = known_fs; s != NULL; s++) {
455 if (strcmp(fs_type, *s) == 0)
456 break;
457 }
458 if (!s)
459 ksft_print_msg("unknown filesystem type: %s\n", fs_type);
460
461 ksft_test_result_pass("statmount fs type\n");
462 free(sm);
463}
464
465static void test_statmount_string(uint64_t mask, size_t off, const char *name)
466{
467 struct statmount *sm;
468 size_t len, shortsize, exactsize;
469 uint32_t start, i;
470 int ret;
471
472 sm = statmount_alloc(root_id, mask, 0);
473 if (!sm) {
474 ksft_test_result_fail("statmount %s: %s\n", name,
475 strerror(errno));
476 goto out;
477 }
478 if (sm->size < sizeof(*sm)) {
479 ksft_test_result_fail("unexpected size: %u < %u\n",
480 sm->size, (uint32_t) sizeof(*sm));
481 goto out;
482 }
483 if (sm->mask != mask) {
484 ksft_test_result_skip("statmount %s unavailable\n", name);
485 goto out;
486 }
487 len = sm->size - sizeof(*sm);
488 start = ((uint32_t *) sm)[off];
489
490 for (i = start;; i++) {
491 if (i >= len) {
492 ksft_test_result_fail("string out of bounds\n");
493 goto out;
494 }
495 if (!sm->str[i])
496 break;
497 }
498 exactsize = sm->size;
499 shortsize = sizeof(*sm) + i;
500
501 ret = statmount(root_id, mask, sm, exactsize, 0);
502 if (ret == -1) {
503 ksft_test_result_fail("statmount exact size: %s\n",
504 strerror(errno));
505 goto out;
506 }
507 errno = 0;
508 ret = statmount(root_id, mask, sm, shortsize, 0);
509 if (ret != -1 || errno != EOVERFLOW) {
510 ksft_test_result_fail("should have failed with EOVERFLOW: %s\n",
511 strerror(errno));
512 goto out;
513 }
514
515 ksft_test_result_pass("statmount string %s\n", name);
516out:
517 free(sm);
518}
519
520static void test_listmount_tree(void)
521{
522 ssize_t res;
523 const unsigned int log2_num = 4;
524 const unsigned int step = 3;
525 const unsigned int size = (1 << log2_num) + step + 1;
526 size_t num, expect = 1 << log2_num;
527 uint64_t list[size];
528 uint64_t list2[size];
529 size_t i;
530
531
532 res = setup_mount_tree(log2_num);
533 if (res == -1)
534 return;
535
536 num = res = listmount(LSMT_ROOT, 0, list, size, 0);
537 if (res == -1) {
538 ksft_test_result_fail("listmount: %s\n", strerror(errno));
539 return;
540 }
541 if (num != expect) {
542 ksft_test_result_fail("listmount result is %zi != %zi\n",
543 res, expect);
544 return;
545 }
546
547 for (i = 0; i < size - step;) {
548 res = listmount(LSMT_ROOT, i ? list2[i - 1] : 0, list2 + i, step, 0);
549 if (res == -1)
550 ksft_test_result_fail("short listmount: %s\n",
551 strerror(errno));
552 i += res;
553 if (res < step)
554 break;
555 }
556 if (i != num) {
557 ksft_test_result_fail("different number of entries: %zu != %zu\n",
558 i, num);
559 return;
560 }
561 for (i = 0; i < num; i++) {
562 if (list2[i] != list[i]) {
563 ksft_test_result_fail("different value for entry %zu: 0x%llx != 0x%llx\n",
564 i,
565 (unsigned long long) list2[i],
566 (unsigned long long) list[i]);
567 }
568 }
569
570 ksft_test_result_pass("listmount tree\n");
571}
572
573#define str_off(memb) (offsetof(struct statmount, memb) / sizeof(uint32_t))
574
575int main(void)
576{
577 int ret;
578 uint64_t all_mask = STATMOUNT_SB_BASIC | STATMOUNT_MNT_BASIC |
579 STATMOUNT_PROPAGATE_FROM | STATMOUNT_MNT_ROOT |
580 STATMOUNT_MNT_POINT | STATMOUNT_FS_TYPE;
581
582 ksft_print_header();
583
584 ret = statmount(0, 0, NULL, 0, 0);
585 assert(ret == -1);
586 if (errno == ENOSYS)
587 ksft_exit_skip("statmount() syscall not supported\n");
588
589 setup_namespace();
590
591 ksft_set_plan(14);
592 test_listmount_empty_root();
593 test_statmount_zero_mask();
594 test_statmount_mnt_basic();
595 test_statmount_sb_basic();
596 test_statmount_mnt_root();
597 test_statmount_mnt_point();
598 test_statmount_fs_type();
599 test_statmount_string(STATMOUNT_MNT_ROOT, str_off(mnt_root), "mount root");
600 test_statmount_string(STATMOUNT_MNT_POINT, str_off(mnt_point), "mount point");
601 test_statmount_string(STATMOUNT_FS_TYPE, str_off(fs_type), "fs type");
602 test_statmount_string(all_mask, str_off(mnt_root), "mount root & all");
603 test_statmount_string(all_mask, str_off(mnt_point), "mount point & all");
604 test_statmount_string(all_mask, str_off(fs_type), "fs type & all");
605
606 test_listmount_tree();
607
608
609 if (ksft_get_fail_cnt() + ksft_get_error_cnt() > 0)
610 ksft_exit_fail();
611 else
612 ksft_exit_pass();
613}