f2fs: support errors=remount-ro|continue|panic mountoption

This patch supports errors=remount-ro|continue|panic mount option
for f2fs.

f2fs behaves as below in three different modes:
mode			continue	remount-ro	panic
access ops		normal		noraml		N/A
syscall errors		-EIO		-EROFS		N/A
mount option		rw		ro		N/A
pending dir write	keep		keep		N/A
pending non-dir write	drop		keep		N/A
pending node write	drop		keep		N/A
pending meta write	keep		keep		N/A

By default it uses "continue" mode.

[Yangtao helps to clean up function's name]
Signed-off-by: Yangtao Li <frank.li@vivo.com>
Signed-off-by: Chao Yu <chao@kernel.org>
Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org>
This commit is contained in:
Chao Yu 2023-04-23 23:49:15 +08:00 committed by Jaegeuk Kim
parent ac9a78681b
commit b62e71be21
8 changed files with 167 additions and 24 deletions

View File

@ -351,6 +351,22 @@ age_extent_cache Enable an age extent cache based on rb-tree. It records
data block update frequency of the extent per inode, in data block update frequency of the extent per inode, in
order to provide better temperature hints for data block order to provide better temperature hints for data block
allocation. allocation.
errors=%s Specify f2fs behavior on critical errors. This supports modes:
"panic", "continue" and "remount-ro", respectively, trigger
panic immediately, continue without doing anything, and remount
the partition in read-only mode. By default it uses "continue"
mode.
====================== =============== =============== ========
mode continue remount-ro panic
====================== =============== =============== ========
access ops normal noraml N/A
syscall errors -EIO -EROFS N/A
mount option rw ro N/A
pending dir write keep keep N/A
pending non-dir write drop keep N/A
pending node write drop keep N/A
pending meta write keep keep N/A
====================== =============== =============== ========
======================== ============================================================ ======================== ============================================================
Debugfs Entries Debugfs Entries

View File

@ -30,12 +30,9 @@ void f2fs_stop_checkpoint(struct f2fs_sb_info *sbi, bool end_io,
unsigned char reason) unsigned char reason)
{ {
f2fs_build_fault_attr(sbi, 0, 0); f2fs_build_fault_attr(sbi, 0, 0);
set_ckpt_flags(sbi, CP_ERROR_FLAG); if (!end_io)
if (!end_io) {
f2fs_flush_merged_writes(sbi); f2fs_flush_merged_writes(sbi);
f2fs_handle_critical_error(sbi, reason, end_io);
f2fs_handle_stop(sbi, reason);
}
} }
/* /*

View File

@ -2807,6 +2807,10 @@ int f2fs_write_single_data_page(struct page *page, int *submitted,
if (S_ISDIR(inode->i_mode) && if (S_ISDIR(inode->i_mode) &&
!is_sbi_flag_set(sbi, SBI_IS_CLOSE)) !is_sbi_flag_set(sbi, SBI_IS_CLOSE))
goto redirty_out; goto redirty_out;
/* keep data pages in remount-ro mode */
if (F2FS_OPTION(sbi).errors == MOUNT_ERRORS_READONLY)
goto redirty_out;
goto out; goto out;
} }

View File

@ -162,6 +162,7 @@ struct f2fs_mount_info {
int fs_mode; /* fs mode: LFS or ADAPTIVE */ int fs_mode; /* fs mode: LFS or ADAPTIVE */
int bggc_mode; /* bggc mode: off, on or sync */ int bggc_mode; /* bggc mode: off, on or sync */
int memory_mode; /* memory mode */ int memory_mode; /* memory mode */
int errors; /* errors parameter */
int discard_unit; /* int discard_unit; /*
* discard command's offset/size should * discard command's offset/size should
* be aligned to this unit: block, * be aligned to this unit: block,
@ -1370,6 +1371,12 @@ enum {
MEMORY_MODE_LOW, /* memory mode for low memry devices */ MEMORY_MODE_LOW, /* memory mode for low memry devices */
}; };
enum errors_option {
MOUNT_ERRORS_READONLY, /* remount fs ro on errors */
MOUNT_ERRORS_CONTINUE, /* continue on errors */
MOUNT_ERRORS_PANIC, /* panic on errors */
};
static inline int f2fs_test_bit(unsigned int nr, char *addr); static inline int f2fs_test_bit(unsigned int nr, char *addr);
static inline void f2fs_set_bit(unsigned int nr, char *addr); static inline void f2fs_set_bit(unsigned int nr, char *addr);
static inline void f2fs_clear_bit(unsigned int nr, char *addr); static inline void f2fs_clear_bit(unsigned int nr, char *addr);
@ -1721,8 +1728,14 @@ struct f2fs_sb_info {
struct workqueue_struct *post_read_wq; /* post read workqueue */ struct workqueue_struct *post_read_wq; /* post read workqueue */
unsigned char errors[MAX_F2FS_ERRORS]; /* error flags */ /*
spinlock_t error_lock; /* protect errors array */ * If we are in irq context, let's update error information into
* on-disk superblock in the work.
*/
struct work_struct s_error_work;
unsigned char errors[MAX_F2FS_ERRORS]; /* error flags */
unsigned char stop_reason[MAX_STOP_REASON]; /* stop reason */
spinlock_t error_lock; /* protect errors/stop_reason array */
bool error_dirty; /* errors of sb is dirty */ bool error_dirty; /* errors of sb is dirty */
struct kmem_cache *inline_xattr_slab; /* inline xattr entry */ struct kmem_cache *inline_xattr_slab; /* inline xattr entry */
@ -3541,8 +3554,9 @@ int f2fs_enable_quota_files(struct f2fs_sb_info *sbi, bool rdonly);
int f2fs_quota_sync(struct super_block *sb, int type); int f2fs_quota_sync(struct super_block *sb, int type);
loff_t max_file_blocks(struct inode *inode); loff_t max_file_blocks(struct inode *inode);
void f2fs_quota_off_umount(struct super_block *sb); void f2fs_quota_off_umount(struct super_block *sb);
void f2fs_handle_stop(struct f2fs_sb_info *sbi, unsigned char reason);
void f2fs_save_errors(struct f2fs_sb_info *sbi, unsigned char flag); void f2fs_save_errors(struct f2fs_sb_info *sbi, unsigned char flag);
void f2fs_handle_critical_error(struct f2fs_sb_info *sbi, unsigned char reason,
bool irq_context);
void f2fs_handle_error(struct f2fs_sb_info *sbi, unsigned char error); void f2fs_handle_error(struct f2fs_sb_info *sbi, unsigned char error);
int f2fs_commit_super(struct f2fs_sb_info *sbi, bool recover); int f2fs_commit_super(struct f2fs_sb_info *sbi, bool recover);
int f2fs_sync_fs(struct super_block *sb, int sync); int f2fs_sync_fs(struct super_block *sb, int sync);

View File

@ -2225,7 +2225,6 @@ static int f2fs_ioc_shutdown(struct file *filp, unsigned long arg)
ret = 0; ret = 0;
f2fs_stop_checkpoint(sbi, false, f2fs_stop_checkpoint(sbi, false,
STOP_CP_REASON_SHUTDOWN); STOP_CP_REASON_SHUTDOWN);
set_sbi_flag(sbi, SBI_IS_SHUTDOWN);
trace_f2fs_shutdown(sbi, in, ret); trace_f2fs_shutdown(sbi, in, ret);
} }
return ret; return ret;
@ -2238,7 +2237,6 @@ static int f2fs_ioc_shutdown(struct file *filp, unsigned long arg)
if (ret) if (ret)
goto out; goto out;
f2fs_stop_checkpoint(sbi, false, STOP_CP_REASON_SHUTDOWN); f2fs_stop_checkpoint(sbi, false, STOP_CP_REASON_SHUTDOWN);
set_sbi_flag(sbi, SBI_IS_SHUTDOWN);
thaw_bdev(sb->s_bdev); thaw_bdev(sb->s_bdev);
break; break;
case F2FS_GOING_DOWN_METASYNC: case F2FS_GOING_DOWN_METASYNC:
@ -2247,16 +2245,13 @@ static int f2fs_ioc_shutdown(struct file *filp, unsigned long arg)
if (ret) if (ret)
goto out; goto out;
f2fs_stop_checkpoint(sbi, false, STOP_CP_REASON_SHUTDOWN); f2fs_stop_checkpoint(sbi, false, STOP_CP_REASON_SHUTDOWN);
set_sbi_flag(sbi, SBI_IS_SHUTDOWN);
break; break;
case F2FS_GOING_DOWN_NOSYNC: case F2FS_GOING_DOWN_NOSYNC:
f2fs_stop_checkpoint(sbi, false, STOP_CP_REASON_SHUTDOWN); f2fs_stop_checkpoint(sbi, false, STOP_CP_REASON_SHUTDOWN);
set_sbi_flag(sbi, SBI_IS_SHUTDOWN);
break; break;
case F2FS_GOING_DOWN_METAFLUSH: case F2FS_GOING_DOWN_METAFLUSH:
f2fs_sync_meta_pages(sbi, META, LONG_MAX, FS_META_IO); f2fs_sync_meta_pages(sbi, META, LONG_MAX, FS_META_IO);
f2fs_stop_checkpoint(sbi, false, STOP_CP_REASON_SHUTDOWN); f2fs_stop_checkpoint(sbi, false, STOP_CP_REASON_SHUTDOWN);
set_sbi_flag(sbi, SBI_IS_SHUTDOWN);
break; break;
case F2FS_GOING_DOWN_NEED_FSCK: case F2FS_GOING_DOWN_NEED_FSCK:
set_sbi_flag(sbi, SBI_NEED_FSCK); set_sbi_flag(sbi, SBI_NEED_FSCK);

View File

@ -59,7 +59,7 @@ static int gc_thread_func(void *data)
if (gc_th->gc_wake) if (gc_th->gc_wake)
gc_th->gc_wake = false; gc_th->gc_wake = false;
if (try_to_freeze()) { if (try_to_freeze() || f2fs_readonly(sbi->sb)) {
stat_other_skip_bggc_count(sbi); stat_other_skip_bggc_count(sbi);
continue; continue;
} }

View File

@ -1596,6 +1596,9 @@ static int __write_node_page(struct page *page, bool atomic, bool *submitted,
trace_f2fs_writepage(page, NODE); trace_f2fs_writepage(page, NODE);
if (unlikely(f2fs_cp_error(sbi))) { if (unlikely(f2fs_cp_error(sbi))) {
/* keep node pages in remount-ro mode */
if (F2FS_OPTION(sbi).errors == MOUNT_ERRORS_READONLY)
goto redirty_out;
ClearPageUptodate(page); ClearPageUptodate(page);
dec_page_count(sbi, F2FS_DIRTY_NODES); dec_page_count(sbi, F2FS_DIRTY_NODES);
unlock_page(page); unlock_page(page);

View File

@ -164,6 +164,7 @@ enum {
Opt_discard_unit, Opt_discard_unit,
Opt_memory_mode, Opt_memory_mode,
Opt_age_extent_cache, Opt_age_extent_cache,
Opt_errors,
Opt_err, Opt_err,
}; };
@ -243,6 +244,7 @@ static match_table_t f2fs_tokens = {
{Opt_discard_unit, "discard_unit=%s"}, {Opt_discard_unit, "discard_unit=%s"},
{Opt_memory_mode, "memory=%s"}, {Opt_memory_mode, "memory=%s"},
{Opt_age_extent_cache, "age_extent_cache"}, {Opt_age_extent_cache, "age_extent_cache"},
{Opt_errors, "errors=%s"},
{Opt_err, NULL}, {Opt_err, NULL},
}; };
@ -1268,6 +1270,25 @@ static int parse_options(struct super_block *sb, char *options, bool is_remount)
case Opt_age_extent_cache: case Opt_age_extent_cache:
set_opt(sbi, AGE_EXTENT_CACHE); set_opt(sbi, AGE_EXTENT_CACHE);
break; break;
case Opt_errors:
name = match_strdup(&args[0]);
if (!name)
return -ENOMEM;
if (!strcmp(name, "remount-ro")) {
F2FS_OPTION(sbi).errors =
MOUNT_ERRORS_READONLY;
} else if (!strcmp(name, "continue")) {
F2FS_OPTION(sbi).errors =
MOUNT_ERRORS_CONTINUE;
} else if (!strcmp(name, "panic")) {
F2FS_OPTION(sbi).errors =
MOUNT_ERRORS_PANIC;
} else {
kfree(name);
return -EINVAL;
}
kfree(name);
break;
default: default:
f2fs_err(sbi, "Unrecognized mount option \"%s\" or missing value", f2fs_err(sbi, "Unrecognized mount option \"%s\" or missing value",
p); p);
@ -1622,6 +1643,9 @@ static void f2fs_put_super(struct super_block *sb)
f2fs_destroy_node_manager(sbi); f2fs_destroy_node_manager(sbi);
f2fs_destroy_segment_manager(sbi); f2fs_destroy_segment_manager(sbi);
/* flush s_error_work before sbi destroy */
flush_work(&sbi->s_error_work);
f2fs_destroy_post_read_wq(sbi); f2fs_destroy_post_read_wq(sbi);
kvfree(sbi->ckpt); kvfree(sbi->ckpt);
@ -2052,6 +2076,13 @@ static int f2fs_show_options(struct seq_file *seq, struct dentry *root)
else if (F2FS_OPTION(sbi).memory_mode == MEMORY_MODE_LOW) else if (F2FS_OPTION(sbi).memory_mode == MEMORY_MODE_LOW)
seq_printf(seq, ",memory=%s", "low"); seq_printf(seq, ",memory=%s", "low");
if (F2FS_OPTION(sbi).errors == MOUNT_ERRORS_READONLY)
seq_printf(seq, ",errors=%s", "remount-ro");
else if (F2FS_OPTION(sbi).errors == MOUNT_ERRORS_CONTINUE)
seq_printf(seq, ",errors=%s", "continue");
else if (F2FS_OPTION(sbi).errors == MOUNT_ERRORS_PANIC)
seq_printf(seq, ",errors=%s", "panic");
return 0; return 0;
} }
@ -2080,6 +2111,7 @@ static void default_options(struct f2fs_sb_info *sbi)
} }
F2FS_OPTION(sbi).bggc_mode = BGGC_MODE_ON; F2FS_OPTION(sbi).bggc_mode = BGGC_MODE_ON;
F2FS_OPTION(sbi).memory_mode = MEMORY_MODE_NORMAL; F2FS_OPTION(sbi).memory_mode = MEMORY_MODE_NORMAL;
F2FS_OPTION(sbi).errors = MOUNT_ERRORS_CONTINUE;
sbi->sb->s_flags &= ~SB_INLINECRYPT; sbi->sb->s_flags &= ~SB_INLINECRYPT;
@ -2281,6 +2313,9 @@ static int f2fs_remount(struct super_block *sb, int *flags, char *data)
if (err) if (err)
goto restore_opts; goto restore_opts;
/* flush outstanding errors before changing fs state */
flush_work(&sbi->s_error_work);
/* /*
* Previous and new state of filesystem is RO, * Previous and new state of filesystem is RO,
* so skip checking GC and FLUSH_MERGE conditions. * so skip checking GC and FLUSH_MERGE conditions.
@ -3926,45 +3961,60 @@ int f2fs_commit_super(struct f2fs_sb_info *sbi, bool recover)
return err; return err;
} }
void f2fs_handle_stop(struct f2fs_sb_info *sbi, unsigned char reason) static void save_stop_reason(struct f2fs_sb_info *sbi, unsigned char reason)
{
unsigned long flags;
spin_lock_irqsave(&sbi->error_lock, flags);
if (sbi->stop_reason[reason] < GENMASK(BITS_PER_BYTE - 1, 0))
sbi->stop_reason[reason]++;
spin_unlock_irqrestore(&sbi->error_lock, flags);
}
static void f2fs_record_stop_reason(struct f2fs_sb_info *sbi)
{ {
struct f2fs_super_block *raw_super = F2FS_RAW_SUPER(sbi); struct f2fs_super_block *raw_super = F2FS_RAW_SUPER(sbi);
unsigned long flags;
int err; int err;
f2fs_down_write(&sbi->sb_lock); f2fs_down_write(&sbi->sb_lock);
if (raw_super->s_stop_reason[reason] < GENMASK(BITS_PER_BYTE - 1, 0)) spin_lock_irqsave(&sbi->error_lock, flags);
raw_super->s_stop_reason[reason]++; memcpy(raw_super->s_stop_reason, sbi->stop_reason, MAX_STOP_REASON);
spin_unlock_irqrestore(&sbi->error_lock, flags);
err = f2fs_commit_super(sbi, false); err = f2fs_commit_super(sbi, false);
if (err)
f2fs_err(sbi, "f2fs_commit_super fails to record reason:%u err:%d",
reason, err);
f2fs_up_write(&sbi->sb_lock); f2fs_up_write(&sbi->sb_lock);
if (err)
f2fs_err(sbi, "f2fs_commit_super fails to record err:%d", err);
} }
void f2fs_save_errors(struct f2fs_sb_info *sbi, unsigned char flag) void f2fs_save_errors(struct f2fs_sb_info *sbi, unsigned char flag)
{ {
spin_lock(&sbi->error_lock); unsigned long flags;
spin_lock_irqsave(&sbi->error_lock, flags);
if (!test_bit(flag, (unsigned long *)sbi->errors)) { if (!test_bit(flag, (unsigned long *)sbi->errors)) {
set_bit(flag, (unsigned long *)sbi->errors); set_bit(flag, (unsigned long *)sbi->errors);
sbi->error_dirty = true; sbi->error_dirty = true;
} }
spin_unlock(&sbi->error_lock); spin_unlock_irqrestore(&sbi->error_lock, flags);
} }
static bool f2fs_update_errors(struct f2fs_sb_info *sbi) static bool f2fs_update_errors(struct f2fs_sb_info *sbi)
{ {
unsigned long flags;
bool need_update = false; bool need_update = false;
spin_lock(&sbi->error_lock); spin_lock_irqsave(&sbi->error_lock, flags);
if (sbi->error_dirty) { if (sbi->error_dirty) {
memcpy(F2FS_RAW_SUPER(sbi)->s_errors, sbi->errors, memcpy(F2FS_RAW_SUPER(sbi)->s_errors, sbi->errors,
MAX_F2FS_ERRORS); MAX_F2FS_ERRORS);
sbi->error_dirty = false; sbi->error_dirty = false;
need_update = true; need_update = true;
} }
spin_unlock(&sbi->error_lock); spin_unlock_irqrestore(&sbi->error_lock, flags);
return need_update; return need_update;
} }
@ -3988,6 +4038,66 @@ void f2fs_handle_error(struct f2fs_sb_info *sbi, unsigned char error)
f2fs_up_write(&sbi->sb_lock); f2fs_up_write(&sbi->sb_lock);
} }
static bool system_going_down(void)
{
return system_state == SYSTEM_HALT || system_state == SYSTEM_POWER_OFF
|| system_state == SYSTEM_RESTART;
}
void f2fs_handle_critical_error(struct f2fs_sb_info *sbi, unsigned char reason,
bool irq_context)
{
struct super_block *sb = sbi->sb;
bool shutdown = reason == STOP_CP_REASON_SHUTDOWN;
bool continue_fs = !shutdown &&
F2FS_OPTION(sbi).errors == MOUNT_ERRORS_CONTINUE;
set_ckpt_flags(sbi, CP_ERROR_FLAG);
if (!f2fs_hw_is_readonly(sbi)) {
save_stop_reason(sbi, reason);
if (irq_context && !shutdown)
schedule_work(&sbi->s_error_work);
else
f2fs_record_stop_reason(sbi);
}
/*
* We force ERRORS_RO behavior when system is rebooting. Otherwise we
* could panic during 'reboot -f' as the underlying device got already
* disabled.
*/
if (F2FS_OPTION(sbi).errors == MOUNT_ERRORS_PANIC &&
!shutdown && !system_going_down() &&
!is_sbi_flag_set(sbi, SBI_IS_SHUTDOWN))
panic("F2FS-fs (device %s): panic forced after error\n",
sb->s_id);
if (shutdown)
set_sbi_flag(sbi, SBI_IS_SHUTDOWN);
/* continue filesystem operators if errors=continue */
if (continue_fs || f2fs_readonly(sb))
return;
f2fs_warn(sbi, "Remounting filesystem read-only");
/*
* Make sure updated value of ->s_mount_flags will be visible before
* ->s_flags update
*/
smp_wmb();
sb->s_flags |= SB_RDONLY;
}
static void f2fs_record_error_work(struct work_struct *work)
{
struct f2fs_sb_info *sbi = container_of(work,
struct f2fs_sb_info, s_error_work);
f2fs_record_stop_reason(sbi);
}
static int f2fs_scan_devices(struct f2fs_sb_info *sbi) static int f2fs_scan_devices(struct f2fs_sb_info *sbi)
{ {
struct f2fs_super_block *raw_super = F2FS_RAW_SUPER(sbi); struct f2fs_super_block *raw_super = F2FS_RAW_SUPER(sbi);
@ -4218,7 +4328,9 @@ static int f2fs_fill_super(struct super_block *sb, void *data, int silent)
sb->s_fs_info = sbi; sb->s_fs_info = sbi;
sbi->raw_super = raw_super; sbi->raw_super = raw_super;
INIT_WORK(&sbi->s_error_work, f2fs_record_error_work);
memcpy(sbi->errors, raw_super->s_errors, MAX_F2FS_ERRORS); memcpy(sbi->errors, raw_super->s_errors, MAX_F2FS_ERRORS);
memcpy(sbi->stop_reason, raw_super->s_stop_reason, MAX_STOP_REASON);
/* precompute checksum seed for metadata */ /* precompute checksum seed for metadata */
if (f2fs_sb_has_inode_chksum(sbi)) if (f2fs_sb_has_inode_chksum(sbi))
@ -4615,6 +4727,8 @@ static int f2fs_fill_super(struct super_block *sb, void *data, int silent)
f2fs_destroy_segment_manager(sbi); f2fs_destroy_segment_manager(sbi);
stop_ckpt_thread: stop_ckpt_thread:
f2fs_stop_ckpt_thread(sbi); f2fs_stop_ckpt_thread(sbi);
/* flush s_error_work before sbi destroy */
flush_work(&sbi->s_error_work);
f2fs_destroy_post_read_wq(sbi); f2fs_destroy_post_read_wq(sbi);
free_devices: free_devices:
destroy_device_list(sbi); destroy_device_list(sbi);