[PATCH RFC 17/28] zonefs: don't set SB_RDONLY after I/O errors

Christian Brauner brauner at kernel.org
Mon Oct 12 03:21:02 PDT 2026


zonefs sets SB_RDONLY from its I/O error handling without holding
sb->s_umount. It does so in three places:

- zonefs_handle_io_error() with errors=remount-ro, which is the default
- __zonefs_io_error() when the zone report fails, for every errors= mode
- zonefs_seq_file_write_close() when closing an explicitly opened zone
  fails

freeze_super() and thaw_super() both look at sb_rdonly() to figure out
whether the s_writers rwsems need to be taken or released. So after an
error while the filesystem is frozen thaw_super() skips ->unfreeze_fs()
and sb_freeze_unlock() and the rwsems stay write-locked. Writers hang in
sb_start_write(). A remount to read-write makes the filesystem writable
again but the rwsems stay locked. So writers keep hanging and the next
freeze blocks forever in sb_wait_write(). And umount warns in
rcu_sync_dtor().

zonefs has no ->freeze_fs() and is only frozen through bdev_freeze(),
e.g. by a device mapper suspend with lockfs which also holds new I/O.
But an fsync() that finds an earlier writeback error in the mapping runs
the error handling without doing any I/O. For a conventional zone the
zone report is faked so zonefs_handle_io_error() sets SB_RDONLY:

  zonefs (dm-0) WARNING: remounting filesystem read-only
  WARNING: kernel/rcu/sync.c:177 at rcu_sync_dtor+0x143/0x190

Do what ext4 did in commit d3476f3dad4a ("ext4: don't set SB_RDONLY
after filesystem errors") and record the error in a new
ZONEFS_SB_EMERGENCY_RO bit instead. All three places call the new
zonefs_set_emergency_ro() which prints the existing "remounting
filesystem read-only" warning once. An error on a read-only mount
doesn't set the bit since there's nothing to refuse there. Write opens,
write_iter and setattr are refused with -EROFS and page_mkwrite fails
with VM_FAULT_SIGBUS. Refusing write opens also avoids a
REQ_OP_ZONE_OPEN with explicit-open.

A remount to read-write fails with -EROFS. A remount to read-only is
allowed, also for emergency remount (sysrq u), and zonefs writes no
metadata on that transition. So far "mount -o remount,rw" re-enabled
writes after a remount-ro error. Now the state lasts until umount and
Documentation/filesystems/zonefs.rst says so.

Like ext4 we show "emergency_ro" in the mount options. The mount stays
"rw". Note that writes through file descriptors opened before the error
failed with -EROFS before as well since zonefs_file_write_iter() already
checked sb_rdonly(). What's new is that ftruncate() through such a file
descriptor, i.e. a zone reset or finish, and write faults on shared
mappings are refused too. fsync and writeback of conventional zone data
dirtied before the error and the zone close on release behave as before
because SB_RDONLY didn't stop those either.

Reproducer:

  modprobe null_blk nr_devices=0
  mkdir /sys/kernel/config/nullb/z && cd /sys/kernel/config/nullb/z
  echo 1 > memory_backed; echo 4096 > blocksize; echo 1 > zoned
  echo 8 > zone_size; echo 4 > zone_nr_conv; echo 128 > size; echo 1 > power
  mkzonefs /dev/z        # the disk is named after the configfs directory
  dmsetup create zfs --table "0 $(blockdev --getsz /dev/z) linear /dev/z 0"
  mount -t zonefs /dev/mapper/zfs /mnt
  echo +16384-16391 > badblocks          # first 4k of zone 1, i.e. cnv/0
  dd if=/dev/zero of=/mnt/cnv/0 bs=4k count=1 conv=notrunc; sync   # writeback fails
  dmsetup suspend zfs
  sync /mnt/cnv/0        # fsync() reports the writeback error -> SB_RDONLY while frozen
  dmsetup resume zfs
  touch /mnt/cnv/1       # hangs in sb_start_write()
  # umount instead of touch: WARNING at kernel/rcu/sync.c:177 rcu_sync_dtor()

Fixes: 8dcc1a9d90c1 ("fs: New zonefs file system")
Cc: stable at vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner at kernel.org>
---
 Documentation/filesystems/zonefs.rst |  5 +++--
 fs/zonefs/file.c                     | 17 +++++++++--------
 fs/zonefs/super.c                    | 37 ++++++++++++++++++++++++++++--------
 fs/zonefs/zonefs.h                   | 16 ++++++++++++++++
 4 files changed, 57 insertions(+), 18 deletions(-)

diff --git a/Documentation/filesystems/zonefs.rst b/Documentation/filesystems/zonefs.rst
index c22124c2213d..2bec05102db4 100644
--- a/Documentation/filesystems/zonefs.rst
+++ b/Documentation/filesystems/zonefs.rst
@@ -285,8 +285,9 @@ Further notes:
 * The "errors=remount-ro" mount option is the default behavior of zonefs I/O
   error processing if no errors mount option is specified.
 * With the "errors=remount-ro" mount option, the change of the file access
-  permissions to read-only applies to all files. The file system is remounted
-  read-only.
+  permissions to read-only applies to all files. A file system mounted
+  read-write refuses all writes until it is unmounted and "emergency_ro" is
+  shown in its mount options.
 * Access permission and file size changes due to the device transitioning zones
   to the offline condition are permanent. Remounting or reformatting the device
   with mkfs.zonefs (mkzonefs) will not change back offline zone files to a good
diff --git a/fs/zonefs/file.c b/fs/zonefs/file.c
index 5b34849be7a2..8959b6b36a83 100644
--- a/fs/zonefs/file.c
+++ b/fs/zonefs/file.c
@@ -299,7 +299,7 @@ static vm_fault_t zonefs_filemap_page_mkwrite(struct vm_fault *vmf)
 	struct inode *inode = file_inode(vmf->vma->vm_file);
 	vm_fault_t ret;
 
-	if (unlikely(IS_IMMUTABLE(inode)))
+	if (unlikely(IS_IMMUTABLE(inode) || zonefs_emergency_ro(inode->i_sb)))
 		return VM_FAULT_SIGBUS;
 
 	/*
@@ -601,7 +601,7 @@ static ssize_t zonefs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
 	if (unlikely(IS_IMMUTABLE(inode)))
 		return -EPERM;
 
-	if (sb_rdonly(inode->i_sb))
+	if (sb_rdonly(inode->i_sb) || zonefs_emergency_ro(inode->i_sb))
 		return -EROFS;
 
 	/* Write operations beyond the zone capacity are not allowed */
@@ -788,6 +788,9 @@ static int zonefs_file_open(struct inode *inode, struct file *file)
 {
 	int ret;
 
+	if ((file->f_mode & FMODE_WRITE) && zonefs_emergency_ro(inode->i_sb))
+		return -EROFS;
+
 	file->f_mode |= FMODE_CAN_ODIRECT;
 	ret = generic_file_open(inode, file);
 	if (ret)
@@ -825,17 +828,15 @@ static void zonefs_seq_file_write_close(struct inode *inode)
 			/*
 			 * Leaving zones explicitly open may lead to a state
 			 * where most zones cannot be written (zone resources
-			 * exhausted). So take preventive action by remounting
-			 * read-only.
+			 * exhausted). So take preventive action by making the
+			 * file system read-only.
 			 */
 			if (z->z_flags & ZONEFS_ZONE_OPEN &&
-			    !(sb->s_flags & SB_RDONLY)) {
+			    !sb_rdonly(sb) && !zonefs_emergency_ro(sb)) {
 				zonefs_warn(sb,
 					"closing zone at %llu failed %d\n",
 					z->z_sector, ret);
-				zonefs_warn(sb,
-					"remounting filesystem read-only\n");
-				sb->s_flags |= SB_RDONLY;
+				zonefs_set_emergency_ro(sb);
 			}
 			goto unlock;
 		}
diff --git a/fs/zonefs/super.c b/fs/zonefs/super.c
index ff43d6d1ea30..367d657febe5 100644
--- a/fs/zonefs/super.c
+++ b/fs/zonefs/super.c
@@ -247,6 +247,20 @@ static void zonefs_inode_update_mode(struct inode *inode)
 	z->z_mode = inode->i_mode;
 }
 
+/*
+ * Make the file system refuse writes after an error. Don't set SB_RDONLY:
+ * that needs sb->s_umount and a proper remount, and setting it while the file
+ * system is frozen makes thaw_super() leave the s_writers rwsems locked.
+ */
+void zonefs_set_emergency_ro(struct super_block *sb)
+{
+	if (sb_rdonly(sb) ||
+	    test_and_set_bit(ZONEFS_SB_EMERGENCY_RO, &ZONEFS_SB(sb)->s_state))
+		return;
+
+	zonefs_warn(sb, "remounting filesystem read-only\n");
+}
+
 static int zonefs_io_error_cb(struct blk_zone *zone, unsigned int idx,
 			      void *data)
 {
@@ -339,13 +353,11 @@ static void zonefs_handle_io_error(struct inode *inode, struct blk_zone *zone,
 		z->z_flags &= ~ZONEFS_ZONE_OPEN;
 
 	/*
-	 * If error=remount-ro was specified, any error result in remounting
-	 * the volume as read-only.
+	 * If error=remount-ro was specified, any error makes the volume
+	 * read-only.
 	 */
-	if ((sbi->s_mount_opts & ZONEFS_MNTOPT_ERRORS_RO) && !sb_rdonly(sb)) {
-		zonefs_warn(sb, "remounting filesystem read-only\n");
-		sb->s_flags |= SB_RDONLY;
-	}
+	if (sbi->s_mount_opts & ZONEFS_MNTOPT_ERRORS_RO)
+		zonefs_set_emergency_ro(sb);
 
 	/*
 	 * Update block usage stats and the inode size  to prevent access to
@@ -404,8 +416,7 @@ void __zonefs_io_error(struct inode *inode, bool write)
 	if (ret != 1) {
 		zonefs_err(sb, "Get inode %llu zone information failed %d\n",
 			   inode->i_ino, ret);
-		zonefs_warn(sb, "remounting filesystem read-only\n");
-		sb->s_flags |= SB_RDONLY;
+		zonefs_set_emergency_ro(sb);
 		return;
 	}
 
@@ -529,6 +540,8 @@ static int zonefs_show_options(struct seq_file *seq, struct dentry *root)
 		seq_puts(seq, ",errors=zone-offline");
 	if (sbi->s_mount_opts & ZONEFS_MNTOPT_ERRORS_REPAIR)
 		seq_puts(seq, ",errors=repair");
+	if (zonefs_emergency_ro(root->d_sb))
+		seq_puts(seq, ",emergency_ro");
 
 	return 0;
 }
@@ -539,6 +552,9 @@ static int zonefs_inode_setattr(struct mnt_idmap *idmap,
 	struct inode *inode = d_inode(dentry);
 	int ret;
 
+	if (unlikely(zonefs_emergency_ro(inode->i_sb)))
+		return -EROFS;
+
 	if (unlikely(IS_IMMUTABLE(inode)))
 		return -EPERM;
 
@@ -1369,6 +1385,11 @@ static int zonefs_reconfigure(struct fs_context *fc)
 	struct super_block *sb = fc->root->d_sb;
 	struct zonefs_sb_info *sbi = sb->s_fs_info;
 
+	/* An error made the file system read-only until it is unmounted. */
+	if (sb_rdonly(sb) && !(fc->sb_flags & SB_RDONLY) &&
+	    zonefs_emergency_ro(sb))
+		return -EROFS;
+
 	sync_filesystem(fc->root->d_sb);
 	/* Copy new options from ctx into sbi. */
 	sbi->s_mount_opts = ctx->s_mount_opts;
diff --git a/fs/zonefs/zonefs.h b/fs/zonefs/zonefs.h
index 8175652241b5..b14822cb9c02 100644
--- a/fs/zonefs/zonefs.h
+++ b/fs/zonefs/zonefs.h
@@ -241,6 +241,16 @@ struct zonefs_sb_info {
 	bool			s_sysfs_registered;
 	struct kobject		s_kobj;
 	struct completion	s_kobj_unregister;
+
+	unsigned long		s_state;
+};
+
+/*
+ * Super block state bits (s_state).
+ */
+enum {
+	/* An error made the file system read-only until it is unmounted */
+	ZONEFS_SB_EMERGENCY_RO,
 };
 
 static inline struct zonefs_sb_info *ZONEFS_SB(struct super_block *sb)
@@ -248,6 +258,11 @@ static inline struct zonefs_sb_info *ZONEFS_SB(struct super_block *sb)
 	return sb->s_fs_info;
 }
 
+static inline bool zonefs_emergency_ro(struct super_block *sb)
+{
+	return test_bit(ZONEFS_SB_EMERGENCY_RO, &ZONEFS_SB(sb)->s_state);
+}
+
 #define zonefs_info(sb, format, args...)	\
 	pr_info("zonefs (%s): " format, sb->s_id, ## args)
 #define zonefs_err(sb, format, args...)		\
@@ -260,6 +275,7 @@ void zonefs_inode_account_active(struct inode *inode);
 int zonefs_inode_zone_mgmt(struct inode *inode, enum req_op op);
 void zonefs_i_size_write(struct inode *inode, loff_t isize);
 void zonefs_update_stats(struct inode *inode, loff_t new_isize);
+void zonefs_set_emergency_ro(struct super_block *sb);
 void __zonefs_io_error(struct inode *inode, bool write);
 
 static inline void zonefs_io_error(struct inode *inode, bool write)

-- 
2.53.0




More information about the linux-mtd mailing list