OSDN Git Service

Implemented recovery of updates committed by Windows
authorJean-Pierre André <jpandre@users.sourceforge.net>
Mon, 9 Nov 2015 12:18:58 +0000 (13:18 +0100)
committerJean-Pierre André <jpandre@users.sourceforge.net>
Mon, 9 Nov 2015 12:18:58 +0000 (13:18 +0100)
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
ntfsprogs/Makefile.am
ntfsprogs/ntfsrecover.8.in [new file with mode: 0644]
ntfsprogs/ntfsrecover.c [new file with mode: 0644]
ntfsprogs/ntfsrecover.h [new file with mode: 0644]
ntfsprogs/playlog.c [new file with mode: 0644]

index 6f29378..c2cf8cd 100644 (file)
@@ -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
index 6b2b8aa..0bbc70c 100644 (file)
@@ -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 (file)
index 0000000..1fe2e44
--- /dev/null
@@ -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 (file)
index 0000000..ae92c89
--- /dev/null
@@ -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 <stdlib.h>
+#endif
+#ifdef HAVE_STDIO_H
+#include <stdio.h>
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#endif
+#ifdef HAVE_MALLOC_H
+#include <malloc.h>
+#endif
+#ifdef HAVE_TIME_H
+#include <time.h>
+#endif
+
+#include "types.h"
+#include "endians.h"
+#include "support.h"
+#include "layout.h"
+#include "param.h"
+#include "ntfstime.h"
+#include "device_io.h"
+#include "device.h"
+#include "logging.h"
+#include "runlist.h"
+#include "mft.h"
+#include "inode.h"
+#include "attrib.h"
+#include "bitmap.h"
+#include "index.h"
+#include "volume.h"
+#include "unistr.h"
+#include "mst.h"
+#include "ntfsrecover.h"
+#include "utils.h"
+#include "misc.h"
+
+typedef struct {
+       ntfs_volume *vol;
+       FILE *file;
+       struct ACTION_RECORD *firstaction;
+       struct ACTION_RECORD *lastaction;
+} CONTEXT;
+
+typedef enum { T_OK, T_ERR, T_DONE } TRISTATE;
+
+struct RESTART_PAGE_HEADER log_header;
+struct RESTART_AREA restart;
+struct RESTART_CLIENT client;
+u32 clustersz = 0;
+int clusterbits;
+u32 blocksz;
+int blockbits;
+u16 bytespersect;
+u64 mftlcn;
+u32 mftrecsz;
+int mftrecbits;
+u32 mftcnt; /* number of entries */
+ntfs_inode *log_ni;
+ntfs_attr *log_na; 
+u64 logfilelcn;
+u32 logfilesz; /* bytes */
+u64 redos_met;
+u64 committed_lsn;
+u64 synced_lsn;
+u64 latest_lsn;
+u64 restart_lsn;
+unsigned long firstblk; /* first block to dump (option -r) */
+unsigned long lastblk;  /* last block to dump (option -r) */
+u64 firstlcn; /* first block to dump (option -c) */
+u64 lastlcn;  /* last block to dump (option -c) */
+BOOL optb; /* show the log backward */
+BOOL optc; /* restrict to cluster range */
+BOOL optd; /* device argument present*/
+BOOL opth; /* show help */
+BOOL opti; /* show invalid (stale) records */
+BOOL optf; /* show full log */
+BOOL optn; /* do not apply modifications */
+BOOL optp; /* count of transaction sets to play */
+BOOL optr; /* show a range of blocks */
+int opts; /* sync the file system */
+BOOL optt; /* show transactions */
+BOOL optu; /* count of transaction sets to undo */
+int optv; /* verbose */
+int optV; /* version */
+int optx[MAXEXCEPTION + 1];
+struct ATTR **attrtable;
+unsigned int actionnum;
+unsigned int attrcount;
+unsigned int playcount;
+unsigned int playedactions; // change the name
+unsigned int redocount;
+unsigned int undocount;
+struct BUFFER *buffer_table[BASEBLKS + BUFFERCNT];
+
+static const le16 SDS[4] = {
+       const_cpu_to_le16('$'), const_cpu_to_le16('S'),
+       const_cpu_to_le16('D'), const_cpu_to_le16('S')
+} ;
+
+static const le16 I30[4] = {
+       const_cpu_to_le16('$'), const_cpu_to_le16('I'),
+       const_cpu_to_le16('3'), const_cpu_to_le16('0')
+} ;
+
+/*
+ *             Byte address of a log block
+ */
+
+static s64 loclogblk(CONTEXT *ctx, unsigned int blk)
+{
+       s64 loc;
+       LCN lcn;
+
+       if (ctx->vol) {
+               lcn = ntfs_attr_vcn_to_lcn(log_na,
+                               ((s64)blk << blockbits) >> clusterbits);
+               loc = lcn << clusterbits;
+       } else {
+               if (((s64)blk << blockbits) >= logfilesz)
+                       loc = -1;
+               else
+                       loc = (logfilelcn << clusterbits)
+                               + ((s64)blk << blockbits);
+       }
+       return (loc);
+}
+
+/*
+ *             Deprotect a block
+ *     Only to be used for log buffers
+ *
+ *     Returns 0 if block was found correct
+ */
+
+static int replaceusa(struct BUFFER *buffer, unsigned int lth)
+{
+       char *buf;
+       struct RECORD_PAGE_HEADER *record;
+       unsigned int j;
+       BOOL err;
+       unsigned int used;
+       unsigned int xusa, nusa;
+
+       err = FALSE;
+                       /* Restart blocks have no protection */
+       if (buffer->num >= RSTBLKS) {
+                       /* Do not check beyond used sectors */
+               record = &buffer->block.record;
+               used = blocksz;
+               xusa = le16_to_cpu(record->head.usa_ofs);
+               nusa = le16_to_cpu(record->head.usa_count);
+               if (xusa && nusa
+                  && ((xusa + 1) < lth)
+                  && ((nusa - 1)*NTFSBLKLTH == lth)) {
+                       buf = buffer->block.data;
+                       for (j=1; (j<nusa) && ((j-1)*NTFSBLKLTH<used); j++)
+                               if ((buf[xusa] == buf[j*NTFSBLKLTH - 2])
+                                  && (buf[xusa+1] == buf[j*NTFSBLKLTH - 1])) {
+                                       buf[j*NTFSBLKLTH - 2] = buf[xusa + 2*j];
+                                       buf[j*NTFSBLKLTH - 1] = buf[xusa + 2*j + 1];
+                               } else {
+                                       printf("* Update sequence number %d does not match\n",j);
+                                       err = TRUE;
+                               }
+               }
+       }
+   return (err);
+   }
+
+/*
+ *             Dynamically allocate an attribute key.
+ *
+ *     As the possible values for a key depend on the version, we
+ *     cannot convert it to an index, so we make dichotomical searches
+ */
+
+struct ATTR *getattrentry(unsigned int key, unsigned int lth)
+{
+       struct ATTR *pa;
+       struct ATTR **old;
+       unsigned int low, mid, high;
+
+       low = 0;
+       if (attrcount) {
+               high = attrcount;
+               while ((low + 1) < high) {
+                       mid = (low + high) >> 1;
+                       if (key < attrtable[mid]->key)
+                               high = mid;
+                       else
+                               if (key > attrtable[mid]->key)
+                                       low = mid;
+                               else {
+                                       low = mid;
+                                       high = mid + 1;
+                               }
+               }
+       }
+       if ((low < attrcount) && (attrtable[low]->key == key)) {
+               pa = attrtable[low];
+               if (pa->namelen < lth) {
+                       pa = (struct ATTR*)realloc(pa,
+                                       sizeof(struct ATTR) + lth);
+                       attrtable[low] = pa;
+               }
+       } else {
+               mid = low + 1;
+                if (!low && attrcount && (attrtable[0]->key > key))
+                   mid = 0;
+               pa = (struct ATTR*)malloc(sizeof(struct ATTR) + lth);
+               if (pa) {
+                       if (attrcount++) {
+                               old = attrtable;
+                               attrtable = (struct ATTR**)realloc(attrtable,
+                                       attrcount*sizeof(struct ATTR*));
+                               if (attrtable) {
+                                       high = attrcount;
+                                       while (--high > mid)
+                                               attrtable[high]
+                                                       = attrtable[high - 1];
+                                       attrtable[mid] = pa;
+                               } else
+                                       attrtable = old;
+                       } else {
+                               attrtable = (struct ATTR**)
+                                               malloc(sizeof(struct ATTR*));
+                               attrtable[0] = pa;
+                       }
+               pa->key = key;
+               pa->namelen = 0;
+               pa->type = const_cpu_to_le32(0);
+               pa->inode = 0;
+               }
+       }
+       return (pa);
+}
+
+/*
+ *             Read blocks in a circular buffer
+ *
+ *     returns NULL if block cannot be read or it is found bad
+ *             otherwise returns the full unprotected block data
+ */
+
+static const struct BUFFER *read_buffer(CONTEXT *ctx, unsigned int num)
+{
+       struct BUFFER *buffer;
+       BOOL got;
+
+               /*
+                * The first four blocks are stored apart, to make
+                * sure pages 2 and 3 and the page which is logically
+                * before them can be accessed at the same time.
+                * Also, block 0 is smaller because it has to be read
+                * before the block size is known.
+                * Note : the last block is supposed to have an odd
+                * number, and cannot be overwritten by block 4 which
+                * follows logically.
+                */
+       if (num < BASEBLKS)
+               buffer = buffer_table[num + BUFFERCNT];
+       else
+               buffer = buffer_table[num & (BUFFERCNT - 1)];
+       if (buffer && (buffer->size < blocksz)) {
+               free(buffer);
+               buffer = (struct BUFFER*)NULL;
+       }
+       if (!buffer) {
+               buffer = (struct BUFFER*)
+                       malloc(sizeof(struct BUFFER) + blocksz);
+               buffer->size = blocksz;
+               buffer->num = num + 1; /* forced to being read */
+               buffer->safe = FALSE;
+               if (num < BASEBLKS)
+                       buffer_table[num + BUFFERCNT] = buffer;
+               else
+                       buffer_table[num & (BUFFERCNT - 1)] = buffer;
+       }
+       if (buffer && (buffer->num != num)) {
+               buffer->num = num;
+               if (ctx->vol)
+                       got = (ntfs_attr_pread(log_na,(u64)num << blockbits,
+                               blocksz, buffer->block.data) == blocksz);
+               else
+                       got = !fseek(ctx->file, loclogblk(ctx, num), 0)
+                           && (fread(buffer->block.data, blocksz,
+                                               1, ctx->file) == 1);
+               if (got) {
+                       char *data = buffer->block.data;
+                       buffer->headsz = sizeof(struct RECORD_PAGE_HEADER)
+                               + ((2*getle16(data,6) - 1) | 7) + 1;
+                       buffer->safe = !replaceusa(buffer, blocksz);
+               } else {
+                       buffer->safe = FALSE;
+                       fprintf(stderr,"** Could not read block %d\n", num);
+               }
+       }
+       return (buffer && buffer->safe ? buffer : (const struct BUFFER*)NULL);
+}
+
+void hexdump(const char *buf, unsigned int lth)
+{
+       unsigned int i,j,k;
+
+       for (i=0; i<lth; i+=16) {
+               printf("%04x ",i);
+               k = ((lth - i) < 16 ? lth : 16 + i);
+               for (j=i; j<k; j++)
+                       printf((j & 3 ? "%02x" : " %02x"),buf[j] & 255);
+               printf("%*c",(152 - 9*(j - i))/4,' ');
+               for (j=i; j<k; j++)
+                       if ((buf[j] > 0x20) && (buf[j] < 0x7f))
+                               printf("%c",buf[j]);
+                       else
+                               printf(".");
+               printf("\n");
+       }
+}
+
+/*
+ *            Display a date
+ */
+
+static void showdate(const char *text, le64 lestamp)
+{
+       time_t utime;
+       struct tm *ptm;
+       s64 stamp;
+       const char *months[]
+               = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+                   "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" } ;
+
+       stamp = le64_to_cpu(lestamp);
+       if ((stamp < ((2147000000 + 134774*86400LL)*10000000LL))
+           && (stamp > ((-2147000000 + 134774*86400LL)*10000000LL))) {
+                               /* date within traditional Unix limits */
+               utime = stamp/10000000 - 134774*86400LL;
+               ptm = gmtime(&utime);
+               printf("%s %02d %3s %4d %2d:%02d:%02d UTC\n",
+                       text,
+                       ptm->tm_mday,months[ptm->tm_mon],ptm->tm_year+1900,
+                       ptm->tm_hour,ptm->tm_min,ptm->tm_sec);
+       } else {
+               u32 days;
+               unsigned int year;
+               int mon;
+               int cnt;
+
+               days = stamp/(86400*10000000LL);
+               year = 1601;
+                                       /* periods of 400 years */
+               cnt = days/146097;
+               days -= 146097*cnt;
+               year += 400*cnt;
+                                       /* periods of 100 years */
+               cnt = (3*days + 3)/109573;
+               days -= 36524*cnt;
+               year += 100*cnt;
+                                       /* periods of 4 years */
+               cnt = days/1461;
+               days -= 1461L*cnt;
+               year += 4*cnt;
+                                       /* periods of a single year */
+               cnt = (3*days + 3)/1096;
+               days -= 365*cnt;
+               year += cnt;
+
+               if ((!(year % 100) ? (year % 400) : (year % 4))
+                   && (days > 58)) days++;
+               if (days > 59) {
+                       mon = (5*days + 161)/153;
+                       days -= (153*mon - 162)/5;
+               } else {
+                       mon = days/31 + 1;
+                       days -= 31*(mon - 1) - 1;
+               }
+if (mon > 12)
+{
+printf("** Bad day stamp %lld days %lu mon %d year %u\n",
+(long long)stamp,(unsigned long)days,mon,year);
+}
+               printf("%s %02u %3s %4u\n",text,
+                       (unsigned int)days,months[mon-1],(unsigned int)year);
+       }
+}
+
+void showname(const char *prefix, const char *name, int cnt)
+{
+       const le16 *n;
+       int i;
+       int c;
+
+       printf("%s",prefix);
+       n = (const le16*)name;
+       for (i=0; (i<cnt) && n[i]; i++) {
+               c = le16_to_cpu(n[i]);
+               if (c < 0x20)
+                       printf(".");
+               else
+                       if (c < 0x80)
+                               printf("%c",c);
+                       else
+                               if (c < 0x800)
+                                       printf("%c%c",
+                                               (c >> 6) + 0xc0,
+                                               (c & 63) + 0x80);
+                               else
+                                       printf("%c%c%c",
+                                               (c >> 12) + 0xe0,
+                                               ((c >> 6) & 63) + 0x80,
+                                               (c & 63) + 0x80);
+       }
+       printf("\n");
+}
+
+static const char *commitment(u64 lsn)
+{
+       const char *commit;
+       s64 diff;
+
+       /* Computations assume lsn could wraparound, they probably never do */
+       diff = lsn - synced_lsn;
+       if (diff <= 0)
+               commit = "synced";
+       else {
+               diff = lsn - committed_lsn;
+               if (diff <= 0)
+                       commit = "committed";
+               else {
+                       /* may find lsn from older session */
+                       diff = lsn - latest_lsn;
+                       if (diff <= 0)
+                               commit = "*uncommitted*";
+                       else
+                               commit = "*stale*";
+               }
+       }
+       return (commit);
+}
+
+const char *actionname(int op)
+{
+       static char buffer[24];
+       const char *p;
+
+       switch (op) {
+       case Noop :
+               p = "Noop";
+               break;
+       case CompensationlogRecord :
+               p = "CompensationlogRecord";
+               break;
+       case InitializeFileRecordSegment :
+               p = "InitializeFileRecordSegment";
+               break;
+       case DeallocateFileRecordSegment :
+               p = "DeallocateFileRecordSegment";
+               break;
+       case WriteEndofFileRecordSegment :
+               p = "WriteEndofFileRecordSegment";
+               break;
+       case CreateAttribute :
+               p = "CreateAttribute";
+               break;
+       case DeleteAttribute :
+               p = "DeleteAttribute";
+               break;
+       case UpdateResidentValue :
+               p = "UpdateResidentValue";
+               break;
+       case UpdateNonResidentValue :
+               p = "UpdateNonResidentValue";
+               break;
+       case UpdateMappingPairs :
+               p = "UpdateMappingPairs";
+               break;
+       case DeleteDirtyClusters :
+               p = "DeleteDirtyClusters";
+               break;
+       case SetNewAttributeSizes :
+               p = "SetNewAttributeSizes";
+               break;
+       case AddIndexEntryRoot :
+               p = "AddIndexEntryRoot";
+               break;
+       case DeleteIndexEntryRoot :
+               p = "DeleteIndexEntryRoot";
+               break;
+       case AddIndexEntryAllocation :
+               p = "AddIndexEntryAllocation";
+               break;
+       case DeleteIndexEntryAllocation :
+               p = "DeleteIndexEntryAllocation";
+               break;
+       case WriteEndOfIndexBuffer :
+               p = "WriteEndOfIndexBuffer";
+               break;
+       case SetIndexEntryVcnRoot :
+               p = "SetIndexEntryVcnRoot";
+               break;
+       case SetIndexEntryVcnAllocation :
+               p = "SetIndexEntryVcnAllocation";
+               break;
+       case UpdateFileNameRoot :
+               p = "UpdateFileNameRoot";
+               break;
+       case UpdateFileNameAllocation :
+               p = "UpdateFileNameAllocation";
+               break;
+       case SetBitsInNonResidentBitMap :
+               p = "SetBitsInNonResidentBitMap";
+               break;
+       case ClearBitsInNonResidentBitMap :
+               p = "ClearBitsInNonResidentBitMap";
+               break;
+       case HotFix :
+               p = "HotFix";
+               break;
+       case EndTopLevelAction :
+               p = "EndTopLevelAction";
+               break;
+       case PrepareTransaction :
+               p = "PrepareTransaction";
+               break;
+       case CommitTransaction :
+               p = "CommitTransaction";
+               break;
+       case ForgetTransaction :
+               p = "ForgetTransaction";
+               break;
+       case OpenNonResidentAttribute :
+               p = "OpenNonResidentAttribute";
+               break;
+       case OpenAttributeTableDump :
+               p = "OpenAttributeTableDump";
+               break;
+       case AttributeNamesDump :
+               p = "AttributeNamesDump";
+               break;
+       case DirtyPageTableDump :
+               p = "DirtyPageTableDump";
+               break;
+       case TransactionTableDump :
+               p = "TransactionTableDump";
+               break;
+       case UpdateRecordDataRoot :
+               p = "UpdateRecordDataRoot";
+               break;
+       case UpdateRecordDataAllocation :
+               p = "UpdateRecordDataAllocation";
+               break;
+       case Win10Action35 :
+               p = "Win10Action35";
+               break;
+       case Win10Action36 :
+               p = "Win10Action36";
+               break;
+       case Win10Action37 :
+               p = "Win10Action37";
+               break;
+       default  :
+               sprintf(buffer,"*Unknown-Action-%d*",op);
+               p = buffer;
+               break;
+       }
+       return (p);
+}
+
+static const char *attrname(unsigned int key)
+{
+       static char name[256];
+       const char *p;
+       struct ATTR *pa;
+       unsigned int i;
+
+       if ((key <= 65535) && !(key & 3)) {
+               pa = getattrentry(key,0);
+               if (pa) {
+                       if (!pa->namelen)
+                               p = "Unnamed";
+                       else {
+                               p = name;
+                                       /* Assume ascii for now */
+                               for (i=0; 2*i<pa->namelen; i++)
+                                       name[i] = le16_to_cpu(pa->name[i]);
+                               name[i] = 0;
+                       }
+               } else
+                       p = "Undefined";
+       } else
+               p = "Invalid";
+       return (p);
+}
+
+int fixnamelen(const char *name, int len)
+{
+       int i;
+
+       i = 0;
+       while ((i < len) && (name[i] || name[i + 1]))
+               i += 2;
+       return (i);
+}
+
+const char *mftattrname(ATTR_TYPES attr)
+{
+       static char badattr[24];
+       const char *p;
+
+       switch (attr) {
+       case AT_STANDARD_INFORMATION :
+               p = "Standard-Information";
+               break;
+       case AT_ATTRIBUTE_LIST :
+               p = "Attribute-List";
+               break;
+       case AT_FILE_NAME :
+               p = "Name";
+               break;
+       case AT_OBJECT_ID :
+               p = "Volume-Version";
+               break;
+       case AT_SECURITY_DESCRIPTOR :
+               p = "Security-Descriptor";
+               break;
+       case AT_VOLUME_NAME :
+               p = "Volume-Name";
+               break;
+       case AT_VOLUME_INFORMATION :
+               p = "Volume-Information";
+               break;
+       case AT_DATA :
+               p = "Data";
+               break;
+       case AT_INDEX_ROOT :
+               p = "Index-Root";
+               break;
+       case AT_INDEX_ALLOCATION :
+               p = "Index-Allocation";
+               break;
+       case AT_BITMAP :
+               p = "Bitmap";
+               break;
+       case AT_REPARSE_POINT :
+               p = "Reparse-Point";
+               break;
+       case AT_EA_INFORMATION :
+               p = "EA-Information";
+               break;
+       case AT_EA :
+               p = "EA";
+               break;
+       case AT_PROPERTY_SET :
+               p = "Property-Set";
+               break;
+       case AT_LOGGED_UTILITY_STREAM :
+               p = "Logged-Utility-Stream";
+               break;
+       case AT_END :
+               p = "End";
+               break;
+       default :
+               sprintf(badattr,"*0x%x-Unknown*",attr);
+               p = badattr;
+               break;
+       }
+       return (p);
+}
+
+static void showattribute(const char *prefix, const struct ATTR *pa)
+{
+       if (pa) {
+               if (pa->type) {
+                       printf("%sattr 0x%x : inode %lld type %s",
+                               prefix, pa->key, (long long)pa->inode,
+                               mftattrname(pa->type));
+                       if (pa->namelen)
+                               showname(" name ",(const char*)pa->name,
+                                       pa->namelen/2);
+                       else
+                               printf("\n");
+               } else {
+                       if (pa->namelen) {
+                               printf("%sattr 0x%x : type Unknown",
+                                               prefix, pa->key);
+                               showname(" name ",(const char*)pa->name,
+                                               pa->namelen/2);
+                       } else
+                               printf("%s(definition of attr 0x%x not met)\n",
+                                               prefix, pa->key);
+               }
+       }
+}
+
+/*
+ *             Determine if an action acts on the MFT
+ */
+
+static BOOL acts_on_mft(int op)
+{
+       BOOL onmft;
+
+                       /* A few actions may have to be added to the list */
+       switch (op) {
+       case InitializeFileRecordSegment :
+       case DeallocateFileRecordSegment :
+       case CreateAttribute :
+       case DeleteAttribute :
+       case UpdateResidentValue :
+       case UpdateMappingPairs :
+       case SetNewAttributeSizes :
+       case AddIndexEntryRoot :
+       case DeleteIndexEntryRoot :
+       case UpdateFileNameRoot :
+       case WriteEndofFileRecordSegment :
+       case Win10Action37 :
+               onmft = TRUE;
+               break;
+       default :
+               onmft = FALSE;
+               break;
+       }
+       return (onmft);
+}
+
+u32 get_undo_offset(const struct LOG_RECORD *logr)
+{
+       u32 offset;
+
+       if (logr->lcns_to_follow)
+               offset = 0x30 + le16_to_cpu(logr->undo_offset);
+       else
+               offset = 0x28 + le16_to_cpu(logr->undo_offset);
+       return (offset);
+}
+
+u32 get_redo_offset(const struct LOG_RECORD *logr)
+{
+       u32 offset;
+
+       if (logr->lcns_to_follow)
+               offset = 0x30 + le16_to_cpu(logr->redo_offset);
+       else
+               offset = 0x28 + le16_to_cpu(logr->redo_offset);
+       return (offset);
+}
+
+u32 get_extra_offset(const struct LOG_RECORD *logr)
+{
+       u32 uoffset;
+       u32 roffset;
+
+       roffset = get_redo_offset(logr)
+                               + le16_to_cpu(logr->redo_length);
+       uoffset = get_undo_offset(logr)
+                               + le16_to_cpu(logr->undo_length);
+       return ((((uoffset > roffset ? uoffset : roffset) - 1) | 7) + 1);
+}
+
+static BOOL likelyop(const struct LOG_RECORD *logr)
+{
+       BOOL likely;
+
+       switch (le32_to_cpu(logr->record_type)) {
+       case LOG_STANDARD : /* standard record */
+            /* Operations in range 0..LastAction-1, can be both null */
+               likely = ((unsigned int)le16_to_cpu(logr->redo_operation)
+                                               < LastAction)
+                   && ((unsigned int)le16_to_cpu(logr->undo_operation)
+                                               < LastAction)
+            /* Offsets aligned to 8 bytes */
+                   && !(le16_to_cpu(logr->redo_offset) & 7)
+                   && !(le16_to_cpu(logr->undo_offset) & 7)
+            /* transaction id must not be null */
+                   && logr->transaction_id
+            /* client data length aligned to 8 bytes */
+                   && !(le32_to_cpu(logr->client_data_length) & 7)
+            /* client data length less than 64K (131K ?) */
+                   && (le32_to_cpu(logr->client_data_length) < MAXRECSIZE)
+            /* if there is redo data, offset must be >= 0x28 */
+                   && (!le16_to_cpu(logr->redo_length)
+                      || ((unsigned int)le16_to_cpu(logr->redo_offset) >= 0x28))
+            /* if there is undo data, offset must be >= 0x28 */
+                   && (!le16_to_cpu(logr->undo_length)
+                      || ((unsigned int)le16_to_cpu(logr->undo_offset) >= 0x28));
+            /* undo data and redo data should be contiguous when both present */
+               if (likely && logr->redo_length && logr->undo_length) {
+            /* undo and redo data may be the same when both present and same size */
+                       if (logr->undo_offset == logr->redo_offset) {
+                               if (logr->redo_length != logr->undo_length)
+                                       likely = FALSE;
+                       } else {
+                               if (le16_to_cpu(logr->redo_offset)
+                                       < le16_to_cpu(logr->undo_offset)) {
+                       /* undo expected just after redo */
+                                       if ((((le16_to_cpu(logr->redo_offset)
+                                           + le16_to_cpu(logr->redo_length)
+                                           - 1) | 7) + 1)
+                                           != le16_to_cpu(logr->undo_offset))
+                                               likely = FALSE;
+                               } else {
+                       /* redo expected just after undo */
+                                       if ((((le16_to_cpu(logr->undo_offset)
+                                           + le16_to_cpu(logr->undo_length)
+                                           - 1) | 7) + 1)
+                                           != le16_to_cpu(logr->redo_offset))
+                                               likely = FALSE;
+                               }
+                       }
+               }
+               break;
+       case LOG_CHECKPOINT : /* check-point */
+            /*
+             * undo and redo operations are null
+             * or CompensationlogRecord with no data
+             */
+               likely = (!logr->redo_operation
+                       || ((logr->redo_operation == const_cpu_to_le16(1))
+                           && !logr->redo_length))
+                   && (!logr->undo_operation
+                       || ((logr->undo_operation == const_cpu_to_le16(1))
+                           && !logr->undo_length))
+            /* transaction id must be null */
+                   && !logr->transaction_id
+            /* client_data_length is 0x68 or 0x70 (Vista and subsequent) */
+                   && ((le32_to_cpu(logr->client_data_length) == 0x68)
+                       || (le32_to_cpu(logr->client_data_length) == 0x70));
+               break;
+       default :
+               likely = FALSE;
+               break;
+       }
+       return (likely);
+}
+
+/*
+ *             Search for a likely record in a block
+ *
+ *     Must not be used when syncing.
+ *
+ *     Returns 0 when not found
+ */
+
+static u16 searchlikely(const struct BUFFER *buf)
+{
+       const struct LOG_RECORD *logr;
+       const char *data;
+       u16 k;
+
+       if (opts)
+               printf("** Error : searchlikely() used for syncing\n");
+        data = buf->block.data;
+       k = buf->headsz;
+       logr = (const struct LOG_RECORD*)&data[k];
+       if (!likelyop(logr)) {
+               do {
+                       k += 8;
+                       logr = (const struct LOG_RECORD*)&data[k];
+               } while ((k <= (blocksz - LOG_RECORD_HEAD_SZ))
+                   && !likelyop(logr));
+               if (k > (blocksz - LOG_RECORD_HEAD_SZ))
+                       k = 0;
+       }
+       return (k);
+}
+
+/*
+ *     From a previous block, determine the location of first record
+ *
+ *     The previous block must have the beginning of an overlapping
+ *     record, and the current block must have the beginning of next
+ *     record (which can overlap on next blocks).
+ *     The argument "skipped" is the number of blocks in-between.
+ *
+ *     Note : the overlapping record from previous block does not reach
+ *     the current block when it ends near the end of the last skipped block.
+ *
+ *     Returns 0 if some bad condition is found
+ *     Returns near blocksz when there is no beginning of record in
+ *             the current block
+ */
+
+static u16 firstrecord(int skipped, const struct BUFFER *buf,
+                  const struct BUFFER *prevbuf)
+{
+       const struct RECORD_PAGE_HEADER *rph;
+       const struct RECORD_PAGE_HEADER *prevrph;
+       const struct LOG_RECORD *logr;
+       const char *data;
+       const char *prevdata;
+       u16 k;
+       u16 blkheadsz;
+       s32 size;
+
+       rph = &buf->block.record;
+       data = buf->block.data;
+       if (prevbuf) {
+               prevrph = &prevbuf->block.record;
+               prevdata = prevbuf->block.data;
+               blkheadsz = prevbuf->headsz;
+               /* From previous page, determine where the current one starts */
+               k = le16_to_cpu(prevrph->next_record_offset);
+               /* a null value means there is no full record in next block */
+               if (!k)
+                       k = blkheadsz;
+       } else
+               k = 0;
+               /* Minimal size is apparently 48 : offset of redo_operation */
+       if (k && ((blocksz - k) >= LOG_RECORD_HEAD_SZ)) {
+               logr = (const struct LOG_RECORD*)&prevdata[k];
+               if (!logr->client_data_length) {
+                       /*
+                        * Sometimes the end of record is free space.
+                        * This apparently means reaching the end of
+                        * a previous session, and must be considered
+                        * as an error.
+                        * We however tolerate this, unless syncing
+                        * is requested.
+                        */
+                       printf("* Reaching free space at end of block %d\n",
+                                       (int)prevbuf->num);
+                       /* As a consequence, there cannot be skipped blocks */
+                       if (skipped) {
+                               printf("*** Inconsistency : blocks skipped after free space\n");
+                               k = 0; /* error returned */
+                       }
+                       if (opts)
+                               k = 0;
+                       else {
+                               k = searchlikely(buf);
+                               printf("* Skipping over free space\n");
+                       }
+               } else {
+                       size = le32_to_cpu(logr->client_data_length)
+                                       + LOG_RECORD_HEAD_SZ;
+                       if ((size < MINRECSIZE) || (size > MAXRECSIZE)) {
+                               printf("** Bad record size %ld in block %ld"
+                                       " offset 0x%x\n",
+                                       (long)size,(long)prevbuf->num,(int)k);
+                               k = blkheadsz;
+                       } else {
+                               if ((int)(blocksz - k) >= size)
+                                       printf("*** Inconsistency : the final"
+                                               " record does not overlap\n");
+                               k += size - (blocksz - blkheadsz)*(skipped + 1);
+                       }
+                       if ((k <= blkheadsz)
+                           && (k > (blkheadsz - LOG_RECORD_HEAD_SZ))) {
+                       /* There were not enough space in the last skipped block */
+                               k = blkheadsz;
+                       } else {
+                               if (optv
+                                   && ((blocksz - k) < LOG_RECORD_HEAD_SZ)) {
+                                       /* Not an error : just no space */
+                                       printf("No minimal record space\n");
+                               }
+                               if (optv >= 2)
+                                       printf("Overlapping record from block %d,"
+                                               " starting at offset 0x%x\n",
+                                               (int)prevbuf->num,(int)k);
+                       }
+               }
+       } else {
+               k = buf->headsz;
+               if (optv >= 2) {
+                       if (prevbuf)
+                               printf("No minimal record from block %d,"
+                                       " starting at offset 0x%x\n",
+                                       (int)prevbuf->num, (int)k);
+                       else
+                               printf("No block before %d,"
+                                       " starting at offset 0x%x\n",
+                                       (int)buf->num, (int)k);
+               }
+       }
+               /*
+                * In a wraparound situation, there is frequently no
+                * match... because there were no wraparound.
+                * Return an error if syncing is requested, otherwise
+                * try to find a starting record.
+                */
+       if (k && prevbuf && (prevbuf->num > buf->num)) {
+               logr = (const struct LOG_RECORD*)&data[k];
+                       /* Accept reaching the end with no record beginning */
+               if ((k != le16_to_cpu(rph->next_record_offset))
+                   && !likelyop(logr)) {
+                       if (opts) {
+                               k = 0;
+                               printf("** Could not wraparound\n");
+                       } else {
+                               k = searchlikely(buf);
+                               printf("* Skipping over bad wraparound\n");
+                       }
+               }
+       }
+       return (k);
+}
+
+/*
+ *             Find the block which defines the first record in current one
+ *
+ *     Either the wanted block has the beginning of a record overlapping
+ *     on current one, or it ends in such as there is no space for an
+ *     overlapping one.
+ *
+ *     Returns 0 if the previous block cannot be determined.
+ */
+
+static const struct BUFFER *findprevious(CONTEXT *ctx, const struct BUFFER *buf)
+{
+       const struct BUFFER *prevbuf;
+       const struct BUFFER *savebuf;
+       const struct RECORD_PAGE_HEADER *rph;
+       int skipped;
+       int prevblk;
+       BOOL prevmiddle;
+       BOOL error;
+       u16 endoff;
+
+       error = FALSE;
+       prevblk = buf->num;
+       skipped = 0;
+       do {
+               prevmiddle = FALSE;
+               if (prevblk > BASEBLKS)
+                       prevblk--;
+               else
+                       if (prevblk == BASEBLKS)
+                               prevblk = (logfilesz >> blockbits) - 1;
+                       else {
+                               rph = &buf->block.record;
+                               prevblk = (le32_to_cpu(rph->copy.file_offset)
+                                                       >> blockbits) - 1;
+                       }
+               /* No previous block if the log only consists of block 2 or 3 */
+               if (prevblk < BASEBLKS) {
+                       prevbuf = (struct BUFFER*)NULL;
+                       error = TRUE; /* not a real error */
+               } else {
+                       prevbuf = read_buffer(ctx, prevblk);
+                       if (prevbuf) {
+                               rph = &prevbuf->block.record;
+                               prevmiddle = !(rph->flags
+                                               & const_cpu_to_le32(1))
+                                       || !rph->next_record_offset;
+                               if (prevmiddle) {
+                                       savebuf = prevbuf;
+                                       skipped++;
+                               }
+                       } else {
+                               error = TRUE;
+                               printf("** Could not read block %d\n",
+                                                               (int)prevblk);
+                       }
+               }
+       } while (prevmiddle && !error);
+
+       if (!prevmiddle && !error && skipped) {
+        /* No luck if there is not enough space in this record */
+               rph = &prevbuf->block.record;
+               endoff = le16_to_cpu(rph->next_record_offset);
+               if (endoff > (blocksz - LOG_RECORD_HEAD_SZ)) {
+                       prevbuf = savebuf;
+               }
+       }
+       return (error ? (struct BUFFER*)NULL : prevbuf);
+}
+
+void copy_attribute(struct ATTR *pa, const char *buf, int length)
+{
+       const struct ATTR_NEW *panew;
+       struct ATTR_OLD old_aligned;
+
+       if (pa) {
+               switch (length) {
+               case sizeof(struct ATTR_NEW) :
+                       panew = (const struct ATTR_NEW*)buf;
+                       pa->type = panew->type;
+                       pa->lsn = le64_to_cpu(panew->lsn);
+                       pa->inode = MREF(le64_to_cpu(panew->inode));
+                       break;
+               case sizeof(struct ATTR_OLD) :
+                               /* Badly aligned, first realign */
+                       memcpy(&old_aligned,buf,sizeof(old_aligned));
+                       pa->type = old_aligned.type;
+                       pa->lsn = le64_to_cpu(old_aligned.lsn);
+                       pa->inode = MREF(le64_to_cpu(old_aligned.inode));
+                       break;
+               default :
+                       printf("** Unexpected attribute format, length %d\n",
+                                       length);
+               }
+       }
+}
+
+static int refresh_attributes(const struct ACTION_RECORD *firstaction)
+{
+       const struct ACTION_RECORD *action;
+       const struct LOG_RECORD *logr;
+       struct ATTR *pa;
+       const char *buf;
+       u32 extra;
+       u32 length;
+       u32 len;
+       u32 key;
+       u32 x;
+       u32 i;
+       u32 step;
+       u32 used;
+
+       for (action=firstaction; action; action=action->next) {
+               logr = &action->record;
+               buf = ((const char*)logr) + get_redo_offset(logr);
+               length = le16_to_cpu(logr->redo_length);
+               switch (le16_to_cpu(action->record.redo_operation)) {
+               case OpenNonResidentAttribute :
+                       extra = get_extra_offset(logr)
+                                               - get_redo_offset(logr);
+                       if (logr->undo_length) {
+                               len = le32_to_cpu(logr->client_data_length)
+                                       + LOG_RECORD_HEAD_SZ
+                                       - get_extra_offset(logr);
+                               /* this gives a length aligned modulo 8 */
+                               len = fixnamelen(&buf[extra], len);
+                       } else
+                               len = 0;
+                       pa = getattrentry(le16_to_cpu(logr->target_attribute),
+                                               len);
+                       if (pa) {
+                               copy_attribute(pa, buf, length);
+                               pa->namelen = len;
+                               if (len) {
+                                       memcpy(pa->name,&buf[extra],len);
+                               }
+                       }
+                       break;
+               case OpenAttributeTableDump :
+                       i = 24;
+                       step = getle16(buf, 8);
+                       used = getle16(buf, 12);
+                       /*
+                        * Changed from Win10, formerly we got step = 44.
+                        * The record layout has also changed
+                        */
+                       for (x=0; (x<used) && (i<length); i+=step, x++) {
+                               pa = getattrentry(i,0);
+                               if (pa) {
+                                       copy_attribute(pa, buf + i, step);
+                               }
+                       }
+                       break;
+               case AttributeNamesDump :
+                       i = 8;
+                       if (i < length) {
+                               x = 0;
+                               do {
+                                       len = getle16(buf, i + 2);
+                                       key = getle16(buf, i);
+                                       if (len > 510) {
+                                               printf("** Error : bad"
+                                                       " attribute name"
+                                                       " length %d\n",
+                                                       len);
+                                               key = 0;
+                                       }
+                                       if (key) { /* Apparently, may have to stop before reaching the end */
+                                               pa = getattrentry(key,len);
+                                               if (pa) {
+                                                       pa->namelen = len;
+                                                       memcpy(pa->name,
+                                                               &buf[i+4],len);
+                                               }
+                                               i += len + 6;
+                                               x++;
+                                       }
+                               } while (key && (i < length));
+                       }
+                       break;
+               default :
+                       break;
+               }
+       }
+       return (0);
+}
+
+/*
+ *              Display a fixup
+ */
+
+static void fixup(CONTEXT *ctx, const struct LOG_RECORD *logr, const char *buf,
+                               BOOL redo)
+{
+       struct ATTR *pa;
+       int action;
+       int attr;
+       int offs;
+       s32 length;
+       int extra;
+       s32 i;
+       int p;
+       s32 base;
+       u16 firstpos; /* position of first mft attribute */
+       le32 v;
+       ATTR_TYPES mftattr;
+       le64 w;
+       le64 inode;
+       le64 size;
+       int lth;
+       int len;
+
+       attr = le16_to_cpu(logr->target_attribute);
+       offs = le16_to_cpu(logr->attribute_offset);
+       if (redo) {
+               action = le16_to_cpu(logr->redo_operation);
+               length = le16_to_cpu(logr->redo_length);
+       } else {
+               action = le16_to_cpu(logr->undo_operation);
+               length = le16_to_cpu(logr->undo_length);
+       }
+       if (redo)
+               printf("redo fixup %dR %s attr 0x%x offs 0x%x\n",
+                       actionnum, actionname(action), attr, offs);
+       else
+               printf("undo fixup %dU %s attr 0x%x offs 0x%x\n",
+                       actionnum, actionname(action), attr, offs);
+       switch (action) {
+       case InitializeFileRecordSegment : /* 2 */
+                       /*
+                        * When this is a redo (with a NoOp undo), the
+                        *   full MFT record is logged.
+                        * When this is an undo (with DeallocateFileRecordSegment redo),
+                        *   only the header of the MFT record is logged.
+                        */
+               if (!ctx->vol && !mftrecsz && (length > 8)) {
+                       /* mftrecsz can be determined from usa_count */
+                       mftrecsz = (getle16(buf,6) - 1)*512;
+                       mftrecbits = 1;
+                       while ((u32)(1 << mftrecbits) < mftrecsz)
+                               mftrecbits++;
+               }
+               printf("   new base MFT record, attr 0x%x (%s)\n",attr,attrname(attr));
+               printf("   inode      %lld\n",
+                               (((long long)le32_to_cpu(logr->target_vcn)
+                                       << clusterbits)
+                               + (le16_to_cpu(logr->cluster_index) << 9))
+                                       >> mftrecbits);
+               if (length >= 18)
+                       printf("   seq number 0x%04x\n",(int)getle16(buf, 16));
+               if (length >= 20)
+                       printf("   link count %d\n",(int)getle16(buf, 18));
+               if (length >= 24) {
+                       u16 flags;
+
+                       flags = getle16(buf, 22);
+                       printf("   flags      0x%x",(int)flags);
+                       switch (flags & 3) {
+                       case 1 :
+                               printf(" (file in use)\n");
+                               break;
+                       case 3 :
+                               printf(" (directory in use)\n");
+                               break;
+                       default :
+                               printf(" (not in use)\n");
+                               break;
+                       }
+               }
+               base = getle16(buf, 4) + ((getle16(buf, 6)*2 - 1) | 7) + 1;
+               while (base < length) {
+                       mftattr = feedle32(buf, base);
+                       printf("   attrib 0x%lx (%s) at offset 0x%x\n",
+                               (long)le32_to_cpu(mftattr),
+                               mftattrname(mftattr), (int)base);
+               if (mftattr == AT_FILE_NAME) {
+                       showname("      name ",&buf[base + 90],
+                                       buf[base + 88] & 255);
+                       inode = feedle64(buf, base + 24);
+                       printf("      parent dir inode %lld\n",
+                                       (long long)MREF(le64_to_cpu(inode)));
+               }
+               lth =  getle32(buf, base + 4);
+               if ((lth <= 0) || (lth & 7))
+                       base = length;
+               else
+                       base += lth;
+               }
+               break;
+       case DeallocateFileRecordSegment : /* 3 */
+               printf("   free base MFT record, attr 0x%x (%s)\n",
+                               attr,attrname(attr));
+               printf("   inode %lld\n",
+                   (((long long)le32_to_cpu(logr->target_vcn) << clusterbits)
+                   + (le16_to_cpu(logr->cluster_index) << 9)) >> mftrecbits);
+               break;
+       case CreateAttribute : /* 5 */
+               pa = getattrentry(attr,0);
+               base = 24;
+               /* Assume the beginning of the attribute is always present */
+               switch (getle32(buf,0)) {
+               case 0x30 :
+                       printf("   create file name, attr 0x%x\n",attr);
+                       if (pa)
+                               showattribute("      ",pa);
+                       showname("   file ",
+                               &buf[base + 66],buf[base + 64] & 255);
+                       if (base >= -8)
+                               showdate("   created  ",feedle64(buf,base + 8));
+                       if (base >= -16)
+                               showdate("   modified ",feedle64(buf,base + 16));
+                       if (base >= -24)
+                               showdate("   changed  ",feedle64(buf,base + 24));
+                       if (base >= -32)
+                               showdate("   read     ",feedle64(buf,base + 32));
+                       size = feedle64(buf,base + 40);
+                       printf("   allocated size %lld\n",
+                                       (long long)le64_to_cpu(size));
+                       size = feedle64(buf,base + 48);
+                       printf("   real size %lld\n",
+                                       (long long)le64_to_cpu(size));
+                       v = feedle32(buf,base + 56);
+                       printf("   DOS flags 0x%lx\n",
+                                       (long)le32_to_cpu(v));
+                       break;
+               case 0x80 :
+                       printf("   create a data stream, attr 0x%x\n",attr);
+                       break;
+               case 0xc0 :
+                       printf("   create reparse data\n");
+                       if (pa)
+                               showattribute("      ",pa);
+                       printf("   tag 0x%lx\n",(long)getle32(buf, base));
+                       showname("   print name ",
+                               &buf[base + 20 + getle16(buf, base + 12)],
+                               getle16(buf, base + 14)/2);
+                       break;
+               }
+               break;
+      case UpdateResidentValue : /* 7 */
+               /*
+                * The record offset designates the mft attribute offset,
+                * offs and length define a right-justified window in this
+                * attribute.
+                * At this stage, we do not know which kind of mft
+                * attribute this is about, we assume this is standard
+                * information when it is the first attribute in the
+                * record.
+                */
+               base = 0x18 - offs; /* p 8 */
+               pa = getattrentry(attr,0);
+               firstpos = 0x30 + (((mftrecsz/512 + 1)*2 - 1 ) | 7) + 1;
+               if (pa
+                  && !pa->inode
+                  && (pa->type == const_cpu_to_le32(0x80))
+                  && !(offs & 3)
+                  && (le16_to_cpu(logr->record_offset) == firstpos)) {
+                       printf("   set standard information, attr 0x%x\n",attr);
+                       showattribute("      ",pa);
+                       if ((base >= 0) && ((base + 8) <= length))
+                               showdate("   created  ",
+                                               feedle64(buf,base));
+                       if (((base + 8) >= 0) && ((base + 16) <= length))
+                               showdate("   modified ",
+                                               feedle64(buf,base + 8));
+                       if (((base + 16) >= 0) && ((base + 24) <= length))
+                               showdate("   changed  ",
+                                               feedle64(buf,base + 16));
+                       if (((base + 24) >= 0) && ((base + 32) <= length))
+                               showdate("   read     ",
+                                               feedle64(buf,base + 24));
+                       if (((base + 32) >= 0) && ((base + 36) <= length)) {
+                               v = feedle32(buf, base + 32);
+                               printf("   DOS flags 0x%lx\n",
+                                               (long)le32_to_cpu(v));
+                       }
+                       if (((base + 52) >= 0) && ((base + 56) <= length)) {
+                               v = feedle32(buf, base + 52);
+                               printf("   security id 0x%lx\n",
+                                               (long)le32_to_cpu(v));
+                       }
+                       if (((base + 64) >= 0) && ((base + 72) <= length)) {
+                               /*
+                                * This is badly aligned for Sparc when
+                                * stamps not present and base == 52
+                                */
+                               memcpy(&w, &buf[base + 64], 8);
+                               printf("   journal idx 0x%llx\n",
+                                               (long long)le64_to_cpu(w));
+                       }
+               } else {
+                       printf("   set an MFT attribute at offset 0x%x, attr 0x%x\n",
+                                       (int)offs, attr);
+                       if (pa)
+                               showattribute("      ",pa);
+               }
+               break;
+       case UpdateNonResidentValue : /* 8 */
+               printf("   set attr 0x%x (%s)\n",attr,attrname(attr));
+               pa = getattrentry(attr,0);
+               if (pa)
+                       showattribute("      ",pa);
+               base = 0; /* ? */
+// Should not be decoded, unless attr is of identified type (I30, ...)
+               if (pa && (pa->namelen == 8) && !memcmp(pa->name, SDS, 8)) {
+                       if (length >= 4)
+                               printf("   security hash 0x%lx\n",
+                                               (long)getle32(buf, 0));
+                       if (length >= 8)
+                               printf("   security id 0x%lx\n",
+                                               (long)getle32(buf, 4));
+                       if (length >= 20)
+                               printf("   entry size  %ld\n",
+                                               (long)getle32(buf, 16));
+               }
+               if (pa && (pa->namelen == 8) && !memcmp(pa->name, I30, 8)) {
+                       if (!memcmp(buf, "INDX", 4))
+                               base = 64; /* full record */
+                       else
+                               base = 0;  /* entries */
+                       inode = feedle64(buf, base);
+                       printf("   inode  %lld\n",
+                            (long long)MREF(le64_to_cpu(inode)));
+                       inode = feedle64(buf, base + 16);
+                       printf("   parent inode %lld\n",
+                            (long long)MREF(le64_to_cpu(inode)));
+                       showname("   file    ",&buf[base + 82],
+                                                       buf[base + 80] & 255);
+                       showdate("   date    ",feedle64(buf, base + 32));
+               }
+               break;
+       case UpdateMappingPairs : /* 9 */
+               printf("   update runlist in attr 0x%x (%s)\n",attr,
+                               attrname(attr));
+              /* argument is a compressed runlist (or part of it ?) */
+              /* stop when finding 00 */
+               break;
+       case SetNewAttributeSizes : /* 11 */
+               printf("   set sizes in attr 0x%x (%s)\n",attr,attrname(attr));
+               base = 0; /* left justified ? */
+               size = feedle64(buf,0);
+               printf("     allocated size %lld\n",(long long)le64_to_cpu(size));
+               size = feedle64(buf,8);
+               printf("          real size %lld\n",(long long)le64_to_cpu(size));
+               size = feedle64(buf,16);
+               printf("   initialized size %lld\n",(long long)le64_to_cpu(size));
+               break;
+       case AddIndexEntryRoot : /* 12 */
+       case AddIndexEntryAllocation : /* 14 */
+               /*
+                * The record offset designates the mft attribute offset,
+                * offs and length define a left-justified window in this
+                * attribute.
+                */
+               if (action == AddIndexEntryRoot)
+                       printf("   add resident index entry, attr 0x%x\n",attr);
+               else
+                       printf("   add nonres index entry, attr 0x%x\n",attr);
+               pa = getattrentry(attr,0);
+               if (pa)
+                       showattribute("      ",pa);
+               base = 0;
+               p = getle16(buf, base + 8);
+               /* index types may be discriminated by inode in base+0 */
+               switch (p) { /* size of index entry */
+               case 32 :  /* $R entry */
+                       memcpy(&inode, &buf[base + 20], 8); /* bad align */
+                       printf("   $R reparse index\n");
+                       printf("   reparsed inode 0x%016llx\n",
+                                       (long long)le64_to_cpu(inode));
+                       printf("   reparse tag 0x%lx\n",
+                                       (long)getle32(buf, 16));
+                       break;
+               case 40 :  /* $SII entry */
+                       printf("   $SII security id index\n");
+                       printf("   security id 0x%lx\n",
+                                       (long)getle32(buf, 16));
+                       printf("   security hash 0x%lx\n",
+                                       (long)getle32(buf, 20));
+                       break;
+               case 48 :  /* $SDH entry */
+                       printf("   $SDH security id index\n");
+                       printf("   security id 0x%lx\n",
+                                       (long)getle32(buf, 20));
+                       printf("   security hash 0x%lx\n",
+                                       (long)getle32(buf, 16));
+                       break;
+               default :
+                 /* directory index are at least 84 bytes long, ntfsdoc p 98 */
+                 /* have everything needed to create the index */
+                       lth = buf[base + 80] & 255;
+                 /* consistency of file name length */
+                       if (getle16(buf,10) == (u32)(2*lth + 66)) {
+                               printf("   directory index\n");
+                               inode = feedle64(buf,16);
+                               printf("   parent dir inode %lld\n",
+                                       (long long)MREF(le64_to_cpu(inode)));
+                               if (feedle32(buf,72)
+                                               & const_cpu_to_le32(0x10000000))
+                                       showname("   file (dir) ",
+                                               &buf[base + 82],
+                                               buf[base + 80] & 255);
+                               else
+                                       showname("   file ",
+                                               &buf[base + 82],
+                                               buf[base + 80] & 255);
+                               inode = feedle64(buf,0);
+                               printf("   file inode %lld\n",
+                                       (long long)MREF(le64_to_cpu(inode)));
+                               size = feedle64(buf,64);
+                               printf("   file size %lld\n",
+                                       (long long)le64_to_cpu(size));
+                               showdate("   created  ",
+                                               feedle64(buf,base + 24));
+                               showdate("   modified ",
+                                               feedle64(buf,base + 32));
+                               showdate("   changed  ",
+                                               feedle64(buf,base + 40));
+                               showdate("   read     ",
+                                               feedle64(buf,base + 48));
+                       } else
+                               printf("   unknown index type\n");
+                       break;
+                       }
+               break;
+       case SetIndexEntryVcnRoot : /* 17 */
+               printf("   set vcn of non-resident index root, attr 0x%x\n",
+                               attr);
+               pa = getattrentry(attr,0);
+               if (pa)
+                       showattribute("      ",pa);
+               printf("   vcn %lld\n", (long long)getle64(buf,0));
+               break;
+       case UpdateFileNameRoot : /* 19 */
+               /*
+                * Update an entry in a resident directory index.
+                * The record offset designates the mft attribute offset,
+                * offs and length define a right-justified window in this
+                * attribute.
+                */
+               printf("   set directory resident entry, attr 0x%x\n",attr);
+               base = length - 0x50;
+               pa = getattrentry(attr,0);
+               if (pa)
+                       showattribute("      ",pa);
+               if (pa
+                  && !pa->inode
+                  && (pa->type == const_cpu_to_le32(0x80))
+                  && !(offs & 3)) {
+                       if (base >= -24)
+                               showdate("   created  ",feedle64(buf,
+                                                       base + 24));
+                       if (base >= -32)
+                               showdate("   modified ",feedle64(buf,
+                                                       base + 32));
+                       if (base >= -40)
+                               showdate("   changed  ",feedle64(buf,
+                                                       base + 40));
+                       if (base >= -48)
+                               showdate("   read     ",feedle64(buf,
+                                                       base + 48));
+                       if (base >= -56) {
+                               size = feedle64(buf,base + 56);
+                               printf("   allocated size %lld\n",
+                                               (long long)le64_to_cpu(size));
+                       }
+                       if (base >= -64) {
+                               size = feedle64(buf,base + 64);
+                               printf("   real size %lld\n",
+                                               (long long)le64_to_cpu(size));
+                       }
+                       if (base > -72) {
+                               v = feedle32(buf,base + 72);
+                               printf("   DOS flags 0x%lx\n",
+                                               (long)le32_to_cpu(v));
+                       }
+               } else {
+                       /* Usually caused by attr not yet defined */
+                       if (pa && pa->type)
+                               printf("** Unexpected index parameters\n");
+               }
+               break;
+       case UpdateFileNameAllocation : /* 20 */
+                    /* update entry in directory index */
+                    /* only dates, sizes and attrib */
+               base = length - 64; /* p 12 */
+               printf("   set directory nonres entry, attr 0x%x\n",attr);
+               pa = getattrentry(attr,0);
+               if (pa)
+                       showattribute("      ",pa);
+               if (base >= -8)
+                       showdate("   created  ",feedle64(buf, base + 8));
+               if (base >= -16)
+                       showdate("   modified ",feedle64(buf, base + 16));
+               if (base >= -24)
+                       showdate("   changed  ",feedle64(buf, base + 24));
+               if (base >= -32)
+                       showdate("   read     ",*(const le64*)&buf[base + 32]);
+               if (base >= -40) {
+                       size = feedle64(buf, base + 40);
+                       printf("   allocated size %lld\n",
+                                               (long long)le64_to_cpu(size));
+               }
+               if (base >= -48) {
+                       size = feedle64(buf, base + 48);
+                       printf("   real size %lld\n",
+                                               (long long)le64_to_cpu(size));
+               }
+               if (base >= -56) {
+                       v = feedle32(buf, base + 56);
+                       printf("   DOS flags 0x%lx\n",(long)le32_to_cpu(v));
+               }
+               break;
+       case SetBitsInNonResidentBitMap : /* 21 */
+       case ClearBitsInNonResidentBitMap : /* 22 */
+               if (action == SetBitsInNonResidentBitMap)
+                       printf("   SetBitsInNonResidentBitMap, attr 0x%x\n",
+                                       attr);
+               else
+                       printf("   ClearBitsInNonResidentBitMap, attr 0x%x\n",
+                                       attr);
+               pa = getattrentry(attr,0);
+               if (pa)
+                       showattribute("      ",pa);
+               v = feedle32(buf, 0);
+               printf("   first bit %ld\n",(long)le32_to_cpu(v));
+               v = feedle32(buf, 4);
+               printf("   bit count %ld\n",(long)le32_to_cpu(v));
+               break;
+       case OpenNonResidentAttribute : /* 28 */
+               printf("   OpenNonResidentAttribute, attr 0x%x\n",attr);
+               extra = get_extra_offset(logr)
+                       - (redo ? get_redo_offset(logr)
+                               : get_undo_offset(logr));
+               if (logr->undo_length) {
+                       len = le32_to_cpu(logr->client_data_length)
+                               + LOG_RECORD_HEAD_SZ
+                               - get_extra_offset(logr);
+                       /* this gives a length aligned modulo 8 */
+                       len = fixnamelen(&buf[extra], len);
+               } else
+                       len = 0;
+               pa = getattrentry(attr,len);
+               if (pa && redo) {
+                       /*
+                        * If this is a redo, collect the attribute data.
+                        * This should only be done when walking forward.
+                        */
+                       copy_attribute(pa, buf, length);
+                       pa->namelen = len;
+                       if (len)
+                               memcpy(pa->name,&buf[extra],len);
+                       printf("   MFT attribute 0x%lx (%s)\n",
+                               (long)le32_to_cpu(pa->type),
+                               mftattrname(pa->type));
+                       printf("   lsn   0x%016llx\n",
+                               (long long)pa->lsn);
+                       printf("   inode %lld\n",
+                               (long long)pa->inode);
+               }
+               if (logr->undo_length)
+                       showname("   extra : attr name ", &buf[extra], len/2);
+               if (!redo && length) {
+                       printf("   * undo attr not shown\n");
+               }
+               break;
+       case OpenAttributeTableDump : /* 29 */
+               printf("   OpenAttributeTableDump, attr 0x%x (%s)\n",
+                               attr,attrname(attr));
+               i = 24;
+               if (i < length) {
+                       int x;
+                       int more;
+                       int step;
+                       int used;
+
+                       step = getle16(buf, 8);
+                       used = getle16(buf, 12);
+                           /*
+                            * Changed from Win10, formerly we got step = 44.
+                            * The record layout has also changed
+                            */
+                       if ((step != sizeof(struct ATTR_OLD))
+                           && (step != sizeof(struct ATTR_NEW))) {
+                               printf("   ** Unexpected step %d\n",step);
+                       }
+                       more = 0;
+                       for (x=0; (x<used) && (i<length); i+=step, x++) {
+                               pa = getattrentry(i,0);
+                               if (pa) {
+                                       copy_attribute(pa, &buf[i], step);
+                                       if (x <= SHOWATTRS) {
+                                               printf("   attr 0x%x inode %lld"
+                                                       " type %s",
+                                                       (int)i,
+                                                       (long long)pa->inode,
+                                                       mftattrname(pa->type));
+                                               if (pa->namelen)
+                                                       showname(" name ",
+                                                               (char*)pa->name,
+                                                               pa->namelen/2);
+                                               else
+                                                       printf("\n");
+                                       } else
+                                               more++;
+                               }
+                       }
+                       if (more)
+                               printf("   (%d more attrs not shown)\n",more);
+               }
+               break;
+       case AttributeNamesDump : /* 30 */
+               printf("   AttributeNamesDump, attr 0x%x (%s)\n",
+                              attr,attrname(attr));
+               i = 8;
+               if (i < length) {
+                       unsigned int l;
+                       unsigned int key;
+                       int x;
+                       int more;
+
+                       more = 0;
+                       x = 0;
+                       do {
+                               l = le16_to_cpu(*(const le16*)&buf[i+2]);
+                               key = le16_to_cpu(*(const le16*)&buf[i]);
+                               if (l > 510) {
+                                       printf("** Error : bad attribute name"
+                                                       " length %d\n",l);
+                                       key = 0;
+                               }
+                /* Apparently, may have to stop before reaching the end */
+                               if (key) {
+                                       pa = getattrentry(key,l);
+                                       if (pa) {
+                                               pa->namelen = l;
+                                               memcpy(pa->name,&buf[i+4],l);
+                                       }
+                                       if (x < SHOWATTRS) {
+                                               printf("   attr 0x%x is",key);
+                                               showname("  ",&buf[i+4],l/2);
+                                       } else
+                                               more++;
+                                       i += l + 6;
+                                       x++;
+                               }
+                       } while (key && (i < length));
+                       if (more)
+                               printf("   (%d more attrs not shown)\n",more);
+               }
+               break;
+       default :
+               break;
+       }
+}
+
+static void detaillogr(CONTEXT *ctx, const struct LOG_RECORD *logr)
+{
+       u64 lcn;
+       u64 baselcn;
+       unsigned int i;
+       unsigned int off;
+       unsigned int undo;
+       unsigned int redo;
+       unsigned int extra;
+       unsigned int end;
+       unsigned int listsize;
+       BOOL onmft;
+
+       switch (le32_to_cpu(logr->record_type)) {
+       case 1 :
+               onmft = logr->cluster_index
+                       || acts_on_mft(le16_to_cpu(logr->redo_operation))
+                       || acts_on_mft(le16_to_cpu(logr->undo_operation));
+               printf("redo_operation         %04x %s\n",
+                       (int)le16_to_cpu(logr->redo_operation),
+                       actionname(le16_to_cpu(logr->redo_operation)));
+               printf("undo_operation         %04x %s\n",
+                       (int)le16_to_cpu(logr->undo_operation),
+                       actionname(le16_to_cpu(logr->undo_operation)));
+               printf("redo_offset            %04x\n",
+                       (int)le16_to_cpu(logr->redo_offset));
+               printf("redo_length            %04x\n",
+                       (int)le16_to_cpu(logr->redo_length));
+               printf("undo_offset            %04x\n",
+                       (int)le16_to_cpu(logr->undo_offset));
+               printf("undo_length            %04x\n",
+                       (int)le16_to_cpu(logr->undo_length));
+               printf("target_attribute       %04x\n",
+                       (int)le16_to_cpu(logr->target_attribute));
+               printf("lcns_to_follow         %04x\n",
+                       (int)le16_to_cpu(logr->lcns_to_follow));
+               printf("record_offset          %04x\n",
+                       (int)le16_to_cpu(logr->record_offset));
+               printf("attribute_offset       %04x\n",
+                       (int)le16_to_cpu(logr->attribute_offset));
+               printf("cluster_index          %04x\n",
+                       (int)le16_to_cpu(logr->cluster_index));
+               printf("attribute_flags        %04x\n",
+                       (int)le16_to_cpu(logr->attribute_flags));
+               if (mftrecbits && onmft)
+                       printf("target_vcn             %08lx (inode %lld)\n",
+                               (long)le32_to_cpu(logr->target_vcn),
+                               (((long long)le32_to_cpu(logr->target_vcn)
+                                       << clusterbits)
+                               + (le16_to_cpu(logr->cluster_index) << 9))
+                                        >> mftrecbits);
+               else
+                       printf("target_vcn             %08lx\n",
+                               (long)le32_to_cpu(logr->target_vcn));
+               printf("reserved3              %08lx\n",
+                               (long)le32_to_cpu(logr->reserved3));
+                       /* Compute a base for the current run of mft */
+               baselcn = le64_to_cpu(logr->lcn_list[0])
+                                       - le32_to_cpu(logr->target_vcn);
+               for (i=0; i<le16_to_cpu(logr->lcns_to_follow)
+                                               && (i<SHOWLISTS); i++) {
+                       lcn = le64_to_cpu(logr->lcn_list[i]);
+                       printf("  (%d offs 0x%x) lcn    %016llx",i,
+                               (int)(8*i + sizeof(LOG_RECORD) - 8),
+                               (long long)lcn);
+                       lcn &= 0xffffffffffffULL;
+                       if (mftrecsz && onmft) {
+                               if (clustersz > mftrecsz)
+                                       printf(" (MFT records for inodes"
+                                               " %lld-%lld)\n",
+                                               (long long)((lcn - baselcn)
+                                                       *clustersz/mftrecsz),
+                                               (long long)((lcn + 1 - baselcn)
+                                                       *clustersz/mftrecsz - 1));
+                               else
+                                       printf(" (MFT record for inode %lld)\n",
+                                               (long long)((lcn - baselcn)
+                                                       *clustersz/mftrecsz));
+                               printf("     assuming record for inode %lld\n",
+                                       (long long)((lcn - baselcn)
+                                               *clustersz/mftrecsz
+                                       + (le16_to_cpu(logr->cluster_index)
+                                                >> 1)));
+                       } else
+                               printf("\n");
+               }
+                /*
+                 *  redo_offset and undo_offset are considered unsafe
+                *  (actually they are safe when you know the logic)
+                 *  2) redo : redo (defined by redo_offset)
+                 *  3) undo : undo (defined by undo_offset)
+                 *  4) extra : unknown data (end of undo to data_length)
+                 */
+         end = le32_to_cpu(logr->client_data_length) + LOG_RECORD_HEAD_SZ;
+         if (logr->redo_length && logr->undo_length)
+            {
+                          /* both undo and redo are present */
+            if (le16_to_cpu(logr->undo_offset) <=
+                                               le16_to_cpu(logr->redo_offset))
+               {
+               undo = sizeof(LOG_RECORD) - 8
+                                       + 8*le16_to_cpu(logr->lcns_to_follow);
+               if (logr->redo_offset == logr->undo_offset)
+                  redo = undo;
+               else
+                  redo = undo + ((le16_to_cpu(logr->undo_length) - 1) | 7) + 1;
+               extra = redo + ((le16_to_cpu(logr->redo_length) - 1) | 7) + 1;
+               }
+            else
+               {
+               redo = sizeof(LOG_RECORD) - 8
+                                       + 8*le16_to_cpu(logr->lcns_to_follow);
+               undo = redo + ((le16_to_cpu(logr->redo_length) - 1) | 7) + 1;
+               extra = undo + ((le16_to_cpu(logr->undo_length) - 1) | 7) + 1;
+               }
+            }
+         else
+            if (logr->redo_length)
+               {
+                                  /* redo and not undo */
+               redo = undo = sizeof(LOG_RECORD) - 8
+                                       + 8*le16_to_cpu(logr->lcns_to_follow);
+               extra = redo + ((le16_to_cpu(logr->redo_length) - 1) | 7) + 1;
+               }
+            else
+               {
+                                  /* optional undo and not redo */
+               redo = undo = sizeof(LOG_RECORD) - 8
+                                       + 8*le16_to_cpu(logr->lcns_to_follow);
+               extra = undo + ((le16_to_cpu(logr->undo_length) - 1) | 7) + 1;
+               }
+
+         printf("redo 0x%x (%u) undo 0x%x (%u) extra 0x%x (%d)\n",
+                  redo,(int)(((le16_to_cpu(logr->redo_length) - 1) | 7) + 1),
+                  undo,(int)(((le16_to_cpu(logr->undo_length) - 1) | 7) + 1),
+                  extra,(int)(end > extra ? end - extra : 0));
+
+       if (logr->redo_length && (get_redo_offset(logr) != redo))
+               printf("** Unexpected redo offset 0x%x %u (%u)\n",
+                       get_redo_offset(logr),(int)redo,
+                       (int)le16_to_cpu(logr->lcns_to_follow));
+       if (logr->undo_length && (get_undo_offset(logr) != undo))
+               printf("** Unexpected undo offset 0x%x %u (%u)\n",
+                       get_undo_offset(logr),(int)undo,
+                       (int)le16_to_cpu(logr->lcns_to_follow));
+       if (get_extra_offset(logr) != extra)
+               printf("** Unexpected extra offset 0x%x %u (%u)\n",
+                       get_extra_offset(logr),(int)extra,
+                       (int)le16_to_cpu(logr->lcns_to_follow));
+
+         if (extra <= end)
+            {
+                                       /* show redo data */
+            if (logr->redo_length)
+               {
+               if (logr->lcns_to_follow)
+                  {
+                  off = le16_to_cpu(logr->record_offset)
+                                       + le16_to_cpu(logr->attribute_offset);
+                  printf("redo data (new data) cluster 0x%llx pos 0x%x :\n",
+                        (long long)le64_to_cpu(logr->lcn_list[off
+                                               >> clusterbits]),
+                        (int)(off & (clustersz - 1)));
+                  }
+               else
+                       printf("redo data (new data) at offs 0x%x :\n",redo);
+               if ((u32)(redo + le16_to_cpu(logr->redo_length))
+                    <= end)
+                  {
+                  hexdump((const char*)logr
+                               + redo,le16_to_cpu(logr->redo_length));
+                  fixup(ctx, logr, (const char*)logr + redo, TRUE);
+                  }
+               else printf("redo data overflowing from record\n");
+               }
+            else
+               {
+               printf("no redo data (new data)\n");
+               fixup(ctx, logr, (const char*)logr + redo, TRUE);
+               }
+
+                                     /* show undo data */
+            if (logr->undo_length)
+               {
+               if (logr->lcns_to_follow)
+                   {
+                   off = le16_to_cpu(logr->record_offset)
+                                       + le16_to_cpu(logr->attribute_offset);
+                   printf("undo data (old data) cluster 0x%llx pos 0x%x :\n",
+                         (long long)le64_to_cpu(logr->lcn_list[off
+                                                       >> clusterbits]),
+                         (int)(off & (clustersz - 1)));
+                   }
+               else printf("undo data (old data) at offs 0x%x :\n",undo);
+               if ((u32)(undo + le16_to_cpu(logr->undo_length)) <= end)
+                  {
+                  if ((undo + le16_to_cpu(logr->undo_length)) < 2*blocksz)
+                     {
+                     hexdump((const char*)logr
+                                       + undo,le16_to_cpu(logr->undo_length));
+                     fixup(ctx, logr, (const char*)logr + undo, FALSE);
+                     }
+                  else printf("undo data overflowing from two blocks\n");
+                  }
+               else printf("undo data overflowing from record\n");
+               }
+            else
+               {
+               printf("no undo data (old data)\n");
+               fixup(ctx, logr, (const char*)logr + undo, FALSE);
+               }
+
+                                    /* show extra data, if any */
+            if (extra != end)
+               {
+               if (end > blocksz)
+                  printf("invalid extra data size\n");
+               else
+                  {
+                  printf("extra data at offs 0x%x\n",extra);
+                  hexdump((const char*)logr + extra,
+                            end - extra);
+                  }
+               }
+            }
+         else
+            {
+                       /* sometimes the designated data overflows */
+            if (logr->redo_length
+              && ((u32)(redo + le16_to_cpu(logr->redo_length)) > end))
+                printf("* redo data overflows from record\n");
+            if (logr->undo_length
+              && ((u32)(undo + le16_to_cpu(logr->undo_length)) > end))
+                printf("* undo data overflows from record\n");
+           }
+               break;
+       case 2 :
+               printf("---> checkpoint record\n");
+               printf("redo_operation         %04x %s\n",
+                       (int)le16_to_cpu(logr->redo_operation),
+                       actionname(le16_to_cpu(logr->redo_operation)));
+               printf("undo_operation         %04x %s\n",
+                       (int)le16_to_cpu(logr->undo_operation),
+                       actionname(le16_to_cpu(logr->undo_operation)));
+               printf("redo_offset            %04x\n",
+                       (int)le16_to_cpu(logr->redo_offset));
+               printf("redo_length            %04x\n",
+                       (int)le16_to_cpu(logr->redo_length));
+               printf("transaction_lsn        %016llx\n",
+                       (long long)le64_to_cpu(logr->transaction_lsn));
+               printf("attributes_lsn         %016llx\n",
+                       (long long)le64_to_cpu(logr->attributes_lsn));
+               printf("names_lsn              %016llx\n",
+                       (long long)le64_to_cpu(logr->names_lsn));
+               printf("dirty_pages_lsn        %016llx\n",
+                       (long long)le64_to_cpu(logr->dirty_pages_lsn));
+               listsize = le32_to_cpu(logr->client_data_length)
+                               + LOG_RECORD_HEAD_SZ
+                               - offsetof(struct LOG_RECORD, unknown_list);
+               if (listsize > 8*SHOWLISTS)
+                       listsize = 8*SHOWLISTS;
+               for (i=0; 8*i<listsize; i++)
+                       printf("unknown-%u              %016llx\n",i,
+                               (long long)le64_to_cpu(logr->unknown_list[i]));
+               break;
+       default :
+               printf("** Unknown action type\n");
+               if (le32_to_cpu(logr->client_data_length) < blocksz) {
+                       printf("client_data for record type %ld\n",
+                               (long)le32_to_cpu(logr->record_type));
+                       hexdump((const char*)&logr->redo_operation,
+                               le32_to_cpu(logr->client_data_length));
+               } else
+                       printf("** Bad client data\n");
+               break;
+       }
+}
+
+BOOL within_lcn_range(const struct LOG_RECORD *logr)
+{
+       u64 lcn;
+       unsigned int i;
+       BOOL within;
+
+       within = FALSE;
+       switch (le32_to_cpu(logr->record_type)) {
+       case 1 :
+               for (i=0; i<le16_to_cpu(logr->lcns_to_follow); i++) {
+                       lcn = MREF(le64_to_cpu(logr->lcn_list[i]));
+                       if ((lcn >= firstlcn) && (lcn <= lastlcn))
+                               within = TRUE;
+               }
+               break;
+       default :
+               break;
+       }
+       return (within);
+}
+
+static void showlogr(CONTEXT *ctx, int k, const struct LOG_RECORD *logr)
+{
+       s32 diff;
+
+       if (optv && (!optc || within_lcn_range(logr))) {
+               diff = le64_to_cpu(logr->this_lsn) - synced_lsn;
+               printf("this_lsn               %016llx (synced%s%ld) %s\n",
+                       (long long)le64_to_cpu(logr->this_lsn),
+                       (diff < 0 ? "" : "+"),(long)diff,
+                       commitment(diff + synced_lsn));
+               printf("client_previous_lsn    %016llx\n",
+                       (long long)le64_to_cpu(logr->client_previous_lsn));
+               printf("client_undo_next_lsn   %016llx\n",
+                       (long long)le64_to_cpu(logr->client_undo_next_lsn));
+               printf("client_data_length     %08lx\n",
+                       (long)le32_to_cpu(logr->client_data_length));
+               printf("seq_number             %d\n",
+                       (int)le16_to_cpu(logr->client_id.seq_number));
+               printf("client_index           %d\n",
+                       (int)le16_to_cpu(logr->client_id.client_index));
+               printf("record_type            %08lx\n",
+                       (long)le32_to_cpu(logr->record_type));
+               printf("transaction_id         %08lx\n",
+                       (long)le32_to_cpu(logr->transaction_id));
+               printf("log_record_flags       %04x\n",
+                       (int)le16_to_cpu(logr->log_record_flags));
+               printf("reserved1              %04x %04x %04x\n",
+                       (int)le16_to_cpu(logr->reserved1[0]),
+               (int)le16_to_cpu(logr->reserved1[1]),
+               (int)le16_to_cpu(logr->reserved1[2]));
+               detaillogr(ctx, logr);
+       }
+       if (optt) {
+               const char *state;
+
+               if (logr->record_type == const_cpu_to_le32(2))
+                       state = "--checkpoint--";
+               else
+                       state = commitment(le64_to_cpu(logr->this_lsn));
+               printf("      at %04x  %016llx %s (%ld) %s\n",k,
+                       (long long)le64_to_cpu(logr->this_lsn),
+                       state,
+                       (long)(le64_to_cpu(logr->this_lsn) - synced_lsn),
+                       actionname(le16_to_cpu(logr->redo_operation)));
+               if (logr->client_previous_lsn || logr->client_undo_next_lsn) {
+                       if (logr->client_previous_lsn
+                                       == logr->client_undo_next_lsn) {
+                               printf("                               "
+                                       " previous and undo %016llx\n",
+                                       (long long)le64_to_cpu(
+                                               logr->client_previous_lsn));
+                       } else {
+                               printf("                               "
+                                       " previous %016llx",
+                                       (long long)le64_to_cpu(
+                                               logr->client_previous_lsn));
+                               
+                               if (logr->client_undo_next_lsn)
+                                       printf(" undo %016llx\n",
+                                               (long long)le64_to_cpu(
+                                               logr->client_undo_next_lsn));
+                               else
+                                       printf("\n");
+                       }
+               }
+       }
+}
+
+/*
+ *             Mark transactions which should be redone
+ */
+
+static void mark_transactions(struct ACTION_RECORD *lastaction)
+{
+       struct ACTION_RECORD *action;
+       const struct LOG_RECORD *logr;
+       le32 id;
+       int actives;
+       BOOL more;
+       BOOL committed;
+
+       actives = 0;
+       do {
+               more = FALSE;
+               id = const_cpu_to_le32(0);
+               for (action=lastaction; action; action=action->prev) {
+                       logr = &action->record;
+                       if ((logr->redo_operation
+                               == const_cpu_to_le16(ForgetTransaction))
+                           && !(action->flags & ACTION_TO_REDO)
+                           && !id) {
+                               id = logr->transaction_id;
+                               action->flags |= ACTION_TO_REDO;
+                               if (optv)
+                                       printf("Marking transaction 0x%x\n",
+                                               (int)le32_to_cpu(id));
+                       }
+                       committed = ((s64)(le64_to_cpu(logr->this_lsn)
+                                       - committed_lsn)) <= 0;
+                       if (!logr->transaction_id
+                           && committed)
+                               action->flags |= ACTION_TO_REDO;
+                       if (id
+                           && (logr->transaction_id == id)
+                           && committed) {
+                               action->flags |= ACTION_TO_REDO;
+                               more = TRUE;
+                       }
+               }
+       if (more)
+               actives++;
+       } while (more);
+               /*
+                * Show unmarked (aborted) actions
+                */
+       if (optv) {
+               for (action=lastaction; action; action=action->prev) {
+                       logr = &action->record;
+                       if (logr->transaction_id
+                          && !(action->flags & ACTION_TO_REDO))
+                               printf("** Action %d was aborted\n",
+                                       (int)action->num);
+               }
+       }
+       if (optv && (actives > 1))
+               printf("%d active transactions in set\n",actives);
+}
+
+/*
+ *             Enqueue an action and play the queued actions on end of set
+ */
+
+static TRISTATE enqueue_action(CONTEXT *ctx, const struct LOG_RECORD *logr,
+                               int size, int num)
+{
+       struct ACTION_RECORD *action;
+       TRISTATE state;
+       int err;
+       err = 1;
+       state = T_ERR;
+               /* enqueue record */
+       action = (struct ACTION_RECORD*)
+                       malloc(size + offsetof(struct ACTION_RECORD, record));
+       if (action) {
+               memcpy(&action->record, logr, size);
+               action->num = num;
+               action->flags = 0;
+               /* enqueue ahead of list, firstaction is the oldest one */
+               action->prev = (struct ACTION_RECORD*)NULL;
+               action->next = ctx->firstaction;
+               if (ctx->firstaction)
+                       ctx->firstaction->prev = action;
+               else
+                       ctx->lastaction = action;
+               ctx->firstaction = action;
+               err = 0;
+               state = T_OK;
+               if ((optp || optu)
+                   && (logr->record_type == const_cpu_to_le32(2))) {
+                       /* if chkp process queue, and increment count */
+                       playedactions++;
+                       if (playedactions <= playcount) {
+                               if (optv)
+                                       printf("* Refreshing attributes\n");
+                               err = refresh_attributes(ctx->firstaction);
+                               if (optv)
+                                       printf("* Undoing transaction set %d"
+                                               " (actions %d->%d)\n",
+                                               (int)playedactions,
+                                               (int)ctx->lastaction->num,
+                                               (int)ctx->firstaction->num);
+                               err = play_undos(ctx->vol, ctx->lastaction);
+                               if (err)
+                                       printf("* Undoing transaction"
+                                                       " set failed\n");
+                       }
+                       if (!err && optp && (playedactions == playcount)) {
+                               if (optv)
+                                       printf("* Redoing transaction set %d"
+                                               " (actions %d->%d)\n",
+                                               (int)playedactions,
+                                               (int)ctx->firstaction->num,
+                                               (int)ctx->lastaction->num);
+                               mark_transactions(ctx->lastaction);
+                               err = play_redos(ctx->vol, ctx->firstaction);
+                               if (err)
+                                       printf("* Redoing transaction"
+                                                       " set failed\n");
+                       }
+                       if (err)
+                               state = T_ERR;
+                       else
+                               if (playedactions == playcount)
+                                       state = T_DONE;
+                               /* free queue */
+                       while (ctx->firstaction) {
+                               action = ctx->firstaction->next;
+                               free(ctx->firstaction);
+                               ctx->firstaction = action;
+                       }
+                       ctx->lastaction = (struct ACTION_RECORD*)NULL;
+               }
+               if (opts
+                   && ((s64)(le64_to_cpu(logr->this_lsn) - synced_lsn) <= 0)) {
+                       if (optv)
+                               printf("* Refreshing attributes\n");
+// should refresh backward ?
+                       err = refresh_attributes(ctx->firstaction);
+                       mark_transactions(ctx->lastaction);
+                       if (!err) {
+                               if (optv)
+                                       printf("* Syncing actions %d->%d\n",
+                                               (int)ctx->firstaction->num,
+                                               (int)ctx->lastaction->num);
+                               err = play_redos(ctx->vol, ctx->firstaction);
+                       }
+                       if (err) {
+                               printf("* Syncing actions failed\n");
+                               state = T_ERR;
+                       } else
+                               state = T_DONE;
+               }
+       }
+       return (state);
+}
+
+
+static void showheadrcrd(u32 blk, const struct RECORD_PAGE_HEADER *rph)
+{
+       s32 diff;
+
+       if (optv) {
+               printf("magic              %08lx\n",
+                       (long)le32_to_cpu(rph->head.magic));
+               printf("usa_ofs            %04x\n",
+                       (int)le16_to_cpu(rph->head.usa_ofs));
+               printf("usa_count          %04x\n",
+                       (int)le16_to_cpu(rph->head.usa_count));
+               if (blk < 4)
+                       printf("file_offset        %08lx\n",
+                               (long)le32_to_cpu(rph->copy.file_offset));
+               else {
+                       diff = le64_to_cpu(rph->copy.last_lsn) - synced_lsn;
+                       printf("last_lsn           %016llx"
+                               " (synced%s%ld)\n",
+                               (long long)le64_to_cpu(rph->copy.last_lsn),
+                               (diff < 0 ? "" : "+"),(long)diff);
+               }
+               printf("flags              %08lx\n",
+                       (long)le32_to_cpu(rph->flags));
+               printf("page_count         %d\n",
+                       (int)le16_to_cpu(rph->page_count));
+               printf("page_position      %d\n",
+                       (int)le16_to_cpu(rph->page_position));
+               printf("next_record_offset %04x\n",
+                       (int)le16_to_cpu(rph->next_record_offset));
+               printf("reserved4          %04x %04x %04x\n",
+                       (int)le16_to_cpu(rph->reserved4[0]),
+                       (int)le16_to_cpu(rph->reserved4[1]),
+                       (int)le16_to_cpu(rph->reserved4[2]));
+               diff = le64_to_cpu(rph->last_end_lsn) - synced_lsn;
+               printf("last_end_lsn       %016llx (synced%s%ld)\n",
+                       (long long)le64_to_cpu(rph->last_end_lsn),
+                       (diff < 0 ? "" : "+"),(long)diff);
+               printf("usn                %04x\n",
+                       (int)getle16(rph,le16_to_cpu(rph->head.usa_ofs)));
+               printf("\n");
+       } else {
+               if (optt) {
+                       const char *state;
+
+                       state = commitment(le64_to_cpu(rph->copy.last_lsn));
+                       diff = le64_to_cpu(rph->copy.last_lsn) - synced_lsn;
+                       printf("   last        %016llx (synced%s%ld) %s\n",
+                               (long long)le64_to_cpu(rph->copy.last_lsn),
+                               (diff < 0 ? "" : "+"),(long)diff, state);
+                       state = commitment(le64_to_cpu(rph->last_end_lsn));
+                       diff = le64_to_cpu(rph->last_end_lsn) - synced_lsn;
+                       printf("   last_end    %016llx (synced%s%ld) %s\n",
+                               (long long)le64_to_cpu(rph->last_end_lsn),
+                               (diff < 0 ? "" : "+"),(long)diff, state);
+               }
+       }
+}
+
+/*
+ *             Analyze and display an action overlapping log blocks
+ *
+ *     Returns the position of first action in next block. If this is
+ *     greater than a block size (for actions overlapping more than
+ *     two blocks), then some blocks have to be skipped.
+ *
+ *     Returns 0 in case of error
+ */
+
+static u16 overlapshow(CONTEXT *ctx, u16 k, u32 blk, const struct BUFFER *buf,
+                       const struct BUFFER *nextbuf)
+{
+       const struct LOG_RECORD *logr;
+       const char *data;
+       const char *nextdata;
+       char *fullrec;
+       u32 size;
+       u32 nextspace;
+       u32 space;
+       BOOL likely;
+       u16 blkheadsz;
+
+       data = buf->block.data;
+       logr = (const struct LOG_RECORD*)&data[k];
+       size = le32_to_cpu(logr->client_data_length) + LOG_RECORD_HEAD_SZ;
+       blkheadsz = buf->headsz;
+       if (nextbuf && (blk >= BASEBLKS)) {
+               nextdata = nextbuf->block.data;
+               space = blocksz - k;
+               nextspace = blocksz - blkheadsz;
+               if ((space >= LOG_RECORD_HEAD_SZ)
+                   && (size > space)) {
+                       fullrec = (char*)malloc(size);
+                       if (size <= (space + nextspace)) {
+                               /* Overlap on two blocks */
+                               memcpy(fullrec,&data[k],space);
+                               memcpy(&fullrec[space],
+                                       nextdata + blkheadsz,
+                                       size - space);
+                               likely = likelyop((struct LOG_RECORD*)fullrec);
+                               actionnum++;
+                               if (optv) {
+                                       printf("\nOverlapping record %u at 0x%x"
+                                               " size %d (next at 0x%x)\n",
+                                               (int)actionnum,(int)k,
+                                               (int)size, (int)(k + size));
+                                       printf("Overlap marked for block %ld"
+                                               " space %d likely %d\n",
+                                               (long)blk,(int)space,likely);
+                               }
+                               if (likely)
+                                       showlogr(ctx, k,
+                                               (struct LOG_RECORD*)fullrec);
+                               else
+                                       printf("** Skipping unlikely"
+                                               " overlapping record\n");
+                               k += size - blocksz + blkheadsz;
+                       } else {
+                               const struct BUFFER *midbuf;
+                               int skip;
+                               u32 next;
+                               u32 pos;
+                               int i;
+
+                       /*
+                        * The maximum size of of log record is 131104
+                        * (when both offset and length are 65528 for
+                        * redo or undo).
+                        * So up to 33 log blocks (useful size 4032)
+                        * could be needed. However never both undo and
+                        * redo have been found big, and 17 should be
+                        * the real maximum.
+                        */
+                               if (optv)
+                                       printf("More than two blocks required"
+                                               " (size %lu)\n",(long)size);
+                               memcpy(fullrec,&data[k],space);
+
+                               skip = (size - space - 1)/nextspace;
+                               pos = space;
+                               likely = TRUE;
+                               for (i=1; (i<=skip) && likely; i++) {
+                                       midbuf = read_buffer(ctx, blk + i);
+                                       if (midbuf) {
+                                               memcpy(&fullrec[pos],
+                                                       &midbuf->block
+                                                           .data[blkheadsz],
+                                                       nextspace);
+                                               pos += nextspace;
+                                       } else
+                                               likely = FALSE;
+                               }
+                               if (pos >= size) {
+                                       printf("** Error : bad big overlap"
+                                               " pos %d size %d\n",
+                                               (int)pos,(int)size);
+                                       likely = FALSE;
+                               }
+                               midbuf = read_buffer(ctx, blk + skip + 1);
+                               if (midbuf)
+                                       memcpy(&fullrec[pos],
+                                               &midbuf->block.data[blkheadsz],
+                                               size - pos);
+                               else
+                                       likely = FALSE;
+                               if (!likelyop((struct LOG_RECORD*)fullrec))
+                                       likely = FALSE;
+                               actionnum++;
+                               if (optv) {
+                                       printf("\nBig overlapping record %u at "
+                                               "0x%x size %u (next at 0x%x)\n",
+                                               (int)actionnum,(int)k,(int)size,
+                                               (int)(k + size));
+                                       printf("Overlap marked for block %ld"
+                                               " space %d likely %d\n",
+                                               (long)blk,(int)space,likely);
+                               }
+                               if (likely)
+                                       showlogr(ctx, k,
+                                               (struct LOG_RECORD*)fullrec);
+                               else
+                                       printf("** Skipping unlikely"
+                                               " overlapping record\n");
+                               /* next and skip are only for displaying */
+                               next = (size - space) % nextspace
+                                                       + blkheadsz;
+                               if ((blocksz - next) < LOG_RECORD_HEAD_SZ)
+                                       next = blkheadsz;
+                               if (next == blkheadsz)
+                                       skip++;
+                               if (optv)
+                                       printf("Next record expected in"
+                                               " block %lu index 0x%x\n",
+                                               (long)(blk + skip + 1),next);
+                                       /* Quick check, with no consequences */
+                               if (firstrecord(skip,buf,buf) != next)
+                                       printf("** Error next != firstrecord"
+                                               " after block %d\n",blk);
+                               k += size - blocksz + blkheadsz;
+                       }
+                       if (!likely)
+                               k = 0;
+                       else
+                               if (!k)
+                                       printf("* Bad return from overlap()\n");
+                       free(fullrec);
+               } else {
+                       /* No conditions for overlap, usually a new session */
+                       printf("* No block found overlapping on block %d\n",
+                                       (int)blk);
+                       k = 0;
+               }
+       } else {
+               /* blocks 2, 3 and the last one have no next block */
+               k = 0;
+       }
+       return (k);
+}
+
+/*
+ *             Analyze and forward display the actions in a log block
+ *
+ *     Returns the position of first action in next block. If this is
+ *     greater than a block size, then some blocks have to be skipped.
+ *
+ *     Returns 0 in case of error
+ */
+
+static u16 forward_rcrd(CONTEXT *ctx, u32 blk, u16 pos,
+                       const struct BUFFER *buf, const struct BUFFER *nextbuf)
+{
+       const struct RECORD_PAGE_HEADER *rph;
+       const struct LOG_RECORD *logr;
+       const char *data;
+       u16 k;
+       u16 endoff;
+       BOOL stop;
+
+       rph = &buf->block.record;
+       if (rph && (rph->head.magic == magic_RCRD)) {
+               data = buf->block.data;
+               showheadrcrd(blk, rph);
+               k = buf->headsz;
+               if ((k < pos) && (pos < blocksz)) {
+                       k = ((pos - 1) | 7) + 1;
+               }
+// TODO check bad start > blocksz - 48
+               logr = (const struct LOG_RECORD*)&data[k];
+               stop = FALSE;
+               if (!likelyop(logr)) {
+                       if (optv)
+                               printf("* Bad start 0x%x for block %d\n",
+                                       (int)pos,(int)blk);
+                       k = searchlikely(buf);
+                       if ((k + sizeof(struct LOG_RECORD)) > blocksz) {
+                               printf("No likely full record in block %lu\n",
+                                               (unsigned long)blk);
+                     /* there can be a partial one */
+                               k = le16_to_cpu(rph->next_record_offset);
+                               if ((k < (u16)sizeof(struct RECORD_PAGE_HEADER))
+                                   || ((blocksz - k) < LOG_RECORD_HEAD_SZ))
+                                       stop = TRUE;
+                       } else {
+                               if (optv)
+                                       printf("First record computed at"
+                                               " offset 0x%x\n", (int)k);
+                       }
+               }
+               while (!stop) {
+                       s32 size;
+
+                       logr = (const struct LOG_RECORD*)&data[k];
+                       size = le32_to_cpu(logr->client_data_length)
+                                               + LOG_RECORD_HEAD_SZ;
+                       if ((size < MINRECSIZE)
+                           || (size > MAXRECSIZE)
+                           || (size & 7)) {
+                               printf("** Bad record size %ld in block %ld"
+                                       " offset 0x%x\n",
+                                       (long)size, (long)buf->num, (int)k);
+                               showlogr(ctx, k, logr);
+                               k = 0;
+                               stop = TRUE;
+                       } else {
+                               endoff = le16_to_cpu(rph->next_record_offset);
+                               if (((u32)(k + size) <= blocksz)
+                                   && ((u32)(k + size) <= endoff)) {
+                                       actionnum++;
+                                       if (optv) {
+                                               printf("\n* log action %u at"
+                                                       " 0x%x size %d (next"
+                                                       " at 0x%x)\n",
+                                                       actionnum,k,size,
+                                                       k + size);
+                                       }
+                                       showlogr(ctx, k, logr);
+                                       if (!logr->client_data_length) {
+                                               printf("** Bad"
+                                                   " client_data_length\n");
+                                               stop = TRUE;
+                                       }
+                                       k += size;
+                                       if ((blocksz - k)
+                                                       < LOG_RECORD_HEAD_SZ) {
+                                               k = nextbuf->headsz;
+                                               stop = TRUE;
+                                       }
+                               } else {
+                                       k = overlapshow(ctx, k, blk,
+                                                               buf, nextbuf);
+                                       stop = TRUE;
+                               }
+                       }
+               }
+       } else {
+               printf("** Not a RCRD record, MAGIC 0x%08lx\n",
+                       (long)le32_to_cpu(rph->head.magic));
+               k = 0;
+       }
+       return (k);
+}
+
+/*
+ *                Display a restart page
+ */
+
+static void showrest(const struct RESTART_PAGE_HEADER *rest)
+{
+       const struct RESTART_AREA *resa;
+       const struct RESTART_CLIENT *rcli;
+       const char *data;
+
+       data = (const char*)rest;
+       if ((rest->head.magic == magic_RSTR)
+                       || (rest->head.magic == magic_CHKD)) {
+               if (optv) {
+                       printf("magic                  %08lx\n",
+                               (long)le32_to_cpu(rest->head.magic));
+                       printf("usa_ofs                %04x\n",
+                               (int)le16_to_cpu(rest->head.usa_ofs));
+                       printf("usa_count              %04x\n",
+                               (int)le16_to_cpu(rest->head.usa_count));
+                       printf("chkdsk_lsn             %016llx\n",
+                               (long long)le64_to_cpu(rest->chkdsk_lsn));
+                       printf("system_page_size       %08lx\n",
+                               (long)le32_to_cpu(rest->system_page_size));
+                       printf("log_page_size          %08lx\n",
+                               (long)le32_to_cpu(rest->log_page_size));
+                       printf("restart_offset         %04x\n",
+                               (int)le16_to_cpu(rest->restart_offset));
+                       printf("minor_vers             %d\n",
+                               (int)le16_to_cpu(rest->minor_ver));
+                       printf("major_vers             %d\n",
+                               (int)le16_to_cpu(rest->major_ver));
+                       printf("usn                    %04x\n",
+                               (int)le16_to_cpu(rest->usn));
+                       printf("\n");
+               } else {
+                       if (optt)
+                               printf("    chkdsk         %016llx\n",
+                                   (long long)le64_to_cpu(rest->chkdsk_lsn));
+               }
+               resa = (const struct RESTART_AREA*)
+                               &data[le16_to_cpu(rest->restart_offset)];
+               if (optv) {
+                       printf("current_lsn            %016llx\n",
+                               (long long)le64_to_cpu(resa->current_lsn));
+                       printf("log_clients            %04x\n",
+                               (int)le16_to_cpu(resa->log_clients));
+                       printf("client_free_list       %04x\n",
+                               (int)le16_to_cpu(resa->client_free_list));
+                       printf("client_in_use_list     %04x\n",
+                               (int)le16_to_cpu(resa->client_in_use_list));
+                       printf("flags                  %04x\n",
+                               (int)le16_to_cpu(resa->flags));
+                       printf("seq_number_bits        %08lx\n",
+                               (long)le32_to_cpu(resa->seq_number_bits));
+                       printf("restart_area_length    %04x\n",
+                               (int)le16_to_cpu(resa->restart_area_length));
+                       printf("client_array_offset    %04x\n",
+                               (int)le16_to_cpu(resa->client_array_offset));
+                       printf("file_size              %016llx\n",
+                               (long long)le64_to_cpu(resa->file_size));
+                       printf("last_lsn_data_len      %08lx\n",
+                               (long)le32_to_cpu(resa->last_lsn_data_length));
+                       printf("record_length          %04x\n",
+                               (int)le16_to_cpu(resa->record_length));
+                       printf("log_page_data_offs     %04x\n",
+                               (int)le16_to_cpu(resa->log_page_data_offset));
+                       printf("restart_log_open_count %08lx\n",
+                               (long)le32_to_cpu(resa->restart_log_open_count));
+                       printf("\n");
+               } else {
+                       if (optt)
+                               printf("    latest         %016llx\n",
+                                   (long long)le64_to_cpu(resa->current_lsn));
+               }
+
+               rcli = (const struct RESTART_CLIENT*)
+                               &data[le16_to_cpu(rest->restart_offset)
+                               + le16_to_cpu(resa->client_array_offset)];
+               if (optv) {
+                       printf("oldest_lsn             %016llx\n",
+                               (long long)le64_to_cpu(rcli->oldest_lsn));
+                       printf("client_restart_lsn     %016llx\n",
+                               (long long)le64_to_cpu(rcli->client_restart_lsn));
+                       printf("prev_client            %04x\n",
+                               (int)le16_to_cpu(rcli->prev_client));
+                       printf("next_client            %04x\n",
+                               (int)le16_to_cpu(rcli->next_client));
+                       printf("seq_number             %04x\n",
+                               (int)le16_to_cpu(rcli->seq_number));
+                       printf("client_name_length     %08x\n",
+                               (int)le32_to_cpu(rcli->client_name_length));
+                       showname("client_name            ",
+                               (const char*)rcli->client_name,
+                               le32_to_cpu(rcli->client_name_length) >> 1);
+               } else {
+                       if (optt) {
+                               printf("    synced         %016llx\n",
+                                       (long long)le64_to_cpu(
+                                               rcli->oldest_lsn));
+                               printf("    committed      %016llx\n",
+                                       (long long)le64_to_cpu(
+                                               rcli->client_restart_lsn));
+                       }
+               }
+       } else
+               printf("Not a RSTR or CHKD record, MAGIC 0x%08lx\n",
+                       (long)le32_to_cpu(rest->head.magic));
+}
+
+static BOOL dorest(CONTEXT *ctx, unsigned long blk,
+                       const struct RESTART_PAGE_HEADER *rph, BOOL initial)
+{
+       const struct RESTART_AREA *resa;
+       const struct RESTART_CLIENT *rcli;
+       const char *data;
+       s64 diff;
+       int offs;
+       int size;
+       BOOL change;
+       BOOL dirty;
+
+       data = (const char*)rph;
+       offs = le16_to_cpu(rph->restart_offset);
+       resa = (const struct RESTART_AREA*)&data[offs];
+       rcli = (const struct RESTART_CLIENT*)&data[offs
+                               + le16_to_cpu(resa->client_array_offset)];
+       if (initial) {
+               /* Information from block initially found best */
+               latest_lsn = le64_to_cpu(resa->current_lsn);
+               committed_lsn = le64_to_cpu(rcli->client_restart_lsn);
+               synced_lsn = le64_to_cpu(rcli->oldest_lsn);
+               memcpy(&log_header, rph,
+                               sizeof(struct RESTART_PAGE_HEADER));
+               offs = le16_to_cpu(log_header.restart_offset);
+               memcpy(&restart, &data[offs],
+                               sizeof(struct RESTART_AREA));
+               offs += le16_to_cpu(restart.client_array_offset);
+               memcpy(&client, &data[offs],
+                               sizeof(struct RESTART_CLIENT));
+               dirty = !(resa->flags & RESTART_VOLUME_IS_CLEAN);
+               if (optv || optt)
+                       printf("* Using initial restart page,"
+                               " syncing from 0x%llx, %s\n",
+                               (long long)synced_lsn,
+                               (dirty ? "dirty" : "clean"));
+                        /* Get the block page size */
+               blocksz = le32_to_cpu(rph->log_page_size);
+               if (optv)
+                       printf("* Block size %ld bytes\n", (long)blocksz);
+               blockbits = 1;
+               while ((u32)(1 << blockbits) < blocksz)
+                       blockbits++;
+       } else {
+               size = offs + le16_to_cpu(resa->restart_area_length);
+               if (optv) {
+                       if (optv >= 2)
+                               hexdump(data,size);
+                       printf("* RSTR in block %ld 0x%lx (addr 0x%llx)\n",
+                                       (long)blk,(long)blk,
+                                       (long long)loclogblk(ctx, blk));
+               } else {
+                       if (optt)
+                               printf("restart %ld\n",(long)blk);
+               }
+               showrest(rph);
+               /* Information from an older restart block if requested */
+               dirty = !(restart.flags & RESTART_VOLUME_IS_CLEAN);
+               diff = le64_to_cpu(rcli->client_restart_lsn) - committed_lsn;
+               if (ctx->vol) {
+                       change = (opts > 1) && (diff < 0);
+               } else {
+                       change = (opts > 1 ? diff < 0 : diff > 0);
+               }
+               if (change) {
+                       committed_lsn = le64_to_cpu(rcli->client_restart_lsn);
+                       synced_lsn = le64_to_cpu(rcli->oldest_lsn);
+                       latest_lsn = le64_to_cpu(resa->current_lsn);
+                       memcpy(&log_header, rph,
+                                       sizeof(struct RESTART_PAGE_HEADER));
+                       offs = le16_to_cpu(log_header.restart_offset);
+                       memcpy(&restart, &data[offs],
+                                       sizeof(struct RESTART_AREA));
+                       offs += le16_to_cpu(restart.client_array_offset);
+                       memcpy(&client, &data[offs],
+                                       sizeof(struct RESTART_CLIENT));
+                       dirty = !(resa->flags & RESTART_VOLUME_IS_CLEAN);
+                       if (optv || optt)
+                               printf("* Using %s restart page,"
+                                       " syncing from 0x%llx, %s\n",
+                                       (diff < 0 ? "older" : "newer"),
+                                       (long long)synced_lsn,
+                                       (dirty ? "dirty" : "clean"));
+               }
+       }
+       restart_lsn = synced_lsn;
+       return (dirty);
+}
+
+/*
+ *             Read and process the first restart block
+ *
+ *     In full mode, both restart page are silently analyzed by the
+ *     library and the most recent readable one is used to define the
+ *     sync parameters.
+ *
+ *     Returns the first restart buffer
+ *             or NULL if the restart block is not valid
+ */
+
+
+static const struct BUFFER *read_restart(CONTEXT *ctx)
+{
+       const struct BUFFER *buf;
+       BOOL bad;
+
+       bad = FALSE;
+       if (ctx->vol) {
+               struct RESTART_PAGE_HEADER *rph;
+
+               rph = (struct RESTART_PAGE_HEADER*)NULL;
+               /* Full mode : use the restart page selected by the library */
+               if (ntfs_check_logfile(log_na, &rph)) {
+                       /* rph is left unchanged for a wiped out log file */
+                       if (rph) {
+                               dorest(ctx, 0, rph, TRUE);
+                               free(rph);
+                               buf = read_buffer(ctx,0);
+                       } else {
+                               buf = (const struct BUFFER*)NULL;
+                               printf("** The log file has been wiped out\n");
+                       }
+               } else {
+                       buf = (const struct BUFFER*)NULL;
+                       printf("** Could not get any restart page\n");
+               }
+       } else {
+               /* Reduced mode : rely on first restart page */
+               blockbits = BLOCKBITS;  /* Until the correct value is read */
+               blocksz = 1L << blockbits;
+               buf = read_buffer(ctx,0);
+       }
+       if (buf) {
+               NTFS_RECORD_TYPES magic;
+
+               magic = buf->block.restart.head.magic;
+               switch (magic) {
+               case magic_RSTR :
+                       break;
+               case magic_CHKD :
+                       printf("** The log file has been obsoleted by chkdsk\n");
+                       bad = TRUE;
+                       break;
+               case magic_empty :
+                       printf("** The log file has been wiped out\n");
+                       bad = TRUE;
+                       break;
+               default :
+                       printf("** Invalid restart block\n");
+                       bad = TRUE;
+                       break;
+               }
+               if (!bad && !ctx->vol)
+                       dorest(ctx, 0, &buf->block.restart, TRUE);
+               if ((buf->block.restart.major_ver != const_cpu_to_le16(1))
+                   || (buf->block.restart.minor_ver != const_cpu_to_le16(1))) {
+                       printf("** Unsupported $LogFile version %d.%d\n",
+                               le16_to_cpu(buf->block.restart.major_ver),
+                               le16_to_cpu(buf->block.restart.minor_ver));
+                       bad = TRUE;
+               }
+               if (bad) {
+                       buf = (const struct BUFFER*)NULL;
+               }
+       }
+       return (buf);
+}
+
+/*
+ *             Mark the logfile as synced
+ */
+
+static int reset_logfile(CONTEXT *ctx __attribute__((unused)))
+{
+       char *buffer;
+       int off;
+       int err;
+
+       err = 1;
+       buffer = (char*)malloc(blocksz);
+       if (buffer) {
+               memset(buffer, 0, blocksz);
+               restart.client_in_use_list = LOGFILE_NO_CLIENT;
+               restart.flags |= RESTART_VOLUME_IS_CLEAN;
+               client.oldest_lsn = cpu_to_le64(restart_lsn);
+               memcpy(buffer, &log_header,
+                                       sizeof(struct RESTART_PAGE_HEADER));
+               off = le16_to_cpu(log_header.restart_offset);
+               memcpy(&buffer[off], &restart,
+                                       sizeof(struct RESTART_AREA));
+               off += le16_to_cpu(restart.client_array_offset);
+               memcpy(&buffer[off], &client,
+                                       sizeof(struct RESTART_CLIENT));
+               if (!ntfs_mst_pre_write_fixup((NTFS_RECORD*)buffer, blocksz)
+                   && (ntfs_attr_pwrite(log_na, 0,
+                               blocksz, buffer) == blocksz)
+                   && (ntfs_attr_pwrite(log_na, (u64)1 << blockbits,
+                               blocksz, buffer) == blocksz))
+                       err = 0;
+       }
+       return (err);
+}
+
+/*
+ *             Determine the most recent valid record block
+ */
+
+static const struct BUFFER *best_start(const struct BUFFER *buf,
+                               const struct BUFFER *altbuf)
+{
+       const struct BUFFER *best;
+       const struct RECORD_PAGE_HEADER *head;
+       const struct RECORD_PAGE_HEADER *althead;
+       s64 diff;
+
+       if (!buf || !altbuf)
+               best = (buf ? buf : altbuf);
+       else {
+               head = &buf->block.record;
+               althead = &altbuf->block.record;
+               /* determine most recent, caring for wraparounds */
+               diff = le64_to_cpu(althead->last_end_lsn)
+                                       - le64_to_cpu(head->last_end_lsn);
+               if (diff > 0)
+                       best = altbuf;
+               else
+                       best = buf;
+       }
+       if (best && (best->block.record.head.magic != magic_RCRD))
+               best = (const struct BUFFER*)NULL;
+       return (best);
+}
+
+/*
+ *                 Interpret the boot data
+ *
+ *     Probably not needed any more, use ctx->vol
+ */
+
+static BOOL getboot(const char *buf)
+{
+       u64 sectors;
+       u64 clusters;
+       u16 sectpercluster;
+       BOOL ok;
+
+       ok = TRUE;
+       /* Beware : bad alignment */
+       bytespersect = (buf[11] & 255) + ((buf[12] & 255) << 8);
+       sectpercluster = buf[13] & 255;
+       clustersz = bytespersect * (u32)sectpercluster;
+       clusterbits = 1;
+       while ((u32)(1 << clusterbits) < clustersz)
+               clusterbits++;
+       sectors = getle64(buf, 0x28);
+       clusters = sectors/sectpercluster;
+       mftlcn = getle64(buf, 0x30);
+       if (buf[0x40] & 0x80)
+               mftrecsz = 1 << (16 - (buf[0x40] & 15));
+       else
+               mftrecsz = (buf[0x40] & 127)*clustersz;
+       mftrecbits = 1;
+       while ((u32)(1 << mftrecbits) < mftrecsz)
+               mftrecbits++;
+       if (optv) {
+               if ((long long)sectors*bytespersect > 10000000000LL)
+                       printf("Capacity %lld bytes (%lld GB)\n",
+                               (long long)sectors*bytespersect,
+                               (long long)sectors*bytespersect/1000000000);
+               else
+                       printf("Capacity %lld bytes (%lld MB)\n",
+                               (long long)sectors*bytespersect,
+                               (long long)sectors*bytespersect/1000000);
+               printf("sectors %lld (0x%llx), sector size %d\n",
+                               (long long)sectors,(long long)sectors,
+                               (int)bytespersect);
+               printf("clusters %lld (0x%llx), cluster size %d (%d bits)\n",
+                               (long long)clusters,(long long)clusters,
+                               (int)clustersz,(int)clusterbits);
+               printf("MFT at cluster %lld (0x%llx), entry size %lu\n",
+                               (long long)mftlcn,(long long)mftlcn,
+                               (unsigned long)mftrecsz);
+               if (mftrecsz > clustersz)
+                       printf("%ld clusters per MFT entry\n",
+                               (long)(mftrecsz/clustersz));
+               else
+                       printf("%ld MFT entries per cluster\n",
+                               (long)(clustersz/mftrecsz));
+       }
+       return (ok);
+}
+
+static int locatelogfile(CONTEXT *ctx)
+{
+       int err;
+
+       err = 1;
+       log_ni = ntfs_inode_open(ctx->vol, FILE_LogFile);
+       if (log_ni) {
+               log_na = ntfs_attr_open(log_ni, AT_DATA, AT_UNNAMED, 0);
+               if (log_na) {
+                       logfilesz = log_na->data_size;
+                       err = 0;
+               }
+       }
+       return (err);
+}
+
+/*
+ *             Analyze a $LogFile copy
+ *
+ *     A $LogFile cannot be played. It can be however be analyzed in
+ *     stand-alone mode.
+ *     The location of the $MFT will have to be determined elsewhere.
+ */
+
+static BOOL getlogfiledata(CONTEXT *ctx, const char *boot)
+{
+       const struct RESTART_PAGE_HEADER *rph;
+       const struct RESTART_AREA *rest;
+       BOOL ok;
+       u32 off;
+       s64 size;
+
+       ok = FALSE;
+       fseek(ctx->file,0L,2);
+       size = ftell(ctx->file);
+       rph = (const struct RESTART_PAGE_HEADER*)boot;
+       off = le16_to_cpu(rph->restart_offset);
+       rest = (const struct RESTART_AREA*)&boot[off];
+
+               /* estimate cluster size from log file size (unreliable) */
+       switch (le32_to_cpu(rest->seq_number_bits)) {
+       case 45 : clustersz = 512; break;
+       case 43 : clustersz = 1024; break; /* can be 1024 or 2048 */
+       case 40 :
+       default : clustersz = 4096; break;
+       }
+
+       clusterbits = 1;
+       while ((u32)(1 << clusterbits) < clustersz)
+               clusterbits++;
+       printf("* Assuming cluster size %ld\n",(long)clustersz);
+       logfilelcn = 0;
+       logfilesz = size;
+       if (optv)
+               printf("Log file size %lld bytes, cluster size %ld\n",
+                       (long long)size, (long)clustersz);
+       /* Have to wait an InitializeFileRecordSegment to get these values */
+       mftrecsz = 0;
+       mftrecbits = 0;
+       ok = TRUE;
+       return (ok);
+}
+
+/*
+ *                 Get basic volume data
+ *
+ *     Locate the MFT and Logfile
+ *     Not supposed to read the first log block...
+ */
+
+static BOOL getvolumedata(CONTEXT *ctx, char *boot)
+{
+       const struct RESTART_AREA *rest;
+       BOOL ok;
+
+       ok = FALSE;
+       rest = (const struct RESTART_AREA*)NULL;
+       if (ctx->vol) {
+               getboot(boot);
+               mftlcn = ctx->vol->mft_lcn;
+               mftcnt = ctx->vol->mft_na->data_size/mftrecsz;
+               if (!locatelogfile(ctx))
+                       ok = TRUE;
+               else {
+                       fprintf(stderr,"** Could not read the log file\n");
+               }
+       } else {
+               if (ctx->file
+                   && (!memcmp(boot,"RSTR",4) || !memcmp(boot,"CHKD",4))) {
+                       printf("* Assuming a log file copy\n");
+                       getlogfiledata(ctx, boot);
+                       ok = TRUE;
+               } else
+                       fprintf(stderr,"** Not an NTFS image or log file\n");
+               }
+// TODO get rest ?, meaningful ?
+       if (ok && rest) {
+               if (rest->client_in_use_list
+                  || !(rest->flags & const_cpu_to_le16(2)))
+                       printf("Volume was not unmounted safely\n");
+               else
+                       printf("Volume was unmounted safely\n");
+               if (le16_to_cpu(rest->client_in_use_list) > 1)
+                       printf("** multiple clients not implemented\n");
+       }
+       return (ok);
+}
+
+/*
+ *             Open the volume (or the log file) and gets its parameters
+ *
+ *     Returns TRUE if successful
+ */
+
+static BOOL open_volume(CONTEXT *ctx, const char *device_name)
+{
+       union {
+               char buf[1024];
+                /* alignment may be needed in getboot() */
+               long long force_align;
+       } boot;
+       BOOL ok;
+       int got;
+
+       ok =FALSE;
+               /*
+                * First check the boot sector, to avoid library errors
+                * when trying to mount a log file.
+                * If the device cannot be fopened or fread, then it is
+                * unlikely to be a file.
+                */
+       ctx->vol = (ntfs_volume*)NULL;
+       ctx->file = fopen(device_name, "rb");
+       if (ctx->file) {
+               got = fread(boot.buf,1,1024,ctx->file);
+               if ((got == 1024)
+                   && (!memcmp(boot.buf, "RSTR", 4)
+                               || !memcmp(boot.buf, "CHKD", 4))) {
+                       /* This appears to be a log file */
+                       ctx->vol = (ntfs_volume*)NULL;
+                       ok = getvolumedata(ctx, boot.buf);
+               }
+               if (!ok)
+                       fclose(ctx->file);
+       }
+       if (!ok) {
+               /* Not a log file, assume an ntfs device, mount it */
+               ctx->file = (FILE*)NULL;
+               ctx->vol = ntfs_mount(device_name,
+                       ((optp || optu || opts) && !optn
+                               ? NTFS_MNT_FORENSIC : NTFS_MNT_RDONLY));
+               if (ctx->vol) {
+                       ok = getvolumedata(ctx, boot.buf);
+                       if (!ok)
+                               ntfs_umount(ctx->vol, TRUE);
+               }
+       }
+       return (ok);
+}
+
+static u16 dorcrd(CONTEXT *ctx, u32 blk, u16 pos, const struct BUFFER *buf,
+                       const struct BUFFER *nextbuf)
+{
+       if (optv) {
+               if (optv >= 2)
+                       hexdump(buf->block.data,blocksz);
+               printf("* RCRD in block %ld 0x%lx (addr 0x%llx)"
+                       " from pos 0x%x\n",
+                       (long)blk,(long)blk,
+                       (long long)loclogblk(ctx, blk),(int)pos);
+       } else {
+               if (optt)
+                       printf("block %ld\n",(long)blk);
+       }
+       return (forward_rcrd(ctx, blk, pos, buf, nextbuf));
+}
+
+/*
+ *             Concatenate and process a record overlapping on several blocks
+ */
+
+static TRISTATE backoverlap(CONTEXT *ctx, int blk,
+                       const char *data, const char *nextdata, int k)
+{
+       const struct LOG_RECORD *logr;
+       char *fullrec;
+       s32 size;
+       int space;
+       int nextspace;
+       TRISTATE state;
+       u16 blkheadsz;
+
+       logr = (const struct LOG_RECORD*)&data[k];
+       state = T_ERR;
+       size = le32_to_cpu(logr->client_data_length) + LOG_RECORD_HEAD_SZ;
+       space = blocksz - k;
+       blkheadsz = sizeof(struct RECORD_PAGE_HEADER)
+                       + ((2*getle16(data,6) - 1) | 7) + 1;
+       nextspace = blocksz - blkheadsz;
+       if ((space >= LOG_RECORD_HEAD_SZ)
+           && (size > space)
+           && (size < MAXRECSIZE)) {
+               fullrec = (char*)malloc(size);
+               memcpy(fullrec,&data[k],space);
+               if (size <= (space + nextspace))
+                       memcpy(&fullrec[space], nextdata + blkheadsz,
+                                               size - space);
+               else {
+                       const struct BUFFER *morebuf;
+                       const char *moredata;
+                       int total;
+                       int more;
+                       unsigned int mblk;
+
+                       if (optv)
+                               printf("* big record, size %d\n",size);
+                       total = space;
+                       mblk = blk + 1;
+                       while (total < size) {
+                               if (mblk >= (logfilesz >> blockbits))
+                                       mblk = BASEBLKS;
+                               more = size - total;
+                               if (more > nextspace)
+                                       more = nextspace;
+                               morebuf = read_buffer(ctx, mblk);
+                               if (morebuf) {
+                                       moredata = morebuf->block.data;
+                                       memcpy(&fullrec[total],
+                                               moredata + blkheadsz, more);
+                               }
+                               total += more;
+                               mblk++;
+                       }
+               }
+
+               state = (likelyop((struct LOG_RECORD*)fullrec) ? T_OK : T_ERR);
+               actionnum++;
+               if (optv) {
+                       printf("\nOverlapping backward action %d at 0x%x"
+                               " size %d (next at 0x%x)\n",
+                               (int)actionnum,(int)k,
+                               (int)size,(int)(k + size));
+                       printf("Overlap marked for block %ld space %d"
+                               " likely %d\n",
+                               (long)blk,(int)space,(state == T_OK));
+               }
+               if (state == T_OK) {
+                       showlogr(ctx, k, (struct LOG_RECORD*)fullrec);
+                       if (optp || optu || opts)
+                               state = enqueue_action(ctx,
+                                               (struct LOG_RECORD*)fullrec,
+                                               size, actionnum);
+               } else {
+                       /* Try to go on unless playing actions */
+                       if (optb && (state == T_ERR))
+                               state = T_OK;
+               }
+               free(fullrec);
+       } else {
+                       /* Error conditions */
+               if ((size < MINRECSIZE) || (size > MAXRECSIZE)) {
+                       printf("** Invalid record size %ld"
+                                       " in block %ld\n",
+                                       (long)size,(long)blk);
+               } else
+                       printf("** Inconsistency : the final"
+                                               " record in block %ld"
+                                               " does not overlap\n",
+                                               (long)blk);
+                       /* Do not abort, unless playing actions */
+               state = (optb ? T_OK : T_ERR);
+       }
+       return (state);
+}
+
+static TRISTATE backward_rcrd(CONTEXT *ctx, u32 blk, int skipped,
+                  const struct BUFFER *buf, const struct BUFFER *prevbuf,
+                  const struct BUFFER *nextbuf)
+{
+       u16 poslist[75]; /* 4096/sizeof(struct LOG_RECORD) */
+       const struct RECORD_PAGE_HEADER *rph;
+       const struct RECORD_PAGE_HEADER *prevrph;
+       const struct LOG_RECORD *logr;
+       const char *data;
+       const char *nextdata;
+       BOOL stop;
+       TRISTATE state;
+       s32 size;
+       int cnt;
+       u16 k;
+       u16 endoff;
+       int j;
+
+       state = T_ERR;
+       rph = &buf->block.record;
+       prevrph = (struct RECORD_PAGE_HEADER*)NULL;
+       if (prevbuf)
+               prevrph = &prevbuf->block.record;
+       data = buf->block.data;
+       nextdata = nextbuf->block.data;
+       if (rph && (rph->head.magic == magic_RCRD)
+           && (!prevrph || (prevrph->head.magic == magic_RCRD))) {
+               if (optv) {
+                       if (optv >= 2)
+                               hexdump(data,blocksz);
+                       printf("* RCRD in block %ld 0x%lx (addr 0x%llx)\n",
+                            (long)blk,(long)blk,
+                            (long long)loclogblk(ctx, blk));
+               } else {
+                       if (optt)
+                               printf("block %ld\n",(long)blk);
+               }
+               showheadrcrd(blk, rph);
+               if (!prevbuf)
+                       k = buf->headsz;
+               else
+                       k = firstrecord(skipped, buf, prevbuf);
+               logr = (const struct LOG_RECORD*)&data[k];
+               cnt = 0;
+          /* check whether there is at least one beginning of record */
+               endoff = le16_to_cpu(rph->next_record_offset);
+               if (k && ((k < endoff) || !endoff)) {
+                       logr = (const struct LOG_RECORD*)&data[k];
+                       if (likelyop(logr)) {
+                               stop = FALSE;
+                               state = T_OK;
+                               if (optv)
+                                       printf("First record checked"
+                                               " at offset 0x%x\n", (int)k);
+                       } else {
+                               printf("** Bad first record at offset 0x%x\n",
+                                                               (int)k);
+                               if (optv)
+                                       showlogr(ctx, k,logr);
+                               k = searchlikely(buf);
+                               stop = !k;
+                               if (stop) {
+                                       printf("** Could not recover,"
+                                               " stopping at block %d\n",
+                                               (int)blk);
+                                       state = T_ERR;
+                               } else {
+                                       /* Try to go on, unless running */
+                                       if (optb)
+                                               state = T_OK;
+                               }
+                       }
+                       while (!stop) {
+                               logr = (const struct LOG_RECORD*)&data[k];
+                               size = le32_to_cpu(logr->client_data_length)
+                                               + LOG_RECORD_HEAD_SZ;
+                               if ((size < MINRECSIZE)
+                                   || (size > MAXRECSIZE)
+                                   || (size & 7)) {
+                                       printf("** Bad size %ld in block %ld"
+                                               " offset 0x%x, stopping\n",
+                                               (long)size,(long)blk,(int)k);
+                                       stop = TRUE;
+                               } else {
+                                       if (((u32)(k + size) <= blocksz)
+                                           && ((u32)(k + size) <= endoff)) {
+                                               poslist[cnt++] = k;
+                                               if (!logr->client_data_length)
+                                                       stop = TRUE;
+                                               k += size;
+                                               if ((u32)(k
+                                                   + LOG_RECORD_HEAD_SZ)
+                                                   > blocksz)
+                                                       stop = TRUE;
+                                       } else {
+                                               stop = TRUE;
+                                       }         
+                               }
+                       }
+               } else {
+                       stop = TRUE;
+                       state = (k ? T_OK : T_ERR);
+               }
+                     /* Now examine an overlapping record */
+               if (k
+                   && ((k == endoff) || !endoff)
+                   && ((u32)(k + LOG_RECORD_HEAD_SZ) <= blocksz)) {
+                       if (nextbuf && (blk >= BASEBLKS)) {
+                               state = backoverlap(ctx, blk,
+                                               data, nextdata, k);
+                       }
+               }
+               for (j=cnt-1; (j>=0) && (state==T_OK); j--) {
+                       k = poslist[j];
+                       logr = (const struct LOG_RECORD*)&data[k];
+                       size = le32_to_cpu(logr->client_data_length)
+                                       + LOG_RECORD_HEAD_SZ;
+                       actionnum++;
+                       if (optv && (!optc || within_lcn_range(logr))) {
+                               printf("\n* log backward action %u at 0x%x"
+                                       " size %d (next at 0x%x)\n",
+                                       actionnum, k, size, k + size);
+                       }
+                       if ((optv | optt)
+                           && (!nextbuf && (j == (cnt - 1)))) {
+                               printf("* This is the latest record\n");
+                               if (logr->this_lsn == restart.current_lsn)
+                                       printf("   its lsn matches the global"
+                                               " restart lsn\n");
+                               if (logr->this_lsn == client.client_restart_lsn)
+                                       printf("   its lsn matches the client"
+                                               " restart lsn\n");
+                               if (logr->client_data_length
+                                   == restart.last_lsn_data_length)
+                                       printf("   its length matches the"
+                                               " last record length\n");
+                       }
+               showlogr(ctx, k, logr);
+               if (optp || optu || opts)
+                       state = enqueue_action(ctx, logr, size, actionnum);
+               }
+       }
+       return (state);
+}
+
+static int walkback(CONTEXT *ctx, const struct BUFFER *buf, u32 blk,
+                       const struct BUFFER *prevbuf, u32 prevblk)
+{
+       const struct BUFFER *nextbuf;
+       NTFS_RECORD_TYPES magic;
+       u32 stopblk;
+       TRISTATE state;
+
+       if (optv)
+               printf("\n* block %d at 0x%llx\n",(int)blk,
+                                       (long long)loclogblk(ctx, blk));
+       ctx->firstaction = (struct ACTION_RECORD*)NULL;
+       ctx->lastaction = (struct ACTION_RECORD*)NULL;
+       nextbuf = (const struct BUFFER*)NULL;
+       stopblk = prevblk + 2; // wraparound !
+       state = backward_rcrd(ctx, blk, 0, buf,
+                       prevbuf, (struct BUFFER*)NULL);
+       while ((state == T_OK)
+          && !((blk > stopblk) && (prevblk <= stopblk))
+          && (!(optp || optu) || (playedactions < playcount))) {
+               int skipped;
+
+               nextbuf = buf;
+               buf = prevbuf;
+               blk = prevblk;
+               skipped = 0;
+               prevbuf = findprevious(ctx, buf);
+               if (prevbuf) {
+                       prevblk = prevbuf->num;
+                       if (prevblk < blk)
+                               skipped = blk - prevblk - 1;
+                       else
+                               skipped = blk - prevblk - 1
+                                       + (logfilesz >> blockbits) - BASEBLKS;
+                       magic = prevbuf->block.record.head.magic;
+                       switch (magic) {
+                       case magic_RCRD :
+                               break;
+                       case magic_CHKD :
+                               printf("** Unexpected block type CHKD\n");
+                               break;
+                       case magic_RSTR :
+                               printf("** Unexpected block type RSTR\n");
+                               break;
+                       default :
+                               printf("** Invalid block %d\n",(int)prevblk);
+                               break;
+                       }
+                       if (optv) {
+                               if (skipped)
+                                       printf("\n* block %ld at 0x%llx (block"
+                                               " %ld used as previous one)\n",
+                                               (long)blk,
+                                               (long long)loclogblk(ctx, blk),
+                                               (long)prevblk);
+                               else
+                                       printf("\n* block %ld at 0x%llx\n",
+                                               (long)blk,
+                                               (long long)loclogblk(ctx, blk));
+                       }
+                       state = backward_rcrd(ctx, blk, skipped,
+                                               buf, prevbuf, nextbuf);
+               } else {
+                       fprintf(stderr,"** Could not read block %lu\n",
+                                                               (long)prevblk);
+                       state = T_ERR;
+               }
+       }
+       if ((blk > stopblk) && (prevblk <= stopblk))
+               printf("* Earliest block reached\n");
+       if ((optp || optu) && (playedactions >= playcount))
+               printf("* Transaction set count reached\n");
+       if (opts)
+               printf("* %s %s after playing %u actions\n",
+                               (optn ? "Sync simulation" : "Syncing"),
+                               (state == T_ERR ? "failed" : "successful"),
+                               redocount);
+                       /* free queue */
+       while (ctx->firstaction) {
+               struct ACTION_RECORD *action;
+
+               action = ctx->firstaction->next;
+               free(ctx->firstaction);
+               ctx->firstaction = action;
+               }
+       ctx->lastaction = (struct ACTION_RECORD*)NULL;
+       return (state == T_ERR ? 1 : 0);
+}
+
+static int walk(CONTEXT *ctx)
+{
+       const struct BUFFER *buf;
+       const struct BUFFER *nextbuf;
+       const struct BUFFER *prevbuf;
+       const struct BUFFER *startbuf;
+       const NTFS_RECORD *record;
+       const struct RECORD_PAGE_HEADER *rph;
+       NTFS_RECORD_TYPES magic;
+       u32 blk;
+       u32 nextblk;
+       u32 prevblk;
+       int err;
+       u16 blkheadsz;
+       u16 pos;
+       BOOL dirty;
+       BOOL done;
+
+       buf = (struct BUFFER*)NULL;
+       nextbuf = (struct BUFFER*)NULL;
+       if (optb || optp || optu || opts) {
+               prevbuf = (struct BUFFER*)NULL;
+       }
+       done = FALSE;
+       dirty = TRUE;
+       err = 0;
+       blk = 0;
+       pos = 0;
+                       /* read and process the first restart block */
+       buf = read_restart(ctx);
+       if (buf) {
+               if (optv)
+                       printf("\n* block %d at 0x%llx\n",(int)blk,
+                                       (long long)loclogblk(ctx, blk));
+       } else {
+               done = TRUE;
+               err = 1;
+       }
+
+       nextblk = blk + 1;
+       while (!done) {
+                /* next block is needed to process the current one */
+               if ((nextblk >= (logfilesz >> blockbits)) && (optr || optf))
+                       nextbuf = read_buffer(ctx, BASEBLKS);
+               else
+                       nextbuf = read_buffer(ctx,nextblk);
+               if (nextbuf) {
+                       record = (const NTFS_RECORD*)&nextbuf->block.data;
+                       blkheadsz = nextbuf->headsz;
+                       magic = record->magic;
+                       switch (magic) {
+                       case magic_CHKD :
+                       case magic_RSTR :
+                       case magic_RCRD :
+                               break;
+                       default :
+                               printf("** Invalid block\n");
+                               err = 1;
+                               break;
+                       }
+                       magic = buf->block.record.head.magic;
+                       switch (magic) {
+                       case magic_CHKD :
+                       case magic_RSTR :
+                               dirty = dorest(ctx, blk, &buf->block.restart,
+                                                               FALSE);
+                               break;
+                       case magic_RCRD :
+                               if (blk < BASEBLKS)
+                                       pos = buf->headsz;
+                               pos = dorcrd(ctx, blk, pos, buf, nextbuf);
+                               while (pos >= blocksz) {
+                                       if (optv > 1)
+                                               printf("Skipping block %d"
+                                               " pos 0x%x\n",
+                                               (int)nextblk,(int)pos);
+                                       pos -= (blocksz - blkheadsz);
+                                       nextblk++;
+                                       }
+                               if ((blocksz - pos) < LOG_RECORD_HEAD_SZ) {
+                                       pos = 0;
+                                       nextblk++;
+                               }
+                               if (nextblk != (blk + 1)) {
+                                       nextbuf = read_buffer(ctx,nextblk);
+                               }
+                               break;
+                       default :
+                               if (!~magic) {
+                                       if (optv)
+                                               printf("   empty block\n");
+                               }
+                               break;
+                       }
+               } else {
+                       fprintf(stderr,"* Could not read block %d\n",nextblk);
+                       if (ctx->vol) {
+                       /* In full mode, ignore errors on restart blocks */
+                               if (blk >= RSTBLKS) {
+                                       done = TRUE;
+                                       err = 1;
+                               }
+                       } else {
+                               done = TRUE;
+                               err = 1;
+                       }
+               }
+               blk = nextblk;
+               nextblk++;
+               if (optr) { /* Only selected range */
+                       if ((nextblk == BASEBLKS) && (nextblk < firstblk))
+                                nextblk = firstblk;
+                       if ((blk >= BASEBLKS) && (blk > lastblk))
+                               done = TRUE;
+               } else
+                       if (optf) { /* Full log, forward */
+                               if (blk*blocksz >= logfilesz)
+                                       done = TRUE;
+                       } else
+                               if (optb || optp || optu || opts) {
+                                       /* Restart blocks only (2 blocks) */
+                                       if (blk >= RSTBLKS)
+                                               done = TRUE;
+                               } else { /* Base blocks only (4 blocks) */
+                                       if (blk >= BASEBLKS)
+                                               done = TRUE;
+                               }
+               if (!done) {
+                       buf = nextbuf;
+                       if (optv)
+                               printf("\n* block %d at 0x%llx\n",(int)blk,
+                                       (long long)loclogblk(ctx, blk));
+               }
+       }
+       if (optv && opts && !dirty)
+               printf("* Volume is clean, nothing to do\n");
+       if (optb || optp || optu
+           || (opts && dirty)) {
+               playedactions = 0;
+               ctx->firstaction = (struct ACTION_RECORD*)NULL;
+               ctx->lastaction = (struct ACTION_RECORD*)NULL;
+               buf = nextbuf;
+               nextbuf = read_buffer(ctx, blk+1);
+               startbuf = best_start(buf,nextbuf);
+               if (startbuf) {
+                       if (startbuf == nextbuf) {
+                               /* nextbuf is better, show blk */
+                               if (optv && buf) {
+                                       printf("* Ignored block %d at 0x%llx\n",
+                                               (int)blk,
+                                               (long long)loclogblk(ctx, blk));
+                                       if (optv >= 2)
+                                               hexdump(buf->block.data,
+                                                               blocksz);
+                                       showheadrcrd(blk, &buf->block.record);
+                               }
+                               blk++;
+                               buf = nextbuf;
+                       } else {
+                               /* buf is better, show blk + 1 */
+                               if (optv && nextbuf) {
+                                       printf("* Ignored block %d at 0x%llx\n",
+                                               (int)(blk + 1),
+                                               (long long)loclogblk(ctx,
+                                                               blk + 1));
+                                       if (optv >= 2)
+                                               hexdump(nextbuf->block.data,
+                                                               blocksz);
+                                       showheadrcrd(blk + 1,
+                                                       &nextbuf->block.record);
+                               }
+                       }
+                       /* The latest buf may be more recent than restart */
+                       rph = &buf->block.record;
+                       if ((s64)(le64_to_cpu(rph->last_end_lsn)
+                                       - committed_lsn) > 0) {
+                               committed_lsn = le64_to_cpu(rph->last_end_lsn);
+                               if (optv)
+                                       printf("* Restart page was obsolete\n");
+                       }
+                       nextbuf = (const struct BUFFER*)NULL;
+                       prevbuf = findprevious(ctx, buf);
+                       if (prevbuf) {
+                               prevblk = prevbuf->num;
+                               magic = prevbuf->block.record.head.magic;
+                               switch (magic) {
+                               case magic_RCRD :
+                                       break;
+                               case magic_CHKD :
+                                       printf("** Unexpected block type CHKD\n");
+                                       err = 1;
+                                       break;
+                               case magic_RSTR :
+                                       err = 1;
+                                       printf("** Unexpected block type RSTR\n");
+                                       break;
+                               default :
+                                       err = 1;
+                                       printf("** Invalid block\n");
+                                       break;
+                               }
+                       } else
+                               prevblk = BASEBLKS;
+                       if (!err)
+                               err = walkback(ctx, buf, blk,
+                                                       prevbuf, prevblk); 
+               } else {
+                       fprintf(stderr,"** No valid start block, aborting\n");
+                       err = 1;
+               }
+       }
+       return (err);
+}
+
+BOOL exception(int num)
+{
+       int i;
+
+       i = 0;
+       while ((i < 10) && optx[i] && (optx[i] != num))
+               i++;
+       return (optx[i] == num);
+}
+
+static void version(void)
+{
+       printf("\n%s v%s (libntfs-3g) - Recover updates committed by Windows"
+                       " on an NTFS Volume.\n\n", "ntfsrecover", VERSION);
+       printf("Copyright (c) 2012-2015 Jean-Pierre Andre\n");
+       printf("\n%s\n%s%s\n", ntfs_gpl, ntfs_bugs, ntfs_home);
+}
+
+static void usage(void)
+{
+       fprintf(stderr,"Usage : for recovering the updates committed by Windows :\n");
+       fprintf(stderr,"        ntfsrecover partition\n");
+       fprintf(stderr,"                (e.g. ntfsrecover /dev/sda1)\n"); 
+       fprintf(stderr,"Advanced : ntfsrecover [-b] [-c first-last] [-i] [-f] [-n] [-p count]\n");
+       fprintf(stderr,"                    [-r first-last] [-t] [-u count] [-v] partition\n");
+       fprintf(stderr,"           -b : show the full log backward\n");
+       fprintf(stderr,"           -c : restrict to the actions related to cluster range\n");
+       fprintf(stderr,"           -i : show invalid (stale) records\n");
+       fprintf(stderr,"           -f : show the full log forward\n");
+       fprintf(stderr,"           -h : show this help information\n");
+       fprintf(stderr,"           -n : do not apply any modification\n");
+       fprintf(stderr,"           -p : undo the latest count transaction sets and play one\n");
+       fprintf(stderr,"           -r : show a range of log blocks forward\n");
+       fprintf(stderr,"           -s : sync the committed changes (default)\n");
+       fprintf(stderr,"           -t : show transactions\n");
+       fprintf(stderr,"           -u : undo the latest count transaction sets\n");
+       fprintf(stderr,"           -v : show more information (-vv yet more)\n");
+       fprintf(stderr,"           -V : show version and exit\n");
+       fprintf(stderr,"     Copyright (c) 2012-2015 Jean-Pierre Andre\n");
+}
+
+/*
+ *             Process command options
+ */
+
+static BOOL getoptions(int argc, char *argv[])
+{
+       int c;
+       int xcount;
+       u32 xval;
+       char *endptr;
+       BOOL err;
+       static const char *sopt = "-bc:hifnp:r:stu:vVx:";
+       static const struct option lopt[] = {
+               { "backward",           no_argument,            NULL, 'b' },
+               { "clusters",           required_argument,      NULL, 'c' },
+               { "forward",            no_argument,            NULL, 'f' },
+               { "help",               no_argument,            NULL, 'h' },
+               { "no-action",          no_argument,            NULL, 'n' },
+               { "play",               required_argument,      NULL, 'p' },
+               { "range",              required_argument,      NULL, 'r' },
+               { "sync",               no_argument,            NULL, 's' },
+               { "transactions",       no_argument,            NULL, 't' },
+               { "undo",               required_argument,      NULL, 'u' },
+               { "verbose",            no_argument,            NULL, 'v' },
+               { "version",            no_argument,            NULL, 'V' },
+               { "exceptions",         required_argument,      NULL, 'x' },
+               { NULL,                 0, NULL, 0 }
+       };
+
+       err = FALSE;
+       optb = FALSE;
+       optc = FALSE;
+       optd = FALSE;
+       optf = FALSE;
+       opth = FALSE;
+       opti = FALSE;
+       optn = FALSE;
+       optp = FALSE;
+       optr = FALSE;
+       opts = 0;
+       optt = FALSE;
+       optu = FALSE;
+       optv = 0;
+       optV = FALSE;
+       optx[0] = 0;
+
+       while ((c = getopt_long(argc, argv, sopt, lopt, NULL)) != -1) {
+               switch (c) {
+               case 1: /* A non-option argument */
+                       if (optind == argc)
+                               optd = TRUE;
+                       else {
+                               fprintf(stderr, "Device must be the"
+                                               " last argument.\n");
+                               err = TRUE;
+                       }
+                       break;
+               case 'b':
+                       optb = TRUE;
+                       break;
+               case 'c':
+                       firstlcn = strtoull(optarg, &endptr, 0);
+                       lastlcn = firstlcn;
+                       if (*endptr == '-')
+                               lastlcn = strtoull(++endptr, &endptr, 0);
+                       if (*endptr || (lastlcn < firstlcn)) {
+                               fprintf(stderr,"Bad cluster range\n");
+                               err = TRUE;
+                       } else
+                               optc = TRUE;
+                       break;
+               case 'f':
+                       optf = TRUE;
+                       break;
+               case '?':
+               case 'h':
+                       opth = TRUE;
+                       break;
+               case 'n':
+                       optn = TRUE;
+                       break;
+               case 'p':
+                       playcount = strtoull(optarg, &endptr, 0);
+                       if (*endptr) {
+                               fprintf(stderr,"Bad play count\n");
+                               err = TRUE;
+                       } else
+                               optp = TRUE;
+                       break;
+               case 'r' :
+                       firstblk = strtoull(optarg, &endptr, 0);
+                       lastblk = firstblk;
+                       if (*endptr == '-')
+                               lastblk = strtoull(++endptr, &endptr, 0);
+                       if (*endptr || (lastblk < firstblk)) {
+                               fprintf(stderr,"Bad log block range\n");
+                               err = TRUE;
+                       } else
+                               optr = TRUE;
+                       break;
+               case 's':
+                       opts++;
+                       break;
+               case 't':
+                       optt = TRUE;
+                       break;
+               case 'u':
+                       playcount = strtoull(optarg, &endptr, 0);
+                       if (*endptr) {
+                               fprintf(stderr,"Bad undo count\n");
+                               err = TRUE;
+                       } else
+                               optu = TRUE;
+                       break;
+               case 'v':
+                       optv++;
+                       break;
+               case 'V':
+                       optV = TRUE;
+                       break;
+               case 'x':
+                               /*
+                                * Undocumented : actions to execute, though
+                                * they should be skipped under normal rules.
+                                */
+                       xcount = 0;
+                       xval = strtoull(optarg, &endptr, 0);
+                       while ((*endptr == ',')
+                           && (xcount < (MAXEXCEPTION - 1))) {
+                               optx[xcount++] = xval;
+                               xval = strtoull(++endptr, &endptr, 0);
+                       }
+                       if (*endptr || (xcount >= MAXEXCEPTION)) {
+                               fprintf(stderr,"Bad exception list\n");
+                               err = TRUE;
+                       } else {
+                               optx[xcount++] = xval;
+                               optx[xcount] = 0;
+                       }
+                       break;
+               default:
+                       fprintf(stderr,"Unknown option '%s'.\n",
+                                                       argv[optind - 1]);
+                       err = TRUE;
+               }
+       }
+
+       if (!optd && !optV && !opth) {
+               fprintf(stderr,"Device argument is missing\n");
+               err = TRUE;
+       }
+       if (!(optb || optf || optp || optr || opts || optt || optu || optV))
+               opts = 1;
+       if (optb && (optf || optr || opts)) {
+               fprintf(stderr,"Options -f, -r and -s are incompatible with -b\n");
+               err = TRUE;
+       }
+       if (optf && (optp || opts || optu)) {
+               fprintf(stderr,"Options -p, -s and -u are incompatible with -f\n");
+               err = TRUE;
+       }
+       if (optp && (optr || opts || optt || optu)) {
+               fprintf(stderr,"Options -r, -s, -t and -u are incompatible with -p\n");
+               err = TRUE;
+       }
+       if (optr && (opts || optu)) {
+               fprintf(stderr,"Options -s and -u are incompatible with -r\n");
+               err = TRUE;
+       }
+       if (opts && (optt || optu)) {
+               fprintf(stderr,"Options -t and -u are incompatible with -s\n");
+               err = TRUE;
+       }
+
+       if (opth || err)
+               usage();
+       else
+               if (optV)
+                       version();
+       return (!err);
+}
+
+/*
+ *             Quick checks on the layout of needed structs
+ */
+
+static BOOL checkstructs(void)
+{
+       BOOL ok;
+
+       ok = TRUE;
+       if (sizeof(struct RECORD_PAGE_HEADER) != 40) {
+               fprintf(stderr,
+                       "* error : bad sizeof(struct RECORD_PAGE_HEADER) %d\n",
+                       (int)sizeof(struct RECORD_PAGE_HEADER));
+               ok = FALSE;
+       }
+       if (sizeof(struct LOG_RECORD) != 88) {
+               fprintf(stderr,
+                       "* error : bad sizeof(struct LOG_RECORD) %d\n",
+                       (int)sizeof(struct LOG_RECORD));
+               ok = FALSE;
+       }
+       if (sizeof(struct RESTART_PAGE_HEADER) != 32) {
+               fprintf(stderr,
+                       "* error : bad sizeof(struct RESTART_PAGE_HEADER) %d\n",
+                       (int)sizeof(struct RESTART_PAGE_HEADER));
+               ok = FALSE;
+       }
+       if (sizeof(struct RESTART_AREA) != 44) {
+               fprintf(stderr,
+                       "* error : bad sizeof(struct RESTART_AREA) %d\n",
+                       (int)sizeof(struct RESTART_AREA));
+               ok = FALSE;
+       }
+       if (sizeof(struct ATTR_OLD) != 44) {
+               fprintf(stderr,
+                       "* error : bad sizeof(struct ATTR_OLD) %d\n",
+                       (int)sizeof(struct ATTR_OLD));
+               ok = FALSE;
+       }
+       if (sizeof(struct ATTR_NEW) != 40) {
+               fprintf(stderr,
+                       "* error : bad sizeof(struct ATTR_NEW) %d\n",
+                       (int)sizeof(struct ATTR_NEW));
+               ok = FALSE;
+       }
+       if (LastAction != 38) {
+               fprintf(stderr,
+                       "* error : bad action list, %d actions\n",
+                       (int)LastAction);
+               ok = FALSE;
+       }
+       return (ok);
+}
+
+int main(int argc, char *argv[])
+{
+       CONTEXT ctx;
+       unsigned int i;
+       int err;
+
+       err = 1;
+       if (checkstructs()
+           && getoptions(argc,argv)) {
+               if (optV || opth) {
+                       err = 0;
+               } else {
+                       redocount = 0;
+                       undocount = 0;
+                       actionnum = 0;
+                       attrcount = 0;
+                       redos_met = 0;
+                       attrtable = (struct ATTR**)NULL;
+                       for (i=0; i<(BUFFERCNT + BASEBLKS); i++)
+                               buffer_table[i] = (struct BUFFER*)NULL;
+                       ntfs_log_set_handler(ntfs_log_handler_outerr);
+                       if (open_volume(&ctx, argv[argc - 1])) {
+                               if (!ctx.vol
+                                   && (opts || optp || optu)) {
+                                       printf("Options -s, -p and -u"
+                                               " require a full device\n");
+                                       err = 1;
+                               } else {
+                                       err = walk(&ctx);
+                                       if (ctx.vol) {
+                                               if ((optp || optu || opts)
+                                                   && !err
+                                                   && !optn) {
+                                                       reset_logfile(&ctx);
+                                               }
+                                               ntfs_attr_close(log_na);
+                                               ntfs_inode_close(log_ni);
+                                               ntfs_umount(ctx.vol, TRUE);
+                                       } else
+                                               fclose(ctx.file);
+                               }
+                       } else
+                               fprintf(stderr,"Could not open %s\n",
+                                                       argv[argc - 1]);
+                       for (i=0; i<(BUFFERCNT + BASEBLKS); i++)
+                               free(buffer_table[i]);
+                       for (i=0; i<attrcount; i++)
+                               free(attrtable[i]);
+                       free(attrtable);
+                       if (ctx.vol) {
+                               freeclusterentry((struct STORE*)NULL);
+                               show_redos();
+                       }
+               }
+       }
+       if (err)
+               exit(1);
+       return (0);
+}
diff --git a/ntfsprogs/ntfsrecover.h b/ntfsprogs/ntfsrecover.h
new file mode 100644 (file)
index 0000000..ed8d9d9
--- /dev/null
@@ -0,0 +1,339 @@
+/*
+ *             Declarations for processing log data
+ *
+ * Copyright (c) 2000-2005 Anton Altaparmakov
+ * Copyright (c) 2014-2015 Jean-Pierre Andre
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program (in the main directory of the NTFS-3G
+ * distribution in the file COPYING); if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*
+ * TODO
+ *     This file partially duplicates logfile.h (with modifications).
+ *     The generic declarations are to be moved to logfile.h, thus
+ *     implying adapting (at least) libntfs-3g/logfile.c and
+ *     ntfsprogs/ntfsdump_logfile.c, and the declarations specific to
+ *     ntfsrecover should be kept in this file.
+ *     (removing ntfsdump_logfile.c might also be considered).
+ */
+
+#define getle16(p,x) le16_to_cpu(*(const le16*)((const char*)(p) + (x)))
+#define getle32(p,x) le32_to_cpu(*(const le32*)((const char*)(p) + (x)))
+#define getle64(p,x) le64_to_cpu(*(const le64*)((const char*)(p) + (x)))
+
+#define feedle16(p,x) (*(const le16*)((const char*)(p) + (x)))
+#define feedle32(p,x) (*(const le32*)((const char*)(p) + (x)))
+#define feedle64(p,x) (*(const le64*)((const char*)(p) + (x)))
+
+enum LOG_RECORD_TYPE {
+       LOG_STANDARD = 1,
+       LOG_CHECKPOINT = 2
+} ;
+
+       /* These flags were introduced in Vista in field attribute_flags */
+enum ATTRIBUTE_FLAGS {
+       ACTS_ON_MFT = 2,
+       ACTS_ON_INDX = 8
+} ;
+
+enum ACTIONS {
+       Noop,                                   /* 0 */
+       CompensationlogRecord,                  /* 1 */
+       InitializeFileRecordSegment,            /* 2 */
+       DeallocateFileRecordSegment,            /* 3 */
+       WriteEndofFileRecordSegment,            /* 4 */
+       CreateAttribute,                        /* 5 */
+       DeleteAttribute,                        /* 6 */
+       UpdateResidentValue,                    /* 7 */
+       UpdateNonResidentValue,                 /* 8 */
+       UpdateMappingPairs,                     /* 9 */
+       DeleteDirtyClusters,                    /* 10 */
+       SetNewAttributeSizes,                   /* 11 */
+       AddIndexEntryRoot,                      /* 12 */
+       DeleteIndexEntryRoot,                   /* 13 */
+       AddIndexEntryAllocation,                /* 14 */
+       DeleteIndexEntryAllocation,             /* 15 */
+       WriteEndOfIndexBuffer,                  /* 16 */
+       SetIndexEntryVcnRoot,                   /* 17 */
+       SetIndexEntryVcnAllocation,             /* 18 */
+       UpdateFileNameRoot,                     /* 19 */
+       UpdateFileNameAllocation,               /* 20 */
+       SetBitsInNonResidentBitMap,             /* 21 */
+       ClearBitsInNonResidentBitMap,           /* 22 */
+       HotFix,                                 /* 23 */
+       EndTopLevelAction,                      /* 24 */
+       PrepareTransaction,                     /* 25 */
+       CommitTransaction,                      /* 26 */
+       ForgetTransaction,                      /* 27 */
+       OpenNonResidentAttribute,               /* 28 */
+       OpenAttributeTableDump,                 /* 29 */
+       AttributeNamesDump,                     /* 30 */
+       DirtyPageTableDump,                     /* 31 */
+       TransactionTableDump,                   /* 32 */
+       UpdateRecordDataRoot,                   /* 33 */
+       UpdateRecordDataAllocation,             /* 34 */
+       Win10Action35,                          /* 35 */
+       Win10Action36,                          /* 36 */
+       Win10Action37,                          /* 37 */
+       LastAction                              /* 38 */
+} ;
+
+       /* Flags for field log_record_flags, their meaning is unclear */
+enum RECORD_FLAGS {
+       RECORD_UNKNOWN = 1,
+       /* The flags below were introduced in Windows 10 */
+       RECORD_DELETING = 2,
+       RECORD_ADDING = 4
+} ;
+typedef le16 LOG_RECORD_FLAGS;
+
+#define LOGFILE_NO_CLIENT const_cpu_to_le16(0xffff)
+#define RESTART_VOLUME_IS_CLEAN const_cpu_to_le16(0x0002)
+
+/* ntfsdoc p 39 (47), not in layout.h */
+
+typedef struct RESTART_PAGE_HEADER { /* size 32 */
+       NTFS_RECORD head;
+       le64 chkdsk_lsn;
+       le32 system_page_size;
+       le32 log_page_size;
+       le16 restart_offset;
+       le16 minor_ver;
+       le16 major_ver;
+       le16 usn;
+} __attribute__((__packed__)) RESTART_PAGE_HEADER;
+
+/* ntfsdoc p 40 (48), not in layout.h */
+
+struct RESTART_AREA { /* size 44 */
+       le64 current_lsn;
+       le16 log_clients;
+       le16 client_free_list;
+       le16 client_in_use_list;
+       le16 flags;
+       le32 seq_number_bits;
+       le16 restart_area_length;
+       le16 client_array_offset;
+       le64 file_size;
+       le32 last_lsn_data_length;
+       le16 record_length;
+       le16 log_page_data_offset;
+       le32 restart_log_open_count;
+} __attribute__((__packed__)) ;
+
+typedef struct RESTART_CLIENT { /* size 160 */
+/*Ofs*/
+/*  0*/        le64 oldest_lsn;        /* Oldest LSN needed by this client.  On create
+                                  set to 0. */
+/*  8*/        le64 client_restart_lsn;/* LSN at which this client needs to restart
+                                  the volume, i.e. the current position within
+                                  the log file.  At present, if clean this
+                                  should = current_lsn in restart area but it
+                                  probably also = current_lsn when dirty most
+                                  of the time.  At create set to 0. */
+/* 16*/        le16 prev_client;       /* The offset to the previous log client record
+                                  in the array of log client records.
+                                  LOGFILE_NO_CLIENT means there is no previous
+                                  client record, i.e. this is the first one.
+                                  This is always LOGFILE_NO_CLIENT. */
+/* 18*/        le16 next_client;       /* The offset to the next log client record in
+                                  the array of log client records.
+                                  LOGFILE_NO_CLIENT means there are no next
+                                  client records, i.e. this is the last one.
+                                  This is always LOGFILE_NO_CLIENT. */
+/* 20*/        le16 seq_number;        /* On Win2k and presumably earlier, this is set
+                                  to zero every time the logfile is restarted
+                                  and it is incremented when the logfile is
+                                  closed at dismount time.  Thus it is 0 when
+                                  dirty and 1 when clean.  On WinXP and
+                                  presumably later, this is always 0. */
+/* 22*/        u8 reserved[6];         /* Reserved/alignment. */
+/* 28*/        le32 client_name_length;/* Length of client name in bytes.  Should
+                                  always be 8. */
+/* 32*/        le16 client_name[64];   /* Name of the client in Unicode.  Should
+                                  always be "NTFS" with the remaining bytes
+                                  set to 0. */
+/* sizeof() = 160 (0xa0) bytes */
+} __attribute__((__packed__)) LOG_CLIENT_RECORD;
+
+/* ntfsdoc p 41 (49), not in layout.h */
+
+struct RECORD_PAGE_HEADER { /* size 40 */
+       NTFS_RECORD head;       /* the magic is "RCRD" */
+       union {
+               le64 last_lsn;
+               le32 file_offset;
+       } __attribute__((__packed__)) copy;
+       le32 flags;
+       le16 page_count;
+       le16 page_position;
+       le16 next_record_offset;
+       le16 reserved4[3];
+       le64 last_end_lsn;
+} __attribute__((__packed__)) ;
+
+/* ntfsdoc p 42 (50), not in layout.h */
+
+#define LOG_RECORD_HEAD_SZ 0x30 /* size of header of struct LOG_RECORD */
+
+typedef struct LOG_RECORD { /* size 80 */
+       le64 this_lsn;
+       le64 client_previous_lsn;
+       le64 client_undo_next_lsn;
+       le32 client_data_length;
+       struct {
+               le16 seq_number;
+               le16 client_index;
+       } __attribute__((__packed__)) client_id;
+       le32 record_type;
+       le32 transaction_id;
+       LOG_RECORD_FLAGS log_record_flags;
+       le16 reserved1[3];
+       le16 redo_operation;
+       le16 undo_operation;
+       le16 redo_offset;
+       le16 redo_length;
+       union {
+               struct {
+                       le16 undo_offset;
+                       le16 undo_length;
+                       le16 target_attribute;
+                       le16 lcns_to_follow;
+                       le16 record_offset;
+                       le16 attribute_offset;
+                       le16 cluster_index;
+                       le16 attribute_flags;
+                       le32 target_vcn;
+                       le32 reserved3;
+                       le64 lcn_list[0];
+               } __attribute__((__packed__));
+               struct {
+                       le64 transaction_lsn;
+                       le64 attributes_lsn;
+                       le64 names_lsn;
+                       le64 dirty_pages_lsn;
+                       le64 unknown_list[0];
+               } __attribute__((__packed__));
+       } __attribute__((__packed__));
+} __attribute__((__packed__)) LOG_RECORD;
+
+struct BUFFER {
+       unsigned int num;
+       unsigned int size;
+       unsigned int headsz;
+       BOOL safe;
+       union {
+               struct RESTART_PAGE_HEADER restart;
+               struct RECORD_PAGE_HEADER record;
+               char data[1];
+       } block;  /* variable length, keep at the end */
+} ;
+
+struct ACTION_RECORD {
+       struct ACTION_RECORD *next;
+       struct ACTION_RECORD *prev;
+       int num;
+       unsigned int flags;
+       struct LOG_RECORD record; /* variable length, keep at the end */
+} ;
+
+enum {         /* Flag values for ACTION_RECORD */
+       ACTION_TO_REDO = 1      /* Committed, possibly not synced */
+       } ;
+
+struct ATTR {
+       u64 inode;
+       u64 lsn;
+       le32 type;
+       u16 key;
+       u16 namelen;
+       le16 name[1];
+} ;
+
+struct BITMAP_ACTION {
+       le32 firstbit;
+       le32 count;
+} ;
+
+/* Danger in arrays : contains le64's though size is not a multiple of 8 */
+typedef struct ATTR_OLD {      /* Format up to Win10 (44 bytes) */
+       le64 unknown1;
+       le64 unknown2;
+       le64 inode;
+       le64 lsn;
+       le32 unknown3;
+       le32 type;
+       le32 unknown4;
+} __attribute__((__packed__)) ATTR_OLD;
+
+typedef struct ATTR_NEW {      /* Format since Win10 (40 bytes) */
+       le64 unknown1;
+       le64 unknown2;
+       le32 type;
+       le32 unknown3;
+       le64 inode;
+       le64 lsn;
+} __attribute__((__packed__)) ATTR_NEW;
+
+extern u32 clustersz;
+extern int clusterbits;
+extern u32 blocksz;
+extern int blockbits;
+extern u16 bytespersect;
+extern u64 mftlcn;
+extern u32 mftrecsz;
+extern int mftrecbits;
+extern u32 mftcnt; /* number of entries */
+extern BOOL optc;
+extern BOOL optn;
+extern int opts;
+extern int optv;
+extern unsigned int redocount;
+extern unsigned int undocount;
+extern ntfs_inode *log_ni;
+extern ntfs_attr *log_na;
+extern u64 logfilelcn;
+extern u32 logfilesz; /* bytes */
+extern u64 redos_met;
+extern u64 committed_lsn;
+extern u64 synced_lsn;
+extern u64 latest_lsn;
+extern u64 restart_lsn;
+
+extern struct RESTART_AREA restart;
+extern struct RESTART_CLIENT client;
+
+const char *actionname(int op);
+const char *mftattrname(ATTR_TYPES attr);
+void showname(const char *prefix, const char *name, int cnt);
+int fixnamelen(const char *name, int len);
+BOOL within_lcn_range(const struct LOG_RECORD *logr);
+struct ATTR *getattrentry(unsigned int key, unsigned int lth);
+void copy_attribute(struct ATTR *pa, const char *buf, int length);
+u32 get_undo_offset(const struct LOG_RECORD *logr);
+u32 get_redo_offset(const struct LOG_RECORD *logr);
+u32 get_extra_offset(const struct LOG_RECORD *logr);
+BOOL exception(int num);
+
+struct STORE;
+BOOL ntfs_check_logfile(ntfs_attr *log_na, RESTART_PAGE_HEADER **rp);
+extern int play_undos(ntfs_volume *vol, const struct ACTION_RECORD *firstundo);
+extern int play_redos(ntfs_volume *vol, const struct ACTION_RECORD *firstredo);
+extern void show_redos(void);
+extern void freeclusterentry(struct STORE*);
+void hexdump(const char *buf, unsigned int lth);
diff --git a/ntfsprogs/playlog.c b/ntfsprogs/playlog.c
new file mode 100644 (file)
index 0000000..46346fa
--- /dev/null
@@ -0,0 +1,4826 @@
+/*
+ *             Redo or undo a list of logged actions
+ *
+ * Copyright (c) 2014-2015 Jean-Pierre Andre
+ *
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program (in the main directory of the NTFS-3G
+ * distribution in the file COPYING); if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "config.h"
+
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#ifdef HAVE_STDIO_H
+#include <stdio.h>
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+#ifdef HAVE_MALLOC_H
+#include <malloc.h>
+#endif
+#ifdef HAVE_TIME_H
+#include <time.h>
+#endif
+
+#include "types.h"
+#include "endians.h"
+#include "support.h"
+#include "layout.h"
+#include "param.h"
+#include "ntfstime.h"
+#include "device_io.h"
+#include "device.h"
+#include "logging.h"
+#include "runlist.h"
+#include "mft.h"
+#include "inode.h"
+#include "attrib.h"
+#include "bitmap.h"
+#include "index.h"
+#include "volume.h"
+#include "unistr.h"
+#include "mst.h"
+#include "ntfsrecover.h"
+#include "misc.h"
+
+struct STORE {
+       struct STORE *upper;
+       struct STORE *lower;
+       LCN lcn;
+       char data[1];
+} ;
+
+#define dump hexdump
+
+struct STORE *cluster_door = (struct STORE*)NULL;
+
+/* check whether a MFT or INDX record is older than action */
+#define older_record(rec, logr) ((s64)(le64_to_cpu((rec)->lsn) \
+                       - le64_to_cpu((logr)->this_lsn)) < 0)
+/* check whether a MFT or INDX record is newer than action */
+#define newer_record(rec, logr) ((s64)(le64_to_cpu((rec)->lsn) \
+                       - le64_to_cpu((logr)->this_lsn)) > 0)
+
+/*
+ *             A few functions for debugging
+ */
+
+static int matchcount(const char *d, const char *s, int n)
+{
+       int m;
+
+       m = 0;
+       while ((--n >= 0) && (*d++ == *s++)) m++;
+       return (m);
+}
+
+/*
+static void locate(const char *s, int n, const char *p, int m)
+{
+       int i,j;
+
+       for (i=0; i<=(n - m); i++)
+               if (s[i] == *p) {
+                       j = 1;
+                       while ((j < m) && (s[i + j] == p[j]))
+                               j++;
+                       if (j == m)
+                               printf("=== found at offset 0x%x %d\n",i,i);
+               }
+}
+*/
+
+static u64 inode_number(const struct LOG_RECORD *logr)
+{
+       u64 offset;
+
+       offset = ((u64)le32_to_cpu(logr->target_vcn)
+                                       << clusterbits)
+               + ((u32)le16_to_cpu(logr->cluster_index)
+                                       << NTFS_BLOCK_SIZE_BITS);
+       return (offset >> mftrecbits);
+}
+
+/*
+ *             Find an in-memory copy of a needed cluster
+ *
+ *     Optionally, allocate a copy.
+ */
+
+static struct STORE *getclusterentry(LCN lcn, BOOL create)
+{
+       struct STORE **current;
+       struct STORE *newone;
+
+       current = &cluster_door;
+               /* A minimal binary tree should be enough */
+       while (*current && (lcn != (*current)->lcn)) {
+               if (lcn > (*current)->lcn)
+                       current = &(*current)->upper;
+               else
+                       current = &(*current)->lower;
+       }
+       if (create && !*current) {
+               newone = (struct STORE*)malloc(sizeof(struct STORE)
+                                               + clustersz);
+               if (newone) {
+                       newone->upper = (struct STORE*)NULL;
+                       newone->lower = (struct STORE*)NULL;
+                       newone->lcn = lcn;
+                       *current = newone;
+               }
+       }
+       return (*current);
+}
+
+void freeclusterentry(struct STORE *entry)
+{
+       if (!entry) {
+               if (cluster_door)
+                       freeclusterentry(cluster_door);
+               cluster_door = (struct STORE*)NULL;
+       } else {
+               if (optv)
+                       printf("* cluster 0x%llx %s updated\n",
+                                       (long long)entry->lcn,
+                                       (optn ? "would be" : "was"));
+               if (entry->upper)
+                       freeclusterentry(entry->upper);
+               if (entry->lower)
+                       freeclusterentry(entry->lower);
+               free(entry);
+       }
+}
+
+/*
+ *             Check whether an attribute type is a valid one
+ */
+
+static BOOL valid_type(ATTR_TYPES type)
+{
+       BOOL ok;
+
+       switch (type) {
+       case AT_STANDARD_INFORMATION :
+       case AT_ATTRIBUTE_LIST :
+       case AT_FILE_NAME :
+       case AT_OBJECT_ID :
+       case AT_SECURITY_DESCRIPTOR :
+       case AT_VOLUME_NAME :
+       case AT_VOLUME_INFORMATION :
+       case AT_DATA :
+       case AT_INDEX_ROOT :
+       case AT_INDEX_ALLOCATION :
+       case AT_BITMAP :
+       case AT_REPARSE_POINT :
+       case AT_EA_INFORMATION :
+       case AT_EA :
+       case AT_PROPERTY_SET :
+       case AT_LOGGED_UTILITY_STREAM :
+       case AT_FIRST_USER_DEFINED_ATTRIBUTE :
+       case AT_END :
+               ok = TRUE;
+               break;
+       default :
+               ok = FALSE;
+               break;
+       }
+       return (ok);
+}
+
+/*
+ *             Rough check of sanity of an index list
+ */
+
+static int sanity_indx_list(const char *buffer, u32 k, u32 end)
+{
+       le64 inode;
+       int err;
+       int lth;
+       BOOL done;
+
+       err = 0;
+       done = FALSE;
+       while ((k <= end) && !done) {
+               lth = getle16(buffer,k+8);
+               if (optv > 1)
+                       /* Usual indexes can be determined from size */
+                       switch (lth) {
+                       case 16 : /* final without subnode */
+                       case 24 : /* final with subnode */
+                               printf("index to none lth 0x%x"
+                                       " flags 0x%x pos 0x%x\n",
+                                       (int)lth,
+                                       (int)getle16(buffer,k+12),(int)k);
+                               break;
+                       case 32 : /* $R in $Reparse */
+                                       /* Badly aligned */
+                               memcpy(&inode, &buffer[k + 20], 8);
+                               printf("index to reparse of 0x%016llx lth 0x%x"
+                                       " flags 0x%x pos 0x%x\n",
+                                       (long long)le64_to_cpu(inode),
+                                       (int)lth,
+                                       (int)getle16(buffer,k+12),(int)k);
+                               break;
+                       case 40 : /* $SII in $Secure */
+                               printf("index to securid 0x%lx lth 0x%x"
+                                       " flags 0x%x pos 0x%x\n",
+                                       (long)getle32(buffer,k + 16),
+                                       (int)lth,
+                                       (int)getle16(buffer,k+12),(int)k);
+                               break;
+                       case 48 : /* $SDH in $Secure */
+                               printf("index to securid 0x%lx lth 0x%x"
+                                       " flags 0x%x pos 0x%x\n",
+                                       (long)getle32(buffer,k + 20),
+                                       (int)lth,
+                                       (int)getle16(buffer,k+12),(int)k);
+                               break;
+                       default : /* at least 80 */
+                               printf("index to inode 0x%016llx lth 0x%x"
+                                       " flags 0x%x pos 0x%x\n",
+                                       (long long)getle64(buffer,k),
+                                       (int)lth,
+                                       (int)getle16(buffer,k+12),(int)k);
+                       }
+               done = (feedle16(buffer,k+12) & INDEX_ENTRY_END) || !lth;
+               k += lth;
+       }
+       if (k != end) {
+               printf("** Bad index record length %ld (computed %ld)\n",
+                                       (long)end, (long)k);
+               err = 1;
+       }
+       if (!done) {
+               printf("** Missing end of index mark\n");
+               err = 1;
+       }
+       return (err);
+}
+
+/*
+ *             Rough check of sanity of an mft record
+ */
+
+static int sanity_mft(const char *buffer)
+{
+       const MFT_RECORD *record;
+       const ATTR_RECORD *attr;
+       u64 instances;
+       u32 k;
+       u32 type;
+       u32 prevtype;
+       u16 nextinstance;
+       u16 instance;
+       int err;
+
+       err = 0;
+       record = (const MFT_RECORD*)buffer;
+       nextinstance = le16_to_cpu(record->next_attr_instance);
+       instances = 0;
+       k = le16_to_cpu(record->attrs_offset);
+       attr = (const ATTR_RECORD*)&buffer[k];
+       prevtype = 0;
+       while ((k < mftrecsz)
+           && (attr->type != AT_END)
+           && valid_type(attr->type)) {
+               type = le32_to_cpu(attr->type);
+               if (type < prevtype) {
+                       printf("** Bad type ordering 0x%lx after 0x%lx\n",
+                               (long)type, (long)prevtype);
+                       err = 1;
+               }
+               instance = le16_to_cpu(attr->instance);
+               /* Can nextinstance wrap around ? */
+               if (instance >= nextinstance) {
+                       printf("** Bad attr instance %d (max %d)\n",
+                                       (int)instance, (int)nextinstance - 1);
+                       err = 1;
+               }
+               if (instance < 64) {
+                       /* Only check up to 64 */
+                       if (((u64)1 << instance) & instances) {
+                               printf("** Duplicated attr instance %d\n",
+                                       (int)instance);
+                       }
+                       instances |= (u64)1 << instance;
+               }
+               if (optv > 1) {
+                       if ((attr->type == AT_FILE_NAME)
+                          && buffer[k + 88]) {
+                               printf("attr %08lx offs 0x%x nres %d",
+                                       (long)type, (int)k,
+                                       (int)attr->non_resident);
+                               showname(" ",&buffer[k+90],
+                                       buffer[k + 88] & 255);
+                       } else
+                               printf("attr %08lx offs 0x%x nres %d\n",
+                                       (long)type, (int)k,
+                                       (int)attr->non_resident);
+               }
+               if ((attr->type == AT_INDEX_ROOT)
+                   && sanity_indx_list(buffer,
+                               k + le16_to_cpu(attr->value_offset) + 32,
+                               k + le32_to_cpu(attr->length))) {
+                       err = 1;
+               }
+               k += le32_to_cpu(attr->length);
+               attr = (const ATTR_RECORD*)&buffer[k];
+               prevtype = type;
+       }
+       if ((optv > 1) && (attr->type == AT_END))
+               printf("attr %08lx offs 0x%x\n",
+                               (long)le32_to_cpu(attr->type), (int)k);
+       if ((attr->type != AT_END)
+           || (le32_to_cpu(record->bytes_in_use) != (k + 8))
+           || (le32_to_cpu(record->bytes_allocated) < (k + 8))) {
+               printf("** Bad MFT record length %ld"
+                               " (computed %ld allocated %ld)\n",
+                               (long)le32_to_cpu(record->bytes_in_use),
+                               (long)(k + 8),
+                               (long)le32_to_cpu(record->bytes_allocated));
+               err = 1;
+       }
+       return (err);
+}
+
+/*
+ *             Rough check of sanity of an index block
+ */
+
+static int sanity_indx(ntfs_volume *vol, const char *buffer)
+{
+       const INDEX_BLOCK *indx;
+       u32 k;
+       int err;
+
+       err = 0;
+       indx = (const INDEX_BLOCK*)buffer;
+       k = offsetof(INDEX_BLOCK, index) +
+               le32_to_cpu(indx->index.entries_offset);
+       err = sanity_indx_list(buffer, k,
+                               le32_to_cpu(indx->index.index_length) + 24);
+       if ((le32_to_cpu(indx->index.index_length)
+               > le32_to_cpu(indx->index.allocated_size))
+           || (le32_to_cpu(indx->index.allocated_size)
+               != (vol->indx_record_size - 24))) {
+               printf("** Bad index length %ld"
+                               " (usable %ld allocated %ld)\n",
+                               (long)le32_to_cpu(indx->index.index_length),
+                               (long)(vol->indx_record_size - 24),
+                               (long)le32_to_cpu(indx->index.allocated_size));
+               err = 1;
+       }
+       return (err);
+}
+
+
+/*
+ *             Allocate a buffer and read a full set of raw clusters
+ *
+ *     Do not use for accessing $LogFile.
+ *     With option -n reading is first attempted from the memory store
+ */
+
+static char *read_raw(ntfs_volume *vol, const struct LOG_RECORD *logr)
+{
+       char *buffer;
+       char *target;
+       struct STORE *store;
+       LCN lcn;
+       int count;
+       int i;
+       BOOL fail;
+
+       count = le16_to_cpu(logr->lcns_to_follow);
+       if (!count) {
+               printf("** Error : no lcn to read from\n");
+               buffer = (char*)NULL;
+       } else 
+               buffer = (char*)malloc(clustersz*count);
+// TODO error messages
+       if (buffer) {
+               fail = FALSE;
+               for (i=0; (i<count) && !fail; i++) {
+                       store = (struct STORE*)NULL;
+                       lcn = le64_to_cpu(logr->lcn_list[i]);
+                       target = buffer + clustersz*i;
+                       if (optn) {
+                               store = getclusterentry(lcn, FALSE);
+                               if (store) {
+                                       memcpy(target, store->data, clustersz);
+                               if (optv)
+                                       printf("== lcn 0x%llx from store\n",
+                                                       (long long)lcn);
+                               if ((optv > 1) && optc
+                                   && within_lcn_range(logr))
+                                       dump(store->data, clustersz);
+                               }
+                       }
+                       if (!store
+                          && (ntfs_pread(vol->dev, lcn << clusterbits,
+                                       clustersz, target) != clustersz)) {
+                               fail = TRUE;
+                       } else {
+                               if (!store) {
+                                       if (optv)
+                                               printf("== lcn 0x%llx"
+                                                       " from device\n",
+                                                       (long long)lcn);
+                                       if ((optv > 1) && optc
+                                           && within_lcn_range(logr))
+                                               dump(target, clustersz);
+                               }
+                       }
+               }
+               if (fail) {
+                       printf("** Could not read cluster 0x%llx\n",
+                                       (long long)lcn);
+                       free(buffer);
+                       buffer = (char*)NULL;
+               }
+       }
+       return (buffer);
+}
+
+/*
+ *             Write a full set of raw clusters
+ *
+ *     Do not use for accessing $LogFile.
+ *     With option -n a copy of the buffer is kept in memory for later use.
+ */
+
+static int write_raw(ntfs_volume *vol, const struct LOG_RECORD *logr,
+                                       char *buffer)
+{
+       int err;
+       struct STORE *store;
+       LCN lcn;
+       char *source;
+       int count;
+       int i;
+
+       err = 0;
+       count = le16_to_cpu(logr->lcns_to_follow);
+       if (!count)
+               printf("** Error : no lcn to write to\n");
+       if (optn) {
+               for (i=0; (i<count) && !err; i++) {
+                       lcn = le64_to_cpu(logr->lcn_list[i]);
+                       source = buffer + clustersz*i;
+                       store = getclusterentry(lcn, TRUE);
+                       if (store) {
+                               memcpy(store->data, source, clustersz);
+                               if (optv)
+                                       printf("== lcn 0x%llx to store\n",
+                                                       (long long)lcn);
+                               if ((optv > 1) && optc
+                                   && within_lcn_range(logr))
+                                       dump(store->data, clustersz);
+                       } else {
+                               printf("** Could not store cluster 0x%llx\n",
+                                       (long long)lcn);
+                               err = 1;
+                       }
+               }
+       } else {
+               for (i=0; (i<count) && !err; i++) {
+                       lcn = le64_to_cpu(logr->lcn_list[i]);
+                       if (optv)
+                               printf("== lcn 0x%llx to device\n",
+                                                       (long long)lcn);
+                       source = buffer + clustersz*i;
+                       if (ntfs_pwrite(vol->dev, lcn << clusterbits,
+                                       clustersz, source) != clustersz) {
+                               printf("** Could not write cluster 0x%llx\n",
+                                               (long long)lcn);
+                               err = 1;
+                       }
+               }
+       }
+       return (err);
+}
+
+/*
+ *             Write a full set of raw clusters to mft_mirr
+ */
+
+static int write_mirr(ntfs_volume *vol, const struct LOG_RECORD *logr,
+                                       char *buffer)
+{
+       int err;
+       LCN lcn;
+       char *source;
+       int count;
+       int i;
+
+       err = 0;
+       count = le16_to_cpu(logr->lcns_to_follow);
+       if (!count)
+               printf("** Error : no lcn to write to\n");
+       if (!optn) {
+               for (i=0; (i<count) && !err; i++) {
+                       lcn = ntfs_attr_vcn_to_lcn(vol->mftmirr_na,
+                               le32_to_cpu(logr->target_vcn) + i);
+                       source = buffer + clustersz*i;
+                       if ((lcn < 0)
+                           || (ntfs_pwrite(vol->dev, lcn << clusterbits,
+                                       clustersz, source) != clustersz)) {
+                               printf("** Could not write cluster 0x%llx\n",
+                                               (long long)lcn);
+                               err = 1;
+                       }
+               }
+       }
+       return (err);
+}
+
+/*
+ *             Allocate a buffer and read a single protected record
+ */
+
+static char *read_protected(ntfs_volume *vol, const struct LOG_RECORD *logr,
+                       u32 size, BOOL warn)
+{
+       char *buffer;
+       char *full;
+       u32 pos;
+       LCN lcn;
+
+               /* read full clusters */
+       buffer = read_raw(vol, logr);
+               /*
+                * if the record is smaller than a cluster,
+                * make a partial copy and free the full buffer
+                */
+       if (buffer && (size < clustersz)) {
+               full = buffer;
+               buffer = (char*)malloc(size);
+               if (buffer) {
+                       pos = le16_to_cpu(logr->cluster_index)
+                                       << NTFS_BLOCK_SIZE_BITS;
+                       memcpy(buffer, full + pos, size);
+               }
+               free(full);
+       }
+       if (buffer && (ntfs_mst_post_read_fixup_warn(
+                               (NTFS_RECORD*)buffer, size, FALSE) < 0)) {
+               if (warn) {
+                       lcn = le64_to_cpu(logr->lcn_list[0]);
+                       printf("** Invalid protected record at 0x%llx"
+                                       " index %d\n",
+                                       (long long)lcn,
+                                       (int)le16_to_cpu(logr->cluster_index));
+               }
+               free(buffer);
+               buffer = (char*)NULL;
+       }
+       return (buffer);
+}
+
+/*
+ *             Protect a single record, write, and deallocate the buffer
+ *
+ *     With option -n a copy of the buffer is kept in protected form in
+ *     memory for later use.
+ *     As the store only knows about clusters, if the record is smaller
+ *     than a cluster, have to read, merge and write.
+ */
+
+static int write_protected(ntfs_volume *vol, const struct LOG_RECORD *logr,
+                               char *buffer, u32 size)
+{
+       MFT_RECORD *record;
+       INDEX_BLOCK *indx;
+       char *full;
+       u32 pos;
+       BOOL mftmirr;
+       BOOL checked;
+       int err;
+
+       err = 0;
+       mftmirr = FALSE;
+       checked = FALSE;
+       if ((size == mftrecsz) && !memcmp(buffer,"FILE",4)) {
+               record = (MFT_RECORD*)buffer;
+               if (optv)
+                       printf("update inode %ld lsn 0x%llx"
+                               " (record %s than action 0x%llx)\n",
+                               (long)le32_to_cpu(record->mft_record_number),
+                               (long long)le64_to_cpu(record->lsn),
+                               ((s64)(le64_to_cpu(record->lsn)
+                                   - le64_to_cpu(logr->this_lsn)) < 0 ?
+                                       "older" : "newer"),
+                               (long long)le64_to_cpu(logr->this_lsn));
+               if (optv > 1)
+                       printf("mft vcn %ld index %d\n",
+                               (long)le32_to_cpu(logr->target_vcn),
+                               (int)le16_to_cpu(logr->cluster_index));
+               err = sanity_mft(buffer);
+                       /* Should set to some previous lsn for undos */
+               if (opts)
+                       record->lsn = logr->this_lsn;
+               /* Duplicate on mftmirr if not overflowing its size */
+               mftmirr = (((u64)le32_to_cpu(logr->target_vcn)
+                               + le16_to_cpu(logr->lcns_to_follow))
+                               << clusterbits)
+                       <= (((u64)vol->mftmirr_size) << mftrecbits);
+               checked = TRUE;
+       }
+       if ((size == vol->indx_record_size) && !memcmp(buffer,"INDX",4)) {
+               indx = (INDEX_BLOCK*)buffer;
+               if (optv)
+                       printf("update index lsn 0x%llx"
+                               " (index %s than action 0x%llx)\n",
+                               (long long)le64_to_cpu(indx->lsn),
+                               ((s64)(le64_to_cpu(indx->lsn)
+                                   - le64_to_cpu(logr->this_lsn)) < 0 ?
+                                       "older" : "newer"),
+                               (long long)le64_to_cpu(logr->this_lsn));
+               err = sanity_indx(vol, buffer);
+                       /* Should set to some previous lsn for undos */
+               if (opts)
+                       indx->lsn = logr->this_lsn;
+               checked = TRUE;
+       }
+       if (!checked) {
+               printf("** Error : writing protected record of unknown type\n");
+               err = 1;
+       }
+       if (!err) {
+               if (!ntfs_mst_pre_write_fixup((NTFS_RECORD*)buffer, size)) {
+                       /*
+                        * If the record is smaller than a cluster, get a full
+                        * cluster, merge and write.
+                        */
+                       if (size < clustersz) {
+                               full = read_raw(vol, logr);
+                               if (full) {
+                                       pos = le16_to_cpu(logr->cluster_index)
+                                               << NTFS_BLOCK_SIZE_BITS;
+                                       memcpy(full + pos, buffer, size);
+                                       err = write_raw(vol, logr, full);
+                                       if (!err && mftmirr && !optn)
+                                               err = write_mirr(vol, logr,
+                                                               full);
+                                       free(full);
+                               }
+                       } else {
+                                       /* write full clusters */
+                               err = write_raw(vol, logr, buffer);
+                               if (!err && mftmirr && !optn)
+                                       err = write_mirr(vol, logr, buffer);
+                       }
+               } else {
+                       printf("** Failed to protect record\n");
+                       err = 1;
+               }
+       }
+       return (err);
+}
+
+/*
+ *             Resize attribute records
+ *
+ *     The attribute value is resized to new size, but the attribute
+ *     and MFT record must be kept aligned to 8 bytes.
+ */
+
+static int resize_attribute(MFT_RECORD *entry, ATTR_RECORD *attr, INDEX_ROOT *index,
+                       int rawresize, int resize)
+{
+       int err;
+       u32 newlength;
+       u32 newused;
+       u32 newvalue;
+       u32 indexlth;
+       u32 indexalloc;
+
+       err = 0;
+       if (attr) {
+               newvalue = le32_to_cpu(attr->value_length) + rawresize;
+               attr->value_length = cpu_to_le32(newvalue);
+               newlength = le32_to_cpu(attr->length) + resize;
+               attr->length = cpu_to_le32(newlength);
+       }
+       if (entry) {
+               newused = le32_to_cpu(entry->bytes_in_use) + resize;
+               entry->bytes_in_use = cpu_to_le32(newused);
+       }
+       if (index) {
+               indexlth = le32_to_cpu(index->index.index_length) + resize;
+               index->index.index_length = cpu_to_le32(indexlth);
+               indexalloc = le32_to_cpu(index->index.allocated_size) + resize;
+               index->index.allocated_size = cpu_to_le32(indexalloc);
+       }
+       return (err);
+}
+
+/*
+ *             Adjust the next attribute instance
+ *
+ *     If a newly created attribute matches the next instance, then
+ *     the next instance has to be incremented.
+ *
+ *     Do the opposite when undoing an attribute creation, but
+ *     do not change the next instance when deleting an attribute
+ *     or undoing the deletion.
+ */
+
+static void adjust_instance(const ATTR_RECORD *attr, MFT_RECORD *entry, int increment)
+{
+       u16 instance;
+
+       if (increment > 0) {
+                       /* Allocating a new instance ? */
+               if (attr->instance == entry->next_attr_instance) {
+                       instance = (le16_to_cpu(entry->next_attr_instance)
+                                       + 1) & 0xffff;
+                       entry->next_attr_instance = cpu_to_le16(instance);
+               }
+       }
+       if (increment < 0) {
+                       /* Freeing the latest instance ? */
+               instance = (le16_to_cpu(entry->next_attr_instance)
+                                       - 1) & 0xffff;
+               if (attr->instance == cpu_to_le16(instance))
+                       entry->next_attr_instance = attr->instance;
+       }
+}
+
+/*
+ *             Adjust the highest vcn according to mapping pairs
+ *
+ *     The runlist has to be fully recomputed
+ */
+
+static int adjust_high_vcn(ntfs_volume *vol, ATTR_RECORD *attr)
+{
+       runlist_element *rl;
+       runlist_element *xrl;
+       VCN high_vcn;
+       int err;
+
+       err = 1;
+       attr->highest_vcn = cpu_to_le64(0);
+       rl = ntfs_mapping_pairs_decompress(vol, attr, (runlist_element*)NULL);
+       if (rl) {
+               xrl = rl;
+               while (xrl->length)
+                       xrl++;
+               high_vcn = xrl->vcn - 1;
+               attr->highest_vcn = cpu_to_le64(high_vcn);
+               free(rl);
+               err = 0;
+       } else {
+               printf("** Failed to decompress the runlist\n");
+               dump((char*)attr,128);
+       }
+       return (err);
+}
+
+/*
+ *             Check index match, to be used for undos only
+ *
+ *     The action UpdateFileNameRoot updates the time stamps and/or the
+ *     sizes, but the lsn is not updated in the index record.
+ *     As a consequence such UpdateFileNameRoot are not always undone
+ *     and the actual record does not fully match the undo data.
+ *     We however accept the match if the parent directory and the name
+ *     match.
+ *     Alternate workaround : do not check the lsn when undoing
+ *     UpdateFileNameRoot
+ */
+
+static BOOL index_match_undo(const char *first, const char *second, int length)
+{
+       int len;
+       BOOL match;
+
+       match = !memcmp(first, second, length);
+       if (!match) {
+               if (optv) {
+                       printf("The existing index does not match :\n");
+                       dump(second,length);
+               }
+               len = (first[80] & 255)*2 + 2;
+               match = (feedle64(first, 16) == feedle64(second, 16))
+                   && !memcmp(first + 80, second + 80, len);
+               if (match && optv)
+                       printf("However parent dir and name do match\n");
+       }
+       return (match);
+}
+
+
+/*
+ *             Generic idempotent change to a resident attribute
+ */
+
+static int change_resident(ntfs_volume *vol, const struct ACTION_RECORD *action,
+               char *buffer, const char *data, u32 target, u32 length)
+{
+       LCN lcn;
+       ATTR_RECORD *attr;
+       u32 attrend;
+       int err;
+       int changed;
+
+       err = 1;
+       if (action->record.undo_length != action->record.redo_length)
+               printf("** Error size change in change_resident\n");
+       if (optv > 1) {
+               lcn = le64_to_cpu(action->record.lcn_list[0]);
+               printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
+                       (long long)inode_number(&action->record),
+                       (long long)lcn, (int)target, (int)length);
+       }
+       attr = (ATTR_RECORD*)(buffer
+                       + le16_to_cpu(action->record.record_offset));
+       if (optv > 1) {
+               printf("-> existing record :\n");
+               dump(&buffer[target], length);
+               printf("-> full MFT record :\n");
+               dump(buffer,mftrecsz);
+       }
+       attrend = le16_to_cpu(action->record.record_offset)
+                       + le32_to_cpu(attr->length);
+       if ((target + length) > attrend) {
+               printf("** Error : update overflows from attribute\n");
+       }
+       if (!(length & 7)
+           && ((target + length) <= attrend)
+           && (attrend <= mftrecsz)
+           && !sanity_mft(buffer)) {
+               changed = memcmp(buffer + target, data, length);
+               err = 0;
+               if (changed) {
+                       memcpy(buffer + target, data, length);
+                       if (optv > 1) {
+                               printf("-> new record :\n");
+                               dump(buffer + target, length);
+                       }
+                       err = write_protected(vol, &action->record,
+                                               buffer, mftrecsz);
+               }
+               if (optv > 1) {
+                       printf("-> MFT record %s\n",
+                               (changed ? "updated" : "unchanged"));
+               }
+       }
+       return (err);
+}
+
+static int change_resident_expect(ntfs_volume *vol, const struct ACTION_RECORD *action,
+               char *buffer, const char *data, const char *expected,
+               u32 target, u32 length, ATTR_TYPES type)
+{
+       LCN lcn;
+       ATTR_RECORD *attr;
+       int err;
+       BOOL found;
+
+       err = 1;
+       if (action->record.undo_length != action->record.redo_length)
+               printf("** Error size change in change_resident\n");
+       if (optv > 1) {
+               lcn = le64_to_cpu(action->record.lcn_list[0]);
+               printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
+                       (long long)inode_number(&action->record),
+                       (long long)lcn, (int)target, (int)length);
+       }
+       attr = (ATTR_RECORD*)(buffer
+                       + le16_to_cpu(action->record.record_offset));
+       if (optv > 1) {
+               printf("-> existing record :\n");
+               dump(&buffer[target], length);
+               printf("-> full record :\n");
+               dump((char*)attr, le32_to_cpu(attr->length));
+       }
+       if ((attr->type == type)
+           && !(length & 7)
+           && ((target + length) <= mftrecsz)) {
+               found = !memcmp(buffer + target, expected, length);
+               err = 0;
+               if (found) {
+                       memcpy(buffer + target, data, length);
+                       if (optv > 1) {
+                               printf("-> new record :\n");
+                               dump(buffer + target, length);
+                       }
+                       err = write_protected(vol, &action->record,
+                                               buffer, mftrecsz);
+               }
+               if (optv > 1) {
+                       printf("-> MFT record %s\n",
+                               (found ? "updated" : "unchanged"));
+               }
+       }
+       return (err);
+}
+
+/*
+ *             Generic idempotent change to a an index value
+ *
+ */
+
+static int change_index_value(ntfs_volume *vol, const struct ACTION_RECORD *action,
+               char *buffer, const char *data, u32 target, u32 length)
+{
+       LCN lcn;
+       u32 count;
+       u32 xsize;
+       int changed;
+       int err;
+
+       err = 1;
+       count = le16_to_cpu(action->record.lcns_to_follow);
+       if (optv > 1) {
+               lcn = le64_to_cpu(action->record.lcn_list[0]);
+               printf("-> lcn 0x%llx target 0x%x length %d\n",
+                       (long long)lcn, (int)target, (int)length);
+       }
+       xsize = vol->indx_record_size;
+       if (optv > 1) {
+               printf("-> existing record :\n");
+               dump(&buffer[target], length);
+       }
+       if ((target + length) <= (count << clusterbits)) {
+               changed = memcmp(buffer + target, data, length);
+               err = 0;
+               if (changed) {
+                       memcpy(buffer + target, data, length);
+                       if (optv > 1) {
+                               printf("-> new record :\n");
+                               dump(buffer + target, length);
+                       }
+                       err = write_protected(vol, &action->record,
+                                                       buffer, xsize);
+               }
+               if (optv > 1) {
+                       printf("-> data record %s\n",
+                               (changed ? "updated" : "unchanged"));
+               }
+       }
+       return (err);
+}
+
+/*
+ *             Add one or more resident attributes
+ */
+
+static int add_resident(ntfs_volume *vol, const struct ACTION_RECORD *action,
+                       char *buffer, const char *data, u32 target,
+                       u32 length, u32 oldlength)
+{
+       LCN lcn;
+       MFT_RECORD *entry;
+       int err;
+       BOOL found;
+       int resize;
+
+       err = 1;
+       if (optv > 1) {
+               lcn = le64_to_cpu(action->record.lcn_list[0]);
+               printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
+                       (long long)inode_number(&action->record),
+                       (long long)lcn, (int)target, (int)length);
+       }
+       entry = (MFT_RECORD*)buffer;
+       resize = length - oldlength;
+       if (optv > 1) {
+               printf("existing data :\n");
+               dump(buffer + target,length);
+       }
+       if (!(length & 7)
+           && !(oldlength & 7)
+           && ((target + length) <= mftrecsz)) {
+               /* This has to be an idempotent action */
+               err = 0;
+               if (data && length)
+                       found = !memcmp(buffer + target,
+                                               data, length);
+               else {
+                       found = TRUE;
+                       err = 1;
+               }
+               if (!found && !err) {
+                       /* Make space to insert the entry */
+                       memmove(buffer + target + resize,
+                               buffer + target,
+                               mftrecsz - target - resize);
+                       if (data)
+                               memcpy(buffer + target, data, length);
+                       else
+                               memset(buffer + target, 0, length);
+                       resize_attribute(entry, NULL, NULL,
+                                               resize, resize);
+                       if (optv > 1) {
+                               printf("new data at same location :\n");
+                               dump(buffer + target, length);
+                       }
+                       err = write_protected(vol, &action->record,
+                                               buffer, mftrecsz);
+               }
+               if (optv > 1) {
+                       printf("-> MFT record %s\n",
+                               (found ? "unchanged" : "expanded"));
+               }
+       }
+       return (err);
+}
+
+/*
+ *             Add one or more non-resident records
+ */
+
+static int delete_non_resident(void /*ntfs_volume *vol,
+               const struct ACTION_RECORD *action,
+               const char *data, u32 target, u32 length, u32 oldlength*/)
+{
+       int err;
+
+       err = 1;
+       printf("** delete_non_resident() not implemented\n");
+       return (err);
+}
+
+/*
+ *             Expand a single resident attribute
+ */
+
+static int expand_resident(ntfs_volume *vol, const struct ACTION_RECORD *action,
+                       char *buffer, const char *data, u32 target,
+                       u32 length, u32 oldlength)
+{
+       LCN lcn;
+       ATTR_RECORD *attr;
+       MFT_RECORD *entry;
+       int err;
+       BOOL found;
+       int resize;
+       u16 base;
+
+       err = 1;
+       if (optv > 1) {
+               lcn = le64_to_cpu(action->record.lcn_list[0]);
+               printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
+                       (long long)inode_number(&action->record),
+                       (long long)lcn, (int)target, (int)length);
+       }
+       entry = (MFT_RECORD*)buffer;
+       attr = (ATTR_RECORD*)(buffer
+                       + le16_to_cpu(action->record.record_offset));
+       if (optv > 1) {
+               printf("existing data :\n");
+               dump(buffer + target,length);
+       }
+       base = 24 + 2*attr->name_length;
+       resize = ((base + length - 1) | 7)
+               - ((base + oldlength - 1) | 7);
+       if ((target + length) <= mftrecsz) {
+               /* This has to be an idempotent action */
+// TODO This test is wrong !
+               found = le32_to_cpu(attr->value_length) == length;
+               if (found && data && length)
+                       found = !memcmp(buffer + target, data, length);
+               err = 0;
+               if (!found) {
+                       /* Make space to insert the entry */
+                       memmove(buffer + target + resize,
+                               buffer + target,
+                               mftrecsz - target - resize);
+// TODO what to do if length is not a multiple of 8 ?
+                       if (data)
+                               memcpy(buffer + target, data, length);
+                       else
+                               memset(buffer + target, 0, length);
+                       resize_attribute(entry, attr, NULL,
+                                               length - oldlength, resize);
+                       if (optv > 1) {
+                               printf("new data at same location :\n");
+                               dump(buffer + target, length);
+                       }
+                       err = write_protected(vol, &action->record,
+                                               buffer, mftrecsz);
+               }
+               if (optv > 1) {
+                       printf("-> MFT record %s\n",
+                                       (found ? "unchanged" : "expanded"));
+               }
+       }
+       return (err);
+}
+
+/*
+ *             Add one or more non-resident records
+ */
+
+static int add_non_resident(void /*ntfs_volume *vol,
+               const struct ACTION_RECORD *action,
+               const char *data, u32 target, u32 length, u32 oldlength*/)
+{
+       int err;
+
+       printf("** add_non_resident() not implemented\n");
+       err = 0;
+       return (err);
+}
+
+/*
+ *             Generic insert a new resident attribute
+ */
+
+static int insert_resident(ntfs_volume *vol, const struct ACTION_RECORD *action,
+                       char *buffer, const char *data, u32 target,
+                       u32 length)
+{
+       LCN lcn;
+       ATTR_RECORD *attr;
+       const ATTR_RECORD *newattr;
+       MFT_RECORD *entry;
+       u32 newused;
+       u16 links;
+       int err;
+       BOOL found;
+
+       err = 1;
+       if (optv > 1) {
+               lcn = le64_to_cpu(action->record.lcn_list[0]);
+               printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
+                       (long long)inode_number(&action->record),
+                       (long long)lcn, (int)target, (int)length);
+       }
+       entry = (MFT_RECORD*)buffer;
+       attr = (ATTR_RECORD*)(buffer
+                       + le16_to_cpu(action->record.record_offset));
+       newattr = (const ATTR_RECORD*)data;
+       if (optv > 1) {
+               printf("existing record :\n");
+               dump(buffer + target,length);
+               if (le32_to_cpu(attr->type) < le32_to_cpu(newattr->type)) {
+                       printf("** Bad attribute order, full record :\n");
+                       dump(buffer, mftrecsz);
+               }
+       }
+       /* Types must be in ascending order */
+       if (valid_type(attr->type)
+           && (le32_to_cpu(attr->type)
+                >= le32_to_cpu(newattr->type))
+           && !(length & 7)
+           && ((target + length) <= mftrecsz)) {
+               /* This has to be an idempotent action */
+               found = !memcmp(buffer + target, data, length);
+               err = 0;
+               if (!found) {
+                       /* Make space to insert the entry */
+                       memmove(buffer + target + length,
+                               buffer + target,
+                               mftrecsz - target - length);
+                       memcpy(buffer + target, data, length);
+                       newused = le32_to_cpu(entry->bytes_in_use)
+                                               + length;
+                       entry->bytes_in_use = cpu_to_le32(newused);
+                       if (action->record.redo_operation
+                           == const_cpu_to_le16(CreateAttribute)) {
+                       /*
+                        * For a real create, may have to adjust
+                        * the next attribute instance
+                        */
+                               adjust_instance(newattr, entry, 1);
+                       }
+                       if (newattr->type == AT_FILE_NAME) {
+                               links = le16_to_cpu(entry->link_count) + 1;
+                               entry->link_count = cpu_to_le16(links);
+                       }
+                       if (optv > 1) {
+                               printf("expanded record (now 0x%x"
+                                       " bytes used) :\n",
+                                       (int)newused);
+                               dump(buffer + target, 2*length);
+                       }
+                       err = write_protected(vol, &action->record,
+                                               buffer, mftrecsz);
+               }
+               if (optv > 1) {
+                       printf("-> MFT record %s\n",
+                               (found ? "unchanged" : "expanded"));
+               }
+       }
+       return (err);
+}
+
+/*
+ *             Generic remove a single resident attribute
+ */
+
+static int remove_resident(ntfs_volume *vol, const struct ACTION_RECORD *action,
+                       char *buffer, const char *data, u32 target,
+                       u32 length)
+{
+       LCN lcn;
+       ATTR_RECORD *attr;
+       MFT_RECORD *entry;
+       u32 newused;
+       u16 links;
+       int err;
+       BOOL found;
+
+       err = 1;
+       if (optv > 1) {
+               lcn = le64_to_cpu(action->record.lcn_list[0]);
+               printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
+                       (long long)inode_number(&action->record),
+                       (long long)lcn, (int)target, (int)length);
+       }
+       entry = (MFT_RECORD*)buffer;
+       attr = (ATTR_RECORD*)(buffer
+                       + le16_to_cpu(action->record.record_offset));
+       if (optv > 1) {
+               printf("existing record :\n");
+               dump(buffer + target,length);
+       }
+       if (!(length & 7)
+           && ((target + length) <= mftrecsz)) {
+               /* This has to be an idempotent action */
+       /* For AT_DATA the value is not always present */
+               if (attr->type == AT_DATA)
+                       found = !memcmp(buffer + target, data, 
+                               le16_to_cpu(attr->value_offset));
+               else
+                       found = !memcmp(buffer + target, data, length);
+               if (!found && optv) {
+                       printf("data 0x%lx 0x%lx offset %d %ld\n",
+                               (long)le32_to_cpu(attr->type),
+                               (long)le32_to_cpu(AT_DATA),
+                               (int)offsetof(ATTR_RECORD, resident_end),
+                               (long)le16_to_cpu(attr->value_offset));
+                       printf("The existing record does not match (%d/%d)\n",
+                               (int)matchcount(buffer + target, data,
+                               length),(int)length);
+                       dump(data,length);
+                       printf("full attr :\n");
+                       dump((const char*)attr,mftrecsz
+                               - le16_to_cpu(action->record.record_offset));
+               }
+               err = 0;
+               if (found) {
+                       if (attr->type == AT_FILE_NAME) {
+                               links = le16_to_cpu(entry->link_count) - 1;
+                               entry->link_count = cpu_to_le16(links);
+                       }
+                       if (action->record.redo_operation
+                           == const_cpu_to_le16(CreateAttribute)) {
+                               adjust_instance(attr, entry, -1);
+                       }
+                       /* Remove the entry */
+                       memmove(buffer + target,
+                               buffer + target + length,
+                               mftrecsz - target - length);
+                       newused = le32_to_cpu(entry->bytes_in_use) - length;
+                       entry->bytes_in_use = cpu_to_le32(newused);
+                       if (optv > 1) {
+                               printf("new record at same location"
+                                       " (now 0x%x bytes used) :\n",
+                                       (int)newused);
+                               dump(buffer + target, length);
+                       }
+                       err = write_protected(vol, &action->record,
+                                       buffer, mftrecsz);
+               }
+               if (optv > 1) {
+                       printf("-> MFT record %s\n",
+                               (found ? "shrinked" : "unchanged"));
+               }
+       }
+       return (err);
+}
+
+/*
+ *             Delete one or more resident attributes
+ */
+
+static int delete_resident(ntfs_volume *vol, const struct ACTION_RECORD *action,
+                       char *buffer, const char *data, u32 target,
+                       u32 length, u32 oldlength)
+{
+       LCN lcn;
+       MFT_RECORD *entry;
+       int err;
+       BOOL found;
+       int resize;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       if (optv > 1) {
+               lcn = le64_to_cpu(action->record.lcn_list[0]);
+               printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
+                       (long long)inode_number(&action->record),
+                       (long long)lcn, (int)target, (int)length);
+       }
+       entry = (MFT_RECORD*)buffer;
+       if (optv > 1) {
+               printf("existing data :\n");
+               dump(buffer + target,length);
+       }
+       resize = length - oldlength;
+       if (!(length & 7)
+           && !(oldlength & 7)
+           && ((target + oldlength) <= mftrecsz)) {
+               /* This has to be an idempotent action */
+               err = 0;
+               if (data && length)
+                       found = !memcmp(buffer + target, data, length);
+               else {
+                       found = FALSE;
+                       err = 1;
+               }
+               if (!found && !err) {
+                       /* Remove the entry, if present */
+                       memmove(buffer + target,
+                               buffer + target - resize,
+                               mftrecsz - target + resize);
+                       resize_attribute(entry, NULL, NULL,
+                                       length - oldlength, resize);
+                       if (optv > 1) {
+                               printf("new data at same location :\n");
+                               dump(buffer + target, length);
+                       }
+                       err = write_protected(vol, &action->record,
+                                                       buffer, mftrecsz);
+               }
+               if (optv > 1) {
+                       printf("-> MFT record %s\n",
+                               (found ? "unchanged" : "shrinked"));
+               }
+       }
+       return (err);
+}
+
+static int shrink_resident(ntfs_volume *vol, const struct ACTION_RECORD *action,
+                       char *buffer, const char *data, u32 target,
+                       u32 length, u32 oldlength)
+{
+       LCN lcn;
+       ATTR_RECORD *attr;
+       MFT_RECORD *entry;
+       int err;
+       BOOL found;
+       int resize;
+       u16 base;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       if (optv > 1) {
+               lcn = le64_to_cpu(action->record.lcn_list[0]);
+               printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
+                       (long long)inode_number(&action->record),
+                       (long long)lcn, (int)target, (int)length);
+       }
+       entry = (MFT_RECORD*)buffer;
+       attr = (ATTR_RECORD*)(buffer
+                               + le16_to_cpu(action->record.record_offset));
+       if (optv > 1) {
+               printf("existing data :\n");
+               dump(buffer + target,length);
+       }
+       base = 24 + 2*attr->name_length;
+       resize = ((base + length - 1) | 7)
+               - ((base + oldlength - 1) | 7);
+       if ((oldlength > length)
+// TODO limit to attr length
+           && ((target + oldlength) <= mftrecsz)) {
+               /* This has to be an idempotent action */
+               if (data && length)
+                       found = !memcmp(buffer + target, data, length);
+               else
+{
+// TODO wrong : need checking against the old data, but in known cases
+// redo data is not available either and existing data is not zero.
+                       found = FALSE;
+printf("* fake test, assuming not shrinked : value length %ld length %ld oldlength %ld\n",(long)le32_to_cpu(attr->value_length),(long)length,(long)oldlength);
+//dump(buffer + target, oldlength);
+}
+               err = 0;
+               if (!found) {
+                       if (length) {
+                               /* Relocate end of record */
+// TODO restrict to bytes_in_use
+                               memmove(buffer + target + length,
+                                       buffer + target + oldlength,
+                                       mftrecsz - target - oldlength);
+                               /* Insert new data or zeroes */
+                               if (data)
+                                       memcpy(buffer + target, data, length);
+                               else
+                                       memset(buffer + target, 0, length);
+                       } else {
+                               /* Remove the entry, unless targeted size */
+                               memmove(buffer + target,
+                                       buffer + target - resize,
+                                       mftrecsz - target + resize);
+                       }
+                       resize_attribute(entry, attr, NULL,
+                                       length - oldlength, resize);
+                       if (optv > 1) {
+                               printf("new data at same location :\n");
+                               dump(buffer + target, length);
+                       }
+                       err = write_protected(vol, &action->record,
+                                               buffer, mftrecsz);
+               }
+               if (optv > 1) {
+                       printf("-> MFT record %s\n",
+                               (found ? "unchanged" : "shrinked"));
+               }
+       }
+       return (err);
+}
+
+static int update_index(ntfs_volume *vol, const struct ACTION_RECORD *action,
+               char *buffer, const char *data, u32 target, u32 length)
+{
+       LCN lcn;
+       INDEX_BLOCK *indx;
+       u32 xsize;
+       BOOL changed;
+       int err;
+
+       err = 1;
+       if (optv > 1) {
+               lcn = le64_to_cpu(action->record.lcn_list[0]);
+               printf("-> lcn 0x%llx target 0x%x length %d\n",
+                       (long long)lcn, (int)target, (int)length);
+       }
+       xsize = vol->indx_record_size;
+       indx = (INDEX_BLOCK*)(buffer
+                       + le16_to_cpu(action->record.record_offset));
+       if (optv > 1) {
+               printf("-> existing index :\n");
+               dump(&buffer[target], length);
+       }
+       if ((indx->magic == magic_INDX)
+           && !(length & 7)
+           && ((target + length) <= xsize)) {
+               /* This has to be an idempotent action */
+               changed = memcmp(buffer + target, data, length);
+               err = 0;
+               if (changed) {
+                       /* Update the entry */
+                       memcpy(buffer + target, data, length);
+                       if (optv > 1) {
+                               printf("-> new index :\n");
+                               dump(&buffer[target], length);
+                       }
+                       err = write_protected(vol, &action->record,
+                                               buffer, xsize);
+               }
+               if (optv > 1) {
+                       printf("-> INDX record %s\n",
+                               (changed ? "updated" : "unchanged"));
+               }
+       }
+       return (err);
+}
+
+/*
+ *             Controversial deletion of file names, see undo_delete_file()
+ */
+
+static int delete_names(char *buffer)
+{
+       MFT_RECORD *record;
+       ATTR_RECORD *attr;
+       u32 used;
+       u32 pos;
+       int length;
+       int cnt;
+
+       record = (MFT_RECORD*)buffer;
+       pos = le16_to_cpu(record->attrs_offset);
+       used = le32_to_cpu(record->bytes_in_use);
+       cnt = 0;
+       do {
+               attr = (ATTR_RECORD*)&buffer[pos];
+               length = le32_to_cpu(attr->length);
+               if (attr->type == AT_FILE_NAME) {
+                       if (optv)
+                               showname("Controversial deletion of ",
+                                       &buffer[pos+90], buffer[pos+88] & 255);
+                       memmove(buffer + pos, buffer + pos + length,
+                               mftrecsz - pos - length);
+                       used -= length;
+                       cnt++;
+               } else
+                       pos += length;
+       } while ((pos < used)
+               && (le32_to_cpu(attr->type) <= le32_to_cpu(AT_FILE_NAME)));
+       record->bytes_in_use = cpu_to_le32(used);
+       record->link_count = cpu_to_le16(0);
+       return (cnt ? 0 : 1);
+}
+
+static int rebuildname(const INDEX_ENTRY *index)
+{
+       ATTR_RECORD *attr;
+       int headlth;
+       int datalth;
+
+       datalth = le16_to_cpu(index->length)
+                               - offsetof(INDEX_ENTRY,key.file_name);
+       headlth = offsetof(ATTR_RECORD,resident_end);
+       attr = (ATTR_RECORD*)malloc(headlth + datalth);
+       if (attr) {
+               attr->type = AT_FILE_NAME;
+               attr->length = cpu_to_le32(headlth + datalth);
+               attr->non_resident = 0;
+               attr->name_length = 0;
+               attr->name_offset = const_cpu_to_le16(0);
+               attr->flags = const_cpu_to_le16(0);
+               attr->instance = const_cpu_to_le16(0);
+               attr->value_length = cpu_to_le32(
+                       2*index->key.file_name.file_name_length
+                       + offsetof(FILE_NAME_ATTR, file_name));
+               attr->value_offset = cpu_to_le16(headlth);
+               attr->resident_flags = RESIDENT_ATTR_IS_INDEXED;
+               memcpy(attr->resident_end, &index->key.file_name, datalth);
+               free(attr);
+       }
+       return (0);
+}
+
+/*
+ *             Controversial creation of an index allocation attribute
+ *
+ *     This is useful for turning the clock backward, but cannot
+ *     work properly in the general case and must not be used for
+ *     a real sync.
+ *     The main problem is to synchronize the file names when an
+ *     inode is reused with a different name.
+ */
+
+static int insert_index_allocation(ntfs_volume *vol, char *buffer, u32 offs)
+{
+       MFT_RECORD *record;
+       ATTR_RECORD *attr;
+       u32 used;
+       u32 pos;
+       u32 xsize;
+       u16 instance;
+       int length;
+       int addedlength;
+       int namelength;
+       int err;
+       static const unsigned char bitmap[] =
+                       { 1, 0, 0, 0, 0, 0, 0, 0 } ;
+
+       err = 1;
+       if (opts) {
+               printf("** Call to unsupported insert_index_allocation()\n");
+       } else {
+               record = (MFT_RECORD*)buffer;
+               pos = le16_to_cpu(record->attrs_offset);
+               used = le32_to_cpu(record->bytes_in_use);
+               attr = (ATTR_RECORD*)&buffer[pos];
+               while ((pos < used)
+                   && (le32_to_cpu(attr->type) < le32_to_cpu(AT_INDEX_ROOT))) {
+                       pos += le32_to_cpu(attr->length);
+                       attr = (ATTR_RECORD*)&buffer[pos];
+               }
+               length = le32_to_cpu(attr->length);
+               addedlength = length - 8 /* index allocation */
+                     + length - 48; /* bitmap */
+               if ((attr->type == AT_INDEX_ROOT)
+                   && ((pos + length) == offs)
+                   && ((used + addedlength) < mftrecsz)) {
+                       /* Make space for the attribute */
+                       memmove(buffer + offs + addedlength, buffer + offs,
+                                       mftrecsz - offs - addedlength);
+                       record->bytes_in_use = cpu_to_le32(used + addedlength);
+                       /*
+                        * Insert an AT_INDEX_ALLOCATION
+                        */
+                       attr = (ATTR_RECORD*)&buffer[offs];
+                       attr->type = AT_INDEX_ALLOCATION;
+                       attr->length = cpu_to_le32(length - 8);
+                       attr->non_resident = 1;
+                       namelength = buffer[pos + 9] & 255;
+                       attr->name_length = namelength;
+                       attr->name_offset = const_cpu_to_le16(0x40);
+                       memcpy(buffer + offs + 0x40, buffer + pos + 0x18,
+                                                               2*namelength);
+                       attr->flags = const_cpu_to_le16(0);
+                       /* Should we really take a new instance ? */
+                       attr->instance = record->next_attr_instance;
+                       instance = le16_to_cpu(record->next_attr_instance) + 1;
+                       record->next_attr_instance = cpu_to_le16(instance);
+                       attr->lowest_vcn = const_cpu_to_le64(0);
+                       attr->highest_vcn = const_cpu_to_le64(0);
+                       attr->mapping_pairs_offset = cpu_to_le16(
+                                                       2*namelength + 0x40);
+                       attr->compression_unit = 0;
+                       xsize = vol->indx_record_size;
+                       attr->allocated_size = cpu_to_le64(xsize);
+                       attr->data_size = attr->allocated_size;
+                       attr->initialized_size = attr->allocated_size;
+                       /*
+                        * Insert an AT_INDEX_BITMAP
+                        */
+                       attr = (ATTR_RECORD*)&buffer[offs + length - 8];
+                       attr->type = AT_BITMAP;
+                       attr->length = cpu_to_le32(length - 48);
+                       attr->non_resident = 0;
+                       namelength = buffer[pos + 9] & 255;
+                       attr->name_length = namelength;
+                       attr->name_offset = const_cpu_to_le16(0x18);
+                       memcpy(buffer + offs + length - 8 + 0x18,
+                                       buffer + pos + 0x18, 2*namelength);
+                       attr->flags = const_cpu_to_le16(0);
+                       attr->value_length = const_cpu_to_le32(8);
+                       attr->value_offset = cpu_to_le16(2*namelength + 24);
+                       attr->resident_flags = 0;
+                       memcpy((char*)attr->resident_end + 2*namelength,
+                                                               bitmap, 8);
+                       /* Should we really take a new instance ? */
+                       attr->instance = record->next_attr_instance;
+                       instance = le16_to_cpu(record->next_attr_instance) + 1;
+                       record->next_attr_instance = cpu_to_le16(instance);
+                       err = sanity_mft(buffer);
+               } else {
+                       printf("** index root does not match\n");
+                       err = 1;
+               }
+       }
+       return (err);
+}
+
+/*
+ *             Check whether a full MFT record is fed by an action
+ *
+ *     If so, checking the validity of existing record is pointless
+ */
+
+static BOOL check_full_mft(const struct ACTION_RECORD *action, BOOL redoing)
+{
+       const MFT_RECORD *record;
+       const ATTR_RECORD *attr;
+       u32 length;
+       u32 k;
+       BOOL ok;
+
+       if (redoing) {
+               record = (const MFT_RECORD*)((const char*)&action->record
+                               + get_redo_offset(&action->record));
+               length = le16_to_cpu(action->record.redo_length);
+       } else {
+               record = (const MFT_RECORD*)((const char*)&action->record
+                               + get_undo_offset(&action->record));
+               length = le16_to_cpu(action->record.undo_length);
+       }
+               /* The length in use must be fed */
+       ok = !action->record.record_offset
+               && !action->record.attribute_offset
+               && (record->magic == magic_FILE)
+               && (length <= mftrecsz)
+               && (length >= (offsetof(MFT_RECORD, bytes_in_use)
+                        + sizeof(record->bytes_in_use)));
+       if (ok) {
+               k = le16_to_cpu(record->attrs_offset);
+               attr = (const ATTR_RECORD*)((const char*)record + k);
+               while (((k + sizeof(attr->type)) <= length)
+                   && (attr->type != AT_END)
+                   && valid_type(attr->type)) {
+                       k += le32_to_cpu(attr->length);
+                       attr = (const ATTR_RECORD*)((const char*)record + k);
+               }
+                       /* AT_END must be present */
+               ok = ((k + sizeof(attr->type)) <= length)
+                   && (attr->type == AT_END);
+       }
+       return (ok);
+}
+
+/*
+ *             Check whether a full index block is fed by the log record
+ *
+ *     If so, checking the validity of existing record is pointless
+ */
+
+static BOOL check_full_index(const struct ACTION_RECORD *action, BOOL redoing)
+{
+       const INDEX_BLOCK *indx;
+       u32 length;
+
+       if (redoing) {
+               indx = (const INDEX_BLOCK*)((const char*)&action->record
+                               + get_redo_offset(&action->record));
+               length = le16_to_cpu(action->record.redo_length);
+       } else {
+               indx = (const INDEX_BLOCK*)((const char*)&action->record
+                               + get_undo_offset(&action->record));
+               length = le16_to_cpu(action->record.undo_length);
+       }
+       /* the index length must be fed, so must be the full index block */
+       return (!action->record.record_offset
+               && !action->record.attribute_offset
+               && (indx->magic == magic_INDX)
+               && (length >= (offsetof(INDEX_BLOCK, index.index_length) + 4))
+               && (length >= (le32_to_cpu(indx->index.index_length) + 24)));
+}
+
+/*
+ *             Create an index block for undoing its deletion
+ *
+ *     This is useful for turning the clock backward, but cannot
+ *     work properly in the general case and must not be used for
+ *     a real sync.
+ */
+
+static int create_indx(ntfs_volume *vol, const struct ACTION_RECORD *action,
+                       char *buffer)
+{
+       INDEX_BLOCK *indx;
+       INDEX_ENTRY_HEADER *ixhead;
+       INDEX_ENTRY *ixentry;
+       VCN vcn;
+       int err;
+
+       if (opts) {
+               printf("** Call to unsupported create_indx()\n");
+               err = 1;
+       } else {
+               err = 0;
+               indx = (INDEX_BLOCK*)buffer;
+               indx->magic = magic_INDX;
+// TODO compute properly
+               indx->usa_ofs = const_cpu_to_le16(0x28);
+               indx->usa_count = const_cpu_to_le16(9);
+               indx->lsn = action->record.this_lsn;
+               vcn = le32_to_cpu(action->record.target_vcn);
+                       /* beware of size change on big-endian cpus */
+               indx->index_block_vcn = cpu_to_le64(vcn);
+                       /* INDEX_HEADER */
+               indx->index.entries_offset = const_cpu_to_le32(0x28);
+               indx->index.index_length = const_cpu_to_le32(0x38);
+               indx->index.allocated_size =
+                               cpu_to_le32(vol->indx_record_size - 24);
+               indx->index.ih_flags = 0;
+                       /* INDEX_ENTRY_HEADER */
+               ixhead = (INDEX_ENTRY_HEADER*)(buffer + 0x28);
+               ixhead->length = cpu_to_le16(vol->indx_record_size - 24);
+                       /* terminating INDEX_ENTRY */
+               ixentry = (INDEX_ENTRY*)(buffer + 0x40);
+               ixentry->indexed_file = const_cpu_to_le64(0);
+               ixentry->length = const_cpu_to_le16(16);
+               ixentry->key_length = const_cpu_to_le16(0);
+               ixentry->ie_flags = INDEX_ENTRY_END;
+       }
+       return (err);
+}
+
+static int redo_action37(ntfs_volume *vol, const struct ACTION_RECORD *action,
+                       char *buffer)
+{
+       u32 target;
+       u32 length;
+       int err;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       length = le16_to_cpu(action->record.redo_length);
+       target = le16_to_cpu(action->record.record_offset)
+               + le16_to_cpu(action->record.attribute_offset);
+       if (optv > 1) {
+               printf("existing data :\n");
+               dump(buffer + target,length);
+       }
+       if ((target + length) == mftrecsz) {
+               memset(buffer + target, 0, length);
+               err = write_protected(vol, &action->record,
+                                       buffer, mftrecsz);
+               if (optv > 1) {
+                       printf("-> MFT record trimmed\n");
+               }
+       } else {
+               printf("** Bad action-37, inode %lld record :\n",
+                       (long long)inode_number(&action->record));
+               printf("target %d length %d sum %d\n",
+                       (int)target,(int)length,(int)(target + length));
+               dump(buffer,mftrecsz);
+       }
+       err = 0;
+       return (err);
+}
+
+static int redo_add_index(ntfs_volume *vol, const struct ACTION_RECORD *action,
+                       char *buffer)
+{
+       LCN lcn;
+       const char *data;
+       INDEX_BLOCK *indx;
+       u32 target;
+       u32 length;
+       u32 xsize;
+       u32 indexlth;
+       int err;
+       BOOL found;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       data = ((const char*)&action->record)
+                       + get_redo_offset(&action->record);
+       length = le16_to_cpu(action->record.redo_length);
+       target = le16_to_cpu(action->record.record_offset)
+               + le16_to_cpu(action->record.attribute_offset);
+       if (optv > 1) {
+               lcn = le64_to_cpu(action->record.lcn_list[0]);
+               printf("-> lcn 0x%llx target 0x%x length %d\n",
+                       (long long)lcn, (int)target, (int)length);
+       }
+       xsize = vol->indx_record_size;
+       indx = (INDEX_BLOCK*)(buffer
+                       + le16_to_cpu(action->record.record_offset));
+       if (optv > 1) {
+               printf("-> existing record :\n");
+               dump(&buffer[target], length);
+       }
+       if ((indx->magic == magic_INDX)
+           && !(length & 7)
+           && ((target + length) <= xsize)) {
+               /* This has to be an idempotent action */
+               found = !memcmp(buffer + target, data, length);
+               err = 0;
+               if (!found) {
+                       /* Make space to insert the entry */
+                       memmove(buffer + target + length,
+                               buffer + target,
+                               xsize - target - length);
+                       memcpy(buffer + target, data, length);
+                       indexlth = le32_to_cpu(indx->index.index_length)
+                                               + length;
+                       indx->index.index_length = cpu_to_le32(indexlth);
+                       if (optv > 1) {
+                               printf("-> inserted record :\n");
+                               dump(&buffer[target], length);
+                       }
+                       err = write_protected(vol, &action->record,
+                                               buffer, xsize);
+               }
+               if (optv > 1) {
+                       printf("-> INDX record %s\n",
+                               (found ? "unchanged" : "inserted"));
+               }
+       }
+       return (err);
+}
+
+static int redo_add_root_index(ntfs_volume *vol,
+                       const struct ACTION_RECORD *action, char *buffer)
+{
+       LCN lcn;
+       const char *data;
+       ATTR_RECORD *attr;
+       MFT_RECORD *entry;
+       INDEX_ROOT *index;
+       u32 target;
+       u32 length;
+       int err;
+       BOOL found;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       data = ((const char*)&action->record)
+                       + get_redo_offset(&action->record);
+       length = le16_to_cpu(action->record.redo_length);
+       target = le16_to_cpu(action->record.record_offset)
+               + le16_to_cpu(action->record.attribute_offset);
+       if (optv > 1) {
+               lcn = le64_to_cpu(action->record.lcn_list[0]);
+               printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
+                       (long long)inode_number(&action->record),
+                       (long long)lcn, (int)target, (int)length);
+       }
+       entry = (MFT_RECORD*)buffer;
+       attr = (ATTR_RECORD*)(buffer
+                       + le16_to_cpu(action->record.record_offset));
+       index = (INDEX_ROOT*)(((char*)attr)
+                       + le16_to_cpu(attr->value_offset));
+       if (optv > 1) {
+               printf("existing index :\n");
+               dump(buffer + target,length);
+       }
+       if ((attr->type == AT_INDEX_ROOT)
+           && !(length & 7)
+           && ((target + length) <= mftrecsz)) {
+               /* This has to be an idempotent action */
+               found = !memcmp(buffer + target, data, length);
+               err = 0;
+               if (!found) {
+                       /* Make space to insert the entry */
+                       memmove(buffer + target + length,
+                               buffer + target,
+                               mftrecsz - target - length);
+                       memcpy(buffer + target, data, length);
+                       resize_attribute(entry, attr, index, length, length);
+                       if (optv > 1) {
+                               printf("new index at same location :\n");
+                               dump(buffer + target, length);
+                       }
+                       err = write_protected(vol, &action->record,
+                                       buffer, mftrecsz);
+               }
+               if (optv > 1) {
+                       printf("-> MFT record %s\n",
+                               (found ? "unchanged" : "expanded"));
+               }
+       }
+       return (err);
+}
+
+static int redo_compensate(ntfs_volume *vol __attribute__((unused)),
+                       const struct ACTION_RECORD *action,
+                       char *buffer __attribute__((unused)))
+{
+       u64 lsn;
+       s64 diff;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       lsn = le64_to_cpu(action->record.this_lsn);
+       diff = lsn - restart_lsn;
+       if (diff > 0)
+               restart_lsn = lsn;
+       return (0);
+}
+
+static int redo_create_file(ntfs_volume *vol,
+                       const struct ACTION_RECORD *action, char *buffer)
+{
+       LCN lcn;
+       const char *data;
+       MFT_RECORD *record;
+       const MFT_RECORD *fullrec;
+       u32 target;
+       u32 length;
+       int err;
+       int changed;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       data = ((const char*)&action->record)
+                       + get_redo_offset(&action->record);
+       length = le16_to_cpu(action->record.redo_length);
+       target = le16_to_cpu(action->record.record_offset)
+               + le16_to_cpu(action->record.attribute_offset);
+       if (optv > 1) {
+               lcn = le64_to_cpu(action->record.lcn_list[0]);
+               printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
+                       (long long)inode_number(&action->record),
+                       (long long)lcn, (int)target, (int)length);
+       }
+       record = (MFT_RECORD*)buffer;
+       if (optv > 1) {
+               printf("-> existing record :\n");
+               dump(buffer,mftrecsz);
+       }
+       if ((target + length) <= mftrecsz) {
+               changed = memcmp(buffer + target, data, length);
+               err = 0;
+               if (changed || !(record->flags & MFT_RECORD_IN_USE)) {
+                       memcpy(buffer + target, data, length);
+                       record->flags |= MFT_RECORD_IN_USE;
+                       if (optv > 1) {
+                               printf("-> new record :\n");
+                               dump(buffer,mftrecsz);
+                       }
+                       err = write_protected(vol, &action->record,
+                                               buffer, mftrecsz);
+               }
+               if (optv > 1) {
+                       printf("-> MFT record %s\n",
+                               (changed ? "updated" : "unchanged"));
+               }
+       } else {
+// TODO make sure this was caused by bad fixups
+               /*
+                * Could not read protected, assume newly allocated record.
+                * Check we are creating a full MFT record, and so
+                * existing data is meaningless.
+                */
+               fullrec = (const MFT_RECORD*)data;
+               if ((length > offsetof(MFT_RECORD, bytes_in_use))
+                   && (le32_to_cpu(fullrec->bytes_in_use) <= length)
+                   && (fullrec->magic == magic_FILE)
+                   && !target
+                   && (length <= mftrecsz)) {
+                       buffer = (char*)malloc(mftrecsz);
+                       memcpy(buffer, data, length);
+                       if (optv > 1) {
+                               printf("-> created MFT record :\n");
+                               dump(buffer, length);
+                       }
+                       err = write_protected(vol, &action->record,
+                                                       buffer, mftrecsz);
+               }
+       }
+       return (err);
+}
+
+static int redo_create_attribute(ntfs_volume *vol,
+                       const struct ACTION_RECORD *action, char *buffer)
+{
+       const char *data;
+       u32 target;
+       u32 length;
+       int err;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       data = ((const char*)&action->record)
+                       + get_redo_offset(&action->record);
+       length = le16_to_cpu(action->record.redo_length);
+       target = le16_to_cpu(action->record.record_offset)
+               + le16_to_cpu(action->record.attribute_offset);
+// Could also be AT_DATA or AT_INDEX_ALLOCATION
+       if (!action->record.undo_length)
+               err = insert_resident(vol, action, buffer, data,
+                               target, length);
+       return (err);
+}
+
+static int redo_delete_attribute(ntfs_volume *vol,
+                       const struct ACTION_RECORD *action, char *buffer)
+{
+       const char *data;
+       u32 target;
+       u32 length;
+       int err;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       data = ((const char*)&action->record)
+                       + get_undo_offset(&action->record);
+       length = le16_to_cpu(action->record.undo_length);
+       target = le16_to_cpu(action->record.record_offset)
+               + le16_to_cpu(action->record.attribute_offset);
+       if (!action->record.redo_length)
+               err = remove_resident(vol, action, buffer, data,
+                               target, length);
+       return (err);
+}
+
+static int redo_delete_file(ntfs_volume *vol,
+                       const struct ACTION_RECORD *action, char *buffer)
+{
+       LCN lcn;
+       const char *data;
+       MFT_RECORD *record;
+       u32 target;
+       u32 length;
+       int err;
+       int changed;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       data = ((const char*)&action->record)
+                       + get_undo_offset(&action->record);
+       length = le16_to_cpu(action->record.undo_length);
+       target = le16_to_cpu(action->record.record_offset)
+               + le16_to_cpu(action->record.attribute_offset);
+       if (optv > 1) {
+               lcn = le64_to_cpu(action->record.lcn_list[0]);
+               printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
+                       (long long)inode_number(&action->record),
+                       (long long)lcn, (int)target, (int)length);
+       }
+       if (optv > 1) {
+               printf("-> existing record :\n");
+               dump(buffer,mftrecsz);
+       }
+       record = (MFT_RECORD*)buffer;
+       if ((target + length) <= mftrecsz) {
+               /* write a void mft entry (needed ?) */
+               changed = memcmp(buffer + target, data, length)
+                       || (record->flags & MFT_RECORD_IN_USE);
+               err = 0;
+               if (changed) {
+                       memcpy(buffer + target, data, length);
+                       record->flags &= ~MFT_RECORD_IN_USE;
+                       if (optv > 1) {
+                               printf("-> new record :\n");
+                               dump(buffer,mftrecsz);
+                       }
+                       err = write_protected(vol, &action->record,
+                                               buffer, mftrecsz);
+               }
+               if (optv > 1) {
+                       printf("-> MFT record %s\n",
+                               (changed ? "updated" : "unchanged"));
+               }
+       }
+       return (err);
+}
+
+static int redo_delete_index(ntfs_volume *vol,
+                       const struct ACTION_RECORD *action, char *buffer)
+{
+       LCN lcn;
+       const char *data;
+       INDEX_BLOCK *indx;
+       u32 target;
+       u32 length;
+       u32 xsize;
+       u32 indexlth;
+       int err;
+       BOOL found;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       data = ((const char*)&action->record)
+                       + get_undo_offset(&action->record);
+       length = le16_to_cpu(action->record.undo_length);
+// TODO merge with undo_add_index ?
+       target = le16_to_cpu(action->record.record_offset)
+               + le16_to_cpu(action->record.attribute_offset);
+       if (optv > 1) {
+               lcn = le64_to_cpu(action->record.lcn_list[0]);
+               printf("-> lcn 0x%llx target 0x%x length %d\n",
+                       (long long)lcn, (int)target, (int)length);
+       }
+       xsize = vol->indx_record_size;
+       indx = (INDEX_BLOCK*)(buffer
+                       + le16_to_cpu(action->record.record_offset));
+       if (optv > 1) {
+               printf("-> existing record :\n");
+               dump(&buffer[target], length);
+       }
+       if ((indx->magic == magic_INDX)
+           && !(length & 7)
+           && ((target + length) <= xsize)) {
+               /* This has to be an idempotent action */
+               found = !memcmp(buffer + target, data, length);
+               err = 0;
+               if (found) {
+                       /* Remove the entry */
+                       memmove(buffer + target,
+                               buffer + target + length,
+                               xsize - target - length);
+                       indexlth = le32_to_cpu(indx->index.index_length)
+                                               - length;
+                       indx->index.index_length = cpu_to_le32(indexlth);
+                       err = write_protected(vol, &action->record,
+                                               buffer, xsize);
+               }
+               if (optv > 1) {
+                       printf("-> INDX record %s\n",
+                               (found ? "unchanged" : "removed"));
+               }
+       }
+       return (err);
+}
+
+static int redo_delete_root_index(ntfs_volume *vol,
+                       const struct ACTION_RECORD *action, char *buffer)
+{
+       LCN lcn;
+       const char *data;
+       ATTR_RECORD *attr;
+       MFT_RECORD *entry;
+       INDEX_ROOT *index;
+       BOOL found;
+       u32 target;
+       u32 length;
+       int err;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       data = ((const char*)&action->record)
+                       + get_undo_offset(&action->record);
+       length = le16_to_cpu(action->record.undo_length);
+       target = le16_to_cpu(action->record.record_offset)
+               + le16_to_cpu(action->record.attribute_offset);
+
+       if (optv > 1) {
+               lcn = le64_to_cpu(action->record.lcn_list[0]);
+               printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
+                       (long long)inode_number(&action->record),
+                       (long long)lcn, (int)target, (int)length);
+       }
+       entry = (MFT_RECORD*)buffer;
+       attr = (ATTR_RECORD*)(buffer
+                       + le16_to_cpu(action->record.record_offset));
+       index = (INDEX_ROOT*)(((char*)attr)
+                       + le16_to_cpu(attr->value_offset));
+       if (optv > 1) {
+               printf("existing index :\n");
+               dump(buffer + target,length);
+       }
+       if ((attr->type == AT_INDEX_ROOT)
+           && !(length & 7)
+           && ((target + length) <= mftrecsz)) {
+               /* This has to be an idempotent action */
+               found = !memcmp(buffer + target, data, length);
+               err = 0;
+               /* Only delete if present */
+               if (found) {
+                       /* Remove the entry */
+                       memmove(buffer + target,
+                               buffer + target + length,
+                               mftrecsz - target - length);
+                       resize_attribute(entry, attr, index, -length, -length);
+                       if (optv > 1) {
+                               printf("new index at same location :\n");
+                               dump(buffer + target, length);
+                       }
+                       err = write_protected(vol, &action->record,
+                                       buffer, mftrecsz);
+               }
+               if (optv > 1) {
+                       printf("-> MFT record %s\n",
+                               (found ? "shrinked" : "updated"));
+               }
+       }
+       return (err);
+}
+
+static int redo_force_bits(ntfs_volume *vol,
+                       const struct ACTION_RECORD *action, char *buffer)
+{
+       LCN lcn;
+       const struct BITMAP_ACTION *data;
+       u32 i;
+       int err;
+       int wanted;
+       u32 firstbit;
+       u32 count;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       data = (const struct BITMAP_ACTION*)
+                       (((const char*)&action->record)
+                               + get_redo_offset(&action->record));
+       firstbit = le32_to_cpu(data->firstbit);
+       count = le32_to_cpu(data->count);
+       if (action->record.redo_operation
+                       == const_cpu_to_le16(SetBitsInNonResidentBitMap))
+               wanted = 1;
+       else
+               wanted = 0;
+// TODO consistency undo_offset == redo_offset, etc.
+// firstbit + count < 8*clustersz (multiple clusters possible ?)
+       if (optv > 1) {
+               lcn = le64_to_cpu(action->record.lcn_list[0]);
+               printf("-> lcn 0x%llx firstbit %d count %d wanted %d\n",
+                       (long long)lcn,(int)firstbit,(int)count,(int)wanted);
+       }
+       for (i=0; i<count; i++)
+               ntfs_bit_set((u8*)buffer, firstbit + i, wanted);
+       if (!write_raw(vol, &action->record, buffer)) {
+               err = 0;
+               if (optv > 1)
+                       printf("-> record updated\n");
+       }
+       if (err)
+               printf("** redo_clearbits failed\n");
+       return (err);
+}
+
+static int redo_open_attribute(ntfs_volume *vol __attribute__((unused)),
+                               const struct ACTION_RECORD *action)
+{
+       const char *data;
+       struct ATTR *pa;
+       const struct ATTR_OLD *attr_old;
+       const struct ATTR_NEW *attr_new;
+       const char *name;
+       le64 inode;
+       u32 namelen;
+       u32 length;
+       u32 extra;
+       int err;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       data = ((const char*)&action->record)
+                       + get_redo_offset(&action->record);
+       length = le16_to_cpu(action->record.redo_length);
+       extra = get_extra_offset(&action->record);
+       if (action->record.undo_length) {
+               name = ((const char*)&action->record) + extra;
+               namelen = le32_to_cpu(action->record.client_data_length)
+                               + LOG_RECORD_HEAD_SZ - extra;
+               /* fix namelen which was aligned modulo 8 */
+               namelen = fixnamelen(name, namelen);
+               if (optv > 1) {
+                       printf("-> length %d namelen %d",(int)length,
+                                                       (int)namelen);
+                       showname(", ", name, namelen/2);
+               }
+       } else {
+               name = "";
+               namelen = 0;
+       }
+       pa = getattrentry(le16_to_cpu(action->record.target_attribute),0);
+       if (pa) {
+               if (optv) {
+                       /*
+                        * If the actions have been displayed, the
+                        * attribute has already been fed. Check
+                        * whether it matches what we have in store.
+                        */
+                       switch (length) {
+                       case sizeof(struct ATTR_OLD) :
+                               attr_old = (const struct ATTR_OLD*)data;
+                                       /* Badly aligned */
+                               memcpy(&inode, &attr_old->inode, 8);
+                               err = (MREF(le64_to_cpu(inode)) != pa->inode)
+                                   || (attr_old->type != pa->type);
+                               break;
+                       case sizeof(struct ATTR_NEW) :
+                               attr_new = (const struct ATTR_NEW*)data;
+                               err = (MREF(le64_to_cpu(attr_new->inode))
+                                                       != pa->inode)
+                                   || (attr_new->type != pa->type);
+                               break;
+                       default : err = 1;
+                       }
+                       if (!err) {
+                               err = (namelen != pa->namelen)
+                                       || (namelen
+                                       && memcmp(name, pa->name, namelen));
+                       }
+                       if (optv > 1)
+                               printf("-> attribute %s the recorded one\n",
+                                       (err ? "does not match" : "matches"));
+               } else {
+                       copy_attribute(pa, data, length);
+                       pa->namelen = namelen;
+                       if (namelen)
+                               memcpy(pa->name, data, namelen);
+                       err = 0;
+               }
+       } else
+               if (optv)
+                       printf("* Unrecorded attribute\n");
+       return (err);
+}
+
+static int redo_sizes(ntfs_volume *vol, const struct ACTION_RECORD *action,
+                       char *buffer)
+{
+       const char *data;
+       u32 target;
+       u32 length;
+       int err;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       data = ((const char*)&action->record)
+                       + get_redo_offset(&action->record);
+       length = le16_to_cpu(action->record.redo_length);
+       target = le16_to_cpu(action->record.record_offset)
+               + le16_to_cpu(action->record.attribute_offset)
+               + offsetof(ATTR_RECORD, allocated_size);
+       err = change_resident(vol, action, buffer,
+                       data, target, length);
+       return (err);
+}
+
+static int redo_update_index(ntfs_volume *vol,
+                       const struct ACTION_RECORD *action, char *buffer)
+{
+       const char *data;
+       u32 target;
+       u32 length;
+       int err;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       data = ((const char*)&action->record)
+                       + get_redo_offset(&action->record);
+       length = le16_to_cpu(action->record.redo_length);
+                       /* target is left-justified to creation time */
+       target = le16_to_cpu(action->record.record_offset)
+               + le16_to_cpu(action->record.attribute_offset)
+               + offsetof(INDEX_ENTRY, key.file_name.creation_time);
+       err = update_index(vol, action, buffer, data, target, length);
+       return (err);
+}
+
+static int redo_update_index_value(ntfs_volume *vol,
+                       const struct ACTION_RECORD *action, char *buffer)
+{
+       const char *data;
+       u32 length;
+       u32 target;
+       int err;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       data = ((const char*)&action->record)
+                               + get_redo_offset(&action->record);
+       length = le16_to_cpu(action->record.redo_length);
+       target = le16_to_cpu(action->record.record_offset)
+               + le16_to_cpu(action->record.attribute_offset);
+       err = change_index_value(vol, action, buffer, data, target, length);
+       return (err);
+}
+
+static int redo_update_mapping(ntfs_volume *vol,
+                       const struct ACTION_RECORD *action, char *buffer)
+{
+       LCN lcn;
+       const char *data;
+       ATTR_RECORD *attr;
+       MFT_RECORD *entry;
+       u32 target;
+       u32 length;
+       u32 source;
+       u32 alen;
+       u32 newused;
+       int resize;
+       int err;
+       int changed;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       data = ((const char*)&action->record)
+                       + get_redo_offset(&action->record);
+       length = le16_to_cpu(action->record.redo_length);
+       resize = length - le16_to_cpu(action->record.undo_length);
+       target = le16_to_cpu(action->record.record_offset)
+               + le16_to_cpu(action->record.attribute_offset);
+       if (optv > 1) {
+               lcn = le64_to_cpu(action->record.lcn_list[0]);
+               printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
+                       (long long)inode_number(&action->record),
+                       (long long)lcn, (int)target, (int)length);
+       }
+       if (optv > 1) {
+               printf("-> existing record :\n");
+               dump(&buffer[target], length);
+       }
+       entry = (MFT_RECORD*)buffer;
+       attr = (ATTR_RECORD*)(buffer
+                       + le16_to_cpu(action->record.record_offset));
+       if (!attr->non_resident) {
+               printf("** Error : update_mapping on resident attr\n");
+       }
+       if (valid_type(attr->type)
+           && attr->non_resident
+           && !(resize & 7)
+           && ((target + length) <= mftrecsz)) {
+               changed = memcmp(buffer + target, data, length);
+               err = 0;
+               if (changed) {
+                       /* Adjust space for new mapping pairs */
+                       source = target - resize;
+                       if (resize > 0) {
+                               memmove(buffer + target + length,
+                                       buffer + source + length,
+                                       mftrecsz - target - length);
+                       }
+                       if (resize < 0) {
+                               memmove(buffer + target + length,
+                                       buffer + source + length,
+                                       mftrecsz - source - length);
+                       }
+                       memcpy(buffer + target, data, length);
+                               /* Resize the attribute */
+                       alen = le32_to_cpu(attr->length) + resize;
+                       attr->length = cpu_to_le32(alen);
+                               /* Resize the mft record */
+                       newused = le32_to_cpu(entry->bytes_in_use)
+                                       + resize;
+                       entry->bytes_in_use = cpu_to_le32(newused);
+                               /* Compute the new highest_vcn */
+                       err = adjust_high_vcn(vol, attr);
+                       if (optv > 1) {
+                               printf("-> new record :\n");
+                               dump(buffer + target, length);
+                       }
+                       if (!err) {
+                               err = write_protected(vol,
+                                       &action->record,
+                                       buffer, mftrecsz);
+                       }
+               }
+               if (optv > 1) {
+                       printf("-> MFT record %s\n",
+                               (changed ? "updated" : "unchanged"));
+               }
+       }
+       return (err);
+}
+
+static int redo_update_resident(ntfs_volume *vol,
+                       const struct ACTION_RECORD *action, char *buffer)
+{
+       LCN lcn;
+       const char *data;
+       u32 target;
+       u32 length;
+       u32 oldlength;
+       u32 end;
+       u32 redo;
+       int err;
+       int changed;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       end = le32_to_cpu(action->record.client_data_length)
+                       + LOG_RECORD_HEAD_SZ;
+       length = le16_to_cpu(action->record.redo_length);
+       redo = get_redo_offset(&action->record);
+       if ((redo + length) > end)
+               data = (char*)NULL;
+       else
+               data = ((const char*)&action->record) + redo;
+       oldlength = le16_to_cpu(action->record.undo_length);
+       target = le16_to_cpu(action->record.record_offset)
+               + le16_to_cpu(action->record.attribute_offset);
+       if (length == oldlength) {
+               if (optv > 1) {
+                       lcn = le64_to_cpu(action->record.lcn_list[0]);
+                       printf("-> inode %lld lcn 0x%llx target 0x%x"
+                               " length %d\n",
+                               (long long)inode_number(&action->record),
+                               (long long)lcn, (int)target, (int)length);
+               }
+               if (optv > 1) {
+                       printf("-> existing record :\n");
+                       dump(&buffer[target], length);
+               }
+               if ((target + length) <= mftrecsz) {
+                       changed = memcmp(buffer + target, data, length);
+                       err = 0;
+                       if (changed) {
+                               memcpy(buffer + target, data, length);
+                               if (optv > 1) {
+                                       printf("-> new record :\n");
+                                       dump(buffer + target, length);
+                               }
+                               err = write_protected(vol, &action->record,
+                                       buffer, mftrecsz);
+                       }
+                       if (optv > 1) {
+                               printf("-> MFT record %s\n",
+                                       (changed ? "updated" : "unchanged"));
+                       }
+               }
+       } else {
+               if (length > oldlength)
+                       err = expand_resident(vol, action, buffer, data,
+                                       target, length, oldlength);
+               else
+                       err = shrink_resident(vol, action, buffer, data,
+                                       target, length, oldlength);
+       }
+       return (err);
+}
+
+static int redo_update_root_index(ntfs_volume *vol,
+                       const struct ACTION_RECORD *action, char *buffer)
+{
+       const char *data;
+       const char *expected;
+       u32 target;
+       u32 length;
+       int err;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       data = ((const char*)&action->record)
+                       + get_redo_offset(&action->record);
+       expected = ((const char*)&action->record)
+                       + get_undo_offset(&action->record);
+       length = le16_to_cpu(action->record.redo_length);
+               /* the fixup is right-justified to the name length */
+       target = le16_to_cpu(action->record.record_offset)
+               + le16_to_cpu(action->record.attribute_offset)
+               + offsetof(INDEX_ENTRY, key.file_name.file_name_length)
+               - length;
+       err = change_resident_expect(vol, action, buffer, data, expected,
+                       target, length, AT_INDEX_ROOT);
+       return (err);
+}
+
+static int redo_update_root_vcn(ntfs_volume *vol,
+                       const struct ACTION_RECORD *action, char *buffer)
+{
+       const char *data;
+       const char *expected;
+       u32 target;
+       u32 length;
+       int err;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       data = ((const char*)&action->record)
+                       + get_redo_offset(&action->record);
+       expected = ((const char*)&action->record)
+                       + get_undo_offset(&action->record);
+       length = le16_to_cpu(action->record.redo_length);
+// length must be 8
+       target = le16_to_cpu(action->record.record_offset)
+               + le16_to_cpu(action->record.attribute_offset)
++ 16; // explanation needed (right justified ?)
+       err = change_resident_expect(vol, action, buffer, data, expected,
+                       target, length, AT_INDEX_ROOT);
+       return (err);
+}
+
+static int redo_update_value(ntfs_volume *vol,
+                       const struct ACTION_RECORD *action, char *buffer)
+{
+       LCN lcn;
+       const char *data;
+       u32 length;
+       u32 target;
+       u32 count;
+       u32 redo;
+       u32 end;
+       u32 i;
+       int changed;
+       int err;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       length = le16_to_cpu(action->record.redo_length);
+       redo = get_redo_offset(&action->record);
+       end = le32_to_cpu(action->record.client_data_length)
+                               + LOG_RECORD_HEAD_SZ;
+               /* sometimes there is no redo data */
+       if ((redo + length) > end)
+               data = (char*)NULL;
+       else
+               data = ((const char*)&action->record) + redo;
+       target = le16_to_cpu(action->record.record_offset)
+               + le16_to_cpu(action->record.attribute_offset);
+       count = le16_to_cpu(action->record.lcns_to_follow);
+       if (optv > 1) {
+               lcn = le64_to_cpu(action->record.lcn_list[0]);
+               printf("-> lcn 0x%llx target 0x%x length %d\n",
+                       (long long)lcn, (int)target, (int)length);
+       }
+       if (optv > 1) {
+               printf("-> existing record :\n");
+               dump(&buffer[target], length);
+       }
+       if ((target + length) <= (count << clusterbits)) {
+               if (data)
+                       changed = memcmp(buffer + target, data, length);
+               else {
+                       for (i=0; (i<length) && !buffer[target+i]; i++) { }
+                       changed = length && (i < length);
+               }
+               err = 0;
+               if (changed) {
+                       if (data)
+                               memcpy(buffer + target, data, length);
+                       else
+                               memset(buffer + target, 0, length);
+                       if (optv > 1) {
+                               printf("-> new record :\n");
+                               dump(buffer + target, length);
+                       }
+                       err = write_raw(vol, &action->record, buffer);
+               }
+               if (optv > 1) {
+                       printf("-> data record %s\n",
+                               (changed ? "updated" : "unchanged"));
+               }
+       }
+
+       return (err);
+}
+
+static int redo_update_vcn(ntfs_volume *vol,
+                       const struct ACTION_RECORD *action, char *buffer)
+{
+       const char *data;
+       u32 target;
+       u32 length;
+       int err;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       data = ((const char*)&action->record)
+                       + get_redo_offset(&action->record);
+       length = le16_to_cpu(action->record.redo_length);
+                       /* target is left-justified to creation time */
+       target = le16_to_cpu(action->record.record_offset)
+               + le16_to_cpu(action->record.attribute_offset)
+               + 16; // to better describe
+       err = update_index(vol, action, buffer, data, target, length);
+       return (err);
+}
+
+static int redo_write_end(ntfs_volume *vol,
+                       const struct ACTION_RECORD *action, char *buffer)
+{
+       LCN lcn;
+       const char *data;
+       u32 target;
+       u32 length;
+       u32 oldlength;
+       u32 end;
+       u32 redo;
+       int err;
+       int changed;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       end = le32_to_cpu(action->record.client_data_length)
+                       + LOG_RECORD_HEAD_SZ;
+       length = le16_to_cpu(action->record.redo_length);
+       redo = get_redo_offset(&action->record);
+       if ((redo + length) > end)
+               data = (char*)NULL;
+       else
+               data = ((const char*)&action->record) + redo;
+       oldlength = le16_to_cpu(action->record.undo_length);
+       target = le16_to_cpu(action->record.record_offset)
+               + le16_to_cpu(action->record.attribute_offset);
+       if (length == oldlength) {
+               if (optv > 1) {
+                       lcn = le64_to_cpu(action->record.lcn_list[0]);
+                       printf("-> inode %lld lcn 0x%llx target 0x%x"
+                               " length %d\n",
+                               (long long)inode_number(&action->record),
+                               (long long)lcn, (int)target, (int)length);
+               }
+               if (optv > 1) {
+                       printf("-> existing record :\n");
+                       dump(&buffer[target], length);
+               }
+               if ((target + length) <= mftrecsz) {
+                       changed = memcmp(buffer + target, data, length);
+                       err = 0;
+                       if (changed) {
+                               memcpy(buffer + target, data, length);
+                               if (optv > 1) {
+                                       printf("-> new record :\n");
+                                       dump(buffer + target, length);
+                               }
+                               err = write_protected(vol, &action->record,
+                                               buffer, mftrecsz);
+                       }
+                       if (optv > 1) {
+                               printf("-> MFT record %s\n",
+                                       (changed ? "updated" : "unchanged"));
+                       }
+               }
+       } else {
+               if (length > oldlength)
+                       err = add_resident(vol, action, buffer, data,
+                                       target, length, oldlength);
+               else
+                       err = delete_resident(vol, action, buffer, data,
+                                       target, length, oldlength);
+       }
+       return (err);
+}
+
+static int redo_write_index(ntfs_volume *vol,
+                       const struct ACTION_RECORD *action, char *buffer)
+{
+       LCN lcn;
+       const char *data;
+       INDEX_BLOCK *indx;
+       u32 target;
+       u32 length;
+       u32 xsize;
+       int err;
+       int changed;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       data = ((const char*)&action->record)
+                       + get_redo_offset(&action->record);
+       length = le16_to_cpu(action->record.redo_length);
+                       /* target is left-justified to creation time */
+       target = le16_to_cpu(action->record.record_offset)
+               + le16_to_cpu(action->record.attribute_offset);
+       if (optv > 1) {
+               lcn = le64_to_cpu(action->record.lcn_list[0]);
+               printf("-> lcn 0x%llx target 0x%x length %d\n",
+                       (long long)lcn, (int)target, (int)length);
+       }
+       xsize = vol->indx_record_size;
+       indx = (INDEX_BLOCK*)buffer;
+       if (action->record.record_offset) {
+               printf("** Non-null record_offset in redo_write_index()\n");
+       }
+       if (optv > 1) {
+               printf("-> existing index :\n");
+               dump(&buffer[target], length);
+       }
+       if ((indx->magic == magic_INDX)
+           && !(length & 7)
+           && ((target + length) <= xsize)) {
+               /* This has to be an idempotent action */
+               changed = memcmp(buffer + target, data, length);
+               err = 0;
+               if (changed) {
+                       /* Update the entry */
+                       memcpy(buffer + target, data, length);
+                       /* If truncating, set the new size */
+                       indx->index.index_length =
+                                       cpu_to_le32(target + length - 0x18);
+                       if (optv > 1) {
+                               printf("-> new index :\n");
+                               dump(&buffer[target], length);
+                       }
+                       err = write_protected(vol, &action->record,
+                                       buffer, xsize);
+               }
+               if (optv > 1) {
+                       printf("-> INDX record %s\n",
+                               (changed ? "updated" : "unchanged"));
+               }
+       }
+       return (err);
+}
+
+static int undo_action37(ntfs_volume *vol __attribute__((unused)),
+                               const struct ACTION_RECORD *action,
+                               char *buffer __attribute__((unused)))
+{
+/*
+       const char *data;
+       u32 target;
+       u32 length;
+*/
+       int err;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+/*
+       data = ((const char*)&action->record)
+                       + get_redo_offset(&action->record);
+       length = le16_to_cpu(action->record.redo_length);
+       target = le16_to_cpu(action->record.record_offset)
+               + le16_to_cpu(action->record.attribute_offset);
+*/
+       printf("* Ignored action-37, inode %lld record :\n",
+                       (long long)inode_number(&action->record));
+       err = 0;
+       return (err);
+}
+
+static int undo_add_index(ntfs_volume *vol,
+                       const struct ACTION_RECORD *action, char *buffer)
+{
+       LCN lcn;
+       const char *data;
+       INDEX_BLOCK *indx;
+       u32 target;
+       u32 length;
+       u32 xsize;
+       u32 indexlth;
+       int err;
+       BOOL found;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       data = ((const char*)&action->record)
+                       + get_redo_offset(&action->record);
+       length = le16_to_cpu(action->record.redo_length);
+       target = le16_to_cpu(action->record.record_offset)
+               + le16_to_cpu(action->record.attribute_offset);
+       if (optv > 1) {
+               lcn = le64_to_cpu(action->record.lcn_list[0]);
+               printf("-> lcn 0x%llx target 0x%x length %d\n",
+                       (long long)lcn, (int)target, (int)length);
+       }
+       xsize = vol->indx_record_size;
+       indx = (INDEX_BLOCK*)(buffer
+                       + le16_to_cpu(action->record.record_offset));
+       if (optv > 1) {
+               printf("-> existing record :\n");
+               dump(&buffer[target], length);
+       }
+       if ((indx->magic == magic_INDX)
+           && !(length & 7)
+           && ((target + length) <= xsize)) {
+               /* This has to be an idempotent action */
+               found = index_match_undo(buffer + target, data, length);
+               err = 0;
+               if (found) {
+                       /* Remove the entry */
+                       memmove(buffer + target,
+                               buffer + target + length,
+                               xsize - target - length);
+                       indexlth = le32_to_cpu(indx->index.index_length)
+                                       - length;
+                       indx->index.index_length = cpu_to_le32(indexlth);
+                       err = write_protected(vol, &action->record,
+                                               buffer, xsize);
+               } else {
+                       sanity_indx(vol,buffer);
+                       printf("full record :\n");
+                       dump(buffer,xsize);
+               }
+               if (optv > 1) {
+                       printf("-> INDX record %s\n",
+                               (found ? "removed" : "unchanged")); 
+               }
+       }
+       return (err);
+}
+
+static int undo_add_root_index(ntfs_volume *vol,
+                       const struct ACTION_RECORD *action, char *buffer)
+{
+       LCN lcn;
+       const char *data;
+       ATTR_RECORD *attr;
+       MFT_RECORD *entry;
+       INDEX_ROOT *index;
+       BOOL found;
+       u32 target;
+       u32 length;
+       int err;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       data = ((const char*)&action->record)
+                       + get_redo_offset(&action->record);
+       length = le16_to_cpu(action->record.redo_length);
+       target = le16_to_cpu(action->record.record_offset)
+               + le16_to_cpu(action->record.attribute_offset);
+       if (optv > 1) {
+               lcn = le64_to_cpu(action->record.lcn_list[0]);
+               printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
+                       (long long)inode_number(&action->record),
+                       (long long)lcn, (int)target, (int)length);
+       }
+       entry = (MFT_RECORD*)buffer;
+       attr = (ATTR_RECORD*)(buffer
+                       + le16_to_cpu(action->record.record_offset));
+       index = (INDEX_ROOT*)(((char*)attr)
+                       + le16_to_cpu(attr->value_offset));
+       if (optv > 1) {
+               printf("existing index :\n");
+               dump(buffer + target,length);
+       }
+       if ((attr->type == AT_INDEX_ROOT)
+           && !(length & 7)
+           && ((target + length) <= mftrecsz)) {
+               /* This has to be an idempotent action */
+               found = index_match_undo(buffer + target, data, length);
+               err = 0;
+               if (found && !older_record(entry, &action->record)) {
+                       /* Remove the entry */
+                       memmove(buffer + target,
+                               buffer + target + length,
+                               mftrecsz - target - length);
+                       resize_attribute(entry, attr, index, -length, -length);
+                       if (optv > 1) {
+                               printf("new index at same location :\n");
+                               dump(buffer + target, length);
+                       }
+                       err = write_protected(vol, &action->record,
+                                               buffer, mftrecsz);
+               }
+               if (optv > 1) {
+                       printf("-> MFT record %s\n",
+                               (found ? "shrinked" : "unchanged"));
+               }
+       }
+       return (err);
+}
+
+static int undo_create_attribute(ntfs_volume *vol,
+                       const struct ACTION_RECORD *action, char *buffer)
+{
+       const char *data;
+       u32 target;
+       u32 length;
+       int err;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       data = ((const char*)&action->record)
+                       + get_redo_offset(&action->record);
+       length = le16_to_cpu(action->record.redo_length);
+       target = le16_to_cpu(action->record.record_offset)
+               + le16_to_cpu(action->record.attribute_offset);
+       if (!action->record.undo_length)
+               err = remove_resident(vol, action, buffer, data,
+                               target, length);
+       return (err);
+}
+
+static int undo_delete_attribute(ntfs_volume *vol,
+                       const struct ACTION_RECORD *action, char *buffer)
+{
+       const char *data;
+       u32 target;
+       u32 length;
+       int err;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       data = ((const char*)&action->record)
+                       + get_undo_offset(&action->record);
+       length = le16_to_cpu(action->record.undo_length);
+       target = le16_to_cpu(action->record.record_offset)
+               + le16_to_cpu(action->record.attribute_offset);
+       if (!action->record.redo_length)
+               err = insert_resident(vol, action, buffer, data,
+                               target, length);
+       return (err);
+}
+
+static int undo_delete_index(ntfs_volume *vol,
+                       const struct ACTION_RECORD *action, char *buffer)
+{
+       LCN lcn;
+       const char *data;
+       INDEX_BLOCK *indx;
+       u32 target;
+       u32 length;
+       u32 xsize;
+       u32 indexlth;
+       int err;
+       BOOL found;
+
+// MERGE with redo_add_root_index() ?
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       data = ((const char*)&action->record)
+                       + get_undo_offset(&action->record);
+       length = le16_to_cpu(action->record.undo_length);
+       target = le16_to_cpu(action->record.record_offset)
+               + le16_to_cpu(action->record.attribute_offset);
+       if (optv > 1) {
+               lcn = le64_to_cpu(action->record.lcn_list[0]);
+               printf("-> lcn 0x%llx target 0x%x length %d\n",
+                       (long long)lcn, (int)target, (int)length);
+       }
+       xsize = vol->indx_record_size;
+       indx = (INDEX_BLOCK*)(buffer
+                       + le16_to_cpu(action->record.record_offset));
+       if (optv > 1) {
+               printf("-> existing record :\n");
+               dump(&buffer[target], length);
+       }
+       if ((indx->magic == magic_INDX)
+           && !(length & 7)
+           && ((target + length) <= xsize)
+           && !sanity_indx(vol,buffer)) {
+               /* This has to be an idempotent action */
+               found = !memcmp(buffer + target, data, length);
+               err = 0;
+               if (!found) {
+                       /* Make space to insert the entry */
+                       memmove(buffer + target + length,
+                               buffer + target,
+                               xsize - target - length);
+                       memcpy(buffer + target, data, length);
+                       indexlth = le32_to_cpu(indx->index.index_length)
+                                       + length;
+                       indx->index.index_length = cpu_to_le32(indexlth);
+                       if (optv > 1) {
+                               printf("-> inserted record :\n");
+                               dump(&buffer[target], length);
+                       }
+                       /* rebuildname() has no effect currently, should drop */
+                       rebuildname((const INDEX_ENTRY*)data);
+                       err = write_protected(vol, &action->record,
+                                               buffer, xsize);
+               }
+               if (optv > 1) {
+                       printf("-> INDX record %s\n",
+                               (found ? "unchanged" : "inserted"));
+               }
+       }
+       return (err);
+}
+
+static int undo_delete_root_index(ntfs_volume *vol,
+                       const struct ACTION_RECORD *action, char *buffer)
+{
+       LCN lcn;
+       const char *data;
+       ATTR_RECORD *attr;
+       MFT_RECORD *entry;
+       INDEX_ROOT *index;
+       u32 target;
+       u32 length;
+       int err;
+       BOOL found;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       data = ((const char*)&action->record)
+                       + get_undo_offset(&action->record);
+       length = le16_to_cpu(action->record.undo_length);
+       target = le16_to_cpu(action->record.record_offset)
+               + le16_to_cpu(action->record.attribute_offset);
+       if (optv > 1) {
+               lcn = le64_to_cpu(action->record.lcn_list[0]);
+               printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
+                       (long long)inode_number(&action->record),
+                       (long long)lcn, (int)target, (int)length);
+       }
+       entry = (MFT_RECORD*)buffer;
+       attr = (ATTR_RECORD*)(buffer
+                       + le16_to_cpu(action->record.record_offset));
+       index = (INDEX_ROOT*)(((char*)attr)
+                       + le16_to_cpu(attr->value_offset));
+       if (attr->type != AT_INDEX_ROOT) {
+               printf("** Unexpected attr type 0x%lx\n",
+                               (long)le32_to_cpu(attr->type));
+               printf("existing mft\n");
+               dump((char*)buffer,512);
+               printf("existing index\n");
+               dump(buffer + target,length);
+       }
+       if (optv > 1) {
+               printf("existing index :\n");
+               dump(buffer + target,length);
+       }
+       if ((attr->type == AT_INDEX_ROOT)
+           && !(length & 7)
+           && ((target + length) <= mftrecsz)) {
+               /* This has to be an idempotent action */
+               found = !memcmp(buffer + target, data, length);
+               err = 0;
+                       /* Do not insert if present */
+               if (!found) {
+                       /* Make space to insert the entry */
+                       memmove(buffer + target + length,
+                               buffer + target,
+                               mftrecsz - target - length);
+                       memcpy(buffer + target, data, length);
+                       resize_attribute(entry, attr, index, length, length);
+                       if (optv > 1) {
+                               printf("new index :\n");
+                               dump(buffer + target, length);
+                       }
+                       err = write_protected(vol, &action->record,
+                                               buffer, mftrecsz);
+               }
+               if (optv > 1) {
+                       printf("-> MFT record %s\n",
+                               (found ? "unchanged" : "expanded"));
+               }
+       }
+       return (err);
+}
+
+static int undo_create_file(ntfs_volume *vol,
+                       const struct ACTION_RECORD *action, char *buffer)
+{
+       LCN lcn;
+       const char *data;
+       MFT_RECORD *record;
+       u32 target;
+       u32 length;
+       int err;
+       int changed;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+               /* redo initialize, clearing the in_use flag ? */
+       data = ((const char*)&action->record)
+                       + get_redo_offset(&action->record);
+       length = le16_to_cpu(action->record.redo_length);
+       target = le16_to_cpu(action->record.record_offset)
+               + le16_to_cpu(action->record.attribute_offset);
+       if (optv > 1) {
+               lcn = le64_to_cpu(action->record.lcn_list[0]);
+               printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
+                       (long long)inode_number(&action->record),
+                       (long long)lcn, (int)target, (int)length);
+       }
+       record = (MFT_RECORD*)buffer;
+       if (optv > 1) {
+               printf("-> existing record :\n");
+               dump(buffer,mftrecsz);
+       }
+       if ((target + length) <= mftrecsz) {
+               changed = memcmp(buffer + target, data, length);
+               err = 0;
+               if (changed || (record->flags & MFT_RECORD_IN_USE)) {
+                       memcpy(buffer + target, data, length);
+                       record->flags &= ~MFT_RECORD_IN_USE;
+                       if (optv > 1) {
+                               printf("-> new record :\n");
+                               dump(buffer,mftrecsz);
+                       }
+                       err = write_protected(vol, &action->record,
+                                               buffer, mftrecsz);
+               }
+               if (optv > 1) {
+                       printf("-> MFT record %s\n",
+                               (changed ? "updated" : "unchanged"));
+               }
+       }
+       return (err);
+}
+
+static int undo_delete_file(ntfs_volume *vol,
+                       const struct ACTION_RECORD *action, char *buffer)
+{
+       LCN lcn;
+       const char *data;
+       MFT_RECORD *record;
+       u32 target;
+       u32 length;
+       int err;
+       int changed;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       data = ((const char*)&action->record)
+                       + get_undo_offset(&action->record);
+       length = le16_to_cpu(action->record.undo_length);
+       target = le16_to_cpu(action->record.record_offset)
+               + le16_to_cpu(action->record.attribute_offset);
+       if (optv > 1) {
+               lcn = le64_to_cpu(action->record.lcn_list[0]);
+               printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
+                       (long long)inode_number(&action->record),
+                       (long long)lcn, (int)target, (int)length);
+       }
+       if (optv > 1) {
+               printf("-> existing record :\n");
+               dump(buffer,mftrecsz);
+       }
+       record = (MFT_RECORD*)buffer;
+       if ((target + length) <= mftrecsz) {
+               changed = memcmp(buffer + target, data, length)
+                       || !(record->flags & MFT_RECORD_IN_USE);
+               err = 0;
+               if (changed) {
+                       memcpy(buffer + target, data, length);
+               /*
+                * Unclear what we should do for recreating a file.
+                * Only 24 bytes are available, the used length is not known,
+                * the number of links suggests we should keep the current
+                * names... If so, when will they be deleted ?
+                * We will have to make stamp changes in the standard
+                * information attribute, so better not to delete it.
+                * Should we create a data or index attribute ?
+                * Here, we assume we should delete the file names when
+                * the record now appears to not be in use and there are
+                * links.
+                */
+                       if (record->link_count
+                           && !(record->flags & MFT_RECORD_IN_USE))
+                               err = delete_names(buffer);
+                       record->flags |= MFT_RECORD_IN_USE;
+                       if (optv > 1) {
+                               printf("-> new record :\n");
+                               dump(buffer,mftrecsz);
+                       }
+                       if (!err)
+                               err = write_protected(vol,
+                                       &action->record,
+                                       buffer, mftrecsz);
+               }
+               if (optv > 1) {
+                       printf("-> MFT record %s\n",
+                               (changed ? "updated" : "unchanged"));
+               }
+       }
+       return (err);
+}
+
+static int undo_force_bits(ntfs_volume *vol,
+                       const struct ACTION_RECORD *action, char *buffer)
+{
+       LCN lcn;
+       const struct BITMAP_ACTION *data;
+       u32 i;
+       int err;
+       int wanted;
+       u32 firstbit;
+       u32 count;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       data = (const struct BITMAP_ACTION*)
+                       (((const char*)&action->record)
+                               + get_redo_offset(&action->record));
+       firstbit = le32_to_cpu(data->firstbit);
+       count = le32_to_cpu(data->count);
+       if (action->record.redo_operation
+                       == const_cpu_to_le16(SetBitsInNonResidentBitMap))
+               wanted = 0;
+       else
+               wanted = 1;
+// TODO consistency undo_offset == redo_offset, etc.
+// firstbit + count < 8*clustersz (multiple clusters possible ?)
+       if (optv > 1) {
+               lcn = le64_to_cpu(action->record.lcn_list[0]);
+               printf("-> lcn 0x%llx firstbit %d count %d wanted %d\n",
+                       (long long)lcn,(int)firstbit,(int)count,(int)wanted);
+       }
+       for (i=0; i<count; i++)
+               ntfs_bit_set((u8*)buffer, firstbit + i, wanted);
+       if (!write_raw(vol, &action->record, buffer)) {
+               err = 0;
+               if (optv > 1)
+                       printf("-> record updated\n");
+       }
+       if (err)
+               printf("** redo_clearbits failed\n");
+       return (err);
+}
+
+static int undo_open_attribute(ntfs_volume *vol __attribute__((unused)),
+                               const struct ACTION_RECORD *action)
+{
+       const char *data;
+       struct ATTR *pa;
+       const struct ATTR_OLD *attr_old;
+       const struct ATTR_NEW *attr_new;
+       const char *name;
+       le64 inode;
+       u32 namelen;
+       u32 length;
+       u32 extra;
+       int err;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       data = ((const char*)&action->record)
+                       + get_redo_offset(&action->record);
+       length = le16_to_cpu(action->record.redo_length);
+       extra = get_extra_offset(&action->record);
+       if (action->record.undo_length) {
+               name = ((const char*)&action->record) + extra;
+               namelen = le32_to_cpu(action->record.client_data_length)
+                               + LOG_RECORD_HEAD_SZ - extra;
+               /* fix namelen which was aligned modulo 8 */
+               namelen = fixnamelen(name, namelen);
+               if (optv > 1) {
+                       printf("-> length %d namelen %d",(int)length,
+                                                       (int)namelen);
+                       showname(", ", name, namelen/2);
+               }
+       } else {
+               namelen = 0;
+               name = "";
+       }
+       pa = getattrentry(le16_to_cpu(action->record.target_attribute),0);
+// TODO Only process is attr is not older ?
+       if (pa) {
+               /* check whether the redo attr matches what we have in store */
+               switch (length) {
+               case sizeof(struct ATTR_OLD) :
+                       attr_old = (const struct ATTR_OLD*)data;
+                               /* Badly aligned */
+                       memcpy(&inode, &attr_old->inode, 8);
+                       err = (MREF(le64_to_cpu(inode)) != pa->inode)
+                           || (attr_old->type != pa->type);
+                       break;
+               case sizeof(struct ATTR_NEW) :
+                       attr_new = (const struct ATTR_NEW*)data;
+                       err = (MREF(le64_to_cpu(attr_new->inode))!= pa->inode)
+                           || (attr_new->type != pa->type);
+                       break;
+               default : err = 1;
+               }
+               if (!err) {
+                       err = (namelen != pa->namelen)
+                               || (namelen
+                                   && memcmp(name, pa->name, namelen));
+               }
+               if (optv > 1)
+                       printf("-> attribute %s the recorded one\n",
+                               (err ? "does not match" : "matches"));
+       } else
+               if (optv)
+                       printf("* Unrecorded attribute\n");
+err = 0;
+       return (err);
+}
+
+static int undo_sizes(ntfs_volume *vol,
+                       const struct ACTION_RECORD *action, char *buffer)
+{
+       const char *data;
+       MFT_RECORD *entry;
+       ATTR_RECORD *attr;
+       u32 target;
+       u32 length;
+       u32 offs;
+       int err;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       data = ((const char*)&action->record)
+                       + get_undo_offset(&action->record);
+       length = le16_to_cpu(action->record.undo_length);
+       target = le16_to_cpu(action->record.record_offset)
+               + le16_to_cpu(action->record.attribute_offset)
+               + offsetof(ATTR_RECORD, allocated_size);
+       entry = (MFT_RECORD*)buffer;
+       if (!(entry->flags & MFT_RECORD_IS_DIRECTORY))
+               err = change_resident(vol, action, buffer,
+                       data, target, length);
+       else {
+               /* On a directory, may have to build an index allocation */
+               offs = le16_to_cpu(action->record.record_offset);
+               attr = (ATTR_RECORD*)(buffer + offs);
+               if (attr->type != AT_INDEX_ALLOCATION) {
+                       err = insert_index_allocation(vol, buffer, offs);
+                       if (!err)
+                               err = change_resident(vol, action, buffer,
+                                               data, target, length);
+               } else
+                       err = change_resident(vol, action, buffer,
+                                       data, target, length);
+       }
+       return (err);
+}
+
+static int undo_update_index(ntfs_volume *vol, const struct ACTION_RECORD *action,
+                       char *buffer)
+{
+       const char *data;
+       u32 target;
+       u32 length;
+       int err;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       data = ((const char*)&action->record)
+                       + get_undo_offset(&action->record);
+       length = le16_to_cpu(action->record.undo_length);
+                       /* target is left-justified to creation time */
+       target = le16_to_cpu(action->record.record_offset)
+               + le16_to_cpu(action->record.attribute_offset)
+               + offsetof(INDEX_ENTRY, key.file_name.creation_time);
+       err = update_index(vol, action, buffer, data, target, length);
+       return (err);
+}
+
+static int undo_update_index_value(ntfs_volume *vol,
+                       const struct ACTION_RECORD *action, char *buffer)
+{
+       LCN lcn;
+       const char *data;
+       u32 length;
+       u32 target;
+       int changed;
+       int err;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       data = ((const char*)&action->record)
+                               + get_undo_offset(&action->record);
+       length = le16_to_cpu(action->record.undo_length);
+       target = le16_to_cpu(action->record.record_offset)
+               + le16_to_cpu(action->record.attribute_offset);
+       if (optv > 1) {
+               lcn = le64_to_cpu(action->record.lcn_list[0]);
+               printf("-> lcn 0x%llx target 0x%x length %d\n",
+                       (long long)lcn, (int)target, (int)length);
+       }
+       if (optv > 1) {
+               printf("-> existing record :\n");
+               dump(&buffer[target], length);
+       }
+       if ((target + length) <= vol->indx_record_size) {
+               changed = length && memcmp(buffer + target, data, length);
+               err = 0;
+               if (changed) {
+                       memcpy(buffer + target, data, length);
+                       if (optv > 1) {
+                               printf("-> new record :\n");
+                               dump(buffer + target, length);
+                       }
+                       err = write_protected(vol, &action->record, buffer,
+                                               vol->indx_record_size);
+               }
+               if (optv > 1) {
+                       printf("-> data record %s\n",
+                               (changed ? "updated" : "unchanged"));
+               }
+       }
+       return (err);
+}
+
+static int undo_update_vcn(ntfs_volume *vol, const struct ACTION_RECORD *action,
+                       char *buffer)
+{
+       const char *data;
+       u32 target;
+       u32 length;
+       int err;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       data = ((const char*)&action->record)
+                       + get_undo_offset(&action->record);
+       length = le16_to_cpu(action->record.undo_length);
+                       /* target is left-justified to creation time */
+       target = le16_to_cpu(action->record.record_offset)
+               + le16_to_cpu(action->record.attribute_offset)
+               + 16; // to better describe
+       err = update_index(vol, action, buffer, data, target, length);
+       return (err);
+}
+
+static int undo_update_mapping(ntfs_volume *vol, const struct ACTION_RECORD *action,
+                       char *buffer)
+{
+       LCN lcn;
+       const char *data;
+       ATTR_RECORD *attr;
+       MFT_RECORD *entry;
+       u32 target;
+       u32 length;
+       u32 source;
+       u32 alen;
+       u32 newused;
+       int err;
+       int changed;
+       int resize;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       data = ((const char*)&action->record)
+                       + get_undo_offset(&action->record);
+       length = le16_to_cpu(action->record.undo_length);
+       resize = length - le16_to_cpu(action->record.redo_length);
+       target = le16_to_cpu(action->record.record_offset)
+               + le16_to_cpu(action->record.attribute_offset);
+       if (optv > 1) {
+               lcn = le64_to_cpu(action->record.lcn_list[0]);
+               printf("-> inode %lld lcn 0x%llx target 0x%x new length %d resize %d\n",
+                       (long long)inode_number(&action->record),
+                       (long long)lcn, (int)target, (int)length, (int)resize);
+       }
+// TODO share with redo_update_mapping()
+       if (optv > 1) {
+               printf("-> existing record :\n");
+               dump(&buffer[target], length);
+       }
+       entry = (MFT_RECORD*)buffer;
+       attr = (ATTR_RECORD*)(buffer
+                       + le16_to_cpu(action->record.record_offset));
+       if (!attr->non_resident) {
+               printf("** Error : update_mapping on resident attr\n");
+       }
+       if (valid_type(attr->type)
+           && attr->non_resident
+           && !(resize & 7)
+           && ((target + length) <= mftrecsz)) {
+               changed = memcmp(buffer + target, data, length);
+               err = 0;
+               if (changed) {
+                       /* Adjust space for new mapping pairs */
+                       source = target - resize;
+                       if (resize > 0) {
+                               memmove(buffer + target + length,
+                                       buffer + source + length,
+                                       mftrecsz - target - length);
+                       }
+                       if (resize < 0) {
+                               memmove(buffer + target + length,
+                                       buffer + source + length,
+                                       mftrecsz - source - length);
+                       }
+                       memcpy(buffer + target, data, length);
+                               /* Resize the attribute */
+                       alen = le32_to_cpu(attr->length) + resize;
+                       attr->length = cpu_to_le32(alen);
+                               /* Resize the mft record */
+                       newused = le32_to_cpu(entry->bytes_in_use)
+                                       + resize;
+                       entry->bytes_in_use = cpu_to_le32(newused);
+                               /* Compute the new highest_vcn */
+                       err = adjust_high_vcn(vol, attr);
+                       if (optv > 1) {
+                               printf("-> new record :\n");
+                               dump(buffer + target, length);
+                       }
+                       if (!err) {
+                               err = write_protected(vol,
+                                       &action->record, buffer,
+                                       mftrecsz);
+                       }
+               }
+               if (optv > 1) {
+                       printf("-> MFT record %s\n",
+                               (changed ? "updated" : "unchanged"));
+               }
+       }
+       return (err);
+}
+
+static int undo_update_resident(ntfs_volume *vol,
+                       const struct ACTION_RECORD *action, char *buffer)
+{
+       LCN lcn;
+       const char *data;
+       u32 target;
+       u32 length;
+       u32 oldlength;
+       u32 end;
+       u32 undo;
+       int err;
+       int changed;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       end = le32_to_cpu(action->record.client_data_length)
+                       + LOG_RECORD_HEAD_SZ;
+       length = le16_to_cpu(action->record.undo_length);
+       undo = get_undo_offset(&action->record);
+       if ((undo + length) > end)
+               data = (char*)NULL;
+       else
+               data = ((const char*)&action->record) + undo;
+       oldlength = le16_to_cpu(action->record.redo_length);
+       target = le16_to_cpu(action->record.record_offset)
+               + le16_to_cpu(action->record.attribute_offset);
+       if (length == oldlength) {
+               if (optv > 1) {
+                       lcn = le64_to_cpu(action->record.lcn_list[0]);
+                       printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
+                               (long long)inode_number(&action->record),
+                               (long long)lcn, (int)target, (int)length);
+               }
+               if (optv > 1) {
+                       printf("-> existing record :\n");
+                       dump(&buffer[target], length);
+               }
+               if ((target + length) <= mftrecsz) {
+                       changed = memcmp(buffer + target, data, length);
+                       err = 0;
+                       if (changed) {
+                               memcpy(buffer + target, data, length);
+                               if (optv > 1) {
+                                       printf("-> new record :\n");
+                                       dump(buffer + target, length);
+                               }
+                               err = write_protected(vol, &action->record,
+                                               buffer, mftrecsz);
+                       }
+                       if (optv > 1) {
+                               printf("-> MFT record %s\n",
+                                       (changed ? "updated" : "unchanged"));
+                       }
+               }
+       } else {
+               if (length > oldlength)
+                       err = expand_resident(vol, action, buffer, data,
+                                       target, length, oldlength);
+               else
+                       err = shrink_resident(vol, action, buffer, data,
+                                       target, length, oldlength);
+       }
+       return (err);
+}
+
+static int undo_update_root_index(ntfs_volume *vol,
+                       const struct ACTION_RECORD *action, char *buffer)
+{
+       const char *data;
+       const char *expected;
+       u32 target;
+       u32 length;
+       int err;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       data = ((const char*)&action->record)
+                       + get_undo_offset(&action->record);
+       expected = ((const char*)&action->record)
+                       + get_redo_offset(&action->record);
+       length = le16_to_cpu(action->record.undo_length);
+               /* the fixup is right-justified to the name length */
+       target = le16_to_cpu(action->record.record_offset)
+               + le16_to_cpu(action->record.attribute_offset)
+               + offsetof(INDEX_ENTRY, key.file_name.file_name_length)
+               - length;
+       err = change_resident_expect(vol, action, buffer, data, expected,
+                       target, length, AT_INDEX_ROOT);
+       return (err);
+}
+
+static int undo_update_root_vcn(ntfs_volume *vol,
+                       const struct ACTION_RECORD *action, char *buffer)
+{
+       const char *data;
+       const char *expected;
+       u32 target;
+       u32 length;
+       int err;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       data = ((const char*)&action->record)
+                       + get_undo_offset(&action->record);
+       expected = ((const char*)&action->record)
+                       + get_redo_offset(&action->record);
+       length = le16_to_cpu(action->record.undo_length);
+               /* the fixup is right-justified to the name length */
+       target = le16_to_cpu(action->record.record_offset)
+               + le16_to_cpu(action->record.attribute_offset)
+               + 16; // explanation needed
+       err = change_resident_expect(vol, action, buffer, data, expected,
+                       target, length, AT_INDEX_ROOT);
+       return (err);
+}
+
+static int undo_update_value(ntfs_volume *vol,
+                       const struct ACTION_RECORD *action, char *buffer)
+{
+       LCN lcn;
+       const char *data;
+       u32 length;
+       u32 target;
+       u32 count;
+       int changed;
+       int err;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       data = ((const char*)&action->record)
+                               + get_undo_offset(&action->record);
+       length = le16_to_cpu(action->record.undo_length);
+       target = le16_to_cpu(action->record.record_offset)
+               + le16_to_cpu(action->record.attribute_offset);
+       count = le16_to_cpu(action->record.lcns_to_follow);
+       if (optv > 1) {
+               lcn = le64_to_cpu(action->record.lcn_list[0]);
+               printf("-> lcn 0x%llx target 0x%x length %d\n",
+                       (long long)lcn, (int)target, (int)length);
+       }
+       if (length) {
+               if (optv > 1) {
+                       printf("-> existing record :\n");
+                       dump(&buffer[target], length);
+               }
+               if ((target + length) <= (count << clusterbits)) {
+                       changed = memcmp(buffer + target, data, length);
+                       err = 0;
+                       if (changed) {
+                               memcpy(buffer + target, data, length);
+                               if (optv > 1) {
+                                       printf("-> new record :\n");
+                                       dump(buffer + target, length);
+                               }
+                               err = write_raw(vol, &action->record, buffer);
+                       }
+                       if (optv > 1) {
+                               printf("-> data record %s\n",
+                                       (changed ? "updated" : "unchanged"));
+                       }
+               }
+       } else {
+               /*
+                * No undo data, we cannot undo, sometimes the redo
+                * data even overflows from record.
+                * Just ignore for now.
+                */
+               if (optv)
+                       printf("Cannot undo, there is no undo data\n");
+               err = 0;
+       }
+
+       return (err);
+}
+
+static int undo_write_end(ntfs_volume *vol,
+                       const struct ACTION_RECORD *action, char *buffer)
+{
+       LCN lcn;
+       const char *data;
+       u32 target;
+       u32 length;
+       u32 oldlength;
+       u32 end;
+       u32 undo;
+       int err;
+       int changed;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       end = le32_to_cpu(action->record.client_data_length)
+                       + LOG_RECORD_HEAD_SZ;
+       length = le16_to_cpu(action->record.undo_length);
+       undo = get_undo_offset(&action->record);
+       if ((undo + length) > end)
+               data = (char*)NULL;
+       else
+               data = ((const char*)&action->record) + undo;
+       oldlength = le16_to_cpu(action->record.redo_length);
+       target = le16_to_cpu(action->record.record_offset)
+               + le16_to_cpu(action->record.attribute_offset);
+       if (length == oldlength) {
+               if (optv > 1) {
+                       lcn = le64_to_cpu(action->record.lcn_list[0]);
+                       printf("-> inode %lld lcn 0x%llx target 0x%x"
+                               " length %d\n",
+                               (long long)inode_number(&action->record),
+                               (long long)lcn, (int)target, (int)length);
+               }
+               if (optv > 1) {
+                       printf("-> existing record :\n");
+                       dump(&buffer[target], length);
+               }
+               if ((target + length) <= mftrecsz) {
+                       changed = memcmp(buffer + target, data, length);
+                       err = 0;
+                       if (changed) {
+                               memcpy(buffer + target, data, length);
+                               if (optv > 1) {
+                                       printf("-> new record :\n");
+                                       dump(buffer + target, length);
+                               }
+                               err = write_protected(vol, &action->record,
+                                               buffer, mftrecsz);
+                       }
+                       if (optv > 1) {
+                               printf("-> MFT record %s\n",
+                                       (changed ? "updated" : "unchanged"));
+                       }
+               }
+       } else {
+               if (length > oldlength)
+                       err = add_resident(vol, action, buffer, data,
+                                       target, length, oldlength);
+               else
+                       err = delete_resident(vol, action, buffer, data,
+                                       target, length, oldlength);
+       }
+       return (err);
+}
+
+static int undo_write_index(ntfs_volume *vol,
+                       const struct ACTION_RECORD *action, char *buffer)
+{
+       LCN lcn;
+       const char *data;
+       u32 target;
+       u32 length;
+       u32 oldlength;
+       u32 end;
+       u32 undo;
+       int err;
+       int changed;
+
+       if (optv > 1)
+               printf("-> %s()\n",__func__);
+       err = 1;
+       end = le32_to_cpu(action->record.client_data_length)
+                       + LOG_RECORD_HEAD_SZ;
+       length = le16_to_cpu(action->record.undo_length);
+       undo = get_undo_offset(&action->record);
+       if ((undo + length) > end)
+               data = (char*)NULL;
+       else
+               data = ((const char*)&action->record) + undo;
+       oldlength = le16_to_cpu(action->record.redo_length);
+       target = le16_to_cpu(action->record.record_offset)
+               + le16_to_cpu(action->record.attribute_offset);
+       if (length == oldlength) {
+               if (optv > 1) {
+                       lcn = le64_to_cpu(action->record.lcn_list[0]);
+                       printf("-> inode %lld lcn 0x%llx target 0x%x"
+                               " length %d\n",
+                               (long long)inode_number(&action->record),
+                               (long long)lcn, (int)target, (int)length);
+               }
+               if (optv > 1) {
+                       printf("-> existing record :\n");
+                       dump(&buffer[target], length);
+               }
+               if ((target + length) <= mftrecsz) {
+                       changed = memcmp(buffer + target, data, length);
+                       err = 0;
+                       if (changed) {
+                               memcpy(buffer + target, data, length);
+                               if (optv > 1) {
+                                       printf("-> new record :\n");
+                                       dump(buffer + target, length);
+                               }
+                               err = write_protected(vol, &action->record,
+                                               buffer, mftrecsz);
+                       }
+                       if (optv > 1) {
+                               printf("-> MFT record %s\n",
+                                       (changed ? "updated" : "unchanged"));
+                       }
+               }
+       } else {
+               if (length > oldlength)
+                       err = add_non_resident(/*vol, action, data,
+                                       target, length, oldlength*/);
+               else
+                       err = delete_non_resident(/*vol, action, data,
+                                       target, length, oldlength*/);
+       }
+       return (err);
+}
+
+enum ACTION_KIND { ON_NONE, ON_MFT, ON_INDX, ON_RAW } ;
+
+static enum ACTION_KIND get_action_kind(const struct ACTION_RECORD *action)
+{
+       struct ATTR *pa;
+       const char *data;
+       enum ACTION_KIND kind;
+               /*
+                * If we are sure the action was defined by Vista
+                * or subsequent, just use attribute_flags.
+                * Unfortunately, only on some cases we can determine
+                * the action was defined by Win10 (or subsequent).
+                */
+       if (action->record.log_record_flags
+                       & const_cpu_to_le16(RECORD_DELETING | RECORD_ADDING)) {
+               if (action->record.attribute_flags
+                                       & const_cpu_to_le16(ACTS_ON_INDX))
+                       kind = ON_INDX;
+               else
+                       if (action->record.attribute_flags
+                                       & const_cpu_to_le16(ACTS_ON_MFT))
+                               kind = ON_MFT;
+                       else
+                               kind = ON_RAW;
+       } else {
+               /*
+                * In other cases, we have to rely on the attribute
+                * definition, but this has defects when undoing.
+                */
+               pa = getattrentry(le16_to_cpu(
+                                       action->record.target_attribute),0);
+               if (!pa || !pa->type) {
+               /*
+                * Even when the attribute has not been recorded,
+                * we can sometimes tell the record does not apply
+                * to MFT or INDX : such records always have a zero
+                * record_offset, and if attribute_offset is zero, their
+                * magic can be checked. If neither condition is true,
+                * the action cannot apply to MFT or INDX.
+                * (this is useful for undoing)
+                */
+                       data = (const char*)&action->record
+                               + get_redo_offset(&action->record);
+                       if (action->record.record_offset
+                           || (!action->record.attribute_offset
+                               && (le16_to_cpu(action->record.redo_length)
+                                                                       >= 4)
+                               && memcmp(data,"FILE",4)
+                               && memcmp(data,"INDX",4))) {
+                                       kind = ON_RAW;
+                       } else {
+                               printf("** Error : attribute 0x%x"
+                                       " is not defined\n",
+                                       (int)le16_to_cpu(
+                                       action->record.target_attribute));
+                               kind = ON_NONE;
+                       }
+               } else {
+                       if (pa->type == AT_INDEX_ALLOCATION)
+                               kind = ON_INDX;
+                       else
+                               kind = ON_RAW;
+               }
+       }
+       return (kind);
+}
+
+
+/*
+ *             Display the redo actions which were executed
+ *
+ *     Useful for getting indications on the coverage of a test
+ */
+
+void show_redos(void)
+{
+       int i;
+
+       if (optv && redos_met) {
+               printf("Redo actions which were executed :\n");
+               for (i=0; i<64; i++)
+                       if ((((u64)1) << i) & redos_met)
+                               printf("%s\n", actionname(i));
+       }
+}
+
+static int distribute_redos(ntfs_volume *vol,
+                       const struct ACTION_RECORD *action, char *buffer)
+{
+       int rop, uop;
+       int err;
+
+       err = 0;
+       rop = le16_to_cpu(action->record.redo_operation);
+       uop = le16_to_cpu(action->record.undo_operation);
+       switch (rop) {
+       case AddIndexEntryAllocation :
+               if (action->record.undo_operation
+                   == const_cpu_to_le16(DeleteIndexEntryAllocation))
+                       err = redo_add_index(vol, action, buffer);
+               break;
+       case AddIndexEntryRoot :
+               if (action->record.undo_operation
+                       == const_cpu_to_le16(DeleteIndexEntryRoot))
+                       err = redo_add_root_index(vol, action, buffer);
+               break;
+       case ClearBitsInNonResidentBitMap :
+               if (action->record.undo_operation
+                   == const_cpu_to_le16(SetBitsInNonResidentBitMap))
+                       err = redo_force_bits(vol, action, buffer);
+               break;
+       case CompensationlogRecord :
+               if (action->record.undo_operation
+                   == const_cpu_to_le16(Noop))
+                       err = redo_compensate(vol, action, buffer);
+               break;
+       case CreateAttribute :
+               if (action->record.undo_operation
+                   == const_cpu_to_le16(DeleteAttribute))
+                       err = redo_create_attribute(vol, action, buffer);
+               break;
+       case DeallocateFileRecordSegment :
+               if (action->record.undo_operation
+                       == const_cpu_to_le16(InitializeFileRecordSegment))
+                       err = redo_delete_file(vol, action, buffer);
+               break;
+       case DeleteAttribute :
+               if (action->record.undo_operation
+                   == const_cpu_to_le16(CreateAttribute))
+                       err = redo_delete_attribute(vol, action, buffer);
+               break;
+       case DeleteIndexEntryAllocation :
+               if (action->record.undo_operation
+                   == const_cpu_to_le16(AddIndexEntryAllocation))
+                       err = redo_delete_index(vol, action, buffer);
+               break;
+       case DeleteIndexEntryRoot :
+               if (action->record.undo_operation
+                       == const_cpu_to_le16(AddIndexEntryRoot))
+                       err = redo_delete_root_index(vol, action, buffer);
+               break;
+       case InitializeFileRecordSegment :
+               if (action->record.undo_operation
+                       == const_cpu_to_le16(Noop))
+                       err = redo_create_file(vol, action, buffer);
+               break;
+       case OpenNonResidentAttribute :
+               if (action->record.undo_operation
+                       == const_cpu_to_le16(Noop))
+                       err = redo_open_attribute(vol, action);
+               break;
+       case SetBitsInNonResidentBitMap :
+               if (action->record.undo_operation
+                   == const_cpu_to_le16(ClearBitsInNonResidentBitMap))
+                       err = redo_force_bits(vol, action, buffer);
+               break;
+       case SetIndexEntryVcnAllocation :
+               if (action->record.undo_operation
+                   == const_cpu_to_le16(SetIndexEntryVcnAllocation))
+                       err = redo_update_vcn(vol, action, buffer);
+               break;
+       case SetIndexEntryVcnRoot :
+               if (action->record.undo_operation
+                       == const_cpu_to_le16(SetIndexEntryVcnRoot))
+                       err = redo_update_root_vcn(vol, action, buffer);
+               break;
+       case SetNewAttributeSizes :
+               if (action->record.undo_operation
+                   == const_cpu_to_le16(SetNewAttributeSizes))
+                       err = redo_sizes(vol, action, buffer);
+               break;
+       case UpdateFileNameAllocation :
+               if (action->record.undo_operation
+                   == const_cpu_to_le16(UpdateFileNameAllocation))
+                       err = redo_update_index(vol, action, buffer);
+               break;
+       case UpdateFileNameRoot :
+               if (action->record.undo_operation
+                       == const_cpu_to_le16(UpdateFileNameRoot))
+                       err = redo_update_root_index(vol, action, buffer);
+               break;
+       case UpdateMappingPairs :
+               if (action->record.undo_operation
+                   == const_cpu_to_le16(UpdateMappingPairs))
+                       err = redo_update_mapping(vol, action, buffer);
+               break;
+       case UpdateNonResidentValue :
+               switch (get_action_kind(action)) {
+               case ON_INDX :
+                       err = redo_update_index_value(vol, action, buffer);
+                       break;
+               case ON_RAW :
+                       err = redo_update_value(vol, action, buffer);
+                       break;
+               default :
+                       printf("** Bad attribute type\n");
+                       err = 1;
+               }
+               break;
+       case UpdateResidentValue :
+               if (action->record.undo_operation
+                   == const_cpu_to_le16(UpdateResidentValue))
+                       err = redo_update_resident(vol, action, buffer);
+               break;
+       case Win10Action37 :
+               if (action->record.undo_operation
+                   == const_cpu_to_le16(Noop))
+                       err = redo_action37(vol, action, buffer);
+               break;
+       case WriteEndofFileRecordSegment :
+               if (action->record.undo_operation
+                   == const_cpu_to_le16(WriteEndofFileRecordSegment))
+                       err = redo_write_end(vol, action, buffer);
+               break;
+       case WriteEndOfIndexBuffer :
+               if (action->record.undo_operation
+                   == const_cpu_to_le16(WriteEndOfIndexBuffer))
+                       err = redo_write_index(vol, action, buffer);
+               break;
+       case AttributeNamesDump :
+       case DirtyPageTableDump :
+       case ForgetTransaction :
+       case Noop :
+       case OpenAttributeTableDump :
+               break;
+       default :
+               printf("** Unsupported redo %s\n", actionname(rop));
+               err = 1;
+               break;
+       }
+       redos_met |= ((u64)1) << rop;
+       if (err)
+               printf("* Redoing action %d %s (%s) failed\n",
+                       action->num,actionname(rop), actionname(uop));
+       return (err);
+}
+
+/*
+ *             Play the redo actions from earliest to latest
+ *
+ *     Currently we can only redo the last undone transaction,
+ *     otherwise the attribute table would be out of phase.
+ */
+
+int play_redos(ntfs_volume *vol, const struct ACTION_RECORD *firstaction)
+{
+       const struct ACTION_RECORD *action;
+       MFT_RECORD *entry;
+       INDEX_BLOCK *indx;
+       char *buffer;
+       s64 this_lsn;
+       s64 data_lsn;
+       u32 xsize;
+       u16 rop;
+       u16 uop;
+       int err;
+       BOOL warn;
+       BOOL executed;
+       enum ACTION_KIND kind;
+
+       err = 0;
+       action = firstaction;
+       while (action && !err) {
+               this_lsn = le64_to_cpu(action->record.this_lsn);
+                       /* Only committed actions should be redone */
+               if ((!optc || within_lcn_range(&action->record))
+                   && (action->flags & ACTION_TO_REDO)) {
+                       rop = le16_to_cpu(action->record.redo_operation);
+                       uop = le16_to_cpu(action->record.undo_operation);
+                       if (optv)
+                               printf("Redo action %d %s (%s) 0x%llx\n",
+                                       action->num,
+                                       actionname(rop), actionname(uop),
+                                       (long long)le64_to_cpu(
+                                               action->record.this_lsn));
+                       buffer = (char*)NULL;
+                       switch (rop) {
+                                       /* Actions always acting on MFT */
+                       case AddIndexEntryRoot :
+                       case CreateAttribute :
+                       case DeallocateFileRecordSegment :
+                       case DeleteAttribute :
+                       case DeleteIndexEntryRoot :
+                       case InitializeFileRecordSegment :
+                       case SetIndexEntryVcnRoot :
+                       case SetNewAttributeSizes :
+                       case UpdateFileNameRoot :
+                       case UpdateMappingPairs :
+                       case UpdateResidentValue :
+                       case Win10Action37 :
+                       case WriteEndofFileRecordSegment :
+                               kind = ON_MFT;
+                               break;
+                                       /* Actions always acting on INDX */
+                       case AddIndexEntryAllocation :
+                       case DeleteIndexEntryAllocation :
+                       case SetIndexEntryVcnAllocation :
+                       case UpdateFileNameAllocation :
+                       case WriteEndOfIndexBuffer :
+                               kind = ON_INDX;
+                               break;
+                                       /* Actions never acting on MFT or INDX */
+                       case ClearBitsInNonResidentBitMap :
+                       case SetBitsInNonResidentBitMap :
+                               kind = ON_RAW;
+                               break;
+                                       /* Actions which may act on MFT */
+                       case Noop : /* on MFT if DeallocateFileRecordSegment */
+                               kind = ON_NONE;
+                               break;
+                                       /* Actions which may act on INDX */
+                       case UpdateNonResidentValue :
+                               /* Known cases : INDX, $SDS, ATTR_LIST */
+                               kind = get_action_kind(action);
+                               if (kind == ON_NONE)
+                                       err = 1;
+                               break;
+                       case CompensationlogRecord :
+                       case OpenNonResidentAttribute :
+                               /* probably not important */
+                               kind = ON_NONE;
+                               break;
+                                       /* Actions currently ignored */
+                       case AttributeNamesDump :
+                       case DirtyPageTableDump :
+                       case ForgetTransaction :
+                       case OpenAttributeTableDump :
+                       case TransactionTableDump :
+                               kind = ON_NONE;
+                               break;
+                                       /* Actions with no known use case */
+                       case CommitTransaction :
+                       case DeleteDirtyClusters :
+                       case EndTopLevelAction :
+                       case HotFix :
+                       case PrepareTransaction :
+                       case UpdateRecordDataAllocation :
+                       case UpdateRecordDataRoot :
+                       case Win10Action35 :
+                       case Win10Action36 :
+                       default :
+                               err = 1;
+                               kind = ON_NONE;
+                               break;
+                       }
+                       executed = FALSE;
+                       switch (kind) {
+                       case ON_MFT :
+/*
+ the check below cannot be used on WinXP
+if (!(action->record.attribute_flags & const_cpu_to_le16(ACTS_ON_MFT)))
+printf("** %s (action %d) not acting on MFT\n",actionname(rop),(int)action->num);
+*/
+                               /* Check whether data is to be discarded */
+                               warn = (rop != InitializeFileRecordSegment)
+                                       || !check_full_mft(action,TRUE);
+                               buffer = read_protected(vol, &action->record,
+                                                       mftrecsz, warn);
+                               entry = (MFT_RECORD*)buffer;
+                               if (entry && (entry->magic == magic_FILE)) {
+                                       data_lsn = le64_to_cpu(entry->lsn);
+                                       /*
+                                        * Beware of records not updated
+                                        * during the last session which may
+                                        * have a stale lsn (consequence
+                                        * of ntfs-3g resetting the log)
+                                        */
+                                       executed = ((s64)(data_lsn
+                                                       - this_lsn) >= 0)
+                                           && (((s64)(data_lsn
+                                                       - latest_lsn)) <= 0)
+                                           && !exception(action->num);
+                               } else {
+                                       if (!warn) {
+                                               /* Old record not needed */
+                                               if (!buffer)
+                                                       buffer =
+                                                       (char*)malloc(mftrecsz);
+                                               if (buffer)
+                                                       executed = FALSE;
+                                               else
+                                                       err = 1;
+                                       } else {
+                                               printf("** %s (action %d) not"
+                                                       " acting on MFT\n",
+                                                       actionname(rop),
+                                                       (int)action->num);
+                                               err = 1;
+                                       }
+                               }
+                               break;
+                       case ON_INDX :
+/*
+ the check below cannot be used on WinXP
+if (!(action->record.attribute_flags & const_cpu_to_le16(ACTS_ON_INDX)))
+printf("** %s (action %d) not acting on INDX\n",actionname(rop),(int)action->num);
+*/
+                               xsize = vol->indx_record_size;
+                               /* Check whether data is to be discarded */
+                               warn = (rop != UpdateNonResidentValue)
+                                       || !check_full_index(action,TRUE);
+                               buffer = read_protected(vol, &action->record,
+                                                               xsize, warn);
+                               indx = (INDEX_BLOCK*)buffer;
+                               if (indx && (indx->magic == magic_INDX)) {
+                                       data_lsn = le64_to_cpu(indx->lsn);
+                                       /*
+                                        * Beware of records not updated
+                                        * during the last session which may
+                                        * have a stale lsn (consequence
+                                        * of ntfs-3g resetting the log)
+                                        */
+                                       executed = ((s64)(data_lsn
+                                                       - this_lsn) >= 0)
+                                           && (((s64)(data_lsn
+                                                       - latest_lsn)) <= 0)
+                                           && ! exception(action->num);
+                               } else {
+                                       if (!warn) {
+                                               /* Old record not needed */
+                                               if (!buffer)
+                                                       buffer = 
+                                                       (char*)malloc(xsize);
+                                               if (buffer)
+                                                       executed = FALSE;
+                                               else
+                                                       err = 1;
+                                       } else {
+                                               printf("** %s (action %d) not"
+                                                       " acting on INDX\n",
+                                                       actionname(rop),
+                                                       (int)action->num);
+                                               err = 1;
+                                       }
+                               }
+                               break;
+                       case ON_RAW :
+                               if (action->record.attribute_flags
+                                       & (const_cpu_to_le16(
+                                               ACTS_ON_INDX | ACTS_ON_MFT))) {
+                                       printf("** Error : action %s on MFT"
+                                               " or INDX\n",
+                                               actionname(rop));
+                                       err = 1;
+                               } else {
+                                       buffer = read_raw(vol, &action->record);
+                                       if (!buffer)
+                                               err = 1;
+                               }
+                               break;
+                       default :
+                               buffer = (char*)NULL;
+                               break;
+                       }
+                       if (!err && (!executed || !opts)) {
+                               err = distribute_redos(vol, action, buffer);
+                               redocount++;
+                       } else {
+                               if (optv)
+                                       printf("Action %d %s (%s) not redone\n",
+                                               action->num,
+                                               actionname(rop),
+                                               actionname(uop));
+                       }
+                       if (buffer)
+                               free(buffer);
+               }
+               if (!err)
+                       action = action->next;
+       }
+       return (err);
+}
+
+static int distribute_undos(ntfs_volume *vol, const struct ACTION_RECORD *action,
+                       char *buffer)
+{
+       int rop, uop;
+       int err;
+
+       err = 0;
+       rop = le16_to_cpu(action->record.redo_operation);
+       uop = le16_to_cpu(action->record.undo_operation);
+       switch (rop) {
+       case AddIndexEntryAllocation :
+               if (action->record.undo_operation
+                   == const_cpu_to_le16(DeleteIndexEntryAllocation))
+                       err = undo_add_index(vol, action, buffer);
+               break;
+       case AddIndexEntryRoot :
+               if (action->record.undo_operation
+                       == const_cpu_to_le16(DeleteIndexEntryRoot))
+                       err = undo_add_root_index(vol, action, buffer);
+               break;
+       case ClearBitsInNonResidentBitMap :
+               if (action->record.undo_operation
+                   == const_cpu_to_le16(SetBitsInNonResidentBitMap))
+                       err = undo_force_bits(vol, action, buffer);
+               break;
+       case CreateAttribute :
+               if (action->record.undo_operation
+                   == const_cpu_to_le16(DeleteAttribute))
+                       err = undo_create_attribute(vol, action, buffer);
+               break;
+       case DeallocateFileRecordSegment :
+               if (action->record.undo_operation
+                   == const_cpu_to_le16(InitializeFileRecordSegment))
+                       err = undo_delete_file(vol, action, buffer);
+               break;
+       case DeleteAttribute :
+               if (action->record.undo_operation
+                   == const_cpu_to_le16(CreateAttribute))
+                       err = undo_delete_attribute(vol, action, buffer);
+               break;
+       case DeleteIndexEntryAllocation :
+               if (action->record.undo_operation
+                   == const_cpu_to_le16(AddIndexEntryAllocation))
+                       err = undo_delete_index(vol, action, buffer);
+               break;
+       case DeleteIndexEntryRoot :
+               if (action->record.undo_operation
+                       == const_cpu_to_le16(AddIndexEntryRoot))
+                       err = undo_delete_root_index(vol, action, buffer);
+               break;
+       case InitializeFileRecordSegment :
+               if (action->record.undo_operation
+                       == const_cpu_to_le16(Noop))
+                       err = undo_create_file(vol, action, buffer);
+               break;
+       case OpenNonResidentAttribute :
+               if (action->record.undo_operation
+                       == const_cpu_to_le16(Noop))
+                       err = undo_open_attribute(vol, action);
+               break;
+       case SetBitsInNonResidentBitMap :
+               if (action->record.undo_operation
+                   == const_cpu_to_le16(ClearBitsInNonResidentBitMap))
+                       err = undo_force_bits(vol, action, buffer);
+               break;
+       case SetIndexEntryVcnAllocation :
+               if (action->record.undo_operation
+                   == const_cpu_to_le16(SetIndexEntryVcnAllocation))
+                       err = undo_update_vcn(vol, action, buffer);
+               break;
+       case SetIndexEntryVcnRoot :
+               if (action->record.undo_operation
+                       == const_cpu_to_le16(SetIndexEntryVcnRoot))
+                       err = undo_update_root_vcn(vol, action, buffer);
+               break;
+       case SetNewAttributeSizes :
+               if (action->record.undo_operation
+                   == const_cpu_to_le16(SetNewAttributeSizes))
+                       err = undo_sizes(vol, action, buffer);
+               break;
+       case UpdateFileNameAllocation :
+               if (action->record.undo_operation
+                   == const_cpu_to_le16(UpdateFileNameAllocation))
+                       err = undo_update_index(vol, action, buffer);
+               break;
+       case UpdateFileNameRoot :
+               if (action->record.undo_operation
+                       == const_cpu_to_le16(UpdateFileNameRoot))
+                       err = undo_update_root_index(vol, action, buffer);
+               break;
+       case UpdateMappingPairs :
+               if (action->record.undo_operation
+                   == const_cpu_to_le16(UpdateMappingPairs))
+                       err = undo_update_mapping(vol, action, buffer);
+               break;
+       case UpdateNonResidentValue :
+               switch (get_action_kind(action)) {
+               case ON_INDX :
+                       err = undo_update_index_value(vol, action, buffer);
+                       break;
+               case ON_RAW :
+                       err = undo_update_value(vol, action, buffer);
+                       break;
+               default :
+                       printf("** Bad attribute type\n");
+                       err = 1;
+               }
+               break;
+       case UpdateResidentValue :
+               if (action->record.undo_operation
+                   == const_cpu_to_le16(UpdateResidentValue))
+                       err = undo_update_resident(vol, action, buffer);
+               break;
+       case Win10Action37 :
+               if (action->record.undo_operation
+                   == const_cpu_to_le16(Noop))
+                       err = undo_action37(vol, action, buffer);
+               break;
+       case WriteEndofFileRecordSegment :
+               if (action->record.undo_operation
+                   == const_cpu_to_le16(WriteEndofFileRecordSegment))
+                       err = undo_write_end(vol, action, buffer);
+               break;
+       case WriteEndOfIndexBuffer :
+               if (action->record.undo_operation
+                   == const_cpu_to_le16(WriteEndOfIndexBuffer))
+                       err = undo_write_index(vol, action, buffer);
+               break;
+       case AttributeNamesDump :
+       case CompensationlogRecord :
+       case DirtyPageTableDump :
+       case ForgetTransaction :
+       case Noop :
+       case OpenAttributeTableDump :
+               break;
+       default :
+               printf("** Unsupported undo %s\n", actionname(rop));
+               err = 1;
+               break;
+       }
+       if (err)
+               printf("* Undoing action %d %s (%s) failed\n",
+                       action->num,actionname(rop), actionname(uop));
+       return (err);
+}
+
+/*
+ *             Play the undo actions from latest to earliest
+ *
+ *     For structured record, a check is made on the lsn to only
+ *     try to undo the actions which were executed. This implies
+ *     identifying actions on a structured record.
+ *
+ *     Returns 0 if successful
+ */
+
+int play_undos(ntfs_volume *vol, const struct ACTION_RECORD *lastaction)
+{
+       const struct ACTION_RECORD *action;
+       MFT_RECORD *entry;
+       INDEX_BLOCK *indx;
+       char *buffer;
+       u32 xsize;
+       u16 rop;
+       u16 uop;
+       int err;
+       BOOL executed;
+       enum ACTION_KIND kind;
+
+       err = 0;
+       action = lastaction;
+       while (action && !err) {
+               if (!optc || within_lcn_range(&action->record)) {
+                       rop = le16_to_cpu(action->record.redo_operation);
+                       uop = le16_to_cpu(action->record.undo_operation);
+                       if (optv)
+                               printf("Undo action %d %s (%s) lsn 0x%llx\n",
+                                       action->num,
+                                       actionname(rop), actionname(uop),
+                                       (long long)le64_to_cpu(
+                                               action->record.this_lsn));
+                       buffer = (char*)NULL;
+                       executed = FALSE;
+                       kind = ON_NONE;
+                       switch (rop) {
+                                       /* Actions always acting on MFT */
+                       case AddIndexEntryRoot :
+                       case CreateAttribute :
+                       case DeallocateFileRecordSegment :
+                       case DeleteAttribute :
+                       case DeleteIndexEntryRoot :
+                       case InitializeFileRecordSegment :
+                       case SetIndexEntryVcnRoot :
+                       case SetNewAttributeSizes :
+                       case UpdateFileNameRoot :
+                       case UpdateMappingPairs :
+                       case UpdateResidentValue :
+                       case Win10Action37 :
+                       case WriteEndofFileRecordSegment :
+                               kind = ON_MFT;
+                               break;
+                                       /* Actions always acting on INDX */
+                       case AddIndexEntryAllocation :
+                       case DeleteIndexEntryAllocation :
+                       case SetIndexEntryVcnAllocation :
+                       case UpdateFileNameAllocation :
+                       case WriteEndOfIndexBuffer :
+                               kind = ON_INDX;
+                               break;
+                                       /* Actions never acting on MFT or INDX */
+                       case ClearBitsInNonResidentBitMap :
+                       case SetBitsInNonResidentBitMap :
+                               kind = ON_RAW;
+                               break;
+                                       /* Actions which may act on MFT */
+                       case Noop : /* on MFT if DeallocateFileRecordSegment */
+                               break;
+                                       /* Actions which may act on INDX */
+                       case UpdateNonResidentValue :
+                               /* Known cases : INDX, $SDS, ATTR_LIST */
+                               kind = get_action_kind(action);
+                               if (kind == ON_NONE)
+                                       err = 1;
+                               break;
+                       case OpenNonResidentAttribute :
+                               /* probably not important */
+                               kind = ON_NONE;
+                               break;
+                                       /* Actions currently ignored */
+                       case AttributeNamesDump :
+                       case CommitTransaction :
+                       case CompensationlogRecord :
+                       case DeleteDirtyClusters :
+                       case DirtyPageTableDump :
+                       case EndTopLevelAction :
+                       case ForgetTransaction :
+                       case HotFix :
+                       case OpenAttributeTableDump :
+                       case PrepareTransaction :
+                       case TransactionTableDump :
+                       case UpdateRecordDataAllocation :
+                       case UpdateRecordDataRoot :
+                       case Win10Action35 :
+                       case Win10Action36 :
+                               kind = ON_NONE;
+                               break;
+                       }
+                       switch (kind) {
+                       case ON_MFT :
+/*
+ the check below cannot be used on WinXP
+if (!(action->record.attribute_flags & const_cpu_to_le16(ACTS_ON_MFT)))
+printf("** %s (action %d) not acting on MFT\n",actionname(rop),(int)action->num);
+*/
+                               buffer = read_protected(vol, &action->record,
+                                                       mftrecsz, TRUE);
+                               entry = (MFT_RECORD*)buffer;
+                               if (entry) {
+                                       if (entry->magic == magic_FILE) {
+                                               executed = !older_record(entry,
+                                                       &action->record);
+                                               if (!executed
+                                                   && exception(action->num))
+                                                       executed = TRUE;
+if (optv > 1)
+printf("record lsn 0x%llx is %s than action %d lsn 0x%llx\n",
+(long long)le64_to_cpu(entry->lsn),
+(executed ? "not older" : "older"),
+(int)action->num,
+(long long)le64_to_cpu(action->record.this_lsn));
+                                       } else {
+                                               printf("** %s (action %d) not"
+                                                       " acting on MFT\n",
+                                                       actionname(rop),
+                                                       (int)action->num);
+                                               err = 1;
+                                       }
+                               } else {
+// TODO make sure this is about a newly allocated record (with bad fixup)
+// TODO check this is inputting a full record (record lth == data lth)
+                                       buffer = (char*)malloc(mftrecsz);
+                               }
+                               break;
+                       case ON_INDX :
+/*
+ the check below cannot be used on WinXP
+if (!(action->record.attribute_flags & const_cpu_to_le16(ACTS_ON_INDX)))
+printf("** %s (action %d) not acting on INDX\n",actionname(rop),(int)action->num);
+*/
+                               xsize = vol->indx_record_size;
+                               buffer = read_protected(vol, &action->record,
+                                                               xsize, TRUE);
+                               indx = (INDEX_BLOCK*)buffer;
+                               if (indx) {
+                                       if (indx->magic == magic_INDX) {
+                                               executed = !older_record(indx,
+                                                       &action->record);
+                                               if (!executed
+                                                   && exception(action->num))
+                                                       executed = TRUE;
+if (optv > 1)
+printf("index lsn 0x%llx is %s than action %d lsn 0x%llx\n",
+(long long)le64_to_cpu(indx->lsn),
+(executed ? "not older" : "older"),
+(int)action->num,
+(long long)le64_to_cpu(action->record.this_lsn));
+                                       } else {
+                                               printf("** %s (action %d) not"
+                                                       " acting on INDX\n",
+                                                       actionname(rop),
+                                                       (int)action->num);
+                                               err = 1;
+                                       }
+                               } else {
+// TODO make sure this is about a newly allocated record (with bad fixup)
+// TODO check this is inputting a full record (record lth == data lth)
+// recreate an INDX record if this is the first entry
+                                       buffer = (char*)malloc(xsize);
+                                       err = create_indx(vol, action, buffer);
+                                       executed = TRUE;
+                               }
+                               break;
+                       case ON_RAW :
+                               if (action->record.attribute_flags
+                                       & (const_cpu_to_le16(
+                                               ACTS_ON_INDX | ACTS_ON_MFT))) {
+                                       printf("** Error : action %s on MFT"
+                                               " or INDX\n",
+                                               actionname(rop));
+                                       err = 1;
+                               } else {
+                                       buffer = read_raw(vol, &action->record);
+                                       if (!buffer)
+                                               err = 1;
+                               }
+                               executed = TRUE;
+                               break;
+                       default :
+                               executed = TRUE;
+                               buffer = (char*)NULL;
+                               break;
+                       }
+                       if (!err && executed) {
+                               err = distribute_undos(vol, action, buffer);
+                               undocount++;
+                       }
+                       if (buffer)
+                               free(buffer);
+               }
+               if (!err)
+                       action = action->prev;
+       }
+       return (err);
+}