[PATCH v4 09/31] ntfs: report iomap corruption and preserve buffered read errors
From: Baolin Liu
Date: Tue Oct 20 2026 - 04:52:00 EST
From: Dandan Wu <wudandan@xxxxxxxxxx>
The iomap paths detect missing required attributes and invalid runs
without reporting them. Report these conditions as EFSCORRUPTED while
preserving allocation and underlying mapping errors.
Capture synchronous iomap errors in read_folio so buffered reads retain
their errno instead of turning corruption and allocation failures into
EIO. Keep the existing partial-read behavior.
Signed-off-by: Dandan Wu <wudandan@xxxxxxxxxx>
---
fs/ntfs/aops.c | 22 ++++++++++++++++++++--
fs/ntfs/iomap.c | 43 ++++++++++++++++++++++++++++++++++---------
2 files changed, 54 insertions(+), 11 deletions(-)
diff --git a/fs/ntfs/aops.c b/fs/ntfs/aops.c
index 7884737657d5..474e99aa123d 100644
--- a/fs/ntfs/aops.c
+++ b/fs/ntfs/aops.c
@@ -48,6 +48,22 @@ static const struct iomap_read_ops ntfs_iomap_bio_read_ops = {
.submit_read = ntfs_iomap_bio_submit_read,
};
+static int ntfs_read_folio_iomap_next(const struct iomap_iter *iter,
+ struct iomap *iomap, struct iomap *srcmap)
+{
+ int *error = iter->private;
+ int ret;
+
+ ret = ntfs_read_iomap_ops.iomap_next(iter, iomap, srcmap);
+ if (ret < 0)
+ *error = ret;
+ return ret;
+}
+
+static const struct iomap_ops ntfs_read_folio_iomap_ops = {
+ .iomap_next = ntfs_read_folio_iomap_next,
+};
+
/*
* ntfs_read_folio - Read data for a folio from the device
* @file: open file to which the folio @folio belongs or NULL
@@ -72,6 +88,7 @@ static int ntfs_read_folio(struct file *file, struct folio *folio)
.cur_folio = folio,
.ops = &ntfs_iomap_bio_read_ops,
};
+ int err = 0;
/*
* Only $DATA attributes can be encrypted and only unnamed $DATA
@@ -103,8 +120,9 @@ static int ntfs_read_folio(struct file *file, struct folio *folio)
return ntfs_read_compressed_block(folio);
}
- iomap_read_folio(&ntfs_read_iomap_ops, &ctx, NULL);
- return 0;
+ /* iomap_read_folio() otherwise discards synchronous mapping errors. */
+ iomap_read_folio(&ntfs_read_folio_iomap_ops, &ctx, &err);
+ return err;
}
/*
diff --git a/fs/ntfs/iomap.c b/fs/ntfs/iomap.c
index c248c61854cc..e0ac03dd5138 100644
--- a/fs/ntfs/iomap.c
+++ b/fs/ntfs/iomap.c
@@ -76,8 +76,13 @@ static int ntfs_read_iomap_begin_resident(struct inode *inode, loff_t offset, lo
err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len,
CASE_SENSITIVE, 0, NULL, 0, ctx);
- if (unlikely(err))
+ if (unlikely(err)) {
+ if (err == -ENOENT) {
+ err = -EFSCORRUPTED;
+ ntfs_report_file_metadata_error(inode, -EFSCORRUPTED);
+ }
goto out;
+ }
attr_len = le32_to_cpu(ctx->attr->data.resident.value_length);
if (unlikely(attr_len > ni->initialized_size))
@@ -215,11 +220,12 @@ static int ntfs_read_iomap_begin_non_resident(struct inode *inode, loff_t offset
rl_length = ntfs_cluster_to_bytes(vol, rl->length - (vcn - rl->vcn));
if (rl_length == 0 && rl->lcn > LCN_DELALLOC) {
+ ntfs_report_file_metadata_error(inode, -EFSCORRUPTED);
ntfs_error(inode->i_sb,
"runlist(vcn : %lld, length : %lld, lcn : %lld) is corrupted\n",
rl->vcn, rl->length, rl->lcn);
up_write(&ni->runlist.lock);
- return -EIO;
+ return -EFSCORRUPTED;
}
if (rl_length && length > rl_length - vcn_ofs)
@@ -418,7 +424,7 @@ static int ntfs_write_simple_iomap_begin_non_resident(struct inode *inode, loff_
err = ntfs_map_runlist(ni, vcn);
if (err) {
mutex_unlock(&mrec_ni->mrec_lock);
- return -ENOENT;
+ return err;
}
down_read(&ni->runlist.lock);
rl = ni->runlist.rl;
@@ -430,9 +436,17 @@ static int ntfs_write_simple_iomap_begin_non_resident(struct inode *inode, loff_
/* Seek to element containing target vcn. */
rl = __ntfs_attr_find_vcn_nolock(&ni->runlist, vcn);
if (IS_ERR(rl)) {
+ unsigned long irqflags;
+ s64 allocated_size;
+
+ read_lock_irqsave(&ni->size_lock, irqflags);
+ allocated_size = ni->allocated_size;
+ read_unlock_irqrestore(&ni->size_lock, irqflags);
+ if (offset < allocated_size)
+ ntfs_report_file_metadata_error(inode, -EFSCORRUPTED);
up_write(&ni->runlist.lock);
mutex_unlock(&mrec_ni->mrec_lock);
- return -EIO;
+ return offset < allocated_size ? -EFSCORRUPTED : -EIO;
}
lcn = ntfs_rl_vcn_to_lcn(rl, vcn);
@@ -451,19 +465,22 @@ static int ntfs_write_simple_iomap_begin_non_resident(struct inode *inode, loff_
* extent over clusters that are allocated on disk but unknown to us.
*/
if (lcn == LCN_RL_NOT_MAPPED) {
+ if (!map_err || map_err == -ENOENT)
+ ntfs_report_file_metadata_error(inode, -EFSCORRUPTED);
up_write(&ni->runlist.lock);
mutex_unlock(&mrec_ni->mrec_lock);
- return map_err == -ENOMEM ? -ENOMEM : -EIO;
+ return !map_err || map_err == -ENOENT ? -EFSCORRUPTED : map_err;
}
max_clu_count = min(max_clu_count, rl->length - (vcn - rl->vcn));
if (max_clu_count == 0) {
+ ntfs_report_file_metadata_error(inode, -EFSCORRUPTED);
ntfs_error(inode->i_sb,
"runlist(vcn : %lld, length : %lld) is corrupted\n",
rl->vcn, rl->length);
up_write(&ni->runlist.lock);
mutex_unlock(&mrec_ni->mrec_lock);
- return -EIO;
+ return -EFSCORRUPTED;
}
iomap->bdev = inode->i_sb->s_bdev;
@@ -506,11 +523,17 @@ static int ntfs_write_simple_iomap_begin_non_resident(struct inode *inode, loff_
rl = ntfs_runlists_merge(&ni->runlist, rlc, 0,
&new_rl_count);
if (IS_ERR(rl)) {
+ err = PTR_ERR(rl);
+ if (err != -ENOMEM) {
+ err = -EFSCORRUPTED;
+ ntfs_report_file_metadata_error(inode,
+ err);
+ }
ntfs_error(vol->sb, "Failed to merge runlists");
up_write(&ni->runlist.lock);
mutex_unlock(&mrec_ni->mrec_lock);
kvfree(rlc);
- return PTR_ERR(rl);
+ return err;
}
ni->runlist.rl = rl;
@@ -698,8 +721,10 @@ static int ntfs_write_iomap_begin_resident(struct inode *inode, loff_t offset,
err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len,
CASE_SENSITIVE, 0, NULL, 0, ctx);
if (err) {
- if (err == -ENOENT)
- err = -EIO;
+ if (err == -ENOENT) {
+ err = -EFSCORRUPTED;
+ ntfs_report_file_metadata_error(inode, -EFSCORRUPTED);
+ }
goto out;
}
--
2.51.0