ntfs: write MFT mirror data in mapped I/O units

Replace the record-specific mirror writer with a unit-based helper
and use a stack-allocated BIO for synchronous mirror writes.

Route write_mft_record_nolock() and ntfs_write_mft_block() through
the common mirror primitive. The helper only returns errors, So callers
handle volume error reporting and retry policy.

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:05 +09:00 committed by Namjae Jeon
parent dfe7ed335b
commit b9fa884a39
2 changed files with 72 additions and 70 deletions

View File

@ -429,6 +429,12 @@ void __mark_mft_record_dirty(struct ntfs_inode *ni)
__mark_inode_dirty(VFS_I(base_ni), I_DIRTY_DATASYNC);
}
struct ntfs_mft_io_unit {
sector_t sector;
unsigned int folio_ofs;
unsigned int len;
};
/*
* ntfs_bio_end_io - bio completion callback for MFT record writes
*
@ -445,65 +451,60 @@ static void ntfs_bio_end_io(struct bio *bio)
}
/*
* ntfs_sync_mft_mirror - synchronize an mft record to the mft mirror
* @vol: ntfs volume on which the mft record to synchronize resides
* @mft_no: mft record number of mft record to synchronize
* @m: mapped, mst protected (extent) mft record to synchronize
*
* Write the mapped, mst protected (extent) mft record @m with mft record
* number @mft_no to the mft mirror ($MFTMirr) of the ntfs volume @vol.
*
* On success return 0. On error return -errno and set the volume errors flag
* in the ntfs volume @vol.
*
* NOTE: We always perform synchronous i/o.
* Write one MFT I/O unit to $MFTMirr. The source folio contains the MST
* protected image that will be written to $MFT.
*/
int ntfs_sync_mft_mirror(struct ntfs_volume *vol, const u64 mft_no,
struct mft_record *m)
static int ntfs_sync_mft_mirror_unit(struct ntfs_volume *vol,
struct folio *source, u64 mirror_file_ofs,
const struct ntfs_mft_io_unit *unit)
{
u8 *kmirr;
struct folio *folio;
unsigned int folio_ofs;
int err = 0;
struct bio *bio;
struct folio *mirror;
struct bio_vec bvec;
struct bio bio;
u64 mirror_size;
unsigned int mirror_ofs;
u8 *src, *dst;
int err;
ntfs_debug("Entering for inode 0x%llx.", mft_no);
if (unlikely(!vol->mftmirr_ino))
return -EIO;
if (unlikely(!vol->mftmirr_ino)) {
/* This could happen during umount... */
mirror_size = (u64)vol->mftmirr_size * vol->mft_record_size;
if (mirror_file_ofs >= mirror_size ||
unit->len > mirror_size - mirror_file_ofs)
return -EIO;
if (unit->folio_ofs + unit->len > folio_size(source))
return -EIO;
mirror = read_mapping_folio(vol->mftmirr_ino->i_mapping,
mirror_file_ofs >> PAGE_SHIFT, NULL);
if (IS_ERR(mirror))
return PTR_ERR(mirror);
folio_lock(mirror);
if (folio_test_writeback(mirror))
folio_wait_writeback(mirror);
mirror_ofs = mirror_file_ofs - folio_pos(mirror);
if (mirror_ofs + unit->len > folio_size(mirror)) {
err = -EIO;
goto err_out;
}
/* Get the page containing the mirror copy of the mft record @m. */
folio = read_mapping_folio(vol->mftmirr_ino->i_mapping,
NTFS_MFT_NR_TO_PIDX(vol, mft_no), NULL);
if (IS_ERR(folio)) {
ntfs_error(vol->sb, "Failed to map mft mirror page.");
err = PTR_ERR(folio);
goto err_out;
goto out_unlock;
}
folio_lock(folio);
folio_clear_uptodate(folio);
/* Offset of the mft mirror record inside the page. */
folio_ofs = NTFS_MFT_NR_TO_POFS(vol, mft_no);
/* The address in the page of the mirror copy of the mft record @m. */
kmirr = kmap_local_folio(folio, 0) + folio_ofs;
/* Copy the mst protected mft record to the mirror. */
memcpy(kmirr, m, vol->mft_record_size);
kunmap_local(kmirr);
folio_clear_uptodate(mirror);
src = kmap_local_folio(source, unit->folio_ofs);
dst = kmap_local_folio(mirror, mirror_ofs);
memcpy(dst, src, unit->len);
kunmap_local(dst);
kunmap_local(src);
bio = bio_alloc(vol->sb->s_bdev, 1, REQ_OP_WRITE, GFP_NOIO);
bio->bi_iter.bi_sector =
ntfs_bytes_to_bio_sector(NTFS_CLU_TO_B(vol, vol->mftmirr_lcn) +
((u64)folio->index << PAGE_SHIFT) +
folio_ofs);
if (bio_add_folio(bio, folio, vol->mft_record_size, folio_ofs))
err = submit_bio_wait(bio);
bio_init(&bio, vol->sb->s_bdev, &bvec, 1, REQ_OP_WRITE);
bio.bi_iter.bi_sector = ntfs_bytes_to_bio_sector(NTFS_CLU_TO_B(vol, vol->mftmirr_lcn) +
mirror_file_ofs);
if (!bio_add_folio(&bio, mirror, unit->len, mirror_ofs))
err = -EIO;
else
err = -EIO;
bio_put(bio);
err = submit_bio_wait(&bio);
bio_uninit(&bio);
/*
* The in-memory mirror is now valid because we just memcpy()'d the
@ -512,23 +513,26 @@ int ntfs_sync_mft_mirror(struct ntfs_volume *vol, const u64 mft_no,
* the stale on-disk mirror and overwrite this copy. The error is
* propagated to the caller via @err.
*/
folio_mark_uptodate(folio);
folio_mark_uptodate(mirror);
folio_unlock(folio);
folio_put(folio);
if (likely(!err)) {
ntfs_debug("Done.");
} else {
ntfs_error(vol->sb, "I/O error while writing mft mirror record 0x%llx!", mft_no);
err_out:
ntfs_error(vol->sb,
"Failed to synchronize $MFTMirr (error code %i). Volume will be left marked dirty on umount. Run chkdsk on the partition after umounting to correct this.",
err);
NVolSetErrors(vol);
}
out_unlock:
folio_unlock(mirror);
folio_put(mirror);
return err;
}
static int ntfs_sync_mft_mirror_record(struct ntfs_volume *vol,
struct folio *source, const u64 mft_no)
{
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);
}
/*
* write_mft_record_nolock - write out a mapped (extent) mft record
* @ni: ntfs inode describing the mapped (extent) mft record
@ -600,8 +604,8 @@ int write_mft_record_nolock(struct ntfs_inode *ni, struct mft_record *m, int syn
/* Synchronize the mft mirror now if not @sync. */
if (!sync && ni->mft_no < vol->mftmirr_size) {
int sub_err = ntfs_sync_mft_mirror(vol, ni->mft_no,
fixup_m);
int sub_err = ntfs_sync_mft_mirror_record(vol, folio,
ni->mft_no);
if (unlikely(sub_err) && !err)
err = sub_err;
}
@ -624,7 +628,8 @@ int write_mft_record_nolock(struct ntfs_inode *ni, struct mft_record *m, int syn
/* If @sync, now synchronize the mft mirror. */
if (sync && ni->mft_no < vol->mftmirr_size) {
int sub_err = ntfs_sync_mft_mirror(vol, ni->mft_no, fixup_m);
int sub_err =
ntfs_sync_mft_mirror_record(vol, folio, ni->mft_no);
if (unlikely(sub_err) && !err)
err = sub_err;
@ -3031,8 +3036,7 @@ static int ntfs_write_mft_block(struct folio *folio, struct writeback_control *w
prev_mft_ofs = mft_ofs;
if (mft_no < vol->mftmirr_size) {
int sub_err = ntfs_sync_mft_mirror(vol, mft_no,
(struct mft_record *)(kaddr + mft_ofs));
int sub_err = ntfs_sync_mft_mirror_record(vol, folio, mft_no);
if (unlikely(sub_err) && !err)
err = sub_err;

View File

@ -42,8 +42,6 @@ static inline void mark_mft_record_dirty(struct ntfs_inode *ni)
__mark_mft_record_dirty(ni);
}
int ntfs_sync_mft_mirror(struct ntfs_volume *vol, const u64 mft_no,
struct mft_record *m);
int write_mft_record_nolock(struct ntfs_inode *ni, struct mft_record *m, int sync);
/*