mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v3 0/6] ntfs: report filesystem errors through fsnotify
@ 2026-09-24  9:00 Baolin Liu
  2026-09-24  9:00 ` [PATCH v3 1/6] ntfs: report allocation metadata errors to fsnotify Baolin Liu
                   ` (6 more replies)
  0 siblings, 7 replies; 13+ messages in thread
From: Baolin Liu @ 2026-09-24  9:00 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 file-level events for errors
associated with an affected inode, and volume-level events for volume
metadata and shutdown errors. Preserve the volume error flags and report
failed recovery independently of the error that triggered it.

Report errors where they are detected and leave queued event merging to
fanotify. No error_reported state is carried through search contexts,
mapping-pairs updates or MFT mappings, so releasing a context cannot lose
that state or suppress a later rollback or restore failure.

Changes since v2:
- Follow Hyunchul Lee's suggestion to remove error_reported tracking and
  the reporting-aware mapping APIs throughout the series.
- Report independent cluster, mapping-pairs, ATTRIBUTE_LIST and MFT
  bitmap recovery failures while preserving the original operation errno.
- Base the series on ntfs-next at 401898d748fc ("ntfs: validate
  attribute-list sizes before allocation"). Retain its MFT parent BIO
  and folio writeback implementation. Report synchronous and asynchronous
  device errors in the existing completion callback; fserror itself
  defers notification work and holds the inode reference. No additional
  NTFS completion workqueue or pending-I/O counter is introduced.
- Cover stale extent references, failed extent-record cleanup, mirror
  failures and MFT preparation errors. Resolve attribute-inode reports
  to their base inode and keep cluster-free counts in 64-bit variables.
- Split MFT reporting into two patches: metadata validation, allocation
  and recovery in patch 4; write preparation, mirror and BIO completion
  errors in patch 5. This keeps the two review topics separate.
  Shutdown reporting is now patch 6.

Testing on this base:
- QEMU passed mount/read/write/fsync/truncate/xattr/rename/unlink, MFT
  growth and unmount without FAN_FS_ERROR events during normal operation.
- Verified EIO with a valid FID for file MFT corruption, EUCLEAN without
  a valid FID for an MFT bitmap mismatch, and ESHUTDOWN for forced
  shutdown.
- Fault injection of an initial mapping-pairs error followed by an
  independent restore failure produced a merged FAN_FS_ERROR event with
  error_count=2. Synchronous and asynchronous MFT completion failures
  also generated notifications. The injection code is test-only and is
  excluded from the series.

Baolin Liu (6):
  ntfs: report allocation metadata errors to fsnotify
  ntfs: report attribute errors to fsnotify
  ntfs: report inode metadata errors to fsnotify
  ntfs: report MFT metadata and recovery errors to fsnotify
  ntfs: report MFT writeback errors to fsnotify
  ntfs: report shutdown errors to fsnotify

 fs/ntfs/attrib.c   |  84 ++++++++++++++------
 fs/ntfs/attrlist.c |  17 ++--
 fs/ntfs/bitmap.c   |   2 +-
 fs/ntfs/inode.c    |  12 ++-
 fs/ntfs/lcnalloc.c |   4 +-
 fs/ntfs/logfile.c  |   2 +-
 fs/ntfs/mft.c      | 192 ++++++++++++++++++++++++++++-----------------
 fs/ntfs/super.c    |  32 +++++++-
 fs/ntfs/volume.h   |   4 +
 9 files changed, 235 insertions(+), 114 deletions(-)

-- 
2.51.0


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v3 1/6] ntfs: report allocation metadata errors to fsnotify
  2026-09-24  9:00 [PATCH v3 0/6] ntfs: report filesystem errors through fsnotify Baolin Liu
@ 2026-09-24  9:00 ` Baolin Liu
  2026-09-24  9:00 ` [PATCH v3 2/6] ntfs: report attribute " Baolin Liu
                   ` (5 subsequent siblings)
  6 siblings, 0 replies; 13+ messages in thread
From: Baolin Liu @ 2026-09-24  9:00 UTC (permalink / raw)
  To: linkinjeon, hyc.lee; +Cc: ntfs, linux-kernel, Baolin Liu

From: Baolin Liu <liubaolin@kylinos.cn>

Cluster allocation and free rollbacks can leave volume metadata
inconsistent without notifying filesystem health monitors.

Add a volume-level helper that records the error and reports it through
fsnotify after mount activation. Report each failed rollback where it
is detected and leave event merging to fanotify.

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 716ba775f1f3..827313df0e28 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"
@@ -394,6 +395,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 fdb57279de84..5bcd0dd2a19d 100644
--- a/fs/ntfs/volume.h
+++ b/fs/ntfs/volume.h
@@ -254,6 +254,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] 13+ messages in thread

* [PATCH v3 2/6] ntfs: report attribute errors to fsnotify
  2026-09-24  9:00 [PATCH v3 0/6] ntfs: report filesystem errors through fsnotify Baolin Liu
  2026-09-24  9:00 ` [PATCH v3 1/6] ntfs: report allocation metadata errors to fsnotify Baolin Liu
@ 2026-09-24  9:00 ` Baolin Liu
  2026-09-24  9:00 ` [PATCH v3 3/6] ntfs: report inode metadata " Baolin Liu
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 13+ messages in thread
From: Baolin Liu @ 2026-09-24  9:00 UTC (permalink / raw)
  To: linkinjeon, hyc.lee; +Cc: ntfs, linux-kernel, Baolin Liu

From: Baolin Liu <liubaolin@kylinos.cn>

Attribute corruption and failed allocation rollbacks can leave files
unusable or their metadata inconsistent without notifying health monitors.

Add a file-level reporting helper and notify fsnotify when attribute
validation fails or a rollback cannot release clusters or restore mapping
pairs. Keep the original operation's return value separate from the
rollback error reported to userspace.

Report rollback failures independently of earlier lookup or mapping
errors. Leave event merging to fanotify instead of carrying reporting
state through search contexts and mapping-pairs updates.

Signed-off-by: Baolin Liu <liubaolin@kylinos.cn>
---
 fs/ntfs/attrib.c   | 84 ++++++++++++++++++++++++++++++++--------------
 fs/ntfs/attrlist.c | 17 ++++++----
 fs/ntfs/bitmap.c   |  2 +-
 fs/ntfs/super.c    | 12 +++++++
 fs/ntfs/volume.h   |  1 +
 5 files changed, 84 insertions(+), 32 deletions(-)

diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c
index 333b3371acb4..10b7f9b7c051 100644
--- a/fs/ntfs/attrib.c
+++ b/fs/ntfs/attrib.c
@@ -838,6 +838,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 +965,11 @@ 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);
 	return -EIO;
 }
 
@@ -1501,8 +1506,12 @@ 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);
+		else
+			NVolSetErrors(vol);
+	}
 	return err;
 not_found:
 	/*
@@ -2233,10 +2242,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:
@@ -3728,6 +3738,7 @@ static int __ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni,
 	struct attr_record *a;
 	s64 stop_vcn;
 	int err = 0, mp_size, cur_max_mp_size, exp_max_mp_size;
+	int rollback_err;
 	bool finished_build;
 	bool first_updated = false;
 	struct super_block *sb;
@@ -4016,10 +4027,14 @@ static int __ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni,
 		m = map_mft_record(ext_ni);
 		if (IS_ERR(m)) {
 			ntfs_error(sb, "Could not map new MFT record");
-			if (ntfs_mft_record_free(ni->vol, ext_ni))
+			rollback_err = ntfs_mft_record_free(ni->vol, ext_ni);
+			if (rollback_err) {
 				ntfs_error(sb, "Could not free MFT record");
+				ntfs_report_file_metadata_error(VFS_I(base_ni),
+								rollback_err);
+			}
 			ntfs_inode_close(ext_ni);
-			err = -ENOMEM;
+			err = PTR_ERR(m);
 			ext_ni = NULL;
 			goto put_err_out;
 		}
@@ -4053,8 +4068,12 @@ static int __ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni,
 		if (err < 0) {
 			ntfs_error(sb, "Could not add attribute extent");
 			unmap_mft_record(ext_ni);
-			if (ntfs_mft_record_free(ni->vol, ext_ni))
+			rollback_err = ntfs_mft_record_free(ni->vol, ext_ni);
+			if (rollback_err) {
 				ntfs_error(sb, "Could not free MFT record");
+				ntfs_report_file_metadata_error(VFS_I(base_ni),
+								rollback_err);
+			}
 			ntfs_inode_close(ext_ni);
 			goto put_err_out;
 		}
@@ -4067,8 +4086,12 @@ static int __ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni,
 		if (err < 0 && err != -ENOSPC) {
 			ntfs_error(sb, "Failed to build MP");
 			unmap_mft_record(ext_ni);
-			if (ntfs_mft_record_free(ni->vol, ext_ni))
+			rollback_err = ntfs_mft_record_free(ni->vol, ext_ni);
+			if (rollback_err) {
 				ntfs_error(sb, "Couldn't free MFT record");
+				ntfs_report_file_metadata_error(VFS_I(base_ni),
+								rollback_err);
+			}
 			goto put_err_out;
 		}
 		a->data.non_resident.highest_vcn = cpu_to_le64(stop_vcn - 1);
@@ -4449,6 +4472,7 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz
 	struct ntfs_attr_search_ctx *ctx = NULL;
 	struct runlist_element *rl, *rln;
 	s64 org_alloc_size, org_compressed_size;
+	s64 freed;
 	int err, err2;
 	struct ntfs_inode *base_ni;
 	struct super_block *sb = ni->vol->sb;
@@ -4678,10 +4702,12 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz
 	return 0;
 rollback:
 	/* Free allocated clusters. */
-	err2 = ntfs_cluster_free(ni, ntfs_bytes_to_cluster(vol, org_alloc_size),
-				-1, ctx);
-	if (err2)
+	freed = ntfs_cluster_free(ni, ntfs_bytes_to_cluster(vol, org_alloc_size),
+				  -1, ctx);
+	if (freed < 0) {
 		ntfs_debug("Leaking clusters");
+		ntfs_report_file_metadata_error(VFS_I(base_ni), freed);
+	}
 
 	/* Now, truncate the runlist itself. */
 	if (ni != locked_ni)
@@ -4698,14 +4724,18 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz
 		kvfree(ni->runlist.rl);
 		ni->runlist.rl = NULL;
 		ntfs_error(sb, "Couldn't truncate runlist. Rollback failed");
+		ntfs_report_file_metadata_error(VFS_I(base_ni), err2);
 	} else {
 		/* Prepare to mapping pairs update. */
 		ni->allocated_size = org_alloc_size;
 		/* Restore mapping pairs. */
 		if (ni != locked_ni)
 			down_read(&ni->runlist.lock);
-		if (__ntfs_attr_update_mapping_pairs(ni, 0, locked_ni, true))
+		err2 = __ntfs_attr_update_mapping_pairs(ni, 0, locked_ni, true);
+		if (err2) {
 			ntfs_error(sb, "Failed to restore old mapping pairs");
+			ntfs_report_file_metadata_error(VFS_I(base_ni), err2);
+		}
 		if (ni != locked_ni)
 			up_read(&ni->runlist.lock);
 
@@ -5139,8 +5169,9 @@ int ntfs_attr_map_cluster(struct ntfs_inode *ni, s64 vcn_start, s64 *lcn_start,
 	struct runlist_element *rl, *rlc;
 	struct runlist_element *old_rl = NULL;
 	s64 vcn = vcn_start, lcn, clu_count;
+	s64 freed;
 	s64 lcn_seek_from = -1;
-	int err = 0;
+	int err = 0, err2;
 	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;
@@ -5252,8 +5284,11 @@ int ntfs_attr_map_cluster(struct ntfs_inode *ni, s64 vcn_start, s64 *lcn_start,
 	if (IS_ERR(rl)) {
 		ntfs_error(vol->sb, "Failed to merge runlists");
 		err = PTR_ERR(rl);
-		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 hot clusters.");
+			ntfs_report_file_metadata_error(VFS_I(ni), err2);
+		}
 		kvfree(rlc);
 		goto out;
 	}
@@ -5272,13 +5307,12 @@ 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) {
+			freed = ntfs_cluster_free(ni, vcn, clu_count, ctx);
+			if (freed < 0 || freed != clu_count) {
 				ntfs_error(vol->sb,
 					   "Failed to free cluster allocation. Leaving inconsistent metadata.\n");
-				NVolSetErrors(vol);
+				err2 = freed < 0 ? freed : -EIO;
+				ntfs_report_file_metadata_error(VFS_I(ni), err2);
 				goto out;
 			}
 
@@ -5290,11 +5324,11 @@ 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(ni, 0, ni);
+			if (err2) {
 				ntfs_error(vol->sb,
 					   "Failed to restore mapping pairs after allocation rollback.\n");
-				NVolSetErrors(vol);
+				ntfs_report_file_metadata_error(VFS_I(ni), err2);
 			}
 		}
 	} else {
diff --git a/fs/ntfs/attrlist.c b/fs/ntfs/attrlist.c
index 1bbd2bc62c58..3c36e849a8ab 100644
--- a/fs/ntfs/attrlist.c
+++ b/fs/ntfs/attrlist.c
@@ -76,7 +76,7 @@ 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;
 	if (attr_ni->mft_no != FILE_MFT || !NInoNonResident(attr_ni) ||
 		min_alloc_size < 0)
 		return -EINVAL;
@@ -127,7 +127,9 @@ static int ntfs_attrlist_repack(struct inode *attr_vi,
 		new_rl_count = 2;
 
 	if (new_rl_count != 2) {
-		ntfs_cluster_free_from_rl(vol, new_rl);
+		free_err = ntfs_cluster_free_from_rl(vol, new_rl);
+		if (free_err)
+			ntfs_report_file_metadata_error(attr_vi, free_err);
 		kvfree(new_rl);
 		err = -ENOSPC;
 		goto out_free_data;
@@ -157,11 +159,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);
@@ -182,10 +185,12 @@ static int ntfs_attrlist_repack(struct inode *attr_vi,
 	if (restore_err) {
 		ntfs_error(vol->sb, "Failed to restore ATTRIBUTE_LIST mapping pairs (%d)",
 			   restore_err);
-		NVolSetErrors(vol);
+		ntfs_report_file_metadata_error(attr_vi, restore_err);
 	}
 
-	ntfs_cluster_free_from_rl(vol, new_rl);
+	free_err = ntfs_cluster_free_from_rl(vol, new_rl);
+	if (free_err)
+		ntfs_report_file_metadata_error(attr_vi, free_err);
 	kvfree(new_rl);
 	err = err ? err : restore_err;
 
diff --git a/fs/ntfs/bitmap.c b/fs/ntfs/bitmap.c
index 912fdcca8e01..54efcb054d0a 100644
--- a/fs/ntfs/bitmap.c
+++ b/fs/ntfs/bitmap.c
@@ -287,7 +287,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 827313df0e28..a7a7ecf9d074 100644
--- a/fs/ntfs/super.c
+++ b/fs/ntfs/super.c
@@ -402,6 +402,18 @@ 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)
+{
+	struct ntfs_inode *ni = NTFS_I(inode);
+
+	/* Attribute inodes share the file handle of their base inode. */
+	if (NInoAttr(ni))
+		inode = VFS_I(ni->ext.base_ntfs_ino);
+	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 5bcd0dd2a19d..366e9f5d0443 100644
--- a/fs/ntfs/volume.h
+++ b/fs/ntfs/volume.h
@@ -255,6 +255,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] 13+ messages in thread

* [PATCH v3 3/6] ntfs: report inode metadata errors to fsnotify
  2026-09-24  9:00 [PATCH v3 0/6] ntfs: report filesystem errors through fsnotify Baolin Liu
  2026-09-24  9:00 ` [PATCH v3 1/6] ntfs: report allocation metadata errors to fsnotify Baolin Liu
  2026-09-24  9:00 ` [PATCH v3 2/6] ntfs: report attribute " Baolin Liu
@ 2026-09-24  9:00 ` Baolin Liu
  2026-09-24  9:01 ` [PATCH v3 4/6] ntfs: report MFT metadata and recovery " Baolin Liu
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 13+ messages in thread
From: Baolin Liu @ 2026-09-24  9:00 UTC (permalink / raw)
  To: linkinjeon, hyc.lee; +Cc: ntfs, linux-kernel, Baolin Liu

From: Baolin Liu <liubaolin@kylinos.cn>

Inode initialization, writeback and LogFile failures currently mark the
volume as having errors without notifying filesystem health monitors.

Report these failures against the affected VFS inode, using the base
inode for attribute and index inodes. Preserve the existing treatment of
allocation failures, unsupported formats and interrupted operations.
Do not propagate reporting state between inode and attribute operations;
fanotify can merge queued error events.

Signed-off-by: Baolin Liu <liubaolin@kylinos.cn>
---
 fs/ntfs/inode.c   | 12 ++++++++----
 fs/ntfs/logfile.c |  2 +-
 2 files changed, 9 insertions(+), 5 deletions(-)

diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c
index 9583b2c6c7a2..591aab2850be 100644
--- a/fs/ntfs/inode.c
+++ b/fs/ntfs/inode.c
@@ -1257,7 +1257,7 @@ static int ntfs_read_locked_inode(struct inode *vi)
 		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;
 }
@@ -1486,7 +1486,7 @@ static int ntfs_read_locked_attr_inode(struct inode *base_vi, struct inode *vi)
 			base_ni->mft_no);
 	if (err != -ENOENT && err != -ENOMEM &&
 	    err != -EINTR && err != -ERESTARTSYS)
-		NVolSetErrors(vol);
+		ntfs_report_file_metadata_error(base_vi, err);
 	return err;
 }
 
@@ -1741,7 +1741,7 @@ static int ntfs_read_locked_index_inode(struct inode *base_vi, struct inode *vi)
 			err, ni->mft_no, ni->name_len);
 	if (err != -EOPNOTSUPP && err != -ENOMEM &&
 	    err != -EINTR && err != -ERESTARTSYS)
-		NVolSetErrors(vol);
+		ntfs_report_file_metadata_error(base_vi, err);
 	return err;
 }
 
@@ -2898,7 +2898,10 @@ 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 (err != -EINTR && err != -ERESTARTSYS)
+			ntfs_report_file_metadata_error(vi, err);
+		else
+			NVolSetErrors(ni->vol);
 	}
 	if (need_iput)
 		iput(vi);
@@ -2967,6 +2970,7 @@ static struct ntfs_inode *ntfs_extent_inode_open(struct ntfs_inode *base_ni,
 			    seq_no != le16_to_cpu(ni_mrec->sequence_number)) {
 				ntfs_error(sb, "Found stale extent mft reference mft=%llu",
 						ni->mft_no);
+				ntfs_report_file_metadata_error(VFS_I(base_ni), -EIO);
 				unmap_mft_record(ni);
 				return ERR_PTR(-EIO);
 			}
diff --git a/fs/ntfs/logfile.c b/fs/ntfs/logfile.c
index 1404664dacc0..6897faf849c1 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] 13+ messages in thread

* [PATCH v3 4/6] ntfs: report MFT metadata and recovery errors to fsnotify
  2026-09-24  9:00 [PATCH v3 0/6] ntfs: report filesystem errors through fsnotify Baolin Liu
                   ` (2 preceding siblings ...)
  2026-09-24  9:00 ` [PATCH v3 3/6] ntfs: report inode metadata " Baolin Liu
@ 2026-09-24  9:01 ` Baolin Liu
  2026-09-26  0:36   ` Hyunchul Lee
  2026-09-24  9:01 ` [PATCH v3 5/6] ntfs: report MFT writeback " Baolin Liu
                   ` (2 subsequent siblings)
  6 siblings, 1 reply; 13+ messages in thread
From: Baolin Liu @ 2026-09-24  9:01 UTC (permalink / raw)
  To: linkinjeon, hyc.lee; +Cc: ntfs, linux-kernel, Baolin Liu

From: Baolin Liu <liubaolin@kylinos.cn>

MFT record corruption, stale extent references and allocation bitmap
inconsistencies can leave files or volume metadata unusable without
notifying filesystem health monitors.

Report these errors against the affected base inode or the volume, as
appropriate. Also report failures to release clusters, restore mapping
pairs and attribute records, or restore MFT bitmap state during recovery.

Keep recovery errors separate from the original operation's return value.
Report each failed recovery where it is detected and leave event merging
to fanotify, without passing reporting state through the mapping APIs.

Signed-off-by: Baolin Liu <liubaolin@kylinos.cn>
---
 fs/ntfs/mft.c | 158 ++++++++++++++++++++++++++++++--------------------
 1 file changed, 96 insertions(+), 62 deletions(-)

diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c
index 8bb8b4085c8b..a1b1c080b256 100644
--- a/fs/ntfs/mft.c
+++ b/fs/ntfs/mft.c
@@ -112,6 +112,7 @@ int ntfs_mft_record_check(const struct ntfs_volume *vol, struct mft_record *m,
 static inline struct mft_record *map_mft_record_folio(struct ntfs_inode *ni)
 {
 	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;
@@ -170,7 +171,11 @@ 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);
 	}
 err_out:
 	ni->folio = NULL;
@@ -313,6 +318,7 @@ struct mft_record *map_extent_mft_record(struct ntfs_inode *base_ni, u64 mref,
 			unmap_mft_record(ni);
 			ntfs_error(base_ni->vol->sb,
 					"Found stale extent mft reference! Corrupt filesystem. Run chkdsk.");
+			ntfs_report_file_metadata_error(VFS_I(base_ni), -EIO);
 			return ERR_PTR(-EIO);
 		}
 map_err_out:
@@ -344,6 +350,7 @@ struct mft_record *map_extent_mft_record(struct ntfs_inode *base_ni, u64 mref,
 	if (seq_no && (le16_to_cpu(m->sequence_number) != seq_no)) {
 		ntfs_error(base_ni->vol->sb,
 				"Found stale extent mft reference! Corrupt filesystem. Run chkdsk.");
+		ntfs_report_file_metadata_error(VFS_I(base_ni), -EIO);
 		destroy_ni = true;
 		m = ERR_PTR(-EIO);
 		goto unm_nolock_err_out;
@@ -1340,8 +1347,9 @@ 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;
 	struct {
 		u8 added_cluster:1;
@@ -1426,10 +1434,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);
@@ -1549,9 +1558,10 @@ 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) {
 		ntfs_error(vol->sb,
 			"Failed to find last attribute extent of mft bitmap attribute.%s", es);
 		write_lock_irqsave(&mftbmp_ni->size_lock, flags);
@@ -1564,7 +1574,7 @@ 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);
+		ntfs_report_metadata_error(vol, err);
 		return ret;
 	}
 	a = ctx->attr;
@@ -1583,31 +1593,36 @@ 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(mftbmp_ni, 0,
+							    mftbmp_ni);
+		if (err) {
+			ntfs_error(vol->sb, "Failed to restore mapping pairs.%s", es);
+			ntfs_report_metadata_error(vol, err);
+		}
 	}
 	if (ctx)
 		ntfs_attr_put_search_ctx(ctx);
@@ -1640,7 +1655,7 @@ 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;
 
 	ntfs_debug("Extending mft bitmap initialized (and data) size.");
 	mft_ni = NTFS_I(vol->mft_ino);
@@ -1700,20 +1715,23 @@ static int ntfs_mft_bitmap_extend_initialized_nolock(struct ntfs_volume *vol)
 	mrec = map_mft_record(mft_ni);
 	if (IS_ERR(mrec)) {
 		ntfs_error(vol->sb, "Failed to map mft record.%s", es);
-		NVolSetErrors(vol);
+		err = PTR_ERR(mrec);
+		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);
+		ntfs_report_metadata_error(vol, err);
 put_err_out:
 		ntfs_attr_put_search_ctx(ctx);
 unm_err_out:
@@ -1767,6 +1785,7 @@ static int ntfs_mft_bitmap_extend_initialized_nolock(struct ntfs_volume *vol)
 static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
 {
 	s64 lcn;
+	s64 freed;
 	s64 old_last_vcn;
 	s64 min_nr, nr, ll;
 	unsigned long flags;
@@ -1775,8 +1794,9 @@ 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;
+	u16 mp_ofs;
 	bool mp_rebuilt = false, mp_extended = false;
 	size_t new_rl_count;
 
@@ -1863,10 +1883,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);
@@ -1986,8 +2007,9 @@ 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) {
 		ntfs_error(vol->sb,
 			"Failed to find last attribute extent of mft data attribute.%s", es);
 		write_lock_irqsave(&mft_ni->size_lock, flags);
@@ -2000,45 +2022,51 @@ 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);
+		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) {
+	freed = ntfs_cluster_free(mft_ni, old_last_vcn, -1, ctx);
+	if (freed < 0) {
 		ntfs_error(vol->sb, "Failed to free clusters from mft data attribute.%s", es);
-		NVolSetErrors(vol);
+		ntfs_report_metadata_error(vol, freed);
 	}
 
-	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(mft_ni, 0);
+		if (err) {
+			ntfs_error(vol->sb, "Failed to restore mapping pairs.%s",
+				   es);
+			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);
 	}
@@ -2270,8 +2298,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
@@ -2302,7 +2330,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;
@@ -2706,7 +2734,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;
 		}
 		/*
@@ -2862,9 +2890,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)
@@ -2901,7 +2932,7 @@ 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;
@@ -2995,8 +3026,11 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni)
 	memalloc_flags = memalloc_nofs_save();
 	if (base_ni->mft_no != FILE_MFT)
 		down_write(&vol->mftbmp_lock);
-	if (ntfs_bitmap_set_bit(vol->mftbmp_ino, mft_no))
+	rollback_err = ntfs_bitmap_set_bit(vol->mftbmp_ino, mft_no);
+	if (rollback_err) {
 		ntfs_error(vol->sb, "ntfs_bitmap_set_bit failed in bitmap_rollback\n");
+		ntfs_report_metadata_error(vol, rollback_err);
+	}
 	if (base_ni->mft_no != FILE_MFT)
 		up_write(&vol->mftbmp_lock);
 	memalloc_nofs_restore(memalloc_flags);
-- 
2.51.0


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v3 5/6] ntfs: report MFT writeback errors to fsnotify
  2026-09-24  9:00 [PATCH v3 0/6] ntfs: report filesystem errors through fsnotify Baolin Liu
                   ` (3 preceding siblings ...)
  2026-09-24  9:01 ` [PATCH v3 4/6] ntfs: report MFT metadata and recovery " Baolin Liu
@ 2026-09-24  9:01 ` Baolin Liu
  2026-09-24  9:01 ` [PATCH v3 6/6] ntfs: report shutdown " Baolin Liu
  2026-09-26  0:33 ` [PATCH v3 0/6] ntfs: report filesystem errors through fsnotify Hyunchul Lee
  6 siblings, 0 replies; 13+ messages in thread
From: Baolin Liu @ 2026-09-24  9:01 UTC (permalink / raw)
  To: linkinjeon, hyc.lee; +Cc: ntfs, linux-kernel, Baolin Liu

From: Baolin Liu <liubaolin@kylinos.cn>

MFT writeback failures need to reach filesystem health monitors as well
as the kernel log and mapping error state.

Report device errors from the existing parent BIO completion callback for
both synchronous and asynchronous writes, before ending folio writeback.
The generic fserror code queues notification work and takes its own inode
reference, so no additional NTFS completion work is needed.

Report mirror and preparation failures where they are detected, including
failures that occur before a BIO can be submitted. Keep these reports
together with completion reporting so errors retained in the write context
still mark the volume and notify userspace independently of device errors.
Preserve the retry handling for allocation failures in MFT write
preparation.

Signed-off-by: Baolin Liu <liubaolin@kylinos.cn>
---
 fs/ntfs/mft.c | 34 ++++++++++++++++++++++++----------
 1 file changed, 24 insertions(+), 10 deletions(-)

diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c
index a1b1c080b256..6a202ffc513a 100644
--- a/fs/ntfs/mft.c
+++ b/fs/ntfs/mft.c
@@ -481,9 +481,11 @@ static void ntfs_mft_end_io(struct bio *bio)
 		err = blk_status_to_errno(bio->bi_status);
 	else
 		err = ctx->error;
+	/* Report device errors from both synchronous and asynchronous writes. */
+	if (bio->bi_status)
+		ntfs_report_file_metadata_error(ctx->mapping->host, err);
 	if (err) {
 		mapping_set_error(ctx->mapping, err);
-		NVolSetErrors(ctx->vol);
 		ntfs_error(ctx->vol->sb, "I/O error while writing MFT: %d",
 			   err);
 	}
@@ -544,22 +546,24 @@ static int ntfs_sync_mft_mirror_unit(struct ntfs_volume *vol,
 	u64 mirror_size;
 	unsigned int mirror_ofs;
 	u8 *src, *dst;
-	int err;
+	int err = -EIO;
 
 	if (unlikely(!vol->mftmirr_ino))
-		return -EIO;
+		goto out_report;
 
 	mirror_size = (u64)vol->mftmirr_size * vol->mft_record_size;
 	if (mirror_file_ofs >= mirror_size ||
 	    unit->len > mirror_size - mirror_file_ofs)
-		return -EIO;
+		goto out_report;
 	if (unit->folio_ofs + unit->len > folio_size(source))
-		return -EIO;
+		goto out_report;
 
 	mirror = read_mapping_folio(vol->mftmirr_ino->i_mapping,
 				    mirror_file_ofs >> PAGE_SHIFT, NULL);
-	if (IS_ERR(mirror))
-		return PTR_ERR(mirror);
+	if (IS_ERR(mirror)) {
+		err = PTR_ERR(mirror);
+		goto out_report;
+	}
 
 	folio_lock(mirror);
 	if (folio_test_writeback(mirror))
@@ -598,6 +602,9 @@ static int ntfs_sync_mft_mirror_unit(struct ntfs_volume *vol,
 out_unlock:
 	folio_unlock(mirror);
 	folio_put(mirror);
+out_report:
+	if (err)
+		ntfs_report_metadata_error(vol, err);
 	return err;
 }
 
@@ -815,8 +822,14 @@ 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);
+	} else {
+		struct ntfs_inode *base_ni = ni;
+
+		if (ni->nr_extents < 0)
+			base_ni = ni->ext.base_ntfs_ino;
+		mapping_set_error(folio->mapping, err);
+		ntfs_report_file_metadata_error(VFS_I(base_ni), err);
+	}
 	return err;
 }
 
@@ -3188,7 +3201,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);
 }
 
@@ -3346,6 +3359,7 @@ static int ntfs_write_mft_block(struct folio *folio, struct writeback_control *w
 						ctx->error = -EIO;
 					redirty = true;
 					err = -EIO;
+					ntfs_mft_write_error(vol, mapping, err);
 					break;
 				}
 			}
-- 
2.51.0


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v3 6/6] ntfs: report shutdown errors to fsnotify
  2026-09-24  9:00 [PATCH v3 0/6] ntfs: report filesystem errors through fsnotify Baolin Liu
                   ` (4 preceding siblings ...)
  2026-09-24  9:01 ` [PATCH v3 5/6] ntfs: report MFT writeback " Baolin Liu
@ 2026-09-24  9:01 ` Baolin Liu
  2026-09-26  0:40   ` Hyunchul Lee
  2026-09-26  0:33 ` [PATCH v3 0/6] ntfs: report filesystem errors through fsnotify Hyunchul Lee
  6 siblings, 1 reply; 13+ messages in thread
From: Baolin Liu @ 2026-09-24  9:01 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 a7a7ecf9d074..a0d3934ef6c8 100644
--- a/fs/ntfs/super.c
+++ b/fs/ntfs/super.c
@@ -414,6 +414,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);
@@ -430,7 +436,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;
 		}
 	}
@@ -2086,10 +2092,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 366e9f5d0443..648fe8991304 100644
--- a/fs/ntfs/volume.h
+++ b/fs/ntfs/volume.h
@@ -256,6 +256,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] 13+ messages in thread

* Re: [PATCH v3 0/6] ntfs: report filesystem errors through fsnotify
  2026-09-24  9:00 [PATCH v3 0/6] ntfs: report filesystem errors through fsnotify Baolin Liu
                   ` (5 preceding siblings ...)
  2026-09-24  9:01 ` [PATCH v3 6/6] ntfs: report shutdown " Baolin Liu
@ 2026-09-26  0:33 ` Hyunchul Lee
  2026-09-26 13:49   ` liubaolin
  6 siblings, 1 reply; 13+ messages in thread
From: Hyunchul Lee @ 2026-09-26  0:33 UTC (permalink / raw)
  To: Baolin Liu; +Cc: linkinjeon, ntfs, linux-kernel, Baolin Liu

Hi Baolin,

2026년 9월 24일 (목) 오후 6:02, Baolin Liu <liubaolin12138@163.com>님이 작성:
>
> From: Baolin Liu <liubaolin@kylinos.cn>
>
> Report NTFS metadata and shutdown errors through the generic filesystem
> error notification infrastructure. Use file-level events for errors

There appear to be several error paths that identify corruption
but don't call any report_*_error(). For example:

* ntfs_lookup_inode_by_example(), ntfs_ib_read(),
  ntfs_read_iomap_begin_non_resident(), and etc.

> associated with an affected inode, and volume-level events for volume
> metadata and shutdown errors. Preserve the volume error flags and report
> failed recovery independently of the error that triggered it.
>
> Report errors where they are detected and leave queued event merging to
> fanotify. No error_reported state is carried through search contexts,
> mapping-pairs updates or MFT mappings, so releasing a context cannot lose
> that state or suppress a later rollback or restore failure.
>
> Changes since v2:
> - Follow Hyunchul Lee's suggestion to remove error_reported tracking and
>   the reporting-aware mapping APIs throughout the series.
> - Report independent cluster, mapping-pairs, ATTRIBUTE_LIST and MFT
>   bitmap recovery failures while preserving the original operation errno.
> - Base the series on ntfs-next at 401898d748fc ("ntfs: validate
>   attribute-list sizes before allocation"). Retain its MFT parent BIO
>   and folio writeback implementation. Report synchronous and asynchronous
>   device errors in the existing completion callback; fserror itself
>   defers notification work and holds the inode reference. No additional
>   NTFS completion workqueue or pending-I/O counter is introduced.
> - Cover stale extent references, failed extent-record cleanup, mirror
>   failures and MFT preparation errors. Resolve attribute-inode reports
>   to their base inode and keep cluster-free counts in 64-bit variables.
> - Split MFT reporting into two patches: metadata validation, allocation
>   and recovery in patch 4; write preparation, mirror and BIO completion
>   errors in patch 5. This keeps the two review topics separate.
>   Shutdown reporting is now patch 6.
>
> Testing on this base:
> - QEMU passed mount/read/write/fsync/truncate/xattr/rename/unlink, MFT
>   growth and unmount without FAN_FS_ERROR events during normal operation.
> - Verified EIO with a valid FID for file MFT corruption, EUCLEAN without
>   a valid FID for an MFT bitmap mismatch, and ESHUTDOWN for forced
>   shutdown.
> - Fault injection of an initial mapping-pairs error followed by an
>   independent restore failure produced a merged FAN_FS_ERROR event with
>   error_count=2. Synchronous and asynchronous MFT completion failures
>   also generated notifications. The injection code is test-only and is
>   excluded from the series.
>
> Baolin Liu (6):
>   ntfs: report allocation metadata errors to fsnotify
>   ntfs: report attribute errors to fsnotify
>   ntfs: report inode metadata errors to fsnotify
>   ntfs: report MFT metadata and recovery errors to fsnotify
>   ntfs: report MFT writeback errors to fsnotify
>   ntfs: report shutdown errors to fsnotify
>
>  fs/ntfs/attrib.c   |  84 ++++++++++++++------
>  fs/ntfs/attrlist.c |  17 ++--
>  fs/ntfs/bitmap.c   |   2 +-
>  fs/ntfs/inode.c    |  12 ++-
>  fs/ntfs/lcnalloc.c |   4 +-
>  fs/ntfs/logfile.c  |   2 +-
>  fs/ntfs/mft.c      | 192 ++++++++++++++++++++++++++++-----------------
>  fs/ntfs/super.c    |  32 +++++++-
>  fs/ntfs/volume.h   |   4 +
>  9 files changed, 235 insertions(+), 114 deletions(-)
>
> --
> 2.51.0
>


--
Thanks,
Hyunchul

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v3 4/6] ntfs: report MFT metadata and recovery errors to fsnotify
  2026-09-24  9:01 ` [PATCH v3 4/6] ntfs: report MFT metadata and recovery " Baolin Liu
@ 2026-09-26  0:36   ` Hyunchul Lee
  2026-09-26 12:43     ` liubaolin
  0 siblings, 1 reply; 13+ messages in thread
From: Hyunchul Lee @ 2026-09-26  0:36 UTC (permalink / raw)
  To: Baolin Liu; +Cc: linkinjeon, ntfs, linux-kernel, Baolin Liu

2026년 9월 24일 (목) 오후 6:02, Baolin Liu <liubaolin12138@163.com>님이 작성:
>
> From: Baolin Liu <liubaolin@kylinos.cn>
>
> MFT record corruption, stale extent references and allocation bitmap
> inconsistencies can leave files or volume metadata unusable without
> notifying filesystem health monitors.
>
> Report these errors against the affected base inode or the volume, as
> appropriate. Also report failures to release clusters, restore mapping
> pairs and attribute records, or restore MFT bitmap state during recovery.
>
> Keep recovery errors separate from the original operation's return value.
> Report each failed recovery where it is detected and leave event merging
> to fanotify, without passing reporting state through the mapping APIs.
>
> Signed-off-by: Baolin Liu <liubaolin@kylinos.cn>
> ---
>  fs/ntfs/mft.c | 158 ++++++++++++++++++++++++++++++--------------------
>  1 file changed, 96 insertions(+), 62 deletions(-)
>
> diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c
> index 8bb8b4085c8b..a1b1c080b256 100644
> --- a/fs/ntfs/mft.c
> +++ b/fs/ntfs/mft.c
> @@ -112,6 +112,7 @@ int ntfs_mft_record_check(const struct ntfs_volume *vol, struct mft_record *m,
>  static inline struct mft_record *map_mft_record_folio(struct ntfs_inode *ni)
>  {
>         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;
> @@ -170,7 +171,11 @@ 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;

The above pattern is repeated in the series, but
there is already the helper, ntfs_inode_base().

Could we use it from the call sites?


> +               ntfs_report_file_metadata_error(VFS_I(base_ni), -EIO);
>         }
>  err_out:
>         ni->folio = NULL;
> @@ -313,6 +318,7 @@ struct mft_record *map_extent_mft_record(struct ntfs_inode *base_ni, u64 mref,
>                         unmap_mft_record(ni);
>                         ntfs_error(base_ni->vol->sb,
>                                         "Found stale extent mft reference! Corrupt filesystem. Run chkdsk.");
> +                       ntfs_report_file_metadata_error(VFS_I(base_ni), -EIO);
>                         return ERR_PTR(-EIO);
>                 }
>  map_err_out:
> @@ -344,6 +350,7 @@ struct mft_record *map_extent_mft_record(struct ntfs_inode *base_ni, u64 mref,
>         if (seq_no && (le16_to_cpu(m->sequence_number) != seq_no)) {
>                 ntfs_error(base_ni->vol->sb,
>                                 "Found stale extent mft reference! Corrupt filesystem. Run chkdsk.");
> +               ntfs_report_file_metadata_error(VFS_I(base_ni), -EIO);
>                 destroy_ni = true;
>                 m = ERR_PTR(-EIO);
>                 goto unm_nolock_err_out;
> @@ -1340,8 +1347,9 @@ 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;
>         struct {
>                 u8 added_cluster:1;
> @@ -1426,10 +1434,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);
> @@ -1549,9 +1558,10 @@ 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) {
>                 ntfs_error(vol->sb,
>                         "Failed to find last attribute extent of mft bitmap attribute.%s", es);
>                 write_lock_irqsave(&mftbmp_ni->size_lock, flags);
> @@ -1564,7 +1574,7 @@ 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);
> +               ntfs_report_metadata_error(vol, err);
>                 return ret;
>         }
>         a = ctx->attr;
> @@ -1583,31 +1593,36 @@ 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(mftbmp_ni, 0,
> +                                                           mftbmp_ni);
> +               if (err) {
> +                       ntfs_error(vol->sb, "Failed to restore mapping pairs.%s", es);
> +                       ntfs_report_metadata_error(vol, err);
> +               }
>         }
>         if (ctx)
>                 ntfs_attr_put_search_ctx(ctx);
> @@ -1640,7 +1655,7 @@ 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;
>
>         ntfs_debug("Extending mft bitmap initialized (and data) size.");
>         mft_ni = NTFS_I(vol->mft_ino);
> @@ -1700,20 +1715,23 @@ static int ntfs_mft_bitmap_extend_initialized_nolock(struct ntfs_volume *vol)
>         mrec = map_mft_record(mft_ni);
>         if (IS_ERR(mrec)) {
>                 ntfs_error(vol->sb, "Failed to map mft record.%s", es);
> -               NVolSetErrors(vol);
> +               err = PTR_ERR(mrec);
> +               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);
> +               ntfs_report_metadata_error(vol, err);
>  put_err_out:
>                 ntfs_attr_put_search_ctx(ctx);
>  unm_err_out:
> @@ -1767,6 +1785,7 @@ static int ntfs_mft_bitmap_extend_initialized_nolock(struct ntfs_volume *vol)
>  static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
>  {
>         s64 lcn;
> +       s64 freed;
>         s64 old_last_vcn;
>         s64 min_nr, nr, ll;
>         unsigned long flags;
> @@ -1775,8 +1794,9 @@ 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;
> +       u16 mp_ofs;
>         bool mp_rebuilt = false, mp_extended = false;
>         size_t new_rl_count;
>
> @@ -1863,10 +1883,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);
> @@ -1986,8 +2007,9 @@ 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) {
>                 ntfs_error(vol->sb,
>                         "Failed to find last attribute extent of mft data attribute.%s", es);
>                 write_lock_irqsave(&mft_ni->size_lock, flags);
> @@ -2000,45 +2022,51 @@ 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);
> +               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) {
> +       freed = ntfs_cluster_free(mft_ni, old_last_vcn, -1, ctx);
> +       if (freed < 0) {
>                 ntfs_error(vol->sb, "Failed to free clusters from mft data attribute.%s", es);
> -               NVolSetErrors(vol);
> +               ntfs_report_metadata_error(vol, freed);
>         }
>
> -       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(mft_ni, 0);
> +               if (err) {
> +                       ntfs_error(vol->sb, "Failed to restore mapping pairs.%s",
> +                                  es);
> +                       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);
>         }
> @@ -2270,8 +2298,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
> @@ -2302,7 +2330,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;
> @@ -2706,7 +2734,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;
>                 }
>                 /*
> @@ -2862,9 +2890,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)
> @@ -2901,7 +2932,7 @@ 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;
> @@ -2995,8 +3026,11 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni)
>         memalloc_flags = memalloc_nofs_save();
>         if (base_ni->mft_no != FILE_MFT)
>                 down_write(&vol->mftbmp_lock);
> -       if (ntfs_bitmap_set_bit(vol->mftbmp_ino, mft_no))
> +       rollback_err = ntfs_bitmap_set_bit(vol->mftbmp_ino, mft_no);
> +       if (rollback_err) {
>                 ntfs_error(vol->sb, "ntfs_bitmap_set_bit failed in bitmap_rollback\n");
> +               ntfs_report_metadata_error(vol, rollback_err);
> +       }
>         if (base_ni->mft_no != FILE_MFT)
>                 up_write(&vol->mftbmp_lock);
>         memalloc_nofs_restore(memalloc_flags);
> --
> 2.51.0
>


--
Thanks,
Hyunchul

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v3 6/6] ntfs: report shutdown errors to fsnotify
  2026-09-24  9:01 ` [PATCH v3 6/6] ntfs: report shutdown " Baolin Liu
@ 2026-09-26  0:40   ` Hyunchul Lee
  2026-09-26 12:52     ` liubaolin
  0 siblings, 1 reply; 13+ messages in thread
From: Hyunchul Lee @ 2026-09-26  0:40 UTC (permalink / raw)
  To: Baolin Liu; +Cc: linkinjeon, ntfs, linux-kernel, Baolin Liu

2026년 9월 24일 (목) 오후 6:02, Baolin Liu <liubaolin12138@163.com>님이 작성:
>
> 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 a7a7ecf9d074..a0d3934ef6c8 100644
> --- a/fs/ntfs/super.c
> +++ b/fs/ntfs/super.c
> @@ -414,6 +414,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);

Could we call test_and_set(NV_Shutdown, &vol->flags)
to avoid races?


> +       fserror_report_shutdown(vol->sb, GFP_ATOMIC);
> +}

we can avoid test_and_set() to prevent race


> +
>  void ntfs_handle_error(struct super_block *sb)
>  {
>         struct ntfs_volume *vol = NTFS_SB(sb);
> @@ -430,7 +436,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;
>                 }
>         }
> @@ -2086,10 +2092,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 366e9f5d0443..648fe8991304 100644
> --- a/fs/ntfs/volume.h
> +++ b/fs/ntfs/volume.h
> @@ -256,6 +256,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
>


--
Thanks,
Hyunchul

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v3 4/6] ntfs: report MFT metadata and recovery errors to fsnotify
  2026-09-26  0:36   ` Hyunchul Lee
@ 2026-09-26 12:43     ` liubaolin
  0 siblings, 0 replies; 13+ messages in thread
From: liubaolin @ 2026-09-26 12:43 UTC (permalink / raw)
  To: Hyunchul Lee; +Cc: linkinjeon, ntfs, linux-kernel, Baolin Liu



在 2026/9/26 08:36, Hyunchul Lee 写道:
> 2026년 9월 24일 (목) 오후 6:02, Baolin Liu <liubaolin12138@163.com>님이 작성:
>>
>> From: Baolin Liu <liubaolin@kylinos.cn>
>>
>> MFT record corruption, stale extent references and allocation bitmap
>> inconsistencies can leave files or volume metadata unusable without
>> notifying filesystem health monitors.
>>
>> Report these errors against the affected base inode or the volume, as
>> appropriate. Also report failures to release clusters, restore mapping
>> pairs and attribute records, or restore MFT bitmap state during recovery.
>>
>> Keep recovery errors separate from the original operation's return value.
>> Report each failed recovery where it is detected and leave event merging
>> to fanotify, without passing reporting state through the mapping APIs.
>>
>> Signed-off-by: Baolin Liu <liubaolin@kylinos.cn>
>> ---
>>   fs/ntfs/mft.c | 158 ++++++++++++++++++++++++++++++--------------------
>>   1 file changed, 96 insertions(+), 62 deletions(-)
>>
>> diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c
>> index 8bb8b4085c8b..a1b1c080b256 100644
>> --- a/fs/ntfs/mft.c
>> +++ b/fs/ntfs/mft.c
>> @@ -112,6 +112,7 @@ int ntfs_mft_record_check(const struct ntfs_volume *vol, struct mft_record *m,
>>   static inline struct mft_record *map_mft_record_folio(struct ntfs_inode *ni)
>>   {
>>          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;
>> @@ -170,7 +171,11 @@ 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;
> 
> The above pattern is repeated in the series, but
> there is already the helper, ntfs_inode_base().
> 
> Could we use it from the call sites?

Hi Hyunchul,
   Thank you for reviewing the patches and pointing this out. I agree 
that we should reuse ntfs_inode_base() at these call sites. I'll make 
this change in v4 as suggested.

Thanks,
Baolin.
> 
>> +               ntfs_report_file_metadata_error(VFS_I(base_ni), -EIO);
>>          }
>>   err_out:
>>          ni->folio = NULL;
>> @@ -313,6 +318,7 @@ struct mft_record *map_extent_mft_record(struct ntfs_inode *base_ni, u64 mref,
>>                          unmap_mft_record(ni);
>>                          ntfs_error(base_ni->vol->sb,
>>                                          "Found stale extent mft reference! Corrupt filesystem. Run chkdsk.");
>> +                       ntfs_report_file_metadata_error(VFS_I(base_ni), -EIO);
>>                          return ERR_PTR(-EIO);
>>                  }
>>   map_err_out:
>> @@ -344,6 +350,7 @@ struct mft_record *map_extent_mft_record(struct ntfs_inode *base_ni, u64 mref,
>>          if (seq_no && (le16_to_cpu(m->sequence_number) != seq_no)) {
>>                  ntfs_error(base_ni->vol->sb,
>>                                  "Found stale extent mft reference! Corrupt filesystem. Run chkdsk.");
>> +               ntfs_report_file_metadata_error(VFS_I(base_ni), -EIO);
>>                  destroy_ni = true;
>>                  m = ERR_PTR(-EIO);
>>                  goto unm_nolock_err_out;
>> @@ -1340,8 +1347,9 @@ 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;
>>          struct {
>>                  u8 added_cluster:1;
>> @@ -1426,10 +1434,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);
>> @@ -1549,9 +1558,10 @@ 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) {
>>                  ntfs_error(vol->sb,
>>                          "Failed to find last attribute extent of mft bitmap attribute.%s", es);
>>                  write_lock_irqsave(&mftbmp_ni->size_lock, flags);
>> @@ -1564,7 +1574,7 @@ 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);
>> +               ntfs_report_metadata_error(vol, err);
>>                  return ret;
>>          }
>>          a = ctx->attr;
>> @@ -1583,31 +1593,36 @@ 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(mftbmp_ni, 0,
>> +                                                           mftbmp_ni);
>> +               if (err) {
>> +                       ntfs_error(vol->sb, "Failed to restore mapping pairs.%s", es);
>> +                       ntfs_report_metadata_error(vol, err);
>> +               }
>>          }
>>          if (ctx)
>>                  ntfs_attr_put_search_ctx(ctx);
>> @@ -1640,7 +1655,7 @@ 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;
>>
>>          ntfs_debug("Extending mft bitmap initialized (and data) size.");
>>          mft_ni = NTFS_I(vol->mft_ino);
>> @@ -1700,20 +1715,23 @@ static int ntfs_mft_bitmap_extend_initialized_nolock(struct ntfs_volume *vol)
>>          mrec = map_mft_record(mft_ni);
>>          if (IS_ERR(mrec)) {
>>                  ntfs_error(vol->sb, "Failed to map mft record.%s", es);
>> -               NVolSetErrors(vol);
>> +               err = PTR_ERR(mrec);
>> +               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);
>> +               ntfs_report_metadata_error(vol, err);
>>   put_err_out:
>>                  ntfs_attr_put_search_ctx(ctx);
>>   unm_err_out:
>> @@ -1767,6 +1785,7 @@ static int ntfs_mft_bitmap_extend_initialized_nolock(struct ntfs_volume *vol)
>>   static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
>>   {
>>          s64 lcn;
>> +       s64 freed;
>>          s64 old_last_vcn;
>>          s64 min_nr, nr, ll;
>>          unsigned long flags;
>> @@ -1775,8 +1794,9 @@ 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;
>> +       u16 mp_ofs;
>>          bool mp_rebuilt = false, mp_extended = false;
>>          size_t new_rl_count;
>>
>> @@ -1863,10 +1883,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);
>> @@ -1986,8 +2007,9 @@ 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) {
>>                  ntfs_error(vol->sb,
>>                          "Failed to find last attribute extent of mft data attribute.%s", es);
>>                  write_lock_irqsave(&mft_ni->size_lock, flags);
>> @@ -2000,45 +2022,51 @@ 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);
>> +               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) {
>> +       freed = ntfs_cluster_free(mft_ni, old_last_vcn, -1, ctx);
>> +       if (freed < 0) {
>>                  ntfs_error(vol->sb, "Failed to free clusters from mft data attribute.%s", es);
>> -               NVolSetErrors(vol);
>> +               ntfs_report_metadata_error(vol, freed);
>>          }
>>
>> -       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(mft_ni, 0);
>> +               if (err) {
>> +                       ntfs_error(vol->sb, "Failed to restore mapping pairs.%s",
>> +                                  es);
>> +                       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);
>>          }
>> @@ -2270,8 +2298,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
>> @@ -2302,7 +2330,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;
>> @@ -2706,7 +2734,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;
>>                  }
>>                  /*
>> @@ -2862,9 +2890,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)
>> @@ -2901,7 +2932,7 @@ 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;
>> @@ -2995,8 +3026,11 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni)
>>          memalloc_flags = memalloc_nofs_save();
>>          if (base_ni->mft_no != FILE_MFT)
>>                  down_write(&vol->mftbmp_lock);
>> -       if (ntfs_bitmap_set_bit(vol->mftbmp_ino, mft_no))
>> +       rollback_err = ntfs_bitmap_set_bit(vol->mftbmp_ino, mft_no);
>> +       if (rollback_err) {
>>                  ntfs_error(vol->sb, "ntfs_bitmap_set_bit failed in bitmap_rollback\n");
>> +               ntfs_report_metadata_error(vol, rollback_err);
>> +       }
>>          if (base_ni->mft_no != FILE_MFT)
>>                  up_write(&vol->mftbmp_lock);
>>          memalloc_nofs_restore(memalloc_flags);
>> --
>> 2.51.0
>>
> 
> 
> --
> Thanks,
> Hyunchul


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v3 6/6] ntfs: report shutdown errors to fsnotify
  2026-09-26  0:40   ` Hyunchul Lee
@ 2026-09-26 12:52     ` liubaolin
  0 siblings, 0 replies; 13+ messages in thread
From: liubaolin @ 2026-09-26 12:52 UTC (permalink / raw)
  To: Hyunchul Lee; +Cc: linkinjeon, ntfs, linux-kernel, Baolin Liu



在 2026/9/26 08:40, Hyunchul Lee 写道:
> 2026년 9월 24일 (목) 오후 6:02, Baolin Liu <liubaolin12138@163.com>님이 작성:
>>
>> 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 a7a7ecf9d074..a0d3934ef6c8 100644
>> --- a/fs/ntfs/super.c
>> +++ b/fs/ntfs/super.c
>> @@ -414,6 +414,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);
> 
> Could we call test_and_set(NV_Shutdown, &vol->flags)
> to avoid races?

Hi Hyunchul,
    Thank you for pointing this out. I'll use test_and_set_bit() in v4 
to avoid duplicate shutdown notifications from concurrent callers.

Thanks,
Baolin.

> 
> 
>> +       fserror_report_shutdown(vol->sb, GFP_ATOMIC);
>> +}
> 
> we can avoid test_and_set() to prevent race
> 
> 
>> +
>>   void ntfs_handle_error(struct super_block *sb)
>>   {
>>          struct ntfs_volume *vol = NTFS_SB(sb);
>> @@ -430,7 +436,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;
>>                  }
>>          }
>> @@ -2086,10 +2092,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 366e9f5d0443..648fe8991304 100644
>> --- a/fs/ntfs/volume.h
>> +++ b/fs/ntfs/volume.h
>> @@ -256,6 +256,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
>>
> 
> 
> --
> Thanks,
> Hyunchul


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v3 0/6] ntfs: report filesystem errors through fsnotify
  2026-09-26  0:33 ` [PATCH v3 0/6] ntfs: report filesystem errors through fsnotify Hyunchul Lee
@ 2026-09-26 13:49   ` liubaolin
  0 siblings, 0 replies; 13+ messages in thread
From: liubaolin @ 2026-09-26 13:49 UTC (permalink / raw)
  To: Hyunchul Lee; +Cc: linkinjeon, ntfs, linux-kernel, Baolin Liu



在 2026/9/26 08:33, Hyunchul Lee 写道:
> Hi Baolin,
> 
> 2026년 9월 24일 (목) 오후 6:02, Baolin Liu <liubaolin12138@163.com>님이 작성:
>>
>> From: Baolin Liu <liubaolin@kylinos.cn>
>>
>> Report NTFS metadata and shutdown errors through the generic filesystem
>> error notification infrastructure. Use file-level events for errors
> 
> There appear to be several error paths that identify corruption
> but don't call any report_*_error(). For example:
> 
> * ntfs_lookup_inode_by_example(), ntfs_ib_read(),
>    ntfs_read_iomap_begin_non_resident(), and etc.
> 

Hi Hyunchul,
   Thank you for the suggestion. I agree that these corruption paths 
should be covered.
   I'll take some time to thoroughly review the remaining error paths 
for similar omissions and add the missing reports in v4.

Thanks,
Baolin.

>> associated with an affected inode, and volume-level events for volume
>> metadata and shutdown errors. Preserve the volume error flags and report
>> failed recovery independently of the error that triggered it.
>>
>> Report errors where they are detected and leave queued event merging to
>> fanotify. No error_reported state is carried through search contexts,
>> mapping-pairs updates or MFT mappings, so releasing a context cannot lose
>> that state or suppress a later rollback or restore failure.
>>
>> Changes since v2:
>> - Follow Hyunchul Lee's suggestion to remove error_reported tracking and
>>    the reporting-aware mapping APIs throughout the series.
>> - Report independent cluster, mapping-pairs, ATTRIBUTE_LIST and MFT
>>    bitmap recovery failures while preserving the original operation errno.
>> - Base the series on ntfs-next at 401898d748fc ("ntfs: validate
>>    attribute-list sizes before allocation"). Retain its MFT parent BIO
>>    and folio writeback implementation. Report synchronous and asynchronous
>>    device errors in the existing completion callback; fserror itself
>>    defers notification work and holds the inode reference. No additional
>>    NTFS completion workqueue or pending-I/O counter is introduced.
>> - Cover stale extent references, failed extent-record cleanup, mirror
>>    failures and MFT preparation errors. Resolve attribute-inode reports
>>    to their base inode and keep cluster-free counts in 64-bit variables.
>> - Split MFT reporting into two patches: metadata validation, allocation
>>    and recovery in patch 4; write preparation, mirror and BIO completion
>>    errors in patch 5. This keeps the two review topics separate.
>>    Shutdown reporting is now patch 6.
>>
>> Testing on this base:
>> - QEMU passed mount/read/write/fsync/truncate/xattr/rename/unlink, MFT
>>    growth and unmount without FAN_FS_ERROR events during normal operation.
>> - Verified EIO with a valid FID for file MFT corruption, EUCLEAN without
>>    a valid FID for an MFT bitmap mismatch, and ESHUTDOWN for forced
>>    shutdown.
>> - Fault injection of an initial mapping-pairs error followed by an
>>    independent restore failure produced a merged FAN_FS_ERROR event with
>>    error_count=2. Synchronous and asynchronous MFT completion failures
>>    also generated notifications. The injection code is test-only and is
>>    excluded from the series.
>>
>> Baolin Liu (6):
>>    ntfs: report allocation metadata errors to fsnotify
>>    ntfs: report attribute errors to fsnotify
>>    ntfs: report inode metadata errors to fsnotify
>>    ntfs: report MFT metadata and recovery errors to fsnotify
>>    ntfs: report MFT writeback errors to fsnotify
>>    ntfs: report shutdown errors to fsnotify
>>
>>   fs/ntfs/attrib.c   |  84 ++++++++++++++------
>>   fs/ntfs/attrlist.c |  17 ++--
>>   fs/ntfs/bitmap.c   |   2 +-
>>   fs/ntfs/inode.c    |  12 ++-
>>   fs/ntfs/lcnalloc.c |   4 +-
>>   fs/ntfs/logfile.c  |   2 +-
>>   fs/ntfs/mft.c      | 192 ++++++++++++++++++++++++++++-----------------
>>   fs/ntfs/super.c    |  32 +++++++-
>>   fs/ntfs/volume.h   |   4 +
>>   9 files changed, 235 insertions(+), 114 deletions(-)
>>
>> --
>> 2.51.0
>>
> 
> 
> --
> Thanks,
> Hyunchul


^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2026-09-26 13:49 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-24  9:00 [PATCH v3 0/6] ntfs: report filesystem errors through fsnotify Baolin Liu
2026-09-24  9:00 ` [PATCH v3 1/6] ntfs: report allocation metadata errors to fsnotify Baolin Liu
2026-09-24  9:00 ` [PATCH v3 2/6] ntfs: report attribute " Baolin Liu
2026-09-24  9:00 ` [PATCH v3 3/6] ntfs: report inode metadata " Baolin Liu
2026-09-24  9:01 ` [PATCH v3 4/6] ntfs: report MFT metadata and recovery " Baolin Liu
2026-09-26  0:36   ` Hyunchul Lee
2026-09-26 12:43     ` liubaolin
2026-09-24  9:01 ` [PATCH v3 5/6] ntfs: report MFT writeback " Baolin Liu
2026-09-24  9:01 ` [PATCH v3 6/6] ntfs: report shutdown " Baolin Liu
2026-09-26  0:40   ` Hyunchul Lee
2026-09-26 12:52     ` liubaolin
2026-09-26  0:33 ` [PATCH v3 0/6] ntfs: report filesystem errors through fsnotify Hyunchul Lee
2026-09-26 13:49   ` 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®