From 631946431ddc66a472c5cc629cd654e62dfa1f88 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Fri, 4 Sep 2026 14:46:38 +0900 Subject: [PATCH] ntfs: account for MFT records added during allocation When no free MFT record is available in the initialized $MFT/$BITMAP, ntfs_mft_record_alloc() extends $MFT/$DATA and formats the requested record together with a dynamically sized tail reserve. Those records become visible through the $MFT file size before charging the requested record to the free-record counter. Account for all newly visible records before releasing the MFT allocation lock, then subtract the one record being allocated. Keep MFT counter updates independent of the asynchronous free-cluster scan and update the counter when a record is successfully cleared in the MFT bitmap. Store the clamped result of the MFT bitmap scan and keep statfs from exposing an invalid cached count if an accounting error occurs. Fixes: 115380f9a2f9 ("ntfs: update mft operations") Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/mft.c | 11 ++++++++--- fs/ntfs/super.c | 12 +++++++++--- fs/ntfs/volume.h | 6 ------ 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index 61d4ee3a57fd..f0656d0bbeeb 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -1539,7 +1539,6 @@ static int ntfs_mft_bitmap_extend_initialized_nolock(struct ntfs_volume *vol) ret = ntfs_attr_set(mftbmp_ni, old_initialized_size, 8, 0); if (likely(!ret)) { ntfs_debug("Done. (Wrote eight initialized bytes to mft bitmap."); - ntfs_inc_free_mft_records(vol, 8 * 8); return 0; } ntfs_error(vol->sb, "Failed to write to mft bitmap."); @@ -2135,6 +2134,7 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, struct mft_record **ni_mrec, const s64 mft_data_vcn) { s64 ll, bit, old_data_initialized, old_data_size; + s64 nr_new_mft_records = 0; s64 max_mft_no = -1, reserve_start = -1, reserve_end = -1; s64 candidate_reserve_end = -1; s64 *reserve_endp; @@ -2501,8 +2501,13 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, mft_ni->initialized_size); WARN_ON(i_size_read(vol->mft_ino) > mft_ni->allocated_size); WARN_ON(mft_ni->initialized_size > i_size_read(vol->mft_ino)); + nr_new_mft_records = (i_size_read(vol->mft_ino) - old_data_size) >> + vol->mft_record_size_bits; read_unlock_irqrestore(&mft_ni->size_lock, flags); mft_rec_already_initialized: + /* Account for newly visible MFT records before dropping the lock. */ + if (nr_new_mft_records > 0) + ntfs_inc_free_mft_records(vol, nr_new_mft_records); /* * We can finally drop the mft bitmap lock as the mft data attribute * has been fully updated. The only disparity left is that the @@ -2813,6 +2818,8 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni) if (base_ni->mft_no != FILE_MFT) down_write(&vol->mftbmp_lock); err = ntfs_bitmap_clear_bit(vol->mftbmp_ino, mft_no); + if (!err) + ntfs_inc_free_mft_records(vol, 1); if (!err && base_ni->mft_no == FILE_MFT && mft_no + 1 == vol->mft_record_reserve_pos && mft_no < vol->mft_record_reserve_end) @@ -2822,9 +2829,7 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni) memalloc_nofs_restore(memalloc_flags); if (err) goto bitmap_rollback; - unmap_mft_record(ni); - ntfs_inc_free_mft_records(vol, 1); return 0; /* Rollback what we did... */ diff --git a/fs/ntfs/super.c b/fs/ntfs/super.c index 60d43339c590..0867d6a82ebe 100644 --- a/fs/ntfs/super.c +++ b/fs/ntfs/super.c @@ -2064,8 +2064,7 @@ static unsigned long __get_nr_free_mft_records(struct ntfs_volume *vol, /* If errors occurred we may well have gone below zero, fix this. */ if (nr_free < 0) nr_free = 0; - else - atomic64_set(&vol->free_mft_records, nr_free); + atomic64_set(&vol->free_mft_records, nr_free); ntfs_debug("Exiting."); return nr_free; @@ -2131,7 +2130,14 @@ static int ntfs_statfs(struct dentry *dentry, struct kstatfs *sfs) read_unlock_irqrestore(&mft_ni->size_lock, flags); /* Free inodes in fs (based on current total count). */ - sfs->f_ffree = atomic64_read(&vol->free_mft_records); + size = atomic64_read(&vol->free_mft_records); + if (unlikely(size < 0 || size > (s64)sfs->f_files)) + ntfs_warning(vol->sb, "Invalid free MFT record count %lld.", size); + if (size < 0) + size = 0; + else if (size > (s64)sfs->f_files) + size = sfs->f_files; + sfs->f_ffree = size; /* * File system id. This is extremely *nix flavour dependent and even diff --git a/fs/ntfs/volume.h b/fs/ntfs/volume.h index 2b60d14fc7ef..bc85a9592245 100644 --- a/fs/ntfs/volume.h +++ b/fs/ntfs/volume.h @@ -258,17 +258,11 @@ static inline void ntfs_dec_free_clusters(struct ntfs_volume *vol, s64 nr) static inline void ntfs_inc_free_mft_records(struct ntfs_volume *vol, s64 nr) { - if (!NVolFreeClusterKnown(vol)) - return; - atomic64_add(nr, &vol->free_mft_records); } static inline void ntfs_dec_free_mft_records(struct ntfs_volume *vol, s64 nr) { - if (!NVolFreeClusterKnown(vol)) - return; - atomic64_sub(nr, &vol->free_mft_records); }