summaryrefslogtreecommitdiff
path: root/block
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-08-17 13:57:04 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-08-17 13:57:04 -0700
commit1781f0b3d75caa22376cf7fa0224f9aca696580d (patch)
tree042a9acf224b96254f38829630c05ee7dd7b2970 /block
parentaaed66fadba2d2de8fe0daa0aa3eac827d2076b9 (diff)
parent9ac8fd831252e52aa78399eaccc11a72f6c2af1a (diff)
downloadlinux-1781f0b3d75caa22376cf7fa0224f9aca696580d.tar.gz
linux-1781f0b3d75caa22376cf7fa0224f9aca696580d.zip
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 'block')
-rw-r--r--block/bdev.c125
1 files changed, 99 insertions, 26 deletions
diff --git a/block/bdev.c b/block/bdev.c
index 85ce57bd2ae4..797d7f0ef609 100644
--- a/block/bdev.c
+++ b/block/bdev.c
@@ -304,7 +304,12 @@ int bdev_freeze(struct block_device *bdev)
mutex_lock(&bdev->bd_fsfreeze_mutex);
- if (atomic_inc_return(&bdev->bd_fsfreeze_count) > 1) {
+ /* A device being removed from its filesystem refuses freezes. */
+ if (!atomic_inc_unless_negative(&bdev->bd_fsfreeze_count)) {
+ mutex_unlock(&bdev->bd_fsfreeze_mutex);
+ return -EBUSY;
+ }
+ if (atomic_read(&bdev->bd_fsfreeze_count) > 1) {
mutex_unlock(&bdev->bd_fsfreeze_mutex);
return 0;
}
@@ -340,18 +345,18 @@ int bdev_thaw(struct block_device *bdev)
mutex_lock(&bdev->bd_fsfreeze_mutex);
- /*
- * If this returns < 0 it means that @bd_fsfreeze_count was
- * already 0 and no decrement was performed.
- */
- nr_freeze = atomic_dec_if_positive(&bdev->bd_fsfreeze_count);
- if (nr_freeze < 0)
+ /* <= 0: not frozen (0) or a freeze deny is held (< 0); leave it. */
+ nr_freeze = atomic_read(&bdev->bd_fsfreeze_count);
+ if (nr_freeze <= 0)
goto out;
error = 0;
- if (nr_freeze > 0)
+ if (nr_freeze > 1) {
+ atomic_dec(&bdev->bd_fsfreeze_count);
goto out;
+ }
+ /* Keep the count positive across the thaw so a deny is refused. */
mutex_lock(&bdev->bd_holder_lock);
if (bdev->bd_holder_ops && bdev->bd_holder_ops->thaw) {
error = bdev->bd_holder_ops->thaw(bdev);
@@ -360,14 +365,52 @@ int bdev_thaw(struct block_device *bdev)
mutex_unlock(&bdev->bd_holder_lock);
}
- if (error)
- atomic_inc(&bdev->bd_fsfreeze_count);
+ if (!error)
+ atomic_dec(&bdev->bd_fsfreeze_count);
out:
mutex_unlock(&bdev->bd_fsfreeze_mutex);
return error;
}
EXPORT_SYMBOL(bdev_thaw);
+/**
+ * bdev_deny_freeze - make a block device unfreezable
+ * @bdev: block device
+ *
+ * Reserve @bdev against bdev_freeze() the way deny_write_access() reserves a
+ * file against writers. bd_fsfreeze_count is sign-encoded: > 0 counts active
+ * freezes, < 0 counts deniers, so a deny succeeds only while no freeze is in
+ * progress. While held, bdev_freeze() returns -EBUSY. Pair with
+ * bdev_allow_freeze().
+ *
+ * A filesystem removing, adding or replacing a member device denies freezes on
+ * it for the duration, so a claim a freeze walk might act on is never torn down
+ * behind the freezer's back. The deny is device-scoped, not (device,
+ * superblock)-scoped: a device shared by several superblocks is refused for all
+ * of them. No in-tree filesystem removes a shared claim from a live superblock.
+ *
+ * Return: 0, or -EBUSY if the device is currently frozen.
+ */
+int bdev_deny_freeze(struct block_device *bdev)
+{
+ return atomic_dec_unless_positive(&bdev->bd_fsfreeze_count) ? 0 : -EBUSY;
+}
+EXPORT_SYMBOL_GPL(bdev_deny_freeze);
+
+/**
+ * bdev_allow_freeze - allow freezing a block device again
+ * @bdev: block device
+ *
+ * Undo one bdev_deny_freeze().
+ */
+void bdev_allow_freeze(struct block_device *bdev)
+{
+ /* A deny must be held, i.e. the count must be negative. */
+ WARN_ON_ONCE(atomic_read(&bdev->bd_fsfreeze_count) >= 0);
+ atomic_inc(&bdev->bd_fsfreeze_count);
+}
+EXPORT_SYMBOL_GPL(bdev_allow_freeze);
+
/*
* pseudo-fs
*/
@@ -1153,6 +1196,39 @@ put_no_open:
}
/**
+ * bdev_yield_claim - give up the holder claim on an open block device
+ * @bdev_file: open block device
+ *
+ * Yield the holder and any write access for @bdev_file without closing it, so
+ * the caller can still act on the device - e.g. bdev_allow_freeze() it - before
+ * the final bdev_fput(). bdev_fput() yields too, so calling it afterwards is
+ * safe.
+ */
+void bdev_yield_claim(struct file *bdev_file)
+{
+ struct block_device *bdev;
+ struct gendisk *disk;
+
+ if (!bdev_file->private_data)
+ return;
+
+ bdev = file_bdev(bdev_file);
+ disk = bdev->bd_disk;
+
+ mutex_lock(&disk->open_mutex);
+ bdev_yield_write_access(bdev_file);
+ bd_yield_claim(bdev_file);
+ /*
+ * Tell release we already gave up our hold on the
+ * device and if write restrictions are available that
+ * we already gave up write access to the device.
+ */
+ bdev_file->private_data = BDEV_I(bdev_file->f_mapping->host);
+ mutex_unlock(&disk->open_mutex);
+}
+EXPORT_SYMBOL_GPL(bdev_yield_claim);
+
+/**
* bdev_fput - yield claim to the block device and put the file
* @bdev_file: open block device
*
@@ -1165,22 +1241,7 @@ void bdev_fput(struct file *bdev_file)
if (WARN_ON_ONCE(bdev_file->f_op != &def_blk_fops))
return;
- if (bdev_file->private_data) {
- struct block_device *bdev = file_bdev(bdev_file);
- struct gendisk *disk = bdev->bd_disk;
-
- mutex_lock(&disk->open_mutex);
- bdev_yield_write_access(bdev_file);
- bd_yield_claim(bdev_file);
- /*
- * Tell release we already gave up our hold on the
- * device and if write restrictions are available that
- * we already gave up write access to the device.
- */
- bdev_file->private_data = BDEV_I(bdev_file->f_mapping->host);
- mutex_unlock(&disk->open_mutex);
- }
-
+ bdev_yield_claim(bdev_file);
fput(bdev_file);
}
EXPORT_SYMBOL(bdev_fput);
@@ -1217,6 +1278,18 @@ int lookup_bdev(const char *pathname, dev_t *dev)
if (!may_open_dev(&path))
goto out_path_put;
+ /*
+ * Reject a block device inode with i_rdev == 0. A dev_t of 0 is
+ * never valid for a block device: no real block device driver
+ * registers major 0. Fake block device inodes (e.g. fuse with
+ * rootmode=S_IFBLK) can expose i_rdev == 0, and letting that
+ * propagate would confuse superblock lookup and trigger warnings
+ * in the device-to-superblock table (super_dev_register).
+ */
+ error = -ENODEV;
+ if (!inode->i_rdev)
+ goto out_path_put;
+
*dev = inode->i_rdev;
error = 0;
out_path_put: