blob: 0248c835e4b4c5a494c0fbd2a986351bbd643aac [file]
From bfc22b3609f567b5d3fb2cd25816354cc92fa6af Mon Sep 17 00:00:00 2001
From: Sasha Levin <sashal@kernel.org>
Date: Tue, 5 Aug 2025 23:12:06 +0200
Subject: gfs2: Asynchronous withdraw
From: Andreas Gruenbacher <agruenba@redhat.com>
[ Upstream commit 9c4a3de6cd5b46aa743c460b86eb58fe5f93daec ]
So far, withdraws are carried out in the context of the calling task.
When another task tries to withdraw while a withdraw is already
underway, that task blocks as well. Change that to carry out withdraws
asynchronously in workqueue context and don't block the task triggering
the withdraw anymore.
Fixes: syzbot+6b156e132970e550194c@syzkaller.appspotmail.com
Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
Stable-dep-of: da6f5bbc2e79 ("gfs2: fix memory leaks in gfs2_fill_super error path")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/gfs2/incore.h | 1 +
fs/gfs2/ops_fstype.c | 2 ++
fs/gfs2/super.c | 2 +-
fs/gfs2/util.c | 55 +++++++++++++++++++++++++-------------------
fs/gfs2/util.h | 2 ++
5 files changed, 37 insertions(+), 25 deletions(-)
diff --git a/fs/gfs2/incore.h b/fs/gfs2/incore.h
index 5a0ea416cfdae..db0e72adb9996 100644
--- a/fs/gfs2/incore.h
+++ b/fs/gfs2/incore.h
@@ -716,6 +716,7 @@ struct gfs2_sbd {
struct gfs2_glock *sd_rename_gl;
struct gfs2_glock *sd_freeze_gl;
struct work_struct sd_freeze_work;
+ struct work_struct sd_withdraw_work;
wait_queue_head_t sd_kill_wait;
wait_queue_head_t sd_async_glock_wait;
atomic_t sd_glock_disposal;
diff --git a/fs/gfs2/ops_fstype.c b/fs/gfs2/ops_fstype.c
index f748d320fa148..c42982bdd4b20 100644
--- a/fs/gfs2/ops_fstype.c
+++ b/fs/gfs2/ops_fstype.c
@@ -1215,6 +1215,8 @@ static int gfs2_fill_super(struct super_block *sb, struct fs_context *fc)
if (error)
goto fail_debug;
+ INIT_WORK(&sdp->sd_withdraw_work, gfs2_withdraw_func);
+
error = init_locking(sdp, &mount_gh, DO);
if (error)
goto fail_lm;
diff --git a/fs/gfs2/super.c b/fs/gfs2/super.c
index 7b5fcd78a3a1a..3985da448a41d 100644
--- a/fs/gfs2/super.c
+++ b/fs/gfs2/super.c
@@ -603,7 +603,7 @@ static void gfs2_put_super(struct super_block *sb)
gfs2_quota_cleanup(sdp);
}
- WARN_ON(gfs2_withdrawing(sdp));
+ flush_work(&sdp->sd_withdraw_work);
/* At this point, we're through modifying the disk */
diff --git a/fs/gfs2/util.c b/fs/gfs2/util.c
index 27fdcbce2d754..c454bea101dec 100644
--- a/fs/gfs2/util.c
+++ b/fs/gfs2/util.c
@@ -309,43 +309,50 @@ void gfs2_lm(struct gfs2_sbd *sdp, const char *fmt, ...)
va_end(args);
}
-void gfs2_withdraw(struct gfs2_sbd *sdp)
+void gfs2_withdraw_func(struct work_struct *work)
{
+ struct gfs2_sbd *sdp = container_of(work, struct gfs2_sbd, sd_withdraw_work);
struct lm_lockstruct *ls = &sdp->sd_lockstruct;
const struct lm_lockops *lm = ls->ls_ops;
+ if (test_bit(SDF_KILL, &sdp->sd_flags))
+ return;
+
+ BUG_ON(sdp->sd_args.ar_debug);
+
+ signal_our_withdraw(sdp);
+
+ kobject_uevent(&sdp->sd_kobj, KOBJ_OFFLINE);
+
+ if (!strcmp(sdp->sd_lockstruct.ls_ops->lm_proto_name, "lock_dlm"))
+ wait_for_completion(&sdp->sd_wdack);
+
+ if (lm->lm_unmount)
+ lm->lm_unmount(sdp, false);
+ fs_err(sdp, "file system withdrawn\n");
+ clear_bit(SDF_WITHDRAW_IN_PROG, &sdp->sd_flags);
+}
+
+void gfs2_withdraw(struct gfs2_sbd *sdp)
+{
if (sdp->sd_args.ar_errors == GFS2_ERRORS_WITHDRAW) {
unsigned long old = READ_ONCE(sdp->sd_flags), new;
do {
- if (old & BIT(SDF_WITHDRAWN)) {
- wait_on_bit(&sdp->sd_flags,
- SDF_WITHDRAW_IN_PROG,
- TASK_UNINTERRUPTIBLE);
+ if (old & BIT(SDF_WITHDRAWN))
return;
- }
new = old | BIT(SDF_WITHDRAWN) | BIT(SDF_WITHDRAW_IN_PROG);
} while (unlikely(!try_cmpxchg(&sdp->sd_flags, &old, new)));
- fs_err(sdp, "about to withdraw this file system\n");
- BUG_ON(sdp->sd_args.ar_debug);
-
- signal_our_withdraw(sdp);
-
- kobject_uevent(&sdp->sd_kobj, KOBJ_OFFLINE);
-
- if (!strcmp(sdp->sd_lockstruct.ls_ops->lm_proto_name, "lock_dlm"))
- wait_for_completion(&sdp->sd_wdack);
-
- if (lm->lm_unmount) {
- fs_err(sdp, "telling LM to unmount\n");
- lm->lm_unmount(sdp, false);
- }
- fs_err(sdp, "File system withdrawn\n");
dump_stack();
- clear_bit(SDF_WITHDRAW_IN_PROG, &sdp->sd_flags);
- smp_mb__after_atomic();
- wake_up_bit(&sdp->sd_flags, SDF_WITHDRAW_IN_PROG);
+ /*
+ * There is no need to withdraw when the superblock hasn't been
+ * fully initialized, yet.
+ */
+ if (!(sdp->sd_vfs->s_flags & SB_BORN))
+ return;
+ fs_err(sdp, "about to withdraw this file system\n");
+ schedule_work(&sdp->sd_withdraw_work);
}
if (sdp->sd_args.ar_errors == GFS2_ERRORS_PANIC)
diff --git a/fs/gfs2/util.h b/fs/gfs2/util.h
index da0373b1e82b9..f424ef2523c43 100644
--- a/fs/gfs2/util.h
+++ b/fs/gfs2/util.h
@@ -232,6 +232,8 @@ gfs2_tune_get_i(&(sdp)->sd_tune, &(sdp)->sd_tune.field)
__printf(2, 3)
void gfs2_lm(struct gfs2_sbd *sdp, const char *fmt, ...);
+
+void gfs2_withdraw_func(struct work_struct *work);
void gfs2_withdraw(struct gfs2_sbd *sdp);
#endif /* __UTIL_DOT_H__ */
--
2.53.0