| 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 |
| |