From c84de57efe88c9c513cfbc3746361502db0978e2 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 12 Oct 2026 14:31:19 +0200 Subject: super: convert to plain hashtable Convert the rhltable to a plain hashtable under a spinlock. The walks keep their place with a cursor entry in the chain. A claim is unhashed the moment it's dropped and a walk that finds a superblock that isn't born yet waits for it to be born or to die and looks again. Since hashing can't fail anymore the entry for the superblock's own s_dev is embedded in struct super_block and the preallocation in alloc_super() goes away. This all simplifies things quite a lot. It also fixes a btrfs bug where fs_bdev_freeze() ended up freezing a btrfs filesystem through a stale member that open_ctree() had already released while the freeze was waiting for SB_BORN. The resume doesn't find a holder for that device anymore so the filesystem stays frozen for good. The walks return superblocks instead of table entries now and user_get_super() hands out the passive reference the walk took. Reproducer, a three device btrfs with a stale copy of a removed member. dm-delay under the remaining members stretches the window between btrfs_open_devices() and btrfs_free_extra_devids(): truncate -s 1G a1 a2 b A1=$(losetup -f --show a1); A2=$(losetup -f --show a2); B=$(losetup -f --show b) mkfs.btrfs -f -d single -m single $A1 $A2 $B cp b b.old mount $A1 /mnt && btrfs device remove 3 /mnt && umount /mnt OLD=$(losetup -f --show b.old) dmsetup create d1 --table "0 $(blockdev --getsz $A1) delay $A1 0 200" dmsetup create d2 --table "0 $(blockdev --getsz $A2) delay $A2 0 200" dmsetup create old --table "0 $(blockdev --getsz $OLD) linear $OLD 0" mount -o device=/dev/mapper/d2,device=/dev/mapper/old /dev/mapper/d1 /mnt & sleep 1; dmsetup suspend old; wait dmsetup resume old touch /mnt/foo # hangs until fsfreeze -u /mnt That left btrfs frozen in 5 of 5 rounds with the same tables issued through raw dm ioctls. The -o degraded variant needs the mount held between the insertion and the bd_fsfreeze_count check in fs_bdev_register() with a kprobe. Fixes: cdb5146f8d5f ("fs: look up superblocks via the device table in fs_holder_ops") Link: https://patch.msgid.link/20261012-work-super-dev_hash-v1-2-305cf71cd334@kernel.org Signed-off-by: Christian Brauner (Amutable) --- include/linux/fs/super_types.h | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'include/linux') diff --git a/include/linux/fs/super_types.h b/include/linux/fs/super_types.h index ecd96aeb1cee..72f4288e16c5 100644 --- a/include/linux/fs/super_types.h +++ b/include/linux/fs/super_types.h @@ -30,7 +30,6 @@ struct mount; struct mtd_info; struct quotactl_ops; struct shrinker; -struct super_dev; struct unicode_map; struct user_namespace; struct workqueue_struct; @@ -132,10 +131,18 @@ struct super_operations { void (*report_error)(const struct fserror_event *event); }; +/* A superblock's claim on a device, see fs/super.c. */ +struct super_dev { + struct hlist_node sd_hash; + struct super_block *sd_sb; + dev_t sd_dev; + unsigned int sd_claims; +}; + struct super_block { struct list_head s_list; /* Keep this first */ dev_t s_dev; /* search index; _not_ kdev_t */ - struct super_dev *s_super_dev; /* sget_fc()'s device table claim */ + struct super_dev s_super_dev; /* claim on s_dev */ unsigned char s_blocksize_bits; unsigned long s_blocksize; loff_t s_maxbytes; /* Max file size */ -- cgit v1.2.3