diff --git a/configure.ac b/configure.ac index 6f293784..c2cf8cd1 100644 --- a/configure.ac +++ b/configure.ac @@ -657,6 +657,7 @@ AC_CONFIG_FILES([ ntfsprogs/ntfswipe.8 ntfsprogs/ntfstruncate.8 ntfsprogs/ntfsfallocate.8 + ntfsprogs/ntfsrecover.8 src/Makefile src/ntfs-3g.8 src/ntfs-3g.probe.8 diff --git a/ntfsprogs/Makefile.am b/ntfsprogs/Makefile.am index 6b2b8aa5..0bbc70c5 100644 --- a/ntfsprogs/Makefile.am +++ b/ntfsprogs/Makefile.am @@ -17,7 +17,7 @@ if ENABLE_NTFSPROGS bin_PROGRAMS = ntfsfix ntfsinfo ntfscluster ntfsls ntfscat ntfscmp sbin_PROGRAMS = mkntfs ntfslabel ntfsundelete ntfsresize ntfsclone \ ntfscp -EXTRA_PROGRAM_NAMES = ntfswipe ntfstruncate +EXTRA_PROGRAM_NAMES = ntfswipe ntfstruncate ntfsrecover QUARANTINED_PROGRAM_NAMES = ntfsdump_logfile ntfsmftalloc ntfsmove ntfsck \ ntfsfallocate @@ -26,7 +26,7 @@ man_MANS = mkntfs.8 ntfsfix.8 ntfslabel.8 ntfsinfo.8 \ ntfsundelete.8 ntfsresize.8 ntfsprogs.8 ntfsls.8 \ ntfsclone.8 ntfscluster.8 ntfscat.8 ntfscp.8 \ ntfscmp.8 ntfswipe.8 ntfstruncate.8 \ - ntfsdecrypt.8 ntfsfallocate.8 + ntfsdecrypt.8 ntfsfallocate.8 ntfsrecover.8 EXTRA_MANS = CLEANFILES = $(EXTRA_PROGRAMS) @@ -102,6 +102,10 @@ ntfscmp_SOURCES = ntfscmp.c utils.c utils.h ntfscmp_LDADD = $(AM_LIBS) ntfscmp_LDFLAGS = $(AM_LFLAGS) +ntfsrecover_SOURCES = playlog.c ntfsrecover.c utils.c utils.h ntfsrecover.h +ntfsrecover_LDADD = $(AM_LIBS) $(NTFSRECOVER_LIBS) +ntfsrecover_LDFLAGS = $(AM_LFLAGS) + # We don't distribute these ntfstruncate_SOURCES = attrdef.c ntfstruncate.c utils.c utils.h diff --git a/ntfsprogs/ntfsrecover.8.in b/ntfsprogs/ntfsrecover.8.in new file mode 100644 index 00000000..1fe2e449 --- /dev/null +++ b/ntfsprogs/ntfsrecover.8.in @@ -0,0 +1,165 @@ +.\" Copyright (c) 2015 Jean-Pierre Andre +.\" This file may be copied under the terms of the GNU Public License. +.\" +.TH NTFSRECOVER 8 "September 2015" "ntfs-3g @VERSION@" +.SH NAME +ntfsrecover \- Recover updates committed by Windows on an NTFS volume +.SH SYNOPSIS +\fBntfsrecover\fR [\fIoptions\fR] \fIdevice\fR +.SH DESCRIPTION +.B ntfsrecover +applies to the metadata the updates which were requested on Windows but could +not be completed because they were interrupted by some event such as a power +failure, a hardware crash, a software crash or the device being unplugged. +Doing so, the file system is restored to a consistent state, however updates +to user data may still be lost. + +Updating the file system generally requires updating several records which +should all be made for the file system to be kept consistent. For instance, +creating a new file requires reserving an inode number (set a bit in a bit +map), creating a file record (store the file name and file attributes), and +registering the file in a directory (locate the file from some path). When an +unfortunate event occurs, and one of these updates could be done but not all +of them, the file system is left inconsistent. + +A group of updates which have all to be done to preserve consistency is +called a transaction, and the end of updates within a transaction is called +the commitment of the transaction. + +To protect from unfortunate events, Windows first logs in a special file all +the metadata update requests without applying any, until the commitment is +known. If the event occurs before the commitment, no update has been made and +the file system is consistent. If the event occurs after the update, the log +file can be analyzed later and the transactions which were committed can be +executed again, thus restoring the integrity of the file system. + +.B ntfsrecover +similarly examines the log file and applies the updates within committed +transactions which could not be done by Windows. + +Currently, ntfs-3g does not log updates, so +.B ntfsrecover +cannot be used to restore consistency after an unfortunate event occurred +while the file system was updated by Linux. + +.SH OPTIONS +Below is a summary of all the options that +.B ntfsrecover +accepts. The normal usage is to use no option at all, as most of these +options are oriented towards developers needs. + +Nearly all options have two equivalent names. The short name is +preceded by +.B \- +and the long name is preceded by +.BR \-\- . +Any single letter options, that don't take an argument, can be combined into a +single command, e.g. +.B \-bv +is equivalent to +.BR "\-b \-v" . +Long named options can be abbreviated to any unique prefix of their name. +.TP +\fB\-b\fR, \fB\-\-backward\fR +Examine the actions described in the logfile backward from the latest one to +the earliest one without applying any update. This may encompass records +generated during several sessions, and when Windows is restarted, it often +does not restart writing where it ended the previous session, so this leads +to errors and bad sequencing when examining the full log file. +.TP +\fB\-c\fR, \fB\-\-clusters\fR \fBCLUSTER-RANGE\fR +Restrict the output generated when using options -b -f -u -p +to the actions operating on a cluster within the given cluster range. +CLUSTER-RANGE is defined by the first and last cluster numbers separated +by a hyphen, for instance 100-109 or 0x3e8-0x3ff. A single number means +restricting to a single cluster. The first four log blocks have a special +role and they are always shown. +.TP +\fB\-f\fR, \fB\-\-forward\fR \fBNUM\fR +Examine the actions described in the logfile forward from the first one to +the last one without applying any update. As the log file is reused +circularly, the first one is generally not the earliest. Moreover when +Windows is restarted, it often does not restart writing where it ended the +previous sessions, and this leads to errors when examining a log file +generated during several sessions. +.TP +\fB\-h\fR, \fB\-\-help\fR +Show some help information. +.TP +\fB\-n\fR, \fB\-\-no-action\fR +Do not apply any modification, useful when using the options -p, -s or -u. +.TP +\fB\-p\fR, \fB\-\-play\fR \fBCOUNT\fR +Undo COUNT transaction sets and redo a single one, a transaction set being +all transactions between two consecutive checkpoints. This is useful for +replaying some transaction in the past. As a few actions are not undoable, +this is not always possible. +.TP +\fB\-r\fR, \fB\-\-range\fR \fBBLOCK-RANGE\fR +Examine the actions described in the logfile forward restricted to the +requested log file block range without applying any update. The first four +log blocks have a special role and they are always examined. +.TP +\fB\-s\fR, \fB\-\-sync\fR +Sync the file system by applying the committed actions which have not +been synced previously. This is the default option, used when none of +the options -n, -f, -r, -p and -u are present. + +The option -s can be repeated to request applying the committed actions +mentioned in the obsolete restart page. This is useful for testing the +situations where the latest restart page cannot be read though it can +actually be read. +.TP +\fB\-t\fR, \fB\-\-transactions\fR \fBCOUNT\fR +Display the transaction parameters when examining the log file with one +of the options --forward, --backward or --range. +.TP +\fB\-u\fR, \fB\-\-undo\fR \fBCOUNT\fR +Undo COUNT transaction sets, thus resetting the file system to some +checkpoint in the past, a transaction set being all transactions between +two consecutive checkpoints. As a few actions are not undoable, this is +not always possible. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +Display more debug/warning/error messages. This option may be used twice +to display even more information. +.TP +\fB\-V\fR, \fB\-\-version\fR +Show the version number, copyright and license of +.BR ntfsrecover . +.SH EXAMPLES +Sync an NTFS volume on /dev/sda1. +.RS +.sp +.B ntfsrecover -s /dev/sda1 +.sp +.RE +Display all actions which updated a cluster in range 100 to 119 : +.RS +.sp +.B ntfsrecover --verbose --backward --clusters=100-119 /dev/sda1 +.sp +.RE +.SH BUGS +If you find a bug please send an email describing the problem to the +development team: +.br +.nh +ntfs\-3g\-devel@lists.sf.net +.hy +.SH AUTHORS +.B ntfsrecover +was written by Jean-Pierre Andre +.SH AVAILABILITY +.B ntfsrecover +is part of the +.B ntfs-3g +package and is available from: +.br +.nh +http://www.tuxera.com/community/ +.hy +.SH SEE ALSO +.BR ntfs-3g (8), +.BR ntfsfix (8), +.BR ntfsprogs (8) diff --git a/ntfsprogs/ntfsrecover.c b/ntfsprogs/ntfsrecover.c new file mode 100644 index 00000000..ae92c896 --- /dev/null +++ b/ntfsprogs/ntfsrecover.c @@ -0,0 +1,4134 @@ +/* + * 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 +#endif +#ifdef HAVE_STDIO_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_GETOPT_H +#include +#endif +#ifdef HAVE_MALLOC_H +#include +#endif +#ifdef HAVE_TIME_H +#include +#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> 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 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> 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*inamelen; 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 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; (xinode, + 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; ilcns_to_follow) + && (ilcn_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*iunknown_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; ilcns_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 +#endif +#ifdef HAVE_STDIO_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_MALLOC_H +#include +#endif +#ifdef HAVE_TIME_H +#include +#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; (ilcn_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; (ilcn_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; (ilcn_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; (imftmirr_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; irecord, 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 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; irecord, 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); +}