summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHyunchul Lee <hyc.lee@gmail.com>2026-09-15 00:19:15 +0900
committerNamjae Jeon <linkinjeon@kernel.org>2026-09-15 21:31:26 +0900
commit56b4f864e199c67f9bfa61bb8e7dc08f1eb20ce1 (patch)
treea8a3a623a29ac371146a9658f6fee312525c9276
parent8809c3f29555f9d1e66fe015080a8d4979d91eaa (diff)
downloadlinux-next-56b4f864e199c67f9bfa61bb8e7dc08f1eb20ce1.tar.gz
linux-next-56b4f864e199c67f9bfa61bb8e7dc08f1eb20ce1.zip
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 <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
-rw-r--r--fs/ntfs/attrib.c11
1 files changed, 7 insertions, 4 deletions
diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c
index c7c09a751c6a..b01cbc9eea4a 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);