mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Baolin Liu <liubaolin12138@163.com>
To: linkinjeon@kernel.org, hyc.lee@gmail.com
Cc: ntfs@lists.linux.dev, linux-kernel@vger.kernel.org,
	Baolin Liu <liubaolin@kylinos.cn>
Subject: [PATCH v2 4/5] ntfs: report MFT errors to fsnotify
Date: Wed, 16 Sep 2026 10:23:44 +0800	[thread overview]
Message-ID: <20260916022345.1151836-5-liubaolin12138@163.com> (raw)
In-Reply-To: <20260916022345.1151836-1-liubaolin12138@163.com>

From: Baolin Liu <liubaolin@kylinos.cn>

Report MFT validation, mapping, allocation, rollback, and writeback
failures through the appropriate volume-level or file-level helper.
Add reporting-aware mapping APIs and propagate their state to callers.
Report asynchronous MFT writeback errors from I/O completion.

Signed-off-by: Baolin Liu <liubaolin@kylinos.cn>
---
 fs/ntfs/attrib.c |  19 ++--
 fs/ntfs/inode.c  |  10 +-
 fs/ntfs/mft.c    | 252 +++++++++++++++++++++++++++++++----------------
 fs/ntfs/mft.h    |   6 ++
 4 files changed, 191 insertions(+), 96 deletions(-)

diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c
index 136bda6433bb..c847bffd0bca 100644
--- a/fs/ntfs/attrib.c
+++ b/fs/ntfs/attrib.c
@@ -94,6 +94,7 @@ int ntfs_map_runlist_nolock(struct ntfs_inode *ni, s64 vcn, struct ntfs_attr_sea
 	struct folio *put_this_folio = NULL;
 	int err = 0;
 	bool ctx_is_temporary = false, ctx_needs_reset = false;
+	bool mft_error_reported = false;
 	struct ntfs_attr_search_ctx old_ctx = { NULL, };
 	size_t new_rl_count;
 
@@ -238,7 +239,10 @@ int ntfs_map_runlist_nolock(struct ntfs_inode *ni, s64 vcn, struct ntfs_attr_sea
 				if (old_ctx.base_ntfs_ino &&
 				    old_ctx.ntfs_ino !=	old_ctx.base_ntfs_ino) {
 retry_map:
-					ctx->mrec = map_mft_record(old_ctx.ntfs_ino);
+					ctx->mrec = map_mft_record_reported(old_ctx.ntfs_ino,
+									    &mft_error_reported);
+					if (mft_error_reported)
+						old_ctx.error_reported = true;
 					/*
 					 * Something bad has happened.  If out
 					 * of memory retry till it succeeds.
@@ -1163,7 +1167,7 @@ static int ntfs_external_attr_find(const __le32 type,
 	__le16 *al_name;
 	u32 al_name_len;
 	u32 attr_len, mft_free_len;
-	bool is_first_search = false;
+	bool error_reported = false, is_first_search = false;
 	int err = 0;
 	static const char *es = " Unmount and run chkdsk.";
 
@@ -1382,10 +1386,12 @@ static int ntfs_external_attr_find(const __le32 type,
 				ctx->mrec = ctx->base_mrec;
 				ctx->mapped_mrec = ctx->mapped_base_mrec;
 			} else {
+				u64 mref = le64_to_cpu(al_entry->mft_reference);
+
 				/* We want an extent record. */
-				ctx->mrec = map_extent_mft_record(base_ni,
-						le64_to_cpu(
-						al_entry->mft_reference), &ni);
+				ctx->mrec = map_extent_mft_record_reported(base_ni, mref,
+									   &ni,
+									   &error_reported);
 				if (IS_ERR(ctx->mrec)) {
 					ntfs_error(vol->sb,
 							"Failed to map extent mft record 0x%lx of base inode 0x%llx.%s",
@@ -1511,7 +1517,8 @@ static int ntfs_external_attr_find(const __le32 type,
 
 	if (err != -ENOMEM) {
 		if (err != -EINTR && err != -ERESTARTSYS) {
-			ntfs_report_file_metadata_error(VFS_I(base_ni), err);
+			if (!error_reported)
+				ntfs_report_file_metadata_error(VFS_I(base_ni), err);
 			ctx->error_reported = true;
 		} else {
 			NVolSetErrors(vol);
diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c
index 13f353905fb3..ab5424b1593f 100644
--- a/fs/ntfs/inode.c
+++ b/fs/ntfs/inode.c
@@ -715,7 +715,7 @@ static int ntfs_read_locked_inode(struct inode *vi)
 	if (vi->i_ino != FILE_MFT)
 		ntfs_init_big_inode(vi);
 
-	m = map_mft_record(ni);
+	m = map_mft_record_reported(ni, &error_reported);
 	if (IS_ERR(m)) {
 		err = PTR_ERR(m);
 		goto err_out;
@@ -1309,7 +1309,7 @@ static int ntfs_read_locked_attr_inode(struct inode *base_vi, struct inode *vi)
 	/* Set inode type to zero but preserve permissions. */
 	vi->i_mode	= base_vi->i_mode & ~S_IFMT;
 
-	m = map_mft_record(base_ni);
+	m = map_mft_record_reported(base_ni, &error_reported);
 	if (IS_ERR(m)) {
 		err = PTR_ERR(m);
 		goto err_out;
@@ -1553,7 +1553,7 @@ static int ntfs_read_locked_index_inode(struct inode *base_vi, struct inode *vi)
 	/* Set inode type to zero but preserve permissions. */
 	vi->i_mode	= base_vi->i_mode & ~S_IFMT;
 	/* Map the mft record for the base inode. */
-	m = map_mft_record(base_ni);
+	m = map_mft_record_reported(base_ni, &error_reported);
 	if (IS_ERR(m)) {
 		err = PTR_ERR(m);
 		goto err_out;
@@ -2793,7 +2793,7 @@ int __ntfs_write_inode(struct inode *vi, int sync)
 
 	mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL);
 	/* Map, pin, and lock the mft record belonging to the inode. */
-	m = map_mft_record(ni);
+	m = map_mft_record_reported(ni, &error_reported);
 	if (IS_ERR(m)) {
 		mutex_unlock(&ni->mrec_lock);
 		err = PTR_ERR(m);
@@ -2873,7 +2873,7 @@ int __ntfs_write_inode(struct inode *vi, int sync)
 				int ret;
 
 				mutex_lock(&tni->mrec_lock);
-				tm = map_mft_record(tni);
+				tm = map_mft_record_reported(tni, &error_reported);
 				if (IS_ERR(tm)) {
 					mutex_unlock(&tni->mrec_lock);
 					if (!err || err == -ENOMEM)
diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c
index c565bec49c7d..b7b018f4350e 100644
--- a/fs/ntfs/mft.c
+++ b/fs/ntfs/mft.c
@@ -108,9 +108,11 @@ int ntfs_mft_record_check(const struct ntfs_volume *vol, struct mft_record *m,
  * The return value needs to be checked with IS_ERR(). If it is true,
  * PTR_ERR() contains the negative error code.
  */
-static inline struct mft_record *map_mft_record_folio(struct ntfs_inode *ni)
+static inline struct mft_record *
+map_mft_record_folio(struct ntfs_inode *ni, bool *error_reported)
 {
 	loff_t i_size;
+	struct ntfs_inode *base_ni;
 	struct ntfs_volume *vol = ni->vol;
 	struct inode *mft_vi = vol->mft_ino;
 	struct folio *folio;
@@ -169,7 +171,13 @@ static inline struct mft_record *map_mft_record_folio(struct ntfs_inode *ni)
 		kfree(ni->mrec);
 		ni->mrec = NULL;
 		folio = ERR_PTR(-EIO);
-		NVolSetErrors(vol);
+		if (ni->nr_extents >= 0)
+			base_ni = ni;
+		else
+			base_ni = ni->ext.base_ntfs_ino;
+		ntfs_report_file_metadata_error(VFS_I(base_ni), -EIO);
+		if (error_reported)
+			*error_reported = true;
 	}
 err_out:
 	ni->folio = NULL;
@@ -195,7 +203,8 @@ static inline struct mft_record *map_mft_record_folio(struct ntfs_inode *ni)
  * Return: A pointer to the mft record. You need to check the returned
  * pointer with IS_ERR().
  */
-struct mft_record *map_mft_record(struct ntfs_inode *ni)
+static struct mft_record *__map_mft_record(struct ntfs_inode *ni,
+					   bool *error_reported)
 {
 	struct mft_record *m;
 
@@ -210,7 +219,7 @@ struct mft_record *map_mft_record(struct ntfs_inode *ni)
 	if (ni->folio)
 		return (struct mft_record *)ni->mrec;
 
-	m = map_mft_record_folio(ni);
+	m = map_mft_record_folio(ni, error_reported);
 	if (!IS_ERR(m))
 		return m;
 
@@ -220,6 +229,17 @@ struct mft_record *map_mft_record(struct ntfs_inode *ni)
 	return m;
 }
 
+struct mft_record *map_mft_record(struct ntfs_inode *ni)
+{
+	return __map_mft_record(ni, NULL);
+}
+
+struct mft_record *map_mft_record_reported(struct ntfs_inode *ni,
+					   bool *error_reported)
+{
+	return __map_mft_record(ni, error_reported);
+}
+
 /*
  * unmap_mft_record - release a reference to a mapped mft record
  * @ni:		ntfs inode whose MFT record to unmap
@@ -262,8 +282,10 @@ void unmap_mft_record(struct ntfs_inode *ni)
  * On successful return, @ntfs_ino contains a pointer to the ntfs_inode
  * structure of the mapped extent inode.
  */
-struct mft_record *map_extent_mft_record(struct ntfs_inode *base_ni, u64 mref,
-		struct ntfs_inode **ntfs_ino)
+static struct mft_record *__map_extent_mft_record(struct ntfs_inode *base_ni,
+						  u64 mref,
+						  struct ntfs_inode **ntfs_ino,
+						  bool *error_reported)
 {
 	struct mft_record *m;
 	struct ntfs_inode *ni = NULL;
@@ -299,7 +321,7 @@ struct mft_record *map_extent_mft_record(struct ntfs_inode *base_ni, u64 mref,
 		mutex_unlock(&base_ni->extent_lock);
 		atomic_dec(&base_ni->count);
 		/* We found the record; just have to map and return it. */
-		m = map_mft_record(ni);
+		m = map_mft_record_reported(ni, error_reported);
 		/* map_mft_record() has incremented this on success. */
 		atomic_dec(&ni->count);
 		if (!IS_ERR(m)) {
@@ -333,7 +355,7 @@ struct mft_record *map_extent_mft_record(struct ntfs_inode *base_ni, u64 mref,
 	ni->nr_extents = -1;
 	ni->ext.base_ntfs_ino = base_ni;
 	/* Now map the record. */
-	m = map_mft_record(ni);
+	m = map_mft_record_reported(ni, error_reported);
 	if (IS_ERR(m)) {
 		atomic_dec(&base_ni->count);
 		ntfs_clear_extent_inode(ni);
@@ -396,6 +418,21 @@ struct mft_record *map_extent_mft_record(struct ntfs_inode *base_ni, u64 mref,
 	return m;
 }
 
+struct mft_record *map_extent_mft_record(struct ntfs_inode *base_ni, u64 mref,
+					 struct ntfs_inode **ntfs_ino)
+{
+	return __map_extent_mft_record(base_ni, mref, ntfs_ino, NULL);
+}
+
+struct mft_record *
+map_extent_mft_record_reported(struct ntfs_inode *base_ni, u64 mref,
+			       struct ntfs_inode **ntfs_ino,
+			       bool *error_reported)
+{
+	return __map_extent_mft_record(base_ni, mref, ntfs_ino,
+			error_reported);
+}
+
 /*
  * __mark_mft_record_dirty - mark the base vfs inode dirty
  * @ni:		ntfs inode describing the mapped mft record
@@ -475,7 +512,8 @@ static void ntfs_mft_end_io(struct bio *bio)
 		err = ctx->error;
 	if (err) {
 		mapping_set_error(ctx->mapping, err);
-		NVolSetErrors(ctx->vol);
+		if (!done)
+			ntfs_report_file_metadata_error(ctx->mapping->host, err);
 		ntfs_error(ctx->vol->sb, "I/O error while writing MFT: %d",
 			   err);
 	}
@@ -672,8 +710,8 @@ static int ntfs_prepare_mft_record_io_units(struct ntfs_inode *ni,
  *
  * On success, clean the mft record and return 0.  On ENOMEM, redirty the
  * record so it can be retried.  Asynchronous callers return success after
- * redirtying while synchronous callers receive the error.  For other errors,
- * mark the volume with errors.
+ * redirtying while synchronous callers receive the error.  The caller is
+ * responsible for reporting other errors.
  *
  * If @sync is false, PG_writeback keeps the folio stable and serializes later
  * writers until the I/O completes.
@@ -807,8 +845,7 @@ int write_mft_record_nolock(struct ntfs_inode *ni, struct mft_record *m, int syn
 		mark_mft_record_dirty(ni);
 		if (!sync)
 			err = 0;
-	} else
-		NVolSetErrors(vol);
+	}
 	return err;
 }
 
@@ -1339,9 +1376,11 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol)
 	struct ntfs_attr_search_ctx *ctx = NULL;
 	struct mft_record *mrec;
 	struct attr_record *a = NULL;
-	int ret, mp_size;
+	int err, ret, mp_size;
 	u32 old_alen = 0;
+	u16 mp_ofs;
 	u8 *b, tb;
+	bool error_reported = false;
 	struct {
 		u8 added_cluster:1;
 		u8 added_run:1;
@@ -1425,10 +1464,11 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol)
 		if (IS_ERR(rl)) {
 			up_write(&mftbmp_ni->runlist.lock);
 			ntfs_error(vol->sb, "Failed to merge runlists for mft bitmap.");
-			if (ntfs_cluster_free_from_rl(vol, rl2)) {
+			err = ntfs_cluster_free_from_rl(vol, rl2);
+			if (err) {
 				ntfs_error(vol->sb, "Failed to deallocate allocated cluster.%s",
 						es);
-				NVolSetErrors(vol);
+				ntfs_report_metadata_error(vol, err);
 			}
 			kvfree(rl2);
 			return PTR_ERR(rl);
@@ -1548,9 +1588,11 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol)
 
 restore_undo_alloc:
 	ntfs_attr_reinit_search_ctx(ctx);
-	if (ntfs_attr_lookup(mftbmp_ni->type, mftbmp_ni->name,
-			mftbmp_ni->name_len, CASE_SENSITIVE, rl[1].vcn, NULL,
-			0, ctx)) {
+	err = ntfs_attr_lookup(mftbmp_ni->type, mftbmp_ni->name,
+			       mftbmp_ni->name_len, CASE_SENSITIVE, rl[1].vcn,
+			       NULL, 0, ctx);
+	if (err) {
+		error_reported = ctx->error_reported;
 		ntfs_error(vol->sb,
 			"Failed to find last attribute extent of mft bitmap attribute.%s", es);
 		write_lock_irqsave(&mftbmp_ni->size_lock, flags);
@@ -1563,7 +1605,8 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol)
 		 * The only thing that is now wrong is ->allocated_size of the
 		 * base attribute extent which chkdsk should be able to fix.
 		 */
-		NVolSetErrors(vol);
+		if (!error_reported)
+			ntfs_report_metadata_error(vol, err);
 		return ret;
 	}
 	a = ctx->attr;
@@ -1582,31 +1625,38 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol)
 	}
 	/* Deallocate the cluster. */
 	down_write(&vol->lcnbmp_lock);
-	if (ntfs_bitmap_clear_bit(vol->lcnbmp_ino, lcn)) {
+	err = ntfs_bitmap_clear_bit(vol->lcnbmp_ino, lcn);
+	if (err) {
 		ntfs_error(vol->sb, "Failed to free allocated cluster.%s", es);
-		NVolSetErrors(vol);
-	} else
+		ntfs_report_metadata_error(vol, err);
+	} else {
 		ntfs_inc_free_clusters(vol, 1);
+	}
 	up_write(&vol->lcnbmp_lock);
 	if (status.mp_rebuilt) {
-		if (ntfs_mapping_pairs_build(vol, (u8 *)a + le16_to_cpu(
-				a->data.non_resident.mapping_pairs_offset),
-				old_alen - le16_to_cpu(
-				a->data.non_resident.mapping_pairs_offset),
-				rl2, ll, -1, NULL, NULL, NULL)) {
+		mp_ofs = le16_to_cpu(a->data.non_resident.mapping_pairs_offset);
+		err = ntfs_mapping_pairs_build(vol, (u8 *)a + mp_ofs,
+					       old_alen - mp_ofs, rl2, ll, -1,
+					       NULL, NULL, NULL);
+		if (err) {
 			ntfs_error(vol->sb, "Failed to restore mapping pairs array.%s", es);
-			NVolSetErrors(vol);
+			ntfs_report_metadata_error(vol, err);
 		}
-		if (ntfs_attr_record_resize(ctx->mrec, a, old_alen)) {
+		err = ntfs_attr_record_resize(ctx->mrec, a, old_alen);
+		if (err) {
 			ntfs_error(vol->sb, "Failed to restore attribute record.%s", es);
-			NVolSetErrors(vol);
+			ntfs_report_metadata_error(vol, err);
 		}
 		mark_mft_record_dirty(ctx->ntfs_ino);
-	} else if (status.mp_extended &&
-		   ntfs_attr_update_mapping_pairs_locked(mftbmp_ni, 0,
-							  mftbmp_ni)) {
-		ntfs_error(vol->sb, "Failed to restore mapping pairs.%s", es);
-		NVolSetErrors(vol);
+	} else if (status.mp_extended) {
+		err = ntfs_attr_update_mapping_pairs_locked_reported(mftbmp_ni, 0,
+								     mftbmp_ni,
+								     &error_reported);
+		if (err) {
+			ntfs_error(vol->sb, "Failed to restore mapping pairs.%s", es);
+			if (!error_reported)
+				ntfs_report_metadata_error(vol, err);
+		}
 	}
 	if (ctx)
 		ntfs_attr_put_search_ctx(ctx);
@@ -1639,7 +1689,8 @@ static int ntfs_mft_bitmap_extend_initialized_nolock(struct ntfs_volume *vol)
 	struct ntfs_attr_search_ctx *ctx;
 	struct mft_record *mrec;
 	struct attr_record *a;
-	int ret;
+	int err, ret;
+	bool error_reported = false;
 
 	ntfs_debug("Extending mft bitmap initialized (and data) size.");
 	mft_ni = NTFS_I(vol->mft_ino);
@@ -1696,23 +1747,28 @@ static int ntfs_mft_bitmap_extend_initialized_nolock(struct ntfs_volume *vol)
 	}
 	ntfs_error(vol->sb, "Failed to write to mft bitmap.");
 	/* Try to recover from the error. */
-	mrec = map_mft_record(mft_ni);
+	mrec = map_mft_record_reported(mft_ni, &error_reported);
 	if (IS_ERR(mrec)) {
 		ntfs_error(vol->sb, "Failed to map mft record.%s", es);
-		NVolSetErrors(vol);
+		err = PTR_ERR(mrec);
+		if (!error_reported)
+			ntfs_report_metadata_error(vol, err);
 		return ret;
 	}
 	ctx = ntfs_attr_get_search_ctx(mft_ni, mrec);
 	if (unlikely(!ctx)) {
 		ntfs_error(vol->sb, "Failed to get search context.%s", es);
-		NVolSetErrors(vol);
+		ntfs_report_metadata_error(vol, -ENOMEM);
 		goto unm_err_out;
 	}
-	if (ntfs_attr_lookup(mftbmp_ni->type, mftbmp_ni->name,
-			mftbmp_ni->name_len, CASE_SENSITIVE, 0, NULL, 0, ctx)) {
+	err = ntfs_attr_lookup(mftbmp_ni->type, mftbmp_ni->name,
+			       mftbmp_ni->name_len, CASE_SENSITIVE, 0, NULL, 0,
+			       ctx);
+	if (err) {
 		ntfs_error(vol->sb,
 			"Failed to find first attribute extent of mft bitmap attribute.%s", es);
-		NVolSetErrors(vol);
+		if (!ctx->error_reported)
+			ntfs_report_metadata_error(vol, err);
 put_err_out:
 		ntfs_attr_put_search_ctx(ctx);
 unm_err_out:
@@ -1774,9 +1830,10 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
 	struct ntfs_attr_search_ctx *ctx = NULL;
 	struct mft_record *mrec;
 	struct attr_record *a = NULL;
-	int ret, mp_size;
+	int err, ret, mp_size;
 	u32 old_alen = 0;
-	bool mp_rebuilt = false, mp_extended = false;
+	u16 mp_ofs;
+	bool error_reported = false, mp_rebuilt = false, mp_extended = false;
 	size_t new_rl_count;
 
 	ntfs_debug("Extending mft data allocation.");
@@ -1862,10 +1919,11 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
 	if (IS_ERR(rl)) {
 		up_write(&mft_ni->runlist.lock);
 		ntfs_error(vol->sb, "Failed to merge runlists for mft data attribute.");
-		if (ntfs_cluster_free_from_rl(vol, rl2)) {
+		err = ntfs_cluster_free_from_rl(vol, rl2);
+		if (err) {
 			ntfs_error(vol->sb,
 				"Failed to deallocate clusters from the mft data attribute.%s", es);
-			NVolSetErrors(vol);
+			ntfs_report_metadata_error(vol, err);
 		}
 		kvfree(rl2);
 		return PTR_ERR(rl);
@@ -1985,8 +2043,10 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
 	return 0;
 restore_undo_alloc:
 	ntfs_attr_reinit_search_ctx(ctx);
-	if (ntfs_attr_lookup(mft_ni->type, mft_ni->name, mft_ni->name_len,
-			CASE_SENSITIVE, rl[1].vcn, NULL, 0, ctx)) {
+	err = ntfs_attr_lookup(mft_ni->type, mft_ni->name, mft_ni->name_len,
+			       CASE_SENSITIVE, rl[1].vcn, NULL, 0, ctx);
+	if (err) {
+		error_reported = ctx->error_reported;
 		ntfs_error(vol->sb,
 			"Failed to find last attribute extent of mft data attribute.%s", es);
 		write_lock_irqsave(&mft_ni->size_lock, flags);
@@ -1999,45 +2059,55 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
 		 * The only thing that is now wrong is ->allocated_size of the
 		 * base attribute extent which chkdsk should be able to fix.
 		 */
-		NVolSetErrors(vol);
+		if (!error_reported)
+			ntfs_report_metadata_error(vol, err);
 		return ret;
 	}
 	ctx->attr->data.non_resident.highest_vcn =
 			cpu_to_le64(old_last_vcn - 1);
 undo_alloc:
-	if (ntfs_cluster_free(mft_ni, old_last_vcn, -1, ctx) < 0) {
+	err = ntfs_cluster_free(mft_ni, old_last_vcn, -1, ctx);
+	if (err < 0) {
 		ntfs_error(vol->sb, "Failed to free clusters from mft data attribute.%s", es);
-		NVolSetErrors(vol);
+		ntfs_report_metadata_error(vol, err);
 	}
 
-	if (ntfs_rl_truncate_nolock(vol, &mft_ni->runlist, old_last_vcn)) {
+	err = ntfs_rl_truncate_nolock(vol, &mft_ni->runlist, old_last_vcn);
+	if (err) {
 		ntfs_error(vol->sb, "Failed to truncate mft data attribute runlist.%s", es);
-		NVolSetErrors(vol);
-	}
-	if (mp_extended && ntfs_attr_update_mapping_pairs(mft_ni, 0)) {
-		ntfs_error(vol->sb, "Failed to restore mapping pairs.%s",
-			   es);
-		NVolSetErrors(vol);
+		ntfs_report_metadata_error(vol, err);
+	}
+	if (mp_extended) {
+		err = ntfs_attr_update_mapping_pairs_locked_reported(mft_ni, 0,
+								     NULL,
+								     &error_reported);
+		if (err) {
+			ntfs_error(vol->sb, "Failed to restore mapping pairs.%s",
+				   es);
+			if (!error_reported)
+				ntfs_report_metadata_error(vol, err);
+		}
 	}
 	if (ctx) {
 		a = ctx->attr;
 		if (mp_rebuilt && !IS_ERR(ctx->mrec)) {
-			if (ntfs_mapping_pairs_build(vol, (u8 *)a + le16_to_cpu(
-				a->data.non_resident.mapping_pairs_offset),
-				old_alen - le16_to_cpu(
-					a->data.non_resident.mapping_pairs_offset),
-				rl2, ll, -1, NULL, NULL, NULL)) {
+			mp_ofs = le16_to_cpu(a->data.non_resident.mapping_pairs_offset);
+			err = ntfs_mapping_pairs_build(vol, (u8 *)a + mp_ofs,
+						       old_alen - mp_ofs, rl2, ll,
+						       -1, NULL, NULL, NULL);
+			if (err) {
 				ntfs_error(vol->sb, "Failed to restore mapping pairs array.%s", es);
-				NVolSetErrors(vol);
+				ntfs_report_metadata_error(vol, err);
 			}
-			if (ntfs_attr_record_resize(ctx->mrec, a, old_alen)) {
+			err = ntfs_attr_record_resize(ctx->mrec, a, old_alen);
+			if (err) {
 				ntfs_error(vol->sb, "Failed to restore attribute record.%s", es);
-				NVolSetErrors(vol);
+				ntfs_report_metadata_error(vol, err);
 			}
 			mark_mft_record_dirty(ctx->ntfs_ino);
 		} else if (IS_ERR(ctx->mrec)) {
 			ntfs_error(vol->sb, "Failed to restore attribute search context.%s", es);
-			NVolSetErrors(vol);
+			ntfs_report_metadata_error(vol, PTR_ERR(ctx->mrec));
 		}
 		ntfs_attr_put_search_ctx(ctx);
 	}
@@ -2269,8 +2339,8 @@ static int ntfs_mft_record_format(const struct ntfs_volume *vol, const s64 mft_n
  *
  * On error, the volume will be left in a consistent state and no record will
  * be allocated.  If rolling back a partial operation fails, we may leave some
- * inconsistent metadata in which case we set NVolErrors() so the volume is
- * left dirty when unmounted.
+ * inconsistent metadata in which case we report the error so the volume is
+ * left dirty when unmounted and userspace is notified.
  *
  * Note, this function cannot make use of most of the normal functions, like
  * for example for attribute resizing, etc, because when the run list overflows
@@ -2301,7 +2371,7 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode,
 	struct attr_record *a;
 	pgoff_t index;
 	unsigned int ofs;
-	int err;
+	int err, rollback_err;
 	__le16 seq_no, usn;
 	bool record_formatted = false, from_reserve = false, tail_alloc = false;
 	bool reserve_created = false;
@@ -2705,7 +2775,7 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode,
 			folio_unlock(folio);
 			kunmap_local(m);
 			folio_put(folio);
-			NVolSetErrors(vol);
+			ntfs_report_metadata_error(vol, -EFSCORRUPTED);
 			goto search_free_rec;
 		}
 		/*
@@ -2861,9 +2931,12 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode,
 	if (!base_ni || base_ni->mft_no != FILE_MFT)
 		down_write(&vol->mftbmp_lock);
 undo_mftbmp_alloc_nolock:
-	if (!forced_reserved_record && ntfs_bitmap_clear_bit(vol->mftbmp_ino, bit)) {
+	rollback_err = 0;
+	if (!forced_reserved_record)
+		rollback_err = ntfs_bitmap_clear_bit(vol->mftbmp_ino, bit);
+	if (rollback_err) {
 		ntfs_error(vol->sb, "Failed to clear bit in mft bitmap.%s", es);
-		NVolSetErrors(vol);
+		ntfs_report_metadata_error(vol, rollback_err);
 	}
 	if ((from_reserve || reserve_created) &&
 	    vol->mft_record_reserve_pos == bit + 1)
@@ -2900,30 +2973,35 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode,
 int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni)
 {
 	u64 mft_no;
-	int err;
+	int err, rollback_err;
 	u16 seq_no;
 	__le16 old_seq_no;
 	__le64 old_base_mft_record;
 	struct mft_record *ni_mrec;
 	unsigned int memalloc_flags;
 	struct ntfs_inode *base_ni;
-	bool keep_reserved;
+	bool error_reported = false, keep_reserved;
 
 	if (!vol || !ni)
 		return -EINVAL;
 
 	ntfs_debug("Entering for inode 0x%llx.\n", (long long)ni->mft_no);
+	if (likely(ni->nr_extents >= 0))
+		base_ni = ni;
+	else
+		base_ni = ni->ext.base_ntfs_ino;
 
-	ni_mrec = map_mft_record(ni);
-	if (IS_ERR(ni_mrec))
+	ni_mrec = map_mft_record_reported(ni, &error_reported);
+	if (IS_ERR(ni_mrec)) {
+		err = PTR_ERR(ni_mrec);
+		if (!error_reported && err != -ENOMEM &&
+		    err != -EINTR && err != -ERESTARTSYS)
+			ntfs_report_file_metadata_error(VFS_I(base_ni), err);
 		return -EIO;
+	}
 
 	/* Cache the mft reference for later. */
 	mft_no = ni->mft_no;
-	if (likely(ni->nr_extents >= 0))
-		base_ni = ni;
-	else
-		base_ni = ni->ext.base_ntfs_ino;
 	keep_reserved = mft_no >= FILE_reserved12 &&
 			mft_no <= FILE_reserved15 &&
 			base_ni->mft_no == FILE_MFT;
@@ -2962,8 +3040,10 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni)
 	 */
 	NInoSetDirty(ni);
 	err = write_mft_record(ni, ni_mrec, 1);
-	if (err)
+	if (err) {
+		ntfs_report_file_metadata_error(VFS_I(base_ni), err);
 		goto sync_rollback;
+	}
 
 	if (keep_reserved) {
 		unmap_mft_record(ni);
@@ -3006,7 +3086,9 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni)
 	ni_mrec->sequence_number = old_seq_no;
 	ni_mrec->base_mft_record = old_base_mft_record;
 	NInoSetDirty(ni);
-	write_mft_record(ni, ni_mrec, 0);
+	rollback_err = write_mft_record(ni, ni_mrec, 0);
+	if (rollback_err)
+		ntfs_report_file_metadata_error(VFS_I(base_ni), rollback_err);
 	unmap_mft_record(ni);
 	return err;
 }
@@ -3153,7 +3235,7 @@ static void ntfs_mft_write_error(struct ntfs_volume *vol,
 				 struct address_space *mapping, int err)
 {
 	mapping_set_error(mapping, err);
-	NVolSetErrors(vol);
+	ntfs_report_file_metadata_error(mapping->host, err);
 	ntfs_error(vol->sb, "Error while writing MFT folio: %d", err);
 }
 
diff --git a/fs/ntfs/mft.h b/fs/ntfs/mft.h
index d2a31205e08c..3eda89aab67a 100644
--- a/fs/ntfs/mft.h
+++ b/fs/ntfs/mft.h
@@ -14,9 +14,15 @@
 #include "inode.h"
 
 struct mft_record *map_mft_record(struct ntfs_inode *ni);
+struct mft_record *map_mft_record_reported(struct ntfs_inode *ni,
+					   bool *error_reported);
 void unmap_mft_record(struct ntfs_inode *ni);
 struct mft_record *map_extent_mft_record(struct ntfs_inode *base_ni, u64 mref,
 		struct ntfs_inode **ntfs_ino);
+struct mft_record *
+map_extent_mft_record_reported(struct ntfs_inode *base_ni, u64 mref,
+			       struct ntfs_inode **ntfs_ino,
+			       bool *error_reported);
 
 static inline void unmap_extent_mft_record(struct ntfs_inode *ni)
 {
-- 
2.51.0


  parent reply	other threads:[~2026-09-16  2:24 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-16  2:23 [PATCH v2 0/5] ntfs: report filesystem errors through fsnotify Baolin Liu
2026-09-16  2:23 ` [PATCH v2 1/5] ntfs: report allocation metadata errors to fsnotify Baolin Liu
2026-09-16  2:23 ` [PATCH v2 2/5] ntfs: report attribute " Baolin Liu
2026-09-16  7:38   ` Hyunchul Lee
2026-09-17  4:21     ` liubaolin
2026-09-16  2:23 ` [PATCH v2 3/5] ntfs: report inode metadata " Baolin Liu
2026-09-16  2:23 ` Baolin Liu [this message]
2026-09-16  2:23 ` [PATCH v2 5/5] ntfs: report shutdown " Baolin Liu
2026-09-16  2:40 ` [PATCH v2 0/5] ntfs: report filesystem errors through fsnotify liubaolin

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260916022345.1151836-5-liubaolin12138@163.com \
    --to=liubaolin12138@163.com \
    --cc=hyc.lee@gmail.com \
    --cc=linkinjeon@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=liubaolin@kylinos.cn \
    --cc=ntfs@lists.linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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®