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