[PATCH RFC 21/28] gfs2: don't set SB_RDONLY on withdraw
Christian Brauner
brauner at kernel.org
Mon Oct 12 03:21:06 PDT 2026
do_withdraw() sets SB_RDONLY from the withdraw work item without holding
sb->s_umount. gfs2_withdraw() sets SDF_WITHDRAWN right away and queues
gfs2_withdraw_func() which runs do_withdraw(). That also happens on a
frozen filesystem. SDF_JOURNAL_LIVE is still set then because the
freeze log flush doesn't clear it.
freeze_super() and thaw_super() both look at sb_rdonly() to figure out
whether the s_writers rwsems need to be taken or released. gfs2 doesn't
even get that far. FITHAW goes through gfs2_thaw_super() to
gfs2_do_thaw() whose gfs2_freeze_lock_shared() fails with -EIO for a
withdrawn filesystem. So FITHAW never gets to thaw_super() and the
filesystem stays frozen. The only ways out are emergency thaw, which
calls thaw_super_locked() directly, and writing 0 to the gfs2 "freeze"
sysfs file, which calls thaw_super() directly. Both see SB_RDONLY and
skip sb_freeze_unlock(). The rwsems stay write-locked, writes through
file descriptors opened before the withdraw hang and umount warns:
gfs2: fsid=s2test:s2fs.0: can't lock the freeze glock: -5
gfs2: fsid=s2test:s2fs.0: GFS2: couldn't thaw filesystem: -5
sysrq: Emergency Thaw of all frozen filesystems
WARNING: kernel/rcu/sync.c:177 at rcu_sync_dtor+0x143/0x190
[<0>] percpu_rwsem_wait+0x203/0x500
[<0>] vfs_write+0xb57/0xfa0
Withdrawing first and freezing afterwards isn't any better.
freeze_super() freezes the read-only superblock without the rwsems,
gfs2_lock_fs_check_clean() fails with -EIO and gfs2_do_thaw() can't get
the freeze glock. FIFREEZE fails with -EIO and the superblock stays
frozen and pinned for good.
SDF_WITHDRAWN already stops all modifications. Glocks fail with -EIO,
gfs2_trans_begin() with -EROFS and log flushes and metadata reads are
refused. So stop setting SB_RDONLY in do_withdraw() like ext4 did in
commit d3476f3dad4a ("ext4: don't set SB_RDONLY after filesystem
errors"). A few things have to come with that:
(1) gfs2_evict_inode() and gfs2_quota_sync() check gfs2_withdrawn() next
to sb_rdonly(). Eviction writes the inode glock's metadata in place
and umount evicts everything.
(2) gfs2_write_inode() returns -EIO once withdrawn. sync(2), syncfs(2),
freeze and umount skipped writeback for the read-only superblock.
Now they reach ->write_inode() which writes the glock address space
in place without a withdrawn check, and gfs2_ail_drain() leaves
committed buffers dirty. Data writeback needs no check because the
block mapping goes through gfs2_meta_read() which fails once
withdrawn.
(3) gfs2_do_thaw() thaws even if the freeze glock can't be taken again
because the filesystem withdrew. A withdrawn filesystem can't write
anything and its lockspace is gone anyway.
(4) gfs2_freeze_super() refuses a withdrawn filesystem with -EIO before
calling freeze_super(). It can't take the freeze glock anymore and
freeze_super() would now take the rwsems of the read-write
superblock before gfs2_lock_fs_check_clean() fails.
Note that the SB_RDONLY change and (3) have to go in together and the
rest has to be in place by then. With only the SB_RDONLY change a freeze
after a withdraw takes the rwsems of the now read-write superblock and
stays frozen for good with writers blocked. With only (3) FITHAW reaches
thaw_super() on the read-only superblock and leaks the rwsems. And
without (4) a freeze after a withdraw goes through the error path of
gfs2_freeze_super() which re-locks sd_freeze_gh while
gfs2_lock_fs_check_clean() still holds it. The umount that is now
possible then hangs in gfs2_wait_glocks().
While withdrawn a remount to read-write fails with -EROFS instead of the
-EIO from gfs2_make_fs_rw(). A remount to read-only goes through
gfs2_make_fs_ro() which writes nothing once do_withdraw() has run, and
since the previous patch gfs2_make_fs_ro() waits for that. So emergency
remount (sysrq u), which now forces every withdrawn gfs2 read-only,
works without a WARN.
We show "withdrawn" in the mount options next to the existing
"demote_interface_used". A withdrawn filesystem stays "rw" so statfs()
doesn't report ST_RDONLY anymore, and the "Mounted RO" line in the sysfs
status file and the RDONLY= uevent field read 0 too. A second mount of
the device and a gfs2meta mount have to be read-write now. And like with
"emergency_ro" on ext4, remount helpers that pass the shown options back
fail. busybox mount for example now gets "Unknown parameter
'withdrawn'".
Path based operations fail with -EIO as before. The mount root is the
exception because it isn't looked up. So operations on it now fail with
-EIO from the glocks instead of -EROFS from mnt_want_write(), and the
same goes for fd based chmod, chown, utimes, setxattr and fileattr
changes. syncfs(2) on a withdrawn filesystem returns gfs2_sync_fs()'s
sd_log_error, e.g. -EIO, instead of 0. FITHAW of a filesystem that
withdrew while frozen works now and FIFREEZE of a withdrawn filesystem
fails with -EIO without freezing it.
Reproducer (the sb_rdonly selftest at the end of this series covers it
as well):
mkfs.gfs2 -O -p lock_nolock -j 1 -t test:fs img; mount -o loop img /mnt
exec 3>>/mnt/a 4</mnt # paths stop working after the withdraw
fsfreeze -f /mnt
echo 1 > /sys/fs/gfs2/test:fs/withdraw; sleep 8
<FITHAW on fd 4> # before: EIO, stays frozen; after: thawed
echo j > /proc/sysrq-trigger # before: the only way out, leaks the rwsems
echo x >&3 # before: hangs in percpu_rwsem_wait(); after: EIO
umount /mnt # before: WARNING at rcu_sync_dtor()
Fixes: 601ef0d52e96 ("gfs2: Force withdraw to replay journals and wait for it to finish")
Fixes: b77b4a4815a9 ("gfs2: Rework freeze / thaw logic")
Cc: stable at vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner at kernel.org>
---
fs/gfs2/ops_fstype.c | 6 +++++-
fs/gfs2/quota.c | 2 +-
fs/gfs2/super.c | 19 +++++++++++++++++--
fs/gfs2/util.c | 6 ++++--
4 files changed, 27 insertions(+), 6 deletions(-)
diff --git a/fs/gfs2/ops_fstype.c b/fs/gfs2/ops_fstype.c
index 718e0da7dfce..d7f7e8b75971 100644
--- a/fs/gfs2/ops_fstype.c
+++ b/fs/gfs2/ops_fstype.c
@@ -1594,7 +1594,11 @@ static int gfs2_reconfigure(struct fs_context *fc)
if (fc->sb_flags & SB_RDONLY) {
gfs2_make_fs_ro(sdp);
} else {
- error = gfs2_make_fs_rw(sdp);
+ /* A withdrawn filesystem can't go back to read-write. */
+ if (gfs2_withdrawn(sdp))
+ error = -EROFS;
+ else
+ error = gfs2_make_fs_rw(sdp);
if (error)
errorfc(fc, "unable to remount read-write");
}
diff --git a/fs/gfs2/quota.c b/fs/gfs2/quota.c
index 001c8b39ca55..7fac70bae129 100644
--- a/fs/gfs2/quota.c
+++ b/fs/gfs2/quota.c
@@ -1329,7 +1329,7 @@ int gfs2_quota_sync(struct super_block *sb, int type)
u64 sync_gen;
int error = 0;
- if (sb_rdonly(sdp->sd_vfs))
+ if (sb_rdonly(sdp->sd_vfs) || gfs2_withdrawn(sdp))
return 0;
qda = kzalloc_objs(struct gfs2_quota_data *, max_qd);
diff --git a/fs/gfs2/super.c b/fs/gfs2/super.c
index 1d7b39354d65..ec7bce124376 100644
--- a/fs/gfs2/super.c
+++ b/fs/gfs2/super.c
@@ -456,6 +456,9 @@ static int gfs2_write_inode(struct inode *inode, struct writeback_control *wbc)
int ret = 0;
bool flush_all = (wbc->sync_mode == WB_SYNC_ALL || gfs2_is_jdata(ip));
+ if (gfs2_withdrawn(sdp))
+ return -EIO;
+
if (flush_all)
gfs2_log_flush(GFS2_SB(inode), ip->i_gl,
GFS2_LOG_HEAD_FLUSH_NORMAL |
@@ -686,7 +689,12 @@ static int gfs2_do_thaw(struct gfs2_sbd *sdp, enum freeze_holder who, const void
int error;
error = gfs2_freeze_lock_shared(sdp);
- if (error)
+ /*
+ * A withdrawn filesystem can't get the freeze glock back, but it won't
+ * write anything either. Thaw it anyway instead of leaving it frozen
+ * for good.
+ */
+ if (error && !gfs2_withdrawn(sdp))
goto fail;
error = thaw_super(sb, who, freeze_owner);
if (!error)
@@ -751,6 +759,11 @@ static int gfs2_freeze_super(struct super_block *sb, enum freeze_holder who,
mutex_unlock(&sdp->sd_freeze_mutex);
return -EBUSY;
}
+ /* A withdrawn filesystem can't take the freeze glock anymore. */
+ if (gfs2_withdrawn(sdp)) {
+ mutex_unlock(&sdp->sd_freeze_mutex);
+ return -EIO;
+ }
for (;;) {
error = freeze_super(sb, who, freeze_owner);
@@ -1165,6 +1178,8 @@ static int gfs2_show_options(struct seq_file *s, struct dentry *root)
seq_puts(s, ",nobarrier");
if (test_bit(SDF_DEMOTE, &sdp->sd_flags))
seq_puts(s, ",demote_interface_used");
+ if (gfs2_withdrawn(sdp))
+ seq_puts(s, ",withdrawn");
if (args->ar_rgrplvb)
seq_puts(s, ",rgrplvb");
if (args->ar_loccookie)
@@ -1466,7 +1481,7 @@ static void gfs2_evict_inode(struct inode *inode)
int ret;
gfs2_holder_mark_uninitialized(&gh);
- if (sb_rdonly(sb) || !ip->i_no_addr || !ip->i_gl)
+ if (sb_rdonly(sb) || gfs2_withdrawn(sdp) || !ip->i_no_addr || !ip->i_gl)
goto out;
/*
diff --git a/fs/gfs2/util.c b/fs/gfs2/util.c
index 1535ce2b7aa8..990552f42a0b 100644
--- a/fs/gfs2/util.c
+++ b/fs/gfs2/util.c
@@ -133,9 +133,11 @@ static void do_withdraw(struct gfs2_sbd *sdp)
gfs2_log_is_empty(sdp),
HZ * 5);
- sdp->sd_vfs->s_flags |= SB_RDONLY;
-
/*
+ * We don't set SB_RDONLY. That requires sb->s_umount, and changing it
+ * under freeze_super() and thaw_super() leaves the freeze protection
+ * unbalanced. SDF_WITHDRAWN already stops all modifications.
+ *
* Dequeue any pending non-system glock holders that can no
* longer be granted because the file system is withdrawn.
*/
--
2.53.0
More information about the linux-mtd
mailing list