/*
 *		Redo or undo a list of logged actions
 *
 * Copyright (c) 2014-2016 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 "logfile.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)(sle64_to_cpu((rec)->lsn) \
			- sle64_to_cpu((logr)->this_lsn)) < 0)
/* check whether a MFT or INDX record is newer than action */
#define newer_record(rec, logr) ((s64)(sle64_to_cpu((rec)->lsn) \
			- sle64_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 LOG_RECORD *logr)
{
	u64 offset;

	offset = ((u64)sle64_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 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 = sle64_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 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 = sle64_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 = sle64_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 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,
				sle64_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 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 = sle64_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 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)sle64_to_cpu(record->lsn),
				((s64)(sle64_to_cpu(record->lsn)
				    - sle64_to_cpu(logr->this_lsn)) < 0 ?
					"older" : "newer"),
				(long long)sle64_to_cpu(logr->this_lsn));
		if (optv > 1)
			printf("mft vcn %lld index %d\n",
				(long long)sle64_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)sle64_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)sle64_to_cpu(indx->lsn),
				((s64)(sle64_to_cpu(indx->lsn)
				    - sle64_to_cpu(logr->this_lsn)) < 0 ?
					"older" : "newer"),
				(long long)sle64_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
					err = 1;
			} 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 = const_cpu_to_sle64(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_sle64(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 = sle64_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 = sle64_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 = sle64_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 = sle64_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 = sle64_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 = sle64_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 = sle64_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 = sle64_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 = sle64_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 = sle64_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_sle64(0);
			attr->highest_vcn = const_cpu_to_sle64(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_sle64(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 = sle64_to_cpu(action->record.target_vcn);
			/* beware of size change on big-endian cpus */
		indx->index_block_vcn = cpu_to_sle64(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 = sle64_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 = sle64_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 = sle64_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;
	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 = sle64_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 {
		err = 1; /* record overflows */
	}
	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 = sle64_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 = sle64_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 = sle64_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 = sle64_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 ATTR_OLD *attr_old;
	const 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(ATTR_OLD) :
				attr_old = (const 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(ATTR_NEW) :
				attr_new = (const 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 = sle64_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 = sle64_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 = sle64_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 = sle64_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 = sle64_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 = sle64_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 = sle64_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 = sle64_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 = sle64_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 = sle64_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 = sle64_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 = sle64_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 ATTR_OLD *attr_old;
	const 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(ATTR_OLD) :
			attr_old = (const 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(ATTR_NEW) :
			attr_new = (const 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 = sle64_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 = sle64_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 = sle64_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 = sle64_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 = sle64_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 = sle64_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
			& (LOG_RECORD_DELETING | LOG_RECORD_ADDING)) {
		if (action->record.attribute_flags & ACTS_ON_INDX)
			kind = ON_INDX;
		else
			if (action->record.attribute_flags & 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);
}

/*
 *		Redo a single action
 *
 *	The record the action acts on is read and, when it is an MFT or
 *	INDX one, the need for redoing is checked.
 *
 *	When this is an action which creates a new MFT or INDX record
 *	and the old one cannot be read (usually because it was not
 *	initialized), a zeroed buffer is allocated.
 */

static int play_one_redo(ntfs_volume *vol, const struct ACTION_RECORD *action)
{
	MFT_RECORD *entry;
	INDEX_BLOCK *indx;
	char *buffer;
	s64 this_lsn;
	s64 data_lsn;
	u32 xsize;
	int err;
	BOOL warn;
	BOOL executed;
	enum ACTION_KIND kind;
	u16 rop;
	u16 uop;

	err = 0;
	rop = le16_to_cpu(action->record.redo_operation);
	uop = le16_to_cpu(action->record.undo_operation);
	this_lsn = sle64_to_cpu(action->record.this_lsn);
	if (optv)
		printf("Redo action %d %s (%s) 0x%llx\n",
			action->num,
			actionname(rop), actionname(uop),
			(long long)sle64_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 & 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 = sle64_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*)calloc(1, 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 & 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 = sle64_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*)calloc(1, 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
				& (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);
	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;
	int err;

	err = 0;
	action = firstaction;
	while (action && !err) {
			/* Only committed actions should be redone */
		if ((!optc || within_lcn_range(&action->record))
		    && (action->flags & ACTION_TO_REDO))
			err = play_one_redo(vol, action);
		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);
}

/*
 *		Undo a single action
 *
 *	The record the action acts on is read and, when it is an MFT or
 *	INDX one, the need for undoing is checked.
 */

static int play_one_undo(ntfs_volume *vol, 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;
	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)sle64_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 & 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)sle64_to_cpu(entry->lsn),
(executed ? "not older" : "older"),
(int)action->num,
(long long)sle64_to_cpu(action->record.this_lsn));
			} else {
				printf("** %s (action %d) not acting on MFT\n",
					actionname(rop), (int)action->num);
				err = 1;
			}
		} else {
			/*
			 * Could not read the MFT record :
			 * if this is undoing a record create (from scratch)
			 * which did not take place, there is nothing to redo,
			 * otherwise this is an error.
			 */
			if (check_full_mft(action,TRUE))
				executed = FALSE;
			else
				err = 1;
		}
		break;
	case ON_INDX :
/*
 the check below cannot be used on WinXP
if (!(action->record.attribute_flags & 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)sle64_to_cpu(indx->lsn),
(executed ? "not older" : "older"),
(int)action->num,
(long long)sle64_to_cpu(action->record.this_lsn));
			} else {
				printf("** %s (action %d) not acting on INDX\n",
					actionname(rop), (int)action->num);
				err = 1;
			}
		} else {
			/*
			 * Could not read the INDX record :
			 * if this is undoing a record create (from scratch)
			 * which did not take place, there is nothing to redo,
			 * otherwise this must be an error.
			 * However, after deleting the last index allocation
			 * in a block, the block is apparently zeroed
			 * and cannot be read. In this case we have to
			 * create an initial index block and apply the undo.
			 */
			if (check_full_index(action,TRUE))
				executed = FALSE;
			else {
				err = 1;
				if (uop == AddIndexEntryAllocation) {
					executed = TRUE;
					buffer = (char*)calloc(1, xsize);
					if (buffer)
						err = create_indx(vol,
							action, buffer);
				}
			}
		}
		break;
	case ON_RAW :
		if (action->record.attribute_flags
				& (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);

	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;
	int err;

	err = 0;
	action = lastaction;
	while (action && !err) {
		if (!optc || within_lcn_range(&action->record))
			err = play_one_undo(vol, action);
		if (!err)
			action = action->prev;
	}
	return (err);
}
