1
0
Fork 0

ext4: save error info to sb through journal if available

If journalling is still working at the moment we get to writing error
information to the superblock we cannot write directly to the superblock
as such write could race with journalled update of the superblock and
cause journal checksum failures, writing inconsistent information to the
journal or other problems. We cannot journal the superblock directly
from the error handling functions as we are running in uncertain context
and could deadlock so just punt journalled superblock update to a
workqueue.

Signed-off-by: Jan Kara <jack@suse.cz>
Link: https://lore.kernel.org/r/20201216101844.22917-5-jack@suse.cz
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
master
Jan Kara 2020-12-16 11:18:40 +01:00 committed by Theodore Ts'o
parent 05c2c00f37
commit 2d01ddc866
1 changed files with 76 additions and 27 deletions

View File

@ -65,6 +65,7 @@ static struct ratelimit_state ext4_mount_msg_ratelimit;
static int ext4_load_journal(struct super_block *, struct ext4_super_block *,
unsigned long journal_devnum);
static int ext4_show_options(struct seq_file *seq, struct dentry *root);
static void ext4_update_super(struct super_block *sb);
static int ext4_commit_super(struct super_block *sb);
static int ext4_mark_recovery_complete(struct super_block *sb,
struct ext4_super_block *es);
@ -586,9 +587,9 @@ static int ext4_errno_to_code(int errno)
return EXT4_ERR_UNKNOWN;
}
static void __save_error_info(struct super_block *sb, int error,
__u32 ino, __u64 block,
const char *func, unsigned int line)
static void save_error_info(struct super_block *sb, int error,
__u32 ino, __u64 block,
const char *func, unsigned int line)
{
struct ext4_sb_info *sbi = EXT4_SB(sb);
@ -615,15 +616,6 @@ static void __save_error_info(struct super_block *sb, int error,
spin_unlock(&sbi->s_error_lock);
}
static void save_error_info(struct super_block *sb, int error,
__u32 ino, __u64 block,
const char *func, unsigned int line)
{
__save_error_info(sb, error, ino, block, func, line);
if (!bdev_read_only(sb->s_bdev))
ext4_commit_super(sb);
}
/* Deal with the reporting of failure conditions on a filesystem such as
* inconsistencies detected or read IO failures.
*
@ -649,20 +641,35 @@ static void ext4_handle_error(struct super_block *sb, bool force_ro, int error,
const char *func, unsigned int line)
{
journal_t *journal = EXT4_SB(sb)->s_journal;
bool continue_fs = !force_ro && test_opt(sb, ERRORS_CONT);
EXT4_SB(sb)->s_mount_state |= EXT4_ERROR_FS;
if (test_opt(sb, WARN_ON_ERROR))
WARN_ON_ONCE(1);
if (!bdev_read_only(sb->s_bdev))
save_error_info(sb, error, ino, block, func, line);
if (!continue_fs && !sb_rdonly(sb)) {
ext4_set_mount_flag(sb, EXT4_MF_FS_ABORTED);
if (journal)
jbd2_journal_abort(journal, -EIO);
}
if (sb_rdonly(sb) || (!force_ro && test_opt(sb, ERRORS_CONT)))
if (!bdev_read_only(sb->s_bdev)) {
save_error_info(sb, error, ino, block, func, line);
/*
* In case the fs should keep running, we need to writeout
* superblock through the journal. Due to lock ordering
* constraints, it may not be safe to do it right here so we
* defer superblock flushing to a workqueue.
*/
if (continue_fs)
schedule_work(&EXT4_SB(sb)->s_error_work);
else
ext4_commit_super(sb);
}
if (sb_rdonly(sb) || continue_fs)
return;
ext4_set_mount_flag(sb, EXT4_MF_FS_ABORTED);
if (journal)
jbd2_journal_abort(journal, -EIO);
/*
* We force ERRORS_RO behavior when system is rebooting. Otherwise we
* could panic during 'reboot -f' as the underlying device got already
@ -685,7 +692,38 @@ static void flush_stashed_error_work(struct work_struct *work)
{
struct ext4_sb_info *sbi = container_of(work, struct ext4_sb_info,
s_error_work);
journal_t *journal = sbi->s_journal;
handle_t *handle;
/*
* If the journal is still running, we have to write out superblock
* through the journal to avoid collisions of other journalled sb
* updates.
*
* We use directly jbd2 functions here to avoid recursing back into
* ext4 error handling code during handling of previous errors.
*/
if (!sb_rdonly(sbi->s_sb) && journal) {
handle = jbd2_journal_start(journal, 1);
if (IS_ERR(handle))
goto write_directly;
if (jbd2_journal_get_write_access(handle, sbi->s_sbh)) {
jbd2_journal_stop(handle);
goto write_directly;
}
ext4_update_super(sbi->s_sb);
if (jbd2_journal_dirty_metadata(handle, sbi->s_sbh)) {
jbd2_journal_stop(handle);
goto write_directly;
}
jbd2_journal_stop(handle);
return;
}
write_directly:
/*
* Write through journal failed. Write sb directly to get error info
* out and hope for the best.
*/
ext4_commit_super(sbi->s_sb);
}
@ -944,9 +982,11 @@ __acquires(bitlock)
if (test_opt(sb, WARN_ON_ERROR))
WARN_ON_ONCE(1);
EXT4_SB(sb)->s_mount_state |= EXT4_ERROR_FS;
__save_error_info(sb, EFSCORRUPTED, ino, block, function, line);
if (!bdev_read_only(sb->s_bdev))
if (!bdev_read_only(sb->s_bdev)) {
save_error_info(sb, EFSCORRUPTED, ino, block, function,
line);
schedule_work(&EXT4_SB(sb)->s_error_work);
}
return;
}
ext4_unlock_group(sb, grp);
@ -5434,15 +5474,12 @@ err_out:
return err;
}
static int ext4_commit_super(struct super_block *sb)
/* Copy state of EXT4_SB(sb) into buffer for on-disk superblock */
static void ext4_update_super(struct super_block *sb)
{
struct ext4_sb_info *sbi = EXT4_SB(sb);
struct ext4_super_block *es = EXT4_SB(sb)->s_es;
struct buffer_head *sbh = EXT4_SB(sb)->s_sbh;
int error = 0;
if (!sbh || block_device_ejected(sb))
return error;
lock_buffer(sbh);
/*
@ -5514,8 +5551,20 @@ static int ext4_commit_super(struct super_block *sb)
}
spin_unlock(&sbi->s_error_lock);
BUFFER_TRACE(sbh, "marking dirty");
ext4_superblock_csum_set(sb);
unlock_buffer(sbh);
}
static int ext4_commit_super(struct super_block *sb)
{
struct buffer_head *sbh = EXT4_SB(sb)->s_sbh;
int error = 0;
if (!sbh || block_device_ejected(sb))
return error;
ext4_update_super(sb);
if (buffer_write_io_error(sbh) || !buffer_uptodate(sbh)) {
/*
* Oh, dear. A previous attempt to write the
@ -5530,8 +5579,8 @@ static int ext4_commit_super(struct super_block *sb)
clear_buffer_write_io_error(sbh);
set_buffer_uptodate(sbh);
}
BUFFER_TRACE(sbh, "marking dirty");
mark_buffer_dirty(sbh);
unlock_buffer(sbh);
error = __sync_dirty_buffer(sbh,
REQ_SYNC | (test_opt(sb, BARRIER) ? REQ_FUA : 0));
if (buffer_write_io_error(sbh)) {