selftests: statmount: tests for STATMOUNT_BY_FD

Add tests for STATMOUNT_BY_FD flag, which adds support for passing a
file descriptors to statmount(). The fd can also be on a "unmounted"
mount (mount unmounted with MNT_DETACH), we also include tests for that.

Co-developed-by: Andrei Vagin <avagin@gmail.com>
Signed-off-by: Andrei Vagin <avagin@gmail.com>
Signed-off-by: Bhavik Sachdev <b.sachdev1904@gmail.com>
Link: https://patch.msgid.link/20251129091455.757724-4-b.sachdev1904@gmail.com
Signed-off-by: Christian Brauner <brauner@kernel.org>
This commit is contained in:
Bhavik Sachdev 2025-11-29 14:41:22 +05:30 committed by Christian Brauner
parent 0e5032237e
commit 0c82fdbbbf
No known key found for this signature in database
GPG Key ID: 91C61BC06578DCA2
3 changed files with 354 additions and 23 deletions

View File

@ -43,19 +43,24 @@
#endif
#endif
static inline int statmount(uint64_t mnt_id, uint64_t mnt_ns_id, uint64_t mask,
struct statmount *buf, size_t bufsize,
static inline int statmount(uint64_t mnt_id, uint64_t mnt_ns_id, uint32_t fd,
uint64_t mask, struct statmount *buf, size_t bufsize,
unsigned int flags)
{
struct mnt_id_req req = {
.size = MNT_ID_REQ_SIZE_VER0,
.mnt_id = mnt_id,
.param = mask,
};
if (mnt_ns_id) {
if (flags & STATMOUNT_BY_FD) {
req.size = MNT_ID_REQ_SIZE_VER1;
req.mnt_ns_id = mnt_ns_id;
req.mnt_fd = fd;
} else {
req.mnt_id = mnt_id;
if (mnt_ns_id) {
req.size = MNT_ID_REQ_SIZE_VER1;
req.mnt_ns_id = mnt_ns_id;
}
}
return syscall(__NR_statmount, &req, buf, bufsize, flags);

View File

@ -33,15 +33,24 @@ static const char *const known_fs[] = {
"sysv", "tmpfs", "tracefs", "ubifs", "udf", "ufs", "v7", "vboxsf",
"vfat", "virtiofs", "vxfs", "xenfs", "xfs", "zonefs", NULL };
static struct statmount *statmount_alloc(uint64_t mnt_id, uint64_t mask, unsigned int flags)
static struct statmount *statmount_alloc(uint64_t mnt_id, int fd, uint64_t mask, unsigned int flags)
{
size_t bufsize = 1 << 15;
struct statmount *buf = NULL, *tmp = alloca(bufsize);
struct statmount *buf = NULL, *tmp = NULL;
int tofree = 0;
int ret;
if (flags & STATMOUNT_BY_FD && fd < 0)
return NULL;
tmp = alloca(bufsize);
for (;;) {
ret = statmount(mnt_id, 0, mask, tmp, bufsize, flags);
if (flags & STATMOUNT_BY_FD)
ret = statmount(0, 0, (uint32_t) fd, mask, tmp, bufsize, flags);
else
ret = statmount(mnt_id, 0, 0, mask, tmp, bufsize, flags);
if (ret != -1)
break;
if (tofree)
@ -237,7 +246,7 @@ static void test_statmount_zero_mask(void)
struct statmount sm;
int ret;
ret = statmount(root_id, 0, 0, &sm, sizeof(sm), 0);
ret = statmount(root_id, 0, 0, 0, &sm, sizeof(sm), 0);
if (ret == -1) {
ksft_test_result_fail("statmount zero mask: %s\n",
strerror(errno));
@ -263,7 +272,7 @@ static void test_statmount_mnt_basic(void)
int ret;
uint64_t mask = STATMOUNT_MNT_BASIC;
ret = statmount(root_id, 0, mask, &sm, sizeof(sm), 0);
ret = statmount(root_id, 0, 0, mask, &sm, sizeof(sm), 0);
if (ret == -1) {
ksft_test_result_fail("statmount mnt basic: %s\n",
strerror(errno));
@ -323,7 +332,7 @@ static void test_statmount_sb_basic(void)
struct statx sx;
struct statfs sf;
ret = statmount(root_id, 0, mask, &sm, sizeof(sm), 0);
ret = statmount(root_id, 0, 0, mask, &sm, sizeof(sm), 0);
if (ret == -1) {
ksft_test_result_fail("statmount sb basic: %s\n",
strerror(errno));
@ -375,7 +384,7 @@ static void test_statmount_mnt_point(void)
{
struct statmount *sm;
sm = statmount_alloc(root_id, STATMOUNT_MNT_POINT, 0);
sm = statmount_alloc(root_id, 0, STATMOUNT_MNT_POINT, 0);
if (!sm) {
ksft_test_result_fail("statmount mount point: %s\n",
strerror(errno));
@ -405,7 +414,7 @@ static void test_statmount_mnt_root(void)
assert(last_dir);
last_dir++;
sm = statmount_alloc(root_id, STATMOUNT_MNT_ROOT, 0);
sm = statmount_alloc(root_id, 0, STATMOUNT_MNT_ROOT, 0);
if (!sm) {
ksft_test_result_fail("statmount mount root: %s\n",
strerror(errno));
@ -438,7 +447,7 @@ static void test_statmount_fs_type(void)
const char *fs_type;
const char *const *s;
sm = statmount_alloc(root_id, STATMOUNT_FS_TYPE, 0);
sm = statmount_alloc(root_id, 0, STATMOUNT_FS_TYPE, 0);
if (!sm) {
ksft_test_result_fail("statmount fs type: %s\n",
strerror(errno));
@ -467,7 +476,7 @@ static void test_statmount_mnt_opts(void)
char *line = NULL;
size_t len = 0;
sm = statmount_alloc(root_id, STATMOUNT_MNT_BASIC | STATMOUNT_MNT_OPTS,
sm = statmount_alloc(root_id, 0, STATMOUNT_MNT_BASIC | STATMOUNT_MNT_OPTS,
0);
if (!sm) {
ksft_test_result_fail("statmount mnt opts: %s\n",
@ -557,7 +566,7 @@ static void test_statmount_string(uint64_t mask, size_t off, const char *name)
uint32_t start, i;
int ret;
sm = statmount_alloc(root_id, mask, 0);
sm = statmount_alloc(root_id, 0, mask, 0);
if (!sm) {
ksft_test_result_fail("statmount %s: %s\n", name,
strerror(errno));
@ -586,14 +595,14 @@ static void test_statmount_string(uint64_t mask, size_t off, const char *name)
exactsize = sm->size;
shortsize = sizeof(*sm) + i;
ret = statmount(root_id, 0, mask, sm, exactsize, 0);
ret = statmount(root_id, 0, 0, mask, sm, exactsize, 0);
if (ret == -1) {
ksft_test_result_fail("statmount exact size: %s\n",
strerror(errno));
goto out;
}
errno = 0;
ret = statmount(root_id, 0, mask, sm, shortsize, 0);
ret = statmount(root_id, 0, 0, mask, sm, shortsize, 0);
if (ret != -1 || errno != EOVERFLOW) {
ksft_test_result_fail("should have failed with EOVERFLOW: %s\n",
strerror(errno));
@ -658,6 +667,226 @@ static void test_listmount_tree(void)
ksft_test_result_pass("listmount tree\n");
}
static void test_statmount_by_fd(void)
{
struct statmount *sm = NULL;
char tmpdir[] = "/statmount.fd.XXXXXX";
const char root[] = "/test";
char subdir[PATH_MAX], tmproot[PATH_MAX];
int fd;
if (!mkdtemp(tmpdir)) {
ksft_perror("mkdtemp");
return;
}
if (mount("statmount.test", tmpdir, "tmpfs", 0, NULL)) {
ksft_perror("mount");
rmdir(tmpdir);
return;
}
snprintf(subdir, PATH_MAX, "%s%s", tmpdir, root);
snprintf(tmproot, PATH_MAX, "%s/%s", tmpdir, "chroot");
if (mkdir(subdir, 0755)) {
ksft_perror("mkdir");
goto err_tmpdir;
}
if (mount(subdir, subdir, NULL, MS_BIND, 0)) {
ksft_perror("mount");
goto err_subdir;
}
if (mkdir(tmproot, 0755)) {
ksft_perror("mkdir");
goto err_subdir;
}
fd = open(subdir, O_PATH);
if (fd < 0) {
ksft_perror("open");
goto err_tmproot;
}
if (chroot(tmproot)) {
ksft_perror("chroot");
goto err_fd;
}
sm = statmount_alloc(0, fd, STATMOUNT_MNT_ROOT | STATMOUNT_MNT_POINT, STATMOUNT_BY_FD);
if (!sm) {
ksft_test_result_fail("statmount by fd failed: %s\n", strerror(errno));
goto err_chroot;
}
if (sm->size < sizeof(*sm)) {
ksft_test_result_fail("unexpected size: %u < %u\n",
sm->size, (uint32_t) sizeof(*sm));
goto err_chroot;
}
if (sm->mask & STATMOUNT_MNT_POINT) {
ksft_test_result_fail("STATMOUNT_MNT_POINT unexpectedly set in statmount\n");
goto err_chroot;
}
if (!(sm->mask & STATMOUNT_MNT_ROOT)) {
ksft_test_result_fail("STATMOUNT_MNT_ROOT not set in statmount\n");
goto err_chroot;
}
if (strcmp(root, sm->str + sm->mnt_root) != 0) {
ksft_test_result_fail("statmount returned incorrect mnt_root,"
"statmount mnt_root: %s != %s\n",
sm->str + sm->mnt_root, root);
goto err_chroot;
}
if (chroot(".")) {
ksft_perror("chroot");
goto out;
}
free(sm);
sm = statmount_alloc(0, fd, STATMOUNT_MNT_ROOT | STATMOUNT_MNT_POINT, STATMOUNT_BY_FD);
if (!sm) {
ksft_test_result_fail("statmount by fd failed: %s\n", strerror(errno));
goto err_fd;
}
if (sm->size < sizeof(*sm)) {
ksft_test_result_fail("unexpected size: %u < %u\n",
sm->size, (uint32_t) sizeof(*sm));
goto out;
}
if (!(sm->mask & STATMOUNT_MNT_POINT)) {
ksft_test_result_fail("STATMOUNT_MNT_POINT not set in statmount\n");
goto out;
}
if (!(sm->mask & STATMOUNT_MNT_ROOT)) {
ksft_test_result_fail("STATMOUNT_MNT_ROOT not set in statmount\n");
goto out;
}
if (strcmp(subdir, sm->str + sm->mnt_point) != 0) {
ksft_test_result_fail("statmount returned incorrect mnt_point,"
"statmount mnt_point: %s != %s\n", sm->str + sm->mnt_point, subdir);
goto out;
}
if (strcmp(root, sm->str + sm->mnt_root) != 0) {
ksft_test_result_fail("statmount returned incorrect mnt_root,"
"statmount mnt_root: %s != %s\n", sm->str + sm->mnt_root, root);
goto out;
}
ksft_test_result_pass("statmount by fd\n");
goto out;
err_chroot:
chroot(".");
out:
free(sm);
err_fd:
close(fd);
err_tmproot:
rmdir(tmproot);
err_subdir:
umount2(subdir, MNT_DETACH);
rmdir(subdir);
err_tmpdir:
umount2(tmpdir, MNT_DETACH);
rmdir(tmpdir);
}
static void test_statmount_by_fd_unmounted(void)
{
const char root[] = "/test.unmounted";
char tmpdir[] = "/statmount.fd.XXXXXX";
char subdir[PATH_MAX];
int fd;
struct statmount *sm = NULL;
if (!mkdtemp(tmpdir)) {
ksft_perror("mkdtemp");
return;
}
if (mount("statmount.test", tmpdir, "tmpfs", 0, NULL)) {
ksft_perror("mount");
rmdir(tmpdir);
return;
}
snprintf(subdir, PATH_MAX, "%s%s", tmpdir, root);
if (mkdir(subdir, 0755)) {
ksft_perror("mkdir");
goto err_tmpdir;
}
if (mount(subdir, subdir, 0, MS_BIND, NULL)) {
ksft_perror("mount");
goto err_subdir;
}
fd = open(subdir, O_PATH);
if (fd < 0) {
ksft_perror("open");
goto err_subdir;
}
if (umount2(tmpdir, MNT_DETACH)) {
ksft_perror("umount2");
goto err_fd;
}
sm = statmount_alloc(0, fd, STATMOUNT_MNT_POINT | STATMOUNT_MNT_ROOT, STATMOUNT_BY_FD);
if (!sm) {
ksft_test_result_fail("statmount by fd unmounted: %s\n",
strerror(errno));
goto err_sm;
}
if (sm->size < sizeof(*sm)) {
ksft_test_result_fail("unexpected size: %u < %u\n",
sm->size, (uint32_t) sizeof(*sm));
goto err_sm;
}
if (sm->mask & STATMOUNT_MNT_POINT) {
ksft_test_result_fail("STATMOUNT_MNT_POINT unexpectedly set in mask\n");
goto err_sm;
}
if (!(sm->mask & STATMOUNT_MNT_ROOT)) {
ksft_test_result_fail("STATMOUNT_MNT_ROOT not set in mask\n");
goto err_sm;
}
if (strcmp(sm->str + sm->mnt_root, root) != 0) {
ksft_test_result_fail("statmount returned incorrect mnt_root,"
"statmount mnt_root: %s != %s\n",
sm->str + sm->mnt_root, root);
goto err_sm;
}
ksft_test_result_pass("statmount by fd on unmounted mount\n");
err_sm:
free(sm);
err_fd:
close(fd);
err_subdir:
umount2(subdir, MNT_DETACH);
rmdir(subdir);
err_tmpdir:
umount2(tmpdir, MNT_DETACH);
rmdir(tmpdir);
}
#define str_off(memb) (offsetof(struct statmount, memb) / sizeof(uint32_t))
int main(void)
@ -669,14 +898,14 @@ int main(void)
ksft_print_header();
ret = statmount(0, 0, 0, NULL, 0, 0);
ret = statmount(0, 0, 0, 0, NULL, 0, 0);
assert(ret == -1);
if (errno == ENOSYS)
ksft_exit_skip("statmount() syscall not supported\n");
setup_namespace();
ksft_set_plan(15);
ksft_set_plan(17);
test_listmount_empty_root();
test_statmount_zero_mask();
test_statmount_mnt_basic();
@ -693,6 +922,8 @@ int main(void)
test_statmount_string(all_mask, str_off(fs_type), "fs type & all");
test_listmount_tree();
test_statmount_by_fd_unmounted();
test_statmount_by_fd();
if (ksft_get_fail_cnt() + ksft_get_error_cnt() > 0)

View File

@ -102,7 +102,7 @@ static int _test_statmount_mnt_ns_id(void)
if (!root_id)
return NSID_ERROR;
ret = statmount(root_id, 0, STATMOUNT_MNT_NS_ID, &sm, sizeof(sm), 0);
ret = statmount(root_id, 0, 0, STATMOUNT_MNT_NS_ID, &sm, sizeof(sm), 0);
if (ret == -1) {
ksft_print_msg("statmount mnt ns id: %s\n", strerror(errno));
return NSID_ERROR;
@ -128,6 +128,98 @@ static int _test_statmount_mnt_ns_id(void)
return NSID_PASS;
}
static int _test_statmount_mnt_ns_id_by_fd(void)
{
struct statmount sm;
uint64_t mnt_ns_id;
int ret, fd, mounted = 1, status = NSID_ERROR;
char mnt[] = "/statmount.fd.XXXXXX";
ret = get_mnt_ns_id("/proc/self/ns/mnt", &mnt_ns_id);
if (ret != NSID_PASS)
return ret;
if (!mkdtemp(mnt)) {
ksft_print_msg("statmount by fd mnt ns id mkdtemp: %s\n", strerror(errno));
return NSID_ERROR;
}
if (mount(mnt, mnt, NULL, MS_BIND, 0)) {
ksft_print_msg("statmount by fd mnt ns id mount: %s\n", strerror(errno));
status = NSID_ERROR;
goto err;
}
fd = open(mnt, O_PATH);
if (fd < 0) {
ksft_print_msg("statmount by fd mnt ns id open: %s\n", strerror(errno));
goto err;
}
ret = statmount(0, 0, fd, STATMOUNT_MNT_NS_ID, &sm, sizeof(sm), STATMOUNT_BY_FD);
if (ret == -1) {
ksft_print_msg("statmount mnt ns id statmount: %s\n", strerror(errno));
status = NSID_ERROR;
goto out;
}
if (sm.size != sizeof(sm)) {
ksft_print_msg("unexpected size: %u != %u\n", sm.size,
(uint32_t)sizeof(sm));
status = NSID_FAIL;
goto out;
}
if (sm.mask != STATMOUNT_MNT_NS_ID) {
ksft_print_msg("statmount mnt ns id unavailable\n");
status = NSID_SKIP;
goto out;
}
if (sm.mnt_ns_id != mnt_ns_id) {
ksft_print_msg("unexpected mnt ns ID: 0x%llx != 0x%llx\n",
(unsigned long long)sm.mnt_ns_id,
(unsigned long long)mnt_ns_id);
status = NSID_FAIL;
goto out;
}
mounted = 0;
if (umount2(mnt, MNT_DETACH)) {
ksft_print_msg("statmount by fd mnt ns id umount2: %s\n", strerror(errno));
goto out;
}
ret = statmount(0, 0, fd, STATMOUNT_MNT_NS_ID, &sm, sizeof(sm), STATMOUNT_BY_FD);
if (ret == -1) {
ksft_print_msg("statmount mnt ns id statmount: %s\n", strerror(errno));
status = NSID_ERROR;
goto out;
}
if (sm.size != sizeof(sm)) {
ksft_print_msg("unexpected size: %u != %u\n", sm.size,
(uint32_t)sizeof(sm));
status = NSID_FAIL;
goto out;
}
if (sm.mask == STATMOUNT_MNT_NS_ID) {
ksft_print_msg("unexpected STATMOUNT_MNT_NS_ID in mask\n");
status = NSID_FAIL;
goto out;
}
status = NSID_PASS;
out:
close(fd);
if (mounted)
umount2(mnt, MNT_DETACH);
err:
rmdir(mnt);
return status;
}
static void test_statmount_mnt_ns_id(void)
{
pid_t pid;
@ -148,6 +240,9 @@ static void test_statmount_mnt_ns_id(void)
if (ret != NSID_PASS)
exit(ret);
ret = _test_statmount_mnt_ns_id();
if (ret != NSID_PASS)
exit(ret);
ret = _test_statmount_mnt_ns_id_by_fd();
exit(ret);
}
@ -179,7 +274,7 @@ static int validate_external_listmount(pid_t pid, uint64_t child_nr_mounts)
for (int i = 0; i < nr_mounts; i++) {
struct statmount sm;
ret = statmount(list[i], mnt_ns_id, STATMOUNT_MNT_NS_ID, &sm,
ret = statmount(list[i], mnt_ns_id, 0, STATMOUNT_MNT_NS_ID, &sm,
sizeof(sm), 0);
if (ret < 0) {
ksft_print_msg("statmount mnt ns id: %s\n", strerror(errno));
@ -275,7 +370,7 @@ int main(void)
int ret;
ksft_print_header();
ret = statmount(0, 0, 0, NULL, 0, 0);
ret = statmount(0, 0, 0, 0, NULL, 0, 0);
assert(ret == -1);
if (errno == ENOSYS)
ksft_exit_skip("statmount() syscall not supported\n");