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", "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 37 static 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 67 static 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 87 static 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 106 static char root_mntpoint[] = "/tmp/statmount_test_root.XXXXXX"; 107 static int orig_root; 108 static uint64_t root_id, parent_id; 109 static uint32_t old_root_id, old_parent_id; 110 static FILE *f_mountinfo; 111 112 static 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 128 static 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 188 static 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 210 static 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 236 static 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 261 static 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 319 static 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 375 static 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 (!(sm->mask & STATMOUNT_MNT_POINT)) { 387 ksft_test_result_fail("missing STATMOUNT_MNT_POINT in mask\n"); 388 return; 389 } 390 if (strcmp(sm->str + sm->mnt_point, "/") != 0) { 391 ksft_test_result_fail("unexpected mount point: '%s' != '/'\n", 392 sm->str + sm->mnt_point); 393 goto out; 394 } 395 ksft_test_result_pass("statmount mount point\n"); 396 out: 397 free(sm); 398 } 399 400 static void test_statmount_mnt_root(void) 401 { 402 struct statmount *sm; 403 const char *mnt_root, *last_dir, *last_root; 404 405 last_dir = strrchr(root_mntpoint, '/'); 406 assert(last_dir); 407 last_dir++; 408 409 sm = statmount_alloc(root_id, STATMOUNT_MNT_ROOT, 0); 410 if (!sm) { 411 ksft_test_result_fail("statmount mount root: %s\n", 412 strerror(errno)); 413 return; 414 } 415 if (!(sm->mask & STATMOUNT_MNT_ROOT)) { 416 ksft_test_result_fail("missing STATMOUNT_MNT_ROOT in mask\n"); 417 return; 418 } 419 mnt_root = sm->str + sm->mnt_root; 420 last_root = strrchr(mnt_root, '/'); 421 if (last_root) 422 last_root++; 423 else 424 last_root = mnt_root; 425 426 if (strcmp(last_dir, last_root) != 0) { 427 ksft_test_result_fail("unexpected mount root last component: '%s' != '%s'\n", 428 last_root, last_dir); 429 goto out; 430 } 431 ksft_test_result_pass("statmount mount root\n"); 432 out: 433 free(sm); 434 } 435 436 static void test_statmount_fs_type(void) 437 { 438 struct statmount *sm; 439 const char *fs_type; 440 const char *const *s; 441 442 sm = statmount_alloc(root_id, STATMOUNT_FS_TYPE, 0); 443 if (!sm) { 444 ksft_test_result_fail("statmount fs type: %s\n", 445 strerror(errno)); 446 return; 447 } 448 if (!(sm->mask & STATMOUNT_FS_TYPE)) { 449 ksft_test_result_fail("missing STATMOUNT_FS_TYPE in mask\n"); 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 464 static void test_statmount_mnt_opts(void) 465 { 466 struct statmount *sm; 467 const char *statmount_opts; 468 char *line = NULL; 469 size_t len = 0; 470 471 sm = statmount_alloc(root_id, STATMOUNT_MNT_BASIC | STATMOUNT_MNT_OPTS, 472 0); 473 if (!sm) { 474 ksft_test_result_fail("statmount mnt opts: %s\n", 475 strerror(errno)); 476 return; 477 } 478 479 if (!(sm->mask & STATMOUNT_MNT_BASIC)) { 480 ksft_test_result_fail("missing STATMOUNT_MNT_BASIC in mask\n"); 481 return; 482 } 483 484 while (getline(&line, &len, f_mountinfo) != -1) { 485 int i; 486 char *p, *p2; 487 unsigned int old_mnt_id; 488 489 old_mnt_id = atoi(line); 490 if (old_mnt_id != sm->mnt_id_old) 491 continue; 492 493 for (p = line, i = 0; p && i < 5; i++) 494 p = strchr(p + 1, ' '); 495 if (!p) 496 continue; 497 498 p2 = strchr(p + 1, ' '); 499 if (!p2) 500 continue; 501 *p2 = '\0'; 502 p = strchr(p2 + 1, '-'); 503 if (!p) 504 continue; 505 for (p++, i = 0; p && i < 2; i++) 506 p = strchr(p + 1, ' '); 507 if (!p) 508 continue; 509 p++; 510 511 /* skip generic superblock options */ 512 if (strncmp(p, "ro", 2) == 0) 513 p += 2; 514 else if (strncmp(p, "rw", 2) == 0) 515 p += 2; 516 if (*p == ',') 517 p++; 518 if (strncmp(p, "sync", 4) == 0) 519 p += 4; 520 if (*p == ',') 521 p++; 522 if (strncmp(p, "dirsync", 7) == 0) 523 p += 7; 524 if (*p == ',') 525 p++; 526 if (strncmp(p, "lazytime", 8) == 0) 527 p += 8; 528 if (*p == ',') 529 p++; 530 p2 = strrchr(p, '\n'); 531 if (p2) 532 *p2 = '\0'; 533 534 if (sm->mask & STATMOUNT_MNT_OPTS) 535 statmount_opts = sm->str + sm->mnt_opts; 536 else 537 statmount_opts = ""; 538 if (strcmp(statmount_opts, p) != 0) 539 ksft_test_result_fail( 540 "unexpected mount options: '%s' != '%s'\n", 541 statmount_opts, p); 542 else 543 ksft_test_result_pass("statmount mount options\n"); 544 free(sm); 545 free(line); 546 return; 547 } 548 549 ksft_test_result_fail("didnt't find mount entry\n"); 550 free(sm); 551 free(line); 552 } 553 554 static void test_statmount_string(uint64_t mask, size_t off, const char *name) 555 { 556 struct statmount *sm; 557 size_t len, shortsize, exactsize; 558 uint32_t start, i; 559 int ret; 560 561 sm = statmount_alloc(root_id, mask, 0); 562 if (!sm) { 563 ksft_test_result_fail("statmount %s: %s\n", name, 564 strerror(errno)); 565 goto out; 566 } 567 if (sm->size < sizeof(*sm)) { 568 ksft_test_result_fail("unexpected size: %u < %u\n", 569 sm->size, (uint32_t) sizeof(*sm)); 570 goto out; 571 } 572 if (sm->mask != mask) { 573 ksft_test_result_skip("statmount %s unavailable\n", name); 574 goto out; 575 } 576 len = sm->size - sizeof(*sm); 577 start = ((uint32_t *) sm)[off]; 578 579 for (i = start;; i++) { 580 if (i >= len) { 581 ksft_test_result_fail("string out of bounds\n"); 582 goto out; 583 } 584 if (!sm->str[i]) 585 break; 586 } 587 exactsize = sm->size; 588 shortsize = sizeof(*sm) + i; 589 590 ret = statmount(root_id, 0, mask, sm, exactsize, 0); 591 if (ret == -1) { 592 ksft_test_result_fail("statmount exact size: %s\n", 593 strerror(errno)); 594 goto out; 595 } 596 errno = 0; 597 ret = statmount(root_id, 0, mask, sm, shortsize, 0); 598 if (ret != -1 || errno != EOVERFLOW) { 599 ksft_test_result_fail("should have failed with EOVERFLOW: %s\n", 600 strerror(errno)); 601 goto out; 602 } 603 604 ksft_test_result_pass("statmount string %s\n", name); 605 out: 606 free(sm); 607 } 608 609 static void test_listmount_tree(void) 610 { 611 ssize_t res; 612 const unsigned int log2_num = 4; 613 const unsigned int step = 3; 614 const unsigned int size = (1 << log2_num) + step + 1; 615 size_t num, expect = 1 << log2_num; 616 uint64_t list[size]; 617 uint64_t list2[size]; 618 size_t i; 619 620 621 res = setup_mount_tree(log2_num); 622 if (res == -1) 623 return; 624 625 num = res = listmount(LSMT_ROOT, 0, 0, list, size, 0); 626 if (res == -1) { 627 ksft_test_result_fail("listmount: %s\n", strerror(errno)); 628 return; 629 } 630 if (num != expect) { 631 ksft_test_result_fail("listmount result is %zi != %zi\n", 632 res, expect); 633 return; 634 } 635 636 for (i = 0; i < size - step;) { 637 res = listmount(LSMT_ROOT, 0, i ? list2[i - 1] : 0, list2 + i, step, 0); 638 if (res == -1) 639 ksft_test_result_fail("short listmount: %s\n", 640 strerror(errno)); 641 i += res; 642 if (res < step) 643 break; 644 } 645 if (i != num) { 646 ksft_test_result_fail("different number of entries: %zu != %zu\n", 647 i, num); 648 return; 649 } 650 for (i = 0; i < num; i++) { 651 if (list2[i] != list[i]) { 652 ksft_test_result_fail("different value for entry %zu: 0x%llx != 0x%llx\n", 653 i, 654 (unsigned long long) list2[i], 655 (unsigned long long) list[i]); 656 } 657 } 658 659 ksft_test_result_pass("listmount tree\n"); 660 } 661 662 #define str_off(memb) (offsetof(struct statmount, memb) / sizeof(uint32_t)) 663 664 int main(void) 665 { 666 int ret; 667 uint64_t all_mask = STATMOUNT_SB_BASIC | STATMOUNT_MNT_BASIC | 668 STATMOUNT_PROPAGATE_FROM | STATMOUNT_MNT_ROOT | 669 STATMOUNT_MNT_POINT | STATMOUNT_FS_TYPE | STATMOUNT_MNT_NS_ID; 670 671 ksft_print_header(); 672 673 ret = statmount(0, 0, 0, NULL, 0, 0); 674 assert(ret == -1); 675 if (errno == ENOSYS) 676 ksft_exit_skip("statmount() syscall not supported\n"); 677 678 setup_namespace(); 679 680 ksft_set_plan(15); 681 test_listmount_empty_root(); 682 test_statmount_zero_mask(); 683 test_statmount_mnt_basic(); 684 test_statmount_sb_basic(); 685 test_statmount_mnt_root(); 686 test_statmount_mnt_point(); 687 test_statmount_fs_type(); 688 test_statmount_mnt_opts(); 689 test_statmount_string(STATMOUNT_MNT_ROOT, str_off(mnt_root), "mount root"); 690 test_statmount_string(STATMOUNT_MNT_POINT, str_off(mnt_point), "mount point"); 691 test_statmount_string(STATMOUNT_FS_TYPE, str_off(fs_type), "fs type"); 692 test_statmount_string(all_mask, str_off(mnt_root), "mount root & all"); 693 test_statmount_string(all_mask, str_off(mnt_point), "mount point & all"); 694 test_statmount_string(all_mask, str_off(fs_type), "fs type & all"); 695 696 test_listmount_tree(); 697 698 699 if (ksft_get_fail_cnt() + ksft_get_error_cnt() > 0) 700 ksft_exit_fail(); 701 else 702 ksft_exit_pass(); 703 } 704