ubifs_ro_mode() sets c->ro_error and SB_RDONLY from whatever context hit the error, e.g. a failing write, and without holding s_umount. UBIFS can't be frozen but that still races with everyone else changing sb->s_flags. And c->ro_error alone already makes every write fail with EROFS. So call sb_set_emergency_ro() instead. The background thread can hit the error before SB_BORN but doesn't hold s_umount either. The xattr setters check c->ro_error as well because ecryptfs calls them without write access on the lower mount and they change the inode in memory first. So far a remount to read-only after an error was refused and a remount to read-write went through and cleared SB_RDONLY again because c->ro_mount was never set. Now going read-only works. Nothing is synced for it in that state and ubifs_remount_ro() skips the write-buffer syncs and the master node like ubifs_put_super() does and only cancels the write-buffer timers. Going read-write after that is refused by the VFS. The flusher still gets to the folios and inodes that were dirty before the error, also after the remount which sets c->ro_mount and frees the write reserve buffer. Every journal write fails with EROFS after an error anyway. So do_writepage() and ubifs_write_inode() drop them without going near the journal. Otherwise reserve_space() trips its !c->ro_mount assert for every folio after the remount. Signed-off-by: Christian Brauner (Amutable) --- fs/ubifs/file.c | 12 ++++++++++++ fs/ubifs/io.c | 6 +++++- fs/ubifs/super.c | 40 ++++++++++++++++++++++++++-------------- fs/ubifs/xattr.c | 6 ++++++ 4 files changed, 49 insertions(+), 15 deletions(-) diff --git a/fs/ubifs/file.c b/fs/ubifs/file.c index 99dd52715eee9..4eae6c2ae2eea 100644 --- a/fs/ubifs/file.c +++ b/fs/ubifs/file.c @@ -898,6 +898,13 @@ static int do_writepage(struct folio *folio, size_t len) folio_start_writeback(folio); + /* Nothing gets written after an error, drop the folio. */ + if (unlikely(c->ro_error)) { + err = -EROFS; + mapping_set_error(folio->mapping, err); + goto out; + } + block = folio->index << UBIFS_BLOCKS_PER_PAGE_SHIFT; for (;;) { blen = min_t(size_t, len, UBIFS_BLOCK_SIZE); @@ -918,6 +925,7 @@ static int do_writepage(struct folio *folio, size_t len) ubifs_ro_mode(c, err); } +out: ubifs_assert(c, folio->private != NULL); if (folio_test_checked(folio)) release_new_page_budget(c); @@ -997,6 +1005,10 @@ static int ubifs_writepage(struct folio *folio, struct writeback_control *wbc) goto out_unlock; } + /* After an error do_writepage() drops the folio. */ + if (unlikely(c->ro_error)) + return do_writepage(folio, len); + spin_lock(&ui->ui_lock); synced_i_size = ui->synced_i_size; spin_unlock(&ui->ui_lock); diff --git a/fs/ubifs/io.c b/fs/ubifs/io.c index 6c6d682427793..931a88329b5f5 100644 --- a/fs/ubifs/io.c +++ b/fs/ubifs/io.c @@ -72,7 +72,11 @@ void ubifs_ro_mode(struct ubifs_info *c, int err) if (!c->ro_error) { c->ro_error = 1; c->no_chk_data_crc = 0; - c->vfs_sb->s_flags |= SB_RDONLY; + /* + * SB_RDONLY needs s_umount, see sb_set_emergency_ro(). The + * background thread runs before SB_BORN already. + */ + sb_set_emergency_ro(c->vfs_sb); ubifs_warn(c, "switched to read-only mode, error %d", err); dump_stack(); } diff --git a/fs/ubifs/super.c b/fs/ubifs/super.c index 38972786817e7..2d71274e89ad0 100644 --- a/fs/ubifs/super.c +++ b/fs/ubifs/super.c @@ -312,6 +312,12 @@ static int ubifs_write_inode(struct inode *inode, struct writeback_control *wbc) return 0; } + /* Nothing gets written after an error, drop the change. */ + if (unlikely(c->ro_error)) { + err = -EROFS; + goto out; + } + /* * As an optimization, do not write orphan inodes to the media just * because this is not needed. @@ -327,6 +333,7 @@ static int ubifs_write_inode(struct inode *inode, struct writeback_control *wbc) err = dbg_check_inode_size(c, inode, ui->ui_size); } +out: ui->dirty = 0; mutex_unlock(&ui->ui_mutex); ubifs_release_dirty_inode_budget(c, ui); @@ -1839,19 +1846,26 @@ static void ubifs_remount_ro(struct ubifs_info *c) dbg_save_space_info(c); - for (i = 0; i < c->jhead_cnt; i++) { - err = ubifs_wbuf_sync(&c->jheads[i].wbuf); + /* Like ubifs_put_super(), don't write anything after an error. */ + if (!c->ro_error) { + for (i = 0; i < c->jhead_cnt; i++) { + err = ubifs_wbuf_sync(&c->jheads[i].wbuf); + if (err) + ubifs_ro_mode(c, err); + } + + c->mst_node->flags &= ~cpu_to_le32(UBIFS_MST_DIRTY); + c->mst_node->flags |= cpu_to_le32(UBIFS_MST_NO_ORPHS); + c->mst_node->gc_lnum = cpu_to_le32(c->gc_lnum); + err = ubifs_write_master(c); if (err) ubifs_ro_mode(c, err); + } else { + /* Syncing the write-buffers would have canceled them. */ + for (i = 0; i < c->jhead_cnt; i++) + hrtimer_cancel(&c->jheads[i].wbuf.timer); } - c->mst_node->flags &= ~cpu_to_le32(UBIFS_MST_DIRTY); - c->mst_node->flags |= cpu_to_le32(UBIFS_MST_NO_ORPHS); - c->mst_node->gc_lnum = cpu_to_le32(c->gc_lnum); - err = ubifs_write_master(c); - if (err) - ubifs_ro_mode(c, err); - vfree(c->orph_buf); c->orph_buf = NULL; kfree(c->write_reserve_buf); @@ -1977,10 +1991,7 @@ static int ubifs_reconfigure(struct fs_context *fc) if (err) return err; } else if (!c->ro_mount && (fc->sb_flags & SB_RDONLY)) { - if (c->ro_error) { - ubifs_msg(c, "cannot re-mount R/O due to prior errors"); - return -EROFS; - } + /* After an error ubifs_remount_ro() can't write but still works. */ ubifs_remount_ro(c); } @@ -1994,7 +2005,8 @@ static int ubifs_reconfigure(struct fs_context *fc) mutex_unlock(&c->bu_mutex); } - if (!c->need_recovery) + /* Only valid without a media failure, see ubifs_put_super(). */ + if (!c->need_recovery && !c->ro_error) ubifs_assert(c, c->lst.taken_empty_lebs > 0); return 0; diff --git a/fs/ubifs/xattr.c b/fs/ubifs/xattr.c index 3b0e8a270f594..572f6f3e52ad3 100644 --- a/fs/ubifs/xattr.c +++ b/fs/ubifs/xattr.c @@ -260,6 +260,9 @@ int ubifs_xattr_set(struct inode *host, const char *name, const void *value, if (check_lock) ubifs_assert(c, inode_is_locked(host)); + if (c->ro_error) + return -EROFS; + if (size > UBIFS_MAX_INO_DATA) return -ERANGE; @@ -564,6 +567,9 @@ static int ubifs_xattr_remove(struct inode *host, const char *name) ubifs_assert(c, inode_is_locked(host)); + if (c->ro_error) + return -EROFS; + if (fname_len(&nm) > UBIFS_MAX_NLEN) return -ENAMETOOLONG; -- 2.53.0