With errors=remount-ro, the nilfs2 default, __nilfs_error() sets SB_RDONLY from whatever context hit the error and without holding s_umount. If that happens while the filesystem is frozen thaw_super() sees a read-only superblock and leaves the s_writers rwsems locked so every writer hangs. nilfs2 already treats a runtime SB_RDONLY as "degraded because of corruption" in a lot of places and that can stay. So call sb_set_emergency_ro() instead and use sb_rdonly_any() for every sb_rdonly() check that guards a write. FITRIM is refused and a remount to read-only doesn't write anything anymore. The VFS doesn't call nilfs_sync_fs() in that state. nilfs2 explicitly supported "mount -o remount,rw" after an error so far. Now the state lasts until umount like on ext4. That includes a mount that reconfigures a superblock only snapshots kept alive. nilfs_get_tree() does that without reconfigure_super() so nilfs_reconfigure() refuses going read-write itself. Writes through file descriptors opened before the error fail with EROFS now instead of being dropped. Reported-by: Karl Mehltretter Link: https://lore.kernel.org/r/20260903060855.4610-1-kmehltretter@gmail.com Acked-by: Ryusuke Konishi Signed-off-by: Christian Brauner (Amutable) --- fs/nilfs2/inode.c | 4 ++-- fs/nilfs2/ioctl.c | 4 ++++ fs/nilfs2/mdt.c | 2 +- fs/nilfs2/page.c | 2 +- fs/nilfs2/segment.c | 13 ++++++------- fs/nilfs2/sufile.h | 2 +- fs/nilfs2/super.c | 25 ++++++++++++++++++------- 7 files changed, 33 insertions(+), 19 deletions(-) diff --git a/fs/nilfs2/inode.c b/fs/nilfs2/inode.c index a99a69e9f91b7..1e336af4a471e 100644 --- a/fs/nilfs2/inode.c +++ b/fs/nilfs2/inode.c @@ -162,7 +162,7 @@ static int nilfs_writepages(struct address_space *mapping, struct inode *inode = mapping->host; int err = 0; - if (sb_rdonly(inode->i_sb)) { + if (sb_rdonly_any(inode->i_sb)) { nilfs_clear_dirty_pages(mapping, false); return -EROFS; } @@ -848,7 +848,7 @@ void nilfs_evict_inode(struct inode *inode) truncate_inode_pages_final(&inode->i_data); nilfs = sb->s_fs_info; - if (unlikely(sb_rdonly(sb) || !nilfs->ns_writer)) { + if (unlikely(sb_rdonly_any(sb) || !nilfs->ns_writer)) { /* * If this inode is about to be disposed after the file system * has been degraded to read-only due to file system corruption diff --git a/fs/nilfs2/ioctl.c b/fs/nilfs2/ioctl.c index 686ac53498bcf..42848601b394c 100644 --- a/fs/nilfs2/ioctl.c +++ b/fs/nilfs2/ioctl.c @@ -1050,6 +1050,10 @@ static int nilfs_ioctl_trim_fs(struct inode *inode, void __user *argp) if (!capable(CAP_SYS_ADMIN)) return -EPERM; + /* Don't trust the segment usage after an error. */ + if (sb_emergency_ro(inode->i_sb)) + return -EROFS; + if (!bdev_max_discard_sectors(nilfs->ns_bdev)) return -EOPNOTSUPP; diff --git a/fs/nilfs2/mdt.c b/fs/nilfs2/mdt.c index 77dc2be2ce652..876a32eedbb17 100644 --- a/fs/nilfs2/mdt.c +++ b/fs/nilfs2/mdt.c @@ -400,7 +400,7 @@ static int nilfs_mdt_writeback(struct address_space *mapping, if (!inode) return 0; - if (sb_rdonly(inode->i_sb)) { + if (sb_rdonly_any(inode->i_sb)) { /* * It means that filesystem was remounted in read-only * mode because of error or metadata corruption. But we diff --git a/fs/nilfs2/page.c b/fs/nilfs2/page.c index 857926da9f21d..9d787c6dc6190 100644 --- a/fs/nilfs2/page.c +++ b/fs/nilfs2/page.c @@ -259,7 +259,7 @@ int nilfs_copy_dirty_pages(struct address_space *dmap, folio_lock(folio); if (unlikely(!folio_test_dirty(folio))) { - if (WARN_ONCE(!sb_rdonly(smap_inode->i_sb), + if (WARN_ONCE(!sb_rdonly_any(smap_inode->i_sb), "inconsistent dirty state\n")) goto unlock_folio; diff --git a/fs/nilfs2/segment.c b/fs/nilfs2/segment.c index af0c3560444f5..05c33c978b82b 100644 --- a/fs/nilfs2/segment.c +++ b/fs/nilfs2/segment.c @@ -317,7 +317,7 @@ void nilfs_relax_pressure_in_lock(struct super_block *sb) struct the_nilfs *nilfs = sb->s_fs_info; struct nilfs_sc_info *sci = nilfs->ns_writer; - if (sb_rdonly(sb) || unlikely(!sci) || !sci->sc_flush_request) + if (sb_rdonly_any(sb) || unlikely(!sci) || !sci->sc_flush_request) return; set_bit(NILFS_SC_PRIOR_FLUSH, &sci->sc_flags); @@ -2099,7 +2099,7 @@ static int nilfs_segctor_do_construct(struct nilfs_sc_info *sci, int mode) struct the_nilfs *nilfs = sci->sc_super->s_fs_info; int err; - if (sb_rdonly(sci->sc_super)) + if (sb_rdonly_any(sci->sc_super)) return -EROFS; nilfs_sc_cstage_set(sci, NILFS_ST_INIT); @@ -2323,7 +2323,7 @@ int nilfs_construct_segment(struct super_block *sb) struct nilfs_sc_info *sci = nilfs->ns_writer; struct nilfs_transaction_info *ti; - if (sb_rdonly(sb) || unlikely(!sci)) + if (sb_rdonly_any(sb) || unlikely(!sci)) return -EROFS; /* A call inside transactions causes a deadlock. */ @@ -2356,7 +2356,7 @@ int nilfs_construct_dsync_segment(struct super_block *sb, struct inode *inode, struct nilfs_transaction_info ti; int err = 0; - if (sb_rdonly(sb) || unlikely(!sci)) + if (sb_rdonly_any(sb) || unlikely(!sci)) return -EROFS; nilfs_transaction_lock(sb, &ti, 0); @@ -2870,9 +2870,8 @@ int nilfs_attach_log_writer(struct super_block *sb, struct nilfs_root *root) if (nilfs->ns_writer) { /* - * This happens if the filesystem is made read-only by - * __nilfs_error or nilfs_remount and then remounted - * read/write. In these cases, reuse the existing + * This happens if the filesystem is remounted read-only and + * then read/write again. In this case, reuse the existing * writer. */ return 0; diff --git a/fs/nilfs2/sufile.h b/fs/nilfs2/sufile.h index 5888ed479c8bd..4711d75e92357 100644 --- a/fs/nilfs2/sufile.h +++ b/fs/nilfs2/sufile.h @@ -76,7 +76,7 @@ int nilfs_sufile_trim_fs(struct inode *sufile, struct fstrim_range *range); int _err = (err); \ \ if (unlikely(_err)) \ - _err = WARN_ONCE(!sb_rdonly((sufile)->i_sb), \ + _err = WARN_ONCE(!sb_rdonly_any((sufile)->i_sb), \ "unexpected sufile error %d\n", _err) ? \ -EIO : -EROFS; \ _err; \ diff --git a/fs/nilfs2/super.c b/fs/nilfs2/super.c index 2e3b591c117c9..add1bb8a16646 100644 --- a/fs/nilfs2/super.c +++ b/fs/nilfs2/super.c @@ -137,12 +137,13 @@ void __nilfs_error(struct super_block *sb, const char *function, va_end(args); - if (!sb_rdonly(sb)) { + if (!sb_rdonly_any(sb)) { nilfs_set_error(sb); if (nilfs_test_opt(nilfs, ERRORS_RO)) { printk(KERN_CRIT "Remounting filesystem read-only\n"); - sb->s_flags |= SB_RDONLY; + /* SB_RDONLY needs s_umount, see sb_set_emergency_ro(). */ + sb_set_emergency_ro(sb); } } @@ -508,7 +509,7 @@ static void nilfs_put_super(struct super_block *sb) nilfs_detach_log_writer(sb); - if (!sb_rdonly(sb)) { + if (!sb_rdonly_any(sb)) { down_write(&nilfs->ns_sem); nilfs_cleanup_super(sb); up_write(&nilfs->ns_sem); @@ -588,7 +589,7 @@ static int nilfs_freeze(struct super_block *sb) struct the_nilfs *nilfs = sb->s_fs_info; int err; - if (sb_rdonly(sb)) + if (sb_rdonly_any(sb)) return 0; /* Mark super block clean */ @@ -602,7 +603,7 @@ static int nilfs_unfreeze(struct super_block *sb) { struct the_nilfs *nilfs = sb->s_fs_info; - if (sb_rdonly(sb)) + if (sb_rdonly_any(sb)) return 0; down_write(&nilfs->ns_sem); @@ -1083,7 +1084,7 @@ nilfs_fill_super(struct super_block *sb, struct fs_context *fc) goto failed_unload; } - if (!sb_rdonly(sb)) { + if (!sb_rdonly_any(sb)) { err = nilfs_attach_log_writer(sb, fsroot); if (err) goto failed_checkpoint; @@ -1095,7 +1096,7 @@ nilfs_fill_super(struct super_block *sb, struct fs_context *fc) nilfs_put_root(fsroot); - if (!sb_rdonly(sb)) { + if (!sb_rdonly_any(sb)) { down_write(&nilfs->ns_sem); nilfs_setup_super(sb, true); up_write(&nilfs->ns_sem); @@ -1141,6 +1142,10 @@ static int nilfs_reconfigure(struct fs_context *fc) if (fc->sb_flags & SB_RDONLY) { sb->s_flags |= SB_RDONLY; + /* After an error nothing may be written, just go read-only. */ + if (sb_emergency_ro(sb)) + goto out; + /* * Remounting a valid RW partition RDONLY, so set * the RDONLY flag and then mark the partition as valid again. @@ -1152,6 +1157,12 @@ static int nilfs_reconfigure(struct fs_context *fc) __u64 features; struct nilfs_root *root; + /* The VFS doesn't refuse this when nilfs_get_tree() calls us. */ + if (sb_emergency_ro(sb)) { + err = -EROFS; + goto ignore_opts; + } + /* * Mounting a RDONLY partition read-write, so reread and * store the current valid flag. (It may have been changed -- 2.53.0