drivers/block/loop.c | 74 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 60 insertions(+), 14 deletions(-)
syzbot is reporting NULL pointer dereference in lo_rw_aio() [1][2].
An analysis by the Gemini AI collaborator [3] considers that this problem
is caused by a timing shift primarily exposed by commit 65565ca5f99b
("block: unify the synchronous bi_end_io callbacks"), along with helper
refactorings like commit 92c3737a2473 ("block: add a bio_submit_or_kill
helper").
But due to difficulty of reproducing this race, discussion about what is
happening and how to fix this problem is stalling. Also, we haven't
identified how many filesystems are subjected to this problem.
Therefore, introduce a grace period for flushing outstanding I/O
(which should be a good thing from the perspective of defensive
programming) so that we won't hit NULL pointer dereference problem.
However, calling drain_workqueue() from __loop_clr_fd() with
disk->open_mutex held causes lockdep warnings. We need to flush
outstanding I/O without disk->open_mutex held. Therefore, defer
__loop_clr_fd() to WQ context, like commit 322c4293ecc5 ("loop: make
autoclear operation asynchronous") did.
The past attempt was reverted by commit bf23747ee053 ("loop: revert "make
autoclear operation asynchronous"") for two reasons:
(1) Userspace might be expecting that fput() on the backing file is
processed before lo_release() from close() returns to user mode.
But a debug patch [4] suggested me that this teardown operation is
racy regardless of whether disk->open_mutex is temporarily released
or not, and therefore the xfs/259 breakage should be addressed on
the xfstests side.
(2) Lockdep reported circular locking dependency caused by flushing
system-wide WQs. But we no longer need to worry that dependency
because all in-tree users no longer flush system-wide WQs.
Therefore, let's retry deferring __loop_clr_fd() to WQ context.
Link: https://syzkaller.appspot.com/bug?extid=cd8a9a308e879a4e2c28 [1]
Link: https://syzkaller.appspot.com/bug?extid=bc273027d5643e48e5b3 [2]
Link: https://lkml.kernel.org/r/fbb3edda-f108-4e5b-acf2-266f043f8125@I-love.SAKURA.ne.jp [3]
Link: https://lkml.kernel.org/r/9f8b5ab0-efbc-4cf3-a1f8-b43377416946@I-love.SAKURA.ne.jp [4]
Fixes: 65565ca5f99b ("block: unify the synchronous bi_end_io callbacks")
Assisted-by: Gemini-Pro
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
---
What an AI coding assistant based on Gemini told Bart about the v7.1 patch:
1. Userspace ABI / Teardown Regression (Asynchronous Autoclear)
The Regression: When a loop device configured with LO_FLAGS_AUTOCLEAR is
closed (such as during unmounting with umount -d or mount -o loop),
userspace expects that close() synchronously tears down the device and
releases the backing file (fput()) before returning to user mode.
Prior History: This exact asynchronous deferral was merged in commit
322c4293ecc5 ("loop: make autoclear operation asynchronous") and had to
be reverted in commit bf23747ee053 ("loop: revert 'make autoclear
operation asynchronous'") because standard filesystem unmount sequences
(such as umount ext4_on_xfs; umount /xfs) broke with -EBUSY in xfstests
(e.g., xfs/259).
Kernel Policy: Handwaving this in the commit log ("the xfs/259 breakage
should be addressed on the xfstests side") is not acceptable. Breaking
synchronous teardown semantics violates the core Linux kernel rule: never
break userspace. Real-world container engines, test suites, and system
utilities rely on fput() having completed when lo_release() / close()
returns.
What Bart says about the v6 patch which does synchronous teardown:
Releasing and reacquiring disk->open_mutex from __loop_clr_fd() seems
risky to me. There is plenty of code in block/bdev.c that assumes that
disk->open_mutex is not released by lo_release().
Then, what direction can we go?
drivers/block/loop.c | 74 +++++++++++++++++++++++++++++++++++---------
1 file changed, 60 insertions(+), 14 deletions(-)
diff --git a/drivers/block/loop.c b/drivers/block/loop.c
index 6f12976035b0..516118b3a16c 100644
--- a/drivers/block/loop.c
+++ b/drivers/block/loop.c
@@ -75,6 +75,7 @@ struct loop_device {
struct gendisk *lo_disk;
struct mutex lo_mutex;
bool idr_visible;
+ struct work_struct lo_clr_work;
};
struct loop_cmd {
@@ -1134,13 +1135,42 @@ static int loop_configure(struct loop_device *lo, blk_mode_t mode,
return error;
}
-static void __loop_clr_fd(struct loop_device *lo)
+static void __loop_clr_fd(struct work_struct *work)
{
+ struct loop_device *lo = container_of(work, struct loop_device, lo_clr_work);
+ struct gendisk *disk = lo->lo_disk;
struct queue_limits lim;
struct file *filp;
gfp_t gfp = lo->old_gfp_mask;
int err;
+ /* Step 1: Flush all outstanding I/O, without open_mutex held. */
+ /*
+ * Since loop_queue_rq() is called with RCU read lock, this synchronize_rcu()
+ * makes sure that no more queue_work() calls are made from loop_queue_work()
+ * from loop_queue_rq(). Subsequent loop_queue_rq() calls which are made after
+ * this synchronize_rcu() returned shall see lo->lo_state != Lo_bound and
+ * return with BLK_STS_IOERR.
+ */
+ synchronize_rcu();
+ /*
+ * This drain_workqueue() makes sure that no more loop_handle_cmd() calls are
+ * made from loop_process_work() from loop_workfn()/loop_rootcg_workfn().
+ */
+ drain_workqueue(lo->workqueue);
+ /*
+ * This blk_mq_freeze_queue() waits for completion of all outstanding I/O
+ * which has been scheduled via loop_queue_rq(), by waiting for q_usage_counter
+ * to reach 0. Since the lo->lo_state != Lo_bound check in loop_queue_rq()
+ * guarantees that no more new I/O requests are made, we can call
+ * blk_mq_unfreeze_queue() immediately after blk_mq_freeze_queue() returns.
+ */
+ blk_mq_unfreeze_queue(lo->lo_queue, blk_mq_freeze_queue(lo->lo_queue));
+
+ /* Step 2: Perform remaining cleanup, with open_mutex held. */
+ mutex_lock(&disk->open_mutex);
+ WARN_ON_ONCE(lo->lo_state != Lo_rundown);
+
spin_lock_irq(&lo->lo_lock);
filp = lo->lo_backing_file;
lo->lo_backing_file = NULL;
@@ -1151,12 +1181,7 @@ static void __loop_clr_fd(struct loop_device *lo)
lo->lo_sizelimit = 0;
memset(lo->lo_file_name, 0, LO_NAME_SIZE);
- /*
- * Reset the block size to the default.
- *
- * No queue freezing needed because this is called from the final
- * ->release call only, so there can't be any outstanding I/O.
- */
+ /* Reset the block size to the default. */
lim = queue_limits_start_update(lo->lo_queue);
lim.logical_block_size = SECTOR_SIZE;
lim.physical_block_size = SECTOR_SIZE;
@@ -1168,8 +1193,6 @@ static void __loop_clr_fd(struct loop_device *lo)
/* let user-space know about this change */
kobject_uevent(&disk_to_dev(lo->lo_disk)->kobj, KOBJ_CHANGE);
mapping_set_gfp_mask(filp->f_mapping, gfp);
- /* This is safe: open() is still holding a reference. */
- module_put(THIS_MODULE);
disk_force_media_change(lo->lo_disk);
@@ -1199,11 +1222,18 @@ static void __loop_clr_fd(struct loop_device *lo)
WRITE_ONCE(lo->lo_state, Lo_unbound);
mutex_unlock(&lo->lo_mutex);
+ /* Step 3: Drop refcounts, without open_mutex held. */
+ mutex_unlock(&disk->open_mutex);
+
+ put_device(disk_to_dev(disk));
+
/*
- * Need not hold lo_mutex to fput backing file. Calling fput holding
- * lo_mutex triggers a circular lock dependency possibility warning as
- * fput can take open_mutex which is usually taken before lo_mutex.
+ * This is safe: flush_work() from loop_remove() from loop_exit() waits
+ * until this function returns; effectively dropping the final module
+ * references synchronously.
*/
+ module_put(THIS_MODULE);
+
fput(filp);
}
@@ -1769,8 +1799,20 @@ static void lo_release(struct gendisk *disk)
need_clear = (lo->lo_state == Lo_rundown);
mutex_unlock(&lo->lo_mutex);
- if (need_clear)
- __loop_clr_fd(lo);
+ if (!need_clear)
+ return;
+ /*
+ * In order to flush outstanding I/O before clearing the backing
+ * device, defer __loop_clr_fd() to WQ context. The Lo_rundown state
+ * guarantees that lo_open() will fail with -ENXIO.
+ *
+ * Grab disk reference which will be dropped as soon as
+ * returning from lo_release() and releasing disk->open_mutex.
+ * We don't need to grab disk->fops->owner reference because
+ * we are holding one obtained by loop_configure().
+ */
+ get_device(disk_to_dev(disk));
+ queue_work(system_long_wq, &lo->lo_clr_work);
}
static void lo_free_disk(struct gendisk *disk)
@@ -2034,6 +2076,7 @@ static int loop_add(int i)
lo = kzalloc_obj(*lo);
if (!lo)
goto out;
+ INIT_WORK(&lo->lo_clr_work, __loop_clr_fd);
lo->worker_tree = RB_ROOT;
INIT_LIST_HEAD(&lo->idle_worker_list);
timer_setup(&lo->timer, loop_free_idle_workers_timer, TIMER_DEFERRABLE);
@@ -2138,6 +2181,9 @@ static int loop_add(int i)
static void loop_remove(struct loop_device *lo)
{
+ /* Wait for __loop_clr_fd() to complete. */
+ flush_work(&lo->lo_clr_work);
+
/* Make this loop device unreachable from pathname. */
del_gendisk(lo->lo_disk);
blk_mq_free_tag_set(&lo->tag_set);
--
2.55.0
syzbot is reporting NULL pointer dereference in lo_rw_aio() [1][2].
An analysis by the Gemini AI collaborator [3] considers that this problem
is caused by a timing shift primarily exposed by commit 65565ca5f99b
("block: unify the synchronous bi_end_io callbacks"), along with helper
refactorings like commit 92c3737a2473 ("block: add a bio_submit_or_kill
helper").
But due to difficulty of reproducing this race, discussion about what is
happening and how to fix this problem is stalling. Also, we haven't
identified how many filesystems are subjected to this problem.
Therefore, introduce a grace period for flushing outstanding I/O
(which should be a good thing from the perspective of defensive
programming) so that we won't hit NULL pointer dereference problem.
However, calling drain_workqueue() from __loop_clr_fd() with
disk->open_mutex held causes lockdep warnings. We need to flush
outstanding I/O without disk->open_mutex held. But we can't use task work
context, for there is no way to reliably wait for completion of a task work
function inside a loadable module when module unloading code for that
loadable module has started. We need to use a callback function which is
embedded into a built-in module so that it can reliably wait for completion
of synchronous teardown for the loop driver module.
Therefore, add a dedicated callback for the loop module to the block core
layer, and invoke that callback immediately after disk->open_mutex is
released. It is possible that multiple threads invoke that callback
when a teardown work was scheduled because disk->open_mutex was already
released, but concurrently calling flush_work() in order to wait for
completion of an outstanding teardown work will be safe.
Link: https://syzkaller.appspot.com/bug?extid=cd8a9a308e879a4e2c28 [1]
Link: https://syzkaller.appspot.com/bug?extid=bc273027d5643e48e5b3 [2]
Link: https://lkml.kernel.org/r/fbb3edda-f108-4e5b-acf2-266f043f8125@I-love.SAKURA.ne.jp [3]
Link: https://lkml.kernel.org/r/9f8b5ab0-efbc-4cf3-a1f8-b43377416946@I-love.SAKURA.ne.jp [4]
Fixes: 65565ca5f99b ("block: unify the synchronous bi_end_io callbacks")
Assisted-by: Gemini-Pro
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
---
What this patch does is basically the same with the v6 patch. Can this be
a possible alternative for Bart's
Releasing and reacquiring disk->open_mutex from __loop_clr_fd() seems
risky to me. There is plenty of code in block/bdev.c that assumes that
disk->open_mutex is not released by lo_release().
comment?
block/bdev.c | 2 ++
drivers/block/loop.c | 65 +++++++++++++++++++++++++++++++++---------
include/linux/blkdev.h | 5 ++++
3 files changed, 59 insertions(+), 13 deletions(-)
diff --git a/block/bdev.c b/block/bdev.c
index cd8323083740..7ce5acaacf43 100644
--- a/block/bdev.c
+++ b/block/bdev.c
@@ -1188,6 +1188,8 @@ void bdev_release(struct file *bdev_file)
else
blkdev_put_whole(bdev);
mutex_unlock(&disk->open_mutex);
+ if (bdev->bd_disk->fops->post_release)
+ bdev->bd_disk->fops->post_release(bdev->bd_disk);
module_put(disk->fops->owner);
put_no_open:
diff --git a/drivers/block/loop.c b/drivers/block/loop.c
index 6f12976035b0..620298978706 100644
--- a/drivers/block/loop.c
+++ b/drivers/block/loop.c
@@ -75,6 +75,7 @@ struct loop_device {
struct gendisk *lo_disk;
struct mutex lo_mutex;
bool idr_visible;
+ struct work_struct lo_clr_work;
};
struct loop_cmd {
@@ -1134,13 +1135,42 @@ static int loop_configure(struct loop_device *lo, blk_mode_t mode,
return error;
}
-static void __loop_clr_fd(struct loop_device *lo)
+static void __loop_clr_fd(struct work_struct *work)
{
+ struct loop_device *lo = container_of(work, struct loop_device, lo_clr_work);
+ struct gendisk *disk = lo->lo_disk;
struct queue_limits lim;
struct file *filp;
gfp_t gfp = lo->old_gfp_mask;
int err;
+ /* Step 1: Flush all outstanding I/O, without open_mutex held. */
+ /*
+ * Since loop_queue_rq() is called with RCU read lock, this synchronize_rcu()
+ * makes sure that no more queue_work() calls are made from loop_queue_work()
+ * from loop_queue_rq(). Subsequent loop_queue_rq() calls which are made after
+ * this synchronize_rcu() returned shall see lo->lo_state != Lo_bound and
+ * return with BLK_STS_IOERR.
+ */
+ synchronize_rcu();
+ /*
+ * This drain_workqueue() makes sure that no more loop_handle_cmd() calls are
+ * made from loop_process_work() from loop_workfn()/loop_rootcg_workfn().
+ */
+ drain_workqueue(lo->workqueue);
+ /*
+ * This blk_mq_freeze_queue() waits for completion of all outstanding I/O
+ * which has been scheduled via loop_queue_rq(), by waiting for q_usage_counter
+ * to reach 0. Since the lo->lo_state != Lo_bound check in loop_queue_rq()
+ * guarantees that no more new I/O requests are made, we can call
+ * blk_mq_unfreeze_queue() immediately after blk_mq_freeze_queue() returns.
+ */
+ blk_mq_unfreeze_queue(lo->lo_queue, blk_mq_freeze_queue(lo->lo_queue));
+
+ /* Step 2: Perform remaining cleanup, with open_mutex held. */
+ mutex_lock(&disk->open_mutex);
+ WARN_ON_ONCE(lo->lo_state != Lo_rundown);
+
spin_lock_irq(&lo->lo_lock);
filp = lo->lo_backing_file;
lo->lo_backing_file = NULL;
@@ -1151,12 +1181,7 @@ static void __loop_clr_fd(struct loop_device *lo)
lo->lo_sizelimit = 0;
memset(lo->lo_file_name, 0, LO_NAME_SIZE);
- /*
- * Reset the block size to the default.
- *
- * No queue freezing needed because this is called from the final
- * ->release call only, so there can't be any outstanding I/O.
- */
+ /* Reset the block size to the default. */
lim = queue_limits_start_update(lo->lo_queue);
lim.logical_block_size = SECTOR_SIZE;
lim.physical_block_size = SECTOR_SIZE;
@@ -1199,11 +1224,9 @@ static void __loop_clr_fd(struct loop_device *lo)
WRITE_ONCE(lo->lo_state, Lo_unbound);
mutex_unlock(&lo->lo_mutex);
- /*
- * Need not hold lo_mutex to fput backing file. Calling fput holding
- * lo_mutex triggers a circular lock dependency possibility warning as
- * fput can take open_mutex which is usually taken before lo_mutex.
- */
+ /* Step 3: Drop refcounts, without open_mutex held. */
+ mutex_unlock(&disk->open_mutex);
+
fput(filp);
}
@@ -1769,8 +1792,22 @@ static void lo_release(struct gendisk *disk)
need_clear = (lo->lo_state == Lo_rundown);
mutex_unlock(&lo->lo_mutex);
+ /*
+ * In order to flush outstanding I/O (without open_mutex for deadlock
+ * avoidance) before clearing the backing device, defer __loop_clr_fd()
+ * to WQ context and let lo_post_release() wait for completion.
+ * The Lo_rundown state guarantees that lo_open() will fail with -ENXIO.
+ */
if (need_clear)
- __loop_clr_fd(lo);
+ queue_work(system_long_wq, &lo->lo_clr_work);
+}
+
+static void lo_post_release(struct gendisk *disk)
+{
+ struct loop_device *lo = disk->private_data;
+
+ /* Wait for __loop_clr_fd() to complete. */
+ flush_work(&lo->lo_clr_work);
}
static void lo_free_disk(struct gendisk *disk)
@@ -1789,6 +1826,7 @@ static const struct block_device_operations lo_fops = {
.owner = THIS_MODULE,
.open = lo_open,
.release = lo_release,
+ .post_release = lo_post_release,
.ioctl = lo_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = lo_compat_ioctl,
@@ -2034,6 +2072,7 @@ static int loop_add(int i)
lo = kzalloc_obj(*lo);
if (!lo)
goto out;
+ INIT_WORK(&lo->lo_clr_work, __loop_clr_fd);
lo->worker_tree = RB_ROOT;
INIT_LIST_HEAD(&lo->idle_worker_list);
timer_setup(&lo->timer, loop_free_idle_workers_timer, TIMER_DEFERRABLE);
diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h
index 4f7905c3412b..5172d6bdd9e7 100644
--- a/include/linux/blkdev.h
+++ b/include/linux/blkdev.h
@@ -1605,6 +1605,11 @@ struct block_device_operations {
* driver.
*/
int (*alternative_gpt_sector)(struct gendisk *disk, sector_t *sector);
+ /*
+ * Special callback for synchronous cleanup without open_mutex.
+ * Needed by loop devices.
+ */
+ void (*post_release)(struct gendisk *disk);
};
#ifdef CONFIG_COMPAT
--
2.55.0
Hi Tetsuo,
kernel test robot noticed the following build errors:
[auto build test ERROR on axboe/for-next]
[also build test ERROR on linus/master v7.3-rc1 next-20260904]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Tetsuo-Handa/loop-Fix-NULL-pointer-dereference-in-lo_rw_aio/20260904-085046
base: https://git.kernel.org/pub/scm/linux/kernel/git/axboe/linux.git for-next
patch link: https://lore.kernel.org/r/dc2f1e00-5e10-4e02-9415-c6ecb2cbc6b3%40I-love.SAKURA.ne.jp
patch subject: [PATCH v8] loop: Fix NULL pointer dereference in lo_rw_aio()
config: arm64-allmodconfig (https://download.01.org/0day-ci/archive/20260905/202609052240.ovdWUOyj-lkp@intel.com/config)
compiler: clang version 24.0.0git (https://github.com/llvm/llvm-project 0edd1b088cc36b4faee80358c925a91e16006258)
rustc: rustc 1.96.0 (ac68faa20 2026-05-25)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260905/202609052240.ovdWUOyj-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202609052240.ovdWUOyj-lkp@intel.com/
All errors (new ones prefixed by >>):
>> error[E0063]: missing field `post_release` in initializer of `block_device_operations`
--> rust/kernel/block/mq/gen_disk.rs:128:58
|
128 | const TABLE: bindings::block_device_operations = bindings::block_device_operations {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ missing `post_release`
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
On Fri, 4 Sep 2026 08:50:46 +0900 Tetsuo Handa wrote:
> syzbot is reporting NULL pointer dereference in lo_rw_aio() [1][2].
> An analysis by the Gemini AI collaborator [3] considers that this problem
> is caused by a timing shift primarily exposed by commit 65565ca5f99b
> ("block: unify the synchronous bi_end_io callbacks"), along with helper
> refactorings like commit 92c3737a2473 ("block: add a bio_submit_or_kill
> helper").
>
> But due to difficulty of reproducing this race, discussion about what is
> happening and how to fix this problem is stalling. Also, we haven't
> identified how many filesystems are subjected to this problem.
>
> Therefore, introduce a grace period for flushing outstanding I/O
> (which should be a good thing from the perspective of defensive
> programming) so that we won't hit NULL pointer dereference problem.
>
> However, calling drain_workqueue() from __loop_clr_fd() with
> disk->open_mutex held causes lockdep warnings. We need to flush
> outstanding I/O without disk->open_mutex held. But we can't use task work
> context, for there is no way to reliably wait for completion of a task work
> function inside a loadable module when module unloading code for that
> loadable module has started. We need to use a callback function which is
> embedded into a built-in module so that it can reliably wait for completion
> of synchronous teardown for the loop driver module.
>
> Therefore, add a dedicated callback for the loop module to the block core
> layer, and invoke that callback immediately after disk->open_mutex is
> released. It is possible that multiple threads invoke that callback
> when a teardown work was scheduled because disk->open_mutex was already
> released, but concurrently calling flush_work() in order to wait for
> completion of an outstanding teardown work will be safe.
>
> Link: https://syzkaller.appspot.com/bug?extid=cd8a9a308e879a4e2c28 [1]
> Link: https://syzkaller.appspot.com/bug?extid=bc273027d5643e48e5b3 [2]
> Link: https://lkml.kernel.org/r/fbb3edda-f108-4e5b-acf2-266f043f8125@I-love.SAKURA.ne.jp [3]
> Link: https://lkml.kernel.org/r/9f8b5ab0-efbc-4cf3-a1f8-b43377416946@I-love.SAKURA.ne.jp [4]
> Fixes: 65565ca5f99b ("block: unify the synchronous bi_end_io callbacks")
> Assisted-by: Gemini-Pro
> Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
> ---
> What this patch does is basically the same with the v6 patch. Can this be
> a possible alternative for Bart's
>
> Releasing and reacquiring disk->open_mutex from __loop_clr_fd() seems
> risky to me. There is plenty of code in block/bdev.c that assumes that
> disk->open_mutex is not released by lo_release().
>
> comment?
>
> block/bdev.c | 2 ++
> drivers/block/loop.c | 65 +++++++++++++++++++++++++++++++++---------
> include/linux/blkdev.h | 5 ++++
> 3 files changed, 59 insertions(+), 13 deletions(-)
>
> diff --git a/block/bdev.c b/block/bdev.c
> index cd8323083740..7ce5acaacf43 100644
> --- a/block/bdev.c
> +++ b/block/bdev.c
> @@ -1188,6 +1188,8 @@ void bdev_release(struct file *bdev_file)
> else
> blkdev_put_whole(bdev);
> mutex_unlock(&disk->open_mutex);
> + if (bdev->bd_disk->fops->post_release)
> + bdev->bd_disk->fops->post_release(bdev->bd_disk);
>
A great leap forward, I like this.
> +
> +static void lo_post_release(struct gendisk *disk)
> +{
> + struct loop_device *lo = disk->private_data;
> +
> + /* Wait for __loop_clr_fd() to complete. */
> + flush_work(&lo->lo_clr_work);
> }
© 2016 - 2026 Red Hat, Inc.