Changes by: antona
Update of /cvsroot/linux-ntfs/linux-ntfs/ntfstools
In directory usw-pr-cvs1:/tmp/cvs-serv10246/ntfstools
Added Files:
ntfsfix.c
Log Message:
Added ntfsfix program.
--- NEW FILE ---
const char *EXEC_NAME = "NtfsFix";
const char *EXEC_VERSION = "0.0.2";
/*
* NtfsFix - Part of the Linux-NTFS project.
*
* Copyright (c) 2000,2001 Anton Altaparmakov.
*
* This utility will attempt to fix a partition that has been damaged by the
* current Linux-NTFS driver. It should be run after dismounting a NTFS
* partition that has been mounted read-write under Linux and before rebooting
* into Windows NT/2000. NtfsFix can be run even after Windows has had mounted
* the partition, but it might be too late and irreversible damage to the data
* might have been done already.
*
* Anton Altaparmakov <ai...@ca...>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program (in the main directory of the Linux-NTFS source
* in the file COPYING); if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* WARNING: This program might not work on architectures which do not allow
* unaligned access. For those, the program would need to start using
* get/put_unaligned macros (#include <asm/unaligned.h>), but not doing it yet,
* since NTFS really mostly applies to ia32 only, which does allow unaligned
* accesses. We might not actually have a problem though, since the structs are
* defined as being packed so that might be enough for gcc to insert the
* correct code.
*
* If anyone using a non-little endian and/or an aligned access only CPU tries
* this program please let me know whether it works or not!
*
* Anton Altaparmakov <ai...@ca...>
*/
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <linux/types.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include "attrib.h"
#include "endians.h"
#include "mft.h"
#include "disk_io.h"
#include "volume.h"
#include "logfile.h"
int main(int argc, char **argv)
{
const char *OK = "OK";
const char *FAILED = "FAILED";
unsigned char *b1 = NULL;
unsigned char *b2 = NULL;
int i, ebits;
ssize_t br;
NONRESIDENT_ATTRIBUTE_RECORD_HEADER *a;
__u64 l;
unsigned char *lfd;
ntfs_volume *vol = NULL;
__u16 flags;
printf("\n");
if (argc != 2) {
printf("NtfsFix v%s - Attempt to fix an NTFS parition that "
"has been damaged by the\nLinux NTFS driver. Note that "
"you should run it every time after you have used\nthe "
"Linux NTFS driver to write to an NTFS partition to "
"prevent massive data\ncorruption from happening when "
"Windows mounts the partition.\nIMPORTANT: Run this "
"only *after* unmounting the partition in Linux but "
"*before*\nrebooting into Windows NT/2000 or you *will*"
" suffer! - You have been warned!\n\n"
/* Generic copyright / disclaimer. */
"Copyright (c) 2000, 2001 Anton Altaparmakov.\n"
"%s is free software, released under the GNU "
"General Public License and you\nare welcome to "
"redistribute it under certain conditions.\n"
"%s comes with ABSOLUTELY NO WARRANTY; for details"
"read the file GNU\nGeneral Public License to be found "
"in the file COPYING in the main Linux-NTFS\n"
"distribution directory.\n\n"
/* Generic part ends here. */
"Syntax: ntfsfix partition_name\n"
" e.g. ntfsfix /dev/hda6\n\n", EXEC_VERSION,
EXEC_NAME, EXEC_NAME);
fprintf(stderr, "Error: incorrect syntax\n");
exit(1);
}
vol = ntfs_open(argv[1]);
if (!vol) {
perror("ntfs_open() failed");
exit(1);
}
/* Lazyness caused define for offset from one Mft record to the next. (-; */
#define mrs (vol->MftRecordSize)
printf("\nBeginning processing of the $Mft and $MftMirr...\n");
b1 = (__u8 *)malloc(mrs * 8);
if (!b1) {
perror("Error allocating internal buffers");
goto error_exit;
}
b2 = b1 + (mrs * 4);
#ifdef DEBUG
printf("$Mft LCN = 0x%Lx\n", vol->mft_lcn);
printf("$MftMirr LCN = 0x%Lx\n", vol->mftmirr_lcn);
#endif
/* Read $MFT and $MFTmirr. */
printf("Going to read $Mft... ");
br = get_mft_records(vol, b1, 0, 4);
if (br != 4) {
puts(FAILED);
goto error_exit;
}
puts(OK);
/* Use mst_pread as we don't want to rely on information in $Mft for
* locating $MftMirr as that would defeat the point of $MftMirr. */
printf("Going to read $MftMirr... ");
br = mst_pread(vol->fd, b2, mrs, 4, vol->mftmirr_lcn <<
vol->cluster_size_bits);
if (br != 4) {
puts(FAILED);
#define ESTR "Error reading $MftMirr"
if (br == -1)
perror(ESTR);
else if (br < 4)
fprintf(stderr, "Error: $MftMirr LCN is outside of "
"the partition!\nIs it maybe part of "
"a RAID array?\n");
else
fprintf(stderr, ESTR ": unknown error\n");
#undef ESTR
goto error_exit;
}
puts(OK);
/* Check first four Mft records. */
printf("Checking first four entries in $Mft. ");
for (i = 0; i < 4; ++i) {
const char *ESTR[4] = { "$Mft", "$MftMirr", "$LogFile",
"$Volume" };
if (is_baad_record(b1 + (i * mrs))) {
puts(FAILED);
fprintf(stderr, "Error: Incomplete multi sector "
"transfer detected in %s.\nCannot "
"handle this yet. )-:\n", ESTR[i]);
goto error_exit;
}
if (!is_mft_recordp(b1 + (i * mrs))) {
puts(FAILED);
fprintf(stderr, "Error: Invalid $Mft record for %s.\n"
"Cannot handle this yet. )-:\n",
ESTR[i]);
goto error_exit;
}
printf("%s\b. ", i == 0? "\b": "");
}
puts(OK);
/*
* FIXME: Need to actually check the $MftMirr for being real. Otherwise
* we might corrupt the partition if someone is experimenting with
* software RAID and the $MftMirr is not actually in the position we
* expect it to be... )-:
* FIXME: We should emit a warning it $MftMirr is damanged and ask
* user whether to recreate it from $Mft or whether to abort. - The
* warning needs to include the danger of software RAID arrays.
* Maybe we should go as far as to detect whether we are running on a
* MD disk and if yes then bomb out right at the start of the program?
*/
/* Check the four MftMirr records. */
printf("Checking entries in $MftMirr. ");
for (i = 0; i < 4; ++i) {
const char *ESTR[4] = { "$Mft", "$MftMirr", "$LogFile",
"$Volume" };
if (is_baad_record(b2 + (i * mrs))) {
puts(FAILED);
fprintf(stderr, "Error: Incomplete multi sector "
"transfer detected in %s.\nCannot "
"handle this yet. )-:\n", ESTR[i]);
goto error_exit;
}
if (!is_mft_recordp(b2 + (i * mrs))) {
puts(FAILED);
fprintf(stderr, "Error: Invalid $MftMirr record for %s.\n"
"Cannot handle this yet. )-:\n",
ESTR[i]);
goto error_exit;
}
printf("%s\b. ", i == 0? "\b": "");
}
puts(OK);
/* Compare each record in turn and set the corresponding bit in ebits
* if a mismatch is encountered. Note that we EXPECT a bit-to-bit match
* of the $Mft and the $MftMirr. This is CORRECT behaviour(!). Even just
* having the USNs in $Mft and $MftMirr being different has to trigger
* the correction procedure! */
printf("Comparing $MftMirr to $Mft... ");
for (i = 0, ebits = 0; i < 4; ++i) {
if (memcmp(b1+i*mrs, b2+i*mrs,
get_mft_record_size((MFT_RECORD_HEADER*)(b1+i*mrs)))) {
ebits |= 1 << i;
#ifdef DEBUG
printf("in memcmp: i = %x, count = 0x%x\n", i, get_mft_record_size((MFT_RECORD_HEADER*)(b1+i*mrs)));
#endif
}
}
if (ebits) {
puts(FAILED);
printf("Assuming $Mft is valid and correcting differences "
"in $MftMirr... ");
/* MST protect & write the $Mft records corresponding to the
mismatched ones to both $Mft and $MftMirr. */
for (i = 0; i < 4; ++i) {
/* Only fix if there was an error! */
if ( !( ebits & (1 << i) ) )
continue;
/* Copy the buffer first (due to USN stuff, can't
recycle the buffer) */
memcpy(b2 + mrs * i, b1 + mrs * i, mrs);
/* Do the writes now. */
br = put_mft_record(vol, b1 + mrs * i, i);
if (br != 1)
goto mirr_sync_failed;
br = mst_pwrite(vol->fd, b2 + mrs * i, mrs,
(vol->mftmirr_lcn * vol->cluster_size) +
(i * mrs));
/* If we succeeded then do the next record. */
if (br == mrs)
continue;
mirr_sync_failed: /* Synchronization failed. )-: */
puts(FAILED);
#define ESTR "Error correcting $MftMirr"
if (br == -1)
perror(ESTR);
else
fprintf(stderr, ESTR ": unknown error\n");
#undef ESTR
goto error_exit;
}
}
puts(OK);
printf("Processing of the $Mft and $MftMirr completed successfully."
"\n\n");
/* Check NTFS version is ok for us (in $Volume) */
printf("NTFS volume version is %i.%i.\n", vol->major_ver,
vol->minor_ver);
switch (vol->major_ver) {
case 1:
if (vol->minor_ver == 1 || vol->minor_ver == 2)
break;
else
goto version_error;
case 2: case 3:
if (vol->minor_ver == 0)
break;
/* Fall through on error. */
default:
version_error:
fprintf(stderr, "Error: Unknown NTFS version.\n");
goto error_exit;
}
printf("Setting required flags on partition... ");
/* Set chkdsk flag, i.e. mark the partition dirty so chkdsk will run
and fix it for us. */
flags = vol->flags;
flags |= VOLUME_IS_DIRTY;
/* If NTFS volume version >= 2.0 then set mounted on NT4 flag. */
if (vol->major_ver >= 2)
flags |= VOLUME_MOUNTED_ON_NT4;
if (!set_ntfs_volume_flags((MFT_RECORD_HEADER*)(b1+3*mrs), flags)) {
puts(FAILED);
fprintf(stderr, "Error setting volume flags.\n");
goto error_exit;
}
vol->flags = flags;
/* FIXME: The above makes me puke! We definitely need functions just
taking the volume and flags and [sg]etting the volume flags! */
puts(OK);
/* Sync $MftMirr and $Mft buffers. */
memcpy(b2 + 3 * mrs, b1 + 3 * mrs, mrs);
/* Write $Volume to $Mft. */
printf("Writing $Volume to $Mft... ");
br = put_mft_record(vol, b1 + (3 * mrs), 3);
if (br != 1) {
puts(FAILED);
#define ESTR "Error writing $Volume to $Mft"
if (br == -1)
perror(ESTR);
else
fprintf(stderr, ESTR ": unknown error\n");
#undef ESTR
goto error_exit;
}
puts(OK);
/* Write $Volume to $MftMirr. */
printf("Writing $Volume to $MftMirr... ");
br = mst_pwrite(vol->fd, b2 + (3 * mrs), mrs,
(vol->mft_mirr_lcn << vol->cluster_size_bits) +
(3 * mrs));
if (br != mrs) {
puts(FAILED);
goto error_exit;
}
puts(OK);
printf("\n");
/* Zero the value of the $LogFile data attribute (ie. the contents of
* the file) thus erasing the journal.
* FIXME(?): We might need to zero the LSN field of every single $Mft
* record as well. (But, first try without doing that and see what
* happens, since chkdsk might pickup the pieces and do it for us...)
* FIXME: This results in winNT/2k saying the partition is corrupt and
* tells us to run chkdsk but when it is the boot drive it doesn't
* boot up so can't run chkdsk!!!
*
* Try to clear the $LogFile by modifying the restart areas... */
printf("Going to fixup the journal ($LogFile)... ");
/* Find the $DATA attribute. */
a = (NONRESIDENT_ATTRIBUTE_RECORD_HEADER*)
find_attribute((MFT_RECORD_HEADER*)(b1+(2*mrs)), $DATA, NULL);
if (!a) {
puts(FAILED);
fprintf(stderr, "Error: Attribute $DATA was not found in" \
"$LogFile!\n");
goto log_file_error;
}
/* Get length of $LogFile contents. */
l = get_attribute_value_length((ATTRIBUTE_RECORD_HEADER*)a);
if (!l) {
puts(OK);
puts("$LogFile has zero length, no need to write to disk.");
goto log_file_error;
}
/* Allocate a buffer to hold all of the $LogFile contents. */
lfd = (unsigned char*)malloc(l);
if (!lfd) {
puts(FAILED);
puts("Not enough memory to load $LogFile.");
goto log_file_error;
}
/* Read in the $LogFile into the buffer. */
if (l != get_attribute_value(vol, (ATTRIBUTE_RECORD_HEADER*)a, lfd)) {
puts(FAILED);
puts("Amount of data read does not correspond to expected "
"length!");
free(lfd);
goto log_file_error;
}
/* Check and then modify restart area. */
if (!is_rstr_recordp(lfd)) {
puts(FAILED);
puts("$LogFile contents are corrupt (magic RSTR missing)!");
free(lfd);
goto log_file_error;
}
{ RESTART_PAGE_HEADER *rph;
RESTART_AREA *rr;
RESTART_CLIENT *cr;
int pass = 1;
int lps;
rph = (RESTART_PAGE_HEADER*)lfd;
lps = le32_to_cpu(rph->LogPageSize);
pass_loc:
if (!post_read_mst_fixup((NTFS_RECORD_HEADER*)rph, lps) ||
is_baad_recordp(rph)) {
puts(FAILED);
puts("$LogFile incomplete multi sector transfer detected! "
"Cannot handle this yet!");
free(lfd);
goto log_file_error;
}
if (le16_to_cpu(rph->major_version != 1) ||
le16_to_cpu(rph->minor_version != 1)) {
puts(FAILED);
printf("$LogFile version %i.%i! Cannot handle this yet!\n",
le16_to_cpu(rph->major_ver),
le16_to_cpu(rph->minor_ver));
free(lfd);
goto log_file_error;
}
rr = (RESTART_AREA*)((char*)rph + le16_to_cpu(rph->restart_offset));
cr = (RESTART_CLIENT*)((char*)rr + sizeof(RESTART_AREA));
#ifdef DEBUG
/* Do a dump of the $LogFile restart area. */
if (pass == 1)
printf("\n$LogFile version %i.%i.\n",
le16_to_cpu(rph->major_ver),
le16_to_cpu(rph->minor_ver));
printf("%s restart area:\n", pass == 1? "1st": "2nd");
printf("magic = RSTR\n");
printf("Lsn = 0x%Lx\n", sle64_to_cpu(rph->chkdsk_lsn));
printf("SystempageSize = %u\n", le32_to_cpu(rph->system_page_size));
printf("LogPageSize = %u\n", le32_to_cpu(rph->log_page_size));
printf("RestartOffset = 0x%x\n", le16_to_cpu(rph->restart_offset));
printf("\n(1st) restart record:\n");
printf("CurrentLsn = %Lx\n", sle64_to_cpu(rr->current_lsn));
printf("LogClients = %u\n", le16_to_cpu(rr->log_clients));
printf("ClientFreeList = %u\n", le16_to_cpu(rr->client_free_list));
printf("ClientInUseList = %u\n", le16_to_cpu(rr->client_in_use_list));
printf("Flags = 0x%x\n", le16_to_cpu(rr->flags));
printf("SeqNumberBits = %u\n", le32_to_cpu(rr->seq_number_bits));
printf("RestartAreaLength = 0x%x\n",
le16_to_cpu(rr->restart_area_length));
printf("ClientArrayOffset = 0x%x\n",
le16_to_cpu(rr->client_array_offset));
printf("FileSize = %Lu\n", le64_to_cpu(rr->file_size));
printf("LastLsnDataLength = 0x%x\n",
le32_to_cpu(rr->last_lsn_data_length));
printf("RecordLength = 0x%x\n", le16_to_cpu(rr->record_length));
printf("LogPageDataOffset = 0x%x\n",
le16_to_cpu(rr->log_page_data_offset));
printf("\n1st Client record:\n");
printf("OldestLsn = 0x%Lx\n", sle64_to_cpu(cr->oldest_lsn));
printf("ClientRestartLsn = 0x%Lx\n",
sle64_to_cpu(cr->client_restart_lsn));
printf("PrevClient = 0x%x\n", le16_to_cpu(cr->prev_client));
printf("NextClient = 0x%x\n", le16_to_cpu(cr->next_client));
printf("SeqNumber = 0x%x\n", le16_to_cpu(cr->seq_number));
printf("ClientName = %u\n", le16_to_cpu(cr->client_name));
printf("\nTerminator = 0x%x\n", *(__u32*)((char*)cr +
sizeof(RESTART_CLIENT)));
printf("Unicode string \"NTFS\"%spresent\n", *(__u64*)((char*)cr +
sizeof(RESTART_CLIENT) + 0x10) ==
cpu_to_le64(0x005300460054004e) ? " ": " not ");
#endif
rr->log_clients = cpu_to_le16(1);
rr->client_free_list = cpu_to_le16(0xffff);
rr->client_in_use_list = cpu_to_le16(0);
rr->flags = cpu_to_le16(0);
if (le64_to_cpu(rr->file_size) != l) {
puts(FAILED);
puts("$LogFile restart area indicates a log file size"
"different from the actual size!");
free(lfd);
goto log_file_error;
}
rr->last_lsn_data_length = cpu_to_le32(0);
rr->current_lsn = cr->client_restart_lsn;
cr->prev_client = cpu_to_le16(0);
cr->next_client = cpu_to_le16(0);
if (le16_to_cpu(cr->client_name) != 0) {
puts(FAILED);
puts("$LogFile restart area Client Name != 0.");
free(lfd);
goto log_file_error;
}
if (*(__u32*)((char*)cr + sizeof(RESTART_CLIENT)) !=
0xffffffff) {
puts(FAILED);
puts("$LogFile restart area not terminated!");
free(lfd);
goto log_file_error;
}
if (!pre_write_mst_fixup((NTFS_RECORD_HEADER*)rph, lps)) {
puts(FAILED);
puts("$LogFile restart area multi sector protection not "
"completed!");
free(lfd);
goto log_file_error;
}
rph = (RESTART_PAGE_HEADER*)((char*)rph + lps);
if (pass == 1) {
++pass;
goto pass_loc;
}
if (!is_rcrd_recordp(rph)) {
puts(FAILED);
puts("$LogFile's first record area magic RCRD is missing!");
free(lfd);
goto log_file_error;
}
}
/* Set the $DATA attribute. */
/* FIXME: set_attribute_value needs to either:
* - know the MFT_RECORD_HEADER so it can adjust it accordingly with
* changes in length of the resident part of the attribute.
* or
* - return a copy of the attribute so the MFT_RECORD_HEADER can be
* adjusted accordingly with changes in the length of the resident
* part of the attribute by the caller.
* For now, just assume that attribute length remains constant, since
* this is the case for the $LogFile zeroing out. */
/* DON'T WRITE IT BACK YET!!!
if (!set_attribute_value(vol, (ATTRIBUTE_RECORD_HEADER*)a, lfd, l)) {
puts(FAILED);
puts("Failed to set the $LogFile attribute value.");
free(lfd);
goto log_file_error;
}
*/
/* Release the allocated buffer. */
free(lfd);
puts(OK);
puts("$LogFile was left untouched as we don't know what to do with "
"it yet!");
log_file_error:
printf("\n");
/* FIXME: If on NTFS 3.0+, check for presence of the usn journal and
disable it (if present) as Win2k might be unhappy otherwise and Bad
Thing(TM) could happen depending on what applications are actually
using it for. */
/* FIXME: Should we be marking the quota out of date, too? */
/* That's all for now! */
printf("NTFS partition %s was processed successfully.\n",
vol->vol_name);
/* Set return code to 0. */
i = 0;
final_exit:
if (b1)
free(b1);
if (vol)
ntfs_close(vol);
return i;
error_exit:
i = 1;
goto final_exit;
}
|