ntfs: write MFT records through containing I/O units

Use the configured MFT I/O unit for direct writes, mirror updates, and
generic folio writeback. On native 4Kn devices, multiple MFT records can
share a device block, so submit complete containing units while preserving
unselected neighboring records.

Track selected records separately from records owned by direct inode
writeback, and retain the existing runlist and cluster-boundary checks.
Force direct MFT writes to complete synchronously when the I/O unit is
larger than an individual record. the following patch restores
caller-controlled completion.

Fixes: 115380f9a2 ("ntfs: update mft operations")
Cc: stable@vger.kernel.org
Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
This commit is contained in:
Hyunchul Lee 2026-09-21 15:44:09 +09:00 committed by Namjae Jeon
parent 1df655bcdd
commit 4437d9d787

View File

@ -597,13 +597,18 @@ static int ntfs_sync_mft_mirror_unit(struct ntfs_volume *vol,
static int ntfs_sync_mft_mirror_record(struct ntfs_volume *vol,
struct folio *source, const u64 mft_no)
{
u64 mirror_file_ofs = (u64)mft_no * vol->mft_record_size;
struct ntfs_mft_io_unit unit = {
.folio_ofs = NTFS_MFT_NR_TO_POFS(vol, mft_no),
.len = vol->mft_record_size,
};
return ntfs_sync_mft_mirror_unit(vol, source,
(u64)mft_no * vol->mft_record_size, &unit);
mirror_file_ofs =
round_down(mirror_file_ofs, (u64)vol->mft_io_unit_size);
unit.folio_ofs = round_down(unit.folio_ofs, vol->mft_io_unit_size);
unit.len = vol->mft_io_unit_size;
return ntfs_sync_mft_mirror_unit(vol, source, mirror_file_ofs, &unit);
}
static int ntfs_prepare_mft_record_io_units(struct ntfs_inode *ni,
@ -623,6 +628,18 @@ static int ntfs_prepare_mft_record_io_units(struct ntfs_inode *ni,
return -EIO;
cluster_ofs = ntfs_bytes_to_cluster_off(vol, record_byte);
if (vol->mft_io_unit_size > vol->mft_record_size) {
cluster_ofs = round_down(cluster_ofs, vol->mft_io_unit_size);
disk_byte = NTFS_CLU_TO_B(vol, ni->mft_lcn[0]) + cluster_ofs;
units[0] = (struct ntfs_mft_io_unit){
.sector = ntfs_bytes_to_bio_sector(disk_byte),
.folio_ofs = round_down(ni->folio_ofs,
vol->mft_io_unit_size),
.len = vol->mft_io_unit_size,
};
return 1;
}
disk_byte = NTFS_CLU_TO_B(vol, ni->mft_lcn[0]) + cluster_ofs;
units[0] = (struct ntfs_mft_io_unit){
.sector = ntfs_bytes_to_bio_sector(disk_byte),
@ -676,6 +693,8 @@ int write_mft_record_nolock(struct ntfs_inode *ni, struct mft_record *m, int syn
WARN_ON(NInoAttr(ni));
WARN_ON(!folio_test_locked(folio));
if (vol->mft_io_unit_size > vol->mft_record_size)
sync = 1;
if (folio_test_writeback(folio))
folio_wait_writeback(folio);
@ -3063,7 +3082,7 @@ static int ntfs_map_mft_io_for_folio(struct ntfs_inode *ni, u64 folio_byte,
static int ntfs_prepare_mft_folio_units(struct ntfs_inode *ni, u64 folio_byte,
u64 file_limit,
const unsigned long *record_writable,
const unsigned long *record_selected,
struct ntfs_mft_io_unit *units,
unsigned int *nr_units,
unsigned int max_units, bool *defer)
@ -3075,28 +3094,37 @@ static int ntfs_prepare_mft_folio_units(struct ntfs_inode *ni, u64 folio_byte,
while (unit_byte < folio_end && unit_byte < file_limit) {
struct ntfs_mft_io_unit unit;
u64 record_byte;
u64 cluster_end = ntfs_cluster_to_bytes(
vol, ntfs_bytes_to_cluster(vol, unit_byte) + 1);
u64 record_end =
round_down(unit_byte, (u64)vol->mft_record_size) +
vol->mft_record_size;
u64 unit_end = min3(record_end, cluster_end, folio_end);
bool writable = false;
u64 unit_end;
u64 io_unit_end;
u64 cluster_end;
bool selected = false;
int err;
io_unit_end =
round_down(unit_byte, (u64)vol->mft_io_unit_size) +
vol->mft_io_unit_size;
cluster_end = ntfs_cluster_to_bytes(
vol, ntfs_bytes_to_cluster(vol, unit_byte) + 1);
unit_end = min3(io_unit_end, cluster_end, folio_end);
record_byte = round_down(unit_byte, (u64)vol->mft_record_size);
while (record_byte < min(unit_end, file_limit)) {
unsigned int record;
record = div_u64(record_byte - folio_byte,
vol->mft_record_size);
if (test_bit(record, record_writable)) {
writable = true;
if (test_bit(record, record_selected)) {
selected = true;
break;
}
record_byte += vol->mft_record_size;
}
if (!writable)
/*
* Direct inode writeback owns unselected records. Their folio
* images are stable while this folio is locked, so a containing
* unit can preserve them when writing a selected record.
*/
if (!selected)
goto next;
unit = (struct ntfs_mft_io_unit){
@ -3153,7 +3181,7 @@ static int ntfs_write_mft_block(struct folio *folio, struct writeback_control *w
struct bio *parent = NULL, *child = NULL;
struct ntfs_mft_write_ctx *ctx = NULL;
u8 *kaddr = NULL;
DECLARE_BITMAP(record_writable, PAGE_SIZE / NTFS_BLOCK_SIZE) = {};
DECLARE_BITMAP(record_selected, PAGE_SIZE / NTFS_BLOCK_SIZE) = {};
u64 folio_byte, file_limit, folio_end, mirror_size;
unsigned int nr_records, nr_units = 0, max_units;
unsigned int nr_locked_nis = 0, nr_ref_inos = 0;
@ -3221,11 +3249,11 @@ static int ntfs_write_mft_block(struct folio *folio, struct writeback_control *w
tni->ext.base_ntfs_ino == NTFS_I(vol->mft_ino))
continue;
}
__set_bit(record, record_writable);
__set_bit(record, record_selected);
}
err = ntfs_prepare_mft_folio_units(ni, folio_byte, file_limit,
record_writable, units, &nr_units,
record_selected, units, &nr_units,
max_units, &defer);
if (err)
goto out_noio;
@ -3309,6 +3337,7 @@ static int ntfs_write_mft_block(struct folio *folio, struct writeback_control *w
submit_bio(parent);
ntfs_release_mft_write_refs(locked_nis, nr_locked_nis, ref_inos,
nr_ref_inos);
return 0;
out_noio: