mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: liubaolin <liubaolin12138@163.com>
To: Hongling Zeng <zenghongling@kylinos.cn>,
	linkinjeon@kernel.org, hyc.lee@gmail.com
Cc: ntfs@lists.linux.dev, linux-kernel@vger.kernel.org,
	zhongling0719@126.com, stable@vger.kernel.org
Subject: Re: [PATCH v2 3/3] ntfs: convert runtime error paths to mark volume dirty after recording errors
Date: Fri, 4 Sep 2026 20:59:06 +0800	[thread overview]
Message-ID: <2d528913-a787-47ce-aa57-661fddaab4bc@163.com> (raw)
In-Reply-To: <20260903075438.306793-3-zenghongling@kylinos.cn>



在 2026/9/3 15:54, Hongling Zeng 写道:
> Follow-up to the helper introduction: the runtime metadata-corruption
> paths in mft.c, lcnalloc.c, bitmap.c and inode.c still record the error
> flag with a bare NVolSetErrors(), leaving the same window in which
> ntfs_sync_fs() can acquire ni->mrec_lock, find NVolErrors() unset, and
> persist the volume as clean before the erroring thread records its
> error.
> 
> Convert the call sites that can run concurrently with sync and hold no
> mrec_lock:
> 
>   - the mft record allocation and $MFT data/$BITMAP extend rollback
>     paths (mft.c), which hold the runlist, mftbmp and lcnbmp locks;
> 
>   - the cluster allocation/free rollback paths (lcnalloc.c), which
>     hold vol->lcnbmp_lock;
> 
>   - the bitmap rollback failure path (bitmap.c), which holds no
>     relevant lock;
> 
>   - the inode read and writeback failure paths (inode.c), which run
>     under I_NEW or with the mrec_lock already released at err_out.
> 
> Blocking on the $Volume mrec_lock from those paths is safe: it is a
> leaf lock, its critical sections never acquire the locks the callers
> hold, and it carries its own lockdep class so the validator does not
> report an inversion against the shared mrec_lock class.
> 
> The remaining call sites keep calling NVolSetErrors() directly, now
> with comments explaining why:
> 
>   - the mft record writeback paths (write_mft_record_nolock() and
>     ntfs_write_mft_block()) can run with a caller's mrec_lock held,
>     which for the $Volume inode itself would self-deadlock the helper;
> 
>   - the ntfs_attr_lookup() failure paths (ntfs_attr_find(),
>     ntfs_external_attr_find() and the ntfs_attr_make_non_resident()
>     rollback) run with the caller's mrec_lock held for the same
>     reason.
> 
> Cc: stable@vger.kernel.org
> Signed-off-by: Hongling Zeng <zenghongling@kylinos.cn>
> ---
>   fs/ntfs/attrib.c   | 18 ++++++++++++++-
>   fs/ntfs/bitmap.c   |  2 +-
>   fs/ntfs/inode.c    |  8 +++----
>   fs/ntfs/lcnalloc.c |  4 ++--
>   fs/ntfs/mft.c      | 55 ++++++++++++++++++++++++++++------------------
>   5 files changed, 58 insertions(+), 29 deletions(-)
> 
> diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c
> index 60264833bb63..5965a67b5971 100644
> --- a/fs/ntfs/attrib.c
> +++ b/fs/ntfs/attrib.c
> @@ -964,6 +964,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));
> +	/*
> +	 * ntfs_attr_lookup() runs with the caller's mrec_lock held, so the
> +	 * dirty bit cannot be written here (it would self-deadlock for the
> +	 * $Volume inode); record the error flag only.
> +	 */
>   	NVolSetErrors(vol);
>   	return -EIO;
>   }
> @@ -1501,8 +1506,14 @@ static int ntfs_external_attr_find(const __le32 type,
>   		err = -EIO;
>   	}
>   
> -	if (err != -ENOMEM)
> +	if (err != -ENOMEM) {
> +		/*
> +		 * ntfs_attr_lookup() runs with the caller's mrec_lock
> +		 * held, so the dirty bit cannot be written here; record
> +		 * the error flag only.
> +		 */
>   		NVolSetErrors(vol);
> +	}
>   	return err;
>   not_found:
>   	/*
> @@ -2236,6 +2247,11 @@ int ntfs_attr_make_non_resident(struct ntfs_inode *ni, const u32 data_size)
>   		if (ntfs_cluster_free_from_rl(vol, rl) < 0) {
>   			ntfs_error(vol->sb,
>   				"Failed to release allocated cluster(s) in error code path.  Run chkdsk to recover the lost cluster(s).");
> +			/*
> +			 * The caller may hold the mrec_lock of the inode
> +			 * being modified, so the dirty bit cannot be
> +			 * written here; record the error flag only.
> +			 */
>   			NVolSetErrors(vol);
>   		}
>   		kvfree(rl);
> diff --git a/fs/ntfs/bitmap.c b/fs/ntfs/bitmap.c
> index b1436b3151b9..6120cb301665 100644
> --- a/fs/ntfs/bitmap.c
> +++ b/fs/ntfs/bitmap.c
> @@ -284,7 +284,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_mark_volume_dirty_with_error(NTFS_SB(vi->i_sb));

Hi Hongling,
   There may still be a case where the dirty bit is not persisted.  This 
call site invokes ntfs_error() before 
ntfs_mark_volume_dirty_with_error().  When errors=remount-ro is in
   use, ntfs_error() sets SB_RDONLY through the following path:

   ntfs_error()
     -> ntfs_handle_error()
       -> sb->s_flags |= SB_RDONLY

   Therefore, when ntfs_mark_volume_dirty_with_error() is called,
NVolErrors() is set first, but sb_rdonly() is already true.  The helper 
returns without writing VOLUME_IS_DIRTY to disk.

   For example, the following sequence still appears possible:

   ntfs_sync_fs()                    error path
   --------------                    ----------
   acquire $Volume mrec_lock
   observe NVolErrors() == false
                                     ntfs_error()
                                       -> set SB_RDONLY
                                     set NVolErrors()
                                     sb_rdonly() == true
                                     skip the dirty-bit update
   clear VOLUME_IS_DIRTY
   release $Volume mrec_lock

   This leaves NVolErrors() set while the on-disk VOLUME_IS_DIRTY bit 
has been cleared.

   The same issue should apply to the other call sites in this patch 
that invoke ntfs_error() before ntfs_mark_volume_dirty_with_error().
   Should the dirty bit be persisted before setting SB_RDONLY?

Thanks,
Baolin.

>   	}
>   	return err;
>   }
> diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c
> index 32edb4045178..0288f6a101dc 100644
> --- a/fs/ntfs/inode.c
> +++ b/fs/ntfs/inode.c
> @@ -1245,7 +1245,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_mark_volume_dirty_with_error(vol);
>   	}
>   	return err;
>   }
> @@ -1473,7 +1473,7 @@ static int ntfs_read_locked_attr_inode(struct inode *base_vi, struct inode *vi)
>   			err, ni->mft_no, ni->type, ni->name_len,
>   			base_ni->mft_no);
>   	if (err != -ENOENT && err != -ENOMEM)
> -		NVolSetErrors(vol);
> +		ntfs_mark_volume_dirty_with_error(vol);
>   	return err;
>   }
>   
> @@ -1725,7 +1725,7 @@ static int ntfs_read_locked_index_inode(struct inode *base_vi, struct inode *vi)
>   		"Failed with error code %i while reading index inode (mft_no 0x%llx, name_len %i.",
>   		err, ni->mft_no, ni->name_len);
>   	if (err != -EOPNOTSUPP && err != -ENOMEM)
> -		NVolSetErrors(vol);
> +		ntfs_mark_volume_dirty_with_error(vol);
>   	return err;
>   }
>   
> @@ -2883,7 +2883,7 @@ 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);
> +		ntfs_mark_volume_dirty_with_error(ni->vol);
>   	}
>   	if (need_iput)
>   		iput(vi);
> diff --git a/fs/ntfs/lcnalloc.c b/fs/ntfs/lcnalloc.c
> index aa2e017a4384..dd6854f16136 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_mark_volume_dirty_with_error(vol);
>   		}
>   		/* 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_mark_volume_dirty_with_error(vol);
>   	}
>   	ntfs_dec_free_clusters(vol, delta);
>   	up_write(&vol->lcnbmp_lock);
> diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c
> index 984a0827f9ac..d8791d7b9678 100644
> --- a/fs/ntfs/mft.c
> +++ b/fs/ntfs/mft.c
> @@ -657,8 +657,15 @@ int write_mft_record_nolock(struct ntfs_inode *ni, struct mft_record *m, int syn
>   			"Not enough memory to write mft record. Redirtying so the write is retried later.");
>   		mark_mft_record_dirty(ni);
>   		err = 0;
> -	} else
> +	} else {
> +		/*
> +		 * The writeback path can run with the caller's mrec_lock
> +		 * held, so the dirty bit cannot be written here (it would
> +		 * self-deadlock for the $Volume inode); record the error
> +		 * flag only.
> +		 */
>   		NVolSetErrors(vol);
> +	}
>   	return err;
>   }
>   
> @@ -1150,7 +1157,7 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol)
>   			if (ntfs_cluster_free_from_rl(vol, rl2)) {
>   				ntfs_error(vol->sb, "Failed to deallocate allocated cluster.%s",
>   						es);
> -				NVolSetErrors(vol);
> +				ntfs_mark_volume_dirty_with_error(vol);
>   			}
>   			kvfree(rl2);
>   			return PTR_ERR(rl);
> @@ -1285,7 +1292,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_mark_volume_dirty_with_error(vol);
>   		return ret;
>   	}
>   	a = ctx->attr;
> @@ -1306,7 +1313,7 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol)
>   	down_write(&vol->lcnbmp_lock);
>   	if (ntfs_bitmap_clear_bit(vol->lcnbmp_ino, lcn)) {
>   		ntfs_error(vol->sb, "Failed to free allocated cluster.%s", es);
> -		NVolSetErrors(vol);
> +		ntfs_mark_volume_dirty_with_error(vol);
>   	} else
>   		ntfs_inc_free_clusters(vol, 1);
>   	up_write(&vol->lcnbmp_lock);
> @@ -1317,16 +1324,16 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol)
>   				a->data.non_resident.mapping_pairs_offset),
>   				rl2, ll, -1, NULL, NULL, NULL)) {
>   			ntfs_error(vol->sb, "Failed to restore mapping pairs array.%s", es);
> -			NVolSetErrors(vol);
> +			ntfs_mark_volume_dirty_with_error(vol);
>   		}
>   		if (ntfs_attr_record_resize(ctx->mrec, a, old_alen)) {
>   			ntfs_error(vol->sb, "Failed to restore attribute record.%s", es);
> -			NVolSetErrors(vol);
> +			ntfs_mark_volume_dirty_with_error(vol);
>   		}
>   		mark_mft_record_dirty(ctx->ntfs_ino);
>   	} else if (status.mp_extended && ntfs_attr_update_mapping_pairs(mftbmp_ni, 0)) {
>   		ntfs_error(vol->sb, "Failed to restore mapping pairs.%s", es);
> -		NVolSetErrors(vol);
> +		ntfs_mark_volume_dirty_with_error(vol);
>   	}
>   	if (ctx)
>   		ntfs_attr_put_search_ctx(ctx);
> @@ -1420,20 +1427,20 @@ 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);
> +		ntfs_mark_volume_dirty_with_error(vol);
>   		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_mark_volume_dirty_with_error(vol);
>   		goto unm_err_out;
>   	}
>   	if (ntfs_attr_lookup(mftbmp_ni->type, mftbmp_ni->name,
>   			mftbmp_ni->name_len, CASE_SENSITIVE, 0, NULL, 0, ctx)) {
>   		ntfs_error(vol->sb,
>   			"Failed to find first attribute extent of mft bitmap attribute.%s", es);
> -		NVolSetErrors(vol);
> +		ntfs_mark_volume_dirty_with_error(vol);
>   put_err_out:
>   		ntfs_attr_put_search_ctx(ctx);
>   unm_err_out:
> @@ -1587,7 +1594,7 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
>   		if (ntfs_cluster_free_from_rl(vol, rl2)) {
>   			ntfs_error(vol->sb,
>   				"Failed to deallocate clusters from the mft data attribute.%s", es);
> -			NVolSetErrors(vol);
> +			ntfs_mark_volume_dirty_with_error(vol);
>   		}
>   		kvfree(rl2);
>   		return PTR_ERR(rl);
> @@ -1721,7 +1728,7 @@ 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_mark_volume_dirty_with_error(vol);
>   		return ret;
>   	}
>   	ctx->attr->data.non_resident.highest_vcn =
> @@ -1729,17 +1736,17 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
>   undo_alloc:
>   	if (ntfs_cluster_free(mft_ni, old_last_vcn, -1, ctx) < 0) {
>   		ntfs_error(vol->sb, "Failed to free clusters from mft data attribute.%s", es);
> -		NVolSetErrors(vol);
> +		ntfs_mark_volume_dirty_with_error(vol);
>   	}
>   
>   	if (ntfs_rl_truncate_nolock(vol, &mft_ni->runlist, old_last_vcn)) {
>   		ntfs_error(vol->sb, "Failed to truncate mft data attribute runlist.%s", es);
> -		NVolSetErrors(vol);
> +		ntfs_mark_volume_dirty_with_error(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_mark_volume_dirty_with_error(vol);
>   	}
>   	if (ctx) {
>   		a = ctx->attr;
> @@ -1750,16 +1757,16 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
>   					a->data.non_resident.mapping_pairs_offset),
>   				rl2, ll, -1, NULL, NULL, NULL)) {
>   				ntfs_error(vol->sb, "Failed to restore mapping pairs array.%s", es);
> -				NVolSetErrors(vol);
> +				ntfs_mark_volume_dirty_with_error(vol);
>   			}
>   			if (ntfs_attr_record_resize(ctx->mrec, a, old_alen)) {
>   				ntfs_error(vol->sb, "Failed to restore attribute record.%s", es);
> -				NVolSetErrors(vol);
> +				ntfs_mark_volume_dirty_with_error(vol);
>   			}
>   			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_mark_volume_dirty_with_error(vol);
>   		}
>   		ntfs_attr_put_search_ctx(ctx);
>   	}
> @@ -2322,7 +2329,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_mark_volume_dirty_with_error(vol);
>   			goto search_free_rec;
>   		}
>   		/*
> @@ -2475,7 +2482,7 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode,
>   undo_mftbmp_alloc_nolock:
>   	if (ntfs_bitmap_clear_bit(vol->mftbmp_ino, bit)) {
>   		ntfs_error(vol->sb, "Failed to clear bit in mft bitmap.%s", es);
> -		NVolSetErrors(vol);
> +		ntfs_mark_volume_dirty_with_error(vol);
>   	}
>   	if (!base_ni || base_ni->mft_no != FILE_MFT)
>   		up_write(&vol->mftbmp_lock);
> @@ -2815,8 +2822,14 @@ static int ntfs_write_mft_block(struct folio *folio, struct writeback_control *w
>   			iput(ref_inos[nr_ref_inos]);
>   	}
>   
> -	if (unlikely(err && err != -ENOMEM))
> +	if (unlikely(err && err != -ENOMEM)) {
> +		/*
> +		 * The writeback path can run with a caller's mrec_lock
> +		 * held, so the dirty bit cannot be written here; record
> +		 * the error flag only.
> +		 */
>   		NVolSetErrors(vol);
> +	}
>   	if (likely(!err))
>   		ntfs_debug("Done.");
>   	return err;


  reply	other threads:[~2026-09-04 12:59 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-03  7:54 [PATCH v2 1/3] ntfs: fix volume flag update races Hongling Zeng
2026-09-03  7:54 ` [PATCH v2 2/3] ntfs: add helper to record error flag before setting volume dirty bit Hongling Zeng
2026-09-04 12:32   ` liubaolin
2026-09-03  7:54 ` [PATCH v2 3/3] ntfs: convert runtime error paths to mark volume dirty after recording errors Hongling Zeng
2026-09-04 12:59   ` liubaolin [this message]
2026-09-04 13:07   ` liubaolin
2026-09-04 12:06 ` [PATCH v2 1/3] ntfs: fix volume flag update races 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=2d528913-a787-47ce-aa57-661fddaab4bc@163.com \
    --to=liubaolin12138@163.com \
    --cc=hyc.lee@gmail.com \
    --cc=linkinjeon@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=ntfs@lists.linux.dev \
    --cc=stable@vger.kernel.org \
    --cc=zenghongling@kylinos.cn \
    --cc=zhongling0719@126.com \
    /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®