[PATCH] fs/ntfs3: free clusters on truncate again when prealloc is off

From: Hengyu Liang

Date: Thu Oct 22 2026 - 12:25:58 EST


Commit ecbb433f9a8e ("fs/ntfs3: fold file size handling into
ntfs_set_size()") made truncate go through ntfs_set_size(), which
always keeps the clusters preallocated past the new size.

However, those clusters are only freed when the last writer closes the
file, and only if the prealloc mount option is set. As of now, with
prealloc=0, a file that is made smaller keeps all its clusters until
it is deleted, also across umount and mount.

The issue can be reproduced on a freshly formatted 32 MiB volume with:

mount -t ntfs3 -o loop,prealloc=0 ntfs.img /mnt
dd if=/dev/zero of=/mnt/f bs=1M count=8 conv=fsync
truncate -s 0 /mnt/f
df -k /mnt

Before that commit, df shows the 8 MiB as free again. After it, they
stay in use until /mnt/f is removed.

This patch makes ntfs_set_size() keep preallocated clusters only with
the prealloc mount option, as ntfs_truncate() did before. fallocate()
with FALLOC_FL_KEEP_SIZE still keeps the clusters it allocates past
i_size.

Fixes: ecbb433f9a8e ("fs/ntfs3: fold file size handling into ntfs_set_size()")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Hengyu Liang <hengyul@xxxxxxxxxx>
---
Tested in KVM with KASAN on ntfs3 master da4391c496fe, without and
with this patch, on a fresh 32 MiB mkntfs image:
- The commands above: with prealloc=0, the used space after truncate
is 11816K without the patch, also after remount, and 3624K with it.
- fallocate() with FALLOC_FL_KEEP_SIZE of 8 MiB on a 1-byte file,
with prealloc=0 and with the default options: same results with and
without the patch.
- truncate(2) by path on the default mount: same results.
- 11 runs of a random write/ftruncate model test (15,900 operations,
buffered and direct I/O, default, prealloc=0 and sparse mounts, and
a nearly full volume): same results with and without the patch.

fs/ntfs3/file.c | 3 ++-
fs/ntfs3/inode.c | 12 +++++++++---
fs/ntfs3/ntfs_fs.h | 1 +
3 files changed, 12 insertions(+), 4 deletions(-)

diff --git a/fs/ntfs3/file.c b/fs/ntfs3/file.c
index c11da747270f..2afc2fe8f009 100644
--- a/fs/ntfs3/file.c
+++ b/fs/ntfs3/file.c
@@ -680,7 +680,8 @@ static long ntfs_fallocate(struct file *file, int mode, loff_t vbo, loff_t len)
}

if (mode & FALLOC_FL_KEEP_SIZE) {
- err = ntfs_set_size(inode, i_size);
+ /* Keep the clusters just allocated past i_size. */
+ err = ntfs_set_size_ex(inode, i_size, true);
if (err)
goto out;
}
diff --git a/fs/ntfs3/inode.c b/fs/ntfs3/inode.c
index 8adbdf1a7d11..72ecd802a8cf 100644
--- a/fs/ntfs3/inode.c
+++ b/fs/ntfs3/inode.c
@@ -887,7 +887,7 @@ static void ntfs_readahead(struct readahead_control *rac)
iomap_readahead(&ntfs_iomap_ops, &ctx, NULL);
}

-int ntfs_set_size(struct inode *inode, u64 new_size)
+int ntfs_set_size_ex(struct inode *inode, u64 new_size, bool keep_prealloc)
{
struct super_block *sb = inode->i_sb;
struct ntfs_sb_info *sbi = sb->s_fs_info;
@@ -911,9 +911,8 @@ int ntfs_set_size(struct inode *inode, u64 new_size)
if (new_size < ni->i_valid)
ni->i_valid = new_size;

- /* last 'true' means keep preallocated. */
err = attr_set_size(ni, ATTR_DATA, ni->file.ads.name, ni->file.ads.len,
- &ni->file.run, new_size, &ni->i_valid, true);
+ &ni->file.run, new_size, &ni->i_valid, keep_prealloc);

up_write(&ni->file.run_lock);
ni_unlock(ni);
@@ -921,6 +920,13 @@ int ntfs_set_size(struct inode *inode, u64 new_size)
return err;
}

+/* Keep clusters preallocated past the new size only with "prealloc". */
+int ntfs_set_size(struct inode *inode, u64 new_size)
+{
+ return ntfs_set_size_ex(inode, new_size,
+ ntfs_sb(inode->i_sb)->options->prealloc);
+}
+
/*
* Special value to detect ntfs_writeback_range call
*/
diff --git a/fs/ntfs3/ntfs_fs.h b/fs/ntfs3/ntfs_fs.h
index 90ccfde951dc..0dd9efa2c88c 100644
--- a/fs/ntfs3/ntfs_fs.h
+++ b/fs/ntfs3/ntfs_fs.h
@@ -801,6 +801,7 @@ static inline struct inode *ntfs_iget5(struct super_block *sb,
return ntfs_iget5_flags(sb, ref, name, 0);
}
int ntfs_set_size(struct inode *inode, u64 new_size);
+int ntfs_set_size_ex(struct inode *inode, u64 new_size, bool keep_prealloc);
int ntfs3_write_inode(struct inode *inode, struct writeback_control *wbc);
int ntfs_sync_inode(struct inode *inode);
int inode_read_data(struct inode *inode, void *data, size_t bytes);

base-commit: da4391c496fedc1ca78a99907712f2256ce8fb80
--
2.53.0