mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [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®