mirror of
https://github.com/torvalds/linux.git
synced 2026-09-23 13:14:02 +02:00
selftests/filesystems: add ustat() coverage
user_get_super() is now backed by the global device-to-superblock table instead of a walk of the super_blocks list. ustat(2) is its most direct user-visible consumer but nothing in the tree exercises it. Add a small regression test: the device number of a mounted tmpfs (an anonymous device, registered in the table by sget_fc()) must resolve, it must stop resolving after the unmount (the entry is dropped again in kill_super_notify()), and bogus device numbers keep reporting EINVAL. The test passes on kernels before the conversion: it pins down the semantics the table-backed lookup must preserve. Link: https://patch.msgid.link/20260616-work-super-bdev_holder_global-v2-18-7df6b864028e@kernel.org Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
This commit is contained in:
parent
aafb9e8c01
commit
a21db4dcaf
|
|
@ -5,3 +5,4 @@ fclog
|
|||
file_stressor
|
||||
anon_inode_test
|
||||
kernfs_test
|
||||
ustat_test
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
CFLAGS += $(KHDR_INCLUDES)
|
||||
TEST_GEN_PROGS := devpts_pts file_stressor anon_inode_test kernfs_test fclog
|
||||
TEST_GEN_PROGS := devpts_pts file_stressor anon_inode_test kernfs_test fclog ustat_test
|
||||
TEST_GEN_PROGS_EXTENDED := dnotify_test
|
||||
|
||||
include ../lib.mk
|
||||
|
|
|
|||
135
tools/testing/selftests/filesystems/ustat_test.c
Normal file
135
tools/testing/selftests/filesystems/ustat_test.c
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Test ustat(2): looking up superblocks by device number.
|
||||
*
|
||||
* ustat() resolves a device number to a mounted superblock via
|
||||
* user_get_super(). Check that the device number of a mounted tmpfs (an
|
||||
* anonymous device) resolves, that it stops resolving once the filesystem
|
||||
* is unmounted and that bogus device numbers report EINVAL.
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sched.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../kselftest_harness.h"
|
||||
|
||||
/* struct ustat is not exported through UAPI, mirror include/linux/types.h. */
|
||||
struct ustat_buf {
|
||||
int f_tfree;
|
||||
unsigned long f_tinode;
|
||||
char f_fname[6];
|
||||
char f_fpack[6];
|
||||
/* slack in case an architecture lays the struct out differently */
|
||||
char pad[64];
|
||||
};
|
||||
|
||||
#ifdef __NR_ustat
|
||||
|
||||
/*
|
||||
* The kernel decodes @dev with new_decode_dev(), which matches the low 32
|
||||
* bits of the st_dev encoding stat(2) returns for any major below 4096.
|
||||
*/
|
||||
static int sys_ustat(unsigned int dev, struct ustat_buf *buf)
|
||||
{
|
||||
return syscall(__NR_ustat, dev, buf);
|
||||
}
|
||||
|
||||
static int write_string(const char *path, const char *string)
|
||||
{
|
||||
ssize_t len = strlen(string);
|
||||
int fd;
|
||||
|
||||
fd = open(path, O_WRONLY);
|
||||
if (fd < 0)
|
||||
return -1;
|
||||
if (write(fd, string, len) != len) {
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
return close(fd);
|
||||
}
|
||||
|
||||
/* Enter namespaces in which mounting a tmpfs instance is allowed. */
|
||||
static int setup_namespaces(void)
|
||||
{
|
||||
uid_t uid = getuid();
|
||||
gid_t gid = getgid();
|
||||
char map[64];
|
||||
|
||||
if (unshare(CLONE_NEWNS | (uid ? CLONE_NEWUSER : 0)))
|
||||
return -1;
|
||||
|
||||
if (uid) {
|
||||
if (write_string("/proc/self/setgroups", "deny"))
|
||||
return -1;
|
||||
snprintf(map, sizeof(map), "0 %d 1", uid);
|
||||
if (write_string("/proc/self/uid_map", map))
|
||||
return -1;
|
||||
snprintf(map, sizeof(map), "0 %d 1", gid);
|
||||
if (write_string("/proc/self/gid_map", map))
|
||||
return -1;
|
||||
}
|
||||
|
||||
return mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL);
|
||||
}
|
||||
|
||||
TEST(resolves_mounted_superblock)
|
||||
{
|
||||
char dir[] = "/tmp/ustat_test.XXXXXX";
|
||||
struct ustat_buf ub;
|
||||
struct stat st;
|
||||
|
||||
ASSERT_NE(NULL, mkdtemp(dir));
|
||||
|
||||
if (setup_namespaces()) {
|
||||
rmdir(dir);
|
||||
SKIP(return, "cannot set up namespaces: %s", strerror(errno));
|
||||
}
|
||||
|
||||
ASSERT_EQ(0, mount("ustat_test", dir, "tmpfs", 0, NULL));
|
||||
ASSERT_EQ(0, stat(dir, &st));
|
||||
|
||||
memset(&ub, 0xff, sizeof(ub));
|
||||
ASSERT_EQ(0, sys_ustat(st.st_dev, &ub))
|
||||
TH_LOG("ustat(%u): %s", (unsigned int)st.st_dev,
|
||||
strerror(errno));
|
||||
|
||||
ASSERT_EQ(0, umount(dir));
|
||||
|
||||
/* The unmount removed the superblock, the device is gone. */
|
||||
ASSERT_EQ(-1, sys_ustat(st.st_dev, &ub));
|
||||
ASSERT_EQ(EINVAL, errno);
|
||||
|
||||
rmdir(dir);
|
||||
}
|
||||
|
||||
TEST(bogus_device_numbers)
|
||||
{
|
||||
struct ustat_buf ub;
|
||||
|
||||
ASSERT_EQ(-1, sys_ustat(0, &ub));
|
||||
ASSERT_EQ(EINVAL, errno);
|
||||
|
||||
/* major 4095, minor 1048575: nothing plausible lives there */
|
||||
ASSERT_EQ(-1, sys_ustat((0xfffu << 8) | 0xffu | (0xfff00u << 12), &ub));
|
||||
ASSERT_EQ(EINVAL, errno);
|
||||
}
|
||||
|
||||
#else /* !__NR_ustat */
|
||||
|
||||
TEST(unsupported)
|
||||
{
|
||||
SKIP(return, "ustat(2) is not available on this architecture");
|
||||
}
|
||||
|
||||
#endif /* __NR_ustat */
|
||||
|
||||
TEST_HARNESS_MAIN
|
||||
Loading…
Reference in New Issue
Block a user