[PATCH v3 2/6] ntfs: do not turn an unmappable runlist fragment into delalloc on write

From: Matthias Goergens

Date: Tue Sep 29 2026 - 23:51:06 EST


ntfs_write_simple_iomap_begin_non_resident() has the same unchecked
retry. LCN_RL_NOT_MAPPED passes its "lcn <= LCN_HOLE" test, so a
buffered write into a range whose extent record cannot be read is
merged into the runlist as LCN_DELALLOC over clusters that are already
allocated on disk, and write() succeeds. If the record stays
unreadable, writeback fails too and the data never reaches the disk;
the error only shows up at a later fsync() or a synchronous write.

On the volume from the previous patch, after a read of the file, an
8 KiB pwrite() at vcn 1000 returns 8192, fsync() returns -EIO, and
after remount the range is unchanged.

Fail the lookup here too, with -ENOMEM or -EIO. That pwrite() now
fails with -EIO. Writes whose range can be mapped are unaffected.

Fixes: b041ca562526 ("ntfs: update iomap and address space operations")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Matthias Goergens <matthias.goergens@xxxxxxxxx>
---
fs/ntfs/iomap.c | 16 ++++++++++++++--
1 file changed, 14 insertions(+), 2 deletions(-)

diff --git a/fs/ntfs/iomap.c b/fs/ntfs/iomap.c
index c812d7f19b360..b4475963e57a7 100644
--- a/fs/ntfs/iomap.c
+++ b/fs/ntfs/iomap.c
@@ -394,7 +394,7 @@ static int ntfs_write_simple_iomap_begin_non_resident(struct inode *inode, loff_
loff_t vcn_ofs, rl_length;
struct runlist_element *rl, *rlc;
bool is_retry = false;
- int err = 0;
+ int err = 0, map_err = 0;
s64 vcn, lcn;
s64 max_clu_count =
ntfs_bytes_to_cluster(vol, round_up(length, vol->cluster_size));
@@ -429,12 +429,24 @@ static int ntfs_write_simple_iomap_begin_non_resident(struct inode *inode, loff_

if (lcn <= LCN_RL_NOT_MAPPED && is_retry == false) {
is_retry = true;
- if (!ntfs_map_runlist_nolock(ni, vcn, NULL)) {
+ map_err = ntfs_map_runlist_nolock(ni, vcn, NULL);
+ if (!map_err) {
rl = ni->runlist.rl;
goto remap_rl;
}
}

+ /*
+ * As in ntfs_attr_vcn_to_rl(): a runlist fragment that could not be
+ * mapped is not a hole. Treating it as one would put a delalloc
+ * extent over clusters that are allocated on disk but unknown to us.
+ */
+ if (lcn == LCN_RL_NOT_MAPPED) {
+ up_write(&ni->runlist.lock);
+ mutex_unlock(&ni->mrec_lock);
+ return map_err == -ENOMEM ? -ENOMEM : -EIO;
+ }
+
max_clu_count = min(max_clu_count, rl->length - (vcn - rl->vcn));
if (max_clu_count == 0) {
ntfs_error(inode->i_sb,
--
2.55.0