Unverified Commit 0c82fdbb authored by Bhavik Sachdev's avatar Bhavik Sachdev Committed by Christian Brauner
Browse files

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: default avatarAndrei Vagin <avagin@gmail.com>
Signed-off-by: default avatarAndrei Vagin <avagin@gmail.com>
Signed-off-by: default avatarBhavik Sachdev <b.sachdev1904@gmail.com>
Link: https://patch.msgid.link/20251129091455.757724-4-b.sachdev1904@gmail.com


Signed-off-by: default avatarChristian Brauner <brauner@kernel.org>
parent 0e503223
Loading
Loading
Loading
Loading
+10 −5
Original line number Diff line number Diff line
@@ -43,20 +43,25 @@
	#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 (flags & STATMOUNT_BY_FD) {
		req.size = MNT_ID_REQ_SIZE_VER1;
		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);
}
+246 −15
Original line number Diff line number Diff line
@@ -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)
+98 −3
Original line number Diff line number Diff line
@@ -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");