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
parent
b41ad439f9
commit
c42c502e92
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue