Developped fixing of a self-located MFT data bug in ntfsfix

Under some rare and obscure circumstances probably unrelated to ntfs-3g,
a part of the runlist of MFT describes its own location, therefore
it cannot be loaded. This patch relocates the MFT extent to inode 15
to fix this. Note : chkdsk cannot fix it and destroys all the files.
edge.strict_endians
Jean-Pierre André 2011-10-07 11:33:22 +02:00
parent b41ad439f9
commit c42c502e92
1 changed files with 458 additions and 3 deletions

View File

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