Re: [PATCH] f2fs: support to report fserror

From: Chao Yu

Date: Sat Mar 28 2026 - 04:28:30 EST


On 3/27/26 12:06, Darrick J. Wong wrote:
On Wed, Mar 25, 2026 at 02:32:31PM +0800, Chao Yu wrote:
+Cc Darrick and fsdevel

On 3/23/26 17:03, Chao Yu wrote:
This patch supports to report fserror, it provides another way to let
userspace to monitor filesystem level error. In addition, it exports
/sys/fs/f2fs/features/fserror once f2fs kernel module start to support
the new feature, then generic/791 of fstests can notice the feature,
and verify validation of fserror report.

Signed-off-by: Chao Yu <chao@xxxxxxxxxx>
---
Documentation/ABI/testing/sysfs-fs-f2fs | 3 ++-
fs/f2fs/compress.c | 2 ++
fs/f2fs/data.c | 13 ++++++++++++-
fs/f2fs/dir.c | 2 ++
fs/f2fs/inline.c | 3 +++
fs/f2fs/inode.c | 5 +++++
fs/f2fs/node.c | 8 ++++++++
fs/f2fs/recovery.c | 2 ++
fs/f2fs/segment.c | 2 ++
fs/f2fs/super.c | 26 +++++++++++++++++++++++++
fs/f2fs/sysfs.c | 2 ++
fs/f2fs/verity.c | 2 ++
fs/f2fs/xattr.c | 6 ++++++
13 files changed, 74 insertions(+), 2 deletions(-)

diff --git a/Documentation/ABI/testing/sysfs-fs-f2fs b/Documentation/ABI/testing/sysfs-fs-f2fs
index 423ec40e2e4e..27d5e88facbe 100644
--- a/Documentation/ABI/testing/sysfs-fs-f2fs
+++ b/Documentation/ABI/testing/sysfs-fs-f2fs
@@ -270,7 +270,8 @@ Description: Shows all enabled kernel features.
inode_checksum, flexible_inline_xattr, quota_ino,
inode_crtime, lost_found, verity, sb_checksum,
casefold, readonly, compression, test_dummy_encryption_v2,
- atomic_write, pin_file, encrypted_casefold, linear_lookup.
+ atomic_write, pin_file, encrypted_casefold, linear_lookup,
+ fserror.
What: /sys/fs/f2fs/<disk>/inject_rate
Date: May 2016
diff --git a/fs/f2fs/compress.c b/fs/f2fs/compress.c
index 8c76400ba631..d1650b763e1f 100644
--- a/fs/f2fs/compress.c
+++ b/fs/f2fs/compress.c
@@ -14,6 +14,7 @@
#include <linux/lz4.h>
#include <linux/zstd.h>
#include <linux/pagevec.h>
+#include <linux/fserror.h>
#include "f2fs.h"
#include "node.h"
@@ -760,6 +761,7 @@ void f2fs_decompress_cluster(struct decompress_io_ctx *dic, bool in_task)
/* Avoid f2fs_commit_super in irq context */
f2fs_handle_error(sbi, ERROR_FAIL_DECOMPRESSION);
+ fserror_report_file_metadata(dic->inode, ret, GFP_NOFS);
goto out_release;
}
diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c
index 9ade0669d615..6d8bcb0d15bc 100644
--- a/fs/f2fs/data.c
+++ b/fs/f2fs/data.c
@@ -20,6 +20,7 @@
#include <linux/sched/signal.h>
#include <linux/fiemap.h>
#include <linux/iomap.h>
+#include <linux/fserror.h>
#include "f2fs.h"
#include "node.h"
@@ -179,6 +180,11 @@ static void f2fs_finish_read_bio(struct bio *bio, bool in_task)
folio, folio->index, NODE_TYPE_REGULAR, true))
bio->bi_status = BLK_STS_IOERR;
+ if (bio->bi_status == BLK_STS_IOERR)
+ fserror_report_io(folio->mapping->host,
+ FSERR_BUFFERED_READ, folio_pos(folio),
+ folio_size(folio), -EIO, GFP_NOWAIT);
+
if (finished)
folio_end_read(folio, bio->bi_status == BLK_STS_OK);
}
@@ -377,9 +383,13 @@ static void f2fs_write_end_io(struct bio *bio)
if (unlikely(bio->bi_status != BLK_STS_OK)) {
mapping_set_error(folio->mapping, -EIO);
- if (type == F2FS_WB_CP_DATA)
+ fserror_report_io(folio->mapping->host,
+ FSERR_BUFFERED_WRITE, folio_pos(folio),
+ folio_size(folio), -EIO, GFP_NOWAIT);

Hrm. fserror_report_* can't be called from interrupt context because it
calls igrab, which in turn takes a spin_lock() (aka the non-irqsave
variant). So far we've worked around it by fixing callers to ensure
that they're always running in process context. I think that's not
necessarily the case for these two callsites? But it's definitely
something to check.

Yes, thanks Darrick for catching this, I missed to look into fserror_report_*,
let me drop fserror_report_io() from f2fs end_io paths.

BTW, do we have the same issue in iomap? Let me know if I'm missing something.

iomap_read_alloc_bio() registers iomap_read_end_io into bio->bi_end_io, then, in
IRQ context, bi_end_io(iomap_read_end_io) -> __iomap_read_end_io ->
iomap_finish_folio_read -> fserror_report_io -> igrab.


(We could change the spin_lock to the irqsave version, but it's rather
odd to be messing around with inode state from inside interrupt
handlers.)

Right, it's not necessary to do that.

Thanks,


The metadata corruption reporting looks ok.

--D

+ if (type == F2FS_WB_CP_DATA) {
f2fs_stop_checkpoint(sbi, true,
STOP_CP_REASON_WRITE_FAIL);
+ }
}
if (is_node_folio(folio)) {
@@ -1725,6 +1735,7 @@ int f2fs_map_blocks(struct inode *inode, struct f2fs_map_blocks *map, int flag)
err = -EFSCORRUPTED;
f2fs_handle_error(sbi,
ERROR_CORRUPTED_CLUSTER);
+ fserror_report_file_metadata(inode, err, GFP_NOFS);
goto sync_out;
}
diff --git a/fs/f2fs/dir.c b/fs/f2fs/dir.c
index f70092e231f0..e8d2e27e8cec 100644
--- a/fs/f2fs/dir.c
+++ b/fs/f2fs/dir.c
@@ -11,6 +11,7 @@
#include <linux/filelock.h>
#include <linux/sched/signal.h>
#include <linux/unicode.h>
+#include <linux/fserror.h>
#include "f2fs.h"
#include "node.h"
#include "acl.h"
@@ -1020,6 +1021,7 @@ int f2fs_fill_dentries(struct dir_context *ctx, struct f2fs_dentry_ptr *d,
set_sbi_flag(sbi, SBI_NEED_FSCK);
err = -EFSCORRUPTED;
f2fs_handle_error(sbi, ERROR_CORRUPTED_DIRENT);
+ fserror_report_file_metadata(d->inode, err, GFP_NOFS);
goto out;
}
diff --git a/fs/f2fs/inline.c b/fs/f2fs/inline.c
index 86d2abbb40ff..cfeb50d46d8d 100644
--- a/fs/f2fs/inline.c
+++ b/fs/f2fs/inline.c
@@ -9,6 +9,7 @@
#include <linux/fs.h>
#include <linux/f2fs_fs.h>
#include <linux/fiemap.h>
+#include <linux/fserror.h>
#include "f2fs.h"
#include "node.h"
@@ -179,6 +180,7 @@ int f2fs_convert_inline_folio(struct dnode_of_data *dn, struct folio *folio)
f2fs_warn(fio.sbi, "%s: corrupted inline inode ino=%lx, i_addr[0]:0x%x, run fsck to fix.",
__func__, dn->inode->i_ino, dn->data_blkaddr);
f2fs_handle_error(fio.sbi, ERROR_INVALID_BLKADDR);
+ fserror_report_file_metadata(dn->inode, -EFSCORRUPTED, GFP_NOFS);
return -EFSCORRUPTED;
}
@@ -435,6 +437,7 @@ static int f2fs_move_inline_dirents(struct inode *dir, struct folio *ifolio,
__func__, dir->i_ino, dn.data_blkaddr);
f2fs_handle_error(F2FS_F_SB(folio), ERROR_INVALID_BLKADDR);
err = -EFSCORRUPTED;
+ fserror_report_file_metadata(dn.inode, err, GFP_NOFS);
goto out;
}
diff --git a/fs/f2fs/inode.c b/fs/f2fs/inode.c
index e7942e6e312c..43cb4d9fc039 100644
--- a/fs/f2fs/inode.c
+++ b/fs/f2fs/inode.c
@@ -11,6 +11,7 @@
#include <linux/sched/mm.h>
#include <linux/lz4.h>
#include <linux/zstd.h>
+#include <linux/fserror.h>
#include "f2fs.h"
#include "node.h"
@@ -480,6 +481,7 @@ static int do_read_inode(struct inode *inode)
f2fs_folio_put(node_folio, true);
set_sbi_flag(sbi, SBI_NEED_FSCK);
f2fs_handle_error(sbi, ERROR_CORRUPTED_INODE);
+ fserror_report_file_metadata(inode, -EFSCORRUPTED, GFP_NOFS);
return -EFSCORRUPTED;
}
@@ -541,6 +543,7 @@ static int do_read_inode(struct inode *inode)
if (!sanity_check_extent_cache(inode, node_folio)) {
f2fs_folio_put(node_folio, true);
f2fs_handle_error(sbi, ERROR_CORRUPTED_INODE);
+ fserror_report_file_metadata(inode, -EFSCORRUPTED, GFP_NOFS);
return -EFSCORRUPTED;
}
@@ -583,6 +586,7 @@ struct inode *f2fs_iget(struct super_block *sb, unsigned long ino)
trace_f2fs_iget_exit(inode, ret);
iput(inode);
f2fs_handle_error(sbi, ERROR_CORRUPTED_INODE);
+ fserror_report_file_metadata(inode, ret, GFP_NOFS);
return ERR_PTR(ret);
}
@@ -787,6 +791,7 @@ void f2fs_update_inode_page(struct inode *inode)
if (err == -ENOMEM || ++count <= DEFAULT_RETRY_IO_COUNT)
goto retry;
stop_checkpoint:
+ fserror_report_file_metadata(inode, -EFSCORRUPTED, GFP_NOFS);
f2fs_stop_checkpoint(sbi, false, STOP_CP_REASON_UPDATE_INODE);
return;
}
diff --git a/fs/f2fs/node.c b/fs/f2fs/node.c
index 0de41526f28a..bb302fae75c2 100644
--- a/fs/f2fs/node.c
+++ b/fs/f2fs/node.c
@@ -12,6 +12,7 @@
#include <linux/blkdev.h>
#include <linux/pagevec.h>
#include <linux/swap.h>
+#include <linux/fserror.h>
#include "f2fs.h"
#include "node.h"
@@ -1267,6 +1268,8 @@ int f2fs_truncate_inode_blocks(struct inode *inode, pgoff_t from)
if (err == -ENOENT) {
set_sbi_flag(F2FS_F_SB(folio), SBI_NEED_FSCK);
f2fs_handle_error(sbi, ERROR_INVALID_BLKADDR);
+ fserror_report_file_metadata(dn.inode, -EFSCORRUPTED,
+ GFP_NOFS);
f2fs_err_ratelimited(sbi,
"truncate node fail, ino:%lu, nid:%u, "
"offset[0]:%d, offset[1]:%d, nofs:%d",
@@ -1558,6 +1561,8 @@ int f2fs_sanity_check_node_footer(struct f2fs_sb_info *sbi,
next_blkaddr_of_node(folio));
f2fs_handle_error(sbi, ERROR_INCONSISTENT_FOOTER);
+ fserror_report_file_metadata(folio->mapping->host,
+ -EFSCORRUPTED, in_irq ? GFP_NOWAIT : GFP_NOFS);
return -EFSCORRUPTED;
}
@@ -1779,6 +1784,7 @@ static bool __write_node_folio(struct folio *folio, bool atomic, bool *submitted
if (f2fs_sanity_check_node_footer(sbi, folio, nid,
NODE_TYPE_REGULAR, false)) {
+ fserror_report_metadata(sbi->sb, -EFSCORRUPTED, GFP_NOFS);
f2fs_stop_checkpoint(sbi, false, STOP_CP_REASON_CORRUPTED_NID);
goto redirty_out;
}
@@ -2696,6 +2702,8 @@ bool f2fs_alloc_nid(struct f2fs_sb_info *sbi, nid_t *nid)
spin_unlock(&nm_i->nid_list_lock);
f2fs_err(sbi, "Corrupted nid %u in free_nid_list",
i->nid);
+ fserror_report_metadata(sbi->sb, -EFSCORRUPTED,
+ GFP_NOFS);
f2fs_stop_checkpoint(sbi, false,
STOP_CP_REASON_CORRUPTED_NID);
return false;
diff --git a/fs/f2fs/recovery.c b/fs/f2fs/recovery.c
index a26071f2b0bc..b127dfc91338 100644
--- a/fs/f2fs/recovery.c
+++ b/fs/f2fs/recovery.c
@@ -9,6 +9,7 @@
#include <linux/fs.h>
#include <linux/f2fs_fs.h>
#include <linux/sched/mm.h>
+#include <linux/fserror.h>
#include "f2fs.h"
#include "node.h"
#include "segment.h"
@@ -679,6 +680,7 @@ static int do_recover_data(struct f2fs_sb_info *sbi, struct inode *inode,
ofs_of_node(folio));
err = -EFSCORRUPTED;
f2fs_handle_error(sbi, ERROR_INCONSISTENT_FOOTER);
+ fserror_report_file_metadata(dn.inode, err, GFP_NOFS);
goto err;
}
diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c
index 0bf25786667f..ee5c35ce5a0f 100644
--- a/fs/f2fs/segment.c
+++ b/fs/f2fs/segment.c
@@ -17,6 +17,7 @@
#include <linux/freezer.h>
#include <linux/sched/signal.h>
#include <linux/random.h>
+#include <linux/fserror.h>
#include "f2fs.h"
#include "segment.h"
@@ -2886,6 +2887,7 @@ static int get_new_segment(struct f2fs_sb_info *sbi,
/* set it as dirty segment in free segmap */
if (test_bit(segno, free_i->free_segmap)) {
ret = -EFSCORRUPTED;
+ fserror_report_metadata(sbi->sb, -EFSCORRUPTED, GFP_NOFS);
f2fs_stop_checkpoint(sbi, false, STOP_CP_REASON_CORRUPTED_FREE_BITMAP);
goto out_unlock;
}
diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c
index 5b552f08fe7b..5330ef981340 100644
--- a/fs/f2fs/super.c
+++ b/fs/f2fs/super.c
@@ -29,6 +29,7 @@
#include <linux/lz4.h>
#include <linux/ctype.h>
#include <linux/fs_parser.h>
+#include <linux/fserror.h>
#include "f2fs.h"
#include "node.h"
@@ -4632,6 +4633,8 @@ static void f2fs_record_stop_reason(struct f2fs_sb_info *sbi)
f2fs_err_ratelimited(sbi,
"f2fs_commit_super fails to record stop_reason, err:%d",
err);
+
+ fserror_report_shutdown(sbi->sb, GFP_NOFS);
}
void f2fs_save_errors(struct f2fs_sb_info *sbi, unsigned char flag)
@@ -4646,6 +4649,27 @@ void f2fs_save_errors(struct f2fs_sb_info *sbi, unsigned char flag)
spin_unlock_irqrestore(&sbi->error_lock, flags);
}
+static void f2fs_report_fserror(struct f2fs_sb_info *sbi, unsigned char error)
+{
+ switch (error) {
+ case ERROR_INVALID_BLKADDR:
+ case ERROR_CORRUPTED_INODE:
+ case ERROR_INCONSISTENT_SUMMARY:
+ case ERROR_INCONSISTENT_SUM_TYPE:
+ case ERROR_CORRUPTED_JOURNAL:
+ case ERROR_INCONSISTENT_NODE_COUNT:
+ case ERROR_INCONSISTENT_BLOCK_COUNT:
+ case ERROR_INVALID_CURSEG:
+ case ERROR_INCONSISTENT_SIT:
+ case ERROR_INVALID_NODE_REFERENCE:
+ case ERROR_INCONSISTENT_NAT:
+ fserror_report_metadata(sbi->sb, -EFSCORRUPTED, GFP_NOFS);
+ break;
+ default:
+ return;
+ }
+}
+
void f2fs_handle_error(struct f2fs_sb_info *sbi, unsigned char error)
{
f2fs_save_errors(sbi, error);
@@ -4655,6 +4679,8 @@ void f2fs_handle_error(struct f2fs_sb_info *sbi, unsigned char error)
if (!test_bit(error, (unsigned long *)sbi->errors))
return;
schedule_work(&sbi->s_error_work);
+
+ f2fs_report_fserror(sbi, error);
}
static bool system_going_down(void)
diff --git a/fs/f2fs/sysfs.c b/fs/f2fs/sysfs.c
index 969e06b65b04..5c1358e48206 100644
--- a/fs/f2fs/sysfs.c
+++ b/fs/f2fs/sysfs.c
@@ -1396,6 +1396,7 @@ F2FS_FEATURE_RO_ATTR(pin_file);
F2FS_FEATURE_RO_ATTR(linear_lookup);
#endif
F2FS_FEATURE_RO_ATTR(packed_ssa);
+F2FS_FEATURE_RO_ATTR(fserror);
#define ATTR_LIST(name) (&f2fs_attr_##name.attr)
static struct attribute *f2fs_attrs[] = {
@@ -1563,6 +1564,7 @@ static struct attribute *f2fs_feat_attrs[] = {
BASE_ATTR_LIST(linear_lookup),
#endif
BASE_ATTR_LIST(packed_ssa),
+ BASE_ATTR_LIST(fserror),
NULL,
};
ATTRIBUTE_GROUPS(f2fs_feat);
diff --git a/fs/f2fs/verity.c b/fs/f2fs/verity.c
index 92ebcc19cab0..39f482515445 100644
--- a/fs/f2fs/verity.c
+++ b/fs/f2fs/verity.c
@@ -25,6 +25,7 @@
*/
#include <linux/f2fs_fs.h>
+#include <linux/fserror.h>
#include "f2fs.h"
#include "xattr.h"
@@ -243,6 +244,7 @@ static int f2fs_get_verity_descriptor(struct inode *inode, void *buf,
f2fs_warn(F2FS_I_SB(inode), "invalid verity xattr");
f2fs_handle_error(F2FS_I_SB(inode),
ERROR_CORRUPTED_VERITY_XATTR);
+ fserror_report_file_metadata(inode, -EFSCORRUPTED, GFP_NOFS);
return -EFSCORRUPTED;
}
if (buf_size) {
diff --git a/fs/f2fs/xattr.c b/fs/f2fs/xattr.c
index 941dc62a6d6f..3ef1e5df0036 100644
--- a/fs/f2fs/xattr.c
+++ b/fs/f2fs/xattr.c
@@ -19,6 +19,7 @@
#include <linux/f2fs_fs.h>
#include <linux/security.h>
#include <linux/posix_acl_xattr.h>
+#include <linux/fserror.h>
#include "f2fs.h"
#include "xattr.h"
#include "segment.h"
@@ -371,6 +372,7 @@ static int lookup_all_xattrs(struct inode *inode, struct folio *ifolio,
err = -ENODATA;
f2fs_handle_error(F2FS_I_SB(inode),
ERROR_CORRUPTED_XATTR);
+ fserror_report_file_metadata(inode, err, GFP_NOFS);
goto out;
}
check:
@@ -590,6 +592,8 @@ ssize_t f2fs_listxattr(struct dentry *dentry, char *buffer, size_t buffer_size)
set_sbi_flag(F2FS_I_SB(inode), SBI_NEED_FSCK);
f2fs_handle_error(F2FS_I_SB(inode),
ERROR_CORRUPTED_XATTR);
+ fserror_report_file_metadata(inode,
+ -EFSCORRUPTED, GFP_NOFS);
break;
}
@@ -677,6 +681,7 @@ static int __f2fs_setxattr(struct inode *inode, int index,
error = -EFSCORRUPTED;
f2fs_handle_error(F2FS_I_SB(inode),
ERROR_CORRUPTED_XATTR);
+ fserror_report_file_metadata(inode, error, GFP_NOFS);
goto exit;
}
@@ -705,6 +710,7 @@ static int __f2fs_setxattr(struct inode *inode, int index,
error = -EFSCORRUPTED;
f2fs_handle_error(F2FS_I_SB(inode),
ERROR_CORRUPTED_XATTR);
+ fserror_report_file_metadata(inode, error, GFP_NOFS);
goto exit;
}
last = XATTR_NEXT_ENTRY(last);