[PATCH v0.1] block: Add run_todo() operation
From: Tetsuo Handa
Date: Sat Sep 19 2026 - 06:00:08 EST
Add run_todo() block device operation which provides a hook for performing
synchronous cleanup without disk->open_mutex held, which is needed by the
loop devices.
Real-world container engines, test suites, and system utilities rely on
fput() from __loop_clr_fd() being completed when lo_release() returns.
But changes which went to the v7.1 merge window broke an assumption that
there is no outstanding I/O when __loop_clr_fd() is called, causing NULL
pointer dereference problem in lo_rw_aio().
In order to fix this regression, we want to allow __loop_clr_fd() to flush
outstanding I/O. But calling drain_workqueue() from __loop_clr_fd() with
disk->open_mutex held causes lockdep warnings. We need a mechanism which
can flush outstanding I/O without disk->open_mutex held.
But deferring __loop_clr_fd() to WQ context has a problem that there is no
way to wait for completion of __loop_clr_fd() before the calling thread
returns to the userspace, for there is no hook for calling flush_work().
Despite what LO_FLAGS_AUTOCLEAR can guarantee is to clear backing device
"eventually" after the last thread called lo_release(), abovementioned
programs are expecting "synchronously" when a thread who is going to call
umount() or open() as soon as returning from close() called close().
That is an unsatisfiable expectation because the former is an objective
behavior and the latter is a subjective dependency. Nonetheless, we need
to try to wait for completion of __loop_clr_fd() at best-effort basis.
Also, since __loop_clr_fd() calls module_put(THIS_MODULE) and there is no
API for waiting for completion of remote thread's task work context,
deferring __loop_clr_fd() to task work context has a problem (aside from
task_work_add() being not exported to loadable modules) that module unload
operation can unmap code/data segment before __loop_clr_fd() completes.
Therefore, allow the loop driver to safely know completion of
__loop_clr_fd(), by adding a hook which is called after disk->open_mutex
is released.
Signed-off-by: Tetsuo Handa <penguin-kernel@xxxxxxxxxxxxxxxxxxx>
---
block/bdev.c | 10 +++++++++-
include/linux/blkdev.h | 10 ++++++++++
rust/kernel/block/mq/gen_disk.rs | 1 +
3 files changed, 20 insertions(+), 1 deletion(-)
diff --git a/block/bdev.c b/block/bdev.c
index cd8323083740..8f09aafbae11 100644
--- a/block/bdev.c
+++ b/block/bdev.c
@@ -974,6 +974,8 @@ int bdev_open(struct block_device *bdev, blk_mode_t mode, void *holder,
bool unblock_events = true;
struct gendisk *disk = bdev->bd_disk;
int ret;
+ struct module *fops_owner = NULL;
+ void (*run_todo)(struct gendisk *disk) = NULL;
if (holder) {
mode |= BLK_OPEN_EXCL;
@@ -996,6 +998,7 @@ int bdev_open(struct block_device *bdev, blk_mode_t mode, void *holder,
ret = -EBUSY;
if (!bdev_may_open(bdev, mode))
goto put_module;
+ run_todo = disk->fops->run_todo;
if (bdev_is_partition(bdev))
ret = blkdev_get_part(bdev, mode);
else
@@ -1037,12 +1040,15 @@ int bdev_open(struct block_device *bdev, blk_mode_t mode, void *holder,
return 0;
put_module:
- module_put(disk->fops->owner);
+ fops_owner = disk->fops->owner;
abort_claiming:
if (holder)
bd_abort_claiming(bdev, holder);
mutex_unlock(&disk->open_mutex);
disk_unblock_events(disk);
+ if (run_todo)
+ run_todo(disk);
+ module_put(fops_owner);
return ret;
}
@@ -1188,6 +1194,8 @@ void bdev_release(struct file *bdev_file)
else
blkdev_put_whole(bdev);
mutex_unlock(&disk->open_mutex);
+ if (disk->fops->run_todo)
+ disk->fops->run_todo(disk);
module_put(disk->fops->owner);
put_no_open:
diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h
index 4f7905c3412b..d14fc43de172 100644
--- a/include/linux/blkdev.h
+++ b/include/linux/blkdev.h
@@ -1577,6 +1577,16 @@ struct block_device_operations {
unsigned int flags);
int (*open)(struct gendisk *disk, blk_mode_t mode);
void (*release)(struct gendisk *disk);
+ /*
+ * This operation is for performing synchronous cleanup without
+ * disk->open_mutex held when blkdev_get_whole() returned an error or
+ * blkdev_put_whole() was called.
+ * Since this operation is called after disk->open_mutex was released,
+ * users of this operation must implement appropriate serialization.
+ * Also, users of this operation must not expect that either open() or
+ * release() was called before this operation is called.
+ */
+ void (*run_todo)(struct gendisk *disk);
int (*ioctl)(struct block_device *bdev, blk_mode_t mode,
unsigned cmd, unsigned long arg);
int (*compat_ioctl)(struct block_device *bdev, blk_mode_t mode,
diff --git a/rust/kernel/block/mq/gen_disk.rs b/rust/kernel/block/mq/gen_disk.rs
index fc97dd873974..fd837d1e3b27 100644
--- a/rust/kernel/block/mq/gen_disk.rs
+++ b/rust/kernel/block/mq/gen_disk.rs
@@ -129,6 +129,7 @@ pub fn build<T: Operations>(
submit_bio: None,
open: None,
release: None,
+ run_todo: None,
ioctl: None,
compat_ioctl: None,
check_events: None,
--
2.55.0