[PATCH v4 02/31] ntfs: preserve cluster-free counts in attribute cleanup

From: Baolin Liu

Date: Tue Oct 20 2026 - 04:50:05 EST


From: Baolin Liu <liubaolin@xxxxxxxxxx>

ntfs_cluster_free() returns a signed 64-bit cluster count on success,
not an integer status. Several attribute cleanup paths truncate that
count or treat a positive count as an error. ntfs_attr_rm() can even
return the count to callers that expect zero on success.

Store the result in s64 variables, test negative results for failure,
and compare the full count when validating an allocation rollback.
Return zero from ntfs_attr_rm() after successful removal. This keeps
successful deallocation from being misclassified by later reporting.

Fixes: 495e90fa3348 ("ntfs: update attrib operations")
Signed-off-by: Baolin Liu <liubaolin@xxxxxxxxxx>
---
fs/ntfs/attrib.c | 27 +++++++++++++++------------
1 file changed, 15 insertions(+), 12 deletions(-)

diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c
index fce7144d7bef..0be410973176 100644
--- a/fs/ntfs/attrib.c
+++ b/fs/ntfs/attrib.c
@@ -4164,7 +4164,7 @@ static int ntfs_attr_make_resident(struct ntfs_inode *ni, struct ntfs_attr_searc
struct super_block *sb = vol->sb;
struct attr_record *a = ctx->attr;
int name_ofs, val_ofs, err;
- s64 arec_size;
+ s64 arec_size, freed;

ntfs_debug("Entering for inode 0x%llx, attr 0x%x.\n",
(unsigned long long)ni->mft_no, ni->type);
@@ -4252,8 +4252,8 @@ static int ntfs_attr_make_resident(struct ntfs_inode *ni, struct ntfs_attr_searc
* the whole run list and thus it doesn't matter that the attribute
* record is in a transiently corrupted state at this moment in time.
*/
- err = ntfs_cluster_free(ni, 0, -1, ctx);
- if (err) {
+ freed = ntfs_cluster_free(ni, 0, -1, ctx);
+ if (freed < 0) {
ntfs_error(sb, "Eeek! Failed to release allocated clusters");
ntfs_debug("Ignoring error and leaving behind wasted clusters.\n");
}
@@ -4496,6 +4496,7 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz
struct ntfs_attr_search_ctx *ctx = NULL;
struct runlist_element *rl, *rln;
s64 org_alloc_size, org_compressed_size;
+ s64 freed;
int err, err2;
struct ntfs_inode *base_ni;
struct super_block *sb = ni->vol->sb;
@@ -4760,9 +4761,9 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz
down_write(&ni->runlist.lock);
rollback_locked:
/* Free allocated clusters. */
- err2 = ntfs_cluster_free(ni, ntfs_bytes_to_cluster(vol, org_alloc_size),
- -1, ctx);
- if (err2)
+ freed = ntfs_cluster_free(ni, ntfs_bytes_to_cluster(vol, org_alloc_size),
+ -1, ctx);
+ if (freed < 0)
ntfs_debug("Leaking clusters");

/*
@@ -5229,6 +5230,7 @@ int ntfs_attr_map_cluster(struct ntfs_inode *ni, s64 vcn_start, s64 *lcn_start,
struct runlist_element *rl, *rlc;
struct runlist_element *old_rl = NULL;
s64 vcn = vcn_start, lcn, clu_count;
+ s64 freed;
s64 lcn_seek_from = -1;
int err = 0;
size_t new_rl_count, old_rl_count;
@@ -5359,10 +5361,8 @@ int ntfs_attr_map_cluster(struct ntfs_inode *ni, s64 vcn_start, s64 *lcn_start,
ntfs_attr_reinit_search_ctx(ctx);
err = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni);
if (err) {
- int err2;
-
- err2 = ntfs_cluster_free(ni, vcn, clu_count, ctx);
- if (err2 < 0 || err2 != clu_count) {
+ freed = ntfs_cluster_free(ni, vcn, clu_count, ctx);
+ if (freed < 0 || freed != clu_count) {
ntfs_error(vol->sb,
"Failed to free cluster allocation. Leaving inconsistent metadata.\n");
NVolSetErrors(vol);
@@ -5410,6 +5410,7 @@ int ntfs_attr_rm(struct ntfs_inode *ni)
{
struct ntfs_attr_search_ctx *ctx;
int err = 0, ret = 0;
+ s64 freed = 0;
struct ntfs_inode *base_ni;
struct super_block *sb = ni->vol->sb;

@@ -5431,10 +5432,12 @@ int ntfs_attr_rm(struct ntfs_inode *ni)
return -ENOMEM;
}

- ret = ntfs_cluster_free(ni, 0, -1, ctx);
- if (ret < 0)
+ freed = ntfs_cluster_free(ni, 0, -1, ctx);
+ if (freed < 0) {
+ ret = freed;
ntfs_error(sb,
"Failed to free cluster allocation. Leaving inconstant metadata.\n");
+ }
ntfs_attr_put_search_ctx(ctx);
}

--
2.51.0