]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
block: allow making a block device unfreezable
authorChristian Brauner <brauner@kernel.org>
Tue, 16 Jun 2026 11:58:14 +0000 (13:58 +0200)
committerChristian Brauner <brauner@kernel.org>
Mon, 29 Jun 2026 08:31:49 +0000 (10:31 +0200)
Add bdev_deny_freeze() and bdev_allow_freeze(), modeled on
deny_write_access()/allow_write_access().  bd_fsfreeze_count becomes a
signed counter: > 0 counts active freezes, < 0 counts deniers, and the
two regimes are mutually exclusive.  bdev_freeze() refuses with -EBUSY
while a deny is held, and bdev_deny_freeze() refuses while the device is
frozen.

A filesystem that mutates a device's membership (a btrfs device add,
remove or replace) denies freezing on the device for the duration, so a
claim a freeze walk might act on is never added or torn down behind the
freezer's back.

The deny/allow helpers are a single atomic on bd_fsfreeze_count and take
no lock, so they can be called while holding s_umount without inverting
against bdev_freeze()'s bd_fsfreeze_mutex -> s_umount order.

Reviewed-by: Jan Kara <jack@suse.cz>
Link: https://patch.msgid.link/20260616-work-super-freeze_deny_upstream-v2-1-b3567c7f994b@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
block/bdev.c
include/linux/blk_types.h
include/linux/blkdev.h

index 85ce57bd2ae4fc7b0d9018383fdb5021368a8218..9b73487a91cab0b1cb55554c1f91c701edac8511 100644 (file)
@@ -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
  */
index 8808ee76e73c09e0ceaac41ba59e86fb0c4efc64..5a725a0cd35f49006e6d75f7e4b28ffd34682906 100644 (file)
@@ -66,7 +66,7 @@ struct block_device {
        int                     bd_holders;
        struct kobject          *bd_holder_dir;
 
-       atomic_t                bd_fsfreeze_count; /* number of freeze requests */
+       atomic_t                bd_fsfreeze_count; /* >0 freeze requests, <0 freeze deniers */
        struct mutex            bd_fsfreeze_mutex; /* serialize freeze/thaw */
 
        struct partition_meta_info *bd_meta_info;
index 9213a5716f95a78c8eea15d55a6573d5142aed46..c419117be083a639d6f02323e346b0cf748ce9cc 100644 (file)
@@ -1837,6 +1837,8 @@ static inline int early_lookup_bdev(const char *pathname, dev_t *dev)
 
 int bdev_freeze(struct block_device *bdev);
 int bdev_thaw(struct block_device *bdev);
+int bdev_deny_freeze(struct block_device *bdev);
+void bdev_allow_freeze(struct block_device *bdev);
 void bdev_fput(struct file *bdev_file);
 
 struct io_comp_batch {