[PATCH v4 22/31] ntfs: report index update and recovery failures

From: Baolin Liu

Date: Tue Oct 20 2026 - 04:53:04 EST


From: Dandan Wu <wudandan@xxxxxxxxxx>

Index bitmap updates, root relocation and entry deletion can fail
after metadata has changed without reporting the incomplete update.
Report failed cleanup and restoration against the index owner,
including ENOMEM that leaves an unfinished deletion.

Report malformed bitmap and tree state as EFSCORRUPTED. Preserve
independent recovery errors and keep early allocation failures distinct.

Signed-off-by: Dandan Wu <wudandan@xxxxxxxxxx>
---
fs/ntfs/index.c | 77 +++++++++++++++++++++++++++++++++++++++----------
1 file changed, 61 insertions(+), 16 deletions(-)

diff --git a/fs/ntfs/index.c b/fs/ntfs/index.c
index ab678998afc8..4887dece73e3 100644
--- a/fs/ntfs/index.c
+++ b/fs/ntfs/index.c
@@ -1084,6 +1084,11 @@ static int ntfs_ibm_modify(struct ntfs_index_context *icx, s64 vcn, int set)

bmp_vi = ntfs_attr_iget(VFS_I(icx->idx_ni), AT_BITMAP, icx->name, icx->name_len);
if (IS_ERR(bmp_vi)) {
+ if (PTR_ERR(bmp_vi) == -ENOENT) {
+ ntfs_report_file_metadata_error(VFS_I(icx->idx_ni),
+ -EFSCORRUPTED);
+ bmp_vi = ERR_PTR(-EFSCORRUPTED);
+ }
ntfs_error(icx->idx_ni->vol->sb, "Failed to open $BITMAP attribute");
return PTR_ERR(bmp_vi);
}
@@ -1101,8 +1106,12 @@ static int ntfs_ibm_modify(struct ntfs_index_context *icx, s64 vcn, int set)
}
}

- if (ntfs_inode_attr_pread(bmp_vi, bpos, 1, &byte) != 1) {
- ret = -EIO;
+ ret = ntfs_inode_attr_pread(bmp_vi, bpos, 1, &byte);
+ if (ret != 1) {
+ if (ret >= 0 || ret == -EINVAL)
+ ret = -EFSCORRUPTED;
+ if (ret == -EIO || ret == -EFSCORRUPTED)
+ ntfs_report_file_metadata_error(VFS_I(icx->idx_ni), ret);
ntfs_error(icx->idx_ni->vol->sb, "Failed to read $BITMAP");
goto err;
}
@@ -1112,11 +1121,15 @@ static int ntfs_ibm_modify(struct ntfs_index_context *icx, s64 vcn, int set)
else
byte &= ~bit;

- if (ntfs_inode_attr_pwrite(bmp_vi, bpos, 1, &byte, false) != 1) {
- ret = -EIO;
+ ret = ntfs_inode_attr_pwrite(bmp_vi, bpos, 1, &byte, false);
+ if (ret != 1) {
+ if (ret >= 0)
+ ret = -EIO;
+ ntfs_report_file_metadata_error(VFS_I(icx->idx_ni), ret);
ntfs_error(icx->idx_ni->vol->sb, "Failed to write $Bitmap");
goto err;
}
+ ret = 0;

err:
iput(bmp_vi);
@@ -1130,7 +1143,12 @@ static int ntfs_ibm_set(struct ntfs_index_context *icx, s64 vcn)

static int ntfs_ibm_clear(struct ntfs_index_context *icx, s64 vcn)
{
- return ntfs_ibm_modify(icx, vcn, 0);
+ int err = ntfs_ibm_modify(icx, vcn, 0);
+
+ /* Failure leaves an index block allocated, including during rollback. */
+ if (err)
+ ntfs_report_file_metadata_error(VFS_I(icx->idx_ni), err);
+ return err;
}

static s64 ntfs_ibm_get_free(struct ntfs_index_context *icx)
@@ -1144,8 +1162,14 @@ static s64 ntfs_ibm_get_free(struct ntfs_index_context *icx)

bm = ntfs_attr_readall(icx->idx_ni, AT_BITMAP, icx->name, icx->name_len,
&size);
- if (IS_ERR(bm))
+ if (IS_ERR(bm)) {
+ if (PTR_ERR(bm) == -ENOENT) {
+ ntfs_report_file_metadata_error(VFS_I(icx->idx_ni),
+ -EFSCORRUPTED);
+ bm = ERR_PTR(-EFSCORRUPTED);
+ }
return PTR_ERR(bm);
+ }

for (byte = 0; byte < size; byte++) {
if (bm[byte] == 255)
@@ -1445,10 +1469,13 @@ static int ntfs_ir_reparent(struct ntfs_index_context *icx)
ntfs_attr_put_search_ctx(ctx);
ctx = NULL;
rollback_ret = ntfs_ir_move_to_base(icx);
- if (rollback_ret)
+ if (rollback_ret) {
+ ntfs_report_file_metadata_error(VFS_I(icx->idx_ni),
+ rollback_ret);
ntfs_error(icx->idx_ni->vol->sb,
"Failed to roll back INDEX_ROOT relocation: %d",
rollback_ret);
+ }
goto clear_bmp;
}

@@ -1916,8 +1943,11 @@ static int ntfs_ih_reparent_end(struct ntfs_index_context *icx, struct index_hea

ie = ntfs_ie_get_by_pos(ih, ntfs_icx_parent_pos(icx));
ie_prev = ntfs_ie_prev(ih, ie);
- if (!ie_prev)
- return -EIO;
+ if (!ie_prev) {
+ ntfs_report_file_metadata_error(VFS_I(icx->idx_ni),
+ -EFSCORRUPTED);
+ return -EFSCORRUPTED;
+ }
ntfs_ie_set_vcn(ie, ntfs_ie_get_vcn(ie_prev));

return ntfs_ih_takeout(icx, ih, ie_prev, ib);
@@ -1944,8 +1974,10 @@ static int ntfs_index_rm_leaf(struct ntfs_index_context *icx)
parent_ih = &icx->ir->index;
else {
ib = kvzalloc(icx->block_size, GFP_NOFS);
- if (!ib)
- return -ENOMEM;
+ if (!ib) {
+ ret = -ENOMEM;
+ goto out;
+ }

ret = ntfs_ib_read(icx, ntfs_icx_parent_vcn(icx), ib);
if (ret)
@@ -1972,6 +2004,9 @@ static int ntfs_index_rm_leaf(struct ntfs_index_context *icx)

ret = ntfs_ih_reparent_end(icx, parent_ih, ib);
out:
+ /* The allocation bit is clear, but the parent update did not finish. */
+ if (ret)
+ ntfs_report_file_metadata_error(VFS_I(icx->idx_ni), ret);
kvfree(ib);
return ret;
}
@@ -2023,7 +2058,9 @@ static int ntfs_index_rm_node(struct ntfs_index_context *icx)
goto descend;

if (ntfs_ih_zero_entry(&ib->index)) {
- ret = -EIO;
+ ntfs_report_file_metadata_error(VFS_I(icx->idx_ni),
+ -EFSCORRUPTED);
+ ret = -EFSCORRUPTED;
ntfs_error(icx->idx_ni->vol->sb, "Empty index block");
goto out;
}
@@ -2068,10 +2105,14 @@ static int ntfs_index_rm_node(struct ntfs_index_context *icx)
ntfs_ie_delete(ih, entry);
ntfs_ie_insert(ih, ie, entry);

- if (icx->is_in_root)
+ if (icx->is_in_root) {
ret = ntfs_ir_truncate(icx, new_size);
- else
+ /* The old key has been replaced without removing its successor. */
+ if (ret)
+ ntfs_report_file_metadata_error(VFS_I(icx->idx_ni), ret);
+ } else {
ret = ntfs_icx_ib_write(icx);
+ }
if (ret)
goto out2;

@@ -2120,10 +2161,14 @@ int ntfs_index_rm(struct ntfs_index_context *icx)
} else if (icx->is_in_root || !ntfs_ih_one_entry(ih)) {
ntfs_ie_delete(ih, icx->entry);

- if (icx->is_in_root)
+ if (icx->is_in_root) {
ret = ntfs_ir_truncate(icx, le32_to_cpu(ih->index_length));
- else
+ /* The entry is already removed and will not be restored. */
+ if (ret)
+ ntfs_report_file_metadata_error(VFS_I(icx->idx_ni), ret);
+ } else {
ret = ntfs_icx_ib_write(icx);
+ }
if (ret)
goto err_out;
} else {
--
2.51.0