Since commit 9c4a3de6cd5b ("gfs2: Asynchronous withdraw") most of a withdraw happens in do_withdraw() from a work item and SDF_JOURNAL_LIVE stays set until then. A remount to read-only in that window bails out of its log flushes on the withdrawn filesystem and gfs2_quota_cleanup() hits its BUG_ON(). Flushing the work isn't enough because gfs2_withdraw() only queues it after setting SDF_WITHDRAWN and a withdraw can also start in the middle of gfs2_make_fs_ro(). So let do_withdraw() wake up waiters when it clears SDF_JOURNAL_LIVE and let gfs2_make_fs_ro() wait for that right before gfs2_quota_cleanup(). umount doesn't need to wait because it sets SDF_NORECOVERY first. gfs2_make_fs_rw() could leave the bit set on a withdrawn filesystem too if do_withdraw() ran between its withdraw check and setting the bit. So do both under sd_log_flush_lock which do_withdraw() holds while it tests the bit. Fixes: 9c4a3de6cd5b ("gfs2: Asynchronous withdraw") Cc: stable@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/gfs2/super.c | 20 ++++++++++++++++---- fs/gfs2/util.c | 2 +- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/fs/gfs2/super.c b/fs/gfs2/super.c index 04bb4cf787d4..f61c309368ef 100644 --- a/fs/gfs2/super.c +++ b/fs/gfs2/super.c @@ -146,12 +146,21 @@ int gfs2_make_fs_rw(struct gfs2_sbd *sdp) } error = gfs2_quota_init(sdp); - if (!error && gfs2_withdrawn(sdp)) { - gfs2_quota_cleanup(sdp); + if (error) + return error; + + /* + * Check for a withdraw under the lock do_withdraw() holds while it + * takes a live journal down, so one of them sees the other. + */ + down_write(&sdp->sd_log_flush_lock); + if (gfs2_withdrawn(sdp)) error = -EIO; - } - if (!error) + else set_bit(SDF_JOURNAL_LIVE, &sdp->sd_flags); + up_write(&sdp->sd_log_flush_lock); + if (error) + gfs2_quota_cleanup(sdp); return error; } @@ -567,6 +576,9 @@ void gfs2_make_fs_ro(struct gfs2_sbd *sdp) HZ * 5); gfs2_assert_warn(sdp, gfs2_log_is_empty(sdp)); } + /* A withdraw that do_withdraw() didn't finish yet leaves it live. */ + if (!test_bit(SDF_NORECOVERY, &sdp->sd_flags)) + wait_on_bit(&sdp->sd_flags, SDF_JOURNAL_LIVE, TASK_UNINTERRUPTIBLE); gfs2_quota_cleanup(sdp); } diff --git a/fs/gfs2/util.c b/fs/gfs2/util.c index 529c3ec37a18..888e0d6bf512 100644 --- a/fs/gfs2/util.c +++ b/fs/gfs2/util.c @@ -121,7 +121,7 @@ static void do_withdraw(struct gfs2_sbd *sdp) up_write(&sdp->sd_log_flush_lock); return; } - clear_bit(SDF_JOURNAL_LIVE, &sdp->sd_flags); + clear_and_wake_up_bit(SDF_JOURNAL_LIVE, &sdp->sd_flags); gfs2_ail_drain(sdp); /* frees all transactions */ up_write(&sdp->sd_log_flush_lock); -- 2.53.0