684 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2 3#define _GNU_SOURCE4 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", "reiserfs",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 else416 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 else523 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 else682 ksft_exit_pass();683}684