--- /dev/null
+/*
+ * Process log data from an NTFS partition
+ *
+ * Copyright (c) 2012-2015 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 "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;
+
+struct RESTART_PAGE_HEADER log_header;
+struct RESTART_AREA restart;
+struct RESTART_CLIENT 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;
+ struct 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->head.usa_ofs);
+ nusa = le16_to_cpu(record->head.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(struct 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 struct 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 struct 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 struct 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 struct LOG_RECORD *logr)
+{
+ BOOL likely;
+
+ switch (le32_to_cpu(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 struct 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 struct LOG_RECORD*)&data[k];
+ if (!likelyop(logr)) {
+ do {
+ k += 8;
+ logr = (const struct 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 struct RECORD_PAGE_HEADER *rph;
+ const struct RECORD_PAGE_HEADER *prevrph;
+ const struct 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 struct 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 struct 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 struct RECORD_PAGE_HEADER *rph;
+ int skipped;
+ int prevblk;
+ BOOL prevmiddle;
+ BOOL error;
+ u16 endoff;
+
+ error = FALSE;
+ prevblk = buf->num;
+ skipped = 0;
+ do {
+ prevmiddle = FALSE;
+ if (prevblk > BASEBLKS)
+ prevblk--;
+ else
+ if (prevblk == BASEBLKS)
+ prevblk = (logfilesz >> blockbits) - 1;
+ else {
+ rph = &buf->block.record;
+ prevblk = (le32_to_cpu(rph->copy.file_offset)
+ >> 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 struct ATTR_NEW *panew;
+ struct ATTR_OLD old_aligned;
+
+ if (pa) {
+ switch (length) {
+ case sizeof(struct ATTR_NEW) :
+ panew = (const struct ATTR_NEW*)buf;
+ pa->type = panew->type;
+ pa->lsn = le64_to_cpu(panew->lsn);
+ pa->inode = MREF(le64_to_cpu(panew->inode));
+ break;
+ case sizeof(struct ATTR_OLD) :
+ /* Badly aligned, first realign */
+ memcpy(&old_aligned,buf,sizeof(old_aligned));
+ pa->type = old_aligned.type;
+ pa->lsn = le64_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 struct 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 struct 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)le32_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)le32_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(struct ATTR_OLD))
+ && (step != sizeof(struct 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 struct 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 (le32_to_cpu(logr->record_type)) {
+ case 1 :
+ 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 %08lx (inode %lld)\n",
+ (long)le32_to_cpu(logr->target_vcn),
+ (((long long)le32_to_cpu(logr->target_vcn)
+ << clusterbits)
+ + (le16_to_cpu(logr->cluster_index) << 9))
+ >> mftrecbits);
+ else
+ printf("target_vcn %08lx\n",
+ (long)le32_to_cpu(logr->target_vcn));
+ printf("reserved3 %08lx\n",
+ (long)le32_to_cpu(logr->reserved3));
+ /* Compute a base for the current run of mft */
+ baselcn = le64_to_cpu(logr->lcn_list[0])
+ - le32_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 2 :
+ 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)le64_to_cpu(logr->transaction_lsn));
+ printf("attributes_lsn %016llx\n",
+ (long long)le64_to_cpu(logr->attributes_lsn));
+ printf("names_lsn %016llx\n",
+ (long long)le64_to_cpu(logr->names_lsn));
+ printf("dirty_pages_lsn %016llx\n",
+ (long long)le64_to_cpu(logr->dirty_pages_lsn));
+ listsize = le32_to_cpu(logr->client_data_length)
+ + LOG_RECORD_HEAD_SZ
+ - offsetof(struct 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 struct LOG_RECORD *logr)
+{
+ u64 lcn;
+ unsigned int i;
+ BOOL within;
+
+ within = FALSE;
+ switch (le32_to_cpu(logr->record_type)) {
+ case 1 :
+ 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 struct LOG_RECORD *logr)
+{
+ s32 diff;
+
+ if (optv && (!optc || within_lcn_range(logr))) {
+ diff = le64_to_cpu(logr->this_lsn) - synced_lsn;
+ printf("this_lsn %016llx (synced%s%ld) %s\n",
+ (long long)le64_to_cpu(logr->this_lsn),
+ (diff < 0 ? "" : "+"),(long)diff,
+ commitment(diff + synced_lsn));
+ printf("client_previous_lsn %016llx\n",
+ (long long)le64_to_cpu(logr->client_previous_lsn));
+ printf("client_undo_next_lsn %016llx\n",
+ (long long)le64_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->reserved1[0]),
+ (int)le16_to_cpu(logr->reserved1[1]),
+ (int)le16_to_cpu(logr->reserved1[2]));
+ detaillogr(ctx, logr);
+ }
+ if (optt) {
+ const char *state;
+
+ if (logr->record_type == const_cpu_to_le32(2))
+ state = "--checkpoint--";
+ else
+ state = commitment(le64_to_cpu(logr->this_lsn));
+ printf(" at %04x %016llx %s (%ld) %s\n",k,
+ (long long)le64_to_cpu(logr->this_lsn),
+ state,
+ (long)(le64_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)le64_to_cpu(
+ logr->client_previous_lsn));
+ } else {
+ printf(" "
+ " previous %016llx",
+ (long long)le64_to_cpu(
+ logr->client_previous_lsn));
+
+ if (logr->client_undo_next_lsn)
+ printf(" undo %016llx\n",
+ (long long)le64_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 struct 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)(le64_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 struct 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 == const_cpu_to_le32(2))) {
+ /* 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)(le64_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 struct RECORD_PAGE_HEADER *rph)
+{
+ s32 diff;
+
+ if (optv) {
+ printf("magic %08lx\n",
+ (long)le32_to_cpu(rph->head.magic));
+ printf("usa_ofs %04x\n",
+ (int)le16_to_cpu(rph->head.usa_ofs));
+ printf("usa_count %04x\n",
+ (int)le16_to_cpu(rph->head.usa_count));
+ if (blk < 4)
+ printf("file_offset %08lx\n",
+ (long)le32_to_cpu(rph->copy.file_offset));
+ else {
+ diff = le64_to_cpu(rph->copy.last_lsn) - synced_lsn;
+ printf("last_lsn %016llx"
+ " (synced%s%ld)\n",
+ (long long)le64_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->reserved4[0]),
+ (int)le16_to_cpu(rph->reserved4[1]),
+ (int)le16_to_cpu(rph->reserved4[2]));
+ diff = le64_to_cpu(rph->last_end_lsn) - synced_lsn;
+ printf("last_end_lsn %016llx (synced%s%ld)\n",
+ (long long)le64_to_cpu(rph->last_end_lsn),
+ (diff < 0 ? "" : "+"),(long)diff);
+ printf("usn %04x\n",
+ (int)getle16(rph,le16_to_cpu(rph->head.usa_ofs)));
+ printf("\n");
+ } else {
+ if (optt) {
+ const char *state;
+
+ state = commitment(le64_to_cpu(rph->copy.last_lsn));
+ diff = le64_to_cpu(rph->copy.last_lsn) - synced_lsn;
+ printf(" last %016llx (synced%s%ld) %s\n",
+ (long long)le64_to_cpu(rph->copy.last_lsn),
+ (diff < 0 ? "" : "+"),(long)diff, state);
+ state = commitment(le64_to_cpu(rph->last_end_lsn));
+ diff = le64_to_cpu(rph->last_end_lsn) - synced_lsn;
+ printf(" last_end %016llx (synced%s%ld) %s\n",
+ (long long)le64_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 struct 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 struct 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((struct 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,
+ (struct 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((struct 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,
+ (struct 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 struct RECORD_PAGE_HEADER *rph;
+ const struct LOG_RECORD *logr;
+ const char *data;
+ u16 k;
+ u16 endoff;
+ BOOL stop;
+
+ rph = &buf->block.record;
+ if (rph && (rph->head.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 struct 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(struct 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(struct 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 struct 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->head.magic));
+ k = 0;
+ }
+ return (k);
+}
+
+/*
+ * Display a restart page
+ */
+
+static void showrest(const struct RESTART_PAGE_HEADER *rest)
+{
+ const struct RESTART_AREA *resa;
+ const struct RESTART_CLIENT *rcli;
+ const char *data;
+
+ data = (const char*)rest;
+ if ((rest->head.magic == magic_RSTR)
+ || (rest->head.magic == magic_CHKD)) {
+ if (optv) {
+ printf("magic %08lx\n",
+ (long)le32_to_cpu(rest->head.magic));
+ printf("usa_ofs %04x\n",
+ (int)le16_to_cpu(rest->head.usa_ofs));
+ printf("usa_count %04x\n",
+ (int)le16_to_cpu(rest->head.usa_count));
+ printf("chkdsk_lsn %016llx\n",
+ (long long)le64_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_offset %04x\n",
+ (int)le16_to_cpu(rest->restart_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)le64_to_cpu(rest->chkdsk_lsn));
+ }
+ resa = (const struct RESTART_AREA*)
+ &data[le16_to_cpu(rest->restart_offset)];
+ if (optv) {
+ printf("current_lsn %016llx\n",
+ (long long)le64_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->record_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)le64_to_cpu(resa->current_lsn));
+ }
+
+ rcli = (const struct RESTART_CLIENT*)
+ &data[le16_to_cpu(rest->restart_offset)
+ + le16_to_cpu(resa->client_array_offset)];
+ if (optv) {
+ printf("oldest_lsn %016llx\n",
+ (long long)le64_to_cpu(rcli->oldest_lsn));
+ printf("client_restart_lsn %016llx\n",
+ (long long)le64_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)le64_to_cpu(
+ rcli->oldest_lsn));
+ printf(" committed %016llx\n",
+ (long long)le64_to_cpu(
+ rcli->client_restart_lsn));
+ }
+ }
+ } else
+ printf("Not a RSTR or CHKD record, MAGIC 0x%08lx\n",
+ (long)le32_to_cpu(rest->head.magic));
+}
+
+static BOOL dorest(CONTEXT *ctx, unsigned long blk,
+ const struct RESTART_PAGE_HEADER *rph, BOOL initial)
+{
+ const struct RESTART_AREA *resa;
+ const struct RESTART_CLIENT *rcli;
+ const char *data;
+ s64 diff;
+ int offs;
+ int size;
+ BOOL change;
+ BOOL dirty;
+
+ data = (const char*)rph;
+ offs = le16_to_cpu(rph->restart_offset);
+ resa = (const struct RESTART_AREA*)&data[offs];
+ rcli = (const struct RESTART_CLIENT*)&data[offs
+ + le16_to_cpu(resa->client_array_offset)];
+ if (initial) {
+ /* Information from block initially found best */
+ latest_lsn = le64_to_cpu(resa->current_lsn);
+ committed_lsn = le64_to_cpu(rcli->client_restart_lsn);
+ synced_lsn = le64_to_cpu(rcli->oldest_lsn);
+ memcpy(&log_header, rph,
+ sizeof(struct RESTART_PAGE_HEADER));
+ offs = le16_to_cpu(log_header.restart_offset);
+ memcpy(&restart, &data[offs],
+ sizeof(struct RESTART_AREA));
+ offs += le16_to_cpu(restart.client_array_offset);
+ memcpy(&client, &data[offs],
+ sizeof(struct RESTART_CLIENT));
+ 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 = le64_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 = le64_to_cpu(rcli->client_restart_lsn);
+ synced_lsn = le64_to_cpu(rcli->oldest_lsn);
+ latest_lsn = le64_to_cpu(resa->current_lsn);
+ memcpy(&log_header, rph,
+ sizeof(struct RESTART_PAGE_HEADER));
+ offs = le16_to_cpu(log_header.restart_offset);
+ memcpy(&restart, &data[offs],
+ sizeof(struct RESTART_AREA));
+ offs += le16_to_cpu(restart.client_array_offset);
+ memcpy(&client, &data[offs],
+ sizeof(struct RESTART_CLIENT));
+ 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) {
+ struct RESTART_PAGE_HEADER *rph;
+
+ rph = (struct 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.head.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_le64(restart_lsn);
+ memcpy(buffer, &log_header,
+ sizeof(struct RESTART_PAGE_HEADER));
+ off = le16_to_cpu(log_header.restart_offset);
+ memcpy(&buffer[off], &restart,
+ sizeof(struct RESTART_AREA));
+ off += le16_to_cpu(restart.client_array_offset);
+ memcpy(&buffer[off], &client,
+ sizeof(struct RESTART_CLIENT));
+ 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;
+ }
+ 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 struct RECORD_PAGE_HEADER *head;
+ const struct 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 = le64_to_cpu(althead->last_end_lsn)
+ - le64_to_cpu(head->last_end_lsn);
+ if (diff > 0)
+ best = altbuf;
+ else
+ best = buf;
+ }
+ if (best && (best->block.record.head.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 struct RESTART_PAGE_HEADER *rph;
+ const struct RESTART_AREA *rest;
+ BOOL ok;
+ u32 off;
+ s64 size;
+
+ ok = FALSE;
+ fseek(ctx->file,0L,2);
+ size = ftell(ctx->file);
+ rph = (const struct RESTART_PAGE_HEADER*)boot;
+ off = le16_to_cpu(rph->restart_offset);
+ rest = (const struct 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;
+ 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 struct RESTART_AREA *rest;
+ BOOL ok;
+
+ ok = FALSE;
+ rest = (const struct 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");
+ getlogfiledata(ctx, boot);
+ ok = TRUE;
+ } 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");
+ }
+ 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);
+ }
+ 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);
+ }
+ }
+ 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 struct LOG_RECORD *logr;
+ char *fullrec;
+ s32 size;
+ int space;
+ int nextspace;
+ TRISTATE state;
+ u16 blkheadsz;
+
+ logr = (const struct LOG_RECORD*)&data[k];
+ state = T_ERR;
+ size = le32_to_cpu(logr->client_data_length) + LOG_RECORD_HEAD_SZ;
+ space = blocksz - k;
+ blkheadsz = sizeof(struct 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((struct 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, (struct LOG_RECORD*)fullrec);
+ if (optp || optu || opts)
+ state = enqueue_action(ctx,
+ (struct 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(struct LOG_RECORD) */
+ const struct RECORD_PAGE_HEADER *rph;
+ const struct RECORD_PAGE_HEADER *prevrph;
+ const struct 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 = (struct RECORD_PAGE_HEADER*)NULL;
+ if (prevbuf)
+ prevrph = &prevbuf->block.record;
+ data = buf->block.data;
+ nextdata = nextbuf->block.data;
+ if (rph && (rph->head.magic == magic_RCRD)
+ && (!prevrph || (prevrph->head.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 struct 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 struct 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 struct 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)) {
+ state = backoverlap(ctx, blk,
+ data, nextdata, k);
+ }
+ }
+ for (j=cnt-1; (j>=0) && (state==T_OK); j--) {
+ k = poslist[j];
+ logr = (const struct 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.head.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 struct 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.head.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 (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)(le64_to_cpu(rph->last_end_lsn)
+ - committed_lsn) > 0) {
+ committed_lsn = le64_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.head.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-2015 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");
+ fprintf(stderr," Copyright (c) 2012-2015 Jean-Pierre Andre\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(struct RECORD_PAGE_HEADER) != 40) {
+ fprintf(stderr,
+ "* error : bad sizeof(struct RECORD_PAGE_HEADER) %d\n",
+ (int)sizeof(struct RECORD_PAGE_HEADER));
+ ok = FALSE;
+ }
+ if (sizeof(struct LOG_RECORD) != 88) {
+ fprintf(stderr,
+ "* error : bad sizeof(struct LOG_RECORD) %d\n",
+ (int)sizeof(struct LOG_RECORD));
+ ok = FALSE;
+ }
+ if (sizeof(struct RESTART_PAGE_HEADER) != 32) {
+ fprintf(stderr,
+ "* error : bad sizeof(struct RESTART_PAGE_HEADER) %d\n",
+ (int)sizeof(struct RESTART_PAGE_HEADER));
+ ok = FALSE;
+ }
+ if (sizeof(struct RESTART_AREA) != 44) {
+ fprintf(stderr,
+ "* error : bad sizeof(struct RESTART_AREA) %d\n",
+ (int)sizeof(struct RESTART_AREA));
+ ok = FALSE;
+ }
+ if (sizeof(struct ATTR_OLD) != 44) {
+ fprintf(stderr,
+ "* error : bad sizeof(struct ATTR_OLD) %d\n",
+ (int)sizeof(struct ATTR_OLD));
+ ok = FALSE;
+ }
+ if (sizeof(struct ATTR_NEW) != 40) {
+ fprintf(stderr,
+ "* error : bad sizeof(struct ATTR_NEW) %d\n",
+ (int)sizeof(struct 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);
+}
--- /dev/null
+/*
+ * Redo or undo a list of logged actions
+ *
+ * Copyright (c) 2014-2015 Jean-Pierre Andre
+ *
+ */
+
+/*
+ * 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
+ */
+
+#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_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 "ntfsrecover.h"
+#include "misc.h"
+
+struct STORE {
+ struct STORE *upper;
+ struct STORE *lower;
+ LCN lcn;
+ char data[1];
+} ;
+
+#define dump hexdump
+
+struct STORE *cluster_door = (struct STORE*)NULL;
+
+/* check whether a MFT or INDX record is older than action */
+#define older_record(rec, logr) ((s64)(le64_to_cpu((rec)->lsn) \
+ - le64_to_cpu((logr)->this_lsn)) < 0)
+/* check whether a MFT or INDX record is newer than action */
+#define newer_record(rec, logr) ((s64)(le64_to_cpu((rec)->lsn) \
+ - le64_to_cpu((logr)->this_lsn)) > 0)
+
+/*
+ * A few functions for debugging
+ */
+
+static int matchcount(const char *d, const char *s, int n)
+{
+ int m;
+
+ m = 0;
+ while ((--n >= 0) && (*d++ == *s++)) m++;
+ return (m);
+}
+
+/*
+static void locate(const char *s, int n, const char *p, int m)
+{
+ int i,j;
+
+ for (i=0; i<=(n - m); i++)
+ if (s[i] == *p) {
+ j = 1;
+ while ((j < m) && (s[i + j] == p[j]))
+ j++;
+ if (j == m)
+ printf("=== found at offset 0x%x %d\n",i,i);
+ }
+}
+*/
+
+static u64 inode_number(const struct LOG_RECORD *logr)
+{
+ u64 offset;
+
+ offset = ((u64)le32_to_cpu(logr->target_vcn)
+ << clusterbits)
+ + ((u32)le16_to_cpu(logr->cluster_index)
+ << NTFS_BLOCK_SIZE_BITS);
+ return (offset >> mftrecbits);
+}
+
+/*
+ * Find an in-memory copy of a needed cluster
+ *
+ * Optionally, allocate a copy.
+ */
+
+static struct STORE *getclusterentry(LCN lcn, BOOL create)
+{
+ struct STORE **current;
+ struct STORE *newone;
+
+ current = &cluster_door;
+ /* A minimal binary tree should be enough */
+ while (*current && (lcn != (*current)->lcn)) {
+ if (lcn > (*current)->lcn)
+ current = &(*current)->upper;
+ else
+ current = &(*current)->lower;
+ }
+ if (create && !*current) {
+ newone = (struct STORE*)malloc(sizeof(struct STORE)
+ + clustersz);
+ if (newone) {
+ newone->upper = (struct STORE*)NULL;
+ newone->lower = (struct STORE*)NULL;
+ newone->lcn = lcn;
+ *current = newone;
+ }
+ }
+ return (*current);
+}
+
+void freeclusterentry(struct STORE *entry)
+{
+ if (!entry) {
+ if (cluster_door)
+ freeclusterentry(cluster_door);
+ cluster_door = (struct STORE*)NULL;
+ } else {
+ if (optv)
+ printf("* cluster 0x%llx %s updated\n",
+ (long long)entry->lcn,
+ (optn ? "would be" : "was"));
+ if (entry->upper)
+ freeclusterentry(entry->upper);
+ if (entry->lower)
+ freeclusterentry(entry->lower);
+ free(entry);
+ }
+}
+
+/*
+ * Check whether an attribute type is a valid one
+ */
+
+static BOOL valid_type(ATTR_TYPES type)
+{
+ BOOL ok;
+
+ switch (type) {
+ case AT_STANDARD_INFORMATION :
+ case AT_ATTRIBUTE_LIST :
+ case AT_FILE_NAME :
+ case AT_OBJECT_ID :
+ case AT_SECURITY_DESCRIPTOR :
+ case AT_VOLUME_NAME :
+ case AT_VOLUME_INFORMATION :
+ case AT_DATA :
+ case AT_INDEX_ROOT :
+ case AT_INDEX_ALLOCATION :
+ case AT_BITMAP :
+ case AT_REPARSE_POINT :
+ case AT_EA_INFORMATION :
+ case AT_EA :
+ case AT_PROPERTY_SET :
+ case AT_LOGGED_UTILITY_STREAM :
+ case AT_FIRST_USER_DEFINED_ATTRIBUTE :
+ case AT_END :
+ ok = TRUE;
+ break;
+ default :
+ ok = FALSE;
+ break;
+ }
+ return (ok);
+}
+
+/*
+ * Rough check of sanity of an index list
+ */
+
+static int sanity_indx_list(const char *buffer, u32 k, u32 end)
+{
+ le64 inode;
+ int err;
+ int lth;
+ BOOL done;
+
+ err = 0;
+ done = FALSE;
+ while ((k <= end) && !done) {
+ lth = getle16(buffer,k+8);
+ if (optv > 1)
+ /* Usual indexes can be determined from size */
+ switch (lth) {
+ case 16 : /* final without subnode */
+ case 24 : /* final with subnode */
+ printf("index to none lth 0x%x"
+ " flags 0x%x pos 0x%x\n",
+ (int)lth,
+ (int)getle16(buffer,k+12),(int)k);
+ break;
+ case 32 : /* $R in $Reparse */
+ /* Badly aligned */
+ memcpy(&inode, &buffer[k + 20], 8);
+ printf("index to reparse of 0x%016llx lth 0x%x"
+ " flags 0x%x pos 0x%x\n",
+ (long long)le64_to_cpu(inode),
+ (int)lth,
+ (int)getle16(buffer,k+12),(int)k);
+ break;
+ case 40 : /* $SII in $Secure */
+ printf("index to securid 0x%lx lth 0x%x"
+ " flags 0x%x pos 0x%x\n",
+ (long)getle32(buffer,k + 16),
+ (int)lth,
+ (int)getle16(buffer,k+12),(int)k);
+ break;
+ case 48 : /* $SDH in $Secure */
+ printf("index to securid 0x%lx lth 0x%x"
+ " flags 0x%x pos 0x%x\n",
+ (long)getle32(buffer,k + 20),
+ (int)lth,
+ (int)getle16(buffer,k+12),(int)k);
+ break;
+ default : /* at least 80 */
+ printf("index to inode 0x%016llx lth 0x%x"
+ " flags 0x%x pos 0x%x\n",
+ (long long)getle64(buffer,k),
+ (int)lth,
+ (int)getle16(buffer,k+12),(int)k);
+ }
+ done = (feedle16(buffer,k+12) & INDEX_ENTRY_END) || !lth;
+ k += lth;
+ }
+ if (k != end) {
+ printf("** Bad index record length %ld (computed %ld)\n",
+ (long)end, (long)k);
+ err = 1;
+ }
+ if (!done) {
+ printf("** Missing end of index mark\n");
+ err = 1;
+ }
+ return (err);
+}
+
+/*
+ * Rough check of sanity of an mft record
+ */
+
+static int sanity_mft(const char *buffer)
+{
+ const MFT_RECORD *record;
+ const ATTR_RECORD *attr;
+ u64 instances;
+ u32 k;
+ u32 type;
+ u32 prevtype;
+ u16 nextinstance;
+ u16 instance;
+ int err;
+
+ err = 0;
+ record = (const MFT_RECORD*)buffer;
+ nextinstance = le16_to_cpu(record->next_attr_instance);
+ instances = 0;
+ k = le16_to_cpu(record->attrs_offset);
+ attr = (const ATTR_RECORD*)&buffer[k];
+ prevtype = 0;
+ while ((k < mftrecsz)
+ && (attr->type != AT_END)
+ && valid_type(attr->type)) {
+ type = le32_to_cpu(attr->type);
+ if (type < prevtype) {
+ printf("** Bad type ordering 0x%lx after 0x%lx\n",
+ (long)type, (long)prevtype);
+ err = 1;
+ }
+ instance = le16_to_cpu(attr->instance);
+ /* Can nextinstance wrap around ? */
+ if (instance >= nextinstance) {
+ printf("** Bad attr instance %d (max %d)\n",
+ (int)instance, (int)nextinstance - 1);
+ err = 1;
+ }
+ if (instance < 64) {
+ /* Only check up to 64 */
+ if (((u64)1 << instance) & instances) {
+ printf("** Duplicated attr instance %d\n",
+ (int)instance);
+ }
+ instances |= (u64)1 << instance;
+ }
+ if (optv > 1) {
+ if ((attr->type == AT_FILE_NAME)
+ && buffer[k + 88]) {
+ printf("attr %08lx offs 0x%x nres %d",
+ (long)type, (int)k,
+ (int)attr->non_resident);
+ showname(" ",&buffer[k+90],
+ buffer[k + 88] & 255);
+ } else
+ printf("attr %08lx offs 0x%x nres %d\n",
+ (long)type, (int)k,
+ (int)attr->non_resident);
+ }
+ if ((attr->type == AT_INDEX_ROOT)
+ && sanity_indx_list(buffer,
+ k + le16_to_cpu(attr->value_offset) + 32,
+ k + le32_to_cpu(attr->length))) {
+ err = 1;
+ }
+ k += le32_to_cpu(attr->length);
+ attr = (const ATTR_RECORD*)&buffer[k];
+ prevtype = type;
+ }
+ if ((optv > 1) && (attr->type == AT_END))
+ printf("attr %08lx offs 0x%x\n",
+ (long)le32_to_cpu(attr->type), (int)k);
+ if ((attr->type != AT_END)
+ || (le32_to_cpu(record->bytes_in_use) != (k + 8))
+ || (le32_to_cpu(record->bytes_allocated) < (k + 8))) {
+ printf("** Bad MFT record length %ld"
+ " (computed %ld allocated %ld)\n",
+ (long)le32_to_cpu(record->bytes_in_use),
+ (long)(k + 8),
+ (long)le32_to_cpu(record->bytes_allocated));
+ err = 1;
+ }
+ return (err);
+}
+
+/*
+ * Rough check of sanity of an index block
+ */
+
+static int sanity_indx(ntfs_volume *vol, const char *buffer)
+{
+ const INDEX_BLOCK *indx;
+ u32 k;
+ int err;
+
+ err = 0;
+ indx = (const INDEX_BLOCK*)buffer;
+ k = offsetof(INDEX_BLOCK, index) +
+ le32_to_cpu(indx->index.entries_offset);
+ err = sanity_indx_list(buffer, k,
+ le32_to_cpu(indx->index.index_length) + 24);
+ if ((le32_to_cpu(indx->index.index_length)
+ > le32_to_cpu(indx->index.allocated_size))
+ || (le32_to_cpu(indx->index.allocated_size)
+ != (vol->indx_record_size - 24))) {
+ printf("** Bad index length %ld"
+ " (usable %ld allocated %ld)\n",
+ (long)le32_to_cpu(indx->index.index_length),
+ (long)(vol->indx_record_size - 24),
+ (long)le32_to_cpu(indx->index.allocated_size));
+ err = 1;
+ }
+ return (err);
+}
+
+
+/*
+ * Allocate a buffer and read a full set of raw clusters
+ *
+ * Do not use for accessing $LogFile.
+ * With option -n reading is first attempted from the memory store
+ */
+
+static char *read_raw(ntfs_volume *vol, const struct LOG_RECORD *logr)
+{
+ char *buffer;
+ char *target;
+ struct STORE *store;
+ LCN lcn;
+ int count;
+ int i;
+ BOOL fail;
+
+ count = le16_to_cpu(logr->lcns_to_follow);
+ if (!count) {
+ printf("** Error : no lcn to read from\n");
+ buffer = (char*)NULL;
+ } else
+ buffer = (char*)malloc(clustersz*count);
+// TODO error messages
+ if (buffer) {
+ fail = FALSE;
+ for (i=0; (i<count) && !fail; i++) {
+ store = (struct STORE*)NULL;
+ lcn = le64_to_cpu(logr->lcn_list[i]);
+ target = buffer + clustersz*i;
+ if (optn) {
+ store = getclusterentry(lcn, FALSE);
+ if (store) {
+ memcpy(target, store->data, clustersz);
+ if (optv)
+ printf("== lcn 0x%llx from store\n",
+ (long long)lcn);
+ if ((optv > 1) && optc
+ && within_lcn_range(logr))
+ dump(store->data, clustersz);
+ }
+ }
+ if (!store
+ && (ntfs_pread(vol->dev, lcn << clusterbits,
+ clustersz, target) != clustersz)) {
+ fail = TRUE;
+ } else {
+ if (!store) {
+ if (optv)
+ printf("== lcn 0x%llx"
+ " from device\n",
+ (long long)lcn);
+ if ((optv > 1) && optc
+ && within_lcn_range(logr))
+ dump(target, clustersz);
+ }
+ }
+ }
+ if (fail) {
+ printf("** Could not read cluster 0x%llx\n",
+ (long long)lcn);
+ free(buffer);
+ buffer = (char*)NULL;
+ }
+ }
+ return (buffer);
+}
+
+/*
+ * Write a full set of raw clusters
+ *
+ * Do not use for accessing $LogFile.
+ * With option -n a copy of the buffer is kept in memory for later use.
+ */
+
+static int write_raw(ntfs_volume *vol, const struct LOG_RECORD *logr,
+ char *buffer)
+{
+ int err;
+ struct STORE *store;
+ LCN lcn;
+ char *source;
+ int count;
+ int i;
+
+ err = 0;
+ count = le16_to_cpu(logr->lcns_to_follow);
+ if (!count)
+ printf("** Error : no lcn to write to\n");
+ if (optn) {
+ for (i=0; (i<count) && !err; i++) {
+ lcn = le64_to_cpu(logr->lcn_list[i]);
+ source = buffer + clustersz*i;
+ store = getclusterentry(lcn, TRUE);
+ if (store) {
+ memcpy(store->data, source, clustersz);
+ if (optv)
+ printf("== lcn 0x%llx to store\n",
+ (long long)lcn);
+ if ((optv > 1) && optc
+ && within_lcn_range(logr))
+ dump(store->data, clustersz);
+ } else {
+ printf("** Could not store cluster 0x%llx\n",
+ (long long)lcn);
+ err = 1;
+ }
+ }
+ } else {
+ for (i=0; (i<count) && !err; i++) {
+ lcn = le64_to_cpu(logr->lcn_list[i]);
+ if (optv)
+ printf("== lcn 0x%llx to device\n",
+ (long long)lcn);
+ source = buffer + clustersz*i;
+ if (ntfs_pwrite(vol->dev, lcn << clusterbits,
+ clustersz, source) != clustersz) {
+ printf("** Could not write cluster 0x%llx\n",
+ (long long)lcn);
+ err = 1;
+ }
+ }
+ }
+ return (err);
+}
+
+/*
+ * Write a full set of raw clusters to mft_mirr
+ */
+
+static int write_mirr(ntfs_volume *vol, const struct LOG_RECORD *logr,
+ char *buffer)
+{
+ int err;
+ LCN lcn;
+ char *source;
+ int count;
+ int i;
+
+ err = 0;
+ count = le16_to_cpu(logr->lcns_to_follow);
+ if (!count)
+ printf("** Error : no lcn to write to\n");
+ if (!optn) {
+ for (i=0; (i<count) && !err; i++) {
+ lcn = ntfs_attr_vcn_to_lcn(vol->mftmirr_na,
+ le32_to_cpu(logr->target_vcn) + i);
+ source = buffer + clustersz*i;
+ if ((lcn < 0)
+ || (ntfs_pwrite(vol->dev, lcn << clusterbits,
+ clustersz, source) != clustersz)) {
+ printf("** Could not write cluster 0x%llx\n",
+ (long long)lcn);
+ err = 1;
+ }
+ }
+ }
+ return (err);
+}
+
+/*
+ * Allocate a buffer and read a single protected record
+ */
+
+static char *read_protected(ntfs_volume *vol, const struct LOG_RECORD *logr,
+ u32 size, BOOL warn)
+{
+ char *buffer;
+ char *full;
+ u32 pos;
+ LCN lcn;
+
+ /* read full clusters */
+ buffer = read_raw(vol, logr);
+ /*
+ * if the record is smaller than a cluster,
+ * make a partial copy and free the full buffer
+ */
+ if (buffer && (size < clustersz)) {
+ full = buffer;
+ buffer = (char*)malloc(size);
+ if (buffer) {
+ pos = le16_to_cpu(logr->cluster_index)
+ << NTFS_BLOCK_SIZE_BITS;
+ memcpy(buffer, full + pos, size);
+ }
+ free(full);
+ }
+ if (buffer && (ntfs_mst_post_read_fixup_warn(
+ (NTFS_RECORD*)buffer, size, FALSE) < 0)) {
+ if (warn) {
+ lcn = le64_to_cpu(logr->lcn_list[0]);
+ printf("** Invalid protected record at 0x%llx"
+ " index %d\n",
+ (long long)lcn,
+ (int)le16_to_cpu(logr->cluster_index));
+ }
+ free(buffer);
+ buffer = (char*)NULL;
+ }
+ return (buffer);
+}
+
+/*
+ * Protect a single record, write, and deallocate the buffer
+ *
+ * With option -n a copy of the buffer is kept in protected form in
+ * memory for later use.
+ * As the store only knows about clusters, if the record is smaller
+ * than a cluster, have to read, merge and write.
+ */
+
+static int write_protected(ntfs_volume *vol, const struct LOG_RECORD *logr,
+ char *buffer, u32 size)
+{
+ MFT_RECORD *record;
+ INDEX_BLOCK *indx;
+ char *full;
+ u32 pos;
+ BOOL mftmirr;
+ BOOL checked;
+ int err;
+
+ err = 0;
+ mftmirr = FALSE;
+ checked = FALSE;
+ if ((size == mftrecsz) && !memcmp(buffer,"FILE",4)) {
+ record = (MFT_RECORD*)buffer;
+ if (optv)
+ printf("update inode %ld lsn 0x%llx"
+ " (record %s than action 0x%llx)\n",
+ (long)le32_to_cpu(record->mft_record_number),
+ (long long)le64_to_cpu(record->lsn),
+ ((s64)(le64_to_cpu(record->lsn)
+ - le64_to_cpu(logr->this_lsn)) < 0 ?
+ "older" : "newer"),
+ (long long)le64_to_cpu(logr->this_lsn));
+ if (optv > 1)
+ printf("mft vcn %ld index %d\n",
+ (long)le32_to_cpu(logr->target_vcn),
+ (int)le16_to_cpu(logr->cluster_index));
+ err = sanity_mft(buffer);
+ /* Should set to some previous lsn for undos */
+ if (opts)
+ record->lsn = logr->this_lsn;
+ /* Duplicate on mftmirr if not overflowing its size */
+ mftmirr = (((u64)le32_to_cpu(logr->target_vcn)
+ + le16_to_cpu(logr->lcns_to_follow))
+ << clusterbits)
+ <= (((u64)vol->mftmirr_size) << mftrecbits);
+ checked = TRUE;
+ }
+ if ((size == vol->indx_record_size) && !memcmp(buffer,"INDX",4)) {
+ indx = (INDEX_BLOCK*)buffer;
+ if (optv)
+ printf("update index lsn 0x%llx"
+ " (index %s than action 0x%llx)\n",
+ (long long)le64_to_cpu(indx->lsn),
+ ((s64)(le64_to_cpu(indx->lsn)
+ - le64_to_cpu(logr->this_lsn)) < 0 ?
+ "older" : "newer"),
+ (long long)le64_to_cpu(logr->this_lsn));
+ err = sanity_indx(vol, buffer);
+ /* Should set to some previous lsn for undos */
+ if (opts)
+ indx->lsn = logr->this_lsn;
+ checked = TRUE;
+ }
+ if (!checked) {
+ printf("** Error : writing protected record of unknown type\n");
+ err = 1;
+ }
+ if (!err) {
+ if (!ntfs_mst_pre_write_fixup((NTFS_RECORD*)buffer, size)) {
+ /*
+ * If the record is smaller than a cluster, get a full
+ * cluster, merge and write.
+ */
+ if (size < clustersz) {
+ full = read_raw(vol, logr);
+ if (full) {
+ pos = le16_to_cpu(logr->cluster_index)
+ << NTFS_BLOCK_SIZE_BITS;
+ memcpy(full + pos, buffer, size);
+ err = write_raw(vol, logr, full);
+ if (!err && mftmirr && !optn)
+ err = write_mirr(vol, logr,
+ full);
+ free(full);
+ }
+ } else {
+ /* write full clusters */
+ err = write_raw(vol, logr, buffer);
+ if (!err && mftmirr && !optn)
+ err = write_mirr(vol, logr, buffer);
+ }
+ } else {
+ printf("** Failed to protect record\n");
+ err = 1;
+ }
+ }
+ return (err);
+}
+
+/*
+ * Resize attribute records
+ *
+ * The attribute value is resized to new size, but the attribute
+ * and MFT record must be kept aligned to 8 bytes.
+ */
+
+static int resize_attribute(MFT_RECORD *entry, ATTR_RECORD *attr, INDEX_ROOT *index,
+ int rawresize, int resize)
+{
+ int err;
+ u32 newlength;
+ u32 newused;
+ u32 newvalue;
+ u32 indexlth;
+ u32 indexalloc;
+
+ err = 0;
+ if (attr) {
+ newvalue = le32_to_cpu(attr->value_length) + rawresize;
+ attr->value_length = cpu_to_le32(newvalue);
+ newlength = le32_to_cpu(attr->length) + resize;
+ attr->length = cpu_to_le32(newlength);
+ }
+ if (entry) {
+ newused = le32_to_cpu(entry->bytes_in_use) + resize;
+ entry->bytes_in_use = cpu_to_le32(newused);
+ }
+ if (index) {
+ indexlth = le32_to_cpu(index->index.index_length) + resize;
+ index->index.index_length = cpu_to_le32(indexlth);
+ indexalloc = le32_to_cpu(index->index.allocated_size) + resize;
+ index->index.allocated_size = cpu_to_le32(indexalloc);
+ }
+ return (err);
+}
+
+/*
+ * Adjust the next attribute instance
+ *
+ * If a newly created attribute matches the next instance, then
+ * the next instance has to be incremented.
+ *
+ * Do the opposite when undoing an attribute creation, but
+ * do not change the next instance when deleting an attribute
+ * or undoing the deletion.
+ */
+
+static void adjust_instance(const ATTR_RECORD *attr, MFT_RECORD *entry, int increment)
+{
+ u16 instance;
+
+ if (increment > 0) {
+ /* Allocating a new instance ? */
+ if (attr->instance == entry->next_attr_instance) {
+ instance = (le16_to_cpu(entry->next_attr_instance)
+ + 1) & 0xffff;
+ entry->next_attr_instance = cpu_to_le16(instance);
+ }
+ }
+ if (increment < 0) {
+ /* Freeing the latest instance ? */
+ instance = (le16_to_cpu(entry->next_attr_instance)
+ - 1) & 0xffff;
+ if (attr->instance == cpu_to_le16(instance))
+ entry->next_attr_instance = attr->instance;
+ }
+}
+
+/*
+ * Adjust the highest vcn according to mapping pairs
+ *
+ * The runlist has to be fully recomputed
+ */
+
+static int adjust_high_vcn(ntfs_volume *vol, ATTR_RECORD *attr)
+{
+ runlist_element *rl;
+ runlist_element *xrl;
+ VCN high_vcn;
+ int err;
+
+ err = 1;
+ attr->highest_vcn = cpu_to_le64(0);
+ rl = ntfs_mapping_pairs_decompress(vol, attr, (runlist_element*)NULL);
+ if (rl) {
+ xrl = rl;
+ while (xrl->length)
+ xrl++;
+ high_vcn = xrl->vcn - 1;
+ attr->highest_vcn = cpu_to_le64(high_vcn);
+ free(rl);
+ err = 0;
+ } else {
+ printf("** Failed to decompress the runlist\n");
+ dump((char*)attr,128);
+ }
+ return (err);
+}
+
+/*
+ * Check index match, to be used for undos only
+ *
+ * The action UpdateFileNameRoot updates the time stamps and/or the
+ * sizes, but the lsn is not updated in the index record.
+ * As a consequence such UpdateFileNameRoot are not always undone
+ * and the actual record does not fully match the undo data.
+ * We however accept the match if the parent directory and the name
+ * match.
+ * Alternate workaround : do not check the lsn when undoing
+ * UpdateFileNameRoot
+ */
+
+static BOOL index_match_undo(const char *first, const char *second, int length)
+{
+ int len;
+ BOOL match;
+
+ match = !memcmp(first, second, length);
+ if (!match) {
+ if (optv) {
+ printf("The existing index does not match :\n");
+ dump(second,length);
+ }
+ len = (first[80] & 255)*2 + 2;
+ match = (feedle64(first, 16) == feedle64(second, 16))
+ && !memcmp(first + 80, second + 80, len);
+ if (match && optv)
+ printf("However parent dir and name do match\n");
+ }
+ return (match);
+}
+
+
+/*
+ * Generic idempotent change to a resident attribute
+ */
+
+static int change_resident(ntfs_volume *vol, const struct ACTION_RECORD *action,
+ char *buffer, const char *data, u32 target, u32 length)
+{
+ LCN lcn;
+ ATTR_RECORD *attr;
+ u32 attrend;
+ int err;
+ int changed;
+
+ err = 1;
+ if (action->record.undo_length != action->record.redo_length)
+ printf("** Error size change in change_resident\n");
+ if (optv > 1) {
+ lcn = le64_to_cpu(action->record.lcn_list[0]);
+ printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
+ (long long)inode_number(&action->record),
+ (long long)lcn, (int)target, (int)length);
+ }
+ attr = (ATTR_RECORD*)(buffer
+ + le16_to_cpu(action->record.record_offset));
+ if (optv > 1) {
+ printf("-> existing record :\n");
+ dump(&buffer[target], length);
+ printf("-> full MFT record :\n");
+ dump(buffer,mftrecsz);
+ }
+ attrend = le16_to_cpu(action->record.record_offset)
+ + le32_to_cpu(attr->length);
+ if ((target + length) > attrend) {
+ printf("** Error : update overflows from attribute\n");
+ }
+ if (!(length & 7)
+ && ((target + length) <= attrend)
+ && (attrend <= mftrecsz)
+ && !sanity_mft(buffer)) {
+ changed = memcmp(buffer + target, data, length);
+ err = 0;
+ if (changed) {
+ memcpy(buffer + target, data, length);
+ if (optv > 1) {
+ printf("-> new record :\n");
+ dump(buffer + target, length);
+ }
+ err = write_protected(vol, &action->record,
+ buffer, mftrecsz);
+ }
+ if (optv > 1) {
+ printf("-> MFT record %s\n",
+ (changed ? "updated" : "unchanged"));
+ }
+ }
+ return (err);
+}
+
+static int change_resident_expect(ntfs_volume *vol, const struct ACTION_RECORD *action,
+ char *buffer, const char *data, const char *expected,
+ u32 target, u32 length, ATTR_TYPES type)
+{
+ LCN lcn;
+ ATTR_RECORD *attr;
+ int err;
+ BOOL found;
+
+ err = 1;
+ if (action->record.undo_length != action->record.redo_length)
+ printf("** Error size change in change_resident\n");
+ if (optv > 1) {
+ lcn = le64_to_cpu(action->record.lcn_list[0]);
+ printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
+ (long long)inode_number(&action->record),
+ (long long)lcn, (int)target, (int)length);
+ }
+ attr = (ATTR_RECORD*)(buffer
+ + le16_to_cpu(action->record.record_offset));
+ if (optv > 1) {
+ printf("-> existing record :\n");
+ dump(&buffer[target], length);
+ printf("-> full record :\n");
+ dump((char*)attr, le32_to_cpu(attr->length));
+ }
+ if ((attr->type == type)
+ && !(length & 7)
+ && ((target + length) <= mftrecsz)) {
+ found = !memcmp(buffer + target, expected, length);
+ err = 0;
+ if (found) {
+ memcpy(buffer + target, data, length);
+ if (optv > 1) {
+ printf("-> new record :\n");
+ dump(buffer + target, length);
+ }
+ err = write_protected(vol, &action->record,
+ buffer, mftrecsz);
+ }
+ if (optv > 1) {
+ printf("-> MFT record %s\n",
+ (found ? "updated" : "unchanged"));
+ }
+ }
+ return (err);
+}
+
+/*
+ * Generic idempotent change to a an index value
+ *
+ */
+
+static int change_index_value(ntfs_volume *vol, const struct ACTION_RECORD *action,
+ char *buffer, const char *data, u32 target, u32 length)
+{
+ LCN lcn;
+ u32 count;
+ u32 xsize;
+ int changed;
+ int err;
+
+ err = 1;
+ count = le16_to_cpu(action->record.lcns_to_follow);
+ if (optv > 1) {
+ lcn = le64_to_cpu(action->record.lcn_list[0]);
+ printf("-> lcn 0x%llx target 0x%x length %d\n",
+ (long long)lcn, (int)target, (int)length);
+ }
+ xsize = vol->indx_record_size;
+ if (optv > 1) {
+ printf("-> existing record :\n");
+ dump(&buffer[target], length);
+ }
+ if ((target + length) <= (count << clusterbits)) {
+ changed = memcmp(buffer + target, data, length);
+ err = 0;
+ if (changed) {
+ memcpy(buffer + target, data, length);
+ if (optv > 1) {
+ printf("-> new record :\n");
+ dump(buffer + target, length);
+ }
+ err = write_protected(vol, &action->record,
+ buffer, xsize);
+ }
+ if (optv > 1) {
+ printf("-> data record %s\n",
+ (changed ? "updated" : "unchanged"));
+ }
+ }
+ return (err);
+}
+
+/*
+ * Add one or more resident attributes
+ */
+
+static int add_resident(ntfs_volume *vol, const struct ACTION_RECORD *action,
+ char *buffer, const char *data, u32 target,
+ u32 length, u32 oldlength)
+{
+ LCN lcn;
+ MFT_RECORD *entry;
+ int err;
+ BOOL found;
+ int resize;
+
+ err = 1;
+ if (optv > 1) {
+ lcn = le64_to_cpu(action->record.lcn_list[0]);
+ printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
+ (long long)inode_number(&action->record),
+ (long long)lcn, (int)target, (int)length);
+ }
+ entry = (MFT_RECORD*)buffer;
+ resize = length - oldlength;
+ if (optv > 1) {
+ printf("existing data :\n");
+ dump(buffer + target,length);
+ }
+ if (!(length & 7)
+ && !(oldlength & 7)
+ && ((target + length) <= mftrecsz)) {
+ /* This has to be an idempotent action */
+ err = 0;
+ if (data && length)
+ found = !memcmp(buffer + target,
+ data, length);
+ else {
+ found = TRUE;
+ err = 1;
+ }
+ if (!found && !err) {
+ /* Make space to insert the entry */
+ memmove(buffer + target + resize,
+ buffer + target,
+ mftrecsz - target - resize);
+ if (data)
+ memcpy(buffer + target, data, length);
+ else
+ memset(buffer + target, 0, length);
+ resize_attribute(entry, NULL, NULL,
+ resize, resize);
+ if (optv > 1) {
+ printf("new data at same location :\n");
+ dump(buffer + target, length);
+ }
+ err = write_protected(vol, &action->record,
+ buffer, mftrecsz);
+ }
+ if (optv > 1) {
+ printf("-> MFT record %s\n",
+ (found ? "unchanged" : "expanded"));
+ }
+ }
+ return (err);
+}
+
+/*
+ * Add one or more non-resident records
+ */
+
+static int delete_non_resident(void /*ntfs_volume *vol,
+ const struct ACTION_RECORD *action,
+ const char *data, u32 target, u32 length, u32 oldlength*/)
+{
+ int err;
+
+ err = 1;
+ printf("** delete_non_resident() not implemented\n");
+ return (err);
+}
+
+/*
+ * Expand a single resident attribute
+ */
+
+static int expand_resident(ntfs_volume *vol, const struct ACTION_RECORD *action,
+ char *buffer, const char *data, u32 target,
+ u32 length, u32 oldlength)
+{
+ LCN lcn;
+ ATTR_RECORD *attr;
+ MFT_RECORD *entry;
+ int err;
+ BOOL found;
+ int resize;
+ u16 base;
+
+ err = 1;
+ if (optv > 1) {
+ lcn = le64_to_cpu(action->record.lcn_list[0]);
+ printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
+ (long long)inode_number(&action->record),
+ (long long)lcn, (int)target, (int)length);
+ }
+ entry = (MFT_RECORD*)buffer;
+ attr = (ATTR_RECORD*)(buffer
+ + le16_to_cpu(action->record.record_offset));
+ if (optv > 1) {
+ printf("existing data :\n");
+ dump(buffer + target,length);
+ }
+ base = 24 + 2*attr->name_length;
+ resize = ((base + length - 1) | 7)
+ - ((base + oldlength - 1) | 7);
+ if ((target + length) <= mftrecsz) {
+ /* This has to be an idempotent action */
+// TODO This test is wrong !
+ found = le32_to_cpu(attr->value_length) == length;
+ if (found && data && length)
+ found = !memcmp(buffer + target, data, length);
+ err = 0;
+ if (!found) {
+ /* Make space to insert the entry */
+ memmove(buffer + target + resize,
+ buffer + target,
+ mftrecsz - target - resize);
+// TODO what to do if length is not a multiple of 8 ?
+ if (data)
+ memcpy(buffer + target, data, length);
+ else
+ memset(buffer + target, 0, length);
+ resize_attribute(entry, attr, NULL,
+ length - oldlength, resize);
+ if (optv > 1) {
+ printf("new data at same location :\n");
+ dump(buffer + target, length);
+ }
+ err = write_protected(vol, &action->record,
+ buffer, mftrecsz);
+ }
+ if (optv > 1) {
+ printf("-> MFT record %s\n",
+ (found ? "unchanged" : "expanded"));
+ }
+ }
+ return (err);
+}
+
+/*
+ * Add one or more non-resident records
+ */
+
+static int add_non_resident(void /*ntfs_volume *vol,
+ const struct ACTION_RECORD *action,
+ const char *data, u32 target, u32 length, u32 oldlength*/)
+{
+ int err;
+
+ printf("** add_non_resident() not implemented\n");
+ err = 0;
+ return (err);
+}
+
+/*
+ * Generic insert a new resident attribute
+ */
+
+static int insert_resident(ntfs_volume *vol, const struct ACTION_RECORD *action,
+ char *buffer, const char *data, u32 target,
+ u32 length)
+{
+ LCN lcn;
+ ATTR_RECORD *attr;
+ const ATTR_RECORD *newattr;
+ MFT_RECORD *entry;
+ u32 newused;
+ u16 links;
+ int err;
+ BOOL found;
+
+ err = 1;
+ if (optv > 1) {
+ lcn = le64_to_cpu(action->record.lcn_list[0]);
+ printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
+ (long long)inode_number(&action->record),
+ (long long)lcn, (int)target, (int)length);
+ }
+ entry = (MFT_RECORD*)buffer;
+ attr = (ATTR_RECORD*)(buffer
+ + le16_to_cpu(action->record.record_offset));
+ newattr = (const ATTR_RECORD*)data;
+ if (optv > 1) {
+ printf("existing record :\n");
+ dump(buffer + target,length);
+ if (le32_to_cpu(attr->type) < le32_to_cpu(newattr->type)) {
+ printf("** Bad attribute order, full record :\n");
+ dump(buffer, mftrecsz);
+ }
+ }
+ /* Types must be in ascending order */
+ if (valid_type(attr->type)
+ && (le32_to_cpu(attr->type)
+ >= le32_to_cpu(newattr->type))
+ && !(length & 7)
+ && ((target + length) <= mftrecsz)) {
+ /* This has to be an idempotent action */
+ found = !memcmp(buffer + target, data, length);
+ err = 0;
+ if (!found) {
+ /* Make space to insert the entry */
+ memmove(buffer + target + length,
+ buffer + target,
+ mftrecsz - target - length);
+ memcpy(buffer + target, data, length);
+ newused = le32_to_cpu(entry->bytes_in_use)
+ + length;
+ entry->bytes_in_use = cpu_to_le32(newused);
+ if (action->record.redo_operation
+ == const_cpu_to_le16(CreateAttribute)) {
+ /*
+ * For a real create, may have to adjust
+ * the next attribute instance
+ */
+ adjust_instance(newattr, entry, 1);
+ }
+ if (newattr->type == AT_FILE_NAME) {
+ links = le16_to_cpu(entry->link_count) + 1;
+ entry->link_count = cpu_to_le16(links);
+ }
+ if (optv > 1) {
+ printf("expanded record (now 0x%x"
+ " bytes used) :\n",
+ (int)newused);
+ dump(buffer + target, 2*length);
+ }
+ err = write_protected(vol, &action->record,
+ buffer, mftrecsz);
+ }
+ if (optv > 1) {
+ printf("-> MFT record %s\n",
+ (found ? "unchanged" : "expanded"));
+ }
+ }
+ return (err);
+}
+
+/*
+ * Generic remove a single resident attribute
+ */
+
+static int remove_resident(ntfs_volume *vol, const struct ACTION_RECORD *action,
+ char *buffer, const char *data, u32 target,
+ u32 length)
+{
+ LCN lcn;
+ ATTR_RECORD *attr;
+ MFT_RECORD *entry;
+ u32 newused;
+ u16 links;
+ int err;
+ BOOL found;
+
+ err = 1;
+ if (optv > 1) {
+ lcn = le64_to_cpu(action->record.lcn_list[0]);
+ printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
+ (long long)inode_number(&action->record),
+ (long long)lcn, (int)target, (int)length);
+ }
+ entry = (MFT_RECORD*)buffer;
+ attr = (ATTR_RECORD*)(buffer
+ + le16_to_cpu(action->record.record_offset));
+ if (optv > 1) {
+ printf("existing record :\n");
+ dump(buffer + target,length);
+ }
+ if (!(length & 7)
+ && ((target + length) <= mftrecsz)) {
+ /* This has to be an idempotent action */
+ /* For AT_DATA the value is not always present */
+ if (attr->type == AT_DATA)
+ found = !memcmp(buffer + target, data,
+ le16_to_cpu(attr->value_offset));
+ else
+ found = !memcmp(buffer + target, data, length);
+ if (!found && optv) {
+ printf("data 0x%lx 0x%lx offset %d %ld\n",
+ (long)le32_to_cpu(attr->type),
+ (long)le32_to_cpu(AT_DATA),
+ (int)offsetof(ATTR_RECORD, resident_end),
+ (long)le16_to_cpu(attr->value_offset));
+ printf("The existing record does not match (%d/%d)\n",
+ (int)matchcount(buffer + target, data,
+ length),(int)length);
+ dump(data,length);
+ printf("full attr :\n");
+ dump((const char*)attr,mftrecsz
+ - le16_to_cpu(action->record.record_offset));
+ }
+ err = 0;
+ if (found) {
+ if (attr->type == AT_FILE_NAME) {
+ links = le16_to_cpu(entry->link_count) - 1;
+ entry->link_count = cpu_to_le16(links);
+ }
+ if (action->record.redo_operation
+ == const_cpu_to_le16(CreateAttribute)) {
+ adjust_instance(attr, entry, -1);
+ }
+ /* Remove the entry */
+ memmove(buffer + target,
+ buffer + target + length,
+ mftrecsz - target - length);
+ newused = le32_to_cpu(entry->bytes_in_use) - length;
+ entry->bytes_in_use = cpu_to_le32(newused);
+ if (optv > 1) {
+ printf("new record at same location"
+ " (now 0x%x bytes used) :\n",
+ (int)newused);
+ dump(buffer + target, length);
+ }
+ err = write_protected(vol, &action->record,
+ buffer, mftrecsz);
+ }
+ if (optv > 1) {
+ printf("-> MFT record %s\n",
+ (found ? "shrinked" : "unchanged"));
+ }
+ }
+ return (err);
+}
+
+/*
+ * Delete one or more resident attributes
+ */
+
+static int delete_resident(ntfs_volume *vol, const struct ACTION_RECORD *action,
+ char *buffer, const char *data, u32 target,
+ u32 length, u32 oldlength)
+{
+ LCN lcn;
+ MFT_RECORD *entry;
+ int err;
+ BOOL found;
+ int resize;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ if (optv > 1) {
+ lcn = le64_to_cpu(action->record.lcn_list[0]);
+ printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
+ (long long)inode_number(&action->record),
+ (long long)lcn, (int)target, (int)length);
+ }
+ entry = (MFT_RECORD*)buffer;
+ if (optv > 1) {
+ printf("existing data :\n");
+ dump(buffer + target,length);
+ }
+ resize = length - oldlength;
+ if (!(length & 7)
+ && !(oldlength & 7)
+ && ((target + oldlength) <= mftrecsz)) {
+ /* This has to be an idempotent action */
+ err = 0;
+ if (data && length)
+ found = !memcmp(buffer + target, data, length);
+ else {
+ found = FALSE;
+ err = 1;
+ }
+ if (!found && !err) {
+ /* Remove the entry, if present */
+ memmove(buffer + target,
+ buffer + target - resize,
+ mftrecsz - target + resize);
+ resize_attribute(entry, NULL, NULL,
+ length - oldlength, resize);
+ if (optv > 1) {
+ printf("new data at same location :\n");
+ dump(buffer + target, length);
+ }
+ err = write_protected(vol, &action->record,
+ buffer, mftrecsz);
+ }
+ if (optv > 1) {
+ printf("-> MFT record %s\n",
+ (found ? "unchanged" : "shrinked"));
+ }
+ }
+ return (err);
+}
+
+static int shrink_resident(ntfs_volume *vol, const struct ACTION_RECORD *action,
+ char *buffer, const char *data, u32 target,
+ u32 length, u32 oldlength)
+{
+ LCN lcn;
+ ATTR_RECORD *attr;
+ MFT_RECORD *entry;
+ int err;
+ BOOL found;
+ int resize;
+ u16 base;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ if (optv > 1) {
+ lcn = le64_to_cpu(action->record.lcn_list[0]);
+ printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
+ (long long)inode_number(&action->record),
+ (long long)lcn, (int)target, (int)length);
+ }
+ entry = (MFT_RECORD*)buffer;
+ attr = (ATTR_RECORD*)(buffer
+ + le16_to_cpu(action->record.record_offset));
+ if (optv > 1) {
+ printf("existing data :\n");
+ dump(buffer + target,length);
+ }
+ base = 24 + 2*attr->name_length;
+ resize = ((base + length - 1) | 7)
+ - ((base + oldlength - 1) | 7);
+ if ((oldlength > length)
+// TODO limit to attr length
+ && ((target + oldlength) <= mftrecsz)) {
+ /* This has to be an idempotent action */
+ if (data && length)
+ found = !memcmp(buffer + target, data, length);
+ else
+{
+// TODO wrong : need checking against the old data, but in known cases
+// redo data is not available either and existing data is not zero.
+ found = FALSE;
+printf("* fake test, assuming not shrinked : value length %ld length %ld oldlength %ld\n",(long)le32_to_cpu(attr->value_length),(long)length,(long)oldlength);
+//dump(buffer + target, oldlength);
+}
+ err = 0;
+ if (!found) {
+ if (length) {
+ /* Relocate end of record */
+// TODO restrict to bytes_in_use
+ memmove(buffer + target + length,
+ buffer + target + oldlength,
+ mftrecsz - target - oldlength);
+ /* Insert new data or zeroes */
+ if (data)
+ memcpy(buffer + target, data, length);
+ else
+ memset(buffer + target, 0, length);
+ } else {
+ /* Remove the entry, unless targeted size */
+ memmove(buffer + target,
+ buffer + target - resize,
+ mftrecsz - target + resize);
+ }
+ resize_attribute(entry, attr, NULL,
+ length - oldlength, resize);
+ if (optv > 1) {
+ printf("new data at same location :\n");
+ dump(buffer + target, length);
+ }
+ err = write_protected(vol, &action->record,
+ buffer, mftrecsz);
+ }
+ if (optv > 1) {
+ printf("-> MFT record %s\n",
+ (found ? "unchanged" : "shrinked"));
+ }
+ }
+ return (err);
+}
+
+static int update_index(ntfs_volume *vol, const struct ACTION_RECORD *action,
+ char *buffer, const char *data, u32 target, u32 length)
+{
+ LCN lcn;
+ INDEX_BLOCK *indx;
+ u32 xsize;
+ BOOL changed;
+ int err;
+
+ err = 1;
+ if (optv > 1) {
+ lcn = le64_to_cpu(action->record.lcn_list[0]);
+ printf("-> lcn 0x%llx target 0x%x length %d\n",
+ (long long)lcn, (int)target, (int)length);
+ }
+ xsize = vol->indx_record_size;
+ indx = (INDEX_BLOCK*)(buffer
+ + le16_to_cpu(action->record.record_offset));
+ if (optv > 1) {
+ printf("-> existing index :\n");
+ dump(&buffer[target], length);
+ }
+ if ((indx->magic == magic_INDX)
+ && !(length & 7)
+ && ((target + length) <= xsize)) {
+ /* This has to be an idempotent action */
+ changed = memcmp(buffer + target, data, length);
+ err = 0;
+ if (changed) {
+ /* Update the entry */
+ memcpy(buffer + target, data, length);
+ if (optv > 1) {
+ printf("-> new index :\n");
+ dump(&buffer[target], length);
+ }
+ err = write_protected(vol, &action->record,
+ buffer, xsize);
+ }
+ if (optv > 1) {
+ printf("-> INDX record %s\n",
+ (changed ? "updated" : "unchanged"));
+ }
+ }
+ return (err);
+}
+
+/*
+ * Controversial deletion of file names, see undo_delete_file()
+ */
+
+static int delete_names(char *buffer)
+{
+ MFT_RECORD *record;
+ ATTR_RECORD *attr;
+ u32 used;
+ u32 pos;
+ int length;
+ int cnt;
+
+ record = (MFT_RECORD*)buffer;
+ pos = le16_to_cpu(record->attrs_offset);
+ used = le32_to_cpu(record->bytes_in_use);
+ cnt = 0;
+ do {
+ attr = (ATTR_RECORD*)&buffer[pos];
+ length = le32_to_cpu(attr->length);
+ if (attr->type == AT_FILE_NAME) {
+ if (optv)
+ showname("Controversial deletion of ",
+ &buffer[pos+90], buffer[pos+88] & 255);
+ memmove(buffer + pos, buffer + pos + length,
+ mftrecsz - pos - length);
+ used -= length;
+ cnt++;
+ } else
+ pos += length;
+ } while ((pos < used)
+ && (le32_to_cpu(attr->type) <= le32_to_cpu(AT_FILE_NAME)));
+ record->bytes_in_use = cpu_to_le32(used);
+ record->link_count = cpu_to_le16(0);
+ return (cnt ? 0 : 1);
+}
+
+static int rebuildname(const INDEX_ENTRY *index)
+{
+ ATTR_RECORD *attr;
+ int headlth;
+ int datalth;
+
+ datalth = le16_to_cpu(index->length)
+ - offsetof(INDEX_ENTRY,key.file_name);
+ headlth = offsetof(ATTR_RECORD,resident_end);
+ attr = (ATTR_RECORD*)malloc(headlth + datalth);
+ if (attr) {
+ attr->type = AT_FILE_NAME;
+ attr->length = cpu_to_le32(headlth + datalth);
+ attr->non_resident = 0;
+ attr->name_length = 0;
+ attr->name_offset = const_cpu_to_le16(0);
+ attr->flags = const_cpu_to_le16(0);
+ attr->instance = const_cpu_to_le16(0);
+ attr->value_length = cpu_to_le32(
+ 2*index->key.file_name.file_name_length
+ + offsetof(FILE_NAME_ATTR, file_name));
+ attr->value_offset = cpu_to_le16(headlth);
+ attr->resident_flags = RESIDENT_ATTR_IS_INDEXED;
+ memcpy(attr->resident_end, &index->key.file_name, datalth);
+ free(attr);
+ }
+ return (0);
+}
+
+/*
+ * Controversial creation of an index allocation attribute
+ *
+ * This is useful for turning the clock backward, but cannot
+ * work properly in the general case and must not be used for
+ * a real sync.
+ * The main problem is to synchronize the file names when an
+ * inode is reused with a different name.
+ */
+
+static int insert_index_allocation(ntfs_volume *vol, char *buffer, u32 offs)
+{
+ MFT_RECORD *record;
+ ATTR_RECORD *attr;
+ u32 used;
+ u32 pos;
+ u32 xsize;
+ u16 instance;
+ int length;
+ int addedlength;
+ int namelength;
+ int err;
+ static const unsigned char bitmap[] =
+ { 1, 0, 0, 0, 0, 0, 0, 0 } ;
+
+ err = 1;
+ if (opts) {
+ printf("** Call to unsupported insert_index_allocation()\n");
+ } else {
+ record = (MFT_RECORD*)buffer;
+ pos = le16_to_cpu(record->attrs_offset);
+ used = le32_to_cpu(record->bytes_in_use);
+ attr = (ATTR_RECORD*)&buffer[pos];
+ while ((pos < used)
+ && (le32_to_cpu(attr->type) < le32_to_cpu(AT_INDEX_ROOT))) {
+ pos += le32_to_cpu(attr->length);
+ attr = (ATTR_RECORD*)&buffer[pos];
+ }
+ length = le32_to_cpu(attr->length);
+ addedlength = length - 8 /* index allocation */
+ + length - 48; /* bitmap */
+ if ((attr->type == AT_INDEX_ROOT)
+ && ((pos + length) == offs)
+ && ((used + addedlength) < mftrecsz)) {
+ /* Make space for the attribute */
+ memmove(buffer + offs + addedlength, buffer + offs,
+ mftrecsz - offs - addedlength);
+ record->bytes_in_use = cpu_to_le32(used + addedlength);
+ /*
+ * Insert an AT_INDEX_ALLOCATION
+ */
+ attr = (ATTR_RECORD*)&buffer[offs];
+ attr->type = AT_INDEX_ALLOCATION;
+ attr->length = cpu_to_le32(length - 8);
+ attr->non_resident = 1;
+ namelength = buffer[pos + 9] & 255;
+ attr->name_length = namelength;
+ attr->name_offset = const_cpu_to_le16(0x40);
+ memcpy(buffer + offs + 0x40, buffer + pos + 0x18,
+ 2*namelength);
+ attr->flags = const_cpu_to_le16(0);
+ /* Should we really take a new instance ? */
+ attr->instance = record->next_attr_instance;
+ instance = le16_to_cpu(record->next_attr_instance) + 1;
+ record->next_attr_instance = cpu_to_le16(instance);
+ attr->lowest_vcn = const_cpu_to_le64(0);
+ attr->highest_vcn = const_cpu_to_le64(0);
+ attr->mapping_pairs_offset = cpu_to_le16(
+ 2*namelength + 0x40);
+ attr->compression_unit = 0;
+ xsize = vol->indx_record_size;
+ attr->allocated_size = cpu_to_le64(xsize);
+ attr->data_size = attr->allocated_size;
+ attr->initialized_size = attr->allocated_size;
+ /*
+ * Insert an AT_INDEX_BITMAP
+ */
+ attr = (ATTR_RECORD*)&buffer[offs + length - 8];
+ attr->type = AT_BITMAP;
+ attr->length = cpu_to_le32(length - 48);
+ attr->non_resident = 0;
+ namelength = buffer[pos + 9] & 255;
+ attr->name_length = namelength;
+ attr->name_offset = const_cpu_to_le16(0x18);
+ memcpy(buffer + offs + length - 8 + 0x18,
+ buffer + pos + 0x18, 2*namelength);
+ attr->flags = const_cpu_to_le16(0);
+ attr->value_length = const_cpu_to_le32(8);
+ attr->value_offset = cpu_to_le16(2*namelength + 24);
+ attr->resident_flags = 0;
+ memcpy((char*)attr->resident_end + 2*namelength,
+ bitmap, 8);
+ /* Should we really take a new instance ? */
+ attr->instance = record->next_attr_instance;
+ instance = le16_to_cpu(record->next_attr_instance) + 1;
+ record->next_attr_instance = cpu_to_le16(instance);
+ err = sanity_mft(buffer);
+ } else {
+ printf("** index root does not match\n");
+ err = 1;
+ }
+ }
+ return (err);
+}
+
+/*
+ * Check whether a full MFT record is fed by an action
+ *
+ * If so, checking the validity of existing record is pointless
+ */
+
+static BOOL check_full_mft(const struct ACTION_RECORD *action, BOOL redoing)
+{
+ const MFT_RECORD *record;
+ const ATTR_RECORD *attr;
+ u32 length;
+ u32 k;
+ BOOL ok;
+
+ if (redoing) {
+ record = (const MFT_RECORD*)((const char*)&action->record
+ + get_redo_offset(&action->record));
+ length = le16_to_cpu(action->record.redo_length);
+ } else {
+ record = (const MFT_RECORD*)((const char*)&action->record
+ + get_undo_offset(&action->record));
+ length = le16_to_cpu(action->record.undo_length);
+ }
+ /* The length in use must be fed */
+ ok = !action->record.record_offset
+ && !action->record.attribute_offset
+ && (record->magic == magic_FILE)
+ && (length <= mftrecsz)
+ && (length >= (offsetof(MFT_RECORD, bytes_in_use)
+ + sizeof(record->bytes_in_use)));
+ if (ok) {
+ k = le16_to_cpu(record->attrs_offset);
+ attr = (const ATTR_RECORD*)((const char*)record + k);
+ while (((k + sizeof(attr->type)) <= length)
+ && (attr->type != AT_END)
+ && valid_type(attr->type)) {
+ k += le32_to_cpu(attr->length);
+ attr = (const ATTR_RECORD*)((const char*)record + k);
+ }
+ /* AT_END must be present */
+ ok = ((k + sizeof(attr->type)) <= length)
+ && (attr->type == AT_END);
+ }
+ return (ok);
+}
+
+/*
+ * Check whether a full index block is fed by the log record
+ *
+ * If so, checking the validity of existing record is pointless
+ */
+
+static BOOL check_full_index(const struct ACTION_RECORD *action, BOOL redoing)
+{
+ const INDEX_BLOCK *indx;
+ u32 length;
+
+ if (redoing) {
+ indx = (const INDEX_BLOCK*)((const char*)&action->record
+ + get_redo_offset(&action->record));
+ length = le16_to_cpu(action->record.redo_length);
+ } else {
+ indx = (const INDEX_BLOCK*)((const char*)&action->record
+ + get_undo_offset(&action->record));
+ length = le16_to_cpu(action->record.undo_length);
+ }
+ /* the index length must be fed, so must be the full index block */
+ return (!action->record.record_offset
+ && !action->record.attribute_offset
+ && (indx->magic == magic_INDX)
+ && (length >= (offsetof(INDEX_BLOCK, index.index_length) + 4))
+ && (length >= (le32_to_cpu(indx->index.index_length) + 24)));
+}
+
+/*
+ * Create an index block for undoing its deletion
+ *
+ * This is useful for turning the clock backward, but cannot
+ * work properly in the general case and must not be used for
+ * a real sync.
+ */
+
+static int create_indx(ntfs_volume *vol, const struct ACTION_RECORD *action,
+ char *buffer)
+{
+ INDEX_BLOCK *indx;
+ INDEX_ENTRY_HEADER *ixhead;
+ INDEX_ENTRY *ixentry;
+ VCN vcn;
+ int err;
+
+ if (opts) {
+ printf("** Call to unsupported create_indx()\n");
+ err = 1;
+ } else {
+ err = 0;
+ indx = (INDEX_BLOCK*)buffer;
+ indx->magic = magic_INDX;
+// TODO compute properly
+ indx->usa_ofs = const_cpu_to_le16(0x28);
+ indx->usa_count = const_cpu_to_le16(9);
+ indx->lsn = action->record.this_lsn;
+ vcn = le32_to_cpu(action->record.target_vcn);
+ /* beware of size change on big-endian cpus */
+ indx->index_block_vcn = cpu_to_le64(vcn);
+ /* INDEX_HEADER */
+ indx->index.entries_offset = const_cpu_to_le32(0x28);
+ indx->index.index_length = const_cpu_to_le32(0x38);
+ indx->index.allocated_size =
+ cpu_to_le32(vol->indx_record_size - 24);
+ indx->index.ih_flags = 0;
+ /* INDEX_ENTRY_HEADER */
+ ixhead = (INDEX_ENTRY_HEADER*)(buffer + 0x28);
+ ixhead->length = cpu_to_le16(vol->indx_record_size - 24);
+ /* terminating INDEX_ENTRY */
+ ixentry = (INDEX_ENTRY*)(buffer + 0x40);
+ ixentry->indexed_file = const_cpu_to_le64(0);
+ ixentry->length = const_cpu_to_le16(16);
+ ixentry->key_length = const_cpu_to_le16(0);
+ ixentry->ie_flags = INDEX_ENTRY_END;
+ }
+ return (err);
+}
+
+static int redo_action37(ntfs_volume *vol, const struct ACTION_RECORD *action,
+ char *buffer)
+{
+ u32 target;
+ u32 length;
+ int err;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ length = le16_to_cpu(action->record.redo_length);
+ target = le16_to_cpu(action->record.record_offset)
+ + le16_to_cpu(action->record.attribute_offset);
+ if (optv > 1) {
+ printf("existing data :\n");
+ dump(buffer + target,length);
+ }
+ if ((target + length) == mftrecsz) {
+ memset(buffer + target, 0, length);
+ err = write_protected(vol, &action->record,
+ buffer, mftrecsz);
+ if (optv > 1) {
+ printf("-> MFT record trimmed\n");
+ }
+ } else {
+ printf("** Bad action-37, inode %lld record :\n",
+ (long long)inode_number(&action->record));
+ printf("target %d length %d sum %d\n",
+ (int)target,(int)length,(int)(target + length));
+ dump(buffer,mftrecsz);
+ }
+ err = 0;
+ return (err);
+}
+
+static int redo_add_index(ntfs_volume *vol, const struct ACTION_RECORD *action,
+ char *buffer)
+{
+ LCN lcn;
+ const char *data;
+ INDEX_BLOCK *indx;
+ u32 target;
+ u32 length;
+ u32 xsize;
+ u32 indexlth;
+ int err;
+ BOOL found;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ data = ((const char*)&action->record)
+ + get_redo_offset(&action->record);
+ length = le16_to_cpu(action->record.redo_length);
+ target = le16_to_cpu(action->record.record_offset)
+ + le16_to_cpu(action->record.attribute_offset);
+ if (optv > 1) {
+ lcn = le64_to_cpu(action->record.lcn_list[0]);
+ printf("-> lcn 0x%llx target 0x%x length %d\n",
+ (long long)lcn, (int)target, (int)length);
+ }
+ xsize = vol->indx_record_size;
+ indx = (INDEX_BLOCK*)(buffer
+ + le16_to_cpu(action->record.record_offset));
+ if (optv > 1) {
+ printf("-> existing record :\n");
+ dump(&buffer[target], length);
+ }
+ if ((indx->magic == magic_INDX)
+ && !(length & 7)
+ && ((target + length) <= xsize)) {
+ /* This has to be an idempotent action */
+ found = !memcmp(buffer + target, data, length);
+ err = 0;
+ if (!found) {
+ /* Make space to insert the entry */
+ memmove(buffer + target + length,
+ buffer + target,
+ xsize - target - length);
+ memcpy(buffer + target, data, length);
+ indexlth = le32_to_cpu(indx->index.index_length)
+ + length;
+ indx->index.index_length = cpu_to_le32(indexlth);
+ if (optv > 1) {
+ printf("-> inserted record :\n");
+ dump(&buffer[target], length);
+ }
+ err = write_protected(vol, &action->record,
+ buffer, xsize);
+ }
+ if (optv > 1) {
+ printf("-> INDX record %s\n",
+ (found ? "unchanged" : "inserted"));
+ }
+ }
+ return (err);
+}
+
+static int redo_add_root_index(ntfs_volume *vol,
+ const struct ACTION_RECORD *action, char *buffer)
+{
+ LCN lcn;
+ const char *data;
+ ATTR_RECORD *attr;
+ MFT_RECORD *entry;
+ INDEX_ROOT *index;
+ u32 target;
+ u32 length;
+ int err;
+ BOOL found;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ data = ((const char*)&action->record)
+ + get_redo_offset(&action->record);
+ length = le16_to_cpu(action->record.redo_length);
+ target = le16_to_cpu(action->record.record_offset)
+ + le16_to_cpu(action->record.attribute_offset);
+ if (optv > 1) {
+ lcn = le64_to_cpu(action->record.lcn_list[0]);
+ printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
+ (long long)inode_number(&action->record),
+ (long long)lcn, (int)target, (int)length);
+ }
+ entry = (MFT_RECORD*)buffer;
+ attr = (ATTR_RECORD*)(buffer
+ + le16_to_cpu(action->record.record_offset));
+ index = (INDEX_ROOT*)(((char*)attr)
+ + le16_to_cpu(attr->value_offset));
+ if (optv > 1) {
+ printf("existing index :\n");
+ dump(buffer + target,length);
+ }
+ if ((attr->type == AT_INDEX_ROOT)
+ && !(length & 7)
+ && ((target + length) <= mftrecsz)) {
+ /* This has to be an idempotent action */
+ found = !memcmp(buffer + target, data, length);
+ err = 0;
+ if (!found) {
+ /* Make space to insert the entry */
+ memmove(buffer + target + length,
+ buffer + target,
+ mftrecsz - target - length);
+ memcpy(buffer + target, data, length);
+ resize_attribute(entry, attr, index, length, length);
+ if (optv > 1) {
+ printf("new index at same location :\n");
+ dump(buffer + target, length);
+ }
+ err = write_protected(vol, &action->record,
+ buffer, mftrecsz);
+ }
+ if (optv > 1) {
+ printf("-> MFT record %s\n",
+ (found ? "unchanged" : "expanded"));
+ }
+ }
+ return (err);
+}
+
+static int redo_compensate(ntfs_volume *vol __attribute__((unused)),
+ const struct ACTION_RECORD *action,
+ char *buffer __attribute__((unused)))
+{
+ u64 lsn;
+ s64 diff;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ lsn = le64_to_cpu(action->record.this_lsn);
+ diff = lsn - restart_lsn;
+ if (diff > 0)
+ restart_lsn = lsn;
+ return (0);
+}
+
+static int redo_create_file(ntfs_volume *vol,
+ const struct ACTION_RECORD *action, char *buffer)
+{
+ LCN lcn;
+ const char *data;
+ MFT_RECORD *record;
+ const MFT_RECORD *fullrec;
+ u32 target;
+ u32 length;
+ int err;
+ int changed;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ data = ((const char*)&action->record)
+ + get_redo_offset(&action->record);
+ length = le16_to_cpu(action->record.redo_length);
+ target = le16_to_cpu(action->record.record_offset)
+ + le16_to_cpu(action->record.attribute_offset);
+ if (optv > 1) {
+ lcn = le64_to_cpu(action->record.lcn_list[0]);
+ printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
+ (long long)inode_number(&action->record),
+ (long long)lcn, (int)target, (int)length);
+ }
+ record = (MFT_RECORD*)buffer;
+ if (optv > 1) {
+ printf("-> existing record :\n");
+ dump(buffer,mftrecsz);
+ }
+ if ((target + length) <= mftrecsz) {
+ changed = memcmp(buffer + target, data, length);
+ err = 0;
+ if (changed || !(record->flags & MFT_RECORD_IN_USE)) {
+ memcpy(buffer + target, data, length);
+ record->flags |= MFT_RECORD_IN_USE;
+ if (optv > 1) {
+ printf("-> new record :\n");
+ dump(buffer,mftrecsz);
+ }
+ err = write_protected(vol, &action->record,
+ buffer, mftrecsz);
+ }
+ if (optv > 1) {
+ printf("-> MFT record %s\n",
+ (changed ? "updated" : "unchanged"));
+ }
+ } else {
+// TODO make sure this was caused by bad fixups
+ /*
+ * Could not read protected, assume newly allocated record.
+ * Check we are creating a full MFT record, and so
+ * existing data is meaningless.
+ */
+ fullrec = (const MFT_RECORD*)data;
+ if ((length > offsetof(MFT_RECORD, bytes_in_use))
+ && (le32_to_cpu(fullrec->bytes_in_use) <= length)
+ && (fullrec->magic == magic_FILE)
+ && !target
+ && (length <= mftrecsz)) {
+ buffer = (char*)malloc(mftrecsz);
+ memcpy(buffer, data, length);
+ if (optv > 1) {
+ printf("-> created MFT record :\n");
+ dump(buffer, length);
+ }
+ err = write_protected(vol, &action->record,
+ buffer, mftrecsz);
+ }
+ }
+ return (err);
+}
+
+static int redo_create_attribute(ntfs_volume *vol,
+ const struct ACTION_RECORD *action, char *buffer)
+{
+ const char *data;
+ u32 target;
+ u32 length;
+ int err;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ data = ((const char*)&action->record)
+ + get_redo_offset(&action->record);
+ length = le16_to_cpu(action->record.redo_length);
+ target = le16_to_cpu(action->record.record_offset)
+ + le16_to_cpu(action->record.attribute_offset);
+// Could also be AT_DATA or AT_INDEX_ALLOCATION
+ if (!action->record.undo_length)
+ err = insert_resident(vol, action, buffer, data,
+ target, length);
+ return (err);
+}
+
+static int redo_delete_attribute(ntfs_volume *vol,
+ const struct ACTION_RECORD *action, char *buffer)
+{
+ const char *data;
+ u32 target;
+ u32 length;
+ int err;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ data = ((const char*)&action->record)
+ + get_undo_offset(&action->record);
+ length = le16_to_cpu(action->record.undo_length);
+ target = le16_to_cpu(action->record.record_offset)
+ + le16_to_cpu(action->record.attribute_offset);
+ if (!action->record.redo_length)
+ err = remove_resident(vol, action, buffer, data,
+ target, length);
+ return (err);
+}
+
+static int redo_delete_file(ntfs_volume *vol,
+ const struct ACTION_RECORD *action, char *buffer)
+{
+ LCN lcn;
+ const char *data;
+ MFT_RECORD *record;
+ u32 target;
+ u32 length;
+ int err;
+ int changed;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ data = ((const char*)&action->record)
+ + get_undo_offset(&action->record);
+ length = le16_to_cpu(action->record.undo_length);
+ target = le16_to_cpu(action->record.record_offset)
+ + le16_to_cpu(action->record.attribute_offset);
+ if (optv > 1) {
+ lcn = le64_to_cpu(action->record.lcn_list[0]);
+ printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
+ (long long)inode_number(&action->record),
+ (long long)lcn, (int)target, (int)length);
+ }
+ if (optv > 1) {
+ printf("-> existing record :\n");
+ dump(buffer,mftrecsz);
+ }
+ record = (MFT_RECORD*)buffer;
+ if ((target + length) <= mftrecsz) {
+ /* write a void mft entry (needed ?) */
+ changed = memcmp(buffer + target, data, length)
+ || (record->flags & MFT_RECORD_IN_USE);
+ err = 0;
+ if (changed) {
+ memcpy(buffer + target, data, length);
+ record->flags &= ~MFT_RECORD_IN_USE;
+ if (optv > 1) {
+ printf("-> new record :\n");
+ dump(buffer,mftrecsz);
+ }
+ err = write_protected(vol, &action->record,
+ buffer, mftrecsz);
+ }
+ if (optv > 1) {
+ printf("-> MFT record %s\n",
+ (changed ? "updated" : "unchanged"));
+ }
+ }
+ return (err);
+}
+
+static int redo_delete_index(ntfs_volume *vol,
+ const struct ACTION_RECORD *action, char *buffer)
+{
+ LCN lcn;
+ const char *data;
+ INDEX_BLOCK *indx;
+ u32 target;
+ u32 length;
+ u32 xsize;
+ u32 indexlth;
+ int err;
+ BOOL found;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ data = ((const char*)&action->record)
+ + get_undo_offset(&action->record);
+ length = le16_to_cpu(action->record.undo_length);
+// TODO merge with undo_add_index ?
+ target = le16_to_cpu(action->record.record_offset)
+ + le16_to_cpu(action->record.attribute_offset);
+ if (optv > 1) {
+ lcn = le64_to_cpu(action->record.lcn_list[0]);
+ printf("-> lcn 0x%llx target 0x%x length %d\n",
+ (long long)lcn, (int)target, (int)length);
+ }
+ xsize = vol->indx_record_size;
+ indx = (INDEX_BLOCK*)(buffer
+ + le16_to_cpu(action->record.record_offset));
+ if (optv > 1) {
+ printf("-> existing record :\n");
+ dump(&buffer[target], length);
+ }
+ if ((indx->magic == magic_INDX)
+ && !(length & 7)
+ && ((target + length) <= xsize)) {
+ /* This has to be an idempotent action */
+ found = !memcmp(buffer + target, data, length);
+ err = 0;
+ if (found) {
+ /* Remove the entry */
+ memmove(buffer + target,
+ buffer + target + length,
+ xsize - target - length);
+ indexlth = le32_to_cpu(indx->index.index_length)
+ - length;
+ indx->index.index_length = cpu_to_le32(indexlth);
+ err = write_protected(vol, &action->record,
+ buffer, xsize);
+ }
+ if (optv > 1) {
+ printf("-> INDX record %s\n",
+ (found ? "unchanged" : "removed"));
+ }
+ }
+ return (err);
+}
+
+static int redo_delete_root_index(ntfs_volume *vol,
+ const struct ACTION_RECORD *action, char *buffer)
+{
+ LCN lcn;
+ const char *data;
+ ATTR_RECORD *attr;
+ MFT_RECORD *entry;
+ INDEX_ROOT *index;
+ BOOL found;
+ u32 target;
+ u32 length;
+ int err;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ data = ((const char*)&action->record)
+ + get_undo_offset(&action->record);
+ length = le16_to_cpu(action->record.undo_length);
+ target = le16_to_cpu(action->record.record_offset)
+ + le16_to_cpu(action->record.attribute_offset);
+
+ if (optv > 1) {
+ lcn = le64_to_cpu(action->record.lcn_list[0]);
+ printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
+ (long long)inode_number(&action->record),
+ (long long)lcn, (int)target, (int)length);
+ }
+ entry = (MFT_RECORD*)buffer;
+ attr = (ATTR_RECORD*)(buffer
+ + le16_to_cpu(action->record.record_offset));
+ index = (INDEX_ROOT*)(((char*)attr)
+ + le16_to_cpu(attr->value_offset));
+ if (optv > 1) {
+ printf("existing index :\n");
+ dump(buffer + target,length);
+ }
+ if ((attr->type == AT_INDEX_ROOT)
+ && !(length & 7)
+ && ((target + length) <= mftrecsz)) {
+ /* This has to be an idempotent action */
+ found = !memcmp(buffer + target, data, length);
+ err = 0;
+ /* Only delete if present */
+ if (found) {
+ /* Remove the entry */
+ memmove(buffer + target,
+ buffer + target + length,
+ mftrecsz - target - length);
+ resize_attribute(entry, attr, index, -length, -length);
+ if (optv > 1) {
+ printf("new index at same location :\n");
+ dump(buffer + target, length);
+ }
+ err = write_protected(vol, &action->record,
+ buffer, mftrecsz);
+ }
+ if (optv > 1) {
+ printf("-> MFT record %s\n",
+ (found ? "shrinked" : "updated"));
+ }
+ }
+ return (err);
+}
+
+static int redo_force_bits(ntfs_volume *vol,
+ const struct ACTION_RECORD *action, char *buffer)
+{
+ LCN lcn;
+ const struct BITMAP_ACTION *data;
+ u32 i;
+ int err;
+ int wanted;
+ u32 firstbit;
+ u32 count;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ data = (const struct BITMAP_ACTION*)
+ (((const char*)&action->record)
+ + get_redo_offset(&action->record));
+ firstbit = le32_to_cpu(data->firstbit);
+ count = le32_to_cpu(data->count);
+ if (action->record.redo_operation
+ == const_cpu_to_le16(SetBitsInNonResidentBitMap))
+ wanted = 1;
+ else
+ wanted = 0;
+// TODO consistency undo_offset == redo_offset, etc.
+// firstbit + count < 8*clustersz (multiple clusters possible ?)
+ if (optv > 1) {
+ lcn = le64_to_cpu(action->record.lcn_list[0]);
+ printf("-> lcn 0x%llx firstbit %d count %d wanted %d\n",
+ (long long)lcn,(int)firstbit,(int)count,(int)wanted);
+ }
+ for (i=0; i<count; i++)
+ ntfs_bit_set((u8*)buffer, firstbit + i, wanted);
+ if (!write_raw(vol, &action->record, buffer)) {
+ err = 0;
+ if (optv > 1)
+ printf("-> record updated\n");
+ }
+ if (err)
+ printf("** redo_clearbits failed\n");
+ return (err);
+}
+
+static int redo_open_attribute(ntfs_volume *vol __attribute__((unused)),
+ const struct ACTION_RECORD *action)
+{
+ const char *data;
+ struct ATTR *pa;
+ const struct ATTR_OLD *attr_old;
+ const struct ATTR_NEW *attr_new;
+ const char *name;
+ le64 inode;
+ u32 namelen;
+ u32 length;
+ u32 extra;
+ int err;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ data = ((const char*)&action->record)
+ + get_redo_offset(&action->record);
+ length = le16_to_cpu(action->record.redo_length);
+ extra = get_extra_offset(&action->record);
+ if (action->record.undo_length) {
+ name = ((const char*)&action->record) + extra;
+ namelen = le32_to_cpu(action->record.client_data_length)
+ + LOG_RECORD_HEAD_SZ - extra;
+ /* fix namelen which was aligned modulo 8 */
+ namelen = fixnamelen(name, namelen);
+ if (optv > 1) {
+ printf("-> length %d namelen %d",(int)length,
+ (int)namelen);
+ showname(", ", name, namelen/2);
+ }
+ } else {
+ name = "";
+ namelen = 0;
+ }
+ pa = getattrentry(le16_to_cpu(action->record.target_attribute),0);
+ if (pa) {
+ if (optv) {
+ /*
+ * If the actions have been displayed, the
+ * attribute has already been fed. Check
+ * whether it matches what we have in store.
+ */
+ switch (length) {
+ case sizeof(struct ATTR_OLD) :
+ attr_old = (const struct ATTR_OLD*)data;
+ /* Badly aligned */
+ memcpy(&inode, &attr_old->inode, 8);
+ err = (MREF(le64_to_cpu(inode)) != pa->inode)
+ || (attr_old->type != pa->type);
+ break;
+ case sizeof(struct ATTR_NEW) :
+ attr_new = (const struct ATTR_NEW*)data;
+ err = (MREF(le64_to_cpu(attr_new->inode))
+ != pa->inode)
+ || (attr_new->type != pa->type);
+ break;
+ default : err = 1;
+ }
+ if (!err) {
+ err = (namelen != pa->namelen)
+ || (namelen
+ && memcmp(name, pa->name, namelen));
+ }
+ if (optv > 1)
+ printf("-> attribute %s the recorded one\n",
+ (err ? "does not match" : "matches"));
+ } else {
+ copy_attribute(pa, data, length);
+ pa->namelen = namelen;
+ if (namelen)
+ memcpy(pa->name, data, namelen);
+ err = 0;
+ }
+ } else
+ if (optv)
+ printf("* Unrecorded attribute\n");
+ return (err);
+}
+
+static int redo_sizes(ntfs_volume *vol, const struct ACTION_RECORD *action,
+ char *buffer)
+{
+ const char *data;
+ u32 target;
+ u32 length;
+ int err;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ data = ((const char*)&action->record)
+ + get_redo_offset(&action->record);
+ length = le16_to_cpu(action->record.redo_length);
+ target = le16_to_cpu(action->record.record_offset)
+ + le16_to_cpu(action->record.attribute_offset)
+ + offsetof(ATTR_RECORD, allocated_size);
+ err = change_resident(vol, action, buffer,
+ data, target, length);
+ return (err);
+}
+
+static int redo_update_index(ntfs_volume *vol,
+ const struct ACTION_RECORD *action, char *buffer)
+{
+ const char *data;
+ u32 target;
+ u32 length;
+ int err;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ data = ((const char*)&action->record)
+ + get_redo_offset(&action->record);
+ length = le16_to_cpu(action->record.redo_length);
+ /* target is left-justified to creation time */
+ target = le16_to_cpu(action->record.record_offset)
+ + le16_to_cpu(action->record.attribute_offset)
+ + offsetof(INDEX_ENTRY, key.file_name.creation_time);
+ err = update_index(vol, action, buffer, data, target, length);
+ return (err);
+}
+
+static int redo_update_index_value(ntfs_volume *vol,
+ const struct ACTION_RECORD *action, char *buffer)
+{
+ const char *data;
+ u32 length;
+ u32 target;
+ int err;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ data = ((const char*)&action->record)
+ + get_redo_offset(&action->record);
+ length = le16_to_cpu(action->record.redo_length);
+ target = le16_to_cpu(action->record.record_offset)
+ + le16_to_cpu(action->record.attribute_offset);
+ err = change_index_value(vol, action, buffer, data, target, length);
+ return (err);
+}
+
+static int redo_update_mapping(ntfs_volume *vol,
+ const struct ACTION_RECORD *action, char *buffer)
+{
+ LCN lcn;
+ const char *data;
+ ATTR_RECORD *attr;
+ MFT_RECORD *entry;
+ u32 target;
+ u32 length;
+ u32 source;
+ u32 alen;
+ u32 newused;
+ int resize;
+ int err;
+ int changed;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ data = ((const char*)&action->record)
+ + get_redo_offset(&action->record);
+ length = le16_to_cpu(action->record.redo_length);
+ resize = length - le16_to_cpu(action->record.undo_length);
+ target = le16_to_cpu(action->record.record_offset)
+ + le16_to_cpu(action->record.attribute_offset);
+ if (optv > 1) {
+ lcn = le64_to_cpu(action->record.lcn_list[0]);
+ printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
+ (long long)inode_number(&action->record),
+ (long long)lcn, (int)target, (int)length);
+ }
+ if (optv > 1) {
+ printf("-> existing record :\n");
+ dump(&buffer[target], length);
+ }
+ entry = (MFT_RECORD*)buffer;
+ attr = (ATTR_RECORD*)(buffer
+ + le16_to_cpu(action->record.record_offset));
+ if (!attr->non_resident) {
+ printf("** Error : update_mapping on resident attr\n");
+ }
+ if (valid_type(attr->type)
+ && attr->non_resident
+ && !(resize & 7)
+ && ((target + length) <= mftrecsz)) {
+ changed = memcmp(buffer + target, data, length);
+ err = 0;
+ if (changed) {
+ /* Adjust space for new mapping pairs */
+ source = target - resize;
+ if (resize > 0) {
+ memmove(buffer + target + length,
+ buffer + source + length,
+ mftrecsz - target - length);
+ }
+ if (resize < 0) {
+ memmove(buffer + target + length,
+ buffer + source + length,
+ mftrecsz - source - length);
+ }
+ memcpy(buffer + target, data, length);
+ /* Resize the attribute */
+ alen = le32_to_cpu(attr->length) + resize;
+ attr->length = cpu_to_le32(alen);
+ /* Resize the mft record */
+ newused = le32_to_cpu(entry->bytes_in_use)
+ + resize;
+ entry->bytes_in_use = cpu_to_le32(newused);
+ /* Compute the new highest_vcn */
+ err = adjust_high_vcn(vol, attr);
+ if (optv > 1) {
+ printf("-> new record :\n");
+ dump(buffer + target, length);
+ }
+ if (!err) {
+ err = write_protected(vol,
+ &action->record,
+ buffer, mftrecsz);
+ }
+ }
+ if (optv > 1) {
+ printf("-> MFT record %s\n",
+ (changed ? "updated" : "unchanged"));
+ }
+ }
+ return (err);
+}
+
+static int redo_update_resident(ntfs_volume *vol,
+ const struct ACTION_RECORD *action, char *buffer)
+{
+ LCN lcn;
+ const char *data;
+ u32 target;
+ u32 length;
+ u32 oldlength;
+ u32 end;
+ u32 redo;
+ int err;
+ int changed;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ end = le32_to_cpu(action->record.client_data_length)
+ + LOG_RECORD_HEAD_SZ;
+ length = le16_to_cpu(action->record.redo_length);
+ redo = get_redo_offset(&action->record);
+ if ((redo + length) > end)
+ data = (char*)NULL;
+ else
+ data = ((const char*)&action->record) + redo;
+ oldlength = le16_to_cpu(action->record.undo_length);
+ target = le16_to_cpu(action->record.record_offset)
+ + le16_to_cpu(action->record.attribute_offset);
+ if (length == oldlength) {
+ if (optv > 1) {
+ lcn = le64_to_cpu(action->record.lcn_list[0]);
+ printf("-> inode %lld lcn 0x%llx target 0x%x"
+ " length %d\n",
+ (long long)inode_number(&action->record),
+ (long long)lcn, (int)target, (int)length);
+ }
+ if (optv > 1) {
+ printf("-> existing record :\n");
+ dump(&buffer[target], length);
+ }
+ if ((target + length) <= mftrecsz) {
+ changed = memcmp(buffer + target, data, length);
+ err = 0;
+ if (changed) {
+ memcpy(buffer + target, data, length);
+ if (optv > 1) {
+ printf("-> new record :\n");
+ dump(buffer + target, length);
+ }
+ err = write_protected(vol, &action->record,
+ buffer, mftrecsz);
+ }
+ if (optv > 1) {
+ printf("-> MFT record %s\n",
+ (changed ? "updated" : "unchanged"));
+ }
+ }
+ } else {
+ if (length > oldlength)
+ err = expand_resident(vol, action, buffer, data,
+ target, length, oldlength);
+ else
+ err = shrink_resident(vol, action, buffer, data,
+ target, length, oldlength);
+ }
+ return (err);
+}
+
+static int redo_update_root_index(ntfs_volume *vol,
+ const struct ACTION_RECORD *action, char *buffer)
+{
+ const char *data;
+ const char *expected;
+ u32 target;
+ u32 length;
+ int err;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ data = ((const char*)&action->record)
+ + get_redo_offset(&action->record);
+ expected = ((const char*)&action->record)
+ + get_undo_offset(&action->record);
+ length = le16_to_cpu(action->record.redo_length);
+ /* the fixup is right-justified to the name length */
+ target = le16_to_cpu(action->record.record_offset)
+ + le16_to_cpu(action->record.attribute_offset)
+ + offsetof(INDEX_ENTRY, key.file_name.file_name_length)
+ - length;
+ err = change_resident_expect(vol, action, buffer, data, expected,
+ target, length, AT_INDEX_ROOT);
+ return (err);
+}
+
+static int redo_update_root_vcn(ntfs_volume *vol,
+ const struct ACTION_RECORD *action, char *buffer)
+{
+ const char *data;
+ const char *expected;
+ u32 target;
+ u32 length;
+ int err;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ data = ((const char*)&action->record)
+ + get_redo_offset(&action->record);
+ expected = ((const char*)&action->record)
+ + get_undo_offset(&action->record);
+ length = le16_to_cpu(action->record.redo_length);
+// length must be 8
+ target = le16_to_cpu(action->record.record_offset)
+ + le16_to_cpu(action->record.attribute_offset)
++ 16; // explanation needed (right justified ?)
+ err = change_resident_expect(vol, action, buffer, data, expected,
+ target, length, AT_INDEX_ROOT);
+ return (err);
+}
+
+static int redo_update_value(ntfs_volume *vol,
+ const struct ACTION_RECORD *action, char *buffer)
+{
+ LCN lcn;
+ const char *data;
+ u32 length;
+ u32 target;
+ u32 count;
+ u32 redo;
+ u32 end;
+ u32 i;
+ int changed;
+ int err;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ length = le16_to_cpu(action->record.redo_length);
+ redo = get_redo_offset(&action->record);
+ end = le32_to_cpu(action->record.client_data_length)
+ + LOG_RECORD_HEAD_SZ;
+ /* sometimes there is no redo data */
+ if ((redo + length) > end)
+ data = (char*)NULL;
+ else
+ data = ((const char*)&action->record) + redo;
+ target = le16_to_cpu(action->record.record_offset)
+ + le16_to_cpu(action->record.attribute_offset);
+ count = le16_to_cpu(action->record.lcns_to_follow);
+ if (optv > 1) {
+ lcn = le64_to_cpu(action->record.lcn_list[0]);
+ printf("-> lcn 0x%llx target 0x%x length %d\n",
+ (long long)lcn, (int)target, (int)length);
+ }
+ if (optv > 1) {
+ printf("-> existing record :\n");
+ dump(&buffer[target], length);
+ }
+ if ((target + length) <= (count << clusterbits)) {
+ if (data)
+ changed = memcmp(buffer + target, data, length);
+ else {
+ for (i=0; (i<length) && !buffer[target+i]; i++) { }
+ changed = length && (i < length);
+ }
+ err = 0;
+ if (changed) {
+ if (data)
+ memcpy(buffer + target, data, length);
+ else
+ memset(buffer + target, 0, length);
+ if (optv > 1) {
+ printf("-> new record :\n");
+ dump(buffer + target, length);
+ }
+ err = write_raw(vol, &action->record, buffer);
+ }
+ if (optv > 1) {
+ printf("-> data record %s\n",
+ (changed ? "updated" : "unchanged"));
+ }
+ }
+
+ return (err);
+}
+
+static int redo_update_vcn(ntfs_volume *vol,
+ const struct ACTION_RECORD *action, char *buffer)
+{
+ const char *data;
+ u32 target;
+ u32 length;
+ int err;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ data = ((const char*)&action->record)
+ + get_redo_offset(&action->record);
+ length = le16_to_cpu(action->record.redo_length);
+ /* target is left-justified to creation time */
+ target = le16_to_cpu(action->record.record_offset)
+ + le16_to_cpu(action->record.attribute_offset)
+ + 16; // to better describe
+ err = update_index(vol, action, buffer, data, target, length);
+ return (err);
+}
+
+static int redo_write_end(ntfs_volume *vol,
+ const struct ACTION_RECORD *action, char *buffer)
+{
+ LCN lcn;
+ const char *data;
+ u32 target;
+ u32 length;
+ u32 oldlength;
+ u32 end;
+ u32 redo;
+ int err;
+ int changed;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ end = le32_to_cpu(action->record.client_data_length)
+ + LOG_RECORD_HEAD_SZ;
+ length = le16_to_cpu(action->record.redo_length);
+ redo = get_redo_offset(&action->record);
+ if ((redo + length) > end)
+ data = (char*)NULL;
+ else
+ data = ((const char*)&action->record) + redo;
+ oldlength = le16_to_cpu(action->record.undo_length);
+ target = le16_to_cpu(action->record.record_offset)
+ + le16_to_cpu(action->record.attribute_offset);
+ if (length == oldlength) {
+ if (optv > 1) {
+ lcn = le64_to_cpu(action->record.lcn_list[0]);
+ printf("-> inode %lld lcn 0x%llx target 0x%x"
+ " length %d\n",
+ (long long)inode_number(&action->record),
+ (long long)lcn, (int)target, (int)length);
+ }
+ if (optv > 1) {
+ printf("-> existing record :\n");
+ dump(&buffer[target], length);
+ }
+ if ((target + length) <= mftrecsz) {
+ changed = memcmp(buffer + target, data, length);
+ err = 0;
+ if (changed) {
+ memcpy(buffer + target, data, length);
+ if (optv > 1) {
+ printf("-> new record :\n");
+ dump(buffer + target, length);
+ }
+ err = write_protected(vol, &action->record,
+ buffer, mftrecsz);
+ }
+ if (optv > 1) {
+ printf("-> MFT record %s\n",
+ (changed ? "updated" : "unchanged"));
+ }
+ }
+ } else {
+ if (length > oldlength)
+ err = add_resident(vol, action, buffer, data,
+ target, length, oldlength);
+ else
+ err = delete_resident(vol, action, buffer, data,
+ target, length, oldlength);
+ }
+ return (err);
+}
+
+static int redo_write_index(ntfs_volume *vol,
+ const struct ACTION_RECORD *action, char *buffer)
+{
+ LCN lcn;
+ const char *data;
+ INDEX_BLOCK *indx;
+ u32 target;
+ u32 length;
+ u32 xsize;
+ int err;
+ int changed;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ data = ((const char*)&action->record)
+ + get_redo_offset(&action->record);
+ length = le16_to_cpu(action->record.redo_length);
+ /* target is left-justified to creation time */
+ target = le16_to_cpu(action->record.record_offset)
+ + le16_to_cpu(action->record.attribute_offset);
+ if (optv > 1) {
+ lcn = le64_to_cpu(action->record.lcn_list[0]);
+ printf("-> lcn 0x%llx target 0x%x length %d\n",
+ (long long)lcn, (int)target, (int)length);
+ }
+ xsize = vol->indx_record_size;
+ indx = (INDEX_BLOCK*)buffer;
+ if (action->record.record_offset) {
+ printf("** Non-null record_offset in redo_write_index()\n");
+ }
+ if (optv > 1) {
+ printf("-> existing index :\n");
+ dump(&buffer[target], length);
+ }
+ if ((indx->magic == magic_INDX)
+ && !(length & 7)
+ && ((target + length) <= xsize)) {
+ /* This has to be an idempotent action */
+ changed = memcmp(buffer + target, data, length);
+ err = 0;
+ if (changed) {
+ /* Update the entry */
+ memcpy(buffer + target, data, length);
+ /* If truncating, set the new size */
+ indx->index.index_length =
+ cpu_to_le32(target + length - 0x18);
+ if (optv > 1) {
+ printf("-> new index :\n");
+ dump(&buffer[target], length);
+ }
+ err = write_protected(vol, &action->record,
+ buffer, xsize);
+ }
+ if (optv > 1) {
+ printf("-> INDX record %s\n",
+ (changed ? "updated" : "unchanged"));
+ }
+ }
+ return (err);
+}
+
+static int undo_action37(ntfs_volume *vol __attribute__((unused)),
+ const struct ACTION_RECORD *action,
+ char *buffer __attribute__((unused)))
+{
+/*
+ const char *data;
+ u32 target;
+ u32 length;
+*/
+ int err;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+/*
+ data = ((const char*)&action->record)
+ + get_redo_offset(&action->record);
+ length = le16_to_cpu(action->record.redo_length);
+ target = le16_to_cpu(action->record.record_offset)
+ + le16_to_cpu(action->record.attribute_offset);
+*/
+ printf("* Ignored action-37, inode %lld record :\n",
+ (long long)inode_number(&action->record));
+ err = 0;
+ return (err);
+}
+
+static int undo_add_index(ntfs_volume *vol,
+ const struct ACTION_RECORD *action, char *buffer)
+{
+ LCN lcn;
+ const char *data;
+ INDEX_BLOCK *indx;
+ u32 target;
+ u32 length;
+ u32 xsize;
+ u32 indexlth;
+ int err;
+ BOOL found;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ data = ((const char*)&action->record)
+ + get_redo_offset(&action->record);
+ length = le16_to_cpu(action->record.redo_length);
+ target = le16_to_cpu(action->record.record_offset)
+ + le16_to_cpu(action->record.attribute_offset);
+ if (optv > 1) {
+ lcn = le64_to_cpu(action->record.lcn_list[0]);
+ printf("-> lcn 0x%llx target 0x%x length %d\n",
+ (long long)lcn, (int)target, (int)length);
+ }
+ xsize = vol->indx_record_size;
+ indx = (INDEX_BLOCK*)(buffer
+ + le16_to_cpu(action->record.record_offset));
+ if (optv > 1) {
+ printf("-> existing record :\n");
+ dump(&buffer[target], length);
+ }
+ if ((indx->magic == magic_INDX)
+ && !(length & 7)
+ && ((target + length) <= xsize)) {
+ /* This has to be an idempotent action */
+ found = index_match_undo(buffer + target, data, length);
+ err = 0;
+ if (found) {
+ /* Remove the entry */
+ memmove(buffer + target,
+ buffer + target + length,
+ xsize - target - length);
+ indexlth = le32_to_cpu(indx->index.index_length)
+ - length;
+ indx->index.index_length = cpu_to_le32(indexlth);
+ err = write_protected(vol, &action->record,
+ buffer, xsize);
+ } else {
+ sanity_indx(vol,buffer);
+ printf("full record :\n");
+ dump(buffer,xsize);
+ }
+ if (optv > 1) {
+ printf("-> INDX record %s\n",
+ (found ? "removed" : "unchanged"));
+ }
+ }
+ return (err);
+}
+
+static int undo_add_root_index(ntfs_volume *vol,
+ const struct ACTION_RECORD *action, char *buffer)
+{
+ LCN lcn;
+ const char *data;
+ ATTR_RECORD *attr;
+ MFT_RECORD *entry;
+ INDEX_ROOT *index;
+ BOOL found;
+ u32 target;
+ u32 length;
+ int err;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ data = ((const char*)&action->record)
+ + get_redo_offset(&action->record);
+ length = le16_to_cpu(action->record.redo_length);
+ target = le16_to_cpu(action->record.record_offset)
+ + le16_to_cpu(action->record.attribute_offset);
+ if (optv > 1) {
+ lcn = le64_to_cpu(action->record.lcn_list[0]);
+ printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
+ (long long)inode_number(&action->record),
+ (long long)lcn, (int)target, (int)length);
+ }
+ entry = (MFT_RECORD*)buffer;
+ attr = (ATTR_RECORD*)(buffer
+ + le16_to_cpu(action->record.record_offset));
+ index = (INDEX_ROOT*)(((char*)attr)
+ + le16_to_cpu(attr->value_offset));
+ if (optv > 1) {
+ printf("existing index :\n");
+ dump(buffer + target,length);
+ }
+ if ((attr->type == AT_INDEX_ROOT)
+ && !(length & 7)
+ && ((target + length) <= mftrecsz)) {
+ /* This has to be an idempotent action */
+ found = index_match_undo(buffer + target, data, length);
+ err = 0;
+ if (found && !older_record(entry, &action->record)) {
+ /* Remove the entry */
+ memmove(buffer + target,
+ buffer + target + length,
+ mftrecsz - target - length);
+ resize_attribute(entry, attr, index, -length, -length);
+ if (optv > 1) {
+ printf("new index at same location :\n");
+ dump(buffer + target, length);
+ }
+ err = write_protected(vol, &action->record,
+ buffer, mftrecsz);
+ }
+ if (optv > 1) {
+ printf("-> MFT record %s\n",
+ (found ? "shrinked" : "unchanged"));
+ }
+ }
+ return (err);
+}
+
+static int undo_create_attribute(ntfs_volume *vol,
+ const struct ACTION_RECORD *action, char *buffer)
+{
+ const char *data;
+ u32 target;
+ u32 length;
+ int err;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ data = ((const char*)&action->record)
+ + get_redo_offset(&action->record);
+ length = le16_to_cpu(action->record.redo_length);
+ target = le16_to_cpu(action->record.record_offset)
+ + le16_to_cpu(action->record.attribute_offset);
+ if (!action->record.undo_length)
+ err = remove_resident(vol, action, buffer, data,
+ target, length);
+ return (err);
+}
+
+static int undo_delete_attribute(ntfs_volume *vol,
+ const struct ACTION_RECORD *action, char *buffer)
+{
+ const char *data;
+ u32 target;
+ u32 length;
+ int err;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ data = ((const char*)&action->record)
+ + get_undo_offset(&action->record);
+ length = le16_to_cpu(action->record.undo_length);
+ target = le16_to_cpu(action->record.record_offset)
+ + le16_to_cpu(action->record.attribute_offset);
+ if (!action->record.redo_length)
+ err = insert_resident(vol, action, buffer, data,
+ target, length);
+ return (err);
+}
+
+static int undo_delete_index(ntfs_volume *vol,
+ const struct ACTION_RECORD *action, char *buffer)
+{
+ LCN lcn;
+ const char *data;
+ INDEX_BLOCK *indx;
+ u32 target;
+ u32 length;
+ u32 xsize;
+ u32 indexlth;
+ int err;
+ BOOL found;
+
+// MERGE with redo_add_root_index() ?
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ data = ((const char*)&action->record)
+ + get_undo_offset(&action->record);
+ length = le16_to_cpu(action->record.undo_length);
+ target = le16_to_cpu(action->record.record_offset)
+ + le16_to_cpu(action->record.attribute_offset);
+ if (optv > 1) {
+ lcn = le64_to_cpu(action->record.lcn_list[0]);
+ printf("-> lcn 0x%llx target 0x%x length %d\n",
+ (long long)lcn, (int)target, (int)length);
+ }
+ xsize = vol->indx_record_size;
+ indx = (INDEX_BLOCK*)(buffer
+ + le16_to_cpu(action->record.record_offset));
+ if (optv > 1) {
+ printf("-> existing record :\n");
+ dump(&buffer[target], length);
+ }
+ if ((indx->magic == magic_INDX)
+ && !(length & 7)
+ && ((target + length) <= xsize)
+ && !sanity_indx(vol,buffer)) {
+ /* This has to be an idempotent action */
+ found = !memcmp(buffer + target, data, length);
+ err = 0;
+ if (!found) {
+ /* Make space to insert the entry */
+ memmove(buffer + target + length,
+ buffer + target,
+ xsize - target - length);
+ memcpy(buffer + target, data, length);
+ indexlth = le32_to_cpu(indx->index.index_length)
+ + length;
+ indx->index.index_length = cpu_to_le32(indexlth);
+ if (optv > 1) {
+ printf("-> inserted record :\n");
+ dump(&buffer[target], length);
+ }
+ /* rebuildname() has no effect currently, should drop */
+ rebuildname((const INDEX_ENTRY*)data);
+ err = write_protected(vol, &action->record,
+ buffer, xsize);
+ }
+ if (optv > 1) {
+ printf("-> INDX record %s\n",
+ (found ? "unchanged" : "inserted"));
+ }
+ }
+ return (err);
+}
+
+static int undo_delete_root_index(ntfs_volume *vol,
+ const struct ACTION_RECORD *action, char *buffer)
+{
+ LCN lcn;
+ const char *data;
+ ATTR_RECORD *attr;
+ MFT_RECORD *entry;
+ INDEX_ROOT *index;
+ u32 target;
+ u32 length;
+ int err;
+ BOOL found;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ data = ((const char*)&action->record)
+ + get_undo_offset(&action->record);
+ length = le16_to_cpu(action->record.undo_length);
+ target = le16_to_cpu(action->record.record_offset)
+ + le16_to_cpu(action->record.attribute_offset);
+ if (optv > 1) {
+ lcn = le64_to_cpu(action->record.lcn_list[0]);
+ printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
+ (long long)inode_number(&action->record),
+ (long long)lcn, (int)target, (int)length);
+ }
+ entry = (MFT_RECORD*)buffer;
+ attr = (ATTR_RECORD*)(buffer
+ + le16_to_cpu(action->record.record_offset));
+ index = (INDEX_ROOT*)(((char*)attr)
+ + le16_to_cpu(attr->value_offset));
+ if (attr->type != AT_INDEX_ROOT) {
+ printf("** Unexpected attr type 0x%lx\n",
+ (long)le32_to_cpu(attr->type));
+ printf("existing mft\n");
+ dump((char*)buffer,512);
+ printf("existing index\n");
+ dump(buffer + target,length);
+ }
+ if (optv > 1) {
+ printf("existing index :\n");
+ dump(buffer + target,length);
+ }
+ if ((attr->type == AT_INDEX_ROOT)
+ && !(length & 7)
+ && ((target + length) <= mftrecsz)) {
+ /* This has to be an idempotent action */
+ found = !memcmp(buffer + target, data, length);
+ err = 0;
+ /* Do not insert if present */
+ if (!found) {
+ /* Make space to insert the entry */
+ memmove(buffer + target + length,
+ buffer + target,
+ mftrecsz - target - length);
+ memcpy(buffer + target, data, length);
+ resize_attribute(entry, attr, index, length, length);
+ if (optv > 1) {
+ printf("new index :\n");
+ dump(buffer + target, length);
+ }
+ err = write_protected(vol, &action->record,
+ buffer, mftrecsz);
+ }
+ if (optv > 1) {
+ printf("-> MFT record %s\n",
+ (found ? "unchanged" : "expanded"));
+ }
+ }
+ return (err);
+}
+
+static int undo_create_file(ntfs_volume *vol,
+ const struct ACTION_RECORD *action, char *buffer)
+{
+ LCN lcn;
+ const char *data;
+ MFT_RECORD *record;
+ u32 target;
+ u32 length;
+ int err;
+ int changed;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ /* redo initialize, clearing the in_use flag ? */
+ data = ((const char*)&action->record)
+ + get_redo_offset(&action->record);
+ length = le16_to_cpu(action->record.redo_length);
+ target = le16_to_cpu(action->record.record_offset)
+ + le16_to_cpu(action->record.attribute_offset);
+ if (optv > 1) {
+ lcn = le64_to_cpu(action->record.lcn_list[0]);
+ printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
+ (long long)inode_number(&action->record),
+ (long long)lcn, (int)target, (int)length);
+ }
+ record = (MFT_RECORD*)buffer;
+ if (optv > 1) {
+ printf("-> existing record :\n");
+ dump(buffer,mftrecsz);
+ }
+ if ((target + length) <= mftrecsz) {
+ changed = memcmp(buffer + target, data, length);
+ err = 0;
+ if (changed || (record->flags & MFT_RECORD_IN_USE)) {
+ memcpy(buffer + target, data, length);
+ record->flags &= ~MFT_RECORD_IN_USE;
+ if (optv > 1) {
+ printf("-> new record :\n");
+ dump(buffer,mftrecsz);
+ }
+ err = write_protected(vol, &action->record,
+ buffer, mftrecsz);
+ }
+ if (optv > 1) {
+ printf("-> MFT record %s\n",
+ (changed ? "updated" : "unchanged"));
+ }
+ }
+ return (err);
+}
+
+static int undo_delete_file(ntfs_volume *vol,
+ const struct ACTION_RECORD *action, char *buffer)
+{
+ LCN lcn;
+ const char *data;
+ MFT_RECORD *record;
+ u32 target;
+ u32 length;
+ int err;
+ int changed;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ data = ((const char*)&action->record)
+ + get_undo_offset(&action->record);
+ length = le16_to_cpu(action->record.undo_length);
+ target = le16_to_cpu(action->record.record_offset)
+ + le16_to_cpu(action->record.attribute_offset);
+ if (optv > 1) {
+ lcn = le64_to_cpu(action->record.lcn_list[0]);
+ printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
+ (long long)inode_number(&action->record),
+ (long long)lcn, (int)target, (int)length);
+ }
+ if (optv > 1) {
+ printf("-> existing record :\n");
+ dump(buffer,mftrecsz);
+ }
+ record = (MFT_RECORD*)buffer;
+ if ((target + length) <= mftrecsz) {
+ changed = memcmp(buffer + target, data, length)
+ || !(record->flags & MFT_RECORD_IN_USE);
+ err = 0;
+ if (changed) {
+ memcpy(buffer + target, data, length);
+ /*
+ * Unclear what we should do for recreating a file.
+ * Only 24 bytes are available, the used length is not known,
+ * the number of links suggests we should keep the current
+ * names... If so, when will they be deleted ?
+ * We will have to make stamp changes in the standard
+ * information attribute, so better not to delete it.
+ * Should we create a data or index attribute ?
+ * Here, we assume we should delete the file names when
+ * the record now appears to not be in use and there are
+ * links.
+ */
+ if (record->link_count
+ && !(record->flags & MFT_RECORD_IN_USE))
+ err = delete_names(buffer);
+ record->flags |= MFT_RECORD_IN_USE;
+ if (optv > 1) {
+ printf("-> new record :\n");
+ dump(buffer,mftrecsz);
+ }
+ if (!err)
+ err = write_protected(vol,
+ &action->record,
+ buffer, mftrecsz);
+ }
+ if (optv > 1) {
+ printf("-> MFT record %s\n",
+ (changed ? "updated" : "unchanged"));
+ }
+ }
+ return (err);
+}
+
+static int undo_force_bits(ntfs_volume *vol,
+ const struct ACTION_RECORD *action, char *buffer)
+{
+ LCN lcn;
+ const struct BITMAP_ACTION *data;
+ u32 i;
+ int err;
+ int wanted;
+ u32 firstbit;
+ u32 count;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ data = (const struct BITMAP_ACTION*)
+ (((const char*)&action->record)
+ + get_redo_offset(&action->record));
+ firstbit = le32_to_cpu(data->firstbit);
+ count = le32_to_cpu(data->count);
+ if (action->record.redo_operation
+ == const_cpu_to_le16(SetBitsInNonResidentBitMap))
+ wanted = 0;
+ else
+ wanted = 1;
+// TODO consistency undo_offset == redo_offset, etc.
+// firstbit + count < 8*clustersz (multiple clusters possible ?)
+ if (optv > 1) {
+ lcn = le64_to_cpu(action->record.lcn_list[0]);
+ printf("-> lcn 0x%llx firstbit %d count %d wanted %d\n",
+ (long long)lcn,(int)firstbit,(int)count,(int)wanted);
+ }
+ for (i=0; i<count; i++)
+ ntfs_bit_set((u8*)buffer, firstbit + i, wanted);
+ if (!write_raw(vol, &action->record, buffer)) {
+ err = 0;
+ if (optv > 1)
+ printf("-> record updated\n");
+ }
+ if (err)
+ printf("** redo_clearbits failed\n");
+ return (err);
+}
+
+static int undo_open_attribute(ntfs_volume *vol __attribute__((unused)),
+ const struct ACTION_RECORD *action)
+{
+ const char *data;
+ struct ATTR *pa;
+ const struct ATTR_OLD *attr_old;
+ const struct ATTR_NEW *attr_new;
+ const char *name;
+ le64 inode;
+ u32 namelen;
+ u32 length;
+ u32 extra;
+ int err;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ data = ((const char*)&action->record)
+ + get_redo_offset(&action->record);
+ length = le16_to_cpu(action->record.redo_length);
+ extra = get_extra_offset(&action->record);
+ if (action->record.undo_length) {
+ name = ((const char*)&action->record) + extra;
+ namelen = le32_to_cpu(action->record.client_data_length)
+ + LOG_RECORD_HEAD_SZ - extra;
+ /* fix namelen which was aligned modulo 8 */
+ namelen = fixnamelen(name, namelen);
+ if (optv > 1) {
+ printf("-> length %d namelen %d",(int)length,
+ (int)namelen);
+ showname(", ", name, namelen/2);
+ }
+ } else {
+ namelen = 0;
+ name = "";
+ }
+ pa = getattrentry(le16_to_cpu(action->record.target_attribute),0);
+// TODO Only process is attr is not older ?
+ if (pa) {
+ /* check whether the redo attr matches what we have in store */
+ switch (length) {
+ case sizeof(struct ATTR_OLD) :
+ attr_old = (const struct ATTR_OLD*)data;
+ /* Badly aligned */
+ memcpy(&inode, &attr_old->inode, 8);
+ err = (MREF(le64_to_cpu(inode)) != pa->inode)
+ || (attr_old->type != pa->type);
+ break;
+ case sizeof(struct ATTR_NEW) :
+ attr_new = (const struct ATTR_NEW*)data;
+ err = (MREF(le64_to_cpu(attr_new->inode))!= pa->inode)
+ || (attr_new->type != pa->type);
+ break;
+ default : err = 1;
+ }
+ if (!err) {
+ err = (namelen != pa->namelen)
+ || (namelen
+ && memcmp(name, pa->name, namelen));
+ }
+ if (optv > 1)
+ printf("-> attribute %s the recorded one\n",
+ (err ? "does not match" : "matches"));
+ } else
+ if (optv)
+ printf("* Unrecorded attribute\n");
+err = 0;
+ return (err);
+}
+
+static int undo_sizes(ntfs_volume *vol,
+ const struct ACTION_RECORD *action, char *buffer)
+{
+ const char *data;
+ MFT_RECORD *entry;
+ ATTR_RECORD *attr;
+ u32 target;
+ u32 length;
+ u32 offs;
+ int err;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ data = ((const char*)&action->record)
+ + get_undo_offset(&action->record);
+ length = le16_to_cpu(action->record.undo_length);
+ target = le16_to_cpu(action->record.record_offset)
+ + le16_to_cpu(action->record.attribute_offset)
+ + offsetof(ATTR_RECORD, allocated_size);
+ entry = (MFT_RECORD*)buffer;
+ if (!(entry->flags & MFT_RECORD_IS_DIRECTORY))
+ err = change_resident(vol, action, buffer,
+ data, target, length);
+ else {
+ /* On a directory, may have to build an index allocation */
+ offs = le16_to_cpu(action->record.record_offset);
+ attr = (ATTR_RECORD*)(buffer + offs);
+ if (attr->type != AT_INDEX_ALLOCATION) {
+ err = insert_index_allocation(vol, buffer, offs);
+ if (!err)
+ err = change_resident(vol, action, buffer,
+ data, target, length);
+ } else
+ err = change_resident(vol, action, buffer,
+ data, target, length);
+ }
+ return (err);
+}
+
+static int undo_update_index(ntfs_volume *vol, const struct ACTION_RECORD *action,
+ char *buffer)
+{
+ const char *data;
+ u32 target;
+ u32 length;
+ int err;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ data = ((const char*)&action->record)
+ + get_undo_offset(&action->record);
+ length = le16_to_cpu(action->record.undo_length);
+ /* target is left-justified to creation time */
+ target = le16_to_cpu(action->record.record_offset)
+ + le16_to_cpu(action->record.attribute_offset)
+ + offsetof(INDEX_ENTRY, key.file_name.creation_time);
+ err = update_index(vol, action, buffer, data, target, length);
+ return (err);
+}
+
+static int undo_update_index_value(ntfs_volume *vol,
+ const struct ACTION_RECORD *action, char *buffer)
+{
+ LCN lcn;
+ const char *data;
+ u32 length;
+ u32 target;
+ int changed;
+ int err;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ data = ((const char*)&action->record)
+ + get_undo_offset(&action->record);
+ length = le16_to_cpu(action->record.undo_length);
+ target = le16_to_cpu(action->record.record_offset)
+ + le16_to_cpu(action->record.attribute_offset);
+ if (optv > 1) {
+ lcn = le64_to_cpu(action->record.lcn_list[0]);
+ printf("-> lcn 0x%llx target 0x%x length %d\n",
+ (long long)lcn, (int)target, (int)length);
+ }
+ if (optv > 1) {
+ printf("-> existing record :\n");
+ dump(&buffer[target], length);
+ }
+ if ((target + length) <= vol->indx_record_size) {
+ changed = length && memcmp(buffer + target, data, length);
+ err = 0;
+ if (changed) {
+ memcpy(buffer + target, data, length);
+ if (optv > 1) {
+ printf("-> new record :\n");
+ dump(buffer + target, length);
+ }
+ err = write_protected(vol, &action->record, buffer,
+ vol->indx_record_size);
+ }
+ if (optv > 1) {
+ printf("-> data record %s\n",
+ (changed ? "updated" : "unchanged"));
+ }
+ }
+ return (err);
+}
+
+static int undo_update_vcn(ntfs_volume *vol, const struct ACTION_RECORD *action,
+ char *buffer)
+{
+ const char *data;
+ u32 target;
+ u32 length;
+ int err;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ data = ((const char*)&action->record)
+ + get_undo_offset(&action->record);
+ length = le16_to_cpu(action->record.undo_length);
+ /* target is left-justified to creation time */
+ target = le16_to_cpu(action->record.record_offset)
+ + le16_to_cpu(action->record.attribute_offset)
+ + 16; // to better describe
+ err = update_index(vol, action, buffer, data, target, length);
+ return (err);
+}
+
+static int undo_update_mapping(ntfs_volume *vol, const struct ACTION_RECORD *action,
+ char *buffer)
+{
+ LCN lcn;
+ const char *data;
+ ATTR_RECORD *attr;
+ MFT_RECORD *entry;
+ u32 target;
+ u32 length;
+ u32 source;
+ u32 alen;
+ u32 newused;
+ int err;
+ int changed;
+ int resize;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ data = ((const char*)&action->record)
+ + get_undo_offset(&action->record);
+ length = le16_to_cpu(action->record.undo_length);
+ resize = length - le16_to_cpu(action->record.redo_length);
+ target = le16_to_cpu(action->record.record_offset)
+ + le16_to_cpu(action->record.attribute_offset);
+ if (optv > 1) {
+ lcn = le64_to_cpu(action->record.lcn_list[0]);
+ printf("-> inode %lld lcn 0x%llx target 0x%x new length %d resize %d\n",
+ (long long)inode_number(&action->record),
+ (long long)lcn, (int)target, (int)length, (int)resize);
+ }
+// TODO share with redo_update_mapping()
+ if (optv > 1) {
+ printf("-> existing record :\n");
+ dump(&buffer[target], length);
+ }
+ entry = (MFT_RECORD*)buffer;
+ attr = (ATTR_RECORD*)(buffer
+ + le16_to_cpu(action->record.record_offset));
+ if (!attr->non_resident) {
+ printf("** Error : update_mapping on resident attr\n");
+ }
+ if (valid_type(attr->type)
+ && attr->non_resident
+ && !(resize & 7)
+ && ((target + length) <= mftrecsz)) {
+ changed = memcmp(buffer + target, data, length);
+ err = 0;
+ if (changed) {
+ /* Adjust space for new mapping pairs */
+ source = target - resize;
+ if (resize > 0) {
+ memmove(buffer + target + length,
+ buffer + source + length,
+ mftrecsz - target - length);
+ }
+ if (resize < 0) {
+ memmove(buffer + target + length,
+ buffer + source + length,
+ mftrecsz - source - length);
+ }
+ memcpy(buffer + target, data, length);
+ /* Resize the attribute */
+ alen = le32_to_cpu(attr->length) + resize;
+ attr->length = cpu_to_le32(alen);
+ /* Resize the mft record */
+ newused = le32_to_cpu(entry->bytes_in_use)
+ + resize;
+ entry->bytes_in_use = cpu_to_le32(newused);
+ /* Compute the new highest_vcn */
+ err = adjust_high_vcn(vol, attr);
+ if (optv > 1) {
+ printf("-> new record :\n");
+ dump(buffer + target, length);
+ }
+ if (!err) {
+ err = write_protected(vol,
+ &action->record, buffer,
+ mftrecsz);
+ }
+ }
+ if (optv > 1) {
+ printf("-> MFT record %s\n",
+ (changed ? "updated" : "unchanged"));
+ }
+ }
+ return (err);
+}
+
+static int undo_update_resident(ntfs_volume *vol,
+ const struct ACTION_RECORD *action, char *buffer)
+{
+ LCN lcn;
+ const char *data;
+ u32 target;
+ u32 length;
+ u32 oldlength;
+ u32 end;
+ u32 undo;
+ int err;
+ int changed;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ end = le32_to_cpu(action->record.client_data_length)
+ + LOG_RECORD_HEAD_SZ;
+ length = le16_to_cpu(action->record.undo_length);
+ undo = get_undo_offset(&action->record);
+ if ((undo + length) > end)
+ data = (char*)NULL;
+ else
+ data = ((const char*)&action->record) + undo;
+ oldlength = le16_to_cpu(action->record.redo_length);
+ target = le16_to_cpu(action->record.record_offset)
+ + le16_to_cpu(action->record.attribute_offset);
+ if (length == oldlength) {
+ if (optv > 1) {
+ lcn = le64_to_cpu(action->record.lcn_list[0]);
+ printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
+ (long long)inode_number(&action->record),
+ (long long)lcn, (int)target, (int)length);
+ }
+ if (optv > 1) {
+ printf("-> existing record :\n");
+ dump(&buffer[target], length);
+ }
+ if ((target + length) <= mftrecsz) {
+ changed = memcmp(buffer + target, data, length);
+ err = 0;
+ if (changed) {
+ memcpy(buffer + target, data, length);
+ if (optv > 1) {
+ printf("-> new record :\n");
+ dump(buffer + target, length);
+ }
+ err = write_protected(vol, &action->record,
+ buffer, mftrecsz);
+ }
+ if (optv > 1) {
+ printf("-> MFT record %s\n",
+ (changed ? "updated" : "unchanged"));
+ }
+ }
+ } else {
+ if (length > oldlength)
+ err = expand_resident(vol, action, buffer, data,
+ target, length, oldlength);
+ else
+ err = shrink_resident(vol, action, buffer, data,
+ target, length, oldlength);
+ }
+ return (err);
+}
+
+static int undo_update_root_index(ntfs_volume *vol,
+ const struct ACTION_RECORD *action, char *buffer)
+{
+ const char *data;
+ const char *expected;
+ u32 target;
+ u32 length;
+ int err;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ data = ((const char*)&action->record)
+ + get_undo_offset(&action->record);
+ expected = ((const char*)&action->record)
+ + get_redo_offset(&action->record);
+ length = le16_to_cpu(action->record.undo_length);
+ /* the fixup is right-justified to the name length */
+ target = le16_to_cpu(action->record.record_offset)
+ + le16_to_cpu(action->record.attribute_offset)
+ + offsetof(INDEX_ENTRY, key.file_name.file_name_length)
+ - length;
+ err = change_resident_expect(vol, action, buffer, data, expected,
+ target, length, AT_INDEX_ROOT);
+ return (err);
+}
+
+static int undo_update_root_vcn(ntfs_volume *vol,
+ const struct ACTION_RECORD *action, char *buffer)
+{
+ const char *data;
+ const char *expected;
+ u32 target;
+ u32 length;
+ int err;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ data = ((const char*)&action->record)
+ + get_undo_offset(&action->record);
+ expected = ((const char*)&action->record)
+ + get_redo_offset(&action->record);
+ length = le16_to_cpu(action->record.undo_length);
+ /* the fixup is right-justified to the name length */
+ target = le16_to_cpu(action->record.record_offset)
+ + le16_to_cpu(action->record.attribute_offset)
+ + 16; // explanation needed
+ err = change_resident_expect(vol, action, buffer, data, expected,
+ target, length, AT_INDEX_ROOT);
+ return (err);
+}
+
+static int undo_update_value(ntfs_volume *vol,
+ const struct ACTION_RECORD *action, char *buffer)
+{
+ LCN lcn;
+ const char *data;
+ u32 length;
+ u32 target;
+ u32 count;
+ int changed;
+ int err;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ data = ((const char*)&action->record)
+ + get_undo_offset(&action->record);
+ length = le16_to_cpu(action->record.undo_length);
+ target = le16_to_cpu(action->record.record_offset)
+ + le16_to_cpu(action->record.attribute_offset);
+ count = le16_to_cpu(action->record.lcns_to_follow);
+ if (optv > 1) {
+ lcn = le64_to_cpu(action->record.lcn_list[0]);
+ printf("-> lcn 0x%llx target 0x%x length %d\n",
+ (long long)lcn, (int)target, (int)length);
+ }
+ if (length) {
+ if (optv > 1) {
+ printf("-> existing record :\n");
+ dump(&buffer[target], length);
+ }
+ if ((target + length) <= (count << clusterbits)) {
+ changed = memcmp(buffer + target, data, length);
+ err = 0;
+ if (changed) {
+ memcpy(buffer + target, data, length);
+ if (optv > 1) {
+ printf("-> new record :\n");
+ dump(buffer + target, length);
+ }
+ err = write_raw(vol, &action->record, buffer);
+ }
+ if (optv > 1) {
+ printf("-> data record %s\n",
+ (changed ? "updated" : "unchanged"));
+ }
+ }
+ } else {
+ /*
+ * No undo data, we cannot undo, sometimes the redo
+ * data even overflows from record.
+ * Just ignore for now.
+ */
+ if (optv)
+ printf("Cannot undo, there is no undo data\n");
+ err = 0;
+ }
+
+ return (err);
+}
+
+static int undo_write_end(ntfs_volume *vol,
+ const struct ACTION_RECORD *action, char *buffer)
+{
+ LCN lcn;
+ const char *data;
+ u32 target;
+ u32 length;
+ u32 oldlength;
+ u32 end;
+ u32 undo;
+ int err;
+ int changed;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ end = le32_to_cpu(action->record.client_data_length)
+ + LOG_RECORD_HEAD_SZ;
+ length = le16_to_cpu(action->record.undo_length);
+ undo = get_undo_offset(&action->record);
+ if ((undo + length) > end)
+ data = (char*)NULL;
+ else
+ data = ((const char*)&action->record) + undo;
+ oldlength = le16_to_cpu(action->record.redo_length);
+ target = le16_to_cpu(action->record.record_offset)
+ + le16_to_cpu(action->record.attribute_offset);
+ if (length == oldlength) {
+ if (optv > 1) {
+ lcn = le64_to_cpu(action->record.lcn_list[0]);
+ printf("-> inode %lld lcn 0x%llx target 0x%x"
+ " length %d\n",
+ (long long)inode_number(&action->record),
+ (long long)lcn, (int)target, (int)length);
+ }
+ if (optv > 1) {
+ printf("-> existing record :\n");
+ dump(&buffer[target], length);
+ }
+ if ((target + length) <= mftrecsz) {
+ changed = memcmp(buffer + target, data, length);
+ err = 0;
+ if (changed) {
+ memcpy(buffer + target, data, length);
+ if (optv > 1) {
+ printf("-> new record :\n");
+ dump(buffer + target, length);
+ }
+ err = write_protected(vol, &action->record,
+ buffer, mftrecsz);
+ }
+ if (optv > 1) {
+ printf("-> MFT record %s\n",
+ (changed ? "updated" : "unchanged"));
+ }
+ }
+ } else {
+ if (length > oldlength)
+ err = add_resident(vol, action, buffer, data,
+ target, length, oldlength);
+ else
+ err = delete_resident(vol, action, buffer, data,
+ target, length, oldlength);
+ }
+ return (err);
+}
+
+static int undo_write_index(ntfs_volume *vol,
+ const struct ACTION_RECORD *action, char *buffer)
+{
+ LCN lcn;
+ const char *data;
+ u32 target;
+ u32 length;
+ u32 oldlength;
+ u32 end;
+ u32 undo;
+ int err;
+ int changed;
+
+ if (optv > 1)
+ printf("-> %s()\n",__func__);
+ err = 1;
+ end = le32_to_cpu(action->record.client_data_length)
+ + LOG_RECORD_HEAD_SZ;
+ length = le16_to_cpu(action->record.undo_length);
+ undo = get_undo_offset(&action->record);
+ if ((undo + length) > end)
+ data = (char*)NULL;
+ else
+ data = ((const char*)&action->record) + undo;
+ oldlength = le16_to_cpu(action->record.redo_length);
+ target = le16_to_cpu(action->record.record_offset)
+ + le16_to_cpu(action->record.attribute_offset);
+ if (length == oldlength) {
+ if (optv > 1) {
+ lcn = le64_to_cpu(action->record.lcn_list[0]);
+ printf("-> inode %lld lcn 0x%llx target 0x%x"
+ " length %d\n",
+ (long long)inode_number(&action->record),
+ (long long)lcn, (int)target, (int)length);
+ }
+ if (optv > 1) {
+ printf("-> existing record :\n");
+ dump(&buffer[target], length);
+ }
+ if ((target + length) <= mftrecsz) {
+ changed = memcmp(buffer + target, data, length);
+ err = 0;
+ if (changed) {
+ memcpy(buffer + target, data, length);
+ if (optv > 1) {
+ printf("-> new record :\n");
+ dump(buffer + target, length);
+ }
+ err = write_protected(vol, &action->record,
+ buffer, mftrecsz);
+ }
+ if (optv > 1) {
+ printf("-> MFT record %s\n",
+ (changed ? "updated" : "unchanged"));
+ }
+ }
+ } else {
+ if (length > oldlength)
+ err = add_non_resident(/*vol, action, data,
+ target, length, oldlength*/);
+ else
+ err = delete_non_resident(/*vol, action, data,
+ target, length, oldlength*/);
+ }
+ return (err);
+}
+
+enum ACTION_KIND { ON_NONE, ON_MFT, ON_INDX, ON_RAW } ;
+
+static enum ACTION_KIND get_action_kind(const struct ACTION_RECORD *action)
+{
+ struct ATTR *pa;
+ const char *data;
+ enum ACTION_KIND kind;
+ /*
+ * If we are sure the action was defined by Vista
+ * or subsequent, just use attribute_flags.
+ * Unfortunately, only on some cases we can determine
+ * the action was defined by Win10 (or subsequent).
+ */
+ if (action->record.log_record_flags
+ & const_cpu_to_le16(RECORD_DELETING | RECORD_ADDING)) {
+ if (action->record.attribute_flags
+ & const_cpu_to_le16(ACTS_ON_INDX))
+ kind = ON_INDX;
+ else
+ if (action->record.attribute_flags
+ & const_cpu_to_le16(ACTS_ON_MFT))
+ kind = ON_MFT;
+ else
+ kind = ON_RAW;
+ } else {
+ /*
+ * In other cases, we have to rely on the attribute
+ * definition, but this has defects when undoing.
+ */
+ pa = getattrentry(le16_to_cpu(
+ action->record.target_attribute),0);
+ if (!pa || !pa->type) {
+ /*
+ * Even when the attribute has not been recorded,
+ * we can sometimes tell the record does not apply
+ * to MFT or INDX : such records always have a zero
+ * record_offset, and if attribute_offset is zero, their
+ * magic can be checked. If neither condition is true,
+ * the action cannot apply to MFT or INDX.
+ * (this is useful for undoing)
+ */
+ data = (const char*)&action->record
+ + get_redo_offset(&action->record);
+ if (action->record.record_offset
+ || (!action->record.attribute_offset
+ && (le16_to_cpu(action->record.redo_length)
+ >= 4)
+ && memcmp(data,"FILE",4)
+ && memcmp(data,"INDX",4))) {
+ kind = ON_RAW;
+ } else {
+ printf("** Error : attribute 0x%x"
+ " is not defined\n",
+ (int)le16_to_cpu(
+ action->record.target_attribute));
+ kind = ON_NONE;
+ }
+ } else {
+ if (pa->type == AT_INDEX_ALLOCATION)
+ kind = ON_INDX;
+ else
+ kind = ON_RAW;
+ }
+ }
+ return (kind);
+}
+
+
+/*
+ * Display the redo actions which were executed
+ *
+ * Useful for getting indications on the coverage of a test
+ */
+
+void show_redos(void)
+{
+ int i;
+
+ if (optv && redos_met) {
+ printf("Redo actions which were executed :\n");
+ for (i=0; i<64; i++)
+ if ((((u64)1) << i) & redos_met)
+ printf("%s\n", actionname(i));
+ }
+}
+
+static int distribute_redos(ntfs_volume *vol,
+ const struct ACTION_RECORD *action, char *buffer)
+{
+ int rop, uop;
+ int err;
+
+ err = 0;
+ rop = le16_to_cpu(action->record.redo_operation);
+ uop = le16_to_cpu(action->record.undo_operation);
+ switch (rop) {
+ case AddIndexEntryAllocation :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(DeleteIndexEntryAllocation))
+ err = redo_add_index(vol, action, buffer);
+ break;
+ case AddIndexEntryRoot :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(DeleteIndexEntryRoot))
+ err = redo_add_root_index(vol, action, buffer);
+ break;
+ case ClearBitsInNonResidentBitMap :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(SetBitsInNonResidentBitMap))
+ err = redo_force_bits(vol, action, buffer);
+ break;
+ case CompensationlogRecord :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(Noop))
+ err = redo_compensate(vol, action, buffer);
+ break;
+ case CreateAttribute :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(DeleteAttribute))
+ err = redo_create_attribute(vol, action, buffer);
+ break;
+ case DeallocateFileRecordSegment :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(InitializeFileRecordSegment))
+ err = redo_delete_file(vol, action, buffer);
+ break;
+ case DeleteAttribute :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(CreateAttribute))
+ err = redo_delete_attribute(vol, action, buffer);
+ break;
+ case DeleteIndexEntryAllocation :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(AddIndexEntryAllocation))
+ err = redo_delete_index(vol, action, buffer);
+ break;
+ case DeleteIndexEntryRoot :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(AddIndexEntryRoot))
+ err = redo_delete_root_index(vol, action, buffer);
+ break;
+ case InitializeFileRecordSegment :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(Noop))
+ err = redo_create_file(vol, action, buffer);
+ break;
+ case OpenNonResidentAttribute :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(Noop))
+ err = redo_open_attribute(vol, action);
+ break;
+ case SetBitsInNonResidentBitMap :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(ClearBitsInNonResidentBitMap))
+ err = redo_force_bits(vol, action, buffer);
+ break;
+ case SetIndexEntryVcnAllocation :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(SetIndexEntryVcnAllocation))
+ err = redo_update_vcn(vol, action, buffer);
+ break;
+ case SetIndexEntryVcnRoot :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(SetIndexEntryVcnRoot))
+ err = redo_update_root_vcn(vol, action, buffer);
+ break;
+ case SetNewAttributeSizes :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(SetNewAttributeSizes))
+ err = redo_sizes(vol, action, buffer);
+ break;
+ case UpdateFileNameAllocation :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(UpdateFileNameAllocation))
+ err = redo_update_index(vol, action, buffer);
+ break;
+ case UpdateFileNameRoot :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(UpdateFileNameRoot))
+ err = redo_update_root_index(vol, action, buffer);
+ break;
+ case UpdateMappingPairs :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(UpdateMappingPairs))
+ err = redo_update_mapping(vol, action, buffer);
+ break;
+ case UpdateNonResidentValue :
+ switch (get_action_kind(action)) {
+ case ON_INDX :
+ err = redo_update_index_value(vol, action, buffer);
+ break;
+ case ON_RAW :
+ err = redo_update_value(vol, action, buffer);
+ break;
+ default :
+ printf("** Bad attribute type\n");
+ err = 1;
+ }
+ break;
+ case UpdateResidentValue :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(UpdateResidentValue))
+ err = redo_update_resident(vol, action, buffer);
+ break;
+ case Win10Action37 :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(Noop))
+ err = redo_action37(vol, action, buffer);
+ break;
+ case WriteEndofFileRecordSegment :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(WriteEndofFileRecordSegment))
+ err = redo_write_end(vol, action, buffer);
+ break;
+ case WriteEndOfIndexBuffer :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(WriteEndOfIndexBuffer))
+ err = redo_write_index(vol, action, buffer);
+ break;
+ case AttributeNamesDump :
+ case DirtyPageTableDump :
+ case ForgetTransaction :
+ case Noop :
+ case OpenAttributeTableDump :
+ break;
+ default :
+ printf("** Unsupported redo %s\n", actionname(rop));
+ err = 1;
+ break;
+ }
+ redos_met |= ((u64)1) << rop;
+ if (err)
+ printf("* Redoing action %d %s (%s) failed\n",
+ action->num,actionname(rop), actionname(uop));
+ return (err);
+}
+
+/*
+ * Play the redo actions from earliest to latest
+ *
+ * Currently we can only redo the last undone transaction,
+ * otherwise the attribute table would be out of phase.
+ */
+
+int play_redos(ntfs_volume *vol, const struct ACTION_RECORD *firstaction)
+{
+ const struct ACTION_RECORD *action;
+ MFT_RECORD *entry;
+ INDEX_BLOCK *indx;
+ char *buffer;
+ s64 this_lsn;
+ s64 data_lsn;
+ u32 xsize;
+ u16 rop;
+ u16 uop;
+ int err;
+ BOOL warn;
+ BOOL executed;
+ enum ACTION_KIND kind;
+
+ err = 0;
+ action = firstaction;
+ while (action && !err) {
+ this_lsn = le64_to_cpu(action->record.this_lsn);
+ /* Only committed actions should be redone */
+ if ((!optc || within_lcn_range(&action->record))
+ && (action->flags & ACTION_TO_REDO)) {
+ rop = le16_to_cpu(action->record.redo_operation);
+ uop = le16_to_cpu(action->record.undo_operation);
+ if (optv)
+ printf("Redo action %d %s (%s) 0x%llx\n",
+ action->num,
+ actionname(rop), actionname(uop),
+ (long long)le64_to_cpu(
+ action->record.this_lsn));
+ buffer = (char*)NULL;
+ switch (rop) {
+ /* Actions always acting on MFT */
+ case AddIndexEntryRoot :
+ case CreateAttribute :
+ case DeallocateFileRecordSegment :
+ case DeleteAttribute :
+ case DeleteIndexEntryRoot :
+ case InitializeFileRecordSegment :
+ case SetIndexEntryVcnRoot :
+ case SetNewAttributeSizes :
+ case UpdateFileNameRoot :
+ case UpdateMappingPairs :
+ case UpdateResidentValue :
+ case Win10Action37 :
+ case WriteEndofFileRecordSegment :
+ kind = ON_MFT;
+ break;
+ /* Actions always acting on INDX */
+ case AddIndexEntryAllocation :
+ case DeleteIndexEntryAllocation :
+ case SetIndexEntryVcnAllocation :
+ case UpdateFileNameAllocation :
+ case WriteEndOfIndexBuffer :
+ kind = ON_INDX;
+ break;
+ /* Actions never acting on MFT or INDX */
+ case ClearBitsInNonResidentBitMap :
+ case SetBitsInNonResidentBitMap :
+ kind = ON_RAW;
+ break;
+ /* Actions which may act on MFT */
+ case Noop : /* on MFT if DeallocateFileRecordSegment */
+ kind = ON_NONE;
+ break;
+ /* Actions which may act on INDX */
+ case UpdateNonResidentValue :
+ /* Known cases : INDX, $SDS, ATTR_LIST */
+ kind = get_action_kind(action);
+ if (kind == ON_NONE)
+ err = 1;
+ break;
+ case CompensationlogRecord :
+ case OpenNonResidentAttribute :
+ /* probably not important */
+ kind = ON_NONE;
+ break;
+ /* Actions currently ignored */
+ case AttributeNamesDump :
+ case DirtyPageTableDump :
+ case ForgetTransaction :
+ case OpenAttributeTableDump :
+ case TransactionTableDump :
+ kind = ON_NONE;
+ break;
+ /* Actions with no known use case */
+ case CommitTransaction :
+ case DeleteDirtyClusters :
+ case EndTopLevelAction :
+ case HotFix :
+ case PrepareTransaction :
+ case UpdateRecordDataAllocation :
+ case UpdateRecordDataRoot :
+ case Win10Action35 :
+ case Win10Action36 :
+ default :
+ err = 1;
+ kind = ON_NONE;
+ break;
+ }
+ executed = FALSE;
+ switch (kind) {
+ case ON_MFT :
+/*
+ the check below cannot be used on WinXP
+if (!(action->record.attribute_flags & const_cpu_to_le16(ACTS_ON_MFT)))
+printf("** %s (action %d) not acting on MFT\n",actionname(rop),(int)action->num);
+*/
+ /* Check whether data is to be discarded */
+ warn = (rop != InitializeFileRecordSegment)
+ || !check_full_mft(action,TRUE);
+ buffer = read_protected(vol, &action->record,
+ mftrecsz, warn);
+ entry = (MFT_RECORD*)buffer;
+ if (entry && (entry->magic == magic_FILE)) {
+ data_lsn = le64_to_cpu(entry->lsn);
+ /*
+ * Beware of records not updated
+ * during the last session which may
+ * have a stale lsn (consequence
+ * of ntfs-3g resetting the log)
+ */
+ executed = ((s64)(data_lsn
+ - this_lsn) >= 0)
+ && (((s64)(data_lsn
+ - latest_lsn)) <= 0)
+ && !exception(action->num);
+ } else {
+ if (!warn) {
+ /* Old record not needed */
+ if (!buffer)
+ buffer =
+ (char*)malloc(mftrecsz);
+ if (buffer)
+ executed = FALSE;
+ else
+ err = 1;
+ } else {
+ printf("** %s (action %d) not"
+ " acting on MFT\n",
+ actionname(rop),
+ (int)action->num);
+ err = 1;
+ }
+ }
+ break;
+ case ON_INDX :
+/*
+ the check below cannot be used on WinXP
+if (!(action->record.attribute_flags & const_cpu_to_le16(ACTS_ON_INDX)))
+printf("** %s (action %d) not acting on INDX\n",actionname(rop),(int)action->num);
+*/
+ xsize = vol->indx_record_size;
+ /* Check whether data is to be discarded */
+ warn = (rop != UpdateNonResidentValue)
+ || !check_full_index(action,TRUE);
+ buffer = read_protected(vol, &action->record,
+ xsize, warn);
+ indx = (INDEX_BLOCK*)buffer;
+ if (indx && (indx->magic == magic_INDX)) {
+ data_lsn = le64_to_cpu(indx->lsn);
+ /*
+ * Beware of records not updated
+ * during the last session which may
+ * have a stale lsn (consequence
+ * of ntfs-3g resetting the log)
+ */
+ executed = ((s64)(data_lsn
+ - this_lsn) >= 0)
+ && (((s64)(data_lsn
+ - latest_lsn)) <= 0)
+ && ! exception(action->num);
+ } else {
+ if (!warn) {
+ /* Old record not needed */
+ if (!buffer)
+ buffer =
+ (char*)malloc(xsize);
+ if (buffer)
+ executed = FALSE;
+ else
+ err = 1;
+ } else {
+ printf("** %s (action %d) not"
+ " acting on INDX\n",
+ actionname(rop),
+ (int)action->num);
+ err = 1;
+ }
+ }
+ break;
+ case ON_RAW :
+ if (action->record.attribute_flags
+ & (const_cpu_to_le16(
+ ACTS_ON_INDX | ACTS_ON_MFT))) {
+ printf("** Error : action %s on MFT"
+ " or INDX\n",
+ actionname(rop));
+ err = 1;
+ } else {
+ buffer = read_raw(vol, &action->record);
+ if (!buffer)
+ err = 1;
+ }
+ break;
+ default :
+ buffer = (char*)NULL;
+ break;
+ }
+ if (!err && (!executed || !opts)) {
+ err = distribute_redos(vol, action, buffer);
+ redocount++;
+ } else {
+ if (optv)
+ printf("Action %d %s (%s) not redone\n",
+ action->num,
+ actionname(rop),
+ actionname(uop));
+ }
+ if (buffer)
+ free(buffer);
+ }
+ if (!err)
+ action = action->next;
+ }
+ return (err);
+}
+
+static int distribute_undos(ntfs_volume *vol, const struct ACTION_RECORD *action,
+ char *buffer)
+{
+ int rop, uop;
+ int err;
+
+ err = 0;
+ rop = le16_to_cpu(action->record.redo_operation);
+ uop = le16_to_cpu(action->record.undo_operation);
+ switch (rop) {
+ case AddIndexEntryAllocation :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(DeleteIndexEntryAllocation))
+ err = undo_add_index(vol, action, buffer);
+ break;
+ case AddIndexEntryRoot :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(DeleteIndexEntryRoot))
+ err = undo_add_root_index(vol, action, buffer);
+ break;
+ case ClearBitsInNonResidentBitMap :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(SetBitsInNonResidentBitMap))
+ err = undo_force_bits(vol, action, buffer);
+ break;
+ case CreateAttribute :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(DeleteAttribute))
+ err = undo_create_attribute(vol, action, buffer);
+ break;
+ case DeallocateFileRecordSegment :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(InitializeFileRecordSegment))
+ err = undo_delete_file(vol, action, buffer);
+ break;
+ case DeleteAttribute :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(CreateAttribute))
+ err = undo_delete_attribute(vol, action, buffer);
+ break;
+ case DeleteIndexEntryAllocation :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(AddIndexEntryAllocation))
+ err = undo_delete_index(vol, action, buffer);
+ break;
+ case DeleteIndexEntryRoot :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(AddIndexEntryRoot))
+ err = undo_delete_root_index(vol, action, buffer);
+ break;
+ case InitializeFileRecordSegment :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(Noop))
+ err = undo_create_file(vol, action, buffer);
+ break;
+ case OpenNonResidentAttribute :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(Noop))
+ err = undo_open_attribute(vol, action);
+ break;
+ case SetBitsInNonResidentBitMap :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(ClearBitsInNonResidentBitMap))
+ err = undo_force_bits(vol, action, buffer);
+ break;
+ case SetIndexEntryVcnAllocation :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(SetIndexEntryVcnAllocation))
+ err = undo_update_vcn(vol, action, buffer);
+ break;
+ case SetIndexEntryVcnRoot :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(SetIndexEntryVcnRoot))
+ err = undo_update_root_vcn(vol, action, buffer);
+ break;
+ case SetNewAttributeSizes :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(SetNewAttributeSizes))
+ err = undo_sizes(vol, action, buffer);
+ break;
+ case UpdateFileNameAllocation :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(UpdateFileNameAllocation))
+ err = undo_update_index(vol, action, buffer);
+ break;
+ case UpdateFileNameRoot :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(UpdateFileNameRoot))
+ err = undo_update_root_index(vol, action, buffer);
+ break;
+ case UpdateMappingPairs :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(UpdateMappingPairs))
+ err = undo_update_mapping(vol, action, buffer);
+ break;
+ case UpdateNonResidentValue :
+ switch (get_action_kind(action)) {
+ case ON_INDX :
+ err = undo_update_index_value(vol, action, buffer);
+ break;
+ case ON_RAW :
+ err = undo_update_value(vol, action, buffer);
+ break;
+ default :
+ printf("** Bad attribute type\n");
+ err = 1;
+ }
+ break;
+ case UpdateResidentValue :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(UpdateResidentValue))
+ err = undo_update_resident(vol, action, buffer);
+ break;
+ case Win10Action37 :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(Noop))
+ err = undo_action37(vol, action, buffer);
+ break;
+ case WriteEndofFileRecordSegment :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(WriteEndofFileRecordSegment))
+ err = undo_write_end(vol, action, buffer);
+ break;
+ case WriteEndOfIndexBuffer :
+ if (action->record.undo_operation
+ == const_cpu_to_le16(WriteEndOfIndexBuffer))
+ err = undo_write_index(vol, action, buffer);
+ break;
+ case AttributeNamesDump :
+ case CompensationlogRecord :
+ case DirtyPageTableDump :
+ case ForgetTransaction :
+ case Noop :
+ case OpenAttributeTableDump :
+ break;
+ default :
+ printf("** Unsupported undo %s\n", actionname(rop));
+ err = 1;
+ break;
+ }
+ if (err)
+ printf("* Undoing action %d %s (%s) failed\n",
+ action->num,actionname(rop), actionname(uop));
+ return (err);
+}
+
+/*
+ * Play the undo actions from latest to earliest
+ *
+ * For structured record, a check is made on the lsn to only
+ * try to undo the actions which were executed. This implies
+ * identifying actions on a structured record.
+ *
+ * Returns 0 if successful
+ */
+
+int play_undos(ntfs_volume *vol, const struct ACTION_RECORD *lastaction)
+{
+ const struct ACTION_RECORD *action;
+ MFT_RECORD *entry;
+ INDEX_BLOCK *indx;
+ char *buffer;
+ u32 xsize;
+ u16 rop;
+ u16 uop;
+ int err;
+ BOOL executed;
+ enum ACTION_KIND kind;
+
+ err = 0;
+ action = lastaction;
+ while (action && !err) {
+ if (!optc || within_lcn_range(&action->record)) {
+ rop = le16_to_cpu(action->record.redo_operation);
+ uop = le16_to_cpu(action->record.undo_operation);
+ if (optv)
+ printf("Undo action %d %s (%s) lsn 0x%llx\n",
+ action->num,
+ actionname(rop), actionname(uop),
+ (long long)le64_to_cpu(
+ action->record.this_lsn));
+ buffer = (char*)NULL;
+ executed = FALSE;
+ kind = ON_NONE;
+ switch (rop) {
+ /* Actions always acting on MFT */
+ case AddIndexEntryRoot :
+ case CreateAttribute :
+ case DeallocateFileRecordSegment :
+ case DeleteAttribute :
+ case DeleteIndexEntryRoot :
+ case InitializeFileRecordSegment :
+ case SetIndexEntryVcnRoot :
+ case SetNewAttributeSizes :
+ case UpdateFileNameRoot :
+ case UpdateMappingPairs :
+ case UpdateResidentValue :
+ case Win10Action37 :
+ case WriteEndofFileRecordSegment :
+ kind = ON_MFT;
+ break;
+ /* Actions always acting on INDX */
+ case AddIndexEntryAllocation :
+ case DeleteIndexEntryAllocation :
+ case SetIndexEntryVcnAllocation :
+ case UpdateFileNameAllocation :
+ case WriteEndOfIndexBuffer :
+ kind = ON_INDX;
+ break;
+ /* Actions never acting on MFT or INDX */
+ case ClearBitsInNonResidentBitMap :
+ case SetBitsInNonResidentBitMap :
+ kind = ON_RAW;
+ break;
+ /* Actions which may act on MFT */
+ case Noop : /* on MFT if DeallocateFileRecordSegment */
+ break;
+ /* Actions which may act on INDX */
+ case UpdateNonResidentValue :
+ /* Known cases : INDX, $SDS, ATTR_LIST */
+ kind = get_action_kind(action);
+ if (kind == ON_NONE)
+ err = 1;
+ break;
+ case OpenNonResidentAttribute :
+ /* probably not important */
+ kind = ON_NONE;
+ break;
+ /* Actions currently ignored */
+ case AttributeNamesDump :
+ case CommitTransaction :
+ case CompensationlogRecord :
+ case DeleteDirtyClusters :
+ case DirtyPageTableDump :
+ case EndTopLevelAction :
+ case ForgetTransaction :
+ case HotFix :
+ case OpenAttributeTableDump :
+ case PrepareTransaction :
+ case TransactionTableDump :
+ case UpdateRecordDataAllocation :
+ case UpdateRecordDataRoot :
+ case Win10Action35 :
+ case Win10Action36 :
+ kind = ON_NONE;
+ break;
+ }
+ switch (kind) {
+ case ON_MFT :
+/*
+ the check below cannot be used on WinXP
+if (!(action->record.attribute_flags & const_cpu_to_le16(ACTS_ON_MFT)))
+printf("** %s (action %d) not acting on MFT\n",actionname(rop),(int)action->num);
+*/
+ buffer = read_protected(vol, &action->record,
+ mftrecsz, TRUE);
+ entry = (MFT_RECORD*)buffer;
+ if (entry) {
+ if (entry->magic == magic_FILE) {
+ executed = !older_record(entry,
+ &action->record);
+ if (!executed
+ && exception(action->num))
+ executed = TRUE;
+if (optv > 1)
+printf("record lsn 0x%llx is %s than action %d lsn 0x%llx\n",
+(long long)le64_to_cpu(entry->lsn),
+(executed ? "not older" : "older"),
+(int)action->num,
+(long long)le64_to_cpu(action->record.this_lsn));
+ } else {
+ printf("** %s (action %d) not"
+ " acting on MFT\n",
+ actionname(rop),
+ (int)action->num);
+ err = 1;
+ }
+ } else {
+// TODO make sure this is about a newly allocated record (with bad fixup)
+// TODO check this is inputting a full record (record lth == data lth)
+ buffer = (char*)malloc(mftrecsz);
+ }
+ break;
+ case ON_INDX :
+/*
+ the check below cannot be used on WinXP
+if (!(action->record.attribute_flags & const_cpu_to_le16(ACTS_ON_INDX)))
+printf("** %s (action %d) not acting on INDX\n",actionname(rop),(int)action->num);
+*/
+ xsize = vol->indx_record_size;
+ buffer = read_protected(vol, &action->record,
+ xsize, TRUE);
+ indx = (INDEX_BLOCK*)buffer;
+ if (indx) {
+ if (indx->magic == magic_INDX) {
+ executed = !older_record(indx,
+ &action->record);
+ if (!executed
+ && exception(action->num))
+ executed = TRUE;
+if (optv > 1)
+printf("index lsn 0x%llx is %s than action %d lsn 0x%llx\n",
+(long long)le64_to_cpu(indx->lsn),
+(executed ? "not older" : "older"),
+(int)action->num,
+(long long)le64_to_cpu(action->record.this_lsn));
+ } else {
+ printf("** %s (action %d) not"
+ " acting on INDX\n",
+ actionname(rop),
+ (int)action->num);
+ err = 1;
+ }
+ } else {
+// TODO make sure this is about a newly allocated record (with bad fixup)
+// TODO check this is inputting a full record (record lth == data lth)
+// recreate an INDX record if this is the first entry
+ buffer = (char*)malloc(xsize);
+ err = create_indx(vol, action, buffer);
+ executed = TRUE;
+ }
+ break;
+ case ON_RAW :
+ if (action->record.attribute_flags
+ & (const_cpu_to_le16(
+ ACTS_ON_INDX | ACTS_ON_MFT))) {
+ printf("** Error : action %s on MFT"
+ " or INDX\n",
+ actionname(rop));
+ err = 1;
+ } else {
+ buffer = read_raw(vol, &action->record);
+ if (!buffer)
+ err = 1;
+ }
+ executed = TRUE;
+ break;
+ default :
+ executed = TRUE;
+ buffer = (char*)NULL;
+ break;
+ }
+ if (!err && executed) {
+ err = distribute_undos(vol, action, buffer);
+ undocount++;
+ }
+ if (buffer)
+ free(buffer);
+ }
+ if (!err)
+ action = action->prev;
+ }
+ return (err);
+}