From 0cb0173bbcf3d528a52f878ece26f60ce0c24e90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Mon, 9 Nov 2015 13:18:58 +0100 Subject: [PATCH] Implemented recovery of updates committed by Windows 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 the latest consistent state. No update to libntfs-3g is required by this implementation. --- configure.ac | 1 + ntfsprogs/Makefile.am | 8 +- ntfsprogs/ntfsrecover.8.in | 165 ++ ntfsprogs/ntfsrecover.c | 4134 ++++++++++++++++++++++++++++++ ntfsprogs/ntfsrecover.h | 339 +++ ntfsprogs/playlog.c | 4826 ++++++++++++++++++++++++++++++++++++ 6 files changed, 9471 insertions(+), 2 deletions(-) create mode 100644 ntfsprogs/ntfsrecover.8.in create mode 100644 ntfsprogs/ntfsrecover.c create mode 100644 ntfsprogs/ntfsrecover.h create mode 100644 ntfsprogs/playlog.c 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); +}