* [PATCH v2 0/5] ntfs: report filesystem errors through fsnotify
@ 2026-09-16 2:23 Baolin Liu
2026-09-16 2:23 ` [PATCH v2 1/5] ntfs: report allocation metadata errors to fsnotify Baolin Liu
` (5 more replies)
0 siblings, 6 replies; 9+ messages in thread
From: Baolin Liu @ 2026-09-16 2:23 UTC (permalink / raw)
To: linkinjeon, hyc.lee; +Cc: ntfs, linux-kernel, Baolin Liu
From: Baolin Liu <liubaolin@kylinos.cn>
Report NTFS metadata and shutdown errors through the generic filesystem
error notification infrastructure. Use volume-level events when no
affected file is available and file-level events when an inode identifies
the failure.
Split the reporting by allocation, attribute, inode, MFT, and shutdown
domains. Propagate reporting state through attribute searches,
mapping-pairs updates, and MFT mappings so callers do not emit duplicate
events.
Changes since v1:
- Restrict patch 1 to the volume helper and non-file allocation rollbacks.
- Introduce the file helper with attribute reporting in patch 2.
- Separate inode and MFT reporting into patches 3 and 4.
- Adapt MFT writeback reporting to the current asynchronous I/O path.
- Keep shutdown reporting independent in patch 5.
Testing:
- Built fs/ntfs with W=1 after each patch.
- Booted the patched kernel in QEMU and monitored FAN_FS_ERROR events on
an NTFS volume with fanotify. Verified that volume metadata errors report
EIO without a valid file handle, file metadata errors report EIO with a
valid FID, and shutdown errors report ESHUTDOWN.
Baolin Liu (5):
ntfs: report allocation metadata errors to fsnotify
ntfs: report attribute errors to fsnotify
ntfs: report inode metadata errors to fsnotify
ntfs: report MFT errors to fsnotify
ntfs: report shutdown errors to fsnotify
fs/ntfs/attrib.c | 88 +++++++++++-----
fs/ntfs/attrib.h | 6 ++
fs/ntfs/attrlist.c | 16 +--
fs/ntfs/bitmap.c | 2 +-
fs/ntfs/inode.c | 62 +++++++----
fs/ntfs/lcnalloc.c | 4 +-
fs/ntfs/logfile.c | 2 +-
fs/ntfs/mft.c | 252 ++++++++++++++++++++++++++++++---------------
fs/ntfs/mft.h | 6 ++
fs/ntfs/super.c | 27 ++++-
fs/ntfs/volume.h | 4 +
11 files changed, 329 insertions(+), 140 deletions(-)
--
2.51.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v2 1/5] ntfs: report allocation metadata errors to fsnotify
2026-09-16 2:23 [PATCH v2 0/5] ntfs: report filesystem errors through fsnotify Baolin Liu
@ 2026-09-16 2:23 ` Baolin Liu
2026-09-16 2:23 ` [PATCH v2 2/5] ntfs: report attribute " Baolin Liu
` (4 subsequent siblings)
5 siblings, 0 replies; 9+ messages in thread
From: Baolin Liu @ 2026-09-16 2:23 UTC (permalink / raw)
To: linkinjeon, hyc.lee; +Cc: ntfs, linux-kernel, Baolin Liu
From: Baolin Liu <liubaolin@kylinos.cn>
Add a volume-level helper that records metadata failures and notifies
FAN_FS_ERROR listeners after mount activation. Use it for cluster
allocation and free rollbacks that leave volume metadata inconsistent.
Signed-off-by: Baolin Liu <liubaolin@kylinos.cn>
---
fs/ntfs/lcnalloc.c | 4 ++--
fs/ntfs/super.c | 8 ++++++++
fs/ntfs/volume.h | 2 ++
3 files changed, 12 insertions(+), 2 deletions(-)
diff --git a/fs/ntfs/lcnalloc.c b/fs/ntfs/lcnalloc.c
index 0d6cd08ee2e7..347f9e152588 100644
--- a/fs/ntfs/lcnalloc.c
+++ b/fs/ntfs/lcnalloc.c
@@ -763,7 +763,7 @@ switch_to_data1_zone: search_zone = 2;
ntfs_error(vol->sb,
"Failed to rollback (error %i). Leaving inconsistent metadata! Unmount and run chkdsk.",
err2);
- NVolSetErrors(vol);
+ ntfs_report_metadata_error(vol, err2);
}
/* Free the runlist. */
kvfree(rl);
@@ -1044,7 +1044,7 @@ s64 __ntfs_cluster_free(struct ntfs_inode *ni, const s64 start_vcn, s64 count,
ntfs_error(vol->sb,
"Failed to rollback (error %i). Leaving inconsistent metadata! Unmount and run chkdsk.",
(int)delta);
- NVolSetErrors(vol);
+ ntfs_report_metadata_error(vol, delta);
} else {
ntfs_dec_free_clusters(vol, delta);
}
diff --git a/fs/ntfs/super.c b/fs/ntfs/super.c
index b8bb2268e609..340aad497b88 100644
--- a/fs/ntfs/super.c
+++ b/fs/ntfs/super.c
@@ -14,6 +14,7 @@
#include <linux/sched/mm.h>
#include <linux/fs_context.h>
#include <linux/fs_parser.h>
+#include <linux/fserror.h>
#include "sysctl.h"
#include "logfile.h"
@@ -337,6 +338,13 @@ const struct option_t on_errors_arr[] = {
{ 0, NULL }
};
+void ntfs_report_metadata_error(struct ntfs_volume *vol, int error)
+{
+ NVolSetErrors(vol);
+ if (vol->sb->s_flags & SB_ACTIVE)
+ fserror_report_metadata(vol->sb, error, GFP_ATOMIC);
+}
+
void ntfs_handle_error(struct super_block *sb)
{
struct ntfs_volume *vol = NTFS_SB(sb);
diff --git a/fs/ntfs/volume.h b/fs/ntfs/volume.h
index 8c17a66b4f22..55d08319820e 100644
--- a/fs/ntfs/volume.h
+++ b/fs/ntfs/volume.h
@@ -250,6 +250,8 @@ DEFINE_NVOL_BIT_OPS(DisableSparse)
DEFINE_NVOL_BIT_OPS(NativeSymlinkRel)
DEFINE_NVOL_BIT_OPS(SymlinkNative)
+void ntfs_report_metadata_error(struct ntfs_volume *vol, int error);
+
static inline void ntfs_inc_free_clusters(struct ntfs_volume *vol, s64 nr)
{
if (!NVolFreeClusterKnown(vol))
--
2.51.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v2 2/5] ntfs: report attribute errors to fsnotify
2026-09-16 2:23 [PATCH v2 0/5] ntfs: report filesystem errors through fsnotify Baolin Liu
2026-09-16 2:23 ` [PATCH v2 1/5] ntfs: report allocation metadata errors to fsnotify Baolin Liu
@ 2026-09-16 2:23 ` Baolin Liu
2026-09-16 7:38 ` Hyunchul Lee
2026-09-16 2:23 ` [PATCH v2 3/5] ntfs: report inode metadata " Baolin Liu
` (3 subsequent siblings)
5 siblings, 1 reply; 9+ messages in thread
From: Baolin Liu @ 2026-09-16 2:23 UTC (permalink / raw)
To: linkinjeon, hyc.lee; +Cc: ntfs, linux-kernel, Baolin Liu
From: Baolin Liu <liubaolin@kylinos.cn>
Add a file-level helper and report attribute validation and lookup
failures against the affected inode. Cover bitmap, cluster, and
ATTRIBUTE_LIST rollback errors while preserving the original errno.
Track reports in search contexts and through mapping-pairs updates.
Signed-off-by: Baolin Liu <liubaolin@kylinos.cn>
---
fs/ntfs/attrib.c | 71 ++++++++++++++++++++++++++++++++++------------
fs/ntfs/attrib.h | 6 ++++
fs/ntfs/attrlist.c | 16 +++++++----
fs/ntfs/bitmap.c | 2 +-
fs/ntfs/super.c | 7 +++++
fs/ntfs/volume.h | 1 +
6 files changed, 78 insertions(+), 25 deletions(-)
diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c
index b01cbc9eea4a..136bda6433bb 100644
--- a/fs/ntfs/attrib.c
+++ b/fs/ntfs/attrib.c
@@ -205,6 +205,8 @@ int ntfs_map_runlist_nolock(struct ntfs_inode *ni, s64 vcn, struct ntfs_attr_sea
ntfs_attr_put_search_ctx(ctx);
unmap_mft_record(base_ni);
} else if (ctx_needs_reset) {
+ if (ctx->error_reported)
+ old_ctx.error_reported = true;
/*
* If there is no attribute list, restoring the search context
* is accomplished simply by copying the saved context back over
@@ -838,6 +840,7 @@ static int ntfs_attr_find(const __le32 type, const __le16 *name,
const u8 *val, const u32 val_len, struct ntfs_attr_search_ctx *ctx)
{
struct attr_record *a;
+ struct ntfs_inode *base_ni;
struct ntfs_volume *vol = ctx->ntfs_ino->vol;
__le16 *upcase = vol->upcase;
u32 upcase_len = vol->upcase_len;
@@ -964,7 +967,12 @@ static int ntfs_attr_find(const __le32 type, const __le16 *name,
}
ntfs_error(vol->sb, "mft %#llx, type %#x is corrupt. Run chkdsk.",
(long long)ctx->ntfs_ino->mft_no, le32_to_cpu(type));
- NVolSetErrors(vol);
+ if (ctx->ntfs_ino->nr_extents >= 0)
+ base_ni = ctx->ntfs_ino;
+ else
+ base_ni = ctx->ntfs_ino->ext.base_ntfs_ino;
+ ntfs_report_file_metadata_error(VFS_I(base_ni), -EIO);
+ ctx->error_reported = true;
return -EIO;
}
@@ -1501,8 +1509,14 @@ static int ntfs_external_attr_find(const __le32 type,
err = -EIO;
}
- if (err != -ENOMEM)
- NVolSetErrors(vol);
+ if (err != -ENOMEM) {
+ if (err != -EINTR && err != -ERESTARTSYS) {
+ ntfs_report_file_metadata_error(VFS_I(base_ni), err);
+ ctx->error_reported = true;
+ } else {
+ NVolSetErrors(vol);
+ }
+ }
return err;
not_found:
/*
@@ -1597,6 +1611,7 @@ int ntfs_attr_lookup(const __le32 type, const __le16 *name,
struct ntfs_inode *base_ni;
ntfs_debug("Entering.");
+ ctx->error_reported = false;
if (ctx->base_ntfs_ino)
base_ni = ctx->base_ntfs_ino;
else
@@ -1633,6 +1648,7 @@ static bool ntfs_attr_init_search_ctx(struct ntfs_attr_search_ctx *ctx,
/* Sanity checks are performed elsewhere. */
ctx->attr = (struct attr_record *)((u8 *)mrec + le16_to_cpu(mrec->attrs_offset));
ctx->is_first = true;
+ ctx->error_reported = false;
ctx->ntfs_ino = ni;
ctx->al_entry = NULL;
ctx->base_ntfs_ino = NULL;
@@ -2233,10 +2249,11 @@ int ntfs_attr_make_non_resident(struct ntfs_inode *ni, const u32 data_size)
rl_err_out:
up_write(&ni->runlist.lock);
if (rl) {
- if (ntfs_cluster_free_from_rl(vol, rl) < 0) {
+ err2 = ntfs_cluster_free_from_rl(vol, rl);
+ if (err2 < 0) {
ntfs_error(vol->sb,
"Failed to release allocated cluster(s) in error code path. Run chkdsk to recover the lost cluster(s).");
- NVolSetErrors(vol);
+ ntfs_report_file_metadata_error(vi, err2);
}
kvfree(rl);
folio_err_out:
@@ -3720,7 +3737,8 @@ static int ntfs_attr_update_meta(struct attr_record *a, struct ntfs_inode *ni,
static int __ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni,
s64 from_vcn,
struct ntfs_inode *locked_ni,
- bool defer_attrlist)
+ bool defer_attrlist,
+ bool *error_reported)
{
struct ntfs_attr_search_ctx *ctx;
struct ntfs_inode *base_ni;
@@ -4086,8 +4104,11 @@ static int __ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni,
return 0;
put_err_out:
- if (ctx)
+ if (ctx) {
+ if (error_reported && ctx->error_reported)
+ *error_reported = true;
ntfs_attr_put_search_ctx(ctx);
+ }
return err;
}
@@ -4096,7 +4117,16 @@ int ntfs_attr_update_mapping_pairs_locked(struct ntfs_inode *ni,
struct ntfs_inode *locked_ni)
{
return __ntfs_attr_update_mapping_pairs(ni, from_vcn, locked_ni,
- false);
+ false, NULL);
+}
+
+int ntfs_attr_update_mapping_pairs_locked_reported(struct ntfs_inode *ni,
+ s64 from_vcn,
+ struct ntfs_inode *locked_ni,
+ bool *error_reported)
+{
+ return __ntfs_attr_update_mapping_pairs(ni, from_vcn, locked_ni,
+ false, error_reported);
}
int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn)
@@ -4704,7 +4734,7 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz
/* Restore mapping pairs. */
if (ni != locked_ni)
down_read(&ni->runlist.lock);
- if (__ntfs_attr_update_mapping_pairs(ni, 0, locked_ni, true))
+ if (__ntfs_attr_update_mapping_pairs(ni, 0, locked_ni, true, NULL))
ntfs_error(sb, "Failed to restore old mapping pairs");
if (ni != locked_ni)
up_read(&ni->runlist.lock);
@@ -5140,7 +5170,8 @@ int ntfs_attr_map_cluster(struct ntfs_inode *ni, s64 vcn_start, s64 *lcn_start,
struct runlist_element *old_rl = NULL;
s64 vcn = vcn_start, lcn, clu_count;
s64 lcn_seek_from = -1;
- int err = 0;
+ int err = 0, err2;
+ bool error_reported = false;
size_t new_rl_count, old_rl_count;
err = ntfs_attr_map_whole_runlist(ni);
@@ -5239,10 +5270,11 @@ int ntfs_attr_map_cluster(struct ntfs_inode *ni, s64 vcn_start, s64 *lcn_start,
old_rl_count * sizeof(*old_rl), GFP_NOFS);
if (!old_rl) {
err = -ENOMEM;
- if (ntfs_cluster_free_from_rl(vol, rlc)) {
+ err2 = ntfs_cluster_free_from_rl(vol, rlc);
+ if (err2) {
ntfs_error(vol->sb,
"Failed to free cluster allocation after runlist backup failure.");
- NVolSetErrors(vol);
+ ntfs_report_file_metadata_error(VFS_I(ni), err2);
}
kvfree(rlc);
goto out;
@@ -5272,13 +5304,13 @@ int ntfs_attr_map_cluster(struct ntfs_inode *ni, s64 vcn_start, s64 *lcn_start,
ntfs_attr_reinit_search_ctx(ctx);
err = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni);
if (err) {
- int err2;
-
err2 = ntfs_cluster_free(ni, vcn, clu_count, ctx);
if (err2 < 0 || err2 != clu_count) {
ntfs_error(vol->sb,
"Failed to free cluster allocation. Leaving inconsistent metadata.\n");
- NVolSetErrors(vol);
+ if (err2 >= 0)
+ err2 = -EIO;
+ ntfs_report_file_metadata_error(VFS_I(ni), err2);
goto out;
}
@@ -5290,11 +5322,14 @@ int ntfs_attr_map_cluster(struct ntfs_inode *ni, s64 vcn_start, s64 *lcn_start,
ni->runlist.rl = old_rl;
ni->runlist.count = old_rl_count;
old_rl = NULL;
- if (ntfs_attr_update_mapping_pairs_locked(
- ni, 0, ni)) {
+ err2 = ntfs_attr_update_mapping_pairs_locked_reported(ni, 0,
+ ni,
+ &error_reported);
+ if (err2) {
ntfs_error(vol->sb,
"Failed to restore mapping pairs after allocation rollback.\n");
- NVolSetErrors(vol);
+ if (!error_reported)
+ ntfs_report_file_metadata_error(VFS_I(ni), err2);
}
}
} else {
diff --git a/fs/ntfs/attrib.h b/fs/ntfs/attrib.h
index 6b4fa9f57640..56cec2c8b914 100644
--- a/fs/ntfs/attrib.h
+++ b/fs/ntfs/attrib.h
@@ -21,6 +21,7 @@ extern __le16 AT_UNNAMED[];
* @mapped_mrec: true if @mrec was mapped by the search functions
* @attr: attribute record in @mrec where to begin/continue search
* @is_first: if true ntfs_attr_lookup() begins search with @attr, else after
+ * @error_reported: current lookup already reported its metadata error
* @ntfs_ino: Inode owning this attribute search
* @al_entry: Current attribute list entry
* @base_ntfs_ino: Base inode
@@ -44,6 +45,7 @@ struct ntfs_attr_search_ctx {
bool mapped_mrec;
struct attr_record *attr;
bool is_first;
+ bool error_reported;
struct ntfs_inode *ntfs_ino;
struct attr_list_entry *al_entry;
struct ntfs_inode *base_ntfs_ino;
@@ -142,6 +144,10 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn);
int ntfs_attr_update_mapping_pairs_locked(struct ntfs_inode *ni,
s64 from_vcn,
struct ntfs_inode *locked_ni);
+int ntfs_attr_update_mapping_pairs_locked_reported(struct ntfs_inode *ni,
+ s64 from_vcn,
+ struct ntfs_inode *locked_ni,
+ bool *error_reported);
struct runlist_element *ntfs_attr_vcn_to_rl(struct ntfs_inode *ni, s64 vcn, s64 *lcn);
/*
diff --git a/fs/ntfs/attrlist.c b/fs/ntfs/attrlist.c
index 7c09d02fd941..69365bf94d91 100644
--- a/fs/ntfs/attrlist.c
+++ b/fs/ntfs/attrlist.c
@@ -78,7 +78,8 @@ static int ntfs_attrlist_repack(struct inode *attr_vi,
s64 old_alloc_size;
size_t old_rl_count, new_rl_count;
unsigned long flags;
- int err, restore_err;
+ int err, free_err, restore_err;
+ bool error_reported = false;
if (attr_ni->mft_no != FILE_MFT || !NInoNonResident(attr_ni) ||
min_alloc_size < 0)
return -EINVAL;
@@ -159,11 +160,12 @@ static int ntfs_attrlist_repack(struct inode *attr_vi,
goto restore_old_runlist;
/* The new mapping is now authoritative; release the old data runs. */
- if (ntfs_cluster_free_from_rl(vol, old_rl)) {
+ free_err = ntfs_cluster_free_from_rl(vol, old_rl);
+ if (free_err) {
ntfs_error(vol->sb,
"Failed to free old ATTRIBUTE_LIST extent: inode %#llx",
(long long)attr_ni->mft_no);
- NVolSetErrors(vol);
+ ntfs_report_file_metadata_error(attr_vi, free_err);
}
kvfree(old_rl);
kvfree(data);
@@ -179,12 +181,14 @@ static int ntfs_attrlist_repack(struct inode *attr_vi,
attr_ni->allocated_size = old_alloc_size;
write_unlock_irqrestore(&attr_ni->size_lock, flags);
- restore_err = ntfs_attr_update_mapping_pairs_locked(
- attr_ni, 0, locked_ni);
+ restore_err = ntfs_attr_update_mapping_pairs_locked_reported(attr_ni, 0,
+ locked_ni,
+ &error_reported);
if (restore_err) {
ntfs_error(vol->sb, "Failed to restore ATTRIBUTE_LIST mapping pairs (%d)",
restore_err);
- NVolSetErrors(vol);
+ if (!error_reported)
+ ntfs_report_file_metadata_error(attr_vi, restore_err);
}
ntfs_cluster_free_from_rl(vol, new_rl);
diff --git a/fs/ntfs/bitmap.c b/fs/ntfs/bitmap.c
index 1840b7d84c62..c3faad256e2e 100644
--- a/fs/ntfs/bitmap.c
+++ b/fs/ntfs/bitmap.c
@@ -286,7 +286,7 @@ int __ntfs_bitmap_set_bits_in_run(struct inode *vi, const s64 start_bit,
ntfs_error(vi->i_sb,
"Failed to map subsequent page (error %i) and rollback failed (error %i). Aborting and leaving inconsistent metadata. Unmount and run chkdsk.",
err, pos);
- NVolSetErrors(NTFS_SB(vi->i_sb));
+ ntfs_report_file_metadata_error(vi, pos);
}
return err;
}
diff --git a/fs/ntfs/super.c b/fs/ntfs/super.c
index 340aad497b88..7f6812d27fd1 100644
--- a/fs/ntfs/super.c
+++ b/fs/ntfs/super.c
@@ -345,6 +345,13 @@ void ntfs_report_metadata_error(struct ntfs_volume *vol, int error)
fserror_report_metadata(vol->sb, error, GFP_ATOMIC);
}
+void ntfs_report_file_metadata_error(struct inode *inode, int error)
+{
+ NVolSetErrors(NTFS_SB(inode->i_sb));
+ if (inode->i_sb->s_flags & SB_ACTIVE)
+ fserror_report_file_metadata(inode, error, GFP_ATOMIC);
+}
+
void ntfs_handle_error(struct super_block *sb)
{
struct ntfs_volume *vol = NTFS_SB(sb);
diff --git a/fs/ntfs/volume.h b/fs/ntfs/volume.h
index 55d08319820e..7daac7048a4c 100644
--- a/fs/ntfs/volume.h
+++ b/fs/ntfs/volume.h
@@ -251,6 +251,7 @@ DEFINE_NVOL_BIT_OPS(NativeSymlinkRel)
DEFINE_NVOL_BIT_OPS(SymlinkNative)
void ntfs_report_metadata_error(struct ntfs_volume *vol, int error);
+void ntfs_report_file_metadata_error(struct inode *inode, int error);
static inline void ntfs_inc_free_clusters(struct ntfs_volume *vol, s64 nr)
{
--
2.51.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v2 3/5] ntfs: report inode metadata errors to fsnotify
2026-09-16 2:23 [PATCH v2 0/5] ntfs: report filesystem errors through fsnotify Baolin Liu
2026-09-16 2:23 ` [PATCH v2 1/5] ntfs: report allocation metadata errors to fsnotify Baolin Liu
2026-09-16 2:23 ` [PATCH v2 2/5] ntfs: report attribute " Baolin Liu
@ 2026-09-16 2:23 ` Baolin Liu
2026-09-16 2:23 ` [PATCH v2 4/5] ntfs: report MFT " Baolin Liu
` (2 subsequent siblings)
5 siblings, 0 replies; 9+ messages in thread
From: Baolin Liu @ 2026-09-16 2:23 UTC (permalink / raw)
To: linkinjeon, hyc.lee; +Cc: ntfs, linux-kernel, Baolin Liu
From: Baolin Liu <liubaolin@kylinos.cn>
Report inode initialization, writeback, and LogFile failures against
the affected VFS inode while retaining existing allocation and signal
handling. Consume attribute reporting state so callers do not emit a
second FAN_FS_ERROR event for the same failure.
Signed-off-by: Baolin Liu <liubaolin@kylinos.cn>
---
fs/ntfs/inode.c | 52 ++++++++++++++++++++++++++++++++++-------------
fs/ntfs/logfile.c | 2 +-
2 files changed, 39 insertions(+), 15 deletions(-)
diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c
index d5e526d3612a..13f353905fb3 100644
--- a/fs/ntfs/inode.c
+++ b/fs/ntfs/inode.c
@@ -689,7 +689,7 @@ static int ntfs_read_locked_inode(struct inode *vi)
unsigned int name_len = 4, flags = 0;
int extend_sys = 0;
dev_t dev = 0;
- bool has_lxmod = false;
+ bool error_reported = false, has_lxmod = false;
bool vol_err = true;
ntfs_debug("Entering for i_ino 0x%llx.", ni->mft_no);
@@ -761,6 +761,7 @@ static int ntfs_read_locked_inode(struct inode *vi)
err = ntfs_attr_lookup(AT_STANDARD_INFORMATION, NULL, 0, 0, 0, NULL, 0,
ctx);
if (unlikely(err)) {
+ error_reported = ctx->error_reported;
if (err == -ENOENT)
ntfs_error(vi->i_sb, "$STANDARD_INFORMATION attribute is missing.");
goto unm_err_out;
@@ -801,6 +802,7 @@ static int ntfs_read_locked_inode(struct inode *vi)
err = ntfs_attr_lookup(AT_ATTRIBUTE_LIST, NULL, 0, 0, 0, NULL, 0, ctx);
if (err) {
if (unlikely(err != -ENOENT)) {
+ error_reported = ctx->error_reported;
ntfs_error(vi->i_sb, "Failed to lookup attribute list attribute.");
goto unm_err_out;
}
@@ -923,6 +925,7 @@ static int ntfs_read_locked_inode(struct inode *vi)
err = ntfs_attr_lookup(AT_INDEX_ROOT, name, name_len, CASE_SENSITIVE,
0, NULL, 0, ctx);
if (unlikely(err)) {
+ error_reported = ctx->error_reported;
if (err == -ENOENT)
ntfs_error(vi->i_sb, "$INDEX_ROOT attribute is missing.");
goto unm_err_out;
@@ -1056,6 +1059,7 @@ static int ntfs_read_locked_inode(struct inode *vi)
vi->i_size = ni->initialized_size =
ni->allocated_size = 0;
if (err != -ENOENT) {
+ error_reported = ctx->error_reported;
ntfs_error(vi->i_sb, "Failed to lookup $DATA attribute.");
goto unm_err_out;
}
@@ -1091,6 +1095,7 @@ static int ntfs_read_locked_inode(struct inode *vi)
}
err = extend_sys;
+ error_reported = ctx->error_reported;
ntfs_error(vi->i_sb, "$DATA attribute is missing, err : %d", err);
goto unm_err_out;
}
@@ -1247,12 +1252,12 @@ static int ntfs_read_locked_inode(struct inode *vi)
if (m)
unmap_mft_record(ni);
err_out:
- if (err != -EOPNOTSUPP && err != -ENOMEM &&
+ if (!error_reported && err != -EOPNOTSUPP && err != -ENOMEM &&
err != -EINTR && err != -ERESTARTSYS && vol_err == true) {
ntfs_error(vol->sb,
"Failed with error code %i. Marking corrupt inode 0x%llx as bad. Run chkdsk.",
err, ni->mft_no);
- NVolSetErrors(vol);
+ ntfs_report_file_metadata_error(vi, err);
}
return err;
}
@@ -1286,6 +1291,7 @@ static int ntfs_read_locked_attr_inode(struct inode *base_vi, struct inode *vi)
struct attr_record *a;
struct ntfs_attr_search_ctx *ctx;
int err = 0;
+ bool error_reported = false;
ntfs_debug("Entering for i_ino 0x%llx.", ni->mft_no);
@@ -1316,8 +1322,10 @@ static int ntfs_read_locked_attr_inode(struct inode *base_vi, struct inode *vi)
/* Find the attribute. */
err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len,
CASE_SENSITIVE, 0, NULL, 0, ctx);
- if (unlikely(err))
+ if (unlikely(err)) {
+ error_reported = ctx->error_reported;
goto unm_err_out;
+ }
a = ctx->attr;
if (a->flags & (ATTR_COMPRESSION_MASK | ATTR_IS_SPARSE)) {
if (a->flags & ATTR_COMPRESSION_MASK) {
@@ -1479,9 +1487,9 @@ static int ntfs_read_locked_attr_inode(struct inode *base_vi, struct inode *vi)
"Failed with error code %i while reading attribute inode (mft_no 0x%llx, type 0x%x, name_len %i). Marking corrupt inode and base inode 0x%llx as bad. Run chkdsk.",
err, ni->mft_no, ni->type, ni->name_len,
base_ni->mft_no);
- if (err != -ENOENT && err != -ENOMEM &&
+ if (!error_reported && err != -ENOENT && err != -ENOMEM &&
err != -EINTR && err != -ERESTARTSYS)
- NVolSetErrors(vol);
+ ntfs_report_file_metadata_error(base_vi, err);
return err;
}
@@ -1528,6 +1536,7 @@ static int ntfs_read_locked_index_inode(struct inode *base_vi, struct inode *vi)
struct ntfs_attr_search_ctx *ctx;
struct index_root *ir;
int err = 0;
+ bool error_reported = false;
ntfs_debug("Entering for i_ino 0x%llx.", ni->mft_no);
lockdep_assert_held(&base_ni->mrec_lock);
@@ -1558,6 +1567,7 @@ static int ntfs_read_locked_index_inode(struct inode *base_vi, struct inode *vi)
err = ntfs_attr_lookup(AT_INDEX_ROOT, ni->name, ni->name_len,
CASE_SENSITIVE, 0, NULL, 0, ctx);
if (unlikely(err)) {
+ error_reported = ctx->error_reported;
if (err == -ENOENT)
ntfs_error(vi->i_sb, "$INDEX_ROOT attribute is missing.");
goto unm_err_out;
@@ -1630,8 +1640,10 @@ static int ntfs_read_locked_index_inode(struct inode *base_vi, struct inode *vi)
m = NULL;
ctx = NULL;
goto skip_large_index_stuff;
- } else
+ } else {
+ error_reported = ctx->error_reported;
ntfs_error(vi->i_sb, "Failed to lookup $INDEX_ALLOCATION attribute.");
+ }
goto unm_err_out;
}
NInoSetIndexAllocPresent(ni);
@@ -1685,6 +1697,9 @@ static int ntfs_read_locked_index_inode(struct inode *base_vi, struct inode *vi)
bvi = ntfs_attr_iget(base_vi, AT_BITMAP, ni->name, ni->name_len);
if (IS_ERR(bvi)) {
err = PTR_ERR(bvi);
+ error_reported = err != -ENOENT && err != -ENOMEM &&
+ err != -EOPNOTSUPP && err != -EINTR &&
+ err != -ERESTARTSYS;
if (err != -EINTR && err != -ERESTARTSYS)
ntfs_error(vi->i_sb, "Failed to get bitmap attribute.");
goto unm_err_out;
@@ -1734,9 +1749,9 @@ static int ntfs_read_locked_index_inode(struct inode *base_vi, struct inode *vi)
ntfs_error(vi->i_sb,
"Failed with error code %i while reading index inode (mft_no 0x%llx, name_len %i.",
err, ni->mft_no, ni->name_len);
- if (err != -EOPNOTSUPP && err != -ENOMEM &&
+ if (!error_reported && err != -EOPNOTSUPP && err != -ENOMEM &&
err != -EINTR && err != -ERESTARTSYS)
- NVolSetErrors(vol);
+ ntfs_report_file_metadata_error(base_vi, err);
return err;
}
@@ -2479,7 +2494,9 @@ int ntfs_truncate_vfs(struct inode *vi, loff_t new_size, loff_t i_size)
*
* Return 0 on success or -errno on error.
*/
-static int ntfs_inode_sync_standard_information(struct inode *vi, struct mft_record *m)
+static int ntfs_inode_sync_standard_information(struct inode *vi,
+ struct mft_record *m,
+ bool *error_reported)
{
struct ntfs_inode *ni = NTFS_I(vi);
struct ntfs_attr_search_ctx *ctx;
@@ -2495,6 +2512,8 @@ static int ntfs_inode_sync_standard_information(struct inode *vi, struct mft_rec
err = ntfs_attr_lookup(AT_STANDARD_INFORMATION, NULL, 0,
CASE_SENSITIVE, 0, NULL, 0, ctx);
if (unlikely(err)) {
+ if (ctx->error_reported)
+ *error_reported = true;
ntfs_attr_put_search_ctx(ctx);
return err;
}
@@ -2749,7 +2768,7 @@ int __ntfs_write_inode(struct inode *vi, int sync)
struct ntfs_inode *mft_ni = NTFS_I(ni->vol->mft_ino);
struct mft_record *m;
int err = 0;
- bool need_iput = false;
+ bool error_reported = false, need_iput = false;
ntfs_debug("Entering for %sinode 0x%llx.", NInoAttr(ni) ? "attr " : "",
ni->mft_no);
@@ -2783,13 +2802,14 @@ int __ntfs_write_inode(struct inode *vi, int sync)
if (NInoNonResident(ni) && NInoRunlistDirty(ni)) {
down_write(&ni->runlist.lock);
- err = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni);
+ err = ntfs_attr_update_mapping_pairs_locked_reported(ni, 0, ni,
+ &error_reported);
if (!err)
NInoClearRunlistDirty(ni);
up_write(&ni->runlist.lock);
}
- err = ntfs_inode_sync_standard_information(vi, m);
+ err = ntfs_inode_sync_standard_information(vi, m, &error_reported);
if (err)
goto unm_err_out;
@@ -2889,7 +2909,11 @@ int __ntfs_write_inode(struct inode *vi, int sync)
mark_inode_dirty(vi);
else {
ntfs_error(vi->i_sb, "Failed (error %i): Run chkdsk.", -err);
- NVolSetErrors(ni->vol);
+ if (!error_reported && err != -EINTR &&
+ err != -ERESTARTSYS)
+ ntfs_report_file_metadata_error(vi, err);
+ else
+ NVolSetErrors(ni->vol);
}
if (need_iput)
iput(vi);
diff --git a/fs/ntfs/logfile.c b/fs/ntfs/logfile.c
index 024ddee42dc8..9be0506fd5c0 100644
--- a/fs/ntfs/logfile.c
+++ b/fs/ntfs/logfile.c
@@ -768,7 +768,7 @@ bool ntfs_empty_logfile(struct inode *log_vi)
rl_err:
ntfs_error(sb, "Runlist is corrupt. Unmount and run chkdsk.");
dirty_err:
- NVolSetErrors(vol);
+ ntfs_report_file_metadata_error(log_vi, -EIO);
err = -EIO;
err:
kvfree(empty_buf);
--
2.51.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v2 4/5] ntfs: report MFT errors to fsnotify
2026-09-16 2:23 [PATCH v2 0/5] ntfs: report filesystem errors through fsnotify Baolin Liu
` (2 preceding siblings ...)
2026-09-16 2:23 ` [PATCH v2 3/5] ntfs: report inode metadata " Baolin Liu
@ 2026-09-16 2:23 ` Baolin Liu
2026-09-16 2:23 ` [PATCH v2 5/5] ntfs: report shutdown " Baolin Liu
2026-09-16 2:40 ` [PATCH v2 0/5] ntfs: report filesystem errors through fsnotify liubaolin
5 siblings, 0 replies; 9+ messages in thread
From: Baolin Liu @ 2026-09-16 2:23 UTC (permalink / raw)
To: linkinjeon, hyc.lee; +Cc: ntfs, linux-kernel, Baolin Liu
From: Baolin Liu <liubaolin@kylinos.cn>
Report MFT validation, mapping, allocation, rollback, and writeback
failures through the appropriate volume-level or file-level helper.
Add reporting-aware mapping APIs and propagate their state to callers.
Report asynchronous MFT writeback errors from I/O completion.
Signed-off-by: Baolin Liu <liubaolin@kylinos.cn>
---
fs/ntfs/attrib.c | 19 ++--
fs/ntfs/inode.c | 10 +-
fs/ntfs/mft.c | 252 +++++++++++++++++++++++++++++++----------------
fs/ntfs/mft.h | 6 ++
4 files changed, 191 insertions(+), 96 deletions(-)
diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c
index 136bda6433bb..c847bffd0bca 100644
--- a/fs/ntfs/attrib.c
+++ b/fs/ntfs/attrib.c
@@ -94,6 +94,7 @@ int ntfs_map_runlist_nolock(struct ntfs_inode *ni, s64 vcn, struct ntfs_attr_sea
struct folio *put_this_folio = NULL;
int err = 0;
bool ctx_is_temporary = false, ctx_needs_reset = false;
+ bool mft_error_reported = false;
struct ntfs_attr_search_ctx old_ctx = { NULL, };
size_t new_rl_count;
@@ -238,7 +239,10 @@ int ntfs_map_runlist_nolock(struct ntfs_inode *ni, s64 vcn, struct ntfs_attr_sea
if (old_ctx.base_ntfs_ino &&
old_ctx.ntfs_ino != old_ctx.base_ntfs_ino) {
retry_map:
- ctx->mrec = map_mft_record(old_ctx.ntfs_ino);
+ ctx->mrec = map_mft_record_reported(old_ctx.ntfs_ino,
+ &mft_error_reported);
+ if (mft_error_reported)
+ old_ctx.error_reported = true;
/*
* Something bad has happened. If out
* of memory retry till it succeeds.
@@ -1163,7 +1167,7 @@ static int ntfs_external_attr_find(const __le32 type,
__le16 *al_name;
u32 al_name_len;
u32 attr_len, mft_free_len;
- bool is_first_search = false;
+ bool error_reported = false, is_first_search = false;
int err = 0;
static const char *es = " Unmount and run chkdsk.";
@@ -1382,10 +1386,12 @@ static int ntfs_external_attr_find(const __le32 type,
ctx->mrec = ctx->base_mrec;
ctx->mapped_mrec = ctx->mapped_base_mrec;
} else {
+ u64 mref = le64_to_cpu(al_entry->mft_reference);
+
/* We want an extent record. */
- ctx->mrec = map_extent_mft_record(base_ni,
- le64_to_cpu(
- al_entry->mft_reference), &ni);
+ ctx->mrec = map_extent_mft_record_reported(base_ni, mref,
+ &ni,
+ &error_reported);
if (IS_ERR(ctx->mrec)) {
ntfs_error(vol->sb,
"Failed to map extent mft record 0x%lx of base inode 0x%llx.%s",
@@ -1511,7 +1517,8 @@ static int ntfs_external_attr_find(const __le32 type,
if (err != -ENOMEM) {
if (err != -EINTR && err != -ERESTARTSYS) {
- ntfs_report_file_metadata_error(VFS_I(base_ni), err);
+ if (!error_reported)
+ ntfs_report_file_metadata_error(VFS_I(base_ni), err);
ctx->error_reported = true;
} else {
NVolSetErrors(vol);
diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c
index 13f353905fb3..ab5424b1593f 100644
--- a/fs/ntfs/inode.c
+++ b/fs/ntfs/inode.c
@@ -715,7 +715,7 @@ static int ntfs_read_locked_inode(struct inode *vi)
if (vi->i_ino != FILE_MFT)
ntfs_init_big_inode(vi);
- m = map_mft_record(ni);
+ m = map_mft_record_reported(ni, &error_reported);
if (IS_ERR(m)) {
err = PTR_ERR(m);
goto err_out;
@@ -1309,7 +1309,7 @@ static int ntfs_read_locked_attr_inode(struct inode *base_vi, struct inode *vi)
/* Set inode type to zero but preserve permissions. */
vi->i_mode = base_vi->i_mode & ~S_IFMT;
- m = map_mft_record(base_ni);
+ m = map_mft_record_reported(base_ni, &error_reported);
if (IS_ERR(m)) {
err = PTR_ERR(m);
goto err_out;
@@ -1553,7 +1553,7 @@ static int ntfs_read_locked_index_inode(struct inode *base_vi, struct inode *vi)
/* Set inode type to zero but preserve permissions. */
vi->i_mode = base_vi->i_mode & ~S_IFMT;
/* Map the mft record for the base inode. */
- m = map_mft_record(base_ni);
+ m = map_mft_record_reported(base_ni, &error_reported);
if (IS_ERR(m)) {
err = PTR_ERR(m);
goto err_out;
@@ -2793,7 +2793,7 @@ int __ntfs_write_inode(struct inode *vi, int sync)
mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL);
/* Map, pin, and lock the mft record belonging to the inode. */
- m = map_mft_record(ni);
+ m = map_mft_record_reported(ni, &error_reported);
if (IS_ERR(m)) {
mutex_unlock(&ni->mrec_lock);
err = PTR_ERR(m);
@@ -2873,7 +2873,7 @@ int __ntfs_write_inode(struct inode *vi, int sync)
int ret;
mutex_lock(&tni->mrec_lock);
- tm = map_mft_record(tni);
+ tm = map_mft_record_reported(tni, &error_reported);
if (IS_ERR(tm)) {
mutex_unlock(&tni->mrec_lock);
if (!err || err == -ENOMEM)
diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c
index c565bec49c7d..b7b018f4350e 100644
--- a/fs/ntfs/mft.c
+++ b/fs/ntfs/mft.c
@@ -108,9 +108,11 @@ int ntfs_mft_record_check(const struct ntfs_volume *vol, struct mft_record *m,
* The return value needs to be checked with IS_ERR(). If it is true,
* PTR_ERR() contains the negative error code.
*/
-static inline struct mft_record *map_mft_record_folio(struct ntfs_inode *ni)
+static inline struct mft_record *
+map_mft_record_folio(struct ntfs_inode *ni, bool *error_reported)
{
loff_t i_size;
+ struct ntfs_inode *base_ni;
struct ntfs_volume *vol = ni->vol;
struct inode *mft_vi = vol->mft_ino;
struct folio *folio;
@@ -169,7 +171,13 @@ static inline struct mft_record *map_mft_record_folio(struct ntfs_inode *ni)
kfree(ni->mrec);
ni->mrec = NULL;
folio = ERR_PTR(-EIO);
- NVolSetErrors(vol);
+ if (ni->nr_extents >= 0)
+ base_ni = ni;
+ else
+ base_ni = ni->ext.base_ntfs_ino;
+ ntfs_report_file_metadata_error(VFS_I(base_ni), -EIO);
+ if (error_reported)
+ *error_reported = true;
}
err_out:
ni->folio = NULL;
@@ -195,7 +203,8 @@ static inline struct mft_record *map_mft_record_folio(struct ntfs_inode *ni)
* Return: A pointer to the mft record. You need to check the returned
* pointer with IS_ERR().
*/
-struct mft_record *map_mft_record(struct ntfs_inode *ni)
+static struct mft_record *__map_mft_record(struct ntfs_inode *ni,
+ bool *error_reported)
{
struct mft_record *m;
@@ -210,7 +219,7 @@ struct mft_record *map_mft_record(struct ntfs_inode *ni)
if (ni->folio)
return (struct mft_record *)ni->mrec;
- m = map_mft_record_folio(ni);
+ m = map_mft_record_folio(ni, error_reported);
if (!IS_ERR(m))
return m;
@@ -220,6 +229,17 @@ struct mft_record *map_mft_record(struct ntfs_inode *ni)
return m;
}
+struct mft_record *map_mft_record(struct ntfs_inode *ni)
+{
+ return __map_mft_record(ni, NULL);
+}
+
+struct mft_record *map_mft_record_reported(struct ntfs_inode *ni,
+ bool *error_reported)
+{
+ return __map_mft_record(ni, error_reported);
+}
+
/*
* unmap_mft_record - release a reference to a mapped mft record
* @ni: ntfs inode whose MFT record to unmap
@@ -262,8 +282,10 @@ void unmap_mft_record(struct ntfs_inode *ni)
* On successful return, @ntfs_ino contains a pointer to the ntfs_inode
* structure of the mapped extent inode.
*/
-struct mft_record *map_extent_mft_record(struct ntfs_inode *base_ni, u64 mref,
- struct ntfs_inode **ntfs_ino)
+static struct mft_record *__map_extent_mft_record(struct ntfs_inode *base_ni,
+ u64 mref,
+ struct ntfs_inode **ntfs_ino,
+ bool *error_reported)
{
struct mft_record *m;
struct ntfs_inode *ni = NULL;
@@ -299,7 +321,7 @@ struct mft_record *map_extent_mft_record(struct ntfs_inode *base_ni, u64 mref,
mutex_unlock(&base_ni->extent_lock);
atomic_dec(&base_ni->count);
/* We found the record; just have to map and return it. */
- m = map_mft_record(ni);
+ m = map_mft_record_reported(ni, error_reported);
/* map_mft_record() has incremented this on success. */
atomic_dec(&ni->count);
if (!IS_ERR(m)) {
@@ -333,7 +355,7 @@ struct mft_record *map_extent_mft_record(struct ntfs_inode *base_ni, u64 mref,
ni->nr_extents = -1;
ni->ext.base_ntfs_ino = base_ni;
/* Now map the record. */
- m = map_mft_record(ni);
+ m = map_mft_record_reported(ni, error_reported);
if (IS_ERR(m)) {
atomic_dec(&base_ni->count);
ntfs_clear_extent_inode(ni);
@@ -396,6 +418,21 @@ struct mft_record *map_extent_mft_record(struct ntfs_inode *base_ni, u64 mref,
return m;
}
+struct mft_record *map_extent_mft_record(struct ntfs_inode *base_ni, u64 mref,
+ struct ntfs_inode **ntfs_ino)
+{
+ return __map_extent_mft_record(base_ni, mref, ntfs_ino, NULL);
+}
+
+struct mft_record *
+map_extent_mft_record_reported(struct ntfs_inode *base_ni, u64 mref,
+ struct ntfs_inode **ntfs_ino,
+ bool *error_reported)
+{
+ return __map_extent_mft_record(base_ni, mref, ntfs_ino,
+ error_reported);
+}
+
/*
* __mark_mft_record_dirty - mark the base vfs inode dirty
* @ni: ntfs inode describing the mapped mft record
@@ -475,7 +512,8 @@ static void ntfs_mft_end_io(struct bio *bio)
err = ctx->error;
if (err) {
mapping_set_error(ctx->mapping, err);
- NVolSetErrors(ctx->vol);
+ if (!done)
+ ntfs_report_file_metadata_error(ctx->mapping->host, err);
ntfs_error(ctx->vol->sb, "I/O error while writing MFT: %d",
err);
}
@@ -672,8 +710,8 @@ static int ntfs_prepare_mft_record_io_units(struct ntfs_inode *ni,
*
* On success, clean the mft record and return 0. On ENOMEM, redirty the
* record so it can be retried. Asynchronous callers return success after
- * redirtying while synchronous callers receive the error. For other errors,
- * mark the volume with errors.
+ * redirtying while synchronous callers receive the error. The caller is
+ * responsible for reporting other errors.
*
* If @sync is false, PG_writeback keeps the folio stable and serializes later
* writers until the I/O completes.
@@ -807,8 +845,7 @@ int write_mft_record_nolock(struct ntfs_inode *ni, struct mft_record *m, int syn
mark_mft_record_dirty(ni);
if (!sync)
err = 0;
- } else
- NVolSetErrors(vol);
+ }
return err;
}
@@ -1339,9 +1376,11 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol)
struct ntfs_attr_search_ctx *ctx = NULL;
struct mft_record *mrec;
struct attr_record *a = NULL;
- int ret, mp_size;
+ int err, ret, mp_size;
u32 old_alen = 0;
+ u16 mp_ofs;
u8 *b, tb;
+ bool error_reported = false;
struct {
u8 added_cluster:1;
u8 added_run:1;
@@ -1425,10 +1464,11 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol)
if (IS_ERR(rl)) {
up_write(&mftbmp_ni->runlist.lock);
ntfs_error(vol->sb, "Failed to merge runlists for mft bitmap.");
- if (ntfs_cluster_free_from_rl(vol, rl2)) {
+ err = ntfs_cluster_free_from_rl(vol, rl2);
+ if (err) {
ntfs_error(vol->sb, "Failed to deallocate allocated cluster.%s",
es);
- NVolSetErrors(vol);
+ ntfs_report_metadata_error(vol, err);
}
kvfree(rl2);
return PTR_ERR(rl);
@@ -1548,9 +1588,11 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol)
restore_undo_alloc:
ntfs_attr_reinit_search_ctx(ctx);
- if (ntfs_attr_lookup(mftbmp_ni->type, mftbmp_ni->name,
- mftbmp_ni->name_len, CASE_SENSITIVE, rl[1].vcn, NULL,
- 0, ctx)) {
+ err = ntfs_attr_lookup(mftbmp_ni->type, mftbmp_ni->name,
+ mftbmp_ni->name_len, CASE_SENSITIVE, rl[1].vcn,
+ NULL, 0, ctx);
+ if (err) {
+ error_reported = ctx->error_reported;
ntfs_error(vol->sb,
"Failed to find last attribute extent of mft bitmap attribute.%s", es);
write_lock_irqsave(&mftbmp_ni->size_lock, flags);
@@ -1563,7 +1605,8 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol)
* The only thing that is now wrong is ->allocated_size of the
* base attribute extent which chkdsk should be able to fix.
*/
- NVolSetErrors(vol);
+ if (!error_reported)
+ ntfs_report_metadata_error(vol, err);
return ret;
}
a = ctx->attr;
@@ -1582,31 +1625,38 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol)
}
/* Deallocate the cluster. */
down_write(&vol->lcnbmp_lock);
- if (ntfs_bitmap_clear_bit(vol->lcnbmp_ino, lcn)) {
+ err = ntfs_bitmap_clear_bit(vol->lcnbmp_ino, lcn);
+ if (err) {
ntfs_error(vol->sb, "Failed to free allocated cluster.%s", es);
- NVolSetErrors(vol);
- } else
+ ntfs_report_metadata_error(vol, err);
+ } else {
ntfs_inc_free_clusters(vol, 1);
+ }
up_write(&vol->lcnbmp_lock);
if (status.mp_rebuilt) {
- if (ntfs_mapping_pairs_build(vol, (u8 *)a + le16_to_cpu(
- a->data.non_resident.mapping_pairs_offset),
- old_alen - le16_to_cpu(
- a->data.non_resident.mapping_pairs_offset),
- rl2, ll, -1, NULL, NULL, NULL)) {
+ mp_ofs = le16_to_cpu(a->data.non_resident.mapping_pairs_offset);
+ err = ntfs_mapping_pairs_build(vol, (u8 *)a + mp_ofs,
+ old_alen - mp_ofs, rl2, ll, -1,
+ NULL, NULL, NULL);
+ if (err) {
ntfs_error(vol->sb, "Failed to restore mapping pairs array.%s", es);
- NVolSetErrors(vol);
+ ntfs_report_metadata_error(vol, err);
}
- if (ntfs_attr_record_resize(ctx->mrec, a, old_alen)) {
+ err = ntfs_attr_record_resize(ctx->mrec, a, old_alen);
+ if (err) {
ntfs_error(vol->sb, "Failed to restore attribute record.%s", es);
- NVolSetErrors(vol);
+ ntfs_report_metadata_error(vol, err);
}
mark_mft_record_dirty(ctx->ntfs_ino);
- } else if (status.mp_extended &&
- ntfs_attr_update_mapping_pairs_locked(mftbmp_ni, 0,
- mftbmp_ni)) {
- ntfs_error(vol->sb, "Failed to restore mapping pairs.%s", es);
- NVolSetErrors(vol);
+ } else if (status.mp_extended) {
+ err = ntfs_attr_update_mapping_pairs_locked_reported(mftbmp_ni, 0,
+ mftbmp_ni,
+ &error_reported);
+ if (err) {
+ ntfs_error(vol->sb, "Failed to restore mapping pairs.%s", es);
+ if (!error_reported)
+ ntfs_report_metadata_error(vol, err);
+ }
}
if (ctx)
ntfs_attr_put_search_ctx(ctx);
@@ -1639,7 +1689,8 @@ static int ntfs_mft_bitmap_extend_initialized_nolock(struct ntfs_volume *vol)
struct ntfs_attr_search_ctx *ctx;
struct mft_record *mrec;
struct attr_record *a;
- int ret;
+ int err, ret;
+ bool error_reported = false;
ntfs_debug("Extending mft bitmap initialized (and data) size.");
mft_ni = NTFS_I(vol->mft_ino);
@@ -1696,23 +1747,28 @@ static int ntfs_mft_bitmap_extend_initialized_nolock(struct ntfs_volume *vol)
}
ntfs_error(vol->sb, "Failed to write to mft bitmap.");
/* Try to recover from the error. */
- mrec = map_mft_record(mft_ni);
+ mrec = map_mft_record_reported(mft_ni, &error_reported);
if (IS_ERR(mrec)) {
ntfs_error(vol->sb, "Failed to map mft record.%s", es);
- NVolSetErrors(vol);
+ err = PTR_ERR(mrec);
+ if (!error_reported)
+ ntfs_report_metadata_error(vol, err);
return ret;
}
ctx = ntfs_attr_get_search_ctx(mft_ni, mrec);
if (unlikely(!ctx)) {
ntfs_error(vol->sb, "Failed to get search context.%s", es);
- NVolSetErrors(vol);
+ ntfs_report_metadata_error(vol, -ENOMEM);
goto unm_err_out;
}
- if (ntfs_attr_lookup(mftbmp_ni->type, mftbmp_ni->name,
- mftbmp_ni->name_len, CASE_SENSITIVE, 0, NULL, 0, ctx)) {
+ err = ntfs_attr_lookup(mftbmp_ni->type, mftbmp_ni->name,
+ mftbmp_ni->name_len, CASE_SENSITIVE, 0, NULL, 0,
+ ctx);
+ if (err) {
ntfs_error(vol->sb,
"Failed to find first attribute extent of mft bitmap attribute.%s", es);
- NVolSetErrors(vol);
+ if (!ctx->error_reported)
+ ntfs_report_metadata_error(vol, err);
put_err_out:
ntfs_attr_put_search_ctx(ctx);
unm_err_out:
@@ -1774,9 +1830,10 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
struct ntfs_attr_search_ctx *ctx = NULL;
struct mft_record *mrec;
struct attr_record *a = NULL;
- int ret, mp_size;
+ int err, ret, mp_size;
u32 old_alen = 0;
- bool mp_rebuilt = false, mp_extended = false;
+ u16 mp_ofs;
+ bool error_reported = false, mp_rebuilt = false, mp_extended = false;
size_t new_rl_count;
ntfs_debug("Extending mft data allocation.");
@@ -1862,10 +1919,11 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
if (IS_ERR(rl)) {
up_write(&mft_ni->runlist.lock);
ntfs_error(vol->sb, "Failed to merge runlists for mft data attribute.");
- if (ntfs_cluster_free_from_rl(vol, rl2)) {
+ err = ntfs_cluster_free_from_rl(vol, rl2);
+ if (err) {
ntfs_error(vol->sb,
"Failed to deallocate clusters from the mft data attribute.%s", es);
- NVolSetErrors(vol);
+ ntfs_report_metadata_error(vol, err);
}
kvfree(rl2);
return PTR_ERR(rl);
@@ -1985,8 +2043,10 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
return 0;
restore_undo_alloc:
ntfs_attr_reinit_search_ctx(ctx);
- if (ntfs_attr_lookup(mft_ni->type, mft_ni->name, mft_ni->name_len,
- CASE_SENSITIVE, rl[1].vcn, NULL, 0, ctx)) {
+ err = ntfs_attr_lookup(mft_ni->type, mft_ni->name, mft_ni->name_len,
+ CASE_SENSITIVE, rl[1].vcn, NULL, 0, ctx);
+ if (err) {
+ error_reported = ctx->error_reported;
ntfs_error(vol->sb,
"Failed to find last attribute extent of mft data attribute.%s", es);
write_lock_irqsave(&mft_ni->size_lock, flags);
@@ -1999,45 +2059,55 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
* The only thing that is now wrong is ->allocated_size of the
* base attribute extent which chkdsk should be able to fix.
*/
- NVolSetErrors(vol);
+ if (!error_reported)
+ ntfs_report_metadata_error(vol, err);
return ret;
}
ctx->attr->data.non_resident.highest_vcn =
cpu_to_le64(old_last_vcn - 1);
undo_alloc:
- if (ntfs_cluster_free(mft_ni, old_last_vcn, -1, ctx) < 0) {
+ err = ntfs_cluster_free(mft_ni, old_last_vcn, -1, ctx);
+ if (err < 0) {
ntfs_error(vol->sb, "Failed to free clusters from mft data attribute.%s", es);
- NVolSetErrors(vol);
+ ntfs_report_metadata_error(vol, err);
}
- if (ntfs_rl_truncate_nolock(vol, &mft_ni->runlist, old_last_vcn)) {
+ err = ntfs_rl_truncate_nolock(vol, &mft_ni->runlist, old_last_vcn);
+ if (err) {
ntfs_error(vol->sb, "Failed to truncate mft data attribute runlist.%s", es);
- NVolSetErrors(vol);
- }
- if (mp_extended && ntfs_attr_update_mapping_pairs(mft_ni, 0)) {
- ntfs_error(vol->sb, "Failed to restore mapping pairs.%s",
- es);
- NVolSetErrors(vol);
+ ntfs_report_metadata_error(vol, err);
+ }
+ if (mp_extended) {
+ err = ntfs_attr_update_mapping_pairs_locked_reported(mft_ni, 0,
+ NULL,
+ &error_reported);
+ if (err) {
+ ntfs_error(vol->sb, "Failed to restore mapping pairs.%s",
+ es);
+ if (!error_reported)
+ ntfs_report_metadata_error(vol, err);
+ }
}
if (ctx) {
a = ctx->attr;
if (mp_rebuilt && !IS_ERR(ctx->mrec)) {
- if (ntfs_mapping_pairs_build(vol, (u8 *)a + le16_to_cpu(
- a->data.non_resident.mapping_pairs_offset),
- old_alen - le16_to_cpu(
- a->data.non_resident.mapping_pairs_offset),
- rl2, ll, -1, NULL, NULL, NULL)) {
+ mp_ofs = le16_to_cpu(a->data.non_resident.mapping_pairs_offset);
+ err = ntfs_mapping_pairs_build(vol, (u8 *)a + mp_ofs,
+ old_alen - mp_ofs, rl2, ll,
+ -1, NULL, NULL, NULL);
+ if (err) {
ntfs_error(vol->sb, "Failed to restore mapping pairs array.%s", es);
- NVolSetErrors(vol);
+ ntfs_report_metadata_error(vol, err);
}
- if (ntfs_attr_record_resize(ctx->mrec, a, old_alen)) {
+ err = ntfs_attr_record_resize(ctx->mrec, a, old_alen);
+ if (err) {
ntfs_error(vol->sb, "Failed to restore attribute record.%s", es);
- NVolSetErrors(vol);
+ ntfs_report_metadata_error(vol, err);
}
mark_mft_record_dirty(ctx->ntfs_ino);
} else if (IS_ERR(ctx->mrec)) {
ntfs_error(vol->sb, "Failed to restore attribute search context.%s", es);
- NVolSetErrors(vol);
+ ntfs_report_metadata_error(vol, PTR_ERR(ctx->mrec));
}
ntfs_attr_put_search_ctx(ctx);
}
@@ -2269,8 +2339,8 @@ static int ntfs_mft_record_format(const struct ntfs_volume *vol, const s64 mft_n
*
* On error, the volume will be left in a consistent state and no record will
* be allocated. If rolling back a partial operation fails, we may leave some
- * inconsistent metadata in which case we set NVolErrors() so the volume is
- * left dirty when unmounted.
+ * inconsistent metadata in which case we report the error so the volume is
+ * left dirty when unmounted and userspace is notified.
*
* Note, this function cannot make use of most of the normal functions, like
* for example for attribute resizing, etc, because when the run list overflows
@@ -2301,7 +2371,7 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode,
struct attr_record *a;
pgoff_t index;
unsigned int ofs;
- int err;
+ int err, rollback_err;
__le16 seq_no, usn;
bool record_formatted = false, from_reserve = false, tail_alloc = false;
bool reserve_created = false;
@@ -2705,7 +2775,7 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode,
folio_unlock(folio);
kunmap_local(m);
folio_put(folio);
- NVolSetErrors(vol);
+ ntfs_report_metadata_error(vol, -EFSCORRUPTED);
goto search_free_rec;
}
/*
@@ -2861,9 +2931,12 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode,
if (!base_ni || base_ni->mft_no != FILE_MFT)
down_write(&vol->mftbmp_lock);
undo_mftbmp_alloc_nolock:
- if (!forced_reserved_record && ntfs_bitmap_clear_bit(vol->mftbmp_ino, bit)) {
+ rollback_err = 0;
+ if (!forced_reserved_record)
+ rollback_err = ntfs_bitmap_clear_bit(vol->mftbmp_ino, bit);
+ if (rollback_err) {
ntfs_error(vol->sb, "Failed to clear bit in mft bitmap.%s", es);
- NVolSetErrors(vol);
+ ntfs_report_metadata_error(vol, rollback_err);
}
if ((from_reserve || reserve_created) &&
vol->mft_record_reserve_pos == bit + 1)
@@ -2900,30 +2973,35 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode,
int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni)
{
u64 mft_no;
- int err;
+ int err, rollback_err;
u16 seq_no;
__le16 old_seq_no;
__le64 old_base_mft_record;
struct mft_record *ni_mrec;
unsigned int memalloc_flags;
struct ntfs_inode *base_ni;
- bool keep_reserved;
+ bool error_reported = false, keep_reserved;
if (!vol || !ni)
return -EINVAL;
ntfs_debug("Entering for inode 0x%llx.\n", (long long)ni->mft_no);
+ if (likely(ni->nr_extents >= 0))
+ base_ni = ni;
+ else
+ base_ni = ni->ext.base_ntfs_ino;
- ni_mrec = map_mft_record(ni);
- if (IS_ERR(ni_mrec))
+ ni_mrec = map_mft_record_reported(ni, &error_reported);
+ if (IS_ERR(ni_mrec)) {
+ err = PTR_ERR(ni_mrec);
+ if (!error_reported && err != -ENOMEM &&
+ err != -EINTR && err != -ERESTARTSYS)
+ ntfs_report_file_metadata_error(VFS_I(base_ni), err);
return -EIO;
+ }
/* Cache the mft reference for later. */
mft_no = ni->mft_no;
- if (likely(ni->nr_extents >= 0))
- base_ni = ni;
- else
- base_ni = ni->ext.base_ntfs_ino;
keep_reserved = mft_no >= FILE_reserved12 &&
mft_no <= FILE_reserved15 &&
base_ni->mft_no == FILE_MFT;
@@ -2962,8 +3040,10 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni)
*/
NInoSetDirty(ni);
err = write_mft_record(ni, ni_mrec, 1);
- if (err)
+ if (err) {
+ ntfs_report_file_metadata_error(VFS_I(base_ni), err);
goto sync_rollback;
+ }
if (keep_reserved) {
unmap_mft_record(ni);
@@ -3006,7 +3086,9 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni)
ni_mrec->sequence_number = old_seq_no;
ni_mrec->base_mft_record = old_base_mft_record;
NInoSetDirty(ni);
- write_mft_record(ni, ni_mrec, 0);
+ rollback_err = write_mft_record(ni, ni_mrec, 0);
+ if (rollback_err)
+ ntfs_report_file_metadata_error(VFS_I(base_ni), rollback_err);
unmap_mft_record(ni);
return err;
}
@@ -3153,7 +3235,7 @@ static void ntfs_mft_write_error(struct ntfs_volume *vol,
struct address_space *mapping, int err)
{
mapping_set_error(mapping, err);
- NVolSetErrors(vol);
+ ntfs_report_file_metadata_error(mapping->host, err);
ntfs_error(vol->sb, "Error while writing MFT folio: %d", err);
}
diff --git a/fs/ntfs/mft.h b/fs/ntfs/mft.h
index d2a31205e08c..3eda89aab67a 100644
--- a/fs/ntfs/mft.h
+++ b/fs/ntfs/mft.h
@@ -14,9 +14,15 @@
#include "inode.h"
struct mft_record *map_mft_record(struct ntfs_inode *ni);
+struct mft_record *map_mft_record_reported(struct ntfs_inode *ni,
+ bool *error_reported);
void unmap_mft_record(struct ntfs_inode *ni);
struct mft_record *map_extent_mft_record(struct ntfs_inode *base_ni, u64 mref,
struct ntfs_inode **ntfs_ino);
+struct mft_record *
+map_extent_mft_record_reported(struct ntfs_inode *base_ni, u64 mref,
+ struct ntfs_inode **ntfs_ino,
+ bool *error_reported);
static inline void unmap_extent_mft_record(struct ntfs_inode *ni)
{
--
2.51.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v2 5/5] ntfs: report shutdown errors to fsnotify
2026-09-16 2:23 [PATCH v2 0/5] ntfs: report filesystem errors through fsnotify Baolin Liu
` (3 preceding siblings ...)
2026-09-16 2:23 ` [PATCH v2 4/5] ntfs: report MFT " Baolin Liu
@ 2026-09-16 2:23 ` Baolin Liu
2026-09-16 2:40 ` [PATCH v2 0/5] ntfs: report filesystem errors through fsnotify liubaolin
5 siblings, 0 replies; 9+ messages in thread
From: Baolin Liu @ 2026-09-16 2:23 UTC (permalink / raw)
To: linkinjeon, hyc.lee; +Cc: ntfs, linux-kernel, Baolin Liu
From: Baolin Liu <liubaolin@kylinos.cn>
Notify FAN_FS_ERROR listeners when NTFS enters forced shutdown
after FS_IOC_SHUTDOWN or backing-device removal. Keep the shutdown
state transition and notification together in a dedicated helper.
Signed-off-by: Baolin Liu <liubaolin@kylinos.cn>
---
fs/ntfs/super.c | 12 +++++++++---
fs/ntfs/volume.h | 1 +
2 files changed, 10 insertions(+), 3 deletions(-)
diff --git a/fs/ntfs/super.c b/fs/ntfs/super.c
index 7f6812d27fd1..a331188286da 100644
--- a/fs/ntfs/super.c
+++ b/fs/ntfs/super.c
@@ -352,6 +352,12 @@ void ntfs_report_file_metadata_error(struct inode *inode, int error)
fserror_report_file_metadata(inode, error, GFP_ATOMIC);
}
+void ntfs_report_shutdown(struct ntfs_volume *vol)
+{
+ NVolSetShutdown(vol);
+ fserror_report_shutdown(vol->sb, GFP_ATOMIC);
+}
+
void ntfs_handle_error(struct super_block *sb)
{
struct ntfs_volume *vol = NTFS_SB(sb);
@@ -368,7 +374,7 @@ void ntfs_handle_error(struct super_block *sb)
sb->s_id);
} else if (vol->on_errors == ON_ERRORS_CONTINUE) {
if (errseq_check(&sb->s_wb_err, vol->wb_err) == -ENODEV) {
- NVolSetShutdown(vol);
+ ntfs_report_shutdown(vol);
vol->wb_err = sb->s_wb_err;
}
}
@@ -1911,10 +1917,10 @@ int ntfs_force_shutdown(struct super_block *sb, u32 flags)
if (ret)
return ret;
bdev_thaw(sb->s_bdev);
- NVolSetShutdown(vol);
+ ntfs_report_shutdown(vol);
break;
case FS_SHUTDOWN_FLAGS_NOLOGFLUSH:
- NVolSetShutdown(vol);
+ ntfs_report_shutdown(vol);
break;
default:
return -EINVAL;
diff --git a/fs/ntfs/volume.h b/fs/ntfs/volume.h
index 7daac7048a4c..09f92dc1117b 100644
--- a/fs/ntfs/volume.h
+++ b/fs/ntfs/volume.h
@@ -252,6 +252,7 @@ DEFINE_NVOL_BIT_OPS(SymlinkNative)
void ntfs_report_metadata_error(struct ntfs_volume *vol, int error);
void ntfs_report_file_metadata_error(struct inode *inode, int error);
+void ntfs_report_shutdown(struct ntfs_volume *vol);
static inline void ntfs_inc_free_clusters(struct ntfs_volume *vol, s64 nr)
{
--
2.51.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v2 0/5] ntfs: report filesystem errors through fsnotify
2026-09-16 2:23 [PATCH v2 0/5] ntfs: report filesystem errors through fsnotify Baolin Liu
` (4 preceding siblings ...)
2026-09-16 2:23 ` [PATCH v2 5/5] ntfs: report shutdown " Baolin Liu
@ 2026-09-16 2:40 ` liubaolin
5 siblings, 0 replies; 9+ messages in thread
From: liubaolin @ 2026-09-16 2:40 UTC (permalink / raw)
To: linkinjeon, hyc.lee; +Cc: ntfs, linux-kernel, Baolin Liu
在 2026/9/16 10:23, Baolin Liu 写道:
> From: Baolin Liu <liubaolin@kylinos.cn>
>
> Report NTFS metadata and shutdown errors through the generic filesystem
> error notification infrastructure. Use volume-level events when no
> affected file is available and file-level events when an inode identifies
> the failure.
>
> Split the reporting by allocation, attribute, inode, MFT, and shutdown
> domains. Propagate reporting state through attribute searches,
> mapping-pairs updates, and MFT mappings so callers do not emit duplicate
> events.
>
> Changes since v1:
> - Restrict patch 1 to the volume helper and non-file allocation rollbacks.
> - Introduce the file helper with attribute reporting in patch 2.
> - Separate inode and MFT reporting into patches 3 and 4.
> - Adapt MFT writeback reporting to the current asynchronous I/O path.
> - Keep shutdown reporting independent in patch 5.
>
> Testing:
> - Built fs/ntfs with W=1 after each patch.
> - Booted the patched kernel in QEMU and monitored FAN_FS_ERROR events on
> an NTFS volume with fanotify. Verified that volume metadata errors report
> EIO without a valid file handle, file metadata errors report EIO with a
> valid FID, and shutdown errors report ESHUTDOWN.
>
> Baolin Liu (5):
> ntfs: report allocation metadata errors to fsnotify
> ntfs: report attribute errors to fsnotify
> ntfs: report inode metadata errors to fsnotify
> ntfs: report MFT errors to fsnotify
> ntfs: report shutdown errors to fsnotify
>
> fs/ntfs/attrib.c | 88 +++++++++++-----
> fs/ntfs/attrib.h | 6 ++
> fs/ntfs/attrlist.c | 16 +--
> fs/ntfs/bitmap.c | 2 +-
> fs/ntfs/inode.c | 62 +++++++----
> fs/ntfs/lcnalloc.c | 4 +-
> fs/ntfs/logfile.c | 2 +-
> fs/ntfs/mft.c | 252 ++++++++++++++++++++++++++++++---------------
> fs/ntfs/mft.h | 6 ++
> fs/ntfs/super.c | 27 ++++-
> fs/ntfs/volume.h | 4 +
> 11 files changed, 329 insertions(+), 140 deletions(-)
>
Hi Namjae and Hyunchul,
Thank you, Hyunchul, for reviewing the v1 patch series and providing
helpful feedback.
Based on your comments, I reorganized the series for v2. Patch 1 now
contains only the volume-level helper and non-file allocation metadata
errors. The file-level helper is introduced in patch 2, and file-related
error reporting is split among the attribute, inode, and MFT patches.
Shutdown reporting remains separate in patch 5. I also expanded the
commit messages and tested the notifications with fanotify in QEMU.
I have now submitted the v2 patch series. Your review and any further
discussion would be very welcome. I look forward to hearing your
comments and suggestions.
Thanks,
Baolin.
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v2 2/5] ntfs: report attribute errors to fsnotify
2026-09-16 2:23 ` [PATCH v2 2/5] ntfs: report attribute " Baolin Liu
@ 2026-09-16 7:38 ` Hyunchul Lee
2026-09-17 4:21 ` liubaolin
0 siblings, 1 reply; 9+ messages in thread
From: Hyunchul Lee @ 2026-09-16 7:38 UTC (permalink / raw)
To: Baolin Liu; +Cc: linkinjeon, ntfs, linux-kernel, Baolin Liu
Hi Baolin,
> folio_err_out:
> @@ -3720,7 +3737,8 @@ static int ntfs_attr_update_meta(struct attr_record *a, struct ntfs_inode *ni,
> static int __ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni,
> s64 from_vcn,
> struct ntfs_inode *locked_ni,
> - bool defer_attrlist)
> + bool defer_attrlist,
> + bool *error_reported)
> {
> struct ntfs_attr_search_ctx *ctx;
> struct ntfs_inode *base_ni;
> @@ -4086,8 +4104,11 @@ static int __ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni,
> return 0;
>
> put_err_out:
> - if (ctx)
> + if (ctx) {
> + if (error_reported && ctx->error_reported)
> + *error_reported = true;
> ntfs_attr_put_search_ctx(ctx);
> + }
> return err;
> }
__ntfs_attr_update_mapping_pairs() does:
ntfs_attr_put_search_ctx(ctx);
ctx = NULL
Therefore any later mapping or other errors can therefore lose report
state and duplicate reports. And The error_reported boolean only tell
us that some lower-level path has already emitted an error. Consequently
a report can incorrectly suppress a separate report for a later rollback
or resotre failure, even though that failure represents an independant
filesystem integrity problem.
Instead of carrying error_reported, would it be simpler to report each
error at its detection point and let fanofify merge repeated error
events? or would there be any alternatives?
--
Thanks,
Hyunchul
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v2 2/5] ntfs: report attribute errors to fsnotify
2026-09-16 7:38 ` Hyunchul Lee
@ 2026-09-17 4:21 ` liubaolin
0 siblings, 0 replies; 9+ messages in thread
From: liubaolin @ 2026-09-17 4:21 UTC (permalink / raw)
To: Hyunchul Lee; +Cc: linkinjeon, ntfs, linux-kernel, Baolin Liu
在 2026/9/16 15:38, Hyunchul Lee 写道:
> Hi Baolin,
>
>> folio_err_out:
>> @@ -3720,7 +3737,8 @@ static int ntfs_attr_update_meta(struct attr_record *a, struct ntfs_inode *ni,
>> static int __ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni,
>> s64 from_vcn,
>> struct ntfs_inode *locked_ni,
>> - bool defer_attrlist)
>> + bool defer_attrlist,
>> + bool *error_reported)
>> {
>> struct ntfs_attr_search_ctx *ctx;
>> struct ntfs_inode *base_ni;
>> @@ -4086,8 +4104,11 @@ static int __ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni,
>> return 0;
>>
>> put_err_out:
>> - if (ctx)
>> + if (ctx) {
>> + if (error_reported && ctx->error_reported)
>> + *error_reported = true;
>> ntfs_attr_put_search_ctx(ctx);
>> + }
>> return err;
>> }
>
> __ntfs_attr_update_mapping_pairs() does:
>
> ntfs_attr_put_search_ctx(ctx);
> ctx = NULL
>
> Therefore any later mapping or other errors can therefore lose report
> state and duplicate reports. And The error_reported boolean only tell
> us that some lower-level path has already emitted an error. Consequently
> a report can incorrectly suppress a separate report for a later rollback
> or resotre failure, even though that failure represents an independant
> filesystem integrity problem.
>
> Instead of carrying error_reported, would it be simpler to report each
> error at its detection point and let fanofify merge repeated error
> events? or would there be any alternatives?
>
Hi Hyunchul,
Thanks for the feedback. I agree that reporting errors at their
detection points and relying on fanotify's event merging is simpler.
I'll update the series accordingly and send v3.
Thanks,
Baolin.
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-09-17 4:22 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-16 2:23 [PATCH v2 0/5] ntfs: report filesystem errors through fsnotify Baolin Liu
2026-09-16 2:23 ` [PATCH v2 1/5] ntfs: report allocation metadata errors to fsnotify Baolin Liu
2026-09-16 2:23 ` [PATCH v2 2/5] ntfs: report attribute " Baolin Liu
2026-09-16 7:38 ` Hyunchul Lee
2026-09-17 4:21 ` liubaolin
2026-09-16 2:23 ` [PATCH v2 3/5] ntfs: report inode metadata " Baolin Liu
2026-09-16 2:23 ` [PATCH v2 4/5] ntfs: report MFT " Baolin Liu
2026-09-16 2:23 ` [PATCH v2 5/5] ntfs: report shutdown " Baolin Liu
2026-09-16 2:40 ` [PATCH v2 0/5] ntfs: report filesystem errors through fsnotify liubaolin
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®