diff --git a/ntfsprogs/ntfsfix.c b/ntfsprogs/ntfsfix.c index 9b3d5eeb..3d977e24 100644 --- a/ntfsprogs/ntfsfix.c +++ b/ntfsprogs/ntfsfix.c @@ -73,6 +73,8 @@ #include "mft.h" #include "device.h" #include "logfile.h" +#include "runlist.h" +#include "mst.h" #include "utils.h" /* #include "version.h" */ #include "logging.h" @@ -87,6 +89,7 @@ switch if you want to be able to build the NTFS utilities." static const char *EXEC_NAME = "ntfsfix"; static const char OK[] = "OK\n"; static const char FAILED[] = "FAILED\n"; +static const char FOUND[] = "FOUND\n"; #define DEFAULT_SECTOR_SIZE 512 @@ -96,6 +99,25 @@ static struct { BOOL clear_bad_sectors; } opt; +/* + * Definitions for fixing the self-located MFT bug + */ + +#define SELFLOC_LIMIT 16 + +struct MFT_SELF_LOCATED { + ntfs_volume *vol; + MFT_RECORD *mft0; + MFT_RECORD *mft1; + MFT_RECORD *mft2; + ATTR_LIST_ENTRY *attrlist; + ATTR_LIST_ENTRY *attrlist_to_ref1; + MFT_REF mft_ref0; + MFT_REF mft_ref1; + LCN attrlist_lcn; + BOOL attrlist_resident; +} ; + /** * usage */ @@ -685,6 +707,437 @@ static int rewrite_boot(struct ntfs_device *dev, char *full_bs, return (res); } +/* + * Locate an unnamed attribute in an MFT record + * + * Returns NULL if not found (with no error message) + */ + +static ATTR_RECORD *find_unnamed_attr(MFT_RECORD *mrec, ATTR_TYPES type) +{ + ATTR_RECORD *a; + u32 offset; + + /* fetch the requested attribute */ + offset = le16_to_cpu(mrec->attrs_offset); + a = (ATTR_RECORD*)((char*)mrec + offset); + while ((a->type != AT_END) + && ((a->type != type) || a->name_length) + && (offset < le32_to_cpu(mrec->bytes_in_use))) { + offset += le32_to_cpu(a->length); + a = (ATTR_RECORD*)((char*)mrec + offset); + } + if ((a->type != type) + || a->name_length) + a = (ATTR_RECORD*)NULL; + return (a); +} + +/* + * First condition for having a self-located MFT : + * only 16 MFT records are defined in MFT record 0 + * + * Only low-level library functions can be used. + * + * Returns TRUE if the condition is met. + */ + +static BOOL short_mft_selfloc_condition(struct MFT_SELF_LOCATED *selfloc) +{ + BOOL ok; + ntfs_volume *vol; + MFT_RECORD *mft0; + ATTR_RECORD *a; + runlist_element *rl; + u16 seqn; + + ok = FALSE; + vol = selfloc->vol; + mft0 = selfloc->mft0; + if ((ntfs_pread(vol->dev, + vol->mft_lcn << vol->cluster_size_bits, + vol->mft_record_size, mft0) + == vol->mft_record_size) + && !ntfs_mst_post_read_fixup((NTFS_RECORD*)mft0, + vol->mft_record_size)) { + a = find_unnamed_attr(mft0,AT_DATA); + if (a + && a->non_resident + && (((le64_to_cpu(a->highest_vcn) + 1) + << vol->cluster_size_bits) + == (SELFLOC_LIMIT*vol->mft_record_size))) { + rl = ntfs_mapping_pairs_decompress(vol, a, NULL); + if (rl) { + /* + * The first error condition is having only + * 16 entries mapped in the first MFT record. + */ + if ((rl[0].lcn >= 0) + && ((rl[0].length << vol->cluster_size_bits) + == SELFLOC_LIMIT*vol->mft_record_size) + && (rl[1].vcn == rl[0].length) + && (rl[1].lcn == LCN_RL_NOT_MAPPED)) { + ok = TRUE; + seqn = le16_to_cpu( + mft0->sequence_number); + selfloc->mft_ref0 + = ((MFT_REF)seqn) << 48; + } + free(rl); + } + } + } + return (ok); +} + +/* + * Second condition for having a self-located MFT : + * The 16th MFT record is defined in MFT record >= 16 + * + * Only low-level library functions can be used. + * + * Returns TRUE if the condition is met. + */ + +static BOOL attrlist_selfloc_condition(struct MFT_SELF_LOCATED *selfloc) +{ + ntfs_volume *vol; + ATTR_RECORD *a; + ATTR_LIST_ENTRY *attrlist; + ATTR_LIST_ENTRY *al; + runlist_element *rl; + VCN vcn; + leVCN levcn; + u32 length; + int ok; + + ok = FALSE; + length = 0; + vol = selfloc->vol; + a = find_unnamed_attr(selfloc->mft0,AT_ATTRIBUTE_LIST); + if (a) { + selfloc->attrlist_resident = !a->non_resident; + selfloc->attrlist_lcn = 0; + if (a->non_resident) { + attrlist = selfloc->attrlist; + rl = ntfs_mapping_pairs_decompress(vol, a, NULL); + if (rl + && (rl->lcn >= 0) + && (le64_to_cpu(a->data_size) < vol->cluster_size) + && (ntfs_pread(vol->dev, + rl->lcn << vol->cluster_size_bits, + vol->cluster_size, attrlist) == vol->cluster_size)) { + selfloc->attrlist_lcn = rl->lcn; + al = attrlist; + length = le64_to_cpu(a->data_size); + } + } else { + al = (ATTR_LIST_ENTRY*) + ((char*)a + le16_to_cpu(a->value_offset)); + length = le32_to_cpu(a->value_length); + } + if (length) { + /* search for a data attribute defining entry 16 */ + vcn = (SELFLOC_LIMIT*vol->mft_record_size) + >> vol->cluster_size_bits; + levcn = cpu_to_le64(vcn); + while ((length > 0) + && al->length + && ((al->type != AT_DATA) + || ((leVCN)al->lowest_vcn != levcn))) { + length -= le16_to_cpu(al->length); + al = (ATTR_LIST_ENTRY*) + ((char*)al + le16_to_cpu(al->length)); + } + if ((length > 0) + && al->length + && (al->type == AT_DATA) + && !al->name_length + && ((leVCN)al->lowest_vcn == levcn) + && (MREF_LE(al->mft_reference) >= SELFLOC_LIMIT)) { + selfloc->mft_ref1 + = le64_to_cpu(al->mft_reference); + selfloc->attrlist_to_ref1 = al; + ok = TRUE; + } + } + } + return (ok); +} + +/* + * Third condition for having a self-located MFT : + * The location of the second part of the MFT is defined in itself + * + * To locate the second part, we have to assume the first and the + * second part of the MFT data are contiguous. + * + * Only low-level library functions can be used. + * + * Returns TRUE if the condition is met. + */ + +static BOOL self_mapped_selfloc_condition(struct MFT_SELF_LOCATED *selfloc) +{ + BOOL ok; + s64 inum; + u64 offs; + VCN lowest_vcn; + MFT_RECORD *mft1; + ATTR_RECORD *a; + ntfs_volume *vol; + runlist_element *rl; + + ok = FALSE; + vol = selfloc->vol; + mft1 = selfloc->mft1; + inum = MREF(selfloc->mft_ref1); + offs = (vol->mft_lcn << vol->cluster_size_bits) + + (inum << vol->mft_record_size_bits); + if ((ntfs_pread(vol->dev, offs, vol->mft_record_size, + mft1) == vol->mft_record_size) + && !ntfs_mst_post_read_fixup((NTFS_RECORD*)mft1, + vol->mft_record_size)) { + lowest_vcn = (SELFLOC_LIMIT*vol->mft_record_size) + >> vol->cluster_size_bits; + a = find_unnamed_attr(mft1,AT_DATA); + if (a + && (mft1->flags & MFT_RECORD_IN_USE) + && ((VCN)le64_to_cpu(a->lowest_vcn) == lowest_vcn) + && (le64_to_cpu(mft1->base_mft_record) + == selfloc->mft_ref0) + && ((u16)MSEQNO(selfloc->mft_ref1) + == le16_to_cpu(mft1->sequence_number))) { + rl = ntfs_mapping_pairs_decompress(vol, a, NULL); + if ((rl[0].lcn == LCN_RL_NOT_MAPPED) + && !rl[0].vcn + && (rl[0].length == lowest_vcn) + && (rl[1].vcn == lowest_vcn) + && ((u64)(rl[1].lcn << vol->cluster_size_bits) + <= offs) + && ((u64)((rl[1].lcn + rl[1].length) + << vol->cluster_size_bits) > offs)) { + ok = TRUE; + } + } + } + return (ok); +} + +/* + * Fourth condition, to be able to fix a self-located MFT : + * The MFT record 15 must be available. + * + * The MFT record 15 is expected to be marked in use, we assume + * it is available if it has no parent, no name and no attr list. + * + * Only low-level library functions can be used. + * + * Returns TRUE if the condition is met. + */ + +static BOOL spare_record_selfloc_condition(struct MFT_SELF_LOCATED *selfloc) +{ + BOOL ok; + s64 inum; + u64 offs; + MFT_RECORD *mft2; + ntfs_volume *vol; + + ok = FALSE; + vol = selfloc->vol; + mft2 = selfloc->mft2; + inum = SELFLOC_LIMIT - 1; + offs = (vol->mft_lcn << vol->cluster_size_bits) + + (inum << vol->mft_record_size_bits); + if ((ntfs_pread(vol->dev, offs, vol->mft_record_size, + mft2) == vol->mft_record_size) + && !ntfs_mst_post_read_fixup((NTFS_RECORD*)mft2, + vol->mft_record_size)) { + if (!mft2->base_mft_record + && (mft2->flags & MFT_RECORD_IN_USE) + && !find_unnamed_attr(mft2,AT_ATTRIBUTE_LIST) + && !find_unnamed_attr(mft2,AT_FILE_NAME)) { + ok = TRUE; + } + } + return (ok); +} + +/* + * Fix a self-located MFT by swapping two MFT records + * + * Only low-level library functions can be used. + * + * Returns 0 if the MFT corruption could be fixed. + */ +static int fix_selfloc_conditions(struct MFT_SELF_LOCATED *selfloc) +{ + MFT_RECORD *mft1; + MFT_RECORD *mft2; + ATTR_RECORD *a; + ATTR_LIST_ENTRY *al; + ntfs_volume *vol; + s64 offs; + s64 offsm; + s64 offs1; + s64 offs2; + s64 inum; + u16 usa_ofs; + int res; + + res = 0; + /* + * In MFT1, we must fix : + * - the self-reference, if present, + * - its own sequence number, must be 15 + * - the sizes of the data attribute. + */ + vol = selfloc->vol; + mft1 = selfloc->mft1; + mft2 = selfloc->mft2; + usa_ofs = le16_to_cpu(mft1->usa_ofs); + if (usa_ofs >= 48) + mft1->mft_record_number = const_cpu_to_le32(SELFLOC_LIMIT - 1); + mft1->sequence_number = const_cpu_to_le16(SELFLOC_LIMIT - 1); + a = find_unnamed_attr(mft1,AT_DATA); + if (a) { + a->allocated_size = const_cpu_to_le64(0); + a->data_size = const_cpu_to_le64(0); + a->initialized_size = const_cpu_to_le64(0); + } else + res = -1; /* bug : it has been found earlier */ + + /* + * In MFT2, we must fix : + * - the self-reference, if present + */ + usa_ofs = le16_to_cpu(mft2->usa_ofs); + if (usa_ofs >= 48) + mft2->mft_record_number = cpu_to_le32(MREF(selfloc->mft_ref1)); + + /* + * In the attribute list, we must fix : + * - the reference to MFT1 + */ + al = selfloc->attrlist_to_ref1; + al->mft_reference = MK_LE_MREF(SELFLOC_LIMIT - 1, SELFLOC_LIMIT - 1); + + /* + * All fixes done, we can write all if allowed + */ + if (!res && !opt.no_action) { + inum = SELFLOC_LIMIT - 1; + offs2 = (vol->mft_lcn << vol->cluster_size_bits) + + (inum << vol->mft_record_size_bits); + inum = MREF(selfloc->mft_ref1); + offs1 = (vol->mft_lcn << vol->cluster_size_bits) + + (inum << vol->mft_record_size_bits); + + /* rewrite the attribute list */ + if (selfloc->attrlist_resident) { + /* write mft0 and mftmirr if it is resident */ + offs = vol->mft_lcn << vol->cluster_size_bits; + offsm = vol->mftmirr_lcn << vol->cluster_size_bits; + if (ntfs_mst_pre_write_fixup( + (NTFS_RECORD*)selfloc->mft0, + vol->mft_record_size) + || (ntfs_pwrite(vol->dev, offs, vol->mft_record_size, + selfloc->mft0) != vol->mft_record_size) + || (ntfs_pwrite(vol->dev, offsm, vol->mft_record_size, + selfloc->mft0) != vol->mft_record_size)) + res = -1; + } else { + /* write a full cluster if non resident */ + offs = selfloc->attrlist_lcn << vol->cluster_size_bits; + if (ntfs_pwrite(vol->dev, offs, vol->cluster_size, + selfloc->attrlist) != vol->cluster_size) + res = -1; + } + /* replace MFT2 by MFT1 and replace MFT1 by MFT2 */ + if (!res + && (ntfs_mst_pre_write_fixup((NTFS_RECORD*)selfloc->mft1, + vol->mft_record_size) + || ntfs_mst_pre_write_fixup((NTFS_RECORD*)selfloc->mft2, + vol->mft_record_size) + || (ntfs_pwrite(vol->dev, offs2, vol->mft_record_size, + mft1) != vol->mft_record_size) + || (ntfs_pwrite(vol->dev, offs1, vol->mft_record_size, + mft2) != vol->mft_record_size))) + res = -1; + } + return (res); +} + +/* + * Detect and fix a Windows XP bug, leading to a corrupt MFT + * + * Windows cannot boot anymore, so chkdsk cannot be started, which + * is a good point, because chkdsk would have deleted all the files. + * Older ntfs-3g fell into an endless recursion (recent versions + * refuse to mount). + * + * This situation is very rare, but it was fun to fix it. + * + * The corrupted condition is : + * - MFT entry 0 has only the runlist for MFT entries 0-15 + * - The attribute list for MFT shows the second part + * in an MFT record beyond 15 + * Of course, this record has to be read in order to know where it is. + * + * Sample case, met in 2011 (Windows XP) : + * MFT record 0 has : stdinfo, nonres attrlist, the first + * part of MFT data (entries 0-15), and bitmap + * MFT record 16 has the name + * MFT record 17 has the third part of MFT data (16-117731) + * MFT record 18 has the second part of MFT data (117732-170908) + * + * Assuming the second part of the MFT is contiguous to the first + * part, we can find it, and fix the condition by relocating it + * and swapping it with MFT record 15. + * This record number 15 appears to be hardcoded into Windows NTFS. + * + * Only low-level library functions can be used. + * + * Returns 0 if the conditions for the error were not met or + * the error could be fixed, + * -1 if some error was encountered + */ + +static int fix_self_located_mft(ntfs_volume *vol) +{ + struct MFT_SELF_LOCATED selfloc; + BOOL res; + + ntfs_log_info("Checking for self-located MFT segment... "); + res = -1; + selfloc.vol = vol; + selfloc.mft0 = (MFT_RECORD*)malloc(vol->mft_record_size); + selfloc.mft1 = (MFT_RECORD*)malloc(vol->mft_record_size); + selfloc.mft2 = (MFT_RECORD*)malloc(vol->mft_record_size); + selfloc.attrlist = (ATTR_LIST_ENTRY*)malloc(vol->cluster_size); + if (selfloc.mft0 && selfloc.mft1 && selfloc.mft2 + && selfloc.attrlist) { + if (short_mft_selfloc_condition(&selfloc) + && attrlist_selfloc_condition(&selfloc) + && self_mapped_selfloc_condition(&selfloc) + && spare_record_selfloc_condition(&selfloc)) { + ntfs_log_info(FOUND); + ntfs_log_info("Fixing the self-located MFT segment... "); + res = fix_selfloc_conditions(&selfloc); + ntfs_log_info(res ? FAILED : OK); + } else { + ntfs_log_info(OK); + res = 0; + } + free(selfloc.mft0); + free(selfloc.mft1); + free(selfloc.mft2); + free(selfloc.attrlist); + } + return (res); +} + /* * Try an alternate boot sector and fix the real one * @@ -891,8 +1344,10 @@ static int fix_startup(struct ntfs_device *dev, unsigned long flags) errno = EINVAL; goto error_exit; } + res = 0; + } else { + res = fix_self_located_mft(vol); } - res = 0; error_exit: if (res) { switch (errno) { @@ -944,10 +1399,10 @@ static int fix_mount(void) ntfs_log_info(FAILED); ntfs_log_perror("Failed to startup volume"); - /* Try fixing the bootsector and redo the startup */ + /* Try fixing the bootsector and MFT, then redo the startup */ if (!fix_startup(dev, flags)) { if (opt.no_action) - ntfs_log_info("The bootsector can be fixed, " + ntfs_log_info("The startup data can be fixed, " "but no change was requested\n"); else vol = ntfs_volume_startup(dev, flags);