Path: blob/master/tools/testing/selftests/filesystems/statmount/statmount_test.c
26302 views
// SPDX-License-Identifier: GPL-2.0-or-later12#define _GNU_SOURCE34#include <assert.h>5#include <stddef.h>6#include <sched.h>7#include <fcntl.h>8#include <sys/param.h>9#include <sys/mount.h>10#include <sys/stat.h>11#include <sys/statfs.h>12#include <linux/stat.h>1314#include "statmount.h"15#include "../../kselftest.h"1617static const char *const known_fs[] = {18"9p", "adfs", "affs", "afs", "aio", "anon_inodefs", "apparmorfs",19"autofs", "bcachefs", "bdev", "befs", "bfs", "binder", "binfmt_misc",20"bpf", "btrfs", "btrfs_test_fs", "ceph", "cgroup", "cgroup2", "cifs",21"coda", "configfs", "cpuset", "cramfs", "cxl", "dax", "debugfs",22"devpts", "devtmpfs", "dmabuf", "drm", "ecryptfs", "efivarfs", "efs",23"erofs", "exfat", "ext2", "ext3", "ext4", "f2fs", "functionfs",24"fuse", "fuseblk", "fusectl", "gadgetfs", "gfs2", "gfs2meta", "hfs",25"hfsplus", "hostfs", "hpfs", "hugetlbfs", "ibmasmfs", "iomem",26"ipathfs", "iso9660", "jffs2", "jfs", "minix", "mqueue", "msdos",27"nfs", "nfs4", "nfsd", "nilfs2", "nsfs", "ntfs", "ntfs3", "ocfs2",28"ocfs2_dlmfs", "omfs", "openpromfs", "overlay", "pipefs", "proc",29"pstore", "pvfs2", "qnx4", "qnx6", "ramfs", "resctrl", "romfs",30"rootfs", "rpc_pipefs", "s390_hypfs", "secretmem", "securityfs",31"selinuxfs", "smackfs", "smb3", "sockfs", "spufs", "squashfs", "sysfs",32"sysv", "tmpfs", "tracefs", "ubifs", "udf", "ufs", "v7", "vboxsf",33"vfat", "virtiofs", "vxfs", "xenfs", "xfs", "zonefs", NULL };3435static struct statmount *statmount_alloc(uint64_t mnt_id, uint64_t mask, unsigned int flags)36{37size_t bufsize = 1 << 15;38struct statmount *buf = NULL, *tmp = alloca(bufsize);39int tofree = 0;40int ret;4142for (;;) {43ret = statmount(mnt_id, 0, mask, tmp, bufsize, flags);44if (ret != -1)45break;46if (tofree)47free(tmp);48if (errno != EOVERFLOW)49return NULL;50bufsize <<= 1;51tofree = 1;52tmp = malloc(bufsize);53if (!tmp)54return NULL;55}56buf = malloc(tmp->size);57if (buf)58memcpy(buf, tmp, tmp->size);59if (tofree)60free(tmp);6162return buf;63}6465static void write_file(const char *path, const char *val)66{67int fd = open(path, O_WRONLY);68size_t len = strlen(val);69int ret;7071if (fd == -1)72ksft_exit_fail_msg("opening %s for write: %s\n", path, strerror(errno));7374ret = write(fd, val, len);75if (ret == -1)76ksft_exit_fail_msg("writing to %s: %s\n", path, strerror(errno));77if (ret != len)78ksft_exit_fail_msg("short write to %s\n", path);7980ret = close(fd);81if (ret == -1)82ksft_exit_fail_msg("closing %s\n", path);83}8485static uint64_t get_mnt_id(const char *name, const char *path, uint64_t mask)86{87struct statx sx;88int ret;8990ret = statx(AT_FDCWD, path, 0, mask, &sx);91if (ret == -1)92ksft_exit_fail_msg("retrieving %s mount ID for %s: %s\n",93mask & STATX_MNT_ID_UNIQUE ? "unique" : "old",94name, strerror(errno));95if (!(sx.stx_mask & mask))96ksft_exit_fail_msg("no %s mount ID available for %s\n",97mask & STATX_MNT_ID_UNIQUE ? "unique" : "old",98name);99100return sx.stx_mnt_id;101}102103104static char root_mntpoint[] = "/tmp/statmount_test_root.XXXXXX";105static int orig_root;106static uint64_t root_id, parent_id;107static uint32_t old_root_id, old_parent_id;108static FILE *f_mountinfo;109110static void cleanup_namespace(void)111{112int ret;113114ret = fchdir(orig_root);115if (ret == -1)116ksft_perror("fchdir to original root");117118ret = chroot(".");119if (ret == -1)120ksft_perror("chroot to original root");121122umount2(root_mntpoint, MNT_DETACH);123rmdir(root_mntpoint);124}125126static void setup_namespace(void)127{128int ret;129char buf[32];130uid_t uid = getuid();131gid_t gid = getgid();132133ret = unshare(CLONE_NEWNS|CLONE_NEWUSER|CLONE_NEWPID);134if (ret == -1)135ksft_exit_fail_msg("unsharing mountns and userns: %s\n",136strerror(errno));137138sprintf(buf, "0 %d 1", uid);139write_file("/proc/self/uid_map", buf);140write_file("/proc/self/setgroups", "deny");141sprintf(buf, "0 %d 1", gid);142write_file("/proc/self/gid_map", buf);143144f_mountinfo = fopen("/proc/self/mountinfo", "re");145if (!f_mountinfo)146ksft_exit_fail_msg("failed to open mountinfo: %s\n",147strerror(errno));148149ret = mount("", "/", NULL, MS_REC|MS_PRIVATE, NULL);150if (ret == -1)151ksft_exit_fail_msg("making mount tree private: %s\n",152strerror(errno));153154if (!mkdtemp(root_mntpoint))155ksft_exit_fail_msg("creating temporary directory %s: %s\n",156root_mntpoint, strerror(errno));157158old_parent_id = get_mnt_id("parent", root_mntpoint, STATX_MNT_ID);159parent_id = get_mnt_id("parent", root_mntpoint, STATX_MNT_ID_UNIQUE);160161orig_root = open("/", O_PATH);162if (orig_root == -1)163ksft_exit_fail_msg("opening root directory: %s",164strerror(errno));165166atexit(cleanup_namespace);167168ret = mount(root_mntpoint, root_mntpoint, NULL, MS_BIND, NULL);169if (ret == -1)170ksft_exit_fail_msg("mounting temp root %s: %s\n",171root_mntpoint, strerror(errno));172173ret = chroot(root_mntpoint);174if (ret == -1)175ksft_exit_fail_msg("chroot to temp root %s: %s\n",176root_mntpoint, strerror(errno));177178ret = chdir("/");179if (ret == -1)180ksft_exit_fail_msg("chdir to root: %s\n", strerror(errno));181182old_root_id = get_mnt_id("root", "/", STATX_MNT_ID);183root_id = get_mnt_id("root", "/", STATX_MNT_ID_UNIQUE);184}185186static int setup_mount_tree(int log2_num)187{188int ret, i;189190ret = mount("", "/", NULL, MS_REC|MS_SHARED, NULL);191if (ret == -1) {192ksft_test_result_fail("making mount tree shared: %s\n",193strerror(errno));194return -1;195}196197for (i = 0; i < log2_num; i++) {198ret = mount("/", "/", NULL, MS_BIND, NULL);199if (ret == -1) {200ksft_test_result_fail("mounting submount %s: %s\n",201root_mntpoint, strerror(errno));202return -1;203}204}205return 0;206}207208static void test_listmount_empty_root(void)209{210ssize_t res;211const unsigned int size = 32;212uint64_t list[size];213214res = listmount(LSMT_ROOT, 0, 0, list, size, 0);215if (res == -1) {216ksft_test_result_fail("listmount: %s\n", strerror(errno));217return;218}219if (res != 1) {220ksft_test_result_fail("listmount result is %zi != 1\n", res);221return;222}223224if (list[0] != root_id) {225ksft_test_result_fail("listmount ID doesn't match 0x%llx != 0x%llx\n",226(unsigned long long) list[0],227(unsigned long long) root_id);228return;229}230231ksft_test_result_pass("listmount empty root\n");232}233234static void test_statmount_zero_mask(void)235{236struct statmount sm;237int ret;238239ret = statmount(root_id, 0, 0, &sm, sizeof(sm), 0);240if (ret == -1) {241ksft_test_result_fail("statmount zero mask: %s\n",242strerror(errno));243return;244}245if (sm.size != sizeof(sm)) {246ksft_test_result_fail("unexpected size: %u != %u\n",247sm.size, (uint32_t) sizeof(sm));248return;249}250if (sm.mask != 0) {251ksft_test_result_fail("unexpected mask: 0x%llx != 0x0\n",252(unsigned long long) sm.mask);253return;254}255256ksft_test_result_pass("statmount zero mask\n");257}258259static void test_statmount_mnt_basic(void)260{261struct statmount sm;262int ret;263uint64_t mask = STATMOUNT_MNT_BASIC;264265ret = statmount(root_id, 0, mask, &sm, sizeof(sm), 0);266if (ret == -1) {267ksft_test_result_fail("statmount mnt basic: %s\n",268strerror(errno));269return;270}271if (sm.size != sizeof(sm)) {272ksft_test_result_fail("unexpected size: %u != %u\n",273sm.size, (uint32_t) sizeof(sm));274return;275}276if (sm.mask != mask) {277ksft_test_result_skip("statmount mnt basic unavailable\n");278return;279}280281if (sm.mnt_id != root_id) {282ksft_test_result_fail("unexpected root ID: 0x%llx != 0x%llx\n",283(unsigned long long) sm.mnt_id,284(unsigned long long) root_id);285return;286}287288if (sm.mnt_id_old != old_root_id) {289ksft_test_result_fail("unexpected old root ID: %u != %u\n",290sm.mnt_id_old, old_root_id);291return;292}293294if (sm.mnt_parent_id != parent_id) {295ksft_test_result_fail("unexpected parent ID: 0x%llx != 0x%llx\n",296(unsigned long long) sm.mnt_parent_id,297(unsigned long long) parent_id);298return;299}300301if (sm.mnt_parent_id_old != old_parent_id) {302ksft_test_result_fail("unexpected old parent ID: %u != %u\n",303sm.mnt_parent_id_old, old_parent_id);304return;305}306307if (sm.mnt_propagation != MS_PRIVATE) {308ksft_test_result_fail("unexpected propagation: 0x%llx\n",309(unsigned long long) sm.mnt_propagation);310return;311}312313ksft_test_result_pass("statmount mnt basic\n");314}315316317static void test_statmount_sb_basic(void)318{319struct statmount sm;320int ret;321uint64_t mask = STATMOUNT_SB_BASIC;322struct statx sx;323struct statfs sf;324325ret = statmount(root_id, 0, mask, &sm, sizeof(sm), 0);326if (ret == -1) {327ksft_test_result_fail("statmount sb basic: %s\n",328strerror(errno));329return;330}331if (sm.size != sizeof(sm)) {332ksft_test_result_fail("unexpected size: %u != %u\n",333sm.size, (uint32_t) sizeof(sm));334return;335}336if (sm.mask != mask) {337ksft_test_result_skip("statmount sb basic unavailable\n");338return;339}340341ret = statx(AT_FDCWD, "/", 0, 0, &sx);342if (ret == -1) {343ksft_test_result_fail("stat root failed: %s\n",344strerror(errno));345return;346}347348if (sm.sb_dev_major != sx.stx_dev_major ||349sm.sb_dev_minor != sx.stx_dev_minor) {350ksft_test_result_fail("unexpected sb dev %u:%u != %u:%u\n",351sm.sb_dev_major, sm.sb_dev_minor,352sx.stx_dev_major, sx.stx_dev_minor);353return;354}355356ret = statfs("/", &sf);357if (ret == -1) {358ksft_test_result_fail("statfs root failed: %s\n",359strerror(errno));360return;361}362363if (sm.sb_magic != sf.f_type) {364ksft_test_result_fail("unexpected sb magic: 0x%llx != 0x%lx\n",365(unsigned long long) sm.sb_magic,366sf.f_type);367return;368}369370ksft_test_result_pass("statmount sb basic\n");371}372373static void test_statmount_mnt_point(void)374{375struct statmount *sm;376377sm = statmount_alloc(root_id, STATMOUNT_MNT_POINT, 0);378if (!sm) {379ksft_test_result_fail("statmount mount point: %s\n",380strerror(errno));381return;382}383384if (!(sm->mask & STATMOUNT_MNT_POINT)) {385ksft_test_result_fail("missing STATMOUNT_MNT_POINT in mask\n");386return;387}388if (strcmp(sm->str + sm->mnt_point, "/") != 0) {389ksft_test_result_fail("unexpected mount point: '%s' != '/'\n",390sm->str + sm->mnt_point);391goto out;392}393ksft_test_result_pass("statmount mount point\n");394out:395free(sm);396}397398static void test_statmount_mnt_root(void)399{400struct statmount *sm;401const char *mnt_root, *last_dir, *last_root;402403last_dir = strrchr(root_mntpoint, '/');404assert(last_dir);405last_dir++;406407sm = statmount_alloc(root_id, STATMOUNT_MNT_ROOT, 0);408if (!sm) {409ksft_test_result_fail("statmount mount root: %s\n",410strerror(errno));411return;412}413if (!(sm->mask & STATMOUNT_MNT_ROOT)) {414ksft_test_result_fail("missing STATMOUNT_MNT_ROOT in mask\n");415return;416}417mnt_root = sm->str + sm->mnt_root;418last_root = strrchr(mnt_root, '/');419if (last_root)420last_root++;421else422last_root = mnt_root;423424if (strcmp(last_dir, last_root) != 0) {425ksft_test_result_fail("unexpected mount root last component: '%s' != '%s'\n",426last_root, last_dir);427goto out;428}429ksft_test_result_pass("statmount mount root\n");430out:431free(sm);432}433434static void test_statmount_fs_type(void)435{436struct statmount *sm;437const char *fs_type;438const char *const *s;439440sm = statmount_alloc(root_id, STATMOUNT_FS_TYPE, 0);441if (!sm) {442ksft_test_result_fail("statmount fs type: %s\n",443strerror(errno));444return;445}446if (!(sm->mask & STATMOUNT_FS_TYPE)) {447ksft_test_result_fail("missing STATMOUNT_FS_TYPE in mask\n");448return;449}450fs_type = sm->str + sm->fs_type;451for (s = known_fs; s != NULL; s++) {452if (strcmp(fs_type, *s) == 0)453break;454}455if (!s)456ksft_print_msg("unknown filesystem type: %s\n", fs_type);457458ksft_test_result_pass("statmount fs type\n");459free(sm);460}461462static void test_statmount_mnt_opts(void)463{464struct statmount *sm;465const char *statmount_opts;466char *line = NULL;467size_t len = 0;468469sm = statmount_alloc(root_id, STATMOUNT_MNT_BASIC | STATMOUNT_MNT_OPTS,4700);471if (!sm) {472ksft_test_result_fail("statmount mnt opts: %s\n",473strerror(errno));474return;475}476477if (!(sm->mask & STATMOUNT_MNT_BASIC)) {478ksft_test_result_fail("missing STATMOUNT_MNT_BASIC in mask\n");479return;480}481482while (getline(&line, &len, f_mountinfo) != -1) {483int i;484char *p, *p2;485unsigned int old_mnt_id;486487old_mnt_id = atoi(line);488if (old_mnt_id != sm->mnt_id_old)489continue;490491for (p = line, i = 0; p && i < 5; i++)492p = strchr(p + 1, ' ');493if (!p)494continue;495496p2 = strchr(p + 1, ' ');497if (!p2)498continue;499*p2 = '\0';500p = strchr(p2 + 1, '-');501if (!p)502continue;503for (p++, i = 0; p && i < 2; i++)504p = strchr(p + 1, ' ');505if (!p)506continue;507p++;508509/* skip generic superblock options */510if (strncmp(p, "ro", 2) == 0)511p += 2;512else if (strncmp(p, "rw", 2) == 0)513p += 2;514if (*p == ',')515p++;516if (strncmp(p, "sync", 4) == 0)517p += 4;518if (*p == ',')519p++;520if (strncmp(p, "dirsync", 7) == 0)521p += 7;522if (*p == ',')523p++;524if (strncmp(p, "lazytime", 8) == 0)525p += 8;526if (*p == ',')527p++;528p2 = strrchr(p, '\n');529if (p2)530*p2 = '\0';531532if (sm->mask & STATMOUNT_MNT_OPTS)533statmount_opts = sm->str + sm->mnt_opts;534else535statmount_opts = "";536if (strcmp(statmount_opts, p) != 0)537ksft_test_result_fail(538"unexpected mount options: '%s' != '%s'\n",539statmount_opts, p);540else541ksft_test_result_pass("statmount mount options\n");542free(sm);543free(line);544return;545}546547ksft_test_result_fail("didnt't find mount entry\n");548free(sm);549free(line);550}551552static void test_statmount_string(uint64_t mask, size_t off, const char *name)553{554struct statmount *sm;555size_t len, shortsize, exactsize;556uint32_t start, i;557int ret;558559sm = statmount_alloc(root_id, mask, 0);560if (!sm) {561ksft_test_result_fail("statmount %s: %s\n", name,562strerror(errno));563goto out;564}565if (sm->size < sizeof(*sm)) {566ksft_test_result_fail("unexpected size: %u < %u\n",567sm->size, (uint32_t) sizeof(*sm));568goto out;569}570if (sm->mask != mask) {571ksft_test_result_skip("statmount %s unavailable\n", name);572goto out;573}574len = sm->size - sizeof(*sm);575start = ((uint32_t *) sm)[off];576577for (i = start;; i++) {578if (i >= len) {579ksft_test_result_fail("string out of bounds\n");580goto out;581}582if (!sm->str[i])583break;584}585exactsize = sm->size;586shortsize = sizeof(*sm) + i;587588ret = statmount(root_id, 0, mask, sm, exactsize, 0);589if (ret == -1) {590ksft_test_result_fail("statmount exact size: %s\n",591strerror(errno));592goto out;593}594errno = 0;595ret = statmount(root_id, 0, mask, sm, shortsize, 0);596if (ret != -1 || errno != EOVERFLOW) {597ksft_test_result_fail("should have failed with EOVERFLOW: %s\n",598strerror(errno));599goto out;600}601602ksft_test_result_pass("statmount string %s\n", name);603out:604free(sm);605}606607static void test_listmount_tree(void)608{609ssize_t res;610const unsigned int log2_num = 4;611const unsigned int step = 3;612const unsigned int size = (1 << log2_num) + step + 1;613size_t num, expect = 1 << log2_num;614uint64_t list[size];615uint64_t list2[size];616size_t i;617618619res = setup_mount_tree(log2_num);620if (res == -1)621return;622623num = res = listmount(LSMT_ROOT, 0, 0, list, size, 0);624if (res == -1) {625ksft_test_result_fail("listmount: %s\n", strerror(errno));626return;627}628if (num != expect) {629ksft_test_result_fail("listmount result is %zi != %zi\n",630res, expect);631return;632}633634for (i = 0; i < size - step;) {635res = listmount(LSMT_ROOT, 0, i ? list2[i - 1] : 0, list2 + i, step, 0);636if (res == -1)637ksft_test_result_fail("short listmount: %s\n",638strerror(errno));639i += res;640if (res < step)641break;642}643if (i != num) {644ksft_test_result_fail("different number of entries: %zu != %zu\n",645i, num);646return;647}648for (i = 0; i < num; i++) {649if (list2[i] != list[i]) {650ksft_test_result_fail("different value for entry %zu: 0x%llx != 0x%llx\n",651i,652(unsigned long long) list2[i],653(unsigned long long) list[i]);654}655}656657ksft_test_result_pass("listmount tree\n");658}659660#define str_off(memb) (offsetof(struct statmount, memb) / sizeof(uint32_t))661662int main(void)663{664int ret;665uint64_t all_mask = STATMOUNT_SB_BASIC | STATMOUNT_MNT_BASIC |666STATMOUNT_PROPAGATE_FROM | STATMOUNT_MNT_ROOT |667STATMOUNT_MNT_POINT | STATMOUNT_FS_TYPE | STATMOUNT_MNT_NS_ID;668669ksft_print_header();670671ret = statmount(0, 0, 0, NULL, 0, 0);672assert(ret == -1);673if (errno == ENOSYS)674ksft_exit_skip("statmount() syscall not supported\n");675676setup_namespace();677678ksft_set_plan(15);679test_listmount_empty_root();680test_statmount_zero_mask();681test_statmount_mnt_basic();682test_statmount_sb_basic();683test_statmount_mnt_root();684test_statmount_mnt_point();685test_statmount_fs_type();686test_statmount_mnt_opts();687test_statmount_string(STATMOUNT_MNT_ROOT, str_off(mnt_root), "mount root");688test_statmount_string(STATMOUNT_MNT_POINT, str_off(mnt_point), "mount point");689test_statmount_string(STATMOUNT_FS_TYPE, str_off(fs_type), "fs type");690test_statmount_string(all_mask, str_off(mnt_root), "mount root & all");691test_statmount_string(all_mask, str_off(mnt_point), "mount point & all");692test_statmount_string(all_mask, str_off(fs_type), "fs type & all");693694test_listmount_tree();695696697if (ksft_get_fail_cnt() + ksft_get_error_cnt() > 0)698ksft_exit_fail();699else700ksft_exit_pass();701}702703704