From fca746064d56329cd81b4f2b1961a3b928c3fe5a Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Tue, 15 Sep 2026 00:19:15 +0900 Subject: [PATCH] ntfs: avoid truncate and writeback deadlock ntfs_non_resident_attr_shrink() drops page-cache folios after freeing clusters and truncating the runlist. The VFS truncate path calls it with mrec_lock held. A folio writeback can need the same lock to resolve its iomap mapping, so truncate can wait for writeback while writeback waits for mrec_lock. A hung-task call stack: truncate: mrec_lock -> truncate_inode_pages() -> folio lock/writeback writeback: folio lock/writeback -> ntfs_write_iomap_begin() -> mrec_lock ntfs_setattr_size() already calls truncate_setsize() before ntfs_truncate_vfs() acquires mrec_lock. Reuse that page-cache truncation in the VFS path and skip the duplicate truncation under mrec_lock. Keep the cleanup for internal attribute callers, which do not call truncate_setsize(). This avoids synchronously flushing the whole discarded tail while preserving page-cache correctness. Fixes: 4e646ecd4475 ("ntfs: drop stale page-cache when shrinking a non-resident attr") Cc: stable@vger.kernel.org Signed-off-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/attrib.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c index 808b44671fff..d8dd40526c3b 100644 --- a/fs/ntfs/attrib.c +++ b/fs/ntfs/attrib.c @@ -4234,12 +4234,14 @@ static int ntfs_attr_make_resident(struct ntfs_inode *ni, struct ntfs_attr_searc * ntfs_non_resident_attr_shrink - shrink a non-resident, open ntfs attribute * @ni: non-resident ntfs attribute to shrink * @newsize: new size (in bytes) to which to shrink the attribute + * @pagecache_truncated: page cache was already truncated to @newsize * * Reduce the size of a non-resident, open ntfs attribute @na to @newsize bytes. */ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsize, - struct ntfs_inode *locked_ni) + struct ntfs_inode *locked_ni, + bool pagecache_truncated) { struct ntfs_volume *vol; struct ntfs_attr_search_ctx *ctx; @@ -4389,7 +4391,8 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, * later writeback map a vcn past the new allocation, which fails with * -ENOENT and loses the write. */ - truncate_inode_pages(VFS_I(ni)->i_mapping, newsize); + if (!pagecache_truncated) + truncate_inode_pages(VFS_I(ni)->i_mapping, newsize); /* Update data size in the index. */ if (ni->type == AT_DATA && ni->name == AT_UNNAMED) @@ -4991,7 +4994,7 @@ int __ntfs_attr_truncate_vfs(struct ntfs_inode *ni, const s64 newsize, up_write(&ni->runlist.lock); } else err = ntfs_non_resident_attr_shrink( - ni, newsize, NULL); + ni, newsize, NULL, true); } else err = ntfs_resident_attr_resize(ni, newsize, 0, NVolDisableSparse(ni->vol) ? @@ -5104,7 +5107,7 @@ int ntfs_attr_truncate_i_locked(struct ntfs_inode *ni, const s64 newsize, ni, newsize, 0, holes, locked_ni); else err = ntfs_non_resident_attr_shrink( - ni, newsize, locked_ni); + ni, newsize, locked_ni, false); } else err = ntfs_resident_attr_resize(ni, newsize, 0, holes); ntfs_debug("Return status %d\n", err);