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 (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"); 392 out: 393 free(sm); 394 } 395 396 static 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"); 424 out: 425 free(sm); 426 } 427 428 static 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 452 static 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 534 static 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); 585 out: 586 free(sm); 587 } 588 589 static 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 644 int 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 } 684