diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-17 13:57:04 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-17 13:57:04 -0700 |
| commit | 1781f0b3d75caa22376cf7fa0224f9aca696580d (patch) | |
| tree | 042a9acf224b96254f38829630c05ee7dd7b2970 /tools/testing | |
| parent | aaed66fadba2d2de8fe0daa0aa3eac827d2076b9 (diff) | |
| parent | 9ac8fd831252e52aa78399eaccc11a72f6c2af1a (diff) | |
Merge tag 'vfs-7.3-rc1.super' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs
Pull vfs superblock updates from Christian Brauner:
- Make it possible to share a block device between multiple
filesystems.
erofs can mount read-only blob devices shared between many
superblocks, but because we only tracked a single superblock a
freeze, thaw, removal or sync on such a device was never propagated
to all the superblocks using it, and there was no way to find them.
Add an efficient table to lookup all superblocks using a given block
device.
- A bunch of pre-existing fixes fell out of this work:
A block-device freeze racing a btrfs device change could leave the
whole filesystem stuck frozen. A bdev_freeze() issued by "dmsetup
suspend" or an LVM snapshot resolves that holder to freeze the
filesystem. and bdev_thaw() resolves it again to thaw. A freeze
landing while btrfs is adding, removing or replacing a device freezes
the filesystem. The membership change then drops that link. So the
matching thaw could no longer find the superblock.
Forbid freezing a device for the duration of a membership change,
modelled on deny_write_access()/allow_write_access().
* tag 'vfs-7.3-rc1.super' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs: (24 commits)
super: fix dying superblock warning messages
block: reject block device inodes with i_rdev == 0 in lookup_bdev()
selftests/filesystems: add ustat() coverage
fs: look up the superblock via the device table in user_get_super()
super: make fs_holder_ops private
f2fs: open via dedicated fs bdev helpers
erofs: open via dedicated fs bdev helpers
fs: tolerate per-superblock freeze errors on shared devices
fs: look up superblocks via the device table in fs_holder_ops
ext4: open via dedicated fs bdev helpers
btrfs: open via dedicated fs bdev helpers
xfs: port to fs_bdev_file_open_by_path()
fs: add dedicated block device open helpers for filesystems
fs: maintain a global device-to-superblock table
ocfs2: don't reset s_dev on dismount
ext4: use anonymous devices for KUnit test superblocks
fs, block: move blk_mode_t and fop_flags_t into <linux/types.h>
super: take lock after last reference count
super: convert s_count to refcount_t s_passive
btrfs: deny freezing devices undergoing a replace
...
Diffstat (limited to 'tools/testing')
| -rw-r--r-- | tools/testing/selftests/filesystems/.gitignore | 1 | ||||
| -rw-r--r-- | tools/testing/selftests/filesystems/Makefile | 2 | ||||
| -rw-r--r-- | tools/testing/selftests/filesystems/ustat_test.c | 135 |
3 files changed, 137 insertions, 1 deletions
diff --git a/tools/testing/selftests/filesystems/.gitignore b/tools/testing/selftests/filesystems/.gitignore index a78f894157de..9eb185fb2f9d 100644 --- a/tools/testing/selftests/filesystems/.gitignore +++ b/tools/testing/selftests/filesystems/.gitignore @@ -6,3 +6,4 @@ file_stressor anon_inode_test kernfs_test idmapped_tmpfile +ustat_test diff --git a/tools/testing/selftests/filesystems/Makefile b/tools/testing/selftests/filesystems/Makefile index a7ec2ba2dd83..03be337c1f35 100644 --- a/tools/testing/selftests/filesystems/Makefile +++ b/tools/testing/selftests/filesystems/Makefile @@ -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 += idmapped_tmpfile TEST_GEN_PROGS_EXTENDED := dnotify_test diff --git a/tools/testing/selftests/filesystems/ustat_test.c b/tools/testing/selftests/filesystems/ustat_test.c new file mode 100644 index 000000000000..d429fd18d779 --- /dev/null +++ b/tools/testing/selftests/filesystems/ustat_test.c @@ -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 |
