mirror of
https://git.code.sf.net/p/ntfs-3g/ntfs-3g.git
synced 2024-11-23 18:14:24 +08:00
d587ff54f9
ntfsrecover -f -v <log file> receives a SIGSEGV because of trying to read memory outside allocated buffer because of no sanity checks on restart page header values. This happens on an empty $LogFile because of no basic checks present. Attached patch adds basic checks similar to those inside logfile library and allows tool to exit with more suitable message. (contributed by Rakesh Pandit)
4184 lines
111 KiB
C
4184 lines
111 KiB
C
/*
|
|
* Process log data from an NTFS partition
|
|
*
|
|
* Copyright (c) 2012-2016 Jean-Pierre Andre
|
|
*
|
|
* This program examines the Windows log file of an ntfs partition
|
|
* and plays the committed transactions in order to restore the
|
|
* integrity of metadata.
|
|
*
|
|
* It can also display the contents of the log file in human-readable
|
|
* text, either from a full partition or from the log file itself.
|
|
*
|
|
*
|
|
* History
|
|
*
|
|
* Sep 2012
|
|
* - displayed textual logfile contents forward
|
|
*
|
|
* Nov 2014
|
|
* - decoded multi-page log records
|
|
* - displayed textual logfile contents backward
|
|
*
|
|
* Nov 2015
|
|
* - made a general cleaning and redesigned as an ntfsprogs
|
|
* - applied committed actions from logfile
|
|
*/
|
|
|
|
/*
|
|
* 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 NTFS-3G
|
|
* distribution in the file COPYING); if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#define BASEBLKS 4 /* number of special blocks (always shown) */
|
|
#define RSTBLKS 2 /* number of restart blocks */
|
|
#define BUFFERCNT 64 /* number of block buffers - a power of 2 */
|
|
#define NTFSBLKLTH 512 /* usa block size */
|
|
#define SHOWATTRS 20 /* max attrs shown in a dump */
|
|
#define SHOWLISTS 10 /* max lcn or lsn shown in a list */
|
|
#define BLOCKBITS 9 /* This is only used to read the restart page */
|
|
#define MAXEXCEPTION 10 /* Max number of exceptions (option -x) */
|
|
#define MINRECSIZE 48 /* Minimal log record size */
|
|
#define MAXRECSIZE 65536 /* Maximal log record size (seen > 56000) */
|
|
|
|
#include "config.h"
|
|
|
|
#ifdef HAVE_STDLIB_H
|
|
#include <stdlib.h>
|
|
#endif
|
|
#ifdef HAVE_STDIO_H
|
|
#include <stdio.h>
|
|
#endif
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
#ifdef HAVE_FCNTL_H
|
|
#include <fcntl.h>
|
|
#endif
|
|
#ifdef HAVE_ERRNO_H
|
|
#include <errno.h>
|
|
#endif
|
|
#ifdef HAVE_STRING_H
|
|
#include <string.h>
|
|
#endif
|
|
#ifdef HAVE_GETOPT_H
|
|
#include <getopt.h>
|
|
#endif
|
|
#ifdef HAVE_MALLOC_H
|
|
#include <malloc.h>
|
|
#endif
|
|
#ifdef HAVE_TIME_H
|
|
#include <time.h>
|
|
#endif
|
|
|
|
#include "types.h"
|
|
#include "endians.h"
|
|
#include "support.h"
|
|
#include "layout.h"
|
|
#include "param.h"
|
|
#include "ntfstime.h"
|
|
#include "device_io.h"
|
|
#include "device.h"
|
|
#include "logging.h"
|
|
#include "runlist.h"
|
|
#include "mft.h"
|
|
#include "inode.h"
|
|
#include "attrib.h"
|
|
#include "bitmap.h"
|
|
#include "index.h"
|
|
#include "volume.h"
|
|
#include "unistr.h"
|
|
#include "mst.h"
|
|
#include "logfile.h"
|
|
#include "ntfsrecover.h"
|
|
#include "utils.h"
|
|
#include "misc.h"
|
|
|
|
typedef struct {
|
|
ntfs_volume *vol;
|
|
FILE *file;
|
|
struct ACTION_RECORD *firstaction;
|
|
struct ACTION_RECORD *lastaction;
|
|
} CONTEXT;
|
|
|
|
typedef enum { T_OK, T_ERR, T_DONE } TRISTATE;
|
|
|
|
RESTART_PAGE_HEADER log_header;
|
|
RESTART_AREA restart;
|
|
LOG_CLIENT_RECORD client;
|
|
u32 clustersz = 0;
|
|
int clusterbits;
|
|
u32 blocksz;
|
|
int blockbits;
|
|
u16 bytespersect;
|
|
u64 mftlcn;
|
|
u32 mftrecsz;
|
|
int mftrecbits;
|
|
u32 mftcnt; /* number of entries */
|
|
ntfs_inode *log_ni;
|
|
ntfs_attr *log_na;
|
|
u64 logfilelcn;
|
|
u32 logfilesz; /* bytes */
|
|
u64 redos_met;
|
|
u64 committed_lsn;
|
|
u64 synced_lsn;
|
|
u64 latest_lsn;
|
|
u64 restart_lsn;
|
|
unsigned long firstblk; /* first block to dump (option -r) */
|
|
unsigned long lastblk; /* last block to dump (option -r) */
|
|
u64 firstlcn; /* first block to dump (option -c) */
|
|
u64 lastlcn; /* last block to dump (option -c) */
|
|
BOOL optb; /* show the log backward */
|
|
BOOL optc; /* restrict to cluster range */
|
|
BOOL optd; /* device argument present*/
|
|
BOOL opth; /* show help */
|
|
BOOL opti; /* show invalid (stale) records */
|
|
BOOL optf; /* show full log */
|
|
BOOL optn; /* do not apply modifications */
|
|
BOOL optp; /* count of transaction sets to play */
|
|
BOOL optr; /* show a range of blocks */
|
|
int opts; /* sync the file system */
|
|
BOOL optt; /* show transactions */
|
|
BOOL optu; /* count of transaction sets to undo */
|
|
int optv; /* verbose */
|
|
int optV; /* version */
|
|
int optx[MAXEXCEPTION + 1];
|
|
struct ATTR **attrtable;
|
|
unsigned int actionnum;
|
|
unsigned int attrcount;
|
|
unsigned int playcount;
|
|
unsigned int playedactions; // change the name
|
|
unsigned int redocount;
|
|
unsigned int undocount;
|
|
struct BUFFER *buffer_table[BASEBLKS + BUFFERCNT];
|
|
|
|
static const le16 SDS[4] = {
|
|
const_cpu_to_le16('$'), const_cpu_to_le16('S'),
|
|
const_cpu_to_le16('D'), const_cpu_to_le16('S')
|
|
} ;
|
|
|
|
static const le16 I30[4] = {
|
|
const_cpu_to_le16('$'), const_cpu_to_le16('I'),
|
|
const_cpu_to_le16('3'), const_cpu_to_le16('0')
|
|
} ;
|
|
|
|
/*
|
|
* Byte address of a log block
|
|
*/
|
|
|
|
static s64 loclogblk(CONTEXT *ctx, unsigned int blk)
|
|
{
|
|
s64 loc;
|
|
LCN lcn;
|
|
|
|
if (ctx->vol) {
|
|
lcn = ntfs_attr_vcn_to_lcn(log_na,
|
|
((s64)blk << blockbits) >> clusterbits);
|
|
loc = lcn << clusterbits;
|
|
} else {
|
|
if (((s64)blk << blockbits) >= logfilesz)
|
|
loc = -1;
|
|
else
|
|
loc = (logfilelcn << clusterbits)
|
|
+ ((s64)blk << blockbits);
|
|
}
|
|
return (loc);
|
|
}
|
|
|
|
/*
|
|
* Deprotect a block
|
|
* Only to be used for log buffers
|
|
*
|
|
* Returns 0 if block was found correct
|
|
*/
|
|
|
|
static int replaceusa(struct BUFFER *buffer, unsigned int lth)
|
|
{
|
|
char *buf;
|
|
RECORD_PAGE_HEADER *record;
|
|
unsigned int j;
|
|
BOOL err;
|
|
unsigned int used;
|
|
unsigned int xusa, nusa;
|
|
|
|
err = FALSE;
|
|
/* Restart blocks have no protection */
|
|
if (buffer->num >= RSTBLKS) {
|
|
/* Do not check beyond used sectors */
|
|
record = &buffer->block.record;
|
|
used = blocksz;
|
|
xusa = le16_to_cpu(record->usa_ofs);
|
|
nusa = le16_to_cpu(record->usa_count);
|
|
if (xusa && nusa
|
|
&& ((xusa + 1) < lth)
|
|
&& ((nusa - 1)*NTFSBLKLTH == lth)) {
|
|
buf = buffer->block.data;
|
|
for (j=1; (j<nusa) && ((j-1)*NTFSBLKLTH<used); j++)
|
|
if ((buf[xusa] == buf[j*NTFSBLKLTH - 2])
|
|
&& (buf[xusa+1] == buf[j*NTFSBLKLTH - 1])) {
|
|
buf[j*NTFSBLKLTH - 2] = buf[xusa + 2*j];
|
|
buf[j*NTFSBLKLTH - 1] = buf[xusa + 2*j + 1];
|
|
} else {
|
|
printf("* Update sequence number %d does not match\n",j);
|
|
err = TRUE;
|
|
}
|
|
}
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
/*
|
|
* Dynamically allocate an attribute key.
|
|
*
|
|
* As the possible values for a key depend on the version, we
|
|
* cannot convert it to an index, so we make dichotomical searches
|
|
*/
|
|
|
|
struct ATTR *getattrentry(unsigned int key, unsigned int lth)
|
|
{
|
|
struct ATTR *pa;
|
|
struct ATTR **old;
|
|
unsigned int low, mid, high;
|
|
|
|
low = 0;
|
|
if (attrcount) {
|
|
high = attrcount;
|
|
while ((low + 1) < high) {
|
|
mid = (low + high) >> 1;
|
|
if (key < attrtable[mid]->key)
|
|
high = mid;
|
|
else
|
|
if (key > attrtable[mid]->key)
|
|
low = mid;
|
|
else {
|
|
low = mid;
|
|
high = mid + 1;
|
|
}
|
|
}
|
|
}
|
|
if ((low < attrcount) && (attrtable[low]->key == key)) {
|
|
pa = attrtable[low];
|
|
if (pa->namelen < lth) {
|
|
pa = (struct ATTR*)realloc(pa,
|
|
sizeof(struct ATTR) + lth);
|
|
attrtable[low] = pa;
|
|
}
|
|
} else {
|
|
mid = low + 1;
|
|
if (!low && attrcount && (attrtable[0]->key > key))
|
|
mid = 0;
|
|
pa = (struct ATTR*)malloc(sizeof(struct ATTR) + lth);
|
|
if (pa) {
|
|
if (attrcount++) {
|
|
old = attrtable;
|
|
attrtable = (struct ATTR**)realloc(attrtable,
|
|
attrcount*sizeof(struct ATTR*));
|
|
if (attrtable) {
|
|
high = attrcount;
|
|
while (--high > mid)
|
|
attrtable[high]
|
|
= attrtable[high - 1];
|
|
attrtable[mid] = pa;
|
|
} else
|
|
attrtable = old;
|
|
} else {
|
|
attrtable = (struct ATTR**)
|
|
malloc(sizeof(struct ATTR*));
|
|
attrtable[0] = pa;
|
|
}
|
|
pa->key = key;
|
|
pa->namelen = 0;
|
|
pa->type = const_cpu_to_le32(0);
|
|
pa->inode = 0;
|
|
}
|
|
}
|
|
return (pa);
|
|
}
|
|
|
|
/*
|
|
* Read blocks in a circular buffer
|
|
*
|
|
* returns NULL if block cannot be read or it is found bad
|
|
* otherwise returns the full unprotected block data
|
|
*/
|
|
|
|
static const struct BUFFER *read_buffer(CONTEXT *ctx, unsigned int num)
|
|
{
|
|
struct BUFFER *buffer;
|
|
BOOL got;
|
|
|
|
/*
|
|
* The first four blocks are stored apart, to make
|
|
* sure pages 2 and 3 and the page which is logically
|
|
* before them can be accessed at the same time.
|
|
* Also, block 0 is smaller because it has to be read
|
|
* before the block size is known.
|
|
* Note : the last block is supposed to have an odd
|
|
* number, and cannot be overwritten by block 4 which
|
|
* follows logically.
|
|
*/
|
|
if (num < BASEBLKS)
|
|
buffer = buffer_table[num + BUFFERCNT];
|
|
else
|
|
buffer = buffer_table[num & (BUFFERCNT - 1)];
|
|
if (buffer && (buffer->size < blocksz)) {
|
|
free(buffer);
|
|
buffer = (struct BUFFER*)NULL;
|
|
}
|
|
if (!buffer) {
|
|
buffer = (struct BUFFER*)
|
|
malloc(sizeof(struct BUFFER) + blocksz);
|
|
buffer->size = blocksz;
|
|
buffer->num = num + 1; /* forced to being read */
|
|
buffer->safe = FALSE;
|
|
if (num < BASEBLKS)
|
|
buffer_table[num + BUFFERCNT] = buffer;
|
|
else
|
|
buffer_table[num & (BUFFERCNT - 1)] = buffer;
|
|
}
|
|
if (buffer && (buffer->num != num)) {
|
|
buffer->num = num;
|
|
if (ctx->vol)
|
|
got = (ntfs_attr_pread(log_na,(u64)num << blockbits,
|
|
blocksz, buffer->block.data) == blocksz);
|
|
else
|
|
got = !fseek(ctx->file, loclogblk(ctx, num), 0)
|
|
&& (fread(buffer->block.data, blocksz,
|
|
1, ctx->file) == 1);
|
|
if (got) {
|
|
char *data = buffer->block.data;
|
|
buffer->headsz = sizeof(RECORD_PAGE_HEADER)
|
|
+ ((2*getle16(data,6) - 1) | 7) + 1;
|
|
buffer->safe = !replaceusa(buffer, blocksz);
|
|
} else {
|
|
buffer->safe = FALSE;
|
|
fprintf(stderr,"** Could not read block %d\n", num);
|
|
}
|
|
}
|
|
return (buffer && buffer->safe ? buffer : (const struct BUFFER*)NULL);
|
|
}
|
|
|
|
void hexdump(const char *buf, unsigned int lth)
|
|
{
|
|
unsigned int i,j,k;
|
|
|
|
for (i=0; i<lth; i+=16) {
|
|
printf("%04x ",i);
|
|
k = ((lth - i) < 16 ? lth : 16 + i);
|
|
for (j=i; j<k; j++)
|
|
printf((j & 3 ? "%02x" : " %02x"),buf[j] & 255);
|
|
printf("%*c",(152 - 9*(j - i))/4,' ');
|
|
for (j=i; j<k; j++)
|
|
if ((buf[j] > 0x20) && (buf[j] < 0x7f))
|
|
printf("%c",buf[j]);
|
|
else
|
|
printf(".");
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Display a date
|
|
*/
|
|
|
|
static void showdate(const char *text, le64 lestamp)
|
|
{
|
|
time_t utime;
|
|
struct tm *ptm;
|
|
s64 stamp;
|
|
const char *months[]
|
|
= { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
|
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec" } ;
|
|
|
|
stamp = le64_to_cpu(lestamp);
|
|
if ((stamp < ((2147000000 + 134774*86400LL)*10000000LL))
|
|
&& (stamp > ((-2147000000 + 134774*86400LL)*10000000LL))) {
|
|
/* date within traditional Unix limits */
|
|
utime = stamp/10000000 - 134774*86400LL;
|
|
ptm = gmtime(&utime);
|
|
printf("%s %02d %3s %4d %2d:%02d:%02d UTC\n",
|
|
text,
|
|
ptm->tm_mday,months[ptm->tm_mon],ptm->tm_year+1900,
|
|
ptm->tm_hour,ptm->tm_min,ptm->tm_sec);
|
|
} else {
|
|
u32 days;
|
|
unsigned int year;
|
|
int mon;
|
|
int cnt;
|
|
|
|
days = stamp/(86400*10000000LL);
|
|
year = 1601;
|
|
/* periods of 400 years */
|
|
cnt = days/146097;
|
|
days -= 146097*cnt;
|
|
year += 400*cnt;
|
|
/* periods of 100 years */
|
|
cnt = (3*days + 3)/109573;
|
|
days -= 36524*cnt;
|
|
year += 100*cnt;
|
|
/* periods of 4 years */
|
|
cnt = days/1461;
|
|
days -= 1461L*cnt;
|
|
year += 4*cnt;
|
|
/* periods of a single year */
|
|
cnt = (3*days + 3)/1096;
|
|
days -= 365*cnt;
|
|
year += cnt;
|
|
|
|
if ((!(year % 100) ? (year % 400) : (year % 4))
|
|
&& (days > 58)) days++;
|
|
if (days > 59) {
|
|
mon = (5*days + 161)/153;
|
|
days -= (153*mon - 162)/5;
|
|
} else {
|
|
mon = days/31 + 1;
|
|
days -= 31*(mon - 1) - 1;
|
|
}
|
|
if (mon > 12)
|
|
{
|
|
printf("** Bad day stamp %lld days %lu mon %d year %u\n",
|
|
(long long)stamp,(unsigned long)days,mon,year);
|
|
}
|
|
printf("%s %02u %3s %4u\n",text,
|
|
(unsigned int)days,months[mon-1],(unsigned int)year);
|
|
}
|
|
}
|
|
|
|
void showname(const char *prefix, const char *name, int cnt)
|
|
{
|
|
const le16 *n;
|
|
int i;
|
|
int c;
|
|
|
|
printf("%s",prefix);
|
|
n = (const le16*)name;
|
|
for (i=0; (i<cnt) && n[i]; i++) {
|
|
c = le16_to_cpu(n[i]);
|
|
if (c < 0x20)
|
|
printf(".");
|
|
else
|
|
if (c < 0x80)
|
|
printf("%c",c);
|
|
else
|
|
if (c < 0x800)
|
|
printf("%c%c",
|
|
(c >> 6) + 0xc0,
|
|
(c & 63) + 0x80);
|
|
else
|
|
printf("%c%c%c",
|
|
(c >> 12) + 0xe0,
|
|
((c >> 6) & 63) + 0x80,
|
|
(c & 63) + 0x80);
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
static const char *commitment(u64 lsn)
|
|
{
|
|
const char *commit;
|
|
s64 diff;
|
|
|
|
/* Computations assume lsn could wraparound, they probably never do */
|
|
diff = lsn - synced_lsn;
|
|
if (diff <= 0)
|
|
commit = "synced";
|
|
else {
|
|
diff = lsn - committed_lsn;
|
|
if (diff <= 0)
|
|
commit = "committed";
|
|
else {
|
|
/* may find lsn from older session */
|
|
diff = lsn - latest_lsn;
|
|
if (diff <= 0)
|
|
commit = "*uncommitted*";
|
|
else
|
|
commit = "*stale*";
|
|
}
|
|
}
|
|
return (commit);
|
|
}
|
|
|
|
const char *actionname(int op)
|
|
{
|
|
static char buffer[24];
|
|
const char *p;
|
|
|
|
switch (op) {
|
|
case Noop :
|
|
p = "Noop";
|
|
break;
|
|
case CompensationlogRecord :
|
|
p = "CompensationlogRecord";
|
|
break;
|
|
case InitializeFileRecordSegment :
|
|
p = "InitializeFileRecordSegment";
|
|
break;
|
|
case DeallocateFileRecordSegment :
|
|
p = "DeallocateFileRecordSegment";
|
|
break;
|
|
case WriteEndofFileRecordSegment :
|
|
p = "WriteEndofFileRecordSegment";
|
|
break;
|
|
case CreateAttribute :
|
|
p = "CreateAttribute";
|
|
break;
|
|
case DeleteAttribute :
|
|
p = "DeleteAttribute";
|
|
break;
|
|
case UpdateResidentValue :
|
|
p = "UpdateResidentValue";
|
|
break;
|
|
case UpdateNonResidentValue :
|
|
p = "UpdateNonResidentValue";
|
|
break;
|
|
case UpdateMappingPairs :
|
|
p = "UpdateMappingPairs";
|
|
break;
|
|
case DeleteDirtyClusters :
|
|
p = "DeleteDirtyClusters";
|
|
break;
|
|
case SetNewAttributeSizes :
|
|
p = "SetNewAttributeSizes";
|
|
break;
|
|
case AddIndexEntryRoot :
|
|
p = "AddIndexEntryRoot";
|
|
break;
|
|
case DeleteIndexEntryRoot :
|
|
p = "DeleteIndexEntryRoot";
|
|
break;
|
|
case AddIndexEntryAllocation :
|
|
p = "AddIndexEntryAllocation";
|
|
break;
|
|
case DeleteIndexEntryAllocation :
|
|
p = "DeleteIndexEntryAllocation";
|
|
break;
|
|
case WriteEndOfIndexBuffer :
|
|
p = "WriteEndOfIndexBuffer";
|
|
break;
|
|
case SetIndexEntryVcnRoot :
|
|
p = "SetIndexEntryVcnRoot";
|
|
break;
|
|
case SetIndexEntryVcnAllocation :
|
|
p = "SetIndexEntryVcnAllocation";
|
|
break;
|
|
case UpdateFileNameRoot :
|
|
p = "UpdateFileNameRoot";
|
|
break;
|
|
case UpdateFileNameAllocation :
|
|
p = "UpdateFileNameAllocation";
|
|
break;
|
|
case SetBitsInNonResidentBitMap :
|
|
p = "SetBitsInNonResidentBitMap";
|
|
break;
|
|
case ClearBitsInNonResidentBitMap :
|
|
p = "ClearBitsInNonResidentBitMap";
|
|
break;
|
|
case HotFix :
|
|
p = "HotFix";
|
|
break;
|
|
case EndTopLevelAction :
|
|
p = "EndTopLevelAction";
|
|
break;
|
|
case PrepareTransaction :
|
|
p = "PrepareTransaction";
|
|
break;
|
|
case CommitTransaction :
|
|
p = "CommitTransaction";
|
|
break;
|
|
case ForgetTransaction :
|
|
p = "ForgetTransaction";
|
|
break;
|
|
case OpenNonResidentAttribute :
|
|
p = "OpenNonResidentAttribute";
|
|
break;
|
|
case OpenAttributeTableDump :
|
|
p = "OpenAttributeTableDump";
|
|
break;
|
|
case AttributeNamesDump :
|
|
p = "AttributeNamesDump";
|
|
break;
|
|
case DirtyPageTableDump :
|
|
p = "DirtyPageTableDump";
|
|
break;
|
|
case TransactionTableDump :
|
|
p = "TransactionTableDump";
|
|
break;
|
|
case UpdateRecordDataRoot :
|
|
p = "UpdateRecordDataRoot";
|
|
break;
|
|
case UpdateRecordDataAllocation :
|
|
p = "UpdateRecordDataAllocation";
|
|
break;
|
|
case Win10Action35 :
|
|
p = "Win10Action35";
|
|
break;
|
|
case Win10Action36 :
|
|
p = "Win10Action36";
|
|
break;
|
|
case Win10Action37 :
|
|
p = "Win10Action37";
|
|
break;
|
|
default :
|
|
sprintf(buffer,"*Unknown-Action-%d*",op);
|
|
p = buffer;
|
|
break;
|
|
}
|
|
return (p);
|
|
}
|
|
|
|
static const char *attrname(unsigned int key)
|
|
{
|
|
static char name[256];
|
|
const char *p;
|
|
struct ATTR *pa;
|
|
unsigned int i;
|
|
|
|
if ((key <= 65535) && !(key & 3)) {
|
|
pa = getattrentry(key,0);
|
|
if (pa) {
|
|
if (!pa->namelen)
|
|
p = "Unnamed";
|
|
else {
|
|
p = name;
|
|
/* Assume ascii for now */
|
|
for (i=0; 2*i<pa->namelen; i++)
|
|
name[i] = le16_to_cpu(pa->name[i]);
|
|
name[i] = 0;
|
|
}
|
|
} else
|
|
p = "Undefined";
|
|
} else
|
|
p = "Invalid";
|
|
return (p);
|
|
}
|
|
|
|
int fixnamelen(const char *name, int len)
|
|
{
|
|
int i;
|
|
|
|
i = 0;
|
|
while ((i < len) && (name[i] || name[i + 1]))
|
|
i += 2;
|
|
return (i);
|
|
}
|
|
|
|
const char *mftattrname(ATTR_TYPES attr)
|
|
{
|
|
static char badattr[24];
|
|
const char *p;
|
|
|
|
switch (attr) {
|
|
case AT_STANDARD_INFORMATION :
|
|
p = "Standard-Information";
|
|
break;
|
|
case AT_ATTRIBUTE_LIST :
|
|
p = "Attribute-List";
|
|
break;
|
|
case AT_FILE_NAME :
|
|
p = "Name";
|
|
break;
|
|
case AT_OBJECT_ID :
|
|
p = "Volume-Version";
|
|
break;
|
|
case AT_SECURITY_DESCRIPTOR :
|
|
p = "Security-Descriptor";
|
|
break;
|
|
case AT_VOLUME_NAME :
|
|
p = "Volume-Name";
|
|
break;
|
|
case AT_VOLUME_INFORMATION :
|
|
p = "Volume-Information";
|
|
break;
|
|
case AT_DATA :
|
|
p = "Data";
|
|
break;
|
|
case AT_INDEX_ROOT :
|
|
p = "Index-Root";
|
|
break;
|
|
case AT_INDEX_ALLOCATION :
|
|
p = "Index-Allocation";
|
|
break;
|
|
case AT_BITMAP :
|
|
p = "Bitmap";
|
|
break;
|
|
case AT_REPARSE_POINT :
|
|
p = "Reparse-Point";
|
|
break;
|
|
case AT_EA_INFORMATION :
|
|
p = "EA-Information";
|
|
break;
|
|
case AT_EA :
|
|
p = "EA";
|
|
break;
|
|
case AT_PROPERTY_SET :
|
|
p = "Property-Set";
|
|
break;
|
|
case AT_LOGGED_UTILITY_STREAM :
|
|
p = "Logged-Utility-Stream";
|
|
break;
|
|
case AT_END :
|
|
p = "End";
|
|
break;
|
|
default :
|
|
sprintf(badattr,"*0x%x-Unknown*",attr);
|
|
p = badattr;
|
|
break;
|
|
}
|
|
return (p);
|
|
}
|
|
|
|
static void showattribute(const char *prefix, const struct ATTR *pa)
|
|
{
|
|
if (pa) {
|
|
if (pa->type) {
|
|
printf("%sattr 0x%x : inode %lld type %s",
|
|
prefix, pa->key, (long long)pa->inode,
|
|
mftattrname(pa->type));
|
|
if (pa->namelen)
|
|
showname(" name ",(const char*)pa->name,
|
|
pa->namelen/2);
|
|
else
|
|
printf("\n");
|
|
} else {
|
|
if (pa->namelen) {
|
|
printf("%sattr 0x%x : type Unknown",
|
|
prefix, pa->key);
|
|
showname(" name ",(const char*)pa->name,
|
|
pa->namelen/2);
|
|
} else
|
|
printf("%s(definition of attr 0x%x not met)\n",
|
|
prefix, pa->key);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Determine if an action acts on the MFT
|
|
*/
|
|
|
|
static BOOL acts_on_mft(int op)
|
|
{
|
|
BOOL onmft;
|
|
|
|
/* A few actions may have to be added to the list */
|
|
switch (op) {
|
|
case InitializeFileRecordSegment :
|
|
case DeallocateFileRecordSegment :
|
|
case CreateAttribute :
|
|
case DeleteAttribute :
|
|
case UpdateResidentValue :
|
|
case UpdateMappingPairs :
|
|
case SetNewAttributeSizes :
|
|
case AddIndexEntryRoot :
|
|
case DeleteIndexEntryRoot :
|
|
case UpdateFileNameRoot :
|
|
case WriteEndofFileRecordSegment :
|
|
case Win10Action37 :
|
|
onmft = TRUE;
|
|
break;
|
|
default :
|
|
onmft = FALSE;
|
|
break;
|
|
}
|
|
return (onmft);
|
|
}
|
|
|
|
u32 get_undo_offset(const LOG_RECORD *logr)
|
|
{
|
|
u32 offset;
|
|
|
|
if (logr->lcns_to_follow)
|
|
offset = 0x30 + le16_to_cpu(logr->undo_offset);
|
|
else
|
|
offset = 0x28 + le16_to_cpu(logr->undo_offset);
|
|
return (offset);
|
|
}
|
|
|
|
u32 get_redo_offset(const LOG_RECORD *logr)
|
|
{
|
|
u32 offset;
|
|
|
|
if (logr->lcns_to_follow)
|
|
offset = 0x30 + le16_to_cpu(logr->redo_offset);
|
|
else
|
|
offset = 0x28 + le16_to_cpu(logr->redo_offset);
|
|
return (offset);
|
|
}
|
|
|
|
u32 get_extra_offset(const LOG_RECORD *logr)
|
|
{
|
|
u32 uoffset;
|
|
u32 roffset;
|
|
|
|
roffset = get_redo_offset(logr)
|
|
+ le16_to_cpu(logr->redo_length);
|
|
uoffset = get_undo_offset(logr)
|
|
+ le16_to_cpu(logr->undo_length);
|
|
return ((((uoffset > roffset ? uoffset : roffset) - 1) | 7) + 1);
|
|
}
|
|
|
|
static BOOL likelyop(const LOG_RECORD *logr)
|
|
{
|
|
BOOL likely;
|
|
|
|
switch (logr->record_type) {
|
|
case LOG_STANDARD : /* standard record */
|
|
/* Operations in range 0..LastAction-1, can be both null */
|
|
likely = ((unsigned int)le16_to_cpu(logr->redo_operation)
|
|
< LastAction)
|
|
&& ((unsigned int)le16_to_cpu(logr->undo_operation)
|
|
< LastAction)
|
|
/* Offsets aligned to 8 bytes */
|
|
&& !(le16_to_cpu(logr->redo_offset) & 7)
|
|
&& !(le16_to_cpu(logr->undo_offset) & 7)
|
|
/* transaction id must not be null */
|
|
&& logr->transaction_id
|
|
/* client data length aligned to 8 bytes */
|
|
&& !(le32_to_cpu(logr->client_data_length) & 7)
|
|
/* client data length less than 64K (131K ?) */
|
|
&& (le32_to_cpu(logr->client_data_length) < MAXRECSIZE)
|
|
/* if there is redo data, offset must be >= 0x28 */
|
|
&& (!le16_to_cpu(logr->redo_length)
|
|
|| ((unsigned int)le16_to_cpu(logr->redo_offset) >= 0x28))
|
|
/* if there is undo data, offset must be >= 0x28 */
|
|
&& (!le16_to_cpu(logr->undo_length)
|
|
|| ((unsigned int)le16_to_cpu(logr->undo_offset) >= 0x28));
|
|
/* undo data and redo data should be contiguous when both present */
|
|
if (likely && logr->redo_length && logr->undo_length) {
|
|
/* undo and redo data may be the same when both present and same size */
|
|
if (logr->undo_offset == logr->redo_offset) {
|
|
if (logr->redo_length != logr->undo_length)
|
|
likely = FALSE;
|
|
} else {
|
|
if (le16_to_cpu(logr->redo_offset)
|
|
< le16_to_cpu(logr->undo_offset)) {
|
|
/* undo expected just after redo */
|
|
if ((((le16_to_cpu(logr->redo_offset)
|
|
+ le16_to_cpu(logr->redo_length)
|
|
- 1) | 7) + 1)
|
|
!= le16_to_cpu(logr->undo_offset))
|
|
likely = FALSE;
|
|
} else {
|
|
/* redo expected just after undo */
|
|
if ((((le16_to_cpu(logr->undo_offset)
|
|
+ le16_to_cpu(logr->undo_length)
|
|
- 1) | 7) + 1)
|
|
!= le16_to_cpu(logr->redo_offset))
|
|
likely = FALSE;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case LOG_CHECKPOINT : /* check-point */
|
|
/*
|
|
* undo and redo operations are null
|
|
* or CompensationlogRecord with no data
|
|
*/
|
|
likely = (!logr->redo_operation
|
|
|| ((logr->redo_operation == const_cpu_to_le16(1))
|
|
&& !logr->redo_length))
|
|
&& (!logr->undo_operation
|
|
|| ((logr->undo_operation == const_cpu_to_le16(1))
|
|
&& !logr->undo_length))
|
|
/* transaction id must be null */
|
|
&& !logr->transaction_id
|
|
/* client_data_length is 0x68 or 0x70 (Vista and subsequent) */
|
|
&& ((le32_to_cpu(logr->client_data_length) == 0x68)
|
|
|| (le32_to_cpu(logr->client_data_length) == 0x70));
|
|
break;
|
|
default :
|
|
likely = FALSE;
|
|
break;
|
|
}
|
|
return (likely);
|
|
}
|
|
|
|
/*
|
|
* Search for a likely record in a block
|
|
*
|
|
* Must not be used when syncing.
|
|
*
|
|
* Returns 0 when not found
|
|
*/
|
|
|
|
static u16 searchlikely(const struct BUFFER *buf)
|
|
{
|
|
const LOG_RECORD *logr;
|
|
const char *data;
|
|
u16 k;
|
|
|
|
if (opts)
|
|
printf("** Error : searchlikely() used for syncing\n");
|
|
data = buf->block.data;
|
|
k = buf->headsz;
|
|
logr = (const LOG_RECORD*)&data[k];
|
|
if (!likelyop(logr)) {
|
|
do {
|
|
k += 8;
|
|
logr = (const LOG_RECORD*)&data[k];
|
|
} while ((k <= (blocksz - LOG_RECORD_HEAD_SZ))
|
|
&& !likelyop(logr));
|
|
if (k > (blocksz - LOG_RECORD_HEAD_SZ))
|
|
k = 0;
|
|
}
|
|
return (k);
|
|
}
|
|
|
|
/*
|
|
* From a previous block, determine the location of first record
|
|
*
|
|
* The previous block must have the beginning of an overlapping
|
|
* record, and the current block must have the beginning of next
|
|
* record (which can overlap on next blocks).
|
|
* The argument "skipped" is the number of blocks in-between.
|
|
*
|
|
* Note : the overlapping record from previous block does not reach
|
|
* the current block when it ends near the end of the last skipped block.
|
|
*
|
|
* Returns 0 if some bad condition is found
|
|
* Returns near blocksz when there is no beginning of record in
|
|
* the current block
|
|
*/
|
|
|
|
static u16 firstrecord(int skipped, const struct BUFFER *buf,
|
|
const struct BUFFER *prevbuf)
|
|
{
|
|
const RECORD_PAGE_HEADER *rph;
|
|
const RECORD_PAGE_HEADER *prevrph;
|
|
const LOG_RECORD *logr;
|
|
const char *data;
|
|
const char *prevdata;
|
|
u16 k;
|
|
u16 blkheadsz;
|
|
s32 size;
|
|
|
|
rph = &buf->block.record;
|
|
data = buf->block.data;
|
|
if (prevbuf) {
|
|
prevrph = &prevbuf->block.record;
|
|
prevdata = prevbuf->block.data;
|
|
blkheadsz = prevbuf->headsz;
|
|
/* From previous page, determine where the current one starts */
|
|
k = le16_to_cpu(prevrph->next_record_offset);
|
|
/* a null value means there is no full record in next block */
|
|
if (!k)
|
|
k = blkheadsz;
|
|
} else
|
|
k = 0;
|
|
/* Minimal size is apparently 48 : offset of redo_operation */
|
|
if (k && ((blocksz - k) >= LOG_RECORD_HEAD_SZ)) {
|
|
logr = (const LOG_RECORD*)&prevdata[k];
|
|
if (!logr->client_data_length) {
|
|
/*
|
|
* Sometimes the end of record is free space.
|
|
* This apparently means reaching the end of
|
|
* a previous session, and must be considered
|
|
* as an error.
|
|
* We however tolerate this, unless syncing
|
|
* is requested.
|
|
*/
|
|
printf("* Reaching free space at end of block %d\n",
|
|
(int)prevbuf->num);
|
|
/* As a consequence, there cannot be skipped blocks */
|
|
if (skipped) {
|
|
printf("*** Inconsistency : blocks skipped after free space\n");
|
|
k = 0; /* error returned */
|
|
}
|
|
if (opts)
|
|
k = 0;
|
|
else {
|
|
k = searchlikely(buf);
|
|
printf("* Skipping over free space\n");
|
|
}
|
|
} else {
|
|
size = le32_to_cpu(logr->client_data_length)
|
|
+ LOG_RECORD_HEAD_SZ;
|
|
if ((size < MINRECSIZE) || (size > MAXRECSIZE)) {
|
|
printf("** Bad record size %ld in block %ld"
|
|
" offset 0x%x\n",
|
|
(long)size,(long)prevbuf->num,(int)k);
|
|
k = blkheadsz;
|
|
} else {
|
|
if ((int)(blocksz - k) >= size)
|
|
printf("*** Inconsistency : the final"
|
|
" record does not overlap\n");
|
|
k += size - (blocksz - blkheadsz)*(skipped + 1);
|
|
}
|
|
if ((k <= blkheadsz)
|
|
&& (k > (blkheadsz - LOG_RECORD_HEAD_SZ))) {
|
|
/* There were not enough space in the last skipped block */
|
|
k = blkheadsz;
|
|
} else {
|
|
if (optv
|
|
&& ((blocksz - k) < LOG_RECORD_HEAD_SZ)) {
|
|
/* Not an error : just no space */
|
|
printf("No minimal record space\n");
|
|
}
|
|
if (optv >= 2)
|
|
printf("Overlapping record from block %d,"
|
|
" starting at offset 0x%x\n",
|
|
(int)prevbuf->num,(int)k);
|
|
}
|
|
}
|
|
} else {
|
|
k = buf->headsz;
|
|
if (optv >= 2) {
|
|
if (prevbuf)
|
|
printf("No minimal record from block %d,"
|
|
" starting at offset 0x%x\n",
|
|
(int)prevbuf->num, (int)k);
|
|
else
|
|
printf("No block before %d,"
|
|
" starting at offset 0x%x\n",
|
|
(int)buf->num, (int)k);
|
|
}
|
|
}
|
|
/*
|
|
* In a wraparound situation, there is frequently no
|
|
* match... because there were no wraparound.
|
|
* Return an error if syncing is requested, otherwise
|
|
* try to find a starting record.
|
|
*/
|
|
if (k && prevbuf && (prevbuf->num > buf->num)) {
|
|
logr = (const LOG_RECORD*)&data[k];
|
|
/* Accept reaching the end with no record beginning */
|
|
if ((k != le16_to_cpu(rph->next_record_offset))
|
|
&& !likelyop(logr)) {
|
|
if (opts) {
|
|
k = 0;
|
|
printf("** Could not wraparound\n");
|
|
} else {
|
|
k = searchlikely(buf);
|
|
printf("* Skipping over bad wraparound\n");
|
|
}
|
|
}
|
|
}
|
|
return (k);
|
|
}
|
|
|
|
/*
|
|
* Find the block which defines the first record in current one
|
|
*
|
|
* Either the wanted block has the beginning of a record overlapping
|
|
* on current one, or it ends in such as there is no space for an
|
|
* overlapping one.
|
|
*
|
|
* Returns 0 if the previous block cannot be determined.
|
|
*/
|
|
|
|
static const struct BUFFER *findprevious(CONTEXT *ctx, const struct BUFFER *buf)
|
|
{
|
|
const struct BUFFER *prevbuf;
|
|
const struct BUFFER *savebuf;
|
|
const RECORD_PAGE_HEADER *rph;
|
|
int skipped;
|
|
int prevblk;
|
|
BOOL prevmiddle;
|
|
BOOL error;
|
|
u16 endoff;
|
|
|
|
error = FALSE;
|
|
prevblk = buf->num;
|
|
savebuf = (struct BUFFER*)NULL;
|
|
skipped = 0;
|
|
do {
|
|
prevmiddle = FALSE;
|
|
if (prevblk > BASEBLKS)
|
|
prevblk--;
|
|
else
|
|
if (prevblk == BASEBLKS)
|
|
prevblk = (logfilesz >> blockbits) - 1;
|
|
else {
|
|
rph = &buf->block.record;
|
|
prevblk = (sle64_to_cpu(rph->copy.file_offset)
|
|
>> blockbits) - 1;
|
|
/*
|
|
* If an initial block leads to block 4, it
|
|
* can mean the last block or no previous
|
|
* block at all. Using the last block is safer,
|
|
* its lsn will indicate whether it is stale.
|
|
*/
|
|
if (prevblk < BASEBLKS)
|
|
prevblk = (logfilesz >> blockbits) - 1;
|
|
}
|
|
/* No previous block if the log only consists of block 2 or 3 */
|
|
if (prevblk < BASEBLKS) {
|
|
prevbuf = (struct BUFFER*)NULL;
|
|
error = TRUE; /* not a real error */
|
|
} else {
|
|
prevbuf = read_buffer(ctx, prevblk);
|
|
if (prevbuf) {
|
|
rph = &prevbuf->block.record;
|
|
prevmiddle = !(rph->flags
|
|
& const_cpu_to_le32(1))
|
|
|| !rph->next_record_offset;
|
|
if (prevmiddle) {
|
|
savebuf = prevbuf;
|
|
skipped++;
|
|
}
|
|
} else {
|
|
error = TRUE;
|
|
printf("** Could not read block %d\n",
|
|
(int)prevblk);
|
|
}
|
|
}
|
|
} while (prevmiddle && !error);
|
|
|
|
if (!prevmiddle && !error && skipped) {
|
|
/* No luck if there is not enough space in this record */
|
|
rph = &prevbuf->block.record;
|
|
endoff = le16_to_cpu(rph->next_record_offset);
|
|
if (endoff > (blocksz - LOG_RECORD_HEAD_SZ)) {
|
|
prevbuf = savebuf;
|
|
}
|
|
}
|
|
return (error ? (struct BUFFER*)NULL : prevbuf);
|
|
}
|
|
|
|
void copy_attribute(struct ATTR *pa, const char *buf, int length)
|
|
{
|
|
const ATTR_NEW *panew;
|
|
ATTR_OLD old_aligned;
|
|
|
|
if (pa) {
|
|
switch (length) {
|
|
case sizeof(ATTR_NEW) :
|
|
panew = (const ATTR_NEW*)buf;
|
|
pa->type = panew->type;
|
|
pa->lsn = sle64_to_cpu(panew->lsn);
|
|
pa->inode = MREF(le64_to_cpu(panew->inode));
|
|
break;
|
|
case sizeof(ATTR_OLD) :
|
|
/* Badly aligned, first realign */
|
|
memcpy(&old_aligned,buf,sizeof(old_aligned));
|
|
pa->type = old_aligned.type;
|
|
pa->lsn = sle64_to_cpu(old_aligned.lsn);
|
|
pa->inode = MREF(le64_to_cpu(old_aligned.inode));
|
|
break;
|
|
default :
|
|
printf("** Unexpected attribute format, length %d\n",
|
|
length);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int refresh_attributes(const struct ACTION_RECORD *firstaction)
|
|
{
|
|
const struct ACTION_RECORD *action;
|
|
const LOG_RECORD *logr;
|
|
struct ATTR *pa;
|
|
const char *buf;
|
|
u32 extra;
|
|
u32 length;
|
|
u32 len;
|
|
u32 key;
|
|
u32 x;
|
|
u32 i;
|
|
u32 step;
|
|
u32 used;
|
|
|
|
for (action=firstaction; action; action=action->next) {
|
|
logr = &action->record;
|
|
buf = ((const char*)logr) + get_redo_offset(logr);
|
|
length = le16_to_cpu(logr->redo_length);
|
|
switch (le16_to_cpu(action->record.redo_operation)) {
|
|
case OpenNonResidentAttribute :
|
|
extra = get_extra_offset(logr)
|
|
- get_redo_offset(logr);
|
|
if (logr->undo_length) {
|
|
len = le32_to_cpu(logr->client_data_length)
|
|
+ LOG_RECORD_HEAD_SZ
|
|
- get_extra_offset(logr);
|
|
/* this gives a length aligned modulo 8 */
|
|
len = fixnamelen(&buf[extra], len);
|
|
} else
|
|
len = 0;
|
|
pa = getattrentry(le16_to_cpu(logr->target_attribute),
|
|
len);
|
|
if (pa) {
|
|
copy_attribute(pa, buf, length);
|
|
pa->namelen = len;
|
|
if (len) {
|
|
memcpy(pa->name,&buf[extra],len);
|
|
}
|
|
}
|
|
break;
|
|
case OpenAttributeTableDump :
|
|
i = 24;
|
|
step = getle16(buf, 8);
|
|
used = getle16(buf, 12);
|
|
/*
|
|
* Changed from Win10, formerly we got step = 44.
|
|
* The record layout has also changed
|
|
*/
|
|
for (x=0; (x<used) && (i<length); i+=step, x++) {
|
|
pa = getattrentry(i,0);
|
|
if (pa) {
|
|
copy_attribute(pa, buf + i, step);
|
|
}
|
|
}
|
|
break;
|
|
case AttributeNamesDump :
|
|
i = 8;
|
|
if (i < length) {
|
|
x = 0;
|
|
do {
|
|
len = getle16(buf, i + 2);
|
|
key = getle16(buf, i);
|
|
if (len > 510) {
|
|
printf("** Error : bad"
|
|
" attribute name"
|
|
" length %d\n",
|
|
len);
|
|
key = 0;
|
|
}
|
|
if (key) { /* Apparently, may have to stop before reaching the end */
|
|
pa = getattrentry(key,len);
|
|
if (pa) {
|
|
pa->namelen = len;
|
|
memcpy(pa->name,
|
|
&buf[i+4],len);
|
|
}
|
|
i += len + 6;
|
|
x++;
|
|
}
|
|
} while (key && (i < length));
|
|
}
|
|
break;
|
|
default :
|
|
break;
|
|
}
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Display a fixup
|
|
*/
|
|
|
|
static void fixup(CONTEXT *ctx, const LOG_RECORD *logr, const char *buf,
|
|
BOOL redo)
|
|
{
|
|
struct ATTR *pa;
|
|
int action;
|
|
int attr;
|
|
int offs;
|
|
s32 length;
|
|
int extra;
|
|
s32 i;
|
|
int p;
|
|
s32 base;
|
|
u16 firstpos; /* position of first mft attribute */
|
|
le32 v;
|
|
ATTR_TYPES mftattr;
|
|
le64 w;
|
|
le64 inode;
|
|
le64 size;
|
|
int lth;
|
|
int len;
|
|
|
|
attr = le16_to_cpu(logr->target_attribute);
|
|
offs = le16_to_cpu(logr->attribute_offset);
|
|
if (redo) {
|
|
action = le16_to_cpu(logr->redo_operation);
|
|
length = le16_to_cpu(logr->redo_length);
|
|
} else {
|
|
action = le16_to_cpu(logr->undo_operation);
|
|
length = le16_to_cpu(logr->undo_length);
|
|
}
|
|
if (redo)
|
|
printf("redo fixup %dR %s attr 0x%x offs 0x%x\n",
|
|
actionnum, actionname(action), attr, offs);
|
|
else
|
|
printf("undo fixup %dU %s attr 0x%x offs 0x%x\n",
|
|
actionnum, actionname(action), attr, offs);
|
|
switch (action) {
|
|
case InitializeFileRecordSegment : /* 2 */
|
|
/*
|
|
* When this is a redo (with a NoOp undo), the
|
|
* full MFT record is logged.
|
|
* When this is an undo (with DeallocateFileRecordSegment redo),
|
|
* only the header of the MFT record is logged.
|
|
*/
|
|
if (!ctx->vol && !mftrecsz && (length > 8)) {
|
|
/* mftrecsz can be determined from usa_count */
|
|
mftrecsz = (getle16(buf,6) - 1)*512;
|
|
mftrecbits = 1;
|
|
while ((u32)(1 << mftrecbits) < mftrecsz)
|
|
mftrecbits++;
|
|
}
|
|
printf(" new base MFT record, attr 0x%x (%s)\n",attr,attrname(attr));
|
|
printf(" inode %lld\n",
|
|
(((long long)le64_to_cpu(logr->target_vcn)
|
|
<< clusterbits)
|
|
+ (le16_to_cpu(logr->cluster_index) << 9))
|
|
>> mftrecbits);
|
|
if (length >= 18)
|
|
printf(" seq number 0x%04x\n",(int)getle16(buf, 16));
|
|
if (length >= 20)
|
|
printf(" link count %d\n",(int)getle16(buf, 18));
|
|
if (length >= 24) {
|
|
u16 flags;
|
|
|
|
flags = getle16(buf, 22);
|
|
printf(" flags 0x%x",(int)flags);
|
|
switch (flags & 3) {
|
|
case 1 :
|
|
printf(" (file in use)\n");
|
|
break;
|
|
case 3 :
|
|
printf(" (directory in use)\n");
|
|
break;
|
|
default :
|
|
printf(" (not in use)\n");
|
|
break;
|
|
}
|
|
}
|
|
base = getle16(buf, 4) + ((getle16(buf, 6)*2 - 1) | 7) + 1;
|
|
while (base < length) {
|
|
mftattr = feedle32(buf, base);
|
|
printf(" attrib 0x%lx (%s) at offset 0x%x\n",
|
|
(long)le32_to_cpu(mftattr),
|
|
mftattrname(mftattr), (int)base);
|
|
if (mftattr == AT_FILE_NAME) {
|
|
showname(" name ",&buf[base + 90],
|
|
buf[base + 88] & 255);
|
|
inode = feedle64(buf, base + 24);
|
|
printf(" parent dir inode %lld\n",
|
|
(long long)MREF(le64_to_cpu(inode)));
|
|
}
|
|
lth = getle32(buf, base + 4);
|
|
if ((lth <= 0) || (lth & 7))
|
|
base = length;
|
|
else
|
|
base += lth;
|
|
}
|
|
break;
|
|
case DeallocateFileRecordSegment : /* 3 */
|
|
printf(" free base MFT record, attr 0x%x (%s)\n",
|
|
attr,attrname(attr));
|
|
printf(" inode %lld\n",
|
|
(((long long)le64_to_cpu(logr->target_vcn) << clusterbits)
|
|
+ (le16_to_cpu(logr->cluster_index) << 9)) >> mftrecbits);
|
|
break;
|
|
case CreateAttribute : /* 5 */
|
|
pa = getattrentry(attr,0);
|
|
base = 24;
|
|
/* Assume the beginning of the attribute is always present */
|
|
switch (getle32(buf,0)) {
|
|
case 0x30 :
|
|
printf(" create file name, attr 0x%x\n",attr);
|
|
if (pa)
|
|
showattribute(" ",pa);
|
|
showname(" file ",
|
|
&buf[base + 66],buf[base + 64] & 255);
|
|
if (base >= -8)
|
|
showdate(" created ",feedle64(buf,base + 8));
|
|
if (base >= -16)
|
|
showdate(" modified ",feedle64(buf,base + 16));
|
|
if (base >= -24)
|
|
showdate(" changed ",feedle64(buf,base + 24));
|
|
if (base >= -32)
|
|
showdate(" read ",feedle64(buf,base + 32));
|
|
size = feedle64(buf,base + 40);
|
|
printf(" allocated size %lld\n",
|
|
(long long)le64_to_cpu(size));
|
|
size = feedle64(buf,base + 48);
|
|
printf(" real size %lld\n",
|
|
(long long)le64_to_cpu(size));
|
|
v = feedle32(buf,base + 56);
|
|
printf(" DOS flags 0x%lx\n",
|
|
(long)le32_to_cpu(v));
|
|
break;
|
|
case 0x80 :
|
|
printf(" create a data stream, attr 0x%x\n",attr);
|
|
break;
|
|
case 0xc0 :
|
|
printf(" create reparse data\n");
|
|
if (pa)
|
|
showattribute(" ",pa);
|
|
printf(" tag 0x%lx\n",(long)getle32(buf, base));
|
|
showname(" print name ",
|
|
&buf[base + 20 + getle16(buf, base + 12)],
|
|
getle16(buf, base + 14)/2);
|
|
break;
|
|
}
|
|
break;
|
|
case UpdateResidentValue : /* 7 */
|
|
/*
|
|
* The record offset designates the mft attribute offset,
|
|
* offs and length define a right-justified window in this
|
|
* attribute.
|
|
* At this stage, we do not know which kind of mft
|
|
* attribute this is about, we assume this is standard
|
|
* information when it is the first attribute in the
|
|
* record.
|
|
*/
|
|
base = 0x18 - offs; /* p 8 */
|
|
pa = getattrentry(attr,0);
|
|
firstpos = 0x30 + (((mftrecsz/512 + 1)*2 - 1 ) | 7) + 1;
|
|
if (pa
|
|
&& !pa->inode
|
|
&& (pa->type == const_cpu_to_le32(0x80))
|
|
&& !(offs & 3)
|
|
&& (le16_to_cpu(logr->record_offset) == firstpos)) {
|
|
printf(" set standard information, attr 0x%x\n",attr);
|
|
showattribute(" ",pa);
|
|
if ((base >= 0) && ((base + 8) <= length))
|
|
showdate(" created ",
|
|
feedle64(buf,base));
|
|
if (((base + 8) >= 0) && ((base + 16) <= length))
|
|
showdate(" modified ",
|
|
feedle64(buf,base + 8));
|
|
if (((base + 16) >= 0) && ((base + 24) <= length))
|
|
showdate(" changed ",
|
|
feedle64(buf,base + 16));
|
|
if (((base + 24) >= 0) && ((base + 32) <= length))
|
|
showdate(" read ",
|
|
feedle64(buf,base + 24));
|
|
if (((base + 32) >= 0) && ((base + 36) <= length)) {
|
|
v = feedle32(buf, base + 32);
|
|
printf(" DOS flags 0x%lx\n",
|
|
(long)le32_to_cpu(v));
|
|
}
|
|
if (((base + 52) >= 0) && ((base + 56) <= length)) {
|
|
v = feedle32(buf, base + 52);
|
|
printf(" security id 0x%lx\n",
|
|
(long)le32_to_cpu(v));
|
|
}
|
|
if (((base + 64) >= 0) && ((base + 72) <= length)) {
|
|
/*
|
|
* This is badly aligned for Sparc when
|
|
* stamps not present and base == 52
|
|
*/
|
|
memcpy(&w, &buf[base + 64], 8);
|
|
printf(" journal idx 0x%llx\n",
|
|
(long long)le64_to_cpu(w));
|
|
}
|
|
} else {
|
|
printf(" set an MFT attribute at offset 0x%x, attr 0x%x\n",
|
|
(int)offs, attr);
|
|
if (pa)
|
|
showattribute(" ",pa);
|
|
}
|
|
break;
|
|
case UpdateNonResidentValue : /* 8 */
|
|
printf(" set attr 0x%x (%s)\n",attr,attrname(attr));
|
|
pa = getattrentry(attr,0);
|
|
if (pa)
|
|
showattribute(" ",pa);
|
|
base = 0; /* ? */
|
|
// Should not be decoded, unless attr is of identified type (I30, ...)
|
|
if (pa && (pa->namelen == 8) && !memcmp(pa->name, SDS, 8)) {
|
|
if (length >= 4)
|
|
printf(" security hash 0x%lx\n",
|
|
(long)getle32(buf, 0));
|
|
if (length >= 8)
|
|
printf(" security id 0x%lx\n",
|
|
(long)getle32(buf, 4));
|
|
if (length >= 20)
|
|
printf(" entry size %ld\n",
|
|
(long)getle32(buf, 16));
|
|
}
|
|
if (pa && (pa->namelen == 8) && !memcmp(pa->name, I30, 8)) {
|
|
if (!memcmp(buf, "INDX", 4))
|
|
base = 64; /* full record */
|
|
else
|
|
base = 0; /* entries */
|
|
inode = feedle64(buf, base);
|
|
printf(" inode %lld\n",
|
|
(long long)MREF(le64_to_cpu(inode)));
|
|
inode = feedle64(buf, base + 16);
|
|
printf(" parent inode %lld\n",
|
|
(long long)MREF(le64_to_cpu(inode)));
|
|
showname(" file ",&buf[base + 82],
|
|
buf[base + 80] & 255);
|
|
showdate(" date ",feedle64(buf, base + 32));
|
|
}
|
|
break;
|
|
case UpdateMappingPairs : /* 9 */
|
|
printf(" update runlist in attr 0x%x (%s)\n",attr,
|
|
attrname(attr));
|
|
/* argument is a compressed runlist (or part of it ?) */
|
|
/* stop when finding 00 */
|
|
break;
|
|
case SetNewAttributeSizes : /* 11 */
|
|
printf(" set sizes in attr 0x%x (%s)\n",attr,attrname(attr));
|
|
base = 0; /* left justified ? */
|
|
size = feedle64(buf,0);
|
|
printf(" allocated size %lld\n",(long long)le64_to_cpu(size));
|
|
size = feedle64(buf,8);
|
|
printf(" real size %lld\n",(long long)le64_to_cpu(size));
|
|
size = feedle64(buf,16);
|
|
printf(" initialized size %lld\n",(long long)le64_to_cpu(size));
|
|
break;
|
|
case AddIndexEntryRoot : /* 12 */
|
|
case AddIndexEntryAllocation : /* 14 */
|
|
/*
|
|
* The record offset designates the mft attribute offset,
|
|
* offs and length define a left-justified window in this
|
|
* attribute.
|
|
*/
|
|
if (action == AddIndexEntryRoot)
|
|
printf(" add resident index entry, attr 0x%x\n",attr);
|
|
else
|
|
printf(" add nonres index entry, attr 0x%x\n",attr);
|
|
pa = getattrentry(attr,0);
|
|
if (pa)
|
|
showattribute(" ",pa);
|
|
base = 0;
|
|
p = getle16(buf, base + 8);
|
|
/* index types may be discriminated by inode in base+0 */
|
|
switch (p) { /* size of index entry */
|
|
case 32 : /* $R entry */
|
|
memcpy(&inode, &buf[base + 20], 8); /* bad align */
|
|
printf(" $R reparse index\n");
|
|
printf(" reparsed inode 0x%016llx\n",
|
|
(long long)le64_to_cpu(inode));
|
|
printf(" reparse tag 0x%lx\n",
|
|
(long)getle32(buf, 16));
|
|
break;
|
|
case 40 : /* $SII entry */
|
|
printf(" $SII security id index\n");
|
|
printf(" security id 0x%lx\n",
|
|
(long)getle32(buf, 16));
|
|
printf(" security hash 0x%lx\n",
|
|
(long)getle32(buf, 20));
|
|
break;
|
|
case 48 : /* $SDH entry */
|
|
printf(" $SDH security id index\n");
|
|
printf(" security id 0x%lx\n",
|
|
(long)getle32(buf, 20));
|
|
printf(" security hash 0x%lx\n",
|
|
(long)getle32(buf, 16));
|
|
break;
|
|
default :
|
|
/* directory index are at least 84 bytes long, ntfsdoc p 98 */
|
|
/* have everything needed to create the index */
|
|
lth = buf[base + 80] & 255;
|
|
/* consistency of file name length */
|
|
if (getle16(buf,10) == (u32)(2*lth + 66)) {
|
|
printf(" directory index\n");
|
|
inode = feedle64(buf,16);
|
|
printf(" parent dir inode %lld\n",
|
|
(long long)MREF(le64_to_cpu(inode)));
|
|
if (feedle32(buf,72)
|
|
& const_cpu_to_le32(0x10000000))
|
|
showname(" file (dir) ",
|
|
&buf[base + 82],
|
|
buf[base + 80] & 255);
|
|
else
|
|
showname(" file ",
|
|
&buf[base + 82],
|
|
buf[base + 80] & 255);
|
|
inode = feedle64(buf,0);
|
|
printf(" file inode %lld\n",
|
|
(long long)MREF(le64_to_cpu(inode)));
|
|
size = feedle64(buf,64);
|
|
printf(" file size %lld\n",
|
|
(long long)le64_to_cpu(size));
|
|
showdate(" created ",
|
|
feedle64(buf,base + 24));
|
|
showdate(" modified ",
|
|
feedle64(buf,base + 32));
|
|
showdate(" changed ",
|
|
feedle64(buf,base + 40));
|
|
showdate(" read ",
|
|
feedle64(buf,base + 48));
|
|
} else
|
|
printf(" unknown index type\n");
|
|
break;
|
|
}
|
|
break;
|
|
case SetIndexEntryVcnRoot : /* 17 */
|
|
printf(" set vcn of non-resident index root, attr 0x%x\n",
|
|
attr);
|
|
pa = getattrentry(attr,0);
|
|
if (pa)
|
|
showattribute(" ",pa);
|
|
printf(" vcn %lld\n", (long long)getle64(buf,0));
|
|
break;
|
|
case UpdateFileNameRoot : /* 19 */
|
|
/*
|
|
* Update an entry in a resident directory index.
|
|
* The record offset designates the mft attribute offset,
|
|
* offs and length define a right-justified window in this
|
|
* attribute.
|
|
*/
|
|
printf(" set directory resident entry, attr 0x%x\n",attr);
|
|
base = length - 0x50;
|
|
pa = getattrentry(attr,0);
|
|
if (pa)
|
|
showattribute(" ",pa);
|
|
if (pa
|
|
&& !pa->inode
|
|
&& (pa->type == const_cpu_to_le32(0x80))
|
|
&& !(offs & 3)) {
|
|
if (base >= -24)
|
|
showdate(" created ",feedle64(buf,
|
|
base + 24));
|
|
if (base >= -32)
|
|
showdate(" modified ",feedle64(buf,
|
|
base + 32));
|
|
if (base >= -40)
|
|
showdate(" changed ",feedle64(buf,
|
|
base + 40));
|
|
if (base >= -48)
|
|
showdate(" read ",feedle64(buf,
|
|
base + 48));
|
|
if (base >= -56) {
|
|
size = feedle64(buf,base + 56);
|
|
printf(" allocated size %lld\n",
|
|
(long long)le64_to_cpu(size));
|
|
}
|
|
if (base >= -64) {
|
|
size = feedle64(buf,base + 64);
|
|
printf(" real size %lld\n",
|
|
(long long)le64_to_cpu(size));
|
|
}
|
|
if (base > -72) {
|
|
v = feedle32(buf,base + 72);
|
|
printf(" DOS flags 0x%lx\n",
|
|
(long)le32_to_cpu(v));
|
|
}
|
|
} else {
|
|
/* Usually caused by attr not yet defined */
|
|
if (pa && pa->type)
|
|
printf("** Unexpected index parameters\n");
|
|
}
|
|
break;
|
|
case UpdateFileNameAllocation : /* 20 */
|
|
/* update entry in directory index */
|
|
/* only dates, sizes and attrib */
|
|
base = length - 64; /* p 12 */
|
|
printf(" set directory nonres entry, attr 0x%x\n",attr);
|
|
pa = getattrentry(attr,0);
|
|
if (pa)
|
|
showattribute(" ",pa);
|
|
if (base >= -8)
|
|
showdate(" created ",feedle64(buf, base + 8));
|
|
if (base >= -16)
|
|
showdate(" modified ",feedle64(buf, base + 16));
|
|
if (base >= -24)
|
|
showdate(" changed ",feedle64(buf, base + 24));
|
|
if (base >= -32)
|
|
showdate(" read ",*(const le64*)&buf[base + 32]);
|
|
if (base >= -40) {
|
|
size = feedle64(buf, base + 40);
|
|
printf(" allocated size %lld\n",
|
|
(long long)le64_to_cpu(size));
|
|
}
|
|
if (base >= -48) {
|
|
size = feedle64(buf, base + 48);
|
|
printf(" real size %lld\n",
|
|
(long long)le64_to_cpu(size));
|
|
}
|
|
if (base >= -56) {
|
|
v = feedle32(buf, base + 56);
|
|
printf(" DOS flags 0x%lx\n",(long)le32_to_cpu(v));
|
|
}
|
|
break;
|
|
case SetBitsInNonResidentBitMap : /* 21 */
|
|
case ClearBitsInNonResidentBitMap : /* 22 */
|
|
if (action == SetBitsInNonResidentBitMap)
|
|
printf(" SetBitsInNonResidentBitMap, attr 0x%x\n",
|
|
attr);
|
|
else
|
|
printf(" ClearBitsInNonResidentBitMap, attr 0x%x\n",
|
|
attr);
|
|
pa = getattrentry(attr,0);
|
|
if (pa)
|
|
showattribute(" ",pa);
|
|
v = feedle32(buf, 0);
|
|
printf(" first bit %ld\n",(long)le32_to_cpu(v));
|
|
v = feedle32(buf, 4);
|
|
printf(" bit count %ld\n",(long)le32_to_cpu(v));
|
|
break;
|
|
case OpenNonResidentAttribute : /* 28 */
|
|
printf(" OpenNonResidentAttribute, attr 0x%x\n",attr);
|
|
extra = get_extra_offset(logr)
|
|
- (redo ? get_redo_offset(logr)
|
|
: get_undo_offset(logr));
|
|
if (logr->undo_length) {
|
|
len = le32_to_cpu(logr->client_data_length)
|
|
+ LOG_RECORD_HEAD_SZ
|
|
- get_extra_offset(logr);
|
|
/* this gives a length aligned modulo 8 */
|
|
len = fixnamelen(&buf[extra], len);
|
|
} else
|
|
len = 0;
|
|
pa = getattrentry(attr,len);
|
|
if (pa && redo) {
|
|
/*
|
|
* If this is a redo, collect the attribute data.
|
|
* This should only be done when walking forward.
|
|
*/
|
|
copy_attribute(pa, buf, length);
|
|
pa->namelen = len;
|
|
if (len)
|
|
memcpy(pa->name,&buf[extra],len);
|
|
printf(" MFT attribute 0x%lx (%s)\n",
|
|
(long)le32_to_cpu(pa->type),
|
|
mftattrname(pa->type));
|
|
printf(" lsn 0x%016llx\n",
|
|
(long long)pa->lsn);
|
|
printf(" inode %lld\n",
|
|
(long long)pa->inode);
|
|
}
|
|
if (logr->undo_length)
|
|
showname(" extra : attr name ", &buf[extra], len/2);
|
|
if (!redo && length) {
|
|
printf(" * undo attr not shown\n");
|
|
}
|
|
break;
|
|
case OpenAttributeTableDump : /* 29 */
|
|
printf(" OpenAttributeTableDump, attr 0x%x (%s)\n",
|
|
attr,attrname(attr));
|
|
i = 24;
|
|
if (i < length) {
|
|
int x;
|
|
int more;
|
|
int step;
|
|
int used;
|
|
|
|
step = getle16(buf, 8);
|
|
used = getle16(buf, 12);
|
|
/*
|
|
* Changed from Win10, formerly we got step = 44.
|
|
* The record layout has also changed
|
|
*/
|
|
if ((step != sizeof(ATTR_OLD))
|
|
&& (step != sizeof(ATTR_NEW))) {
|
|
printf(" ** Unexpected step %d\n",step);
|
|
}
|
|
more = 0;
|
|
for (x=0; (x<used) && (i<length); i+=step, x++) {
|
|
pa = getattrentry(i,0);
|
|
if (pa) {
|
|
copy_attribute(pa, &buf[i], step);
|
|
if (x <= SHOWATTRS) {
|
|
printf(" attr 0x%x inode %lld"
|
|
" type %s",
|
|
(int)i,
|
|
(long long)pa->inode,
|
|
mftattrname(pa->type));
|
|
if (pa->namelen)
|
|
showname(" name ",
|
|
(char*)pa->name,
|
|
pa->namelen/2);
|
|
else
|
|
printf("\n");
|
|
} else
|
|
more++;
|
|
}
|
|
}
|
|
if (more)
|
|
printf(" (%d more attrs not shown)\n",more);
|
|
}
|
|
break;
|
|
case AttributeNamesDump : /* 30 */
|
|
printf(" AttributeNamesDump, attr 0x%x (%s)\n",
|
|
attr,attrname(attr));
|
|
i = 8;
|
|
if (i < length) {
|
|
unsigned int l;
|
|
unsigned int key;
|
|
int x;
|
|
int more;
|
|
|
|
more = 0;
|
|
x = 0;
|
|
do {
|
|
l = le16_to_cpu(*(const le16*)&buf[i+2]);
|
|
key = le16_to_cpu(*(const le16*)&buf[i]);
|
|
if (l > 510) {
|
|
printf("** Error : bad attribute name"
|
|
" length %d\n",l);
|
|
key = 0;
|
|
}
|
|
/* Apparently, may have to stop before reaching the end */
|
|
if (key) {
|
|
pa = getattrentry(key,l);
|
|
if (pa) {
|
|
pa->namelen = l;
|
|
memcpy(pa->name,&buf[i+4],l);
|
|
}
|
|
if (x < SHOWATTRS) {
|
|
printf(" attr 0x%x is",key);
|
|
showname(" ",&buf[i+4],l/2);
|
|
} else
|
|
more++;
|
|
i += l + 6;
|
|
x++;
|
|
}
|
|
} while (key && (i < length));
|
|
if (more)
|
|
printf(" (%d more attrs not shown)\n",more);
|
|
}
|
|
break;
|
|
default :
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void detaillogr(CONTEXT *ctx, const LOG_RECORD *logr)
|
|
{
|
|
u64 lcn;
|
|
u64 baselcn;
|
|
unsigned int i;
|
|
unsigned int off;
|
|
unsigned int undo;
|
|
unsigned int redo;
|
|
unsigned int extra;
|
|
unsigned int end;
|
|
unsigned int listsize;
|
|
BOOL onmft;
|
|
|
|
switch (logr->record_type) {
|
|
case LOG_STANDARD :
|
|
onmft = logr->cluster_index
|
|
|| acts_on_mft(le16_to_cpu(logr->redo_operation))
|
|
|| acts_on_mft(le16_to_cpu(logr->undo_operation));
|
|
printf("redo_operation %04x %s\n",
|
|
(int)le16_to_cpu(logr->redo_operation),
|
|
actionname(le16_to_cpu(logr->redo_operation)));
|
|
printf("undo_operation %04x %s\n",
|
|
(int)le16_to_cpu(logr->undo_operation),
|
|
actionname(le16_to_cpu(logr->undo_operation)));
|
|
printf("redo_offset %04x\n",
|
|
(int)le16_to_cpu(logr->redo_offset));
|
|
printf("redo_length %04x\n",
|
|
(int)le16_to_cpu(logr->redo_length));
|
|
printf("undo_offset %04x\n",
|
|
(int)le16_to_cpu(logr->undo_offset));
|
|
printf("undo_length %04x\n",
|
|
(int)le16_to_cpu(logr->undo_length));
|
|
printf("target_attribute %04x\n",
|
|
(int)le16_to_cpu(logr->target_attribute));
|
|
printf("lcns_to_follow %04x\n",
|
|
(int)le16_to_cpu(logr->lcns_to_follow));
|
|
printf("record_offset %04x\n",
|
|
(int)le16_to_cpu(logr->record_offset));
|
|
printf("attribute_offset %04x\n",
|
|
(int)le16_to_cpu(logr->attribute_offset));
|
|
printf("cluster_index %04x\n",
|
|
(int)le16_to_cpu(logr->cluster_index));
|
|
printf("attribute_flags %04x\n",
|
|
(int)le16_to_cpu(logr->attribute_flags));
|
|
if (mftrecbits && onmft)
|
|
printf("target_vcn %016llx (inode %lld)\n",
|
|
(long long)le64_to_cpu(logr->target_vcn),
|
|
(((long long)le64_to_cpu(logr->target_vcn)
|
|
<< clusterbits)
|
|
+ (le16_to_cpu(logr->cluster_index) << 9))
|
|
>> mftrecbits);
|
|
else
|
|
printf("target_vcn %016llx\n",
|
|
(long long)le64_to_cpu(logr->target_vcn));
|
|
/* Compute a base for the current run of mft */
|
|
baselcn = le64_to_cpu(logr->lcn_list[0])
|
|
- le64_to_cpu(logr->target_vcn);
|
|
for (i=0; i<le16_to_cpu(logr->lcns_to_follow)
|
|
&& (i<SHOWLISTS); i++) {
|
|
lcn = le64_to_cpu(logr->lcn_list[i]);
|
|
printf(" (%d offs 0x%x) lcn %016llx",i,
|
|
(int)(8*i + sizeof(LOG_RECORD) - 8),
|
|
(long long)lcn);
|
|
lcn &= 0xffffffffffffULL;
|
|
if (mftrecsz && onmft) {
|
|
if (clustersz > mftrecsz)
|
|
printf(" (MFT records for inodes"
|
|
" %lld-%lld)\n",
|
|
(long long)((lcn - baselcn)
|
|
*clustersz/mftrecsz),
|
|
(long long)((lcn + 1 - baselcn)
|
|
*clustersz/mftrecsz - 1));
|
|
else
|
|
printf(" (MFT record for inode %lld)\n",
|
|
(long long)((lcn - baselcn)
|
|
*clustersz/mftrecsz));
|
|
printf(" assuming record for inode %lld\n",
|
|
(long long)((lcn - baselcn)
|
|
*clustersz/mftrecsz
|
|
+ (le16_to_cpu(logr->cluster_index)
|
|
>> 1)));
|
|
} else
|
|
printf("\n");
|
|
}
|
|
/*
|
|
* redo_offset and undo_offset are considered unsafe
|
|
* (actually they are safe when you know the logic)
|
|
* 2) redo : redo (defined by redo_offset)
|
|
* 3) undo : undo (defined by undo_offset)
|
|
* 4) extra : unknown data (end of undo to data_length)
|
|
*/
|
|
end = le32_to_cpu(logr->client_data_length) + LOG_RECORD_HEAD_SZ;
|
|
if (logr->redo_length && logr->undo_length)
|
|
{
|
|
/* both undo and redo are present */
|
|
if (le16_to_cpu(logr->undo_offset) <=
|
|
le16_to_cpu(logr->redo_offset))
|
|
{
|
|
undo = sizeof(LOG_RECORD) - 8
|
|
+ 8*le16_to_cpu(logr->lcns_to_follow);
|
|
if (logr->redo_offset == logr->undo_offset)
|
|
redo = undo;
|
|
else
|
|
redo = undo + ((le16_to_cpu(logr->undo_length) - 1) | 7) + 1;
|
|
extra = redo + ((le16_to_cpu(logr->redo_length) - 1) | 7) + 1;
|
|
}
|
|
else
|
|
{
|
|
redo = sizeof(LOG_RECORD) - 8
|
|
+ 8*le16_to_cpu(logr->lcns_to_follow);
|
|
undo = redo + ((le16_to_cpu(logr->redo_length) - 1) | 7) + 1;
|
|
extra = undo + ((le16_to_cpu(logr->undo_length) - 1) | 7) + 1;
|
|
}
|
|
}
|
|
else
|
|
if (logr->redo_length)
|
|
{
|
|
/* redo and not undo */
|
|
redo = undo = sizeof(LOG_RECORD) - 8
|
|
+ 8*le16_to_cpu(logr->lcns_to_follow);
|
|
extra = redo + ((le16_to_cpu(logr->redo_length) - 1) | 7) + 1;
|
|
}
|
|
else
|
|
{
|
|
/* optional undo and not redo */
|
|
redo = undo = sizeof(LOG_RECORD) - 8
|
|
+ 8*le16_to_cpu(logr->lcns_to_follow);
|
|
extra = undo + ((le16_to_cpu(logr->undo_length) - 1) | 7) + 1;
|
|
}
|
|
|
|
printf("redo 0x%x (%u) undo 0x%x (%u) extra 0x%x (%d)\n",
|
|
redo,(int)(((le16_to_cpu(logr->redo_length) - 1) | 7) + 1),
|
|
undo,(int)(((le16_to_cpu(logr->undo_length) - 1) | 7) + 1),
|
|
extra,(int)(end > extra ? end - extra : 0));
|
|
|
|
if (logr->redo_length && (get_redo_offset(logr) != redo))
|
|
printf("** Unexpected redo offset 0x%x %u (%u)\n",
|
|
get_redo_offset(logr),(int)redo,
|
|
(int)le16_to_cpu(logr->lcns_to_follow));
|
|
if (logr->undo_length && (get_undo_offset(logr) != undo))
|
|
printf("** Unexpected undo offset 0x%x %u (%u)\n",
|
|
get_undo_offset(logr),(int)undo,
|
|
(int)le16_to_cpu(logr->lcns_to_follow));
|
|
if (get_extra_offset(logr) != extra)
|
|
printf("** Unexpected extra offset 0x%x %u (%u)\n",
|
|
get_extra_offset(logr),(int)extra,
|
|
(int)le16_to_cpu(logr->lcns_to_follow));
|
|
|
|
if (extra <= end)
|
|
{
|
|
/* show redo data */
|
|
if (logr->redo_length)
|
|
{
|
|
if (logr->lcns_to_follow)
|
|
{
|
|
off = le16_to_cpu(logr->record_offset)
|
|
+ le16_to_cpu(logr->attribute_offset);
|
|
printf("redo data (new data) cluster 0x%llx pos 0x%x :\n",
|
|
(long long)le64_to_cpu(logr->lcn_list[off
|
|
>> clusterbits]),
|
|
(int)(off & (clustersz - 1)));
|
|
}
|
|
else
|
|
printf("redo data (new data) at offs 0x%x :\n",redo);
|
|
if ((u32)(redo + le16_to_cpu(logr->redo_length))
|
|
<= end)
|
|
{
|
|
hexdump((const char*)logr
|
|
+ redo,le16_to_cpu(logr->redo_length));
|
|
fixup(ctx, logr, (const char*)logr + redo, TRUE);
|
|
}
|
|
else printf("redo data overflowing from record\n");
|
|
}
|
|
else
|
|
{
|
|
printf("no redo data (new data)\n");
|
|
fixup(ctx, logr, (const char*)logr + redo, TRUE);
|
|
}
|
|
|
|
/* show undo data */
|
|
if (logr->undo_length)
|
|
{
|
|
if (logr->lcns_to_follow)
|
|
{
|
|
off = le16_to_cpu(logr->record_offset)
|
|
+ le16_to_cpu(logr->attribute_offset);
|
|
printf("undo data (old data) cluster 0x%llx pos 0x%x :\n",
|
|
(long long)le64_to_cpu(logr->lcn_list[off
|
|
>> clusterbits]),
|
|
(int)(off & (clustersz - 1)));
|
|
}
|
|
else printf("undo data (old data) at offs 0x%x :\n",undo);
|
|
if ((u32)(undo + le16_to_cpu(logr->undo_length)) <= end)
|
|
{
|
|
if ((undo + le16_to_cpu(logr->undo_length)) < 2*blocksz)
|
|
{
|
|
hexdump((const char*)logr
|
|
+ undo,le16_to_cpu(logr->undo_length));
|
|
fixup(ctx, logr, (const char*)logr + undo, FALSE);
|
|
}
|
|
else printf("undo data overflowing from two blocks\n");
|
|
}
|
|
else printf("undo data overflowing from record\n");
|
|
}
|
|
else
|
|
{
|
|
printf("no undo data (old data)\n");
|
|
fixup(ctx, logr, (const char*)logr + undo, FALSE);
|
|
}
|
|
|
|
/* show extra data, if any */
|
|
if (extra != end)
|
|
{
|
|
if (end > blocksz)
|
|
printf("invalid extra data size\n");
|
|
else
|
|
{
|
|
printf("extra data at offs 0x%x\n",extra);
|
|
hexdump((const char*)logr + extra,
|
|
end - extra);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* sometimes the designated data overflows */
|
|
if (logr->redo_length
|
|
&& ((u32)(redo + le16_to_cpu(logr->redo_length)) > end))
|
|
printf("* redo data overflows from record\n");
|
|
if (logr->undo_length
|
|
&& ((u32)(undo + le16_to_cpu(logr->undo_length)) > end))
|
|
printf("* undo data overflows from record\n");
|
|
}
|
|
break;
|
|
case LOG_CHECKPOINT :
|
|
printf("---> checkpoint record\n");
|
|
printf("redo_operation %04x %s\n",
|
|
(int)le16_to_cpu(logr->redo_operation),
|
|
actionname(le16_to_cpu(logr->redo_operation)));
|
|
printf("undo_operation %04x %s\n",
|
|
(int)le16_to_cpu(logr->undo_operation),
|
|
actionname(le16_to_cpu(logr->undo_operation)));
|
|
printf("redo_offset %04x\n",
|
|
(int)le16_to_cpu(logr->redo_offset));
|
|
printf("redo_length %04x\n",
|
|
(int)le16_to_cpu(logr->redo_length));
|
|
printf("transaction_lsn %016llx\n",
|
|
(long long)sle64_to_cpu(logr->transaction_lsn));
|
|
printf("attributes_lsn %016llx\n",
|
|
(long long)sle64_to_cpu(logr->attributes_lsn));
|
|
printf("names_lsn %016llx\n",
|
|
(long long)sle64_to_cpu(logr->names_lsn));
|
|
printf("dirty_pages_lsn %016llx\n",
|
|
(long long)sle64_to_cpu(logr->dirty_pages_lsn));
|
|
listsize = le32_to_cpu(logr->client_data_length)
|
|
+ LOG_RECORD_HEAD_SZ
|
|
- offsetof(LOG_RECORD, unknown_list);
|
|
if (listsize > 8*SHOWLISTS)
|
|
listsize = 8*SHOWLISTS;
|
|
for (i=0; 8*i<listsize; i++)
|
|
printf("unknown-%u %016llx\n",i,
|
|
(long long)le64_to_cpu(logr->unknown_list[i]));
|
|
break;
|
|
default :
|
|
printf("** Unknown action type\n");
|
|
if (le32_to_cpu(logr->client_data_length) < blocksz) {
|
|
printf("client_data for record type %ld\n",
|
|
(long)le32_to_cpu(logr->record_type));
|
|
hexdump((const char*)&logr->redo_operation,
|
|
le32_to_cpu(logr->client_data_length));
|
|
} else
|
|
printf("** Bad client data\n");
|
|
break;
|
|
}
|
|
}
|
|
|
|
BOOL within_lcn_range(const LOG_RECORD *logr)
|
|
{
|
|
u64 lcn;
|
|
unsigned int i;
|
|
BOOL within;
|
|
|
|
within = FALSE;
|
|
switch (logr->record_type) {
|
|
case LOG_STANDARD :
|
|
for (i=0; i<le16_to_cpu(logr->lcns_to_follow); i++) {
|
|
lcn = MREF(le64_to_cpu(logr->lcn_list[i]));
|
|
if ((lcn >= firstlcn) && (lcn <= lastlcn))
|
|
within = TRUE;
|
|
}
|
|
break;
|
|
default :
|
|
break;
|
|
}
|
|
return (within);
|
|
}
|
|
|
|
static void showlogr(CONTEXT *ctx, int k, const LOG_RECORD *logr)
|
|
{
|
|
s32 diff;
|
|
|
|
if (optv && (!optc || within_lcn_range(logr))) {
|
|
diff = sle64_to_cpu(logr->this_lsn) - synced_lsn;
|
|
printf("this_lsn %016llx (synced%s%ld) %s\n",
|
|
(long long)sle64_to_cpu(logr->this_lsn),
|
|
(diff < 0 ? "" : "+"),(long)diff,
|
|
commitment(diff + synced_lsn));
|
|
printf("client_previous_lsn %016llx\n",
|
|
(long long)sle64_to_cpu(logr->client_previous_lsn));
|
|
printf("client_undo_next_lsn %016llx\n",
|
|
(long long)sle64_to_cpu(logr->client_undo_next_lsn));
|
|
printf("client_data_length %08lx\n",
|
|
(long)le32_to_cpu(logr->client_data_length));
|
|
printf("seq_number %d\n",
|
|
(int)le16_to_cpu(logr->client_id.seq_number));
|
|
printf("client_index %d\n",
|
|
(int)le16_to_cpu(logr->client_id.client_index));
|
|
printf("record_type %08lx\n",
|
|
(long)le32_to_cpu(logr->record_type));
|
|
printf("transaction_id %08lx\n",
|
|
(long)le32_to_cpu(logr->transaction_id));
|
|
printf("log_record_flags %04x\n",
|
|
(int)le16_to_cpu(logr->log_record_flags));
|
|
printf("reserved1 %04x %04x %04x\n",
|
|
(int)le16_to_cpu(logr->reserved_or_alignment[0]),
|
|
(int)le16_to_cpu(logr->reserved_or_alignment[1]),
|
|
(int)le16_to_cpu(logr->reserved_or_alignment[2]));
|
|
detaillogr(ctx, logr);
|
|
}
|
|
if (optt) {
|
|
const char *state;
|
|
|
|
if (logr->record_type == LOG_CHECKPOINT)
|
|
state = "--checkpoint--";
|
|
else
|
|
state = commitment(sle64_to_cpu(logr->this_lsn));
|
|
printf(" at %04x %016llx %s (%ld) %s\n",k,
|
|
(long long)sle64_to_cpu(logr->this_lsn),
|
|
state,
|
|
(long)(sle64_to_cpu(logr->this_lsn) - synced_lsn),
|
|
actionname(le16_to_cpu(logr->redo_operation)));
|
|
if (logr->client_previous_lsn || logr->client_undo_next_lsn) {
|
|
if (logr->client_previous_lsn
|
|
== logr->client_undo_next_lsn) {
|
|
printf(" "
|
|
" previous and undo %016llx\n",
|
|
(long long)sle64_to_cpu(
|
|
logr->client_previous_lsn));
|
|
} else {
|
|
printf(" "
|
|
" previous %016llx",
|
|
(long long)sle64_to_cpu(
|
|
logr->client_previous_lsn));
|
|
|
|
if (logr->client_undo_next_lsn)
|
|
printf(" undo %016llx\n",
|
|
(long long)sle64_to_cpu(
|
|
logr->client_undo_next_lsn));
|
|
else
|
|
printf("\n");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Mark transactions which should be redone
|
|
*/
|
|
|
|
static void mark_transactions(struct ACTION_RECORD *lastaction)
|
|
{
|
|
struct ACTION_RECORD *action;
|
|
const LOG_RECORD *logr;
|
|
le32 id;
|
|
int actives;
|
|
BOOL more;
|
|
BOOL committed;
|
|
|
|
actives = 0;
|
|
do {
|
|
more = FALSE;
|
|
id = const_cpu_to_le32(0);
|
|
for (action=lastaction; action; action=action->prev) {
|
|
logr = &action->record;
|
|
if ((logr->redo_operation
|
|
== const_cpu_to_le16(ForgetTransaction))
|
|
&& !(action->flags & ACTION_TO_REDO)
|
|
&& !id) {
|
|
id = logr->transaction_id;
|
|
action->flags |= ACTION_TO_REDO;
|
|
if (optv)
|
|
printf("Marking transaction 0x%x\n",
|
|
(int)le32_to_cpu(id));
|
|
}
|
|
committed = ((s64)(sle64_to_cpu(logr->this_lsn)
|
|
- committed_lsn)) <= 0;
|
|
if (!logr->transaction_id
|
|
&& committed)
|
|
action->flags |= ACTION_TO_REDO;
|
|
if (id
|
|
&& (logr->transaction_id == id)
|
|
&& committed) {
|
|
action->flags |= ACTION_TO_REDO;
|
|
more = TRUE;
|
|
}
|
|
}
|
|
if (more)
|
|
actives++;
|
|
} while (more);
|
|
/*
|
|
* Show unmarked (aborted) actions
|
|
*/
|
|
if (optv) {
|
|
for (action=lastaction; action; action=action->prev) {
|
|
logr = &action->record;
|
|
if (logr->transaction_id
|
|
&& !(action->flags & ACTION_TO_REDO))
|
|
printf("** Action %d was aborted\n",
|
|
(int)action->num);
|
|
}
|
|
}
|
|
if (optv && (actives > 1))
|
|
printf("%d active transactions in set\n",actives);
|
|
}
|
|
|
|
/*
|
|
* Enqueue an action and play the queued actions on end of set
|
|
*/
|
|
|
|
static TRISTATE enqueue_action(CONTEXT *ctx, const LOG_RECORD *logr,
|
|
int size, int num)
|
|
{
|
|
struct ACTION_RECORD *action;
|
|
TRISTATE state;
|
|
int err;
|
|
|
|
err = 1;
|
|
state = T_ERR;
|
|
/* enqueue record */
|
|
action = (struct ACTION_RECORD*)
|
|
malloc(size + offsetof(struct ACTION_RECORD, record));
|
|
if (action) {
|
|
memcpy(&action->record, logr, size);
|
|
action->num = num;
|
|
action->flags = 0;
|
|
/* enqueue ahead of list, firstaction is the oldest one */
|
|
action->prev = (struct ACTION_RECORD*)NULL;
|
|
action->next = ctx->firstaction;
|
|
if (ctx->firstaction)
|
|
ctx->firstaction->prev = action;
|
|
else
|
|
ctx->lastaction = action;
|
|
ctx->firstaction = action;
|
|
err = 0;
|
|
state = T_OK;
|
|
if ((optp || optu)
|
|
&& (logr->record_type == LOG_CHECKPOINT)) {
|
|
/* if chkp process queue, and increment count */
|
|
playedactions++;
|
|
if (playedactions <= playcount) {
|
|
if (optv)
|
|
printf("* Refreshing attributes\n");
|
|
err = refresh_attributes(ctx->firstaction);
|
|
if (optv)
|
|
printf("* Undoing transaction set %d"
|
|
" (actions %d->%d)\n",
|
|
(int)playedactions,
|
|
(int)ctx->lastaction->num,
|
|
(int)ctx->firstaction->num);
|
|
err = play_undos(ctx->vol, ctx->lastaction);
|
|
if (err)
|
|
printf("* Undoing transaction"
|
|
" set failed\n");
|
|
}
|
|
if (!err && optp && (playedactions == playcount)) {
|
|
if (optv)
|
|
printf("* Redoing transaction set %d"
|
|
" (actions %d->%d)\n",
|
|
(int)playedactions,
|
|
(int)ctx->firstaction->num,
|
|
(int)ctx->lastaction->num);
|
|
mark_transactions(ctx->lastaction);
|
|
err = play_redos(ctx->vol, ctx->firstaction);
|
|
if (err)
|
|
printf("* Redoing transaction"
|
|
" set failed\n");
|
|
}
|
|
if (err)
|
|
state = T_ERR;
|
|
else
|
|
if (playedactions == playcount)
|
|
state = T_DONE;
|
|
/* free queue */
|
|
while (ctx->firstaction) {
|
|
action = ctx->firstaction->next;
|
|
free(ctx->firstaction);
|
|
ctx->firstaction = action;
|
|
}
|
|
ctx->lastaction = (struct ACTION_RECORD*)NULL;
|
|
}
|
|
if (opts
|
|
&& ((s64)(sle64_to_cpu(logr->this_lsn) - synced_lsn) <= 0)) {
|
|
if (optv)
|
|
printf("* Refreshing attributes\n");
|
|
// should refresh backward ?
|
|
err = refresh_attributes(ctx->firstaction);
|
|
mark_transactions(ctx->lastaction);
|
|
if (!err) {
|
|
if (optv)
|
|
printf("* Syncing actions %d->%d\n",
|
|
(int)ctx->firstaction->num,
|
|
(int)ctx->lastaction->num);
|
|
err = play_redos(ctx->vol, ctx->firstaction);
|
|
}
|
|
if (err) {
|
|
printf("* Syncing actions failed\n");
|
|
state = T_ERR;
|
|
} else
|
|
state = T_DONE;
|
|
}
|
|
}
|
|
return (state);
|
|
}
|
|
|
|
|
|
static void showheadrcrd(u32 blk, const RECORD_PAGE_HEADER *rph)
|
|
{
|
|
s32 diff;
|
|
|
|
if (optv) {
|
|
printf("magic %08lx\n",
|
|
(long)le32_to_cpu(rph->magic));
|
|
printf("usa_ofs %04x\n",
|
|
(int)le16_to_cpu(rph->usa_ofs));
|
|
printf("usa_count %04x\n",
|
|
(int)le16_to_cpu(rph->usa_count));
|
|
if (blk < 4)
|
|
printf("file_offset %016llx\n",
|
|
(long long)sle64_to_cpu(rph->copy.file_offset));
|
|
else {
|
|
diff = sle64_to_cpu(rph->copy.last_lsn) - synced_lsn;
|
|
printf("last_lsn %016llx"
|
|
" (synced%s%ld)\n",
|
|
(long long)sle64_to_cpu(rph->copy.last_lsn),
|
|
(diff < 0 ? "" : "+"),(long)diff);
|
|
}
|
|
printf("flags %08lx\n",
|
|
(long)le32_to_cpu(rph->flags));
|
|
printf("page_count %d\n",
|
|
(int)le16_to_cpu(rph->page_count));
|
|
printf("page_position %d\n",
|
|
(int)le16_to_cpu(rph->page_position));
|
|
printf("next_record_offset %04x\n",
|
|
(int)le16_to_cpu(rph->next_record_offset));
|
|
printf("reserved4 %04x %04x %04x\n",
|
|
(int)le16_to_cpu(rph->reserved[0]),
|
|
(int)le16_to_cpu(rph->reserved[1]),
|
|
(int)le16_to_cpu(rph->reserved[2]));
|
|
diff = sle64_to_cpu(rph->last_end_lsn) - synced_lsn;
|
|
printf("last_end_lsn %016llx (synced%s%ld)\n",
|
|
(long long)sle64_to_cpu(rph->last_end_lsn),
|
|
(diff < 0 ? "" : "+"),(long)diff);
|
|
printf("usn %04x\n",
|
|
(int)getle16(rph,le16_to_cpu(rph->usa_ofs)));
|
|
printf("\n");
|
|
} else {
|
|
if (optt) {
|
|
const char *state;
|
|
|
|
state = commitment(sle64_to_cpu(rph->copy.last_lsn));
|
|
diff = sle64_to_cpu(rph->copy.last_lsn) - synced_lsn;
|
|
printf(" last %016llx (synced%s%ld) %s\n",
|
|
(long long)sle64_to_cpu(rph->copy.last_lsn),
|
|
(diff < 0 ? "" : "+"),(long)diff, state);
|
|
state = commitment(sle64_to_cpu(rph->last_end_lsn));
|
|
diff = sle64_to_cpu(rph->last_end_lsn) - synced_lsn;
|
|
printf(" last_end %016llx (synced%s%ld) %s\n",
|
|
(long long)sle64_to_cpu(rph->last_end_lsn),
|
|
(diff < 0 ? "" : "+"),(long)diff, state);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Analyze and display an action overlapping log blocks
|
|
*
|
|
* Returns the position of first action in next block. If this is
|
|
* greater than a block size (for actions overlapping more than
|
|
* two blocks), then some blocks have to be skipped.
|
|
*
|
|
* Returns 0 in case of error
|
|
*/
|
|
|
|
static u16 overlapshow(CONTEXT *ctx, u16 k, u32 blk, const struct BUFFER *buf,
|
|
const struct BUFFER *nextbuf)
|
|
{
|
|
const LOG_RECORD *logr;
|
|
const char *data;
|
|
const char *nextdata;
|
|
char *fullrec;
|
|
u32 size;
|
|
u32 nextspace;
|
|
u32 space;
|
|
BOOL likely;
|
|
u16 blkheadsz;
|
|
|
|
data = buf->block.data;
|
|
logr = (const LOG_RECORD*)&data[k];
|
|
size = le32_to_cpu(logr->client_data_length) + LOG_RECORD_HEAD_SZ;
|
|
blkheadsz = buf->headsz;
|
|
if (nextbuf && (blk >= BASEBLKS)) {
|
|
nextdata = nextbuf->block.data;
|
|
space = blocksz - k;
|
|
nextspace = blocksz - blkheadsz;
|
|
if ((space >= LOG_RECORD_HEAD_SZ)
|
|
&& (size > space)) {
|
|
fullrec = (char*)malloc(size);
|
|
if (size <= (space + nextspace)) {
|
|
/* Overlap on two blocks */
|
|
memcpy(fullrec,&data[k],space);
|
|
memcpy(&fullrec[space],
|
|
nextdata + blkheadsz,
|
|
size - space);
|
|
likely = likelyop((LOG_RECORD*)fullrec);
|
|
actionnum++;
|
|
if (optv) {
|
|
printf("\nOverlapping record %u at 0x%x"
|
|
" size %d (next at 0x%x)\n",
|
|
(int)actionnum,(int)k,
|
|
(int)size, (int)(k + size));
|
|
printf("Overlap marked for block %ld"
|
|
" space %d likely %d\n",
|
|
(long)blk,(int)space,likely);
|
|
}
|
|
if (likely)
|
|
showlogr(ctx, k,
|
|
(LOG_RECORD*)fullrec);
|
|
else
|
|
printf("** Skipping unlikely"
|
|
" overlapping record\n");
|
|
k += size - blocksz + blkheadsz;
|
|
} else {
|
|
const struct BUFFER *midbuf;
|
|
int skip;
|
|
u32 next;
|
|
u32 pos;
|
|
int i;
|
|
|
|
/*
|
|
* The maximum size of of log record is 131104
|
|
* (when both offset and length are 65528 for
|
|
* redo or undo).
|
|
* So up to 33 log blocks (useful size 4032)
|
|
* could be needed. However never both undo and
|
|
* redo have been found big, and 17 should be
|
|
* the real maximum.
|
|
*/
|
|
if (optv)
|
|
printf("More than two blocks required"
|
|
" (size %lu)\n",(long)size);
|
|
memcpy(fullrec,&data[k],space);
|
|
|
|
skip = (size - space - 1)/nextspace;
|
|
pos = space;
|
|
likely = TRUE;
|
|
for (i=1; (i<=skip) && likely; i++) {
|
|
midbuf = read_buffer(ctx, blk + i);
|
|
if (midbuf) {
|
|
memcpy(&fullrec[pos],
|
|
&midbuf->block
|
|
.data[blkheadsz],
|
|
nextspace);
|
|
pos += nextspace;
|
|
} else
|
|
likely = FALSE;
|
|
}
|
|
if (pos >= size) {
|
|
printf("** Error : bad big overlap"
|
|
" pos %d size %d\n",
|
|
(int)pos,(int)size);
|
|
likely = FALSE;
|
|
}
|
|
midbuf = read_buffer(ctx, blk + skip + 1);
|
|
if (midbuf)
|
|
memcpy(&fullrec[pos],
|
|
&midbuf->block.data[blkheadsz],
|
|
size - pos);
|
|
else
|
|
likely = FALSE;
|
|
if (!likelyop((LOG_RECORD*)fullrec))
|
|
likely = FALSE;
|
|
actionnum++;
|
|
if (optv) {
|
|
printf("\nBig overlapping record %u at "
|
|
"0x%x size %u (next at 0x%x)\n",
|
|
(int)actionnum,(int)k,(int)size,
|
|
(int)(k + size));
|
|
printf("Overlap marked for block %ld"
|
|
" space %d likely %d\n",
|
|
(long)blk,(int)space,likely);
|
|
}
|
|
if (likely)
|
|
showlogr(ctx, k,
|
|
(LOG_RECORD*)fullrec);
|
|
else
|
|
printf("** Skipping unlikely"
|
|
" overlapping record\n");
|
|
/* next and skip are only for displaying */
|
|
next = (size - space) % nextspace
|
|
+ blkheadsz;
|
|
if ((blocksz - next) < LOG_RECORD_HEAD_SZ)
|
|
next = blkheadsz;
|
|
if (next == blkheadsz)
|
|
skip++;
|
|
if (optv)
|
|
printf("Next record expected in"
|
|
" block %lu index 0x%x\n",
|
|
(long)(blk + skip + 1),next);
|
|
/* Quick check, with no consequences */
|
|
if (firstrecord(skip,buf,buf) != next)
|
|
printf("** Error next != firstrecord"
|
|
" after block %d\n",blk);
|
|
k += size - blocksz + blkheadsz;
|
|
}
|
|
if (!likely)
|
|
k = 0;
|
|
else
|
|
if (!k)
|
|
printf("* Bad return from overlap()\n");
|
|
free(fullrec);
|
|
} else {
|
|
/* No conditions for overlap, usually a new session */
|
|
printf("* No block found overlapping on block %d\n",
|
|
(int)blk);
|
|
k = 0;
|
|
}
|
|
} else {
|
|
/* blocks 2, 3 and the last one have no next block */
|
|
k = 0;
|
|
}
|
|
return (k);
|
|
}
|
|
|
|
/*
|
|
* Analyze and forward display the actions in a log block
|
|
*
|
|
* Returns the position of first action in next block. If this is
|
|
* greater than a block size, then some blocks have to be skipped.
|
|
*
|
|
* Returns 0 in case of error
|
|
*/
|
|
|
|
static u16 forward_rcrd(CONTEXT *ctx, u32 blk, u16 pos,
|
|
const struct BUFFER *buf, const struct BUFFER *nextbuf)
|
|
{
|
|
const RECORD_PAGE_HEADER *rph;
|
|
const LOG_RECORD *logr;
|
|
const char *data;
|
|
u16 k;
|
|
u16 endoff;
|
|
BOOL stop;
|
|
|
|
rph = &buf->block.record;
|
|
if (rph && (rph->magic == magic_RCRD)) {
|
|
data = buf->block.data;
|
|
showheadrcrd(blk, rph);
|
|
k = buf->headsz;
|
|
if ((k < pos) && (pos < blocksz)) {
|
|
k = ((pos - 1) | 7) + 1;
|
|
}
|
|
// TODO check bad start > blocksz - 48
|
|
logr = (const LOG_RECORD*)&data[k];
|
|
stop = FALSE;
|
|
if (!likelyop(logr)) {
|
|
if (optv)
|
|
printf("* Bad start 0x%x for block %d\n",
|
|
(int)pos,(int)blk);
|
|
k = searchlikely(buf);
|
|
if ((k + sizeof(LOG_RECORD)) > blocksz) {
|
|
printf("No likely full record in block %lu\n",
|
|
(unsigned long)blk);
|
|
/* there can be a partial one */
|
|
k = le16_to_cpu(rph->next_record_offset);
|
|
if ((k < (u16)sizeof(RECORD_PAGE_HEADER))
|
|
|| ((blocksz - k) < LOG_RECORD_HEAD_SZ))
|
|
stop = TRUE;
|
|
} else {
|
|
if (optv)
|
|
printf("First record computed at"
|
|
" offset 0x%x\n", (int)k);
|
|
}
|
|
}
|
|
while (!stop) {
|
|
s32 size;
|
|
|
|
logr = (const LOG_RECORD*)&data[k];
|
|
size = le32_to_cpu(logr->client_data_length)
|
|
+ LOG_RECORD_HEAD_SZ;
|
|
if ((size < MINRECSIZE)
|
|
|| (size > MAXRECSIZE)
|
|
|| (size & 7)) {
|
|
printf("** Bad record size %ld in block %ld"
|
|
" offset 0x%x\n",
|
|
(long)size, (long)buf->num, (int)k);
|
|
showlogr(ctx, k, logr);
|
|
k = 0;
|
|
stop = TRUE;
|
|
} else {
|
|
endoff = le16_to_cpu(rph->next_record_offset);
|
|
if (((u32)(k + size) <= blocksz)
|
|
&& ((u32)(k + size) <= endoff)) {
|
|
actionnum++;
|
|
if (optv) {
|
|
printf("\n* log action %u at"
|
|
" 0x%x size %d (next"
|
|
" at 0x%x)\n",
|
|
actionnum,k,size,
|
|
k + size);
|
|
}
|
|
showlogr(ctx, k, logr);
|
|
if (!logr->client_data_length) {
|
|
printf("** Bad"
|
|
" client_data_length\n");
|
|
stop = TRUE;
|
|
}
|
|
k += size;
|
|
if ((blocksz - k)
|
|
< LOG_RECORD_HEAD_SZ) {
|
|
k = nextbuf->headsz;
|
|
stop = TRUE;
|
|
}
|
|
} else {
|
|
k = overlapshow(ctx, k, blk,
|
|
buf, nextbuf);
|
|
stop = TRUE;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
printf("** Not a RCRD record, MAGIC 0x%08lx\n",
|
|
(long)le32_to_cpu(rph->magic));
|
|
k = 0;
|
|
}
|
|
return (k);
|
|
}
|
|
|
|
/*
|
|
* Display a restart page
|
|
*/
|
|
|
|
static void showrest(const RESTART_PAGE_HEADER *rest)
|
|
{
|
|
const RESTART_AREA *resa;
|
|
const LOG_CLIENT_RECORD *rcli;
|
|
const char *data;
|
|
|
|
data = (const char*)rest;
|
|
if ((rest->magic == magic_RSTR)
|
|
|| (rest->magic == magic_CHKD)) {
|
|
if (optv) {
|
|
printf("magic %08lx\n",
|
|
(long)le32_to_cpu(rest->magic));
|
|
printf("usa_ofs %04x\n",
|
|
(int)le16_to_cpu(rest->usa_ofs));
|
|
printf("usa_count %04x\n",
|
|
(int)le16_to_cpu(rest->usa_count));
|
|
printf("chkdsk_lsn %016llx\n",
|
|
(long long)sle64_to_cpu(rest->chkdsk_lsn));
|
|
printf("system_page_size %08lx\n",
|
|
(long)le32_to_cpu(rest->system_page_size));
|
|
printf("log_page_size %08lx\n",
|
|
(long)le32_to_cpu(rest->log_page_size));
|
|
printf("restart_area_offset %04x\n",
|
|
(int)le16_to_cpu(rest->restart_area_offset));
|
|
printf("minor_vers %d\n",
|
|
(int)le16_to_cpu(rest->minor_ver));
|
|
printf("major_vers %d\n",
|
|
(int)le16_to_cpu(rest->major_ver));
|
|
printf("usn %04x\n",
|
|
(int)le16_to_cpu(rest->usn));
|
|
printf("\n");
|
|
} else {
|
|
if (optt)
|
|
printf(" chkdsk %016llx\n",
|
|
(long long)sle64_to_cpu(rest->chkdsk_lsn));
|
|
}
|
|
resa = (const RESTART_AREA*)
|
|
&data[le16_to_cpu(rest->restart_area_offset)];
|
|
if (optv) {
|
|
printf("current_lsn %016llx\n",
|
|
(long long)sle64_to_cpu(resa->current_lsn));
|
|
printf("log_clients %04x\n",
|
|
(int)le16_to_cpu(resa->log_clients));
|
|
printf("client_free_list %04x\n",
|
|
(int)le16_to_cpu(resa->client_free_list));
|
|
printf("client_in_use_list %04x\n",
|
|
(int)le16_to_cpu(resa->client_in_use_list));
|
|
printf("flags %04x\n",
|
|
(int)le16_to_cpu(resa->flags));
|
|
printf("seq_number_bits %08lx\n",
|
|
(long)le32_to_cpu(resa->seq_number_bits));
|
|
printf("restart_area_length %04x\n",
|
|
(int)le16_to_cpu(resa->restart_area_length));
|
|
printf("client_array_offset %04x\n",
|
|
(int)le16_to_cpu(resa->client_array_offset));
|
|
printf("file_size %016llx\n",
|
|
(long long)le64_to_cpu(resa->file_size));
|
|
printf("last_lsn_data_len %08lx\n",
|
|
(long)le32_to_cpu(resa->last_lsn_data_length));
|
|
printf("record_length %04x\n",
|
|
(int)le16_to_cpu(resa->log_record_header_length));
|
|
printf("log_page_data_offs %04x\n",
|
|
(int)le16_to_cpu(resa->log_page_data_offset));
|
|
printf("restart_log_open_count %08lx\n",
|
|
(long)le32_to_cpu(resa->restart_log_open_count));
|
|
printf("\n");
|
|
} else {
|
|
if (optt)
|
|
printf(" latest %016llx\n",
|
|
(long long)sle64_to_cpu(resa->current_lsn));
|
|
}
|
|
|
|
rcli = (const LOG_CLIENT_RECORD*)
|
|
&data[le16_to_cpu(rest->restart_area_offset)
|
|
+ le16_to_cpu(resa->client_array_offset)];
|
|
if (optv) {
|
|
printf("oldest_lsn %016llx\n",
|
|
(long long)sle64_to_cpu(rcli->oldest_lsn));
|
|
printf("client_restart_lsn %016llx\n",
|
|
(long long)sle64_to_cpu(rcli->client_restart_lsn));
|
|
printf("prev_client %04x\n",
|
|
(int)le16_to_cpu(rcli->prev_client));
|
|
printf("next_client %04x\n",
|
|
(int)le16_to_cpu(rcli->next_client));
|
|
printf("seq_number %04x\n",
|
|
(int)le16_to_cpu(rcli->seq_number));
|
|
printf("client_name_length %08x\n",
|
|
(int)le32_to_cpu(rcli->client_name_length));
|
|
showname("client_name ",
|
|
(const char*)rcli->client_name,
|
|
le32_to_cpu(rcli->client_name_length) >> 1);
|
|
} else {
|
|
if (optt) {
|
|
printf(" synced %016llx\n",
|
|
(long long)sle64_to_cpu(
|
|
rcli->oldest_lsn));
|
|
printf(" committed %016llx\n",
|
|
(long long)sle64_to_cpu(
|
|
rcli->client_restart_lsn));
|
|
}
|
|
}
|
|
} else
|
|
printf("Not a RSTR or CHKD record, MAGIC 0x%08lx\n",
|
|
(long)le32_to_cpu(rest->magic));
|
|
}
|
|
|
|
static BOOL dorest(CONTEXT *ctx, unsigned long blk,
|
|
const RESTART_PAGE_HEADER *rph, BOOL initial)
|
|
{
|
|
const RESTART_AREA *resa;
|
|
const LOG_CLIENT_RECORD *rcli;
|
|
const char *data;
|
|
s64 diff;
|
|
int offs;
|
|
int size;
|
|
BOOL change;
|
|
BOOL dirty;
|
|
|
|
data = (const char*)rph;
|
|
offs = le16_to_cpu(rph->restart_area_offset);
|
|
resa = (const RESTART_AREA*)&data[offs];
|
|
rcli = (const LOG_CLIENT_RECORD*)&data[offs
|
|
+ le16_to_cpu(resa->client_array_offset)];
|
|
if (initial) {
|
|
/* Information from block initially found best */
|
|
latest_lsn = sle64_to_cpu(resa->current_lsn);
|
|
committed_lsn = sle64_to_cpu(rcli->client_restart_lsn);
|
|
synced_lsn = sle64_to_cpu(rcli->oldest_lsn);
|
|
memcpy(&log_header, rph,
|
|
sizeof(RESTART_PAGE_HEADER));
|
|
offs = le16_to_cpu(log_header.restart_area_offset);
|
|
memcpy(&restart, &data[offs],
|
|
sizeof(RESTART_AREA));
|
|
offs += le16_to_cpu(restart.client_array_offset);
|
|
memcpy(&client, &data[offs],
|
|
sizeof(LOG_CLIENT_RECORD));
|
|
dirty = !(resa->flags & RESTART_VOLUME_IS_CLEAN);
|
|
if (optv || optt)
|
|
printf("* Using initial restart page,"
|
|
" syncing from 0x%llx, %s\n",
|
|
(long long)synced_lsn,
|
|
(dirty ? "dirty" : "clean"));
|
|
/* Get the block page size */
|
|
blocksz = le32_to_cpu(rph->log_page_size);
|
|
if (optv)
|
|
printf("* Block size %ld bytes\n", (long)blocksz);
|
|
blockbits = 1;
|
|
while ((u32)(1 << blockbits) < blocksz)
|
|
blockbits++;
|
|
} else {
|
|
size = offs + le16_to_cpu(resa->restart_area_length);
|
|
if (optv) {
|
|
if (optv >= 2)
|
|
hexdump(data,size);
|
|
printf("* RSTR in block %ld 0x%lx (addr 0x%llx)\n",
|
|
(long)blk,(long)blk,
|
|
(long long)loclogblk(ctx, blk));
|
|
} else {
|
|
if (optt)
|
|
printf("restart %ld\n",(long)blk);
|
|
}
|
|
showrest(rph);
|
|
/* Information from an older restart block if requested */
|
|
dirty = !(restart.flags & RESTART_VOLUME_IS_CLEAN);
|
|
diff = sle64_to_cpu(rcli->client_restart_lsn) - committed_lsn;
|
|
if (ctx->vol) {
|
|
change = (opts > 1) && (diff < 0);
|
|
} else {
|
|
change = (opts > 1 ? diff < 0 : diff > 0);
|
|
}
|
|
if (change) {
|
|
committed_lsn = sle64_to_cpu(rcli->client_restart_lsn);
|
|
synced_lsn = sle64_to_cpu(rcli->oldest_lsn);
|
|
latest_lsn = sle64_to_cpu(resa->current_lsn);
|
|
memcpy(&log_header, rph,
|
|
sizeof(RESTART_PAGE_HEADER));
|
|
offs = le16_to_cpu(log_header.restart_area_offset);
|
|
memcpy(&restart, &data[offs],
|
|
sizeof(RESTART_AREA));
|
|
offs += le16_to_cpu(restart.client_array_offset);
|
|
memcpy(&client, &data[offs],
|
|
sizeof(LOG_CLIENT_RECORD));
|
|
dirty = !(resa->flags & RESTART_VOLUME_IS_CLEAN);
|
|
if (optv || optt)
|
|
printf("* Using %s restart page,"
|
|
" syncing from 0x%llx, %s\n",
|
|
(diff < 0 ? "older" : "newer"),
|
|
(long long)synced_lsn,
|
|
(dirty ? "dirty" : "clean"));
|
|
}
|
|
}
|
|
restart_lsn = synced_lsn;
|
|
return (dirty);
|
|
}
|
|
|
|
/*
|
|
* Read and process the first restart block
|
|
*
|
|
* In full mode, both restart page are silently analyzed by the
|
|
* library and the most recent readable one is used to define the
|
|
* sync parameters.
|
|
*
|
|
* Returns the first restart buffer
|
|
* or NULL if the restart block is not valid
|
|
*/
|
|
|
|
|
|
static const struct BUFFER *read_restart(CONTEXT *ctx)
|
|
{
|
|
const struct BUFFER *buf;
|
|
BOOL bad;
|
|
|
|
bad = FALSE;
|
|
if (ctx->vol) {
|
|
RESTART_PAGE_HEADER *rph;
|
|
|
|
rph = (RESTART_PAGE_HEADER*)NULL;
|
|
/* Full mode : use the restart page selected by the library */
|
|
if (ntfs_check_logfile(log_na, &rph)) {
|
|
/* rph is left unchanged for a wiped out log file */
|
|
if (rph) {
|
|
dorest(ctx, 0, rph, TRUE);
|
|
free(rph);
|
|
buf = read_buffer(ctx,0);
|
|
} else {
|
|
buf = (const struct BUFFER*)NULL;
|
|
printf("** The log file has been wiped out\n");
|
|
}
|
|
} else {
|
|
buf = (const struct BUFFER*)NULL;
|
|
printf("** Could not get any restart page\n");
|
|
}
|
|
} else {
|
|
/* Reduced mode : rely on first restart page */
|
|
blockbits = BLOCKBITS; /* Until the correct value is read */
|
|
blocksz = 1L << blockbits;
|
|
buf = read_buffer(ctx,0);
|
|
}
|
|
if (buf) {
|
|
NTFS_RECORD_TYPES magic;
|
|
|
|
magic = buf->block.restart.magic;
|
|
switch (magic) {
|
|
case magic_RSTR :
|
|
break;
|
|
case magic_CHKD :
|
|
printf("** The log file has been obsoleted by chkdsk\n");
|
|
bad = TRUE;
|
|
break;
|
|
case magic_empty :
|
|
printf("** The log file has been wiped out\n");
|
|
bad = TRUE;
|
|
break;
|
|
default :
|
|
printf("** Invalid restart block\n");
|
|
bad = TRUE;
|
|
break;
|
|
}
|
|
if (!bad && !ctx->vol)
|
|
dorest(ctx, 0, &buf->block.restart, TRUE);
|
|
if ((buf->block.restart.major_ver != const_cpu_to_le16(1))
|
|
|| (buf->block.restart.minor_ver != const_cpu_to_le16(1))) {
|
|
printf("** Unsupported $LogFile version %d.%d\n",
|
|
le16_to_cpu(buf->block.restart.major_ver),
|
|
le16_to_cpu(buf->block.restart.minor_ver));
|
|
bad = TRUE;
|
|
}
|
|
if (bad) {
|
|
buf = (const struct BUFFER*)NULL;
|
|
}
|
|
}
|
|
return (buf);
|
|
}
|
|
|
|
/*
|
|
* Mark the logfile as synced
|
|
*/
|
|
|
|
static int reset_logfile(CONTEXT *ctx __attribute__((unused)))
|
|
{
|
|
char *buffer;
|
|
int off;
|
|
int err;
|
|
|
|
err = 1;
|
|
buffer = (char*)malloc(blocksz);
|
|
if (buffer) {
|
|
memset(buffer, 0, blocksz);
|
|
restart.client_in_use_list = LOGFILE_NO_CLIENT;
|
|
restart.flags |= RESTART_VOLUME_IS_CLEAN;
|
|
client.oldest_lsn = cpu_to_sle64(restart_lsn);
|
|
memcpy(buffer, &log_header,
|
|
sizeof(RESTART_PAGE_HEADER));
|
|
off = le16_to_cpu(log_header.restart_area_offset);
|
|
memcpy(&buffer[off], &restart,
|
|
sizeof(RESTART_AREA));
|
|
off += le16_to_cpu(restart.client_array_offset);
|
|
memcpy(&buffer[off], &client,
|
|
sizeof(LOG_CLIENT_RECORD));
|
|
if (!ntfs_mst_pre_write_fixup((NTFS_RECORD*)buffer, blocksz)
|
|
&& (ntfs_attr_pwrite(log_na, 0,
|
|
blocksz, buffer) == blocksz)
|
|
&& (ntfs_attr_pwrite(log_na, (u64)1 << blockbits,
|
|
blocksz, buffer) == blocksz))
|
|
err = 0;
|
|
free(buffer);
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
/*
|
|
* Determine the most recent valid record block
|
|
*/
|
|
|
|
static const struct BUFFER *best_start(const struct BUFFER *buf,
|
|
const struct BUFFER *altbuf)
|
|
{
|
|
const struct BUFFER *best;
|
|
const RECORD_PAGE_HEADER *head;
|
|
const RECORD_PAGE_HEADER *althead;
|
|
s64 diff;
|
|
|
|
if (!buf || !altbuf)
|
|
best = (buf ? buf : altbuf);
|
|
else {
|
|
head = &buf->block.record;
|
|
althead = &altbuf->block.record;
|
|
/* determine most recent, caring for wraparounds */
|
|
diff = sle64_to_cpu(althead->last_end_lsn)
|
|
- sle64_to_cpu(head->last_end_lsn);
|
|
if (diff > 0)
|
|
best = altbuf;
|
|
else
|
|
best = buf;
|
|
}
|
|
if (best && (best->block.record.magic != magic_RCRD))
|
|
best = (const struct BUFFER*)NULL;
|
|
return (best);
|
|
}
|
|
|
|
/*
|
|
* Interpret the boot data
|
|
*
|
|
* Probably not needed any more, use ctx->vol
|
|
*/
|
|
|
|
static BOOL getboot(const char *buf)
|
|
{
|
|
u64 sectors;
|
|
u64 clusters;
|
|
u16 sectpercluster;
|
|
BOOL ok;
|
|
|
|
ok = TRUE;
|
|
/* Beware : bad alignment */
|
|
bytespersect = (buf[11] & 255) + ((buf[12] & 255) << 8);
|
|
sectpercluster = buf[13] & 255;
|
|
clustersz = bytespersect * (u32)sectpercluster;
|
|
clusterbits = 1;
|
|
while ((u32)(1 << clusterbits) < clustersz)
|
|
clusterbits++;
|
|
sectors = getle64(buf, 0x28);
|
|
clusters = sectors/sectpercluster;
|
|
mftlcn = getle64(buf, 0x30);
|
|
if (buf[0x40] & 0x80)
|
|
mftrecsz = 1 << (16 - (buf[0x40] & 15));
|
|
else
|
|
mftrecsz = (buf[0x40] & 127)*clustersz;
|
|
mftrecbits = 1;
|
|
while ((u32)(1 << mftrecbits) < mftrecsz)
|
|
mftrecbits++;
|
|
if (optv) {
|
|
if ((long long)sectors*bytespersect > 10000000000LL)
|
|
printf("Capacity %lld bytes (%lld GB)\n",
|
|
(long long)sectors*bytespersect,
|
|
(long long)sectors*bytespersect/1000000000);
|
|
else
|
|
printf("Capacity %lld bytes (%lld MB)\n",
|
|
(long long)sectors*bytespersect,
|
|
(long long)sectors*bytespersect/1000000);
|
|
printf("sectors %lld (0x%llx), sector size %d\n",
|
|
(long long)sectors,(long long)sectors,
|
|
(int)bytespersect);
|
|
printf("clusters %lld (0x%llx), cluster size %d (%d bits)\n",
|
|
(long long)clusters,(long long)clusters,
|
|
(int)clustersz,(int)clusterbits);
|
|
printf("MFT at cluster %lld (0x%llx), entry size %lu\n",
|
|
(long long)mftlcn,(long long)mftlcn,
|
|
(unsigned long)mftrecsz);
|
|
if (mftrecsz > clustersz)
|
|
printf("%ld clusters per MFT entry\n",
|
|
(long)(mftrecsz/clustersz));
|
|
else
|
|
printf("%ld MFT entries per cluster\n",
|
|
(long)(clustersz/mftrecsz));
|
|
}
|
|
return (ok);
|
|
}
|
|
|
|
static int locatelogfile(CONTEXT *ctx)
|
|
{
|
|
int err;
|
|
|
|
err = 1;
|
|
log_ni = ntfs_inode_open(ctx->vol, FILE_LogFile);
|
|
if (log_ni) {
|
|
log_na = ntfs_attr_open(log_ni, AT_DATA, AT_UNNAMED, 0);
|
|
if (log_na) {
|
|
logfilesz = log_na->data_size;
|
|
err = 0;
|
|
}
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
/*
|
|
* Analyze a $LogFile copy
|
|
*
|
|
* A $LogFile cannot be played. It can be however be analyzed in
|
|
* stand-alone mode.
|
|
* The location of the $MFT will have to be determined elsewhere.
|
|
*/
|
|
|
|
static BOOL getlogfiledata(CONTEXT *ctx, const char *boot)
|
|
{
|
|
const RESTART_PAGE_HEADER *rph;
|
|
const RESTART_AREA *rest;
|
|
BOOL ok;
|
|
u32 off;
|
|
s64 size;
|
|
u32 system_page_size;
|
|
u32 log_page_size;
|
|
|
|
ok = FALSE;
|
|
fseek(ctx->file,0L,2);
|
|
size = ftell(ctx->file);
|
|
rph = (const RESTART_PAGE_HEADER*)boot;
|
|
off = le16_to_cpu(rph->restart_area_offset);
|
|
/*
|
|
* If the system or log page sizes are smaller than the ntfs block size
|
|
* or either is not a power of 2 we cannot handle this log file.
|
|
*/
|
|
system_page_size = le32_to_cpu(rph->system_page_size);
|
|
log_page_size = le32_to_cpu(rph->log_page_size);
|
|
if (system_page_size < NTFS_BLOCK_SIZE ||
|
|
log_page_size < NTFS_BLOCK_SIZE ||
|
|
system_page_size & (system_page_size - 1) ||
|
|
log_page_size & (log_page_size - 1)) {
|
|
printf("** Unsupported page size.\n");
|
|
goto out;
|
|
}
|
|
if (off & 7 || off > system_page_size) {
|
|
printf("** Inconsistent restart area offset.\n");
|
|
goto out;
|
|
}
|
|
rest = (const RESTART_AREA*)&boot[off];
|
|
|
|
/* estimate cluster size from log file size (unreliable) */
|
|
switch (le32_to_cpu(rest->seq_number_bits)) {
|
|
case 45 : clustersz = 512; break;
|
|
case 43 : clustersz = 1024; break; /* can be 1024 or 2048 */
|
|
case 40 :
|
|
default : clustersz = 4096; break;
|
|
}
|
|
|
|
clusterbits = 1;
|
|
while ((u32)(1 << clusterbits) < clustersz)
|
|
clusterbits++;
|
|
printf("* Assuming cluster size %ld\n",(long)clustersz);
|
|
logfilelcn = 0;
|
|
logfilesz = size;
|
|
if (optv)
|
|
printf("Log file size %lld bytes, cluster size %ld\n",
|
|
(long long)size, (long)clustersz);
|
|
/* Have to wait an InitializeFileRecordSegment to get these values */
|
|
mftrecsz = 0;
|
|
mftrecbits = 0;
|
|
ok = TRUE;
|
|
out:
|
|
return (ok);
|
|
}
|
|
|
|
/*
|
|
* Get basic volume data
|
|
*
|
|
* Locate the MFT and Logfile
|
|
* Not supposed to read the first log block...
|
|
*/
|
|
|
|
static BOOL getvolumedata(CONTEXT *ctx, char *boot)
|
|
{
|
|
const RESTART_AREA *rest;
|
|
BOOL ok;
|
|
|
|
ok = FALSE;
|
|
rest = (const RESTART_AREA*)NULL;
|
|
if (ctx->vol) {
|
|
getboot(boot);
|
|
mftlcn = ctx->vol->mft_lcn;
|
|
mftcnt = ctx->vol->mft_na->data_size/mftrecsz;
|
|
if (!locatelogfile(ctx))
|
|
ok = TRUE;
|
|
else {
|
|
fprintf(stderr,"** Could not read the log file\n");
|
|
}
|
|
} else {
|
|
if (ctx->file
|
|
&& (!memcmp(boot,"RSTR",4) || !memcmp(boot,"CHKD",4))) {
|
|
printf("* Assuming a log file copy\n");
|
|
ok = getlogfiledata(ctx, boot);
|
|
if (!ok)
|
|
goto out;
|
|
} else
|
|
fprintf(stderr,"** Not an NTFS image or log file\n");
|
|
}
|
|
// TODO get rest ?, meaningful ?
|
|
if (ok && rest) {
|
|
if (rest->client_in_use_list
|
|
|| !(rest->flags & const_cpu_to_le16(2)))
|
|
printf("Volume was not unmounted safely\n");
|
|
else
|
|
printf("Volume was unmounted safely\n");
|
|
if (le16_to_cpu(rest->client_in_use_list) > 1)
|
|
printf("** multiple clients not implemented\n");
|
|
}
|
|
out:
|
|
return (ok);
|
|
}
|
|
|
|
/*
|
|
* Open the volume (or the log file) and gets its parameters
|
|
*
|
|
* Returns TRUE if successful
|
|
*/
|
|
|
|
static BOOL open_volume(CONTEXT *ctx, const char *device_name)
|
|
{
|
|
union {
|
|
char buf[1024];
|
|
/* alignment may be needed in getboot() */
|
|
long long force_align;
|
|
} boot;
|
|
BOOL ok;
|
|
int got;
|
|
|
|
ok =FALSE;
|
|
/*
|
|
* First check the boot sector, to avoid library errors
|
|
* when trying to mount a log file.
|
|
* If the device cannot be fopened or fread, then it is
|
|
* unlikely to be a file.
|
|
*/
|
|
ctx->vol = (ntfs_volume*)NULL;
|
|
ctx->file = fopen(device_name, "rb");
|
|
if (ctx->file) {
|
|
got = fread(boot.buf,1,1024,ctx->file);
|
|
if ((got == 1024)
|
|
&& (!memcmp(boot.buf, "RSTR", 4)
|
|
|| !memcmp(boot.buf, "CHKD", 4))) {
|
|
/* This appears to be a log file */
|
|
ctx->vol = (ntfs_volume*)NULL;
|
|
ok = getvolumedata(ctx, boot.buf);
|
|
if (!ok) {
|
|
fclose(ctx->file);
|
|
goto out;
|
|
}
|
|
} else {
|
|
fclose(ctx->file);
|
|
}
|
|
}
|
|
if (!ok) {
|
|
/* Not a log file, assume an ntfs device, mount it */
|
|
ctx->file = (FILE*)NULL;
|
|
ctx->vol = ntfs_mount(device_name,
|
|
((optp || optu || opts) && !optn
|
|
? NTFS_MNT_FORENSIC : NTFS_MNT_RDONLY));
|
|
if (ctx->vol) {
|
|
ok = getvolumedata(ctx, boot.buf);
|
|
if (!ok)
|
|
ntfs_umount(ctx->vol, TRUE);
|
|
}
|
|
}
|
|
out:
|
|
return (ok);
|
|
}
|
|
|
|
static u16 dorcrd(CONTEXT *ctx, u32 blk, u16 pos, const struct BUFFER *buf,
|
|
const struct BUFFER *nextbuf)
|
|
{
|
|
if (optv) {
|
|
if (optv >= 2)
|
|
hexdump(buf->block.data,blocksz);
|
|
printf("* RCRD in block %ld 0x%lx (addr 0x%llx)"
|
|
" from pos 0x%x\n",
|
|
(long)blk,(long)blk,
|
|
(long long)loclogblk(ctx, blk),(int)pos);
|
|
} else {
|
|
if (optt)
|
|
printf("block %ld\n",(long)blk);
|
|
}
|
|
return (forward_rcrd(ctx, blk, pos, buf, nextbuf));
|
|
}
|
|
|
|
/*
|
|
* Concatenate and process a record overlapping on several blocks
|
|
*/
|
|
|
|
static TRISTATE backoverlap(CONTEXT *ctx, int blk,
|
|
const char *data, const char *nextdata, int k)
|
|
{
|
|
const LOG_RECORD *logr;
|
|
char *fullrec;
|
|
s32 size;
|
|
int space;
|
|
int nextspace;
|
|
TRISTATE state;
|
|
u16 blkheadsz;
|
|
|
|
logr = (const LOG_RECORD*)&data[k];
|
|
state = T_ERR;
|
|
size = le32_to_cpu(logr->client_data_length) + LOG_RECORD_HEAD_SZ;
|
|
space = blocksz - k;
|
|
blkheadsz = sizeof(RECORD_PAGE_HEADER)
|
|
+ ((2*getle16(data,6) - 1) | 7) + 1;
|
|
nextspace = blocksz - blkheadsz;
|
|
if ((space >= LOG_RECORD_HEAD_SZ)
|
|
&& (size > space)
|
|
&& (size < MAXRECSIZE)) {
|
|
fullrec = (char*)malloc(size);
|
|
memcpy(fullrec,&data[k],space);
|
|
if (size <= (space + nextspace))
|
|
memcpy(&fullrec[space], nextdata + blkheadsz,
|
|
size - space);
|
|
else {
|
|
const struct BUFFER *morebuf;
|
|
const char *moredata;
|
|
int total;
|
|
int more;
|
|
unsigned int mblk;
|
|
|
|
if (optv)
|
|
printf("* big record, size %d\n",size);
|
|
total = space;
|
|
mblk = blk + 1;
|
|
while (total < size) {
|
|
if (mblk >= (logfilesz >> blockbits))
|
|
mblk = BASEBLKS;
|
|
more = size - total;
|
|
if (more > nextspace)
|
|
more = nextspace;
|
|
morebuf = read_buffer(ctx, mblk);
|
|
if (morebuf) {
|
|
moredata = morebuf->block.data;
|
|
memcpy(&fullrec[total],
|
|
moredata + blkheadsz, more);
|
|
}
|
|
total += more;
|
|
mblk++;
|
|
}
|
|
}
|
|
|
|
state = (likelyop((LOG_RECORD*)fullrec) ? T_OK : T_ERR);
|
|
actionnum++;
|
|
if (optv) {
|
|
printf("\nOverlapping backward action %d at 0x%x"
|
|
" size %d (next at 0x%x)\n",
|
|
(int)actionnum,(int)k,
|
|
(int)size,(int)(k + size));
|
|
printf("Overlap marked for block %ld space %d"
|
|
" likely %d\n",
|
|
(long)blk,(int)space,(state == T_OK));
|
|
}
|
|
if (state == T_OK) {
|
|
showlogr(ctx, k, (LOG_RECORD*)fullrec);
|
|
if (optp || optu || opts)
|
|
state = enqueue_action(ctx,
|
|
(LOG_RECORD*)fullrec,
|
|
size, actionnum);
|
|
} else {
|
|
/* Try to go on unless playing actions */
|
|
if (optb && (state == T_ERR))
|
|
state = T_OK;
|
|
}
|
|
free(fullrec);
|
|
} else {
|
|
/* Error conditions */
|
|
if ((size < MINRECSIZE) || (size > MAXRECSIZE)) {
|
|
printf("** Invalid record size %ld"
|
|
" in block %ld\n",
|
|
(long)size,(long)blk);
|
|
} else
|
|
printf("** Inconsistency : the final"
|
|
" record in block %ld"
|
|
" does not overlap\n",
|
|
(long)blk);
|
|
/* Do not abort, unless playing actions */
|
|
state = (optb ? T_OK : T_ERR);
|
|
}
|
|
return (state);
|
|
}
|
|
|
|
static TRISTATE backward_rcrd(CONTEXT *ctx, u32 blk, int skipped,
|
|
const struct BUFFER *buf, const struct BUFFER *prevbuf,
|
|
const struct BUFFER *nextbuf)
|
|
{
|
|
u16 poslist[75]; /* 4096/sizeof(LOG_RECORD) */
|
|
const RECORD_PAGE_HEADER *rph;
|
|
const RECORD_PAGE_HEADER *prevrph;
|
|
const LOG_RECORD *logr;
|
|
const char *data;
|
|
const char *nextdata;
|
|
BOOL stop;
|
|
TRISTATE state;
|
|
s32 size;
|
|
int cnt;
|
|
u16 k;
|
|
u16 endoff;
|
|
int j;
|
|
|
|
state = T_ERR;
|
|
rph = &buf->block.record;
|
|
prevrph = (RECORD_PAGE_HEADER*)NULL;
|
|
if (prevbuf)
|
|
prevrph = &prevbuf->block.record;
|
|
data = buf->block.data;
|
|
if (rph && (rph->magic == magic_RCRD)
|
|
&& (!prevrph || (prevrph->magic == magic_RCRD))) {
|
|
if (optv) {
|
|
if (optv >= 2)
|
|
hexdump(data,blocksz);
|
|
printf("* RCRD in block %ld 0x%lx (addr 0x%llx)\n",
|
|
(long)blk,(long)blk,
|
|
(long long)loclogblk(ctx, blk));
|
|
} else {
|
|
if (optt)
|
|
printf("block %ld\n",(long)blk);
|
|
}
|
|
showheadrcrd(blk, rph);
|
|
if (!prevbuf)
|
|
k = buf->headsz;
|
|
else
|
|
k = firstrecord(skipped, buf, prevbuf);
|
|
logr = (const LOG_RECORD*)&data[k];
|
|
cnt = 0;
|
|
/* check whether there is at least one beginning of record */
|
|
endoff = le16_to_cpu(rph->next_record_offset);
|
|
if (k && ((k < endoff) || !endoff)) {
|
|
logr = (const LOG_RECORD*)&data[k];
|
|
if (likelyop(logr)) {
|
|
stop = FALSE;
|
|
state = T_OK;
|
|
if (optv)
|
|
printf("First record checked"
|
|
" at offset 0x%x\n", (int)k);
|
|
} else {
|
|
printf("** Bad first record at offset 0x%x\n",
|
|
(int)k);
|
|
if (optv)
|
|
showlogr(ctx, k,logr);
|
|
k = searchlikely(buf);
|
|
stop = !k;
|
|
if (stop) {
|
|
printf("** Could not recover,"
|
|
" stopping at block %d\n",
|
|
(int)blk);
|
|
state = T_ERR;
|
|
} else {
|
|
/* Try to go on, unless running */
|
|
if (optb)
|
|
state = T_OK;
|
|
}
|
|
}
|
|
while (!stop) {
|
|
logr = (const LOG_RECORD*)&data[k];
|
|
size = le32_to_cpu(logr->client_data_length)
|
|
+ LOG_RECORD_HEAD_SZ;
|
|
if ((size < MINRECSIZE)
|
|
|| (size > MAXRECSIZE)
|
|
|| (size & 7)) {
|
|
printf("** Bad size %ld in block %ld"
|
|
" offset 0x%x, stopping\n",
|
|
(long)size,(long)blk,(int)k);
|
|
stop = TRUE;
|
|
} else {
|
|
if (((u32)(k + size) <= blocksz)
|
|
&& ((u32)(k + size) <= endoff)) {
|
|
poslist[cnt++] = k;
|
|
if (!logr->client_data_length)
|
|
stop = TRUE;
|
|
k += size;
|
|
if ((u32)(k
|
|
+ LOG_RECORD_HEAD_SZ)
|
|
> blocksz)
|
|
stop = TRUE;
|
|
} else {
|
|
stop = TRUE;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
stop = TRUE;
|
|
state = (k ? T_OK : T_ERR);
|
|
}
|
|
/* Now examine an overlapping record */
|
|
if (k
|
|
&& ((k == endoff) || !endoff)
|
|
&& ((u32)(k + LOG_RECORD_HEAD_SZ) <= blocksz)) {
|
|
if (nextbuf && (blk >= BASEBLKS)) {
|
|
nextdata = nextbuf->block.data;
|
|
state = backoverlap(ctx, blk,
|
|
data, nextdata, k);
|
|
}
|
|
}
|
|
for (j=cnt-1; (j>=0) && (state==T_OK); j--) {
|
|
k = poslist[j];
|
|
logr = (const LOG_RECORD*)&data[k];
|
|
size = le32_to_cpu(logr->client_data_length)
|
|
+ LOG_RECORD_HEAD_SZ;
|
|
actionnum++;
|
|
if (optv && (!optc || within_lcn_range(logr))) {
|
|
printf("\n* log backward action %u at 0x%x"
|
|
" size %d (next at 0x%x)\n",
|
|
actionnum, k, size, k + size);
|
|
}
|
|
if ((optv | optt)
|
|
&& (!nextbuf && (j == (cnt - 1)))) {
|
|
printf("* This is the latest record\n");
|
|
if (logr->this_lsn == restart.current_lsn)
|
|
printf(" its lsn matches the global"
|
|
" restart lsn\n");
|
|
if (logr->this_lsn == client.client_restart_lsn)
|
|
printf(" its lsn matches the client"
|
|
" restart lsn\n");
|
|
if (logr->client_data_length
|
|
== restart.last_lsn_data_length)
|
|
printf(" its length matches the"
|
|
" last record length\n");
|
|
}
|
|
showlogr(ctx, k, logr);
|
|
if (optp || optu || opts)
|
|
state = enqueue_action(ctx, logr, size, actionnum);
|
|
}
|
|
}
|
|
return (state);
|
|
}
|
|
|
|
static int walkback(CONTEXT *ctx, const struct BUFFER *buf, u32 blk,
|
|
const struct BUFFER *prevbuf, u32 prevblk)
|
|
{
|
|
const struct BUFFER *nextbuf;
|
|
NTFS_RECORD_TYPES magic;
|
|
u32 stopblk;
|
|
TRISTATE state;
|
|
|
|
if (optv)
|
|
printf("\n* block %d at 0x%llx\n",(int)blk,
|
|
(long long)loclogblk(ctx, blk));
|
|
ctx->firstaction = (struct ACTION_RECORD*)NULL;
|
|
ctx->lastaction = (struct ACTION_RECORD*)NULL;
|
|
nextbuf = (const struct BUFFER*)NULL;
|
|
stopblk = prevblk + 2; // wraparound !
|
|
state = backward_rcrd(ctx, blk, 0, buf,
|
|
prevbuf, (struct BUFFER*)NULL);
|
|
while ((state == T_OK)
|
|
&& !((blk > stopblk) && (prevblk <= stopblk))
|
|
&& (!(optp || optu) || (playedactions < playcount))) {
|
|
int skipped;
|
|
|
|
nextbuf = buf;
|
|
buf = prevbuf;
|
|
blk = prevblk;
|
|
skipped = 0;
|
|
prevbuf = findprevious(ctx, buf);
|
|
if (prevbuf) {
|
|
prevblk = prevbuf->num;
|
|
if (prevblk < blk)
|
|
skipped = blk - prevblk - 1;
|
|
else
|
|
skipped = blk - prevblk - 1
|
|
+ (logfilesz >> blockbits) - BASEBLKS;
|
|
magic = prevbuf->block.record.magic;
|
|
switch (magic) {
|
|
case magic_RCRD :
|
|
break;
|
|
case magic_CHKD :
|
|
printf("** Unexpected block type CHKD\n");
|
|
break;
|
|
case magic_RSTR :
|
|
printf("** Unexpected block type RSTR\n");
|
|
break;
|
|
default :
|
|
printf("** Invalid block %d\n",(int)prevblk);
|
|
break;
|
|
}
|
|
if (optv) {
|
|
if (skipped)
|
|
printf("\n* block %ld at 0x%llx (block"
|
|
" %ld used as previous one)\n",
|
|
(long)blk,
|
|
(long long)loclogblk(ctx, blk),
|
|
(long)prevblk);
|
|
else
|
|
printf("\n* block %ld at 0x%llx\n",
|
|
(long)blk,
|
|
(long long)loclogblk(ctx, blk));
|
|
}
|
|
state = backward_rcrd(ctx, blk, skipped,
|
|
buf, prevbuf, nextbuf);
|
|
} else {
|
|
fprintf(stderr,"** Could not read block %lu\n",
|
|
(long)prevblk);
|
|
state = T_ERR;
|
|
}
|
|
}
|
|
if ((blk > stopblk) && (prevblk <= stopblk))
|
|
printf("* Earliest block reached\n");
|
|
if ((optp || optu) && (playedactions >= playcount))
|
|
printf("* Transaction set count reached\n");
|
|
if (opts)
|
|
printf("* %s %s after playing %u actions\n",
|
|
(optn ? "Sync simulation" : "Syncing"),
|
|
(state == T_ERR ? "failed" : "successful"),
|
|
redocount);
|
|
/* free queue */
|
|
while (ctx->firstaction) {
|
|
struct ACTION_RECORD *action;
|
|
|
|
action = ctx->firstaction->next;
|
|
free(ctx->firstaction);
|
|
ctx->firstaction = action;
|
|
}
|
|
ctx->lastaction = (struct ACTION_RECORD*)NULL;
|
|
return (state == T_ERR ? 1 : 0);
|
|
}
|
|
|
|
static int walk(CONTEXT *ctx)
|
|
{
|
|
const struct BUFFER *buf;
|
|
const struct BUFFER *nextbuf;
|
|
const struct BUFFER *prevbuf;
|
|
const struct BUFFER *startbuf;
|
|
const NTFS_RECORD *record;
|
|
const RECORD_PAGE_HEADER *rph;
|
|
NTFS_RECORD_TYPES magic;
|
|
u32 blk;
|
|
u32 nextblk;
|
|
u32 prevblk;
|
|
int err;
|
|
u16 blkheadsz;
|
|
u16 pos;
|
|
BOOL dirty;
|
|
BOOL done;
|
|
|
|
buf = (struct BUFFER*)NULL;
|
|
nextbuf = (struct BUFFER*)NULL;
|
|
if (optb || optp || optu || opts) {
|
|
prevbuf = (struct BUFFER*)NULL;
|
|
}
|
|
done = FALSE;
|
|
dirty = TRUE;
|
|
err = 0;
|
|
blk = 0;
|
|
pos = 0;
|
|
/* read and process the first restart block */
|
|
buf = read_restart(ctx);
|
|
if (buf) {
|
|
if (optv)
|
|
printf("\n* block %d at 0x%llx\n",(int)blk,
|
|
(long long)loclogblk(ctx, blk));
|
|
} else {
|
|
done = TRUE;
|
|
err = 1;
|
|
}
|
|
|
|
nextblk = blk + 1;
|
|
while (!done) {
|
|
/* next block is needed to process the current one */
|
|
if ((nextblk >= (logfilesz >> blockbits)) && (optr || optf))
|
|
nextbuf = read_buffer(ctx, BASEBLKS);
|
|
else
|
|
nextbuf = read_buffer(ctx,nextblk);
|
|
if (nextbuf) {
|
|
record = (const NTFS_RECORD*)&nextbuf->block.data;
|
|
blkheadsz = nextbuf->headsz;
|
|
magic = record->magic;
|
|
switch (magic) {
|
|
case magic_CHKD :
|
|
case magic_RSTR :
|
|
case magic_RCRD :
|
|
break;
|
|
default :
|
|
printf("** Invalid block\n");
|
|
err = 1;
|
|
break;
|
|
}
|
|
magic = buf->block.record.magic;
|
|
switch (magic) {
|
|
case magic_CHKD :
|
|
case magic_RSTR :
|
|
dirty = dorest(ctx, blk, &buf->block.restart,
|
|
FALSE);
|
|
break;
|
|
case magic_RCRD :
|
|
if (blk < BASEBLKS)
|
|
pos = buf->headsz;
|
|
pos = dorcrd(ctx, blk, pos, buf, nextbuf);
|
|
while (pos >= blocksz) {
|
|
if (optv > 1)
|
|
printf("Skipping block %d"
|
|
" pos 0x%x\n",
|
|
(int)nextblk,(int)pos);
|
|
pos -= (blocksz - blkheadsz);
|
|
nextblk++;
|
|
}
|
|
if ((blocksz - pos) < LOG_RECORD_HEAD_SZ) {
|
|
pos = 0;
|
|
nextblk++;
|
|
}
|
|
if (nextblk != (blk + 1)) {
|
|
nextbuf = read_buffer(ctx,nextblk);
|
|
}
|
|
break;
|
|
default :
|
|
if (!~magic) {
|
|
if (optv)
|
|
printf(" empty block\n");
|
|
}
|
|
break;
|
|
}
|
|
} else {
|
|
fprintf(stderr,"* Could not read block %d\n",nextblk);
|
|
if (ctx->vol) {
|
|
/* In full mode, ignore errors on restart blocks */
|
|
if (blk >= RSTBLKS) {
|
|
done = TRUE;
|
|
err = 1;
|
|
}
|
|
} else {
|
|
done = TRUE;
|
|
err = 1;
|
|
}
|
|
}
|
|
blk = nextblk;
|
|
nextblk++;
|
|
if (optr) { /* Only selected range */
|
|
if ((nextblk == BASEBLKS) && (nextblk < firstblk))
|
|
nextblk = firstblk;
|
|
if ((blk >= BASEBLKS) && (blk > lastblk))
|
|
done = TRUE;
|
|
} else
|
|
if (optf) { /* Full log, forward */
|
|
if (blk*blocksz >= logfilesz)
|
|
done = TRUE;
|
|
} else
|
|
if (optb || optp || optu || opts) {
|
|
/* Restart blocks only (2 blocks) */
|
|
if (blk >= RSTBLKS)
|
|
done = TRUE;
|
|
} else { /* Base blocks only (4 blocks) */
|
|
if (blk >= BASEBLKS)
|
|
done = TRUE;
|
|
}
|
|
if (!done) {
|
|
buf = nextbuf;
|
|
if (blk >= RSTBLKS && blk < BASEBLKS) {
|
|
/* The latest buf may be more recent
|
|
than restart */
|
|
rph = &buf->block.record;
|
|
if ((s64)(sle64_to_cpu(rph->last_end_lsn)
|
|
- committed_lsn) > 0) {
|
|
committed_lsn =
|
|
sle64_to_cpu(rph->last_end_lsn);
|
|
if (optv)
|
|
printf("* Restart page was "
|
|
"obsolete, updated "
|
|
"committed lsn\n");
|
|
}
|
|
}
|
|
if (optv)
|
|
printf("\n* block %d at 0x%llx\n",(int)blk,
|
|
(long long)loclogblk(ctx, blk));
|
|
}
|
|
}
|
|
if (optv && opts && !dirty)
|
|
printf("* Volume is clean, nothing to do\n");
|
|
if (optb || optp || optu
|
|
|| (opts && dirty)) {
|
|
playedactions = 0;
|
|
ctx->firstaction = (struct ACTION_RECORD*)NULL;
|
|
ctx->lastaction = (struct ACTION_RECORD*)NULL;
|
|
buf = nextbuf;
|
|
nextbuf = read_buffer(ctx, blk+1);
|
|
startbuf = best_start(buf,nextbuf);
|
|
if (startbuf) {
|
|
if (startbuf == nextbuf) {
|
|
/* nextbuf is better, show blk */
|
|
if (optv && buf) {
|
|
printf("* Ignored block %d at 0x%llx\n",
|
|
(int)blk,
|
|
(long long)loclogblk(ctx, blk));
|
|
if (optv >= 2)
|
|
hexdump(buf->block.data,
|
|
blocksz);
|
|
showheadrcrd(blk, &buf->block.record);
|
|
}
|
|
blk++;
|
|
buf = nextbuf;
|
|
} else {
|
|
/* buf is better, show blk + 1 */
|
|
if (optv && nextbuf) {
|
|
printf("* Ignored block %d at 0x%llx\n",
|
|
(int)(blk + 1),
|
|
(long long)loclogblk(ctx,
|
|
blk + 1));
|
|
if (optv >= 2)
|
|
hexdump(nextbuf->block.data,
|
|
blocksz);
|
|
showheadrcrd(blk + 1,
|
|
&nextbuf->block.record);
|
|
}
|
|
}
|
|
/* The latest buf may be more recent than restart */
|
|
rph = &buf->block.record;
|
|
if ((s64)(sle64_to_cpu(rph->last_end_lsn)
|
|
- committed_lsn) > 0) {
|
|
committed_lsn = sle64_to_cpu(rph->last_end_lsn);
|
|
if (optv)
|
|
printf("* Restart page was obsolete\n");
|
|
}
|
|
nextbuf = (const struct BUFFER*)NULL;
|
|
prevbuf = findprevious(ctx, buf);
|
|
if (prevbuf) {
|
|
prevblk = prevbuf->num;
|
|
magic = prevbuf->block.record.magic;
|
|
switch (magic) {
|
|
case magic_RCRD :
|
|
break;
|
|
case magic_CHKD :
|
|
printf("** Unexpected block type CHKD\n");
|
|
err = 1;
|
|
break;
|
|
case magic_RSTR :
|
|
err = 1;
|
|
printf("** Unexpected block type RSTR\n");
|
|
break;
|
|
default :
|
|
err = 1;
|
|
printf("** Invalid block\n");
|
|
break;
|
|
}
|
|
} else
|
|
prevblk = BASEBLKS;
|
|
if (!err)
|
|
err = walkback(ctx, buf, blk,
|
|
prevbuf, prevblk);
|
|
} else {
|
|
fprintf(stderr,"** No valid start block, aborting\n");
|
|
err = 1;
|
|
}
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
BOOL exception(int num)
|
|
{
|
|
int i;
|
|
|
|
i = 0;
|
|
while ((i < 10) && optx[i] && (optx[i] != num))
|
|
i++;
|
|
return (optx[i] == num);
|
|
}
|
|
|
|
static void version(void)
|
|
{
|
|
printf("\n%s v%s (libntfs-3g) - Recover updates committed by Windows"
|
|
" on an NTFS Volume.\n\n", "ntfsrecover", VERSION);
|
|
printf("Copyright (c) 2012-2016 Jean-Pierre Andre\n");
|
|
printf("\n%s\n%s%s\n", ntfs_gpl, ntfs_bugs, ntfs_home);
|
|
}
|
|
|
|
static void usage(void)
|
|
{
|
|
fprintf(stderr,"Usage : for recovering the updates committed by Windows :\n");
|
|
fprintf(stderr," ntfsrecover partition\n");
|
|
fprintf(stderr," (e.g. ntfsrecover /dev/sda1)\n");
|
|
fprintf(stderr,"Advanced : ntfsrecover [-b] [-c first-last] [-i] [-f] [-n] [-p count]\n");
|
|
fprintf(stderr," [-r first-last] [-t] [-u count] [-v] partition\n");
|
|
fprintf(stderr," -b : show the full log backward\n");
|
|
fprintf(stderr," -c : restrict to the actions related to cluster range\n");
|
|
fprintf(stderr," -i : show invalid (stale) records\n");
|
|
fprintf(stderr," -f : show the full log forward\n");
|
|
fprintf(stderr," -h : show this help information\n");
|
|
fprintf(stderr," -n : do not apply any modification\n");
|
|
fprintf(stderr," -p : undo the latest count transaction sets and play one\n");
|
|
fprintf(stderr," -r : show a range of log blocks forward\n");
|
|
fprintf(stderr," -s : sync the committed changes (default)\n");
|
|
fprintf(stderr," -t : show transactions\n");
|
|
fprintf(stderr," -u : undo the latest count transaction sets\n");
|
|
fprintf(stderr," -v : show more information (-vv yet more)\n");
|
|
fprintf(stderr," -V : show version and exit\n");
|
|
}
|
|
|
|
/*
|
|
* Process command options
|
|
*/
|
|
|
|
static BOOL getoptions(int argc, char *argv[])
|
|
{
|
|
int c;
|
|
int xcount;
|
|
u32 xval;
|
|
char *endptr;
|
|
BOOL err;
|
|
static const char *sopt = "-bc:hifnp:r:stu:vVx:";
|
|
static const struct option lopt[] = {
|
|
{ "backward", no_argument, NULL, 'b' },
|
|
{ "clusters", required_argument, NULL, 'c' },
|
|
{ "forward", no_argument, NULL, 'f' },
|
|
{ "help", no_argument, NULL, 'h' },
|
|
{ "no-action", no_argument, NULL, 'n' },
|
|
{ "play", required_argument, NULL, 'p' },
|
|
{ "range", required_argument, NULL, 'r' },
|
|
{ "sync", no_argument, NULL, 's' },
|
|
{ "transactions", no_argument, NULL, 't' },
|
|
{ "undo", required_argument, NULL, 'u' },
|
|
{ "verbose", no_argument, NULL, 'v' },
|
|
{ "version", no_argument, NULL, 'V' },
|
|
{ "exceptions", required_argument, NULL, 'x' },
|
|
{ NULL, 0, NULL, 0 }
|
|
};
|
|
|
|
err = FALSE;
|
|
optb = FALSE;
|
|
optc = FALSE;
|
|
optd = FALSE;
|
|
optf = FALSE;
|
|
opth = FALSE;
|
|
opti = FALSE;
|
|
optn = FALSE;
|
|
optp = FALSE;
|
|
optr = FALSE;
|
|
opts = 0;
|
|
optt = FALSE;
|
|
optu = FALSE;
|
|
optv = 0;
|
|
optV = FALSE;
|
|
optx[0] = 0;
|
|
|
|
while ((c = getopt_long(argc, argv, sopt, lopt, NULL)) != -1) {
|
|
switch (c) {
|
|
case 1: /* A non-option argument */
|
|
if (optind == argc)
|
|
optd = TRUE;
|
|
else {
|
|
fprintf(stderr, "Device must be the"
|
|
" last argument.\n");
|
|
err = TRUE;
|
|
}
|
|
break;
|
|
case 'b':
|
|
optb = TRUE;
|
|
break;
|
|
case 'c':
|
|
firstlcn = strtoull(optarg, &endptr, 0);
|
|
lastlcn = firstlcn;
|
|
if (*endptr == '-')
|
|
lastlcn = strtoull(++endptr, &endptr, 0);
|
|
if (*endptr || (lastlcn < firstlcn)) {
|
|
fprintf(stderr,"Bad cluster range\n");
|
|
err = TRUE;
|
|
} else
|
|
optc = TRUE;
|
|
break;
|
|
case 'f':
|
|
optf = TRUE;
|
|
break;
|
|
case '?':
|
|
case 'h':
|
|
opth = TRUE;
|
|
break;
|
|
case 'n':
|
|
optn = TRUE;
|
|
break;
|
|
case 'p':
|
|
playcount = strtoull(optarg, &endptr, 0);
|
|
if (*endptr) {
|
|
fprintf(stderr,"Bad play count\n");
|
|
err = TRUE;
|
|
} else
|
|
optp = TRUE;
|
|
break;
|
|
case 'r' :
|
|
firstblk = strtoull(optarg, &endptr, 0);
|
|
lastblk = firstblk;
|
|
if (*endptr == '-')
|
|
lastblk = strtoull(++endptr, &endptr, 0);
|
|
if (*endptr || (lastblk < firstblk)) {
|
|
fprintf(stderr,"Bad log block range\n");
|
|
err = TRUE;
|
|
} else
|
|
optr = TRUE;
|
|
break;
|
|
case 's':
|
|
opts++;
|
|
break;
|
|
case 't':
|
|
optt = TRUE;
|
|
break;
|
|
case 'u':
|
|
playcount = strtoull(optarg, &endptr, 0);
|
|
if (*endptr) {
|
|
fprintf(stderr,"Bad undo count\n");
|
|
err = TRUE;
|
|
} else
|
|
optu = TRUE;
|
|
break;
|
|
case 'v':
|
|
optv++;
|
|
break;
|
|
case 'V':
|
|
optV = TRUE;
|
|
break;
|
|
case 'x':
|
|
/*
|
|
* Undocumented : actions to execute, though
|
|
* they should be skipped under normal rules.
|
|
*/
|
|
xcount = 0;
|
|
xval = strtoull(optarg, &endptr, 0);
|
|
while ((*endptr == ',')
|
|
&& (xcount < (MAXEXCEPTION - 1))) {
|
|
optx[xcount++] = xval;
|
|
xval = strtoull(++endptr, &endptr, 0);
|
|
}
|
|
if (*endptr || (xcount >= MAXEXCEPTION)) {
|
|
fprintf(stderr,"Bad exception list\n");
|
|
err = TRUE;
|
|
} else {
|
|
optx[xcount++] = xval;
|
|
optx[xcount] = 0;
|
|
}
|
|
break;
|
|
default:
|
|
fprintf(stderr,"Unknown option '%s'.\n",
|
|
argv[optind - 1]);
|
|
err = TRUE;
|
|
}
|
|
}
|
|
|
|
if (!optd && !optV && !opth) {
|
|
fprintf(stderr,"Device argument is missing\n");
|
|
err = TRUE;
|
|
}
|
|
if (!(optb || optf || optp || optr || opts || optt || optu || optV))
|
|
opts = 1;
|
|
if (optb && (optf || optr || opts)) {
|
|
fprintf(stderr,"Options -f, -r and -s are incompatible with -b\n");
|
|
err = TRUE;
|
|
}
|
|
if (optf && (optp || opts || optu)) {
|
|
fprintf(stderr,"Options -p, -s and -u are incompatible with -f\n");
|
|
err = TRUE;
|
|
}
|
|
if (optp && (optr || opts || optt || optu)) {
|
|
fprintf(stderr,"Options -r, -s, -t and -u are incompatible with -p\n");
|
|
err = TRUE;
|
|
}
|
|
if (optr && (opts || optu)) {
|
|
fprintf(stderr,"Options -s and -u are incompatible with -r\n");
|
|
err = TRUE;
|
|
}
|
|
if (opts && (optt || optu)) {
|
|
fprintf(stderr,"Options -t and -u are incompatible with -s\n");
|
|
err = TRUE;
|
|
}
|
|
|
|
if (opth || err)
|
|
usage();
|
|
else
|
|
if (optV)
|
|
version();
|
|
return (!err);
|
|
}
|
|
|
|
/*
|
|
* Quick checks on the layout of needed structs
|
|
*/
|
|
|
|
static BOOL checkstructs(void)
|
|
{
|
|
BOOL ok;
|
|
|
|
ok = TRUE;
|
|
if (sizeof(RECORD_PAGE_HEADER) != 40) {
|
|
fprintf(stderr,
|
|
"* error : bad sizeof(RECORD_PAGE_HEADER) %d\n",
|
|
(int)sizeof(RECORD_PAGE_HEADER));
|
|
ok = FALSE;
|
|
}
|
|
if (sizeof(LOG_RECORD) != 88) {
|
|
fprintf(stderr,
|
|
"* error : bad sizeof(LOG_RECORD) %d\n",
|
|
(int)sizeof(LOG_RECORD));
|
|
ok = FALSE;
|
|
}
|
|
if (sizeof(RESTART_PAGE_HEADER) != 32) {
|
|
fprintf(stderr,
|
|
"* error : bad sizeof(RESTART_PAGE_HEADER) %d\n",
|
|
(int)sizeof(RESTART_PAGE_HEADER));
|
|
ok = FALSE;
|
|
}
|
|
if (sizeof(RESTART_AREA) != 48) {
|
|
fprintf(stderr,
|
|
"* error : bad sizeof(RESTART_AREA) %d\n",
|
|
(int)sizeof(RESTART_AREA));
|
|
ok = FALSE;
|
|
}
|
|
if (sizeof(ATTR_OLD) != 44) {
|
|
fprintf(stderr,
|
|
"* error : bad sizeof(ATTR_OLD) %d\n",
|
|
(int)sizeof(ATTR_OLD));
|
|
ok = FALSE;
|
|
}
|
|
if (sizeof(ATTR_NEW) != 40) {
|
|
fprintf(stderr,
|
|
"* error : bad sizeof(ATTR_NEW) %d\n",
|
|
(int)sizeof(ATTR_NEW));
|
|
ok = FALSE;
|
|
}
|
|
if (LastAction != 38) {
|
|
fprintf(stderr,
|
|
"* error : bad action list, %d actions\n",
|
|
(int)LastAction);
|
|
ok = FALSE;
|
|
}
|
|
return (ok);
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
CONTEXT ctx;
|
|
unsigned int i;
|
|
int err;
|
|
|
|
err = 1;
|
|
if (checkstructs()
|
|
&& getoptions(argc,argv)) {
|
|
if (optV || opth) {
|
|
err = 0;
|
|
} else {
|
|
redocount = 0;
|
|
undocount = 0;
|
|
actionnum = 0;
|
|
attrcount = 0;
|
|
redos_met = 0;
|
|
attrtable = (struct ATTR**)NULL;
|
|
for (i=0; i<(BUFFERCNT + BASEBLKS); i++)
|
|
buffer_table[i] = (struct BUFFER*)NULL;
|
|
ntfs_log_set_handler(ntfs_log_handler_outerr);
|
|
if (open_volume(&ctx, argv[argc - 1])) {
|
|
if (!ctx.vol
|
|
&& (opts || optp || optu)) {
|
|
printf("Options -s, -p and -u"
|
|
" require a full device\n");
|
|
err = 1;
|
|
} else {
|
|
err = walk(&ctx);
|
|
if (ctx.vol) {
|
|
if ((optp || optu || opts)
|
|
&& !err
|
|
&& !optn) {
|
|
reset_logfile(&ctx);
|
|
}
|
|
ntfs_attr_close(log_na);
|
|
ntfs_inode_close(log_ni);
|
|
ntfs_umount(ctx.vol, TRUE);
|
|
} else
|
|
fclose(ctx.file);
|
|
}
|
|
} else
|
|
fprintf(stderr,"Could not open %s\n",
|
|
argv[argc - 1]);
|
|
for (i=0; i<(BUFFERCNT + BASEBLKS); i++)
|
|
free(buffer_table[i]);
|
|
for (i=0; i<attrcount; i++)
|
|
free(attrtable[i]);
|
|
free(attrtable);
|
|
if (ctx.vol) {
|
|
freeclusterentry((struct STORE*)NULL);
|
|
show_redos();
|
|
}
|
|
}
|
|
}
|
|
if (err)
|
|
exit(1);
|
|
return (0);
|
|
}
|