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: 115380f9a2 ("ntfs: update mft operations")
Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
This commit is contained in:
Namjae Jeon 2026-09-04 14:46:38 +09:00
parent b1d732e62a
commit 631946431d
3 changed files with 17 additions and 12 deletions

View File

@ -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... */

View File

@ -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

View File

@ -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);
}