mirror of
https://git.code.sf.net/p/ntfs-3g/ntfs-3g.git
synced 2024-11-23 18:14:24 +08:00
8fc9c96305
When an INDX or MFT record could not be read while undoing the creation of this record, there is nothing to do. However if this was undoing the deletion of the last entry in an index, a new void index block has to be created.
4851 lines
126 KiB
C
4851 lines
126 KiB
C
/*
|
|
* Redo or undo a list of logged actions
|
|
*
|
|
* Copyright (c) 2014-2015 Jean-Pierre Andre
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program (in the main directory of the NTFS-3G
|
|
* distribution in the file COPYING); if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#ifdef HAVE_STDLIB_H
|
|
#include <stdlib.h>
|
|
#endif
|
|
#ifdef HAVE_STDIO_H
|
|
#include <stdio.h>
|
|
#endif
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
#ifdef HAVE_FCNTL_H
|
|
#include <fcntl.h>
|
|
#endif
|
|
#ifdef HAVE_ERRNO_H
|
|
#include <errno.h>
|
|
#endif
|
|
#ifdef HAVE_STRING_H
|
|
#include <string.h>
|
|
#endif
|
|
#ifdef HAVE_MALLOC_H
|
|
#include <malloc.h>
|
|
#endif
|
|
#ifdef HAVE_TIME_H
|
|
#include <time.h>
|
|
#endif
|
|
|
|
#include "types.h"
|
|
#include "endians.h"
|
|
#include "support.h"
|
|
#include "layout.h"
|
|
#include "param.h"
|
|
#include "ntfstime.h"
|
|
#include "device_io.h"
|
|
#include "device.h"
|
|
#include "logging.h"
|
|
#include "runlist.h"
|
|
#include "mft.h"
|
|
#include "inode.h"
|
|
#include "attrib.h"
|
|
#include "bitmap.h"
|
|
#include "index.h"
|
|
#include "volume.h"
|
|
#include "unistr.h"
|
|
#include "mst.h"
|
|
#include "ntfsrecover.h"
|
|
#include "misc.h"
|
|
|
|
struct STORE {
|
|
struct STORE *upper;
|
|
struct STORE *lower;
|
|
LCN lcn;
|
|
char data[1];
|
|
} ;
|
|
|
|
#define dump hexdump
|
|
|
|
struct STORE *cluster_door = (struct STORE*)NULL;
|
|
|
|
/* check whether a MFT or INDX record is older than action */
|
|
#define older_record(rec, logr) ((s64)(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 struct LOG_RECORD *logr)
|
|
{
|
|
u64 offset;
|
|
|
|
offset = ((u64)le32_to_cpu(logr->target_vcn)
|
|
<< clusterbits)
|
|
+ ((u32)le16_to_cpu(logr->cluster_index)
|
|
<< NTFS_BLOCK_SIZE_BITS);
|
|
return (offset >> mftrecbits);
|
|
}
|
|
|
|
/*
|
|
* Find an in-memory copy of a needed cluster
|
|
*
|
|
* Optionally, allocate a copy.
|
|
*/
|
|
|
|
static struct STORE *getclusterentry(LCN lcn, BOOL create)
|
|
{
|
|
struct STORE **current;
|
|
struct STORE *newone;
|
|
|
|
current = &cluster_door;
|
|
/* A minimal binary tree should be enough */
|
|
while (*current && (lcn != (*current)->lcn)) {
|
|
if (lcn > (*current)->lcn)
|
|
current = &(*current)->upper;
|
|
else
|
|
current = &(*current)->lower;
|
|
}
|
|
if (create && !*current) {
|
|
newone = (struct STORE*)malloc(sizeof(struct STORE)
|
|
+ clustersz);
|
|
if (newone) {
|
|
newone->upper = (struct STORE*)NULL;
|
|
newone->lower = (struct STORE*)NULL;
|
|
newone->lcn = lcn;
|
|
*current = newone;
|
|
}
|
|
}
|
|
return (*current);
|
|
}
|
|
|
|
void freeclusterentry(struct STORE *entry)
|
|
{
|
|
if (!entry) {
|
|
if (cluster_door)
|
|
freeclusterentry(cluster_door);
|
|
cluster_door = (struct STORE*)NULL;
|
|
} else {
|
|
if (optv)
|
|
printf("* cluster 0x%llx %s updated\n",
|
|
(long long)entry->lcn,
|
|
(optn ? "would be" : "was"));
|
|
if (entry->upper)
|
|
freeclusterentry(entry->upper);
|
|
if (entry->lower)
|
|
freeclusterentry(entry->lower);
|
|
free(entry);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check whether an attribute type is a valid one
|
|
*/
|
|
|
|
static BOOL valid_type(ATTR_TYPES type)
|
|
{
|
|
BOOL ok;
|
|
|
|
switch (type) {
|
|
case AT_STANDARD_INFORMATION :
|
|
case AT_ATTRIBUTE_LIST :
|
|
case AT_FILE_NAME :
|
|
case AT_OBJECT_ID :
|
|
case AT_SECURITY_DESCRIPTOR :
|
|
case AT_VOLUME_NAME :
|
|
case AT_VOLUME_INFORMATION :
|
|
case AT_DATA :
|
|
case AT_INDEX_ROOT :
|
|
case AT_INDEX_ALLOCATION :
|
|
case AT_BITMAP :
|
|
case AT_REPARSE_POINT :
|
|
case AT_EA_INFORMATION :
|
|
case AT_EA :
|
|
case AT_PROPERTY_SET :
|
|
case AT_LOGGED_UTILITY_STREAM :
|
|
case AT_FIRST_USER_DEFINED_ATTRIBUTE :
|
|
case AT_END :
|
|
ok = TRUE;
|
|
break;
|
|
default :
|
|
ok = FALSE;
|
|
break;
|
|
}
|
|
return (ok);
|
|
}
|
|
|
|
/*
|
|
* Rough check of sanity of an index list
|
|
*/
|
|
|
|
static int sanity_indx_list(const char *buffer, u32 k, u32 end)
|
|
{
|
|
le64 inode;
|
|
int err;
|
|
int lth;
|
|
BOOL done;
|
|
|
|
err = 0;
|
|
done = FALSE;
|
|
while ((k <= end) && !done) {
|
|
lth = getle16(buffer,k+8);
|
|
if (optv > 1)
|
|
/* Usual indexes can be determined from size */
|
|
switch (lth) {
|
|
case 16 : /* final without subnode */
|
|
case 24 : /* final with subnode */
|
|
printf("index to none lth 0x%x"
|
|
" flags 0x%x pos 0x%x\n",
|
|
(int)lth,
|
|
(int)getle16(buffer,k+12),(int)k);
|
|
break;
|
|
case 32 : /* $R in $Reparse */
|
|
/* Badly aligned */
|
|
memcpy(&inode, &buffer[k + 20], 8);
|
|
printf("index to reparse of 0x%016llx lth 0x%x"
|
|
" flags 0x%x pos 0x%x\n",
|
|
(long long)le64_to_cpu(inode),
|
|
(int)lth,
|
|
(int)getle16(buffer,k+12),(int)k);
|
|
break;
|
|
case 40 : /* $SII in $Secure */
|
|
printf("index to securid 0x%lx lth 0x%x"
|
|
" flags 0x%x pos 0x%x\n",
|
|
(long)getle32(buffer,k + 16),
|
|
(int)lth,
|
|
(int)getle16(buffer,k+12),(int)k);
|
|
break;
|
|
case 48 : /* $SDH in $Secure */
|
|
printf("index to securid 0x%lx lth 0x%x"
|
|
" flags 0x%x pos 0x%x\n",
|
|
(long)getle32(buffer,k + 20),
|
|
(int)lth,
|
|
(int)getle16(buffer,k+12),(int)k);
|
|
break;
|
|
default : /* at least 80 */
|
|
printf("index to inode 0x%016llx lth 0x%x"
|
|
" flags 0x%x pos 0x%x\n",
|
|
(long long)getle64(buffer,k),
|
|
(int)lth,
|
|
(int)getle16(buffer,k+12),(int)k);
|
|
}
|
|
done = (feedle16(buffer,k+12) & INDEX_ENTRY_END) || !lth;
|
|
k += lth;
|
|
}
|
|
if (k != end) {
|
|
printf("** Bad index record length %ld (computed %ld)\n",
|
|
(long)end, (long)k);
|
|
err = 1;
|
|
}
|
|
if (!done) {
|
|
printf("** Missing end of index mark\n");
|
|
err = 1;
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
/*
|
|
* Rough check of sanity of an mft record
|
|
*/
|
|
|
|
static int sanity_mft(const char *buffer)
|
|
{
|
|
const MFT_RECORD *record;
|
|
const ATTR_RECORD *attr;
|
|
u64 instances;
|
|
u32 k;
|
|
u32 type;
|
|
u32 prevtype;
|
|
u16 nextinstance;
|
|
u16 instance;
|
|
int err;
|
|
|
|
err = 0;
|
|
record = (const MFT_RECORD*)buffer;
|
|
nextinstance = le16_to_cpu(record->next_attr_instance);
|
|
instances = 0;
|
|
k = le16_to_cpu(record->attrs_offset);
|
|
attr = (const ATTR_RECORD*)&buffer[k];
|
|
prevtype = 0;
|
|
while ((k < mftrecsz)
|
|
&& (attr->type != AT_END)
|
|
&& valid_type(attr->type)) {
|
|
type = le32_to_cpu(attr->type);
|
|
if (type < prevtype) {
|
|
printf("** Bad type ordering 0x%lx after 0x%lx\n",
|
|
(long)type, (long)prevtype);
|
|
err = 1;
|
|
}
|
|
instance = le16_to_cpu(attr->instance);
|
|
/* Can nextinstance wrap around ? */
|
|
if (instance >= nextinstance) {
|
|
printf("** Bad attr instance %d (max %d)\n",
|
|
(int)instance, (int)nextinstance - 1);
|
|
err = 1;
|
|
}
|
|
if (instance < 64) {
|
|
/* Only check up to 64 */
|
|
if (((u64)1 << instance) & instances) {
|
|
printf("** Duplicated attr instance %d\n",
|
|
(int)instance);
|
|
}
|
|
instances |= (u64)1 << instance;
|
|
}
|
|
if (optv > 1) {
|
|
if ((attr->type == AT_FILE_NAME)
|
|
&& buffer[k + 88]) {
|
|
printf("attr %08lx offs 0x%x nres %d",
|
|
(long)type, (int)k,
|
|
(int)attr->non_resident);
|
|
showname(" ",&buffer[k+90],
|
|
buffer[k + 88] & 255);
|
|
} else
|
|
printf("attr %08lx offs 0x%x nres %d\n",
|
|
(long)type, (int)k,
|
|
(int)attr->non_resident);
|
|
}
|
|
if ((attr->type == AT_INDEX_ROOT)
|
|
&& sanity_indx_list(buffer,
|
|
k + le16_to_cpu(attr->value_offset) + 32,
|
|
k + le32_to_cpu(attr->length))) {
|
|
err = 1;
|
|
}
|
|
k += le32_to_cpu(attr->length);
|
|
attr = (const ATTR_RECORD*)&buffer[k];
|
|
prevtype = type;
|
|
}
|
|
if ((optv > 1) && (attr->type == AT_END))
|
|
printf("attr %08lx offs 0x%x\n",
|
|
(long)le32_to_cpu(attr->type), (int)k);
|
|
if ((attr->type != AT_END)
|
|
|| (le32_to_cpu(record->bytes_in_use) != (k + 8))
|
|
|| (le32_to_cpu(record->bytes_allocated) < (k + 8))) {
|
|
printf("** Bad MFT record length %ld"
|
|
" (computed %ld allocated %ld)\n",
|
|
(long)le32_to_cpu(record->bytes_in_use),
|
|
(long)(k + 8),
|
|
(long)le32_to_cpu(record->bytes_allocated));
|
|
err = 1;
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
/*
|
|
* Rough check of sanity of an index block
|
|
*/
|
|
|
|
static int sanity_indx(ntfs_volume *vol, const char *buffer)
|
|
{
|
|
const INDEX_BLOCK *indx;
|
|
u32 k;
|
|
int err;
|
|
|
|
err = 0;
|
|
indx = (const INDEX_BLOCK*)buffer;
|
|
k = offsetof(INDEX_BLOCK, index) +
|
|
le32_to_cpu(indx->index.entries_offset);
|
|
err = sanity_indx_list(buffer, k,
|
|
le32_to_cpu(indx->index.index_length) + 24);
|
|
if ((le32_to_cpu(indx->index.index_length)
|
|
> le32_to_cpu(indx->index.allocated_size))
|
|
|| (le32_to_cpu(indx->index.allocated_size)
|
|
!= (vol->indx_record_size - 24))) {
|
|
printf("** Bad index length %ld"
|
|
" (usable %ld allocated %ld)\n",
|
|
(long)le32_to_cpu(indx->index.index_length),
|
|
(long)(vol->indx_record_size - 24),
|
|
(long)le32_to_cpu(indx->index.allocated_size));
|
|
err = 1;
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
|
|
/*
|
|
* Allocate a buffer and read a full set of raw clusters
|
|
*
|
|
* Do not use for accessing $LogFile.
|
|
* With option -n reading is first attempted from the memory store
|
|
*/
|
|
|
|
static char *read_raw(ntfs_volume *vol, const struct LOG_RECORD *logr)
|
|
{
|
|
char *buffer;
|
|
char *target;
|
|
struct STORE *store;
|
|
LCN lcn;
|
|
int count;
|
|
int i;
|
|
BOOL fail;
|
|
|
|
count = le16_to_cpu(logr->lcns_to_follow);
|
|
if (!count) {
|
|
printf("** Error : no lcn to read from\n");
|
|
buffer = (char*)NULL;
|
|
} else
|
|
buffer = (char*)malloc(clustersz*count);
|
|
// TODO error messages
|
|
if (buffer) {
|
|
fail = FALSE;
|
|
for (i=0; (i<count) && !fail; i++) {
|
|
store = (struct STORE*)NULL;
|
|
lcn = le64_to_cpu(logr->lcn_list[i]);
|
|
target = buffer + clustersz*i;
|
|
if (optn) {
|
|
store = getclusterentry(lcn, FALSE);
|
|
if (store) {
|
|
memcpy(target, store->data, clustersz);
|
|
if (optv)
|
|
printf("== lcn 0x%llx from store\n",
|
|
(long long)lcn);
|
|
if ((optv > 1) && optc
|
|
&& within_lcn_range(logr))
|
|
dump(store->data, clustersz);
|
|
}
|
|
}
|
|
if (!store
|
|
&& (ntfs_pread(vol->dev, lcn << clusterbits,
|
|
clustersz, target) != clustersz)) {
|
|
fail = TRUE;
|
|
} else {
|
|
if (!store) {
|
|
if (optv)
|
|
printf("== lcn 0x%llx"
|
|
" from device\n",
|
|
(long long)lcn);
|
|
if ((optv > 1) && optc
|
|
&& within_lcn_range(logr))
|
|
dump(target, clustersz);
|
|
}
|
|
}
|
|
}
|
|
if (fail) {
|
|
printf("** Could not read cluster 0x%llx\n",
|
|
(long long)lcn);
|
|
free(buffer);
|
|
buffer = (char*)NULL;
|
|
}
|
|
}
|
|
return (buffer);
|
|
}
|
|
|
|
/*
|
|
* Write a full set of raw clusters
|
|
*
|
|
* Do not use for accessing $LogFile.
|
|
* With option -n a copy of the buffer is kept in memory for later use.
|
|
*/
|
|
|
|
static int write_raw(ntfs_volume *vol, const struct LOG_RECORD *logr,
|
|
char *buffer)
|
|
{
|
|
int err;
|
|
struct STORE *store;
|
|
LCN lcn;
|
|
char *source;
|
|
int count;
|
|
int i;
|
|
|
|
err = 0;
|
|
count = le16_to_cpu(logr->lcns_to_follow);
|
|
if (!count)
|
|
printf("** Error : no lcn to write to\n");
|
|
if (optn) {
|
|
for (i=0; (i<count) && !err; i++) {
|
|
lcn = le64_to_cpu(logr->lcn_list[i]);
|
|
source = buffer + clustersz*i;
|
|
store = getclusterentry(lcn, TRUE);
|
|
if (store) {
|
|
memcpy(store->data, source, clustersz);
|
|
if (optv)
|
|
printf("== lcn 0x%llx to store\n",
|
|
(long long)lcn);
|
|
if ((optv > 1) && optc
|
|
&& within_lcn_range(logr))
|
|
dump(store->data, clustersz);
|
|
} else {
|
|
printf("** Could not store cluster 0x%llx\n",
|
|
(long long)lcn);
|
|
err = 1;
|
|
}
|
|
}
|
|
} else {
|
|
for (i=0; (i<count) && !err; i++) {
|
|
lcn = le64_to_cpu(logr->lcn_list[i]);
|
|
if (optv)
|
|
printf("== lcn 0x%llx to device\n",
|
|
(long long)lcn);
|
|
source = buffer + clustersz*i;
|
|
if (ntfs_pwrite(vol->dev, lcn << clusterbits,
|
|
clustersz, source) != clustersz) {
|
|
printf("** Could not write cluster 0x%llx\n",
|
|
(long long)lcn);
|
|
err = 1;
|
|
}
|
|
}
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
/*
|
|
* Write a full set of raw clusters to mft_mirr
|
|
*/
|
|
|
|
static int write_mirr(ntfs_volume *vol, const struct LOG_RECORD *logr,
|
|
char *buffer)
|
|
{
|
|
int err;
|
|
LCN lcn;
|
|
char *source;
|
|
int count;
|
|
int i;
|
|
|
|
err = 0;
|
|
count = le16_to_cpu(logr->lcns_to_follow);
|
|
if (!count)
|
|
printf("** Error : no lcn to write to\n");
|
|
if (!optn) {
|
|
for (i=0; (i<count) && !err; i++) {
|
|
lcn = ntfs_attr_vcn_to_lcn(vol->mftmirr_na,
|
|
le32_to_cpu(logr->target_vcn) + i);
|
|
source = buffer + clustersz*i;
|
|
if ((lcn < 0)
|
|
|| (ntfs_pwrite(vol->dev, lcn << clusterbits,
|
|
clustersz, source) != clustersz)) {
|
|
printf("** Could not write cluster 0x%llx\n",
|
|
(long long)lcn);
|
|
err = 1;
|
|
}
|
|
}
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
/*
|
|
* Allocate a buffer and read a single protected record
|
|
*/
|
|
|
|
static char *read_protected(ntfs_volume *vol, const struct LOG_RECORD *logr,
|
|
u32 size, BOOL warn)
|
|
{
|
|
char *buffer;
|
|
char *full;
|
|
u32 pos;
|
|
LCN lcn;
|
|
|
|
/* read full clusters */
|
|
buffer = read_raw(vol, logr);
|
|
/*
|
|
* if the record is smaller than a cluster,
|
|
* make a partial copy and free the full buffer
|
|
*/
|
|
if (buffer && (size < clustersz)) {
|
|
full = buffer;
|
|
buffer = (char*)malloc(size);
|
|
if (buffer) {
|
|
pos = le16_to_cpu(logr->cluster_index)
|
|
<< NTFS_BLOCK_SIZE_BITS;
|
|
memcpy(buffer, full + pos, size);
|
|
}
|
|
free(full);
|
|
}
|
|
if (buffer && (ntfs_mst_post_read_fixup_warn(
|
|
(NTFS_RECORD*)buffer, size, FALSE) < 0)) {
|
|
if (warn) {
|
|
lcn = le64_to_cpu(logr->lcn_list[0]);
|
|
printf("** Invalid protected record at 0x%llx"
|
|
" index %d\n",
|
|
(long long)lcn,
|
|
(int)le16_to_cpu(logr->cluster_index));
|
|
}
|
|
free(buffer);
|
|
buffer = (char*)NULL;
|
|
}
|
|
return (buffer);
|
|
}
|
|
|
|
/*
|
|
* Protect a single record, write, and deallocate the buffer
|
|
*
|
|
* With option -n a copy of the buffer is kept in protected form in
|
|
* memory for later use.
|
|
* As the store only knows about clusters, if the record is smaller
|
|
* than a cluster, have to read, merge and write.
|
|
*/
|
|
|
|
static int write_protected(ntfs_volume *vol, const struct LOG_RECORD *logr,
|
|
char *buffer, u32 size)
|
|
{
|
|
MFT_RECORD *record;
|
|
INDEX_BLOCK *indx;
|
|
char *full;
|
|
u32 pos;
|
|
BOOL mftmirr;
|
|
BOOL checked;
|
|
int err;
|
|
|
|
err = 0;
|
|
mftmirr = FALSE;
|
|
checked = FALSE;
|
|
if ((size == mftrecsz) && !memcmp(buffer,"FILE",4)) {
|
|
record = (MFT_RECORD*)buffer;
|
|
if (optv)
|
|
printf("update inode %ld lsn 0x%llx"
|
|
" (record %s than action 0x%llx)\n",
|
|
(long)le32_to_cpu(record->mft_record_number),
|
|
(long long)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 %ld index %d\n",
|
|
(long)le32_to_cpu(logr->target_vcn),
|
|
(int)le16_to_cpu(logr->cluster_index));
|
|
err = sanity_mft(buffer);
|
|
/* Should set to some previous lsn for undos */
|
|
if (opts)
|
|
record->lsn = logr->this_lsn;
|
|
/* Duplicate on mftmirr if not overflowing its size */
|
|
mftmirr = (((u64)le32_to_cpu(logr->target_vcn)
|
|
+ le16_to_cpu(logr->lcns_to_follow))
|
|
<< clusterbits)
|
|
<= (((u64)vol->mftmirr_size) << mftrecbits);
|
|
checked = TRUE;
|
|
}
|
|
if ((size == vol->indx_record_size) && !memcmp(buffer,"INDX",4)) {
|
|
indx = (INDEX_BLOCK*)buffer;
|
|
if (optv)
|
|
printf("update index lsn 0x%llx"
|
|
" (index %s than action 0x%llx)\n",
|
|
(long long)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 = le64_to_cpu(action->record.lcn_list[0]);
|
|
printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
|
|
(long long)inode_number(&action->record),
|
|
(long long)lcn, (int)target, (int)length);
|
|
}
|
|
attr = (ATTR_RECORD*)(buffer
|
|
+ le16_to_cpu(action->record.record_offset));
|
|
if (optv > 1) {
|
|
printf("-> existing record :\n");
|
|
dump(&buffer[target], length);
|
|
printf("-> full MFT record :\n");
|
|
dump(buffer,mftrecsz);
|
|
}
|
|
attrend = le16_to_cpu(action->record.record_offset)
|
|
+ le32_to_cpu(attr->length);
|
|
if ((target + length) > attrend) {
|
|
printf("** Error : update overflows from attribute\n");
|
|
}
|
|
if (!(length & 7)
|
|
&& ((target + length) <= attrend)
|
|
&& (attrend <= mftrecsz)
|
|
&& !sanity_mft(buffer)) {
|
|
changed = memcmp(buffer + target, data, length);
|
|
err = 0;
|
|
if (changed) {
|
|
memcpy(buffer + target, data, length);
|
|
if (optv > 1) {
|
|
printf("-> new record :\n");
|
|
dump(buffer + target, length);
|
|
}
|
|
err = write_protected(vol, &action->record,
|
|
buffer, mftrecsz);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> MFT record %s\n",
|
|
(changed ? "updated" : "unchanged"));
|
|
}
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
static int change_resident_expect(ntfs_volume *vol, const struct ACTION_RECORD *action,
|
|
char *buffer, const char *data, const char *expected,
|
|
u32 target, u32 length, ATTR_TYPES type)
|
|
{
|
|
LCN lcn;
|
|
ATTR_RECORD *attr;
|
|
int err;
|
|
BOOL found;
|
|
|
|
err = 1;
|
|
if (action->record.undo_length != action->record.redo_length)
|
|
printf("** Error size change in change_resident\n");
|
|
if (optv > 1) {
|
|
lcn = le64_to_cpu(action->record.lcn_list[0]);
|
|
printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
|
|
(long long)inode_number(&action->record),
|
|
(long long)lcn, (int)target, (int)length);
|
|
}
|
|
attr = (ATTR_RECORD*)(buffer
|
|
+ le16_to_cpu(action->record.record_offset));
|
|
if (optv > 1) {
|
|
printf("-> existing record :\n");
|
|
dump(&buffer[target], length);
|
|
printf("-> full record :\n");
|
|
dump((char*)attr, le32_to_cpu(attr->length));
|
|
}
|
|
if ((attr->type == type)
|
|
&& !(length & 7)
|
|
&& ((target + length) <= mftrecsz)) {
|
|
found = !memcmp(buffer + target, expected, length);
|
|
err = 0;
|
|
if (found) {
|
|
memcpy(buffer + target, data, length);
|
|
if (optv > 1) {
|
|
printf("-> new record :\n");
|
|
dump(buffer + target, length);
|
|
}
|
|
err = write_protected(vol, &action->record,
|
|
buffer, mftrecsz);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> MFT record %s\n",
|
|
(found ? "updated" : "unchanged"));
|
|
}
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
/*
|
|
* Generic idempotent change to a an index value
|
|
*
|
|
*/
|
|
|
|
static int change_index_value(ntfs_volume *vol, const struct ACTION_RECORD *action,
|
|
char *buffer, const char *data, u32 target, u32 length)
|
|
{
|
|
LCN lcn;
|
|
u32 count;
|
|
u32 xsize;
|
|
int changed;
|
|
int err;
|
|
|
|
err = 1;
|
|
count = le16_to_cpu(action->record.lcns_to_follow);
|
|
if (optv > 1) {
|
|
lcn = le64_to_cpu(action->record.lcn_list[0]);
|
|
printf("-> lcn 0x%llx target 0x%x length %d\n",
|
|
(long long)lcn, (int)target, (int)length);
|
|
}
|
|
xsize = vol->indx_record_size;
|
|
if (optv > 1) {
|
|
printf("-> existing record :\n");
|
|
dump(&buffer[target], length);
|
|
}
|
|
if ((target + length) <= (count << clusterbits)) {
|
|
changed = memcmp(buffer + target, data, length);
|
|
err = 0;
|
|
if (changed) {
|
|
memcpy(buffer + target, data, length);
|
|
if (optv > 1) {
|
|
printf("-> new record :\n");
|
|
dump(buffer + target, length);
|
|
}
|
|
err = write_protected(vol, &action->record,
|
|
buffer, xsize);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> data record %s\n",
|
|
(changed ? "updated" : "unchanged"));
|
|
}
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
/*
|
|
* Add one or more resident attributes
|
|
*/
|
|
|
|
static int add_resident(ntfs_volume *vol, const struct ACTION_RECORD *action,
|
|
char *buffer, const char *data, u32 target,
|
|
u32 length, u32 oldlength)
|
|
{
|
|
LCN lcn;
|
|
MFT_RECORD *entry;
|
|
int err;
|
|
BOOL found;
|
|
int resize;
|
|
|
|
err = 1;
|
|
if (optv > 1) {
|
|
lcn = le64_to_cpu(action->record.lcn_list[0]);
|
|
printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
|
|
(long long)inode_number(&action->record),
|
|
(long long)lcn, (int)target, (int)length);
|
|
}
|
|
entry = (MFT_RECORD*)buffer;
|
|
resize = length - oldlength;
|
|
if (optv > 1) {
|
|
printf("existing data :\n");
|
|
dump(buffer + target,length);
|
|
}
|
|
if (!(length & 7)
|
|
&& !(oldlength & 7)
|
|
&& ((target + length) <= mftrecsz)) {
|
|
/* This has to be an idempotent action */
|
|
err = 0;
|
|
if (data && length)
|
|
found = !memcmp(buffer + target,
|
|
data, length);
|
|
else {
|
|
found = TRUE;
|
|
err = 1;
|
|
}
|
|
if (!found && !err) {
|
|
/* Make space to insert the entry */
|
|
memmove(buffer + target + resize,
|
|
buffer + target,
|
|
mftrecsz - target - resize);
|
|
if (data)
|
|
memcpy(buffer + target, data, length);
|
|
else
|
|
memset(buffer + target, 0, length);
|
|
resize_attribute(entry, NULL, NULL,
|
|
resize, resize);
|
|
if (optv > 1) {
|
|
printf("new data at same location :\n");
|
|
dump(buffer + target, length);
|
|
}
|
|
err = write_protected(vol, &action->record,
|
|
buffer, mftrecsz);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> MFT record %s\n",
|
|
(found ? "unchanged" : "expanded"));
|
|
}
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
/*
|
|
* Add one or more non-resident records
|
|
*/
|
|
|
|
static int delete_non_resident(void /*ntfs_volume *vol,
|
|
const struct ACTION_RECORD *action,
|
|
const char *data, u32 target, u32 length, u32 oldlength*/)
|
|
{
|
|
int err;
|
|
|
|
err = 1;
|
|
printf("** delete_non_resident() not implemented\n");
|
|
return (err);
|
|
}
|
|
|
|
/*
|
|
* Expand a single resident attribute
|
|
*/
|
|
|
|
static int expand_resident(ntfs_volume *vol, const struct ACTION_RECORD *action,
|
|
char *buffer, const char *data, u32 target,
|
|
u32 length, u32 oldlength)
|
|
{
|
|
LCN lcn;
|
|
ATTR_RECORD *attr;
|
|
MFT_RECORD *entry;
|
|
int err;
|
|
BOOL found;
|
|
int resize;
|
|
u16 base;
|
|
|
|
err = 1;
|
|
if (optv > 1) {
|
|
lcn = le64_to_cpu(action->record.lcn_list[0]);
|
|
printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
|
|
(long long)inode_number(&action->record),
|
|
(long long)lcn, (int)target, (int)length);
|
|
}
|
|
entry = (MFT_RECORD*)buffer;
|
|
attr = (ATTR_RECORD*)(buffer
|
|
+ le16_to_cpu(action->record.record_offset));
|
|
if (optv > 1) {
|
|
printf("existing data :\n");
|
|
dump(buffer + target,length);
|
|
}
|
|
base = 24 + 2*attr->name_length;
|
|
resize = ((base + length - 1) | 7)
|
|
- ((base + oldlength - 1) | 7);
|
|
if ((target + length) <= mftrecsz) {
|
|
/* This has to be an idempotent action */
|
|
// TODO This test is wrong !
|
|
found = le32_to_cpu(attr->value_length) == length;
|
|
if (found && data && length)
|
|
found = !memcmp(buffer + target, data, length);
|
|
err = 0;
|
|
if (!found) {
|
|
/* Make space to insert the entry */
|
|
memmove(buffer + target + resize,
|
|
buffer + target,
|
|
mftrecsz - target - resize);
|
|
// TODO what to do if length is not a multiple of 8 ?
|
|
if (data)
|
|
memcpy(buffer + target, data, length);
|
|
else
|
|
memset(buffer + target, 0, length);
|
|
resize_attribute(entry, attr, NULL,
|
|
length - oldlength, resize);
|
|
if (optv > 1) {
|
|
printf("new data at same location :\n");
|
|
dump(buffer + target, length);
|
|
}
|
|
err = write_protected(vol, &action->record,
|
|
buffer, mftrecsz);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> MFT record %s\n",
|
|
(found ? "unchanged" : "expanded"));
|
|
}
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
/*
|
|
* Add one or more non-resident records
|
|
*/
|
|
|
|
static int add_non_resident(void /*ntfs_volume *vol,
|
|
const struct ACTION_RECORD *action,
|
|
const char *data, u32 target, u32 length, u32 oldlength*/)
|
|
{
|
|
int err;
|
|
|
|
printf("** add_non_resident() not implemented\n");
|
|
err = 0;
|
|
return (err);
|
|
}
|
|
|
|
/*
|
|
* Generic insert a new resident attribute
|
|
*/
|
|
|
|
static int insert_resident(ntfs_volume *vol, const struct ACTION_RECORD *action,
|
|
char *buffer, const char *data, u32 target,
|
|
u32 length)
|
|
{
|
|
LCN lcn;
|
|
ATTR_RECORD *attr;
|
|
const ATTR_RECORD *newattr;
|
|
MFT_RECORD *entry;
|
|
u32 newused;
|
|
u16 links;
|
|
int err;
|
|
BOOL found;
|
|
|
|
err = 1;
|
|
if (optv > 1) {
|
|
lcn = le64_to_cpu(action->record.lcn_list[0]);
|
|
printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
|
|
(long long)inode_number(&action->record),
|
|
(long long)lcn, (int)target, (int)length);
|
|
}
|
|
entry = (MFT_RECORD*)buffer;
|
|
attr = (ATTR_RECORD*)(buffer
|
|
+ le16_to_cpu(action->record.record_offset));
|
|
newattr = (const ATTR_RECORD*)data;
|
|
if (optv > 1) {
|
|
printf("existing record :\n");
|
|
dump(buffer + target,length);
|
|
if (le32_to_cpu(attr->type) < le32_to_cpu(newattr->type)) {
|
|
printf("** Bad attribute order, full record :\n");
|
|
dump(buffer, mftrecsz);
|
|
}
|
|
}
|
|
/* Types must be in ascending order */
|
|
if (valid_type(attr->type)
|
|
&& (le32_to_cpu(attr->type)
|
|
>= le32_to_cpu(newattr->type))
|
|
&& !(length & 7)
|
|
&& ((target + length) <= mftrecsz)) {
|
|
/* This has to be an idempotent action */
|
|
found = !memcmp(buffer + target, data, length);
|
|
err = 0;
|
|
if (!found) {
|
|
/* Make space to insert the entry */
|
|
memmove(buffer + target + length,
|
|
buffer + target,
|
|
mftrecsz - target - length);
|
|
memcpy(buffer + target, data, length);
|
|
newused = le32_to_cpu(entry->bytes_in_use)
|
|
+ length;
|
|
entry->bytes_in_use = cpu_to_le32(newused);
|
|
if (action->record.redo_operation
|
|
== const_cpu_to_le16(CreateAttribute)) {
|
|
/*
|
|
* For a real create, may have to adjust
|
|
* the next attribute instance
|
|
*/
|
|
adjust_instance(newattr, entry, 1);
|
|
}
|
|
if (newattr->type == AT_FILE_NAME) {
|
|
links = le16_to_cpu(entry->link_count) + 1;
|
|
entry->link_count = cpu_to_le16(links);
|
|
}
|
|
if (optv > 1) {
|
|
printf("expanded record (now 0x%x"
|
|
" bytes used) :\n",
|
|
(int)newused);
|
|
dump(buffer + target, 2*length);
|
|
}
|
|
err = write_protected(vol, &action->record,
|
|
buffer, mftrecsz);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> MFT record %s\n",
|
|
(found ? "unchanged" : "expanded"));
|
|
}
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
/*
|
|
* Generic remove a single resident attribute
|
|
*/
|
|
|
|
static int remove_resident(ntfs_volume *vol, const struct ACTION_RECORD *action,
|
|
char *buffer, const char *data, u32 target,
|
|
u32 length)
|
|
{
|
|
LCN lcn;
|
|
ATTR_RECORD *attr;
|
|
MFT_RECORD *entry;
|
|
u32 newused;
|
|
u16 links;
|
|
int err;
|
|
BOOL found;
|
|
|
|
err = 1;
|
|
if (optv > 1) {
|
|
lcn = le64_to_cpu(action->record.lcn_list[0]);
|
|
printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
|
|
(long long)inode_number(&action->record),
|
|
(long long)lcn, (int)target, (int)length);
|
|
}
|
|
entry = (MFT_RECORD*)buffer;
|
|
attr = (ATTR_RECORD*)(buffer
|
|
+ le16_to_cpu(action->record.record_offset));
|
|
if (optv > 1) {
|
|
printf("existing record :\n");
|
|
dump(buffer + target,length);
|
|
}
|
|
if (!(length & 7)
|
|
&& ((target + length) <= mftrecsz)) {
|
|
/* This has to be an idempotent action */
|
|
/* For AT_DATA the value is not always present */
|
|
if (attr->type == AT_DATA)
|
|
found = !memcmp(buffer + target, data,
|
|
le16_to_cpu(attr->value_offset));
|
|
else
|
|
found = !memcmp(buffer + target, data, length);
|
|
if (!found && optv) {
|
|
printf("data 0x%lx 0x%lx offset %d %ld\n",
|
|
(long)le32_to_cpu(attr->type),
|
|
(long)le32_to_cpu(AT_DATA),
|
|
(int)offsetof(ATTR_RECORD, resident_end),
|
|
(long)le16_to_cpu(attr->value_offset));
|
|
printf("The existing record does not match (%d/%d)\n",
|
|
(int)matchcount(buffer + target, data,
|
|
length),(int)length);
|
|
dump(data,length);
|
|
printf("full attr :\n");
|
|
dump((const char*)attr,mftrecsz
|
|
- le16_to_cpu(action->record.record_offset));
|
|
}
|
|
err = 0;
|
|
if (found) {
|
|
if (attr->type == AT_FILE_NAME) {
|
|
links = le16_to_cpu(entry->link_count) - 1;
|
|
entry->link_count = cpu_to_le16(links);
|
|
}
|
|
if (action->record.redo_operation
|
|
== const_cpu_to_le16(CreateAttribute)) {
|
|
adjust_instance(attr, entry, -1);
|
|
}
|
|
/* Remove the entry */
|
|
memmove(buffer + target,
|
|
buffer + target + length,
|
|
mftrecsz - target - length);
|
|
newused = le32_to_cpu(entry->bytes_in_use) - length;
|
|
entry->bytes_in_use = cpu_to_le32(newused);
|
|
if (optv > 1) {
|
|
printf("new record at same location"
|
|
" (now 0x%x bytes used) :\n",
|
|
(int)newused);
|
|
dump(buffer + target, length);
|
|
}
|
|
err = write_protected(vol, &action->record,
|
|
buffer, mftrecsz);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> MFT record %s\n",
|
|
(found ? "shrinked" : "unchanged"));
|
|
}
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
/*
|
|
* Delete one or more resident attributes
|
|
*/
|
|
|
|
static int delete_resident(ntfs_volume *vol, const struct ACTION_RECORD *action,
|
|
char *buffer, const char *data, u32 target,
|
|
u32 length, u32 oldlength)
|
|
{
|
|
LCN lcn;
|
|
MFT_RECORD *entry;
|
|
int err;
|
|
BOOL found;
|
|
int resize;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
if (optv > 1) {
|
|
lcn = le64_to_cpu(action->record.lcn_list[0]);
|
|
printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
|
|
(long long)inode_number(&action->record),
|
|
(long long)lcn, (int)target, (int)length);
|
|
}
|
|
entry = (MFT_RECORD*)buffer;
|
|
if (optv > 1) {
|
|
printf("existing data :\n");
|
|
dump(buffer + target,length);
|
|
}
|
|
resize = length - oldlength;
|
|
if (!(length & 7)
|
|
&& !(oldlength & 7)
|
|
&& ((target + oldlength) <= mftrecsz)) {
|
|
/* This has to be an idempotent action */
|
|
err = 0;
|
|
if (data && length)
|
|
found = !memcmp(buffer + target, data, length);
|
|
else {
|
|
found = FALSE;
|
|
err = 1;
|
|
}
|
|
if (!found && !err) {
|
|
/* Remove the entry, if present */
|
|
memmove(buffer + target,
|
|
buffer + target - resize,
|
|
mftrecsz - target + resize);
|
|
resize_attribute(entry, NULL, NULL,
|
|
length - oldlength, resize);
|
|
if (optv > 1) {
|
|
printf("new data at same location :\n");
|
|
dump(buffer + target, length);
|
|
}
|
|
err = write_protected(vol, &action->record,
|
|
buffer, mftrecsz);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> MFT record %s\n",
|
|
(found ? "unchanged" : "shrinked"));
|
|
}
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
static int shrink_resident(ntfs_volume *vol, const struct ACTION_RECORD *action,
|
|
char *buffer, const char *data, u32 target,
|
|
u32 length, u32 oldlength)
|
|
{
|
|
LCN lcn;
|
|
ATTR_RECORD *attr;
|
|
MFT_RECORD *entry;
|
|
int err;
|
|
BOOL found;
|
|
int resize;
|
|
u16 base;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
if (optv > 1) {
|
|
lcn = le64_to_cpu(action->record.lcn_list[0]);
|
|
printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
|
|
(long long)inode_number(&action->record),
|
|
(long long)lcn, (int)target, (int)length);
|
|
}
|
|
entry = (MFT_RECORD*)buffer;
|
|
attr = (ATTR_RECORD*)(buffer
|
|
+ le16_to_cpu(action->record.record_offset));
|
|
if (optv > 1) {
|
|
printf("existing data :\n");
|
|
dump(buffer + target,length);
|
|
}
|
|
base = 24 + 2*attr->name_length;
|
|
resize = ((base + length - 1) | 7)
|
|
- ((base + oldlength - 1) | 7);
|
|
if ((oldlength > length)
|
|
// TODO limit to attr length
|
|
&& ((target + oldlength) <= mftrecsz)) {
|
|
/* This has to be an idempotent action */
|
|
if (data && length)
|
|
found = !memcmp(buffer + target, data, length);
|
|
else
|
|
{
|
|
// TODO wrong : need checking against the old data, but in known cases
|
|
// redo data is not available either and existing data is not zero.
|
|
found = FALSE;
|
|
printf("* fake test, assuming not shrinked : value length %ld length %ld oldlength %ld\n",(long)le32_to_cpu(attr->value_length),(long)length,(long)oldlength);
|
|
//dump(buffer + target, oldlength);
|
|
}
|
|
err = 0;
|
|
if (!found) {
|
|
if (length) {
|
|
/* Relocate end of record */
|
|
// TODO restrict to bytes_in_use
|
|
memmove(buffer + target + length,
|
|
buffer + target + oldlength,
|
|
mftrecsz - target - oldlength);
|
|
/* Insert new data or zeroes */
|
|
if (data)
|
|
memcpy(buffer + target, data, length);
|
|
else
|
|
memset(buffer + target, 0, length);
|
|
} else {
|
|
/* Remove the entry, unless targeted size */
|
|
memmove(buffer + target,
|
|
buffer + target - resize,
|
|
mftrecsz - target + resize);
|
|
}
|
|
resize_attribute(entry, attr, NULL,
|
|
length - oldlength, resize);
|
|
if (optv > 1) {
|
|
printf("new data at same location :\n");
|
|
dump(buffer + target, length);
|
|
}
|
|
err = write_protected(vol, &action->record,
|
|
buffer, mftrecsz);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> MFT record %s\n",
|
|
(found ? "unchanged" : "shrinked"));
|
|
}
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
static int update_index(ntfs_volume *vol, const struct ACTION_RECORD *action,
|
|
char *buffer, const char *data, u32 target, u32 length)
|
|
{
|
|
LCN lcn;
|
|
INDEX_BLOCK *indx;
|
|
u32 xsize;
|
|
BOOL changed;
|
|
int err;
|
|
|
|
err = 1;
|
|
if (optv > 1) {
|
|
lcn = le64_to_cpu(action->record.lcn_list[0]);
|
|
printf("-> lcn 0x%llx target 0x%x length %d\n",
|
|
(long long)lcn, (int)target, (int)length);
|
|
}
|
|
xsize = vol->indx_record_size;
|
|
indx = (INDEX_BLOCK*)(buffer
|
|
+ le16_to_cpu(action->record.record_offset));
|
|
if (optv > 1) {
|
|
printf("-> existing index :\n");
|
|
dump(&buffer[target], length);
|
|
}
|
|
if ((indx->magic == magic_INDX)
|
|
&& !(length & 7)
|
|
&& ((target + length) <= xsize)) {
|
|
/* This has to be an idempotent action */
|
|
changed = memcmp(buffer + target, data, length);
|
|
err = 0;
|
|
if (changed) {
|
|
/* Update the entry */
|
|
memcpy(buffer + target, data, length);
|
|
if (optv > 1) {
|
|
printf("-> new index :\n");
|
|
dump(&buffer[target], length);
|
|
}
|
|
err = write_protected(vol, &action->record,
|
|
buffer, xsize);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> INDX record %s\n",
|
|
(changed ? "updated" : "unchanged"));
|
|
}
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
/*
|
|
* Controversial deletion of file names, see undo_delete_file()
|
|
*/
|
|
|
|
static int delete_names(char *buffer)
|
|
{
|
|
MFT_RECORD *record;
|
|
ATTR_RECORD *attr;
|
|
u32 used;
|
|
u32 pos;
|
|
int length;
|
|
int cnt;
|
|
|
|
record = (MFT_RECORD*)buffer;
|
|
pos = le16_to_cpu(record->attrs_offset);
|
|
used = le32_to_cpu(record->bytes_in_use);
|
|
cnt = 0;
|
|
do {
|
|
attr = (ATTR_RECORD*)&buffer[pos];
|
|
length = le32_to_cpu(attr->length);
|
|
if (attr->type == AT_FILE_NAME) {
|
|
if (optv)
|
|
showname("Controversial deletion of ",
|
|
&buffer[pos+90], buffer[pos+88] & 255);
|
|
memmove(buffer + pos, buffer + pos + length,
|
|
mftrecsz - pos - length);
|
|
used -= length;
|
|
cnt++;
|
|
} else
|
|
pos += length;
|
|
} while ((pos < used)
|
|
&& (le32_to_cpu(attr->type) <= le32_to_cpu(AT_FILE_NAME)));
|
|
record->bytes_in_use = cpu_to_le32(used);
|
|
record->link_count = cpu_to_le16(0);
|
|
return (cnt ? 0 : 1);
|
|
}
|
|
|
|
static int rebuildname(const INDEX_ENTRY *index)
|
|
{
|
|
ATTR_RECORD *attr;
|
|
int headlth;
|
|
int datalth;
|
|
|
|
datalth = le16_to_cpu(index->length)
|
|
- offsetof(INDEX_ENTRY,key.file_name);
|
|
headlth = offsetof(ATTR_RECORD,resident_end);
|
|
attr = (ATTR_RECORD*)malloc(headlth + datalth);
|
|
if (attr) {
|
|
attr->type = AT_FILE_NAME;
|
|
attr->length = cpu_to_le32(headlth + datalth);
|
|
attr->non_resident = 0;
|
|
attr->name_length = 0;
|
|
attr->name_offset = const_cpu_to_le16(0);
|
|
attr->flags = const_cpu_to_le16(0);
|
|
attr->instance = const_cpu_to_le16(0);
|
|
attr->value_length = cpu_to_le32(
|
|
2*index->key.file_name.file_name_length
|
|
+ offsetof(FILE_NAME_ATTR, file_name));
|
|
attr->value_offset = cpu_to_le16(headlth);
|
|
attr->resident_flags = RESIDENT_ATTR_IS_INDEXED;
|
|
memcpy(attr->resident_end, &index->key.file_name, datalth);
|
|
free(attr);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Controversial creation of an index allocation attribute
|
|
*
|
|
* This is useful for turning the clock backward, but cannot
|
|
* work properly in the general case and must not be used for
|
|
* a real sync.
|
|
* The main problem is to synchronize the file names when an
|
|
* inode is reused with a different name.
|
|
*/
|
|
|
|
static int insert_index_allocation(ntfs_volume *vol, char *buffer, u32 offs)
|
|
{
|
|
MFT_RECORD *record;
|
|
ATTR_RECORD *attr;
|
|
u32 used;
|
|
u32 pos;
|
|
u32 xsize;
|
|
u16 instance;
|
|
int length;
|
|
int addedlength;
|
|
int namelength;
|
|
int err;
|
|
static const unsigned char bitmap[] =
|
|
{ 1, 0, 0, 0, 0, 0, 0, 0 } ;
|
|
|
|
err = 1;
|
|
if (opts) {
|
|
printf("** Call to unsupported insert_index_allocation()\n");
|
|
} else {
|
|
record = (MFT_RECORD*)buffer;
|
|
pos = le16_to_cpu(record->attrs_offset);
|
|
used = le32_to_cpu(record->bytes_in_use);
|
|
attr = (ATTR_RECORD*)&buffer[pos];
|
|
while ((pos < used)
|
|
&& (le32_to_cpu(attr->type) < le32_to_cpu(AT_INDEX_ROOT))) {
|
|
pos += le32_to_cpu(attr->length);
|
|
attr = (ATTR_RECORD*)&buffer[pos];
|
|
}
|
|
length = le32_to_cpu(attr->length);
|
|
addedlength = length - 8 /* index allocation */
|
|
+ length - 48; /* bitmap */
|
|
if ((attr->type == AT_INDEX_ROOT)
|
|
&& ((pos + length) == offs)
|
|
&& ((used + addedlength) < mftrecsz)) {
|
|
/* Make space for the attribute */
|
|
memmove(buffer + offs + addedlength, buffer + offs,
|
|
mftrecsz - offs - addedlength);
|
|
record->bytes_in_use = cpu_to_le32(used + addedlength);
|
|
/*
|
|
* Insert an AT_INDEX_ALLOCATION
|
|
*/
|
|
attr = (ATTR_RECORD*)&buffer[offs];
|
|
attr->type = AT_INDEX_ALLOCATION;
|
|
attr->length = cpu_to_le32(length - 8);
|
|
attr->non_resident = 1;
|
|
namelength = buffer[pos + 9] & 255;
|
|
attr->name_length = namelength;
|
|
attr->name_offset = const_cpu_to_le16(0x40);
|
|
memcpy(buffer + offs + 0x40, buffer + pos + 0x18,
|
|
2*namelength);
|
|
attr->flags = const_cpu_to_le16(0);
|
|
/* Should we really take a new instance ? */
|
|
attr->instance = record->next_attr_instance;
|
|
instance = le16_to_cpu(record->next_attr_instance) + 1;
|
|
record->next_attr_instance = cpu_to_le16(instance);
|
|
attr->lowest_vcn = const_cpu_to_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 = le32_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 = le64_to_cpu(action->record.lcn_list[0]);
|
|
printf("-> lcn 0x%llx target 0x%x length %d\n",
|
|
(long long)lcn, (int)target, (int)length);
|
|
}
|
|
xsize = vol->indx_record_size;
|
|
indx = (INDEX_BLOCK*)(buffer
|
|
+ le16_to_cpu(action->record.record_offset));
|
|
if (optv > 1) {
|
|
printf("-> existing record :\n");
|
|
dump(&buffer[target], length);
|
|
}
|
|
if ((indx->magic == magic_INDX)
|
|
&& !(length & 7)
|
|
&& ((target + length) <= xsize)) {
|
|
/* This has to be an idempotent action */
|
|
found = !memcmp(buffer + target, data, length);
|
|
err = 0;
|
|
if (!found) {
|
|
/* Make space to insert the entry */
|
|
memmove(buffer + target + length,
|
|
buffer + target,
|
|
xsize - target - length);
|
|
memcpy(buffer + target, data, length);
|
|
indexlth = le32_to_cpu(indx->index.index_length)
|
|
+ length;
|
|
indx->index.index_length = cpu_to_le32(indexlth);
|
|
if (optv > 1) {
|
|
printf("-> inserted record :\n");
|
|
dump(&buffer[target], length);
|
|
}
|
|
err = write_protected(vol, &action->record,
|
|
buffer, xsize);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> INDX record %s\n",
|
|
(found ? "unchanged" : "inserted"));
|
|
}
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
static int redo_add_root_index(ntfs_volume *vol,
|
|
const struct ACTION_RECORD *action, char *buffer)
|
|
{
|
|
LCN lcn;
|
|
const char *data;
|
|
ATTR_RECORD *attr;
|
|
MFT_RECORD *entry;
|
|
INDEX_ROOT *index;
|
|
u32 target;
|
|
u32 length;
|
|
int err;
|
|
BOOL found;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
data = ((const char*)&action->record)
|
|
+ get_redo_offset(&action->record);
|
|
length = le16_to_cpu(action->record.redo_length);
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset);
|
|
if (optv > 1) {
|
|
lcn = le64_to_cpu(action->record.lcn_list[0]);
|
|
printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
|
|
(long long)inode_number(&action->record),
|
|
(long long)lcn, (int)target, (int)length);
|
|
}
|
|
entry = (MFT_RECORD*)buffer;
|
|
attr = (ATTR_RECORD*)(buffer
|
|
+ le16_to_cpu(action->record.record_offset));
|
|
index = (INDEX_ROOT*)(((char*)attr)
|
|
+ le16_to_cpu(attr->value_offset));
|
|
if (optv > 1) {
|
|
printf("existing index :\n");
|
|
dump(buffer + target,length);
|
|
}
|
|
if ((attr->type == AT_INDEX_ROOT)
|
|
&& !(length & 7)
|
|
&& ((target + length) <= mftrecsz)) {
|
|
/* This has to be an idempotent action */
|
|
found = !memcmp(buffer + target, data, length);
|
|
err = 0;
|
|
if (!found) {
|
|
/* Make space to insert the entry */
|
|
memmove(buffer + target + length,
|
|
buffer + target,
|
|
mftrecsz - target - length);
|
|
memcpy(buffer + target, data, length);
|
|
resize_attribute(entry, attr, index, length, length);
|
|
if (optv > 1) {
|
|
printf("new index at same location :\n");
|
|
dump(buffer + target, length);
|
|
}
|
|
err = write_protected(vol, &action->record,
|
|
buffer, mftrecsz);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> MFT record %s\n",
|
|
(found ? "unchanged" : "expanded"));
|
|
}
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
static int redo_compensate(ntfs_volume *vol __attribute__((unused)),
|
|
const struct ACTION_RECORD *action,
|
|
char *buffer __attribute__((unused)))
|
|
{
|
|
u64 lsn;
|
|
s64 diff;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
lsn = 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 = le64_to_cpu(action->record.lcn_list[0]);
|
|
printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
|
|
(long long)inode_number(&action->record),
|
|
(long long)lcn, (int)target, (int)length);
|
|
}
|
|
record = (MFT_RECORD*)buffer;
|
|
if (optv > 1) {
|
|
printf("-> existing record :\n");
|
|
dump(buffer,mftrecsz);
|
|
}
|
|
if ((target + length) <= mftrecsz) {
|
|
changed = memcmp(buffer + target, data, length);
|
|
err = 0;
|
|
if (changed || !(record->flags & MFT_RECORD_IN_USE)) {
|
|
memcpy(buffer + target, data, length);
|
|
record->flags |= MFT_RECORD_IN_USE;
|
|
if (optv > 1) {
|
|
printf("-> new record :\n");
|
|
dump(buffer,mftrecsz);
|
|
}
|
|
err = write_protected(vol, &action->record,
|
|
buffer, mftrecsz);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> MFT record %s\n",
|
|
(changed ? "updated" : "unchanged"));
|
|
}
|
|
} else {
|
|
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 = le64_to_cpu(action->record.lcn_list[0]);
|
|
printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
|
|
(long long)inode_number(&action->record),
|
|
(long long)lcn, (int)target, (int)length);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> existing record :\n");
|
|
dump(buffer,mftrecsz);
|
|
}
|
|
record = (MFT_RECORD*)buffer;
|
|
if ((target + length) <= mftrecsz) {
|
|
/* write a void mft entry (needed ?) */
|
|
changed = memcmp(buffer + target, data, length)
|
|
|| (record->flags & MFT_RECORD_IN_USE);
|
|
err = 0;
|
|
if (changed) {
|
|
memcpy(buffer + target, data, length);
|
|
record->flags &= ~MFT_RECORD_IN_USE;
|
|
if (optv > 1) {
|
|
printf("-> new record :\n");
|
|
dump(buffer,mftrecsz);
|
|
}
|
|
err = write_protected(vol, &action->record,
|
|
buffer, mftrecsz);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> MFT record %s\n",
|
|
(changed ? "updated" : "unchanged"));
|
|
}
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
static int redo_delete_index(ntfs_volume *vol,
|
|
const struct ACTION_RECORD *action, char *buffer)
|
|
{
|
|
LCN lcn;
|
|
const char *data;
|
|
INDEX_BLOCK *indx;
|
|
u32 target;
|
|
u32 length;
|
|
u32 xsize;
|
|
u32 indexlth;
|
|
int err;
|
|
BOOL found;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
data = ((const char*)&action->record)
|
|
+ get_undo_offset(&action->record);
|
|
length = le16_to_cpu(action->record.undo_length);
|
|
// TODO merge with undo_add_index ?
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset);
|
|
if (optv > 1) {
|
|
lcn = le64_to_cpu(action->record.lcn_list[0]);
|
|
printf("-> lcn 0x%llx target 0x%x length %d\n",
|
|
(long long)lcn, (int)target, (int)length);
|
|
}
|
|
xsize = vol->indx_record_size;
|
|
indx = (INDEX_BLOCK*)(buffer
|
|
+ le16_to_cpu(action->record.record_offset));
|
|
if (optv > 1) {
|
|
printf("-> existing record :\n");
|
|
dump(&buffer[target], length);
|
|
}
|
|
if ((indx->magic == magic_INDX)
|
|
&& !(length & 7)
|
|
&& ((target + length) <= xsize)) {
|
|
/* This has to be an idempotent action */
|
|
found = !memcmp(buffer + target, data, length);
|
|
err = 0;
|
|
if (found) {
|
|
/* Remove the entry */
|
|
memmove(buffer + target,
|
|
buffer + target + length,
|
|
xsize - target - length);
|
|
indexlth = le32_to_cpu(indx->index.index_length)
|
|
- length;
|
|
indx->index.index_length = cpu_to_le32(indexlth);
|
|
err = write_protected(vol, &action->record,
|
|
buffer, xsize);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> INDX record %s\n",
|
|
(found ? "unchanged" : "removed"));
|
|
}
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
static int redo_delete_root_index(ntfs_volume *vol,
|
|
const struct ACTION_RECORD *action, char *buffer)
|
|
{
|
|
LCN lcn;
|
|
const char *data;
|
|
ATTR_RECORD *attr;
|
|
MFT_RECORD *entry;
|
|
INDEX_ROOT *index;
|
|
BOOL found;
|
|
u32 target;
|
|
u32 length;
|
|
int err;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
data = ((const char*)&action->record)
|
|
+ get_undo_offset(&action->record);
|
|
length = le16_to_cpu(action->record.undo_length);
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset);
|
|
|
|
if (optv > 1) {
|
|
lcn = le64_to_cpu(action->record.lcn_list[0]);
|
|
printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
|
|
(long long)inode_number(&action->record),
|
|
(long long)lcn, (int)target, (int)length);
|
|
}
|
|
entry = (MFT_RECORD*)buffer;
|
|
attr = (ATTR_RECORD*)(buffer
|
|
+ le16_to_cpu(action->record.record_offset));
|
|
index = (INDEX_ROOT*)(((char*)attr)
|
|
+ le16_to_cpu(attr->value_offset));
|
|
if (optv > 1) {
|
|
printf("existing index :\n");
|
|
dump(buffer + target,length);
|
|
}
|
|
if ((attr->type == AT_INDEX_ROOT)
|
|
&& !(length & 7)
|
|
&& ((target + length) <= mftrecsz)) {
|
|
/* This has to be an idempotent action */
|
|
found = !memcmp(buffer + target, data, length);
|
|
err = 0;
|
|
/* Only delete if present */
|
|
if (found) {
|
|
/* Remove the entry */
|
|
memmove(buffer + target,
|
|
buffer + target + length,
|
|
mftrecsz - target - length);
|
|
resize_attribute(entry, attr, index, -length, -length);
|
|
if (optv > 1) {
|
|
printf("new index at same location :\n");
|
|
dump(buffer + target, length);
|
|
}
|
|
err = write_protected(vol, &action->record,
|
|
buffer, mftrecsz);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> MFT record %s\n",
|
|
(found ? "shrinked" : "updated"));
|
|
}
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
static int redo_force_bits(ntfs_volume *vol,
|
|
const struct ACTION_RECORD *action, char *buffer)
|
|
{
|
|
LCN lcn;
|
|
const struct BITMAP_ACTION *data;
|
|
u32 i;
|
|
int err;
|
|
int wanted;
|
|
u32 firstbit;
|
|
u32 count;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
data = (const struct BITMAP_ACTION*)
|
|
(((const char*)&action->record)
|
|
+ get_redo_offset(&action->record));
|
|
firstbit = le32_to_cpu(data->firstbit);
|
|
count = le32_to_cpu(data->count);
|
|
if (action->record.redo_operation
|
|
== const_cpu_to_le16(SetBitsInNonResidentBitMap))
|
|
wanted = 1;
|
|
else
|
|
wanted = 0;
|
|
// TODO consistency undo_offset == redo_offset, etc.
|
|
// firstbit + count < 8*clustersz (multiple clusters possible ?)
|
|
if (optv > 1) {
|
|
lcn = le64_to_cpu(action->record.lcn_list[0]);
|
|
printf("-> lcn 0x%llx firstbit %d count %d wanted %d\n",
|
|
(long long)lcn,(int)firstbit,(int)count,(int)wanted);
|
|
}
|
|
for (i=0; i<count; i++)
|
|
ntfs_bit_set((u8*)buffer, firstbit + i, wanted);
|
|
if (!write_raw(vol, &action->record, buffer)) {
|
|
err = 0;
|
|
if (optv > 1)
|
|
printf("-> record updated\n");
|
|
}
|
|
if (err)
|
|
printf("** redo_clearbits failed\n");
|
|
return (err);
|
|
}
|
|
|
|
static int redo_open_attribute(ntfs_volume *vol __attribute__((unused)),
|
|
const struct ACTION_RECORD *action)
|
|
{
|
|
const char *data;
|
|
struct ATTR *pa;
|
|
const struct ATTR_OLD *attr_old;
|
|
const struct ATTR_NEW *attr_new;
|
|
const char *name;
|
|
le64 inode;
|
|
u32 namelen;
|
|
u32 length;
|
|
u32 extra;
|
|
int err;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
data = ((const char*)&action->record)
|
|
+ get_redo_offset(&action->record);
|
|
length = le16_to_cpu(action->record.redo_length);
|
|
extra = get_extra_offset(&action->record);
|
|
if (action->record.undo_length) {
|
|
name = ((const char*)&action->record) + extra;
|
|
namelen = le32_to_cpu(action->record.client_data_length)
|
|
+ LOG_RECORD_HEAD_SZ - extra;
|
|
/* fix namelen which was aligned modulo 8 */
|
|
namelen = fixnamelen(name, namelen);
|
|
if (optv > 1) {
|
|
printf("-> length %d namelen %d",(int)length,
|
|
(int)namelen);
|
|
showname(", ", name, namelen/2);
|
|
}
|
|
} else {
|
|
name = "";
|
|
namelen = 0;
|
|
}
|
|
pa = getattrentry(le16_to_cpu(action->record.target_attribute),0);
|
|
if (pa) {
|
|
if (optv) {
|
|
/*
|
|
* If the actions have been displayed, the
|
|
* attribute has already been fed. Check
|
|
* whether it matches what we have in store.
|
|
*/
|
|
switch (length) {
|
|
case sizeof(struct ATTR_OLD) :
|
|
attr_old = (const struct ATTR_OLD*)data;
|
|
/* Badly aligned */
|
|
memcpy(&inode, &attr_old->inode, 8);
|
|
err = (MREF(le64_to_cpu(inode)) != pa->inode)
|
|
|| (attr_old->type != pa->type);
|
|
break;
|
|
case sizeof(struct ATTR_NEW) :
|
|
attr_new = (const struct ATTR_NEW*)data;
|
|
err = (MREF(le64_to_cpu(attr_new->inode))
|
|
!= pa->inode)
|
|
|| (attr_new->type != pa->type);
|
|
break;
|
|
default : err = 1;
|
|
}
|
|
if (!err) {
|
|
err = (namelen != pa->namelen)
|
|
|| (namelen
|
|
&& memcmp(name, pa->name, namelen));
|
|
}
|
|
if (optv > 1)
|
|
printf("-> attribute %s the recorded one\n",
|
|
(err ? "does not match" : "matches"));
|
|
} else {
|
|
copy_attribute(pa, data, length);
|
|
pa->namelen = namelen;
|
|
if (namelen)
|
|
memcpy(pa->name, data, namelen);
|
|
err = 0;
|
|
}
|
|
} else
|
|
if (optv)
|
|
printf("* Unrecorded attribute\n");
|
|
return (err);
|
|
}
|
|
|
|
static int redo_sizes(ntfs_volume *vol, const struct ACTION_RECORD *action,
|
|
char *buffer)
|
|
{
|
|
const char *data;
|
|
u32 target;
|
|
u32 length;
|
|
int err;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
data = ((const char*)&action->record)
|
|
+ get_redo_offset(&action->record);
|
|
length = le16_to_cpu(action->record.redo_length);
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset)
|
|
+ offsetof(ATTR_RECORD, allocated_size);
|
|
err = change_resident(vol, action, buffer,
|
|
data, target, length);
|
|
return (err);
|
|
}
|
|
|
|
static int redo_update_index(ntfs_volume *vol,
|
|
const struct ACTION_RECORD *action, char *buffer)
|
|
{
|
|
const char *data;
|
|
u32 target;
|
|
u32 length;
|
|
int err;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
data = ((const char*)&action->record)
|
|
+ get_redo_offset(&action->record);
|
|
length = le16_to_cpu(action->record.redo_length);
|
|
/* target is left-justified to creation time */
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset)
|
|
+ offsetof(INDEX_ENTRY, key.file_name.creation_time);
|
|
err = update_index(vol, action, buffer, data, target, length);
|
|
return (err);
|
|
}
|
|
|
|
static int redo_update_index_value(ntfs_volume *vol,
|
|
const struct ACTION_RECORD *action, char *buffer)
|
|
{
|
|
const char *data;
|
|
u32 length;
|
|
u32 target;
|
|
int err;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
data = ((const char*)&action->record)
|
|
+ get_redo_offset(&action->record);
|
|
length = le16_to_cpu(action->record.redo_length);
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset);
|
|
err = change_index_value(vol, action, buffer, data, target, length);
|
|
return (err);
|
|
}
|
|
|
|
static int redo_update_mapping(ntfs_volume *vol,
|
|
const struct ACTION_RECORD *action, char *buffer)
|
|
{
|
|
LCN lcn;
|
|
const char *data;
|
|
ATTR_RECORD *attr;
|
|
MFT_RECORD *entry;
|
|
u32 target;
|
|
u32 length;
|
|
u32 source;
|
|
u32 alen;
|
|
u32 newused;
|
|
int resize;
|
|
int err;
|
|
int changed;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
data = ((const char*)&action->record)
|
|
+ get_redo_offset(&action->record);
|
|
length = le16_to_cpu(action->record.redo_length);
|
|
resize = length - le16_to_cpu(action->record.undo_length);
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset);
|
|
if (optv > 1) {
|
|
lcn = le64_to_cpu(action->record.lcn_list[0]);
|
|
printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
|
|
(long long)inode_number(&action->record),
|
|
(long long)lcn, (int)target, (int)length);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> existing record :\n");
|
|
dump(&buffer[target], length);
|
|
}
|
|
entry = (MFT_RECORD*)buffer;
|
|
attr = (ATTR_RECORD*)(buffer
|
|
+ le16_to_cpu(action->record.record_offset));
|
|
if (!attr->non_resident) {
|
|
printf("** Error : update_mapping on resident attr\n");
|
|
}
|
|
if (valid_type(attr->type)
|
|
&& attr->non_resident
|
|
&& !(resize & 7)
|
|
&& ((target + length) <= mftrecsz)) {
|
|
changed = memcmp(buffer + target, data, length);
|
|
err = 0;
|
|
if (changed) {
|
|
/* Adjust space for new mapping pairs */
|
|
source = target - resize;
|
|
if (resize > 0) {
|
|
memmove(buffer + target + length,
|
|
buffer + source + length,
|
|
mftrecsz - target - length);
|
|
}
|
|
if (resize < 0) {
|
|
memmove(buffer + target + length,
|
|
buffer + source + length,
|
|
mftrecsz - source - length);
|
|
}
|
|
memcpy(buffer + target, data, length);
|
|
/* Resize the attribute */
|
|
alen = le32_to_cpu(attr->length) + resize;
|
|
attr->length = cpu_to_le32(alen);
|
|
/* Resize the mft record */
|
|
newused = le32_to_cpu(entry->bytes_in_use)
|
|
+ resize;
|
|
entry->bytes_in_use = cpu_to_le32(newused);
|
|
/* Compute the new highest_vcn */
|
|
err = adjust_high_vcn(vol, attr);
|
|
if (optv > 1) {
|
|
printf("-> new record :\n");
|
|
dump(buffer + target, length);
|
|
}
|
|
if (!err) {
|
|
err = write_protected(vol,
|
|
&action->record,
|
|
buffer, mftrecsz);
|
|
}
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> MFT record %s\n",
|
|
(changed ? "updated" : "unchanged"));
|
|
}
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
static int redo_update_resident(ntfs_volume *vol,
|
|
const struct ACTION_RECORD *action, char *buffer)
|
|
{
|
|
LCN lcn;
|
|
const char *data;
|
|
u32 target;
|
|
u32 length;
|
|
u32 oldlength;
|
|
u32 end;
|
|
u32 redo;
|
|
int err;
|
|
int changed;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
end = le32_to_cpu(action->record.client_data_length)
|
|
+ LOG_RECORD_HEAD_SZ;
|
|
length = le16_to_cpu(action->record.redo_length);
|
|
redo = get_redo_offset(&action->record);
|
|
if ((redo + length) > end)
|
|
data = (char*)NULL;
|
|
else
|
|
data = ((const char*)&action->record) + redo;
|
|
oldlength = le16_to_cpu(action->record.undo_length);
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset);
|
|
if (length == oldlength) {
|
|
if (optv > 1) {
|
|
lcn = le64_to_cpu(action->record.lcn_list[0]);
|
|
printf("-> inode %lld lcn 0x%llx target 0x%x"
|
|
" length %d\n",
|
|
(long long)inode_number(&action->record),
|
|
(long long)lcn, (int)target, (int)length);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> existing record :\n");
|
|
dump(&buffer[target], length);
|
|
}
|
|
if ((target + length) <= mftrecsz) {
|
|
changed = memcmp(buffer + target, data, length);
|
|
err = 0;
|
|
if (changed) {
|
|
memcpy(buffer + target, data, length);
|
|
if (optv > 1) {
|
|
printf("-> new record :\n");
|
|
dump(buffer + target, length);
|
|
}
|
|
err = write_protected(vol, &action->record,
|
|
buffer, mftrecsz);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> MFT record %s\n",
|
|
(changed ? "updated" : "unchanged"));
|
|
}
|
|
}
|
|
} else {
|
|
if (length > oldlength)
|
|
err = expand_resident(vol, action, buffer, data,
|
|
target, length, oldlength);
|
|
else
|
|
err = shrink_resident(vol, action, buffer, data,
|
|
target, length, oldlength);
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
static int redo_update_root_index(ntfs_volume *vol,
|
|
const struct ACTION_RECORD *action, char *buffer)
|
|
{
|
|
const char *data;
|
|
const char *expected;
|
|
u32 target;
|
|
u32 length;
|
|
int err;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
data = ((const char*)&action->record)
|
|
+ get_redo_offset(&action->record);
|
|
expected = ((const char*)&action->record)
|
|
+ get_undo_offset(&action->record);
|
|
length = le16_to_cpu(action->record.redo_length);
|
|
/* the fixup is right-justified to the name length */
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset)
|
|
+ offsetof(INDEX_ENTRY, key.file_name.file_name_length)
|
|
- length;
|
|
err = change_resident_expect(vol, action, buffer, data, expected,
|
|
target, length, AT_INDEX_ROOT);
|
|
return (err);
|
|
}
|
|
|
|
static int redo_update_root_vcn(ntfs_volume *vol,
|
|
const struct ACTION_RECORD *action, char *buffer)
|
|
{
|
|
const char *data;
|
|
const char *expected;
|
|
u32 target;
|
|
u32 length;
|
|
int err;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
data = ((const char*)&action->record)
|
|
+ get_redo_offset(&action->record);
|
|
expected = ((const char*)&action->record)
|
|
+ get_undo_offset(&action->record);
|
|
length = le16_to_cpu(action->record.redo_length);
|
|
// length must be 8
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset)
|
|
+ 16; // explanation needed (right justified ?)
|
|
err = change_resident_expect(vol, action, buffer, data, expected,
|
|
target, length, AT_INDEX_ROOT);
|
|
return (err);
|
|
}
|
|
|
|
static int redo_update_value(ntfs_volume *vol,
|
|
const struct ACTION_RECORD *action, char *buffer)
|
|
{
|
|
LCN lcn;
|
|
const char *data;
|
|
u32 length;
|
|
u32 target;
|
|
u32 count;
|
|
u32 redo;
|
|
u32 end;
|
|
u32 i;
|
|
int changed;
|
|
int err;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
length = le16_to_cpu(action->record.redo_length);
|
|
redo = get_redo_offset(&action->record);
|
|
end = le32_to_cpu(action->record.client_data_length)
|
|
+ LOG_RECORD_HEAD_SZ;
|
|
/* sometimes there is no redo data */
|
|
if ((redo + length) > end)
|
|
data = (char*)NULL;
|
|
else
|
|
data = ((const char*)&action->record) + redo;
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset);
|
|
count = le16_to_cpu(action->record.lcns_to_follow);
|
|
if (optv > 1) {
|
|
lcn = le64_to_cpu(action->record.lcn_list[0]);
|
|
printf("-> lcn 0x%llx target 0x%x length %d\n",
|
|
(long long)lcn, (int)target, (int)length);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> existing record :\n");
|
|
dump(&buffer[target], length);
|
|
}
|
|
if ((target + length) <= (count << clusterbits)) {
|
|
if (data)
|
|
changed = memcmp(buffer + target, data, length);
|
|
else {
|
|
for (i=0; (i<length) && !buffer[target+i]; i++) { }
|
|
changed = length && (i < length);
|
|
}
|
|
err = 0;
|
|
if (changed) {
|
|
if (data)
|
|
memcpy(buffer + target, data, length);
|
|
else
|
|
memset(buffer + target, 0, length);
|
|
if (optv > 1) {
|
|
printf("-> new record :\n");
|
|
dump(buffer + target, length);
|
|
}
|
|
err = write_raw(vol, &action->record, buffer);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> data record %s\n",
|
|
(changed ? "updated" : "unchanged"));
|
|
}
|
|
}
|
|
|
|
return (err);
|
|
}
|
|
|
|
static int redo_update_vcn(ntfs_volume *vol,
|
|
const struct ACTION_RECORD *action, char *buffer)
|
|
{
|
|
const char *data;
|
|
u32 target;
|
|
u32 length;
|
|
int err;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
data = ((const char*)&action->record)
|
|
+ get_redo_offset(&action->record);
|
|
length = le16_to_cpu(action->record.redo_length);
|
|
/* target is left-justified to creation time */
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset)
|
|
+ 16; // to better describe
|
|
err = update_index(vol, action, buffer, data, target, length);
|
|
return (err);
|
|
}
|
|
|
|
static int redo_write_end(ntfs_volume *vol,
|
|
const struct ACTION_RECORD *action, char *buffer)
|
|
{
|
|
LCN lcn;
|
|
const char *data;
|
|
u32 target;
|
|
u32 length;
|
|
u32 oldlength;
|
|
u32 end;
|
|
u32 redo;
|
|
int err;
|
|
int changed;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
end = le32_to_cpu(action->record.client_data_length)
|
|
+ LOG_RECORD_HEAD_SZ;
|
|
length = le16_to_cpu(action->record.redo_length);
|
|
redo = get_redo_offset(&action->record);
|
|
if ((redo + length) > end)
|
|
data = (char*)NULL;
|
|
else
|
|
data = ((const char*)&action->record) + redo;
|
|
oldlength = le16_to_cpu(action->record.undo_length);
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset);
|
|
if (length == oldlength) {
|
|
if (optv > 1) {
|
|
lcn = le64_to_cpu(action->record.lcn_list[0]);
|
|
printf("-> inode %lld lcn 0x%llx target 0x%x"
|
|
" length %d\n",
|
|
(long long)inode_number(&action->record),
|
|
(long long)lcn, (int)target, (int)length);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> existing record :\n");
|
|
dump(&buffer[target], length);
|
|
}
|
|
if ((target + length) <= mftrecsz) {
|
|
changed = memcmp(buffer + target, data, length);
|
|
err = 0;
|
|
if (changed) {
|
|
memcpy(buffer + target, data, length);
|
|
if (optv > 1) {
|
|
printf("-> new record :\n");
|
|
dump(buffer + target, length);
|
|
}
|
|
err = write_protected(vol, &action->record,
|
|
buffer, mftrecsz);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> MFT record %s\n",
|
|
(changed ? "updated" : "unchanged"));
|
|
}
|
|
}
|
|
} else {
|
|
if (length > oldlength)
|
|
err = add_resident(vol, action, buffer, data,
|
|
target, length, oldlength);
|
|
else
|
|
err = delete_resident(vol, action, buffer, data,
|
|
target, length, oldlength);
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
static int redo_write_index(ntfs_volume *vol,
|
|
const struct ACTION_RECORD *action, char *buffer)
|
|
{
|
|
LCN lcn;
|
|
const char *data;
|
|
INDEX_BLOCK *indx;
|
|
u32 target;
|
|
u32 length;
|
|
u32 xsize;
|
|
int err;
|
|
int changed;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
data = ((const char*)&action->record)
|
|
+ get_redo_offset(&action->record);
|
|
length = le16_to_cpu(action->record.redo_length);
|
|
/* target is left-justified to creation time */
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset);
|
|
if (optv > 1) {
|
|
lcn = le64_to_cpu(action->record.lcn_list[0]);
|
|
printf("-> lcn 0x%llx target 0x%x length %d\n",
|
|
(long long)lcn, (int)target, (int)length);
|
|
}
|
|
xsize = vol->indx_record_size;
|
|
indx = (INDEX_BLOCK*)buffer;
|
|
if (action->record.record_offset) {
|
|
printf("** Non-null record_offset in redo_write_index()\n");
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> existing index :\n");
|
|
dump(&buffer[target], length);
|
|
}
|
|
if ((indx->magic == magic_INDX)
|
|
&& !(length & 7)
|
|
&& ((target + length) <= xsize)) {
|
|
/* This has to be an idempotent action */
|
|
changed = memcmp(buffer + target, data, length);
|
|
err = 0;
|
|
if (changed) {
|
|
/* Update the entry */
|
|
memcpy(buffer + target, data, length);
|
|
/* If truncating, set the new size */
|
|
indx->index.index_length =
|
|
cpu_to_le32(target + length - 0x18);
|
|
if (optv > 1) {
|
|
printf("-> new index :\n");
|
|
dump(&buffer[target], length);
|
|
}
|
|
err = write_protected(vol, &action->record,
|
|
buffer, xsize);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> INDX record %s\n",
|
|
(changed ? "updated" : "unchanged"));
|
|
}
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
static int undo_action37(ntfs_volume *vol __attribute__((unused)),
|
|
const struct ACTION_RECORD *action,
|
|
char *buffer __attribute__((unused)))
|
|
{
|
|
/*
|
|
const char *data;
|
|
u32 target;
|
|
u32 length;
|
|
*/
|
|
int err;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
/*
|
|
data = ((const char*)&action->record)
|
|
+ get_redo_offset(&action->record);
|
|
length = le16_to_cpu(action->record.redo_length);
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset);
|
|
*/
|
|
printf("* Ignored action-37, inode %lld record :\n",
|
|
(long long)inode_number(&action->record));
|
|
err = 0;
|
|
return (err);
|
|
}
|
|
|
|
static int undo_add_index(ntfs_volume *vol,
|
|
const struct ACTION_RECORD *action, char *buffer)
|
|
{
|
|
LCN lcn;
|
|
const char *data;
|
|
INDEX_BLOCK *indx;
|
|
u32 target;
|
|
u32 length;
|
|
u32 xsize;
|
|
u32 indexlth;
|
|
int err;
|
|
BOOL found;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
data = ((const char*)&action->record)
|
|
+ get_redo_offset(&action->record);
|
|
length = le16_to_cpu(action->record.redo_length);
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset);
|
|
if (optv > 1) {
|
|
lcn = le64_to_cpu(action->record.lcn_list[0]);
|
|
printf("-> lcn 0x%llx target 0x%x length %d\n",
|
|
(long long)lcn, (int)target, (int)length);
|
|
}
|
|
xsize = vol->indx_record_size;
|
|
indx = (INDEX_BLOCK*)(buffer
|
|
+ le16_to_cpu(action->record.record_offset));
|
|
if (optv > 1) {
|
|
printf("-> existing record :\n");
|
|
dump(&buffer[target], length);
|
|
}
|
|
if ((indx->magic == magic_INDX)
|
|
&& !(length & 7)
|
|
&& ((target + length) <= xsize)) {
|
|
/* This has to be an idempotent action */
|
|
found = index_match_undo(buffer + target, data, length);
|
|
err = 0;
|
|
if (found) {
|
|
/* Remove the entry */
|
|
memmove(buffer + target,
|
|
buffer + target + length,
|
|
xsize - target - length);
|
|
indexlth = le32_to_cpu(indx->index.index_length)
|
|
- length;
|
|
indx->index.index_length = cpu_to_le32(indexlth);
|
|
err = write_protected(vol, &action->record,
|
|
buffer, xsize);
|
|
} else {
|
|
sanity_indx(vol,buffer);
|
|
printf("full record :\n");
|
|
dump(buffer,xsize);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> INDX record %s\n",
|
|
(found ? "removed" : "unchanged"));
|
|
}
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
static int undo_add_root_index(ntfs_volume *vol,
|
|
const struct ACTION_RECORD *action, char *buffer)
|
|
{
|
|
LCN lcn;
|
|
const char *data;
|
|
ATTR_RECORD *attr;
|
|
MFT_RECORD *entry;
|
|
INDEX_ROOT *index;
|
|
BOOL found;
|
|
u32 target;
|
|
u32 length;
|
|
int err;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
data = ((const char*)&action->record)
|
|
+ get_redo_offset(&action->record);
|
|
length = le16_to_cpu(action->record.redo_length);
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset);
|
|
if (optv > 1) {
|
|
lcn = le64_to_cpu(action->record.lcn_list[0]);
|
|
printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
|
|
(long long)inode_number(&action->record),
|
|
(long long)lcn, (int)target, (int)length);
|
|
}
|
|
entry = (MFT_RECORD*)buffer;
|
|
attr = (ATTR_RECORD*)(buffer
|
|
+ le16_to_cpu(action->record.record_offset));
|
|
index = (INDEX_ROOT*)(((char*)attr)
|
|
+ le16_to_cpu(attr->value_offset));
|
|
if (optv > 1) {
|
|
printf("existing index :\n");
|
|
dump(buffer + target,length);
|
|
}
|
|
if ((attr->type == AT_INDEX_ROOT)
|
|
&& !(length & 7)
|
|
&& ((target + length) <= mftrecsz)) {
|
|
/* This has to be an idempotent action */
|
|
found = index_match_undo(buffer + target, data, length);
|
|
err = 0;
|
|
if (found && !older_record(entry, &action->record)) {
|
|
/* Remove the entry */
|
|
memmove(buffer + target,
|
|
buffer + target + length,
|
|
mftrecsz - target - length);
|
|
resize_attribute(entry, attr, index, -length, -length);
|
|
if (optv > 1) {
|
|
printf("new index at same location :\n");
|
|
dump(buffer + target, length);
|
|
}
|
|
err = write_protected(vol, &action->record,
|
|
buffer, mftrecsz);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> MFT record %s\n",
|
|
(found ? "shrinked" : "unchanged"));
|
|
}
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
static int undo_create_attribute(ntfs_volume *vol,
|
|
const struct ACTION_RECORD *action, char *buffer)
|
|
{
|
|
const char *data;
|
|
u32 target;
|
|
u32 length;
|
|
int err;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
data = ((const char*)&action->record)
|
|
+ get_redo_offset(&action->record);
|
|
length = le16_to_cpu(action->record.redo_length);
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset);
|
|
if (!action->record.undo_length)
|
|
err = remove_resident(vol, action, buffer, data,
|
|
target, length);
|
|
return (err);
|
|
}
|
|
|
|
static int undo_delete_attribute(ntfs_volume *vol,
|
|
const struct ACTION_RECORD *action, char *buffer)
|
|
{
|
|
const char *data;
|
|
u32 target;
|
|
u32 length;
|
|
int err;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
data = ((const char*)&action->record)
|
|
+ get_undo_offset(&action->record);
|
|
length = le16_to_cpu(action->record.undo_length);
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset);
|
|
if (!action->record.redo_length)
|
|
err = insert_resident(vol, action, buffer, data,
|
|
target, length);
|
|
return (err);
|
|
}
|
|
|
|
static int undo_delete_index(ntfs_volume *vol,
|
|
const struct ACTION_RECORD *action, char *buffer)
|
|
{
|
|
LCN lcn;
|
|
const char *data;
|
|
INDEX_BLOCK *indx;
|
|
u32 target;
|
|
u32 length;
|
|
u32 xsize;
|
|
u32 indexlth;
|
|
int err;
|
|
BOOL found;
|
|
|
|
// MERGE with redo_add_root_index() ?
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
data = ((const char*)&action->record)
|
|
+ get_undo_offset(&action->record);
|
|
length = le16_to_cpu(action->record.undo_length);
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset);
|
|
if (optv > 1) {
|
|
lcn = le64_to_cpu(action->record.lcn_list[0]);
|
|
printf("-> lcn 0x%llx target 0x%x length %d\n",
|
|
(long long)lcn, (int)target, (int)length);
|
|
}
|
|
xsize = vol->indx_record_size;
|
|
indx = (INDEX_BLOCK*)(buffer
|
|
+ le16_to_cpu(action->record.record_offset));
|
|
if (optv > 1) {
|
|
printf("-> existing record :\n");
|
|
dump(&buffer[target], length);
|
|
}
|
|
if ((indx->magic == magic_INDX)
|
|
&& !(length & 7)
|
|
&& ((target + length) <= xsize)
|
|
&& !sanity_indx(vol,buffer)) {
|
|
/* This has to be an idempotent action */
|
|
found = !memcmp(buffer + target, data, length);
|
|
err = 0;
|
|
if (!found) {
|
|
/* Make space to insert the entry */
|
|
memmove(buffer + target + length,
|
|
buffer + target,
|
|
xsize - target - length);
|
|
memcpy(buffer + target, data, length);
|
|
indexlth = le32_to_cpu(indx->index.index_length)
|
|
+ length;
|
|
indx->index.index_length = cpu_to_le32(indexlth);
|
|
if (optv > 1) {
|
|
printf("-> inserted record :\n");
|
|
dump(&buffer[target], length);
|
|
}
|
|
/* rebuildname() has no effect currently, should drop */
|
|
rebuildname((const INDEX_ENTRY*)data);
|
|
err = write_protected(vol, &action->record,
|
|
buffer, xsize);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> INDX record %s\n",
|
|
(found ? "unchanged" : "inserted"));
|
|
}
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
static int undo_delete_root_index(ntfs_volume *vol,
|
|
const struct ACTION_RECORD *action, char *buffer)
|
|
{
|
|
LCN lcn;
|
|
const char *data;
|
|
ATTR_RECORD *attr;
|
|
MFT_RECORD *entry;
|
|
INDEX_ROOT *index;
|
|
u32 target;
|
|
u32 length;
|
|
int err;
|
|
BOOL found;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
data = ((const char*)&action->record)
|
|
+ get_undo_offset(&action->record);
|
|
length = le16_to_cpu(action->record.undo_length);
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset);
|
|
if (optv > 1) {
|
|
lcn = le64_to_cpu(action->record.lcn_list[0]);
|
|
printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
|
|
(long long)inode_number(&action->record),
|
|
(long long)lcn, (int)target, (int)length);
|
|
}
|
|
entry = (MFT_RECORD*)buffer;
|
|
attr = (ATTR_RECORD*)(buffer
|
|
+ le16_to_cpu(action->record.record_offset));
|
|
index = (INDEX_ROOT*)(((char*)attr)
|
|
+ le16_to_cpu(attr->value_offset));
|
|
if (attr->type != AT_INDEX_ROOT) {
|
|
printf("** Unexpected attr type 0x%lx\n",
|
|
(long)le32_to_cpu(attr->type));
|
|
printf("existing mft\n");
|
|
dump((char*)buffer,512);
|
|
printf("existing index\n");
|
|
dump(buffer + target,length);
|
|
}
|
|
if (optv > 1) {
|
|
printf("existing index :\n");
|
|
dump(buffer + target,length);
|
|
}
|
|
if ((attr->type == AT_INDEX_ROOT)
|
|
&& !(length & 7)
|
|
&& ((target + length) <= mftrecsz)) {
|
|
/* This has to be an idempotent action */
|
|
found = !memcmp(buffer + target, data, length);
|
|
err = 0;
|
|
/* Do not insert if present */
|
|
if (!found) {
|
|
/* Make space to insert the entry */
|
|
memmove(buffer + target + length,
|
|
buffer + target,
|
|
mftrecsz - target - length);
|
|
memcpy(buffer + target, data, length);
|
|
resize_attribute(entry, attr, index, length, length);
|
|
if (optv > 1) {
|
|
printf("new index :\n");
|
|
dump(buffer + target, length);
|
|
}
|
|
err = write_protected(vol, &action->record,
|
|
buffer, mftrecsz);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> MFT record %s\n",
|
|
(found ? "unchanged" : "expanded"));
|
|
}
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
static int undo_create_file(ntfs_volume *vol,
|
|
const struct ACTION_RECORD *action, char *buffer)
|
|
{
|
|
LCN lcn;
|
|
const char *data;
|
|
MFT_RECORD *record;
|
|
u32 target;
|
|
u32 length;
|
|
int err;
|
|
int changed;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
/* redo initialize, clearing the in_use flag ? */
|
|
data = ((const char*)&action->record)
|
|
+ get_redo_offset(&action->record);
|
|
length = le16_to_cpu(action->record.redo_length);
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset);
|
|
if (optv > 1) {
|
|
lcn = le64_to_cpu(action->record.lcn_list[0]);
|
|
printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
|
|
(long long)inode_number(&action->record),
|
|
(long long)lcn, (int)target, (int)length);
|
|
}
|
|
record = (MFT_RECORD*)buffer;
|
|
if (optv > 1) {
|
|
printf("-> existing record :\n");
|
|
dump(buffer,mftrecsz);
|
|
}
|
|
if ((target + length) <= mftrecsz) {
|
|
changed = memcmp(buffer + target, data, length);
|
|
err = 0;
|
|
if (changed || (record->flags & MFT_RECORD_IN_USE)) {
|
|
memcpy(buffer + target, data, length);
|
|
record->flags &= ~MFT_RECORD_IN_USE;
|
|
if (optv > 1) {
|
|
printf("-> new record :\n");
|
|
dump(buffer,mftrecsz);
|
|
}
|
|
err = write_protected(vol, &action->record,
|
|
buffer, mftrecsz);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> MFT record %s\n",
|
|
(changed ? "updated" : "unchanged"));
|
|
}
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
static int undo_delete_file(ntfs_volume *vol,
|
|
const struct ACTION_RECORD *action, char *buffer)
|
|
{
|
|
LCN lcn;
|
|
const char *data;
|
|
MFT_RECORD *record;
|
|
u32 target;
|
|
u32 length;
|
|
int err;
|
|
int changed;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
data = ((const char*)&action->record)
|
|
+ get_undo_offset(&action->record);
|
|
length = le16_to_cpu(action->record.undo_length);
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset);
|
|
if (optv > 1) {
|
|
lcn = le64_to_cpu(action->record.lcn_list[0]);
|
|
printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
|
|
(long long)inode_number(&action->record),
|
|
(long long)lcn, (int)target, (int)length);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> existing record :\n");
|
|
dump(buffer,mftrecsz);
|
|
}
|
|
record = (MFT_RECORD*)buffer;
|
|
if ((target + length) <= mftrecsz) {
|
|
changed = memcmp(buffer + target, data, length)
|
|
|| !(record->flags & MFT_RECORD_IN_USE);
|
|
err = 0;
|
|
if (changed) {
|
|
memcpy(buffer + target, data, length);
|
|
/*
|
|
* Unclear what we should do for recreating a file.
|
|
* Only 24 bytes are available, the used length is not known,
|
|
* the number of links suggests we should keep the current
|
|
* names... If so, when will they be deleted ?
|
|
* We will have to make stamp changes in the standard
|
|
* information attribute, so better not to delete it.
|
|
* Should we create a data or index attribute ?
|
|
* Here, we assume we should delete the file names when
|
|
* the record now appears to not be in use and there are
|
|
* links.
|
|
*/
|
|
if (record->link_count
|
|
&& !(record->flags & MFT_RECORD_IN_USE))
|
|
err = delete_names(buffer);
|
|
record->flags |= MFT_RECORD_IN_USE;
|
|
if (optv > 1) {
|
|
printf("-> new record :\n");
|
|
dump(buffer,mftrecsz);
|
|
}
|
|
if (!err)
|
|
err = write_protected(vol,
|
|
&action->record,
|
|
buffer, mftrecsz);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> MFT record %s\n",
|
|
(changed ? "updated" : "unchanged"));
|
|
}
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
static int undo_force_bits(ntfs_volume *vol,
|
|
const struct ACTION_RECORD *action, char *buffer)
|
|
{
|
|
LCN lcn;
|
|
const struct BITMAP_ACTION *data;
|
|
u32 i;
|
|
int err;
|
|
int wanted;
|
|
u32 firstbit;
|
|
u32 count;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
data = (const struct BITMAP_ACTION*)
|
|
(((const char*)&action->record)
|
|
+ get_redo_offset(&action->record));
|
|
firstbit = le32_to_cpu(data->firstbit);
|
|
count = le32_to_cpu(data->count);
|
|
if (action->record.redo_operation
|
|
== const_cpu_to_le16(SetBitsInNonResidentBitMap))
|
|
wanted = 0;
|
|
else
|
|
wanted = 1;
|
|
// TODO consistency undo_offset == redo_offset, etc.
|
|
// firstbit + count < 8*clustersz (multiple clusters possible ?)
|
|
if (optv > 1) {
|
|
lcn = le64_to_cpu(action->record.lcn_list[0]);
|
|
printf("-> lcn 0x%llx firstbit %d count %d wanted %d\n",
|
|
(long long)lcn,(int)firstbit,(int)count,(int)wanted);
|
|
}
|
|
for (i=0; i<count; i++)
|
|
ntfs_bit_set((u8*)buffer, firstbit + i, wanted);
|
|
if (!write_raw(vol, &action->record, buffer)) {
|
|
err = 0;
|
|
if (optv > 1)
|
|
printf("-> record updated\n");
|
|
}
|
|
if (err)
|
|
printf("** redo_clearbits failed\n");
|
|
return (err);
|
|
}
|
|
|
|
static int undo_open_attribute(ntfs_volume *vol __attribute__((unused)),
|
|
const struct ACTION_RECORD *action)
|
|
{
|
|
const char *data;
|
|
struct ATTR *pa;
|
|
const struct ATTR_OLD *attr_old;
|
|
const struct ATTR_NEW *attr_new;
|
|
const char *name;
|
|
le64 inode;
|
|
u32 namelen;
|
|
u32 length;
|
|
u32 extra;
|
|
int err;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
data = ((const char*)&action->record)
|
|
+ get_redo_offset(&action->record);
|
|
length = le16_to_cpu(action->record.redo_length);
|
|
extra = get_extra_offset(&action->record);
|
|
if (action->record.undo_length) {
|
|
name = ((const char*)&action->record) + extra;
|
|
namelen = le32_to_cpu(action->record.client_data_length)
|
|
+ LOG_RECORD_HEAD_SZ - extra;
|
|
/* fix namelen which was aligned modulo 8 */
|
|
namelen = fixnamelen(name, namelen);
|
|
if (optv > 1) {
|
|
printf("-> length %d namelen %d",(int)length,
|
|
(int)namelen);
|
|
showname(", ", name, namelen/2);
|
|
}
|
|
} else {
|
|
namelen = 0;
|
|
name = "";
|
|
}
|
|
pa = getattrentry(le16_to_cpu(action->record.target_attribute),0);
|
|
// TODO Only process is attr is not older ?
|
|
if (pa) {
|
|
/* check whether the redo attr matches what we have in store */
|
|
switch (length) {
|
|
case sizeof(struct ATTR_OLD) :
|
|
attr_old = (const struct ATTR_OLD*)data;
|
|
/* Badly aligned */
|
|
memcpy(&inode, &attr_old->inode, 8);
|
|
err = (MREF(le64_to_cpu(inode)) != pa->inode)
|
|
|| (attr_old->type != pa->type);
|
|
break;
|
|
case sizeof(struct ATTR_NEW) :
|
|
attr_new = (const struct ATTR_NEW*)data;
|
|
err = (MREF(le64_to_cpu(attr_new->inode))!= pa->inode)
|
|
|| (attr_new->type != pa->type);
|
|
break;
|
|
default : err = 1;
|
|
}
|
|
if (!err) {
|
|
err = (namelen != pa->namelen)
|
|
|| (namelen
|
|
&& memcmp(name, pa->name, namelen));
|
|
}
|
|
if (optv > 1)
|
|
printf("-> attribute %s the recorded one\n",
|
|
(err ? "does not match" : "matches"));
|
|
} else
|
|
if (optv)
|
|
printf("* Unrecorded attribute\n");
|
|
err = 0;
|
|
return (err);
|
|
}
|
|
|
|
static int undo_sizes(ntfs_volume *vol,
|
|
const struct ACTION_RECORD *action, char *buffer)
|
|
{
|
|
const char *data;
|
|
MFT_RECORD *entry;
|
|
ATTR_RECORD *attr;
|
|
u32 target;
|
|
u32 length;
|
|
u32 offs;
|
|
int err;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
data = ((const char*)&action->record)
|
|
+ get_undo_offset(&action->record);
|
|
length = le16_to_cpu(action->record.undo_length);
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset)
|
|
+ offsetof(ATTR_RECORD, allocated_size);
|
|
entry = (MFT_RECORD*)buffer;
|
|
if (!(entry->flags & MFT_RECORD_IS_DIRECTORY))
|
|
err = change_resident(vol, action, buffer,
|
|
data, target, length);
|
|
else {
|
|
/* On a directory, may have to build an index allocation */
|
|
offs = le16_to_cpu(action->record.record_offset);
|
|
attr = (ATTR_RECORD*)(buffer + offs);
|
|
if (attr->type != AT_INDEX_ALLOCATION) {
|
|
err = insert_index_allocation(vol, buffer, offs);
|
|
if (!err)
|
|
err = change_resident(vol, action, buffer,
|
|
data, target, length);
|
|
} else
|
|
err = change_resident(vol, action, buffer,
|
|
data, target, length);
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
static int undo_update_index(ntfs_volume *vol, const struct ACTION_RECORD *action,
|
|
char *buffer)
|
|
{
|
|
const char *data;
|
|
u32 target;
|
|
u32 length;
|
|
int err;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
data = ((const char*)&action->record)
|
|
+ get_undo_offset(&action->record);
|
|
length = le16_to_cpu(action->record.undo_length);
|
|
/* target is left-justified to creation time */
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset)
|
|
+ offsetof(INDEX_ENTRY, key.file_name.creation_time);
|
|
err = update_index(vol, action, buffer, data, target, length);
|
|
return (err);
|
|
}
|
|
|
|
static int undo_update_index_value(ntfs_volume *vol,
|
|
const struct ACTION_RECORD *action, char *buffer)
|
|
{
|
|
LCN lcn;
|
|
const char *data;
|
|
u32 length;
|
|
u32 target;
|
|
int changed;
|
|
int err;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
data = ((const char*)&action->record)
|
|
+ get_undo_offset(&action->record);
|
|
length = le16_to_cpu(action->record.undo_length);
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset);
|
|
if (optv > 1) {
|
|
lcn = le64_to_cpu(action->record.lcn_list[0]);
|
|
printf("-> lcn 0x%llx target 0x%x length %d\n",
|
|
(long long)lcn, (int)target, (int)length);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> existing record :\n");
|
|
dump(&buffer[target], length);
|
|
}
|
|
if ((target + length) <= vol->indx_record_size) {
|
|
changed = length && memcmp(buffer + target, data, length);
|
|
err = 0;
|
|
if (changed) {
|
|
memcpy(buffer + target, data, length);
|
|
if (optv > 1) {
|
|
printf("-> new record :\n");
|
|
dump(buffer + target, length);
|
|
}
|
|
err = write_protected(vol, &action->record, buffer,
|
|
vol->indx_record_size);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> data record %s\n",
|
|
(changed ? "updated" : "unchanged"));
|
|
}
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
static int undo_update_vcn(ntfs_volume *vol, const struct ACTION_RECORD *action,
|
|
char *buffer)
|
|
{
|
|
const char *data;
|
|
u32 target;
|
|
u32 length;
|
|
int err;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
data = ((const char*)&action->record)
|
|
+ get_undo_offset(&action->record);
|
|
length = le16_to_cpu(action->record.undo_length);
|
|
/* target is left-justified to creation time */
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset)
|
|
+ 16; // to better describe
|
|
err = update_index(vol, action, buffer, data, target, length);
|
|
return (err);
|
|
}
|
|
|
|
static int undo_update_mapping(ntfs_volume *vol, const struct ACTION_RECORD *action,
|
|
char *buffer)
|
|
{
|
|
LCN lcn;
|
|
const char *data;
|
|
ATTR_RECORD *attr;
|
|
MFT_RECORD *entry;
|
|
u32 target;
|
|
u32 length;
|
|
u32 source;
|
|
u32 alen;
|
|
u32 newused;
|
|
int err;
|
|
int changed;
|
|
int resize;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
data = ((const char*)&action->record)
|
|
+ get_undo_offset(&action->record);
|
|
length = le16_to_cpu(action->record.undo_length);
|
|
resize = length - le16_to_cpu(action->record.redo_length);
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset);
|
|
if (optv > 1) {
|
|
lcn = le64_to_cpu(action->record.lcn_list[0]);
|
|
printf("-> inode %lld lcn 0x%llx target 0x%x new length %d resize %d\n",
|
|
(long long)inode_number(&action->record),
|
|
(long long)lcn, (int)target, (int)length, (int)resize);
|
|
}
|
|
// TODO share with redo_update_mapping()
|
|
if (optv > 1) {
|
|
printf("-> existing record :\n");
|
|
dump(&buffer[target], length);
|
|
}
|
|
entry = (MFT_RECORD*)buffer;
|
|
attr = (ATTR_RECORD*)(buffer
|
|
+ le16_to_cpu(action->record.record_offset));
|
|
if (!attr->non_resident) {
|
|
printf("** Error : update_mapping on resident attr\n");
|
|
}
|
|
if (valid_type(attr->type)
|
|
&& attr->non_resident
|
|
&& !(resize & 7)
|
|
&& ((target + length) <= mftrecsz)) {
|
|
changed = memcmp(buffer + target, data, length);
|
|
err = 0;
|
|
if (changed) {
|
|
/* Adjust space for new mapping pairs */
|
|
source = target - resize;
|
|
if (resize > 0) {
|
|
memmove(buffer + target + length,
|
|
buffer + source + length,
|
|
mftrecsz - target - length);
|
|
}
|
|
if (resize < 0) {
|
|
memmove(buffer + target + length,
|
|
buffer + source + length,
|
|
mftrecsz - source - length);
|
|
}
|
|
memcpy(buffer + target, data, length);
|
|
/* Resize the attribute */
|
|
alen = le32_to_cpu(attr->length) + resize;
|
|
attr->length = cpu_to_le32(alen);
|
|
/* Resize the mft record */
|
|
newused = le32_to_cpu(entry->bytes_in_use)
|
|
+ resize;
|
|
entry->bytes_in_use = cpu_to_le32(newused);
|
|
/* Compute the new highest_vcn */
|
|
err = adjust_high_vcn(vol, attr);
|
|
if (optv > 1) {
|
|
printf("-> new record :\n");
|
|
dump(buffer + target, length);
|
|
}
|
|
if (!err) {
|
|
err = write_protected(vol,
|
|
&action->record, buffer,
|
|
mftrecsz);
|
|
}
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> MFT record %s\n",
|
|
(changed ? "updated" : "unchanged"));
|
|
}
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
static int undo_update_resident(ntfs_volume *vol,
|
|
const struct ACTION_RECORD *action, char *buffer)
|
|
{
|
|
LCN lcn;
|
|
const char *data;
|
|
u32 target;
|
|
u32 length;
|
|
u32 oldlength;
|
|
u32 end;
|
|
u32 undo;
|
|
int err;
|
|
int changed;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
end = le32_to_cpu(action->record.client_data_length)
|
|
+ LOG_RECORD_HEAD_SZ;
|
|
length = le16_to_cpu(action->record.undo_length);
|
|
undo = get_undo_offset(&action->record);
|
|
if ((undo + length) > end)
|
|
data = (char*)NULL;
|
|
else
|
|
data = ((const char*)&action->record) + undo;
|
|
oldlength = le16_to_cpu(action->record.redo_length);
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset);
|
|
if (length == oldlength) {
|
|
if (optv > 1) {
|
|
lcn = le64_to_cpu(action->record.lcn_list[0]);
|
|
printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n",
|
|
(long long)inode_number(&action->record),
|
|
(long long)lcn, (int)target, (int)length);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> existing record :\n");
|
|
dump(&buffer[target], length);
|
|
}
|
|
if ((target + length) <= mftrecsz) {
|
|
changed = memcmp(buffer + target, data, length);
|
|
err = 0;
|
|
if (changed) {
|
|
memcpy(buffer + target, data, length);
|
|
if (optv > 1) {
|
|
printf("-> new record :\n");
|
|
dump(buffer + target, length);
|
|
}
|
|
err = write_protected(vol, &action->record,
|
|
buffer, mftrecsz);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> MFT record %s\n",
|
|
(changed ? "updated" : "unchanged"));
|
|
}
|
|
}
|
|
} else {
|
|
if (length > oldlength)
|
|
err = expand_resident(vol, action, buffer, data,
|
|
target, length, oldlength);
|
|
else
|
|
err = shrink_resident(vol, action, buffer, data,
|
|
target, length, oldlength);
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
static int undo_update_root_index(ntfs_volume *vol,
|
|
const struct ACTION_RECORD *action, char *buffer)
|
|
{
|
|
const char *data;
|
|
const char *expected;
|
|
u32 target;
|
|
u32 length;
|
|
int err;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
data = ((const char*)&action->record)
|
|
+ get_undo_offset(&action->record);
|
|
expected = ((const char*)&action->record)
|
|
+ get_redo_offset(&action->record);
|
|
length = le16_to_cpu(action->record.undo_length);
|
|
/* the fixup is right-justified to the name length */
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset)
|
|
+ offsetof(INDEX_ENTRY, key.file_name.file_name_length)
|
|
- length;
|
|
err = change_resident_expect(vol, action, buffer, data, expected,
|
|
target, length, AT_INDEX_ROOT);
|
|
return (err);
|
|
}
|
|
|
|
static int undo_update_root_vcn(ntfs_volume *vol,
|
|
const struct ACTION_RECORD *action, char *buffer)
|
|
{
|
|
const char *data;
|
|
const char *expected;
|
|
u32 target;
|
|
u32 length;
|
|
int err;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
data = ((const char*)&action->record)
|
|
+ get_undo_offset(&action->record);
|
|
expected = ((const char*)&action->record)
|
|
+ get_redo_offset(&action->record);
|
|
length = le16_to_cpu(action->record.undo_length);
|
|
/* the fixup is right-justified to the name length */
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset)
|
|
+ 16; // explanation needed
|
|
err = change_resident_expect(vol, action, buffer, data, expected,
|
|
target, length, AT_INDEX_ROOT);
|
|
return (err);
|
|
}
|
|
|
|
static int undo_update_value(ntfs_volume *vol,
|
|
const struct ACTION_RECORD *action, char *buffer)
|
|
{
|
|
LCN lcn;
|
|
const char *data;
|
|
u32 length;
|
|
u32 target;
|
|
u32 count;
|
|
int changed;
|
|
int err;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
data = ((const char*)&action->record)
|
|
+ get_undo_offset(&action->record);
|
|
length = le16_to_cpu(action->record.undo_length);
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset);
|
|
count = le16_to_cpu(action->record.lcns_to_follow);
|
|
if (optv > 1) {
|
|
lcn = le64_to_cpu(action->record.lcn_list[0]);
|
|
printf("-> lcn 0x%llx target 0x%x length %d\n",
|
|
(long long)lcn, (int)target, (int)length);
|
|
}
|
|
if (length) {
|
|
if (optv > 1) {
|
|
printf("-> existing record :\n");
|
|
dump(&buffer[target], length);
|
|
}
|
|
if ((target + length) <= (count << clusterbits)) {
|
|
changed = memcmp(buffer + target, data, length);
|
|
err = 0;
|
|
if (changed) {
|
|
memcpy(buffer + target, data, length);
|
|
if (optv > 1) {
|
|
printf("-> new record :\n");
|
|
dump(buffer + target, length);
|
|
}
|
|
err = write_raw(vol, &action->record, buffer);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> data record %s\n",
|
|
(changed ? "updated" : "unchanged"));
|
|
}
|
|
}
|
|
} else {
|
|
/*
|
|
* No undo data, we cannot undo, sometimes the redo
|
|
* data even overflows from record.
|
|
* Just ignore for now.
|
|
*/
|
|
if (optv)
|
|
printf("Cannot undo, there is no undo data\n");
|
|
err = 0;
|
|
}
|
|
|
|
return (err);
|
|
}
|
|
|
|
static int undo_write_end(ntfs_volume *vol,
|
|
const struct ACTION_RECORD *action, char *buffer)
|
|
{
|
|
LCN lcn;
|
|
const char *data;
|
|
u32 target;
|
|
u32 length;
|
|
u32 oldlength;
|
|
u32 end;
|
|
u32 undo;
|
|
int err;
|
|
int changed;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
end = le32_to_cpu(action->record.client_data_length)
|
|
+ LOG_RECORD_HEAD_SZ;
|
|
length = le16_to_cpu(action->record.undo_length);
|
|
undo = get_undo_offset(&action->record);
|
|
if ((undo + length) > end)
|
|
data = (char*)NULL;
|
|
else
|
|
data = ((const char*)&action->record) + undo;
|
|
oldlength = le16_to_cpu(action->record.redo_length);
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset);
|
|
if (length == oldlength) {
|
|
if (optv > 1) {
|
|
lcn = le64_to_cpu(action->record.lcn_list[0]);
|
|
printf("-> inode %lld lcn 0x%llx target 0x%x"
|
|
" length %d\n",
|
|
(long long)inode_number(&action->record),
|
|
(long long)lcn, (int)target, (int)length);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> existing record :\n");
|
|
dump(&buffer[target], length);
|
|
}
|
|
if ((target + length) <= mftrecsz) {
|
|
changed = memcmp(buffer + target, data, length);
|
|
err = 0;
|
|
if (changed) {
|
|
memcpy(buffer + target, data, length);
|
|
if (optv > 1) {
|
|
printf("-> new record :\n");
|
|
dump(buffer + target, length);
|
|
}
|
|
err = write_protected(vol, &action->record,
|
|
buffer, mftrecsz);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> MFT record %s\n",
|
|
(changed ? "updated" : "unchanged"));
|
|
}
|
|
}
|
|
} else {
|
|
if (length > oldlength)
|
|
err = add_resident(vol, action, buffer, data,
|
|
target, length, oldlength);
|
|
else
|
|
err = delete_resident(vol, action, buffer, data,
|
|
target, length, oldlength);
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
static int undo_write_index(ntfs_volume *vol,
|
|
const struct ACTION_RECORD *action, char *buffer)
|
|
{
|
|
LCN lcn;
|
|
const char *data;
|
|
u32 target;
|
|
u32 length;
|
|
u32 oldlength;
|
|
u32 end;
|
|
u32 undo;
|
|
int err;
|
|
int changed;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
end = le32_to_cpu(action->record.client_data_length)
|
|
+ LOG_RECORD_HEAD_SZ;
|
|
length = le16_to_cpu(action->record.undo_length);
|
|
undo = get_undo_offset(&action->record);
|
|
if ((undo + length) > end)
|
|
data = (char*)NULL;
|
|
else
|
|
data = ((const char*)&action->record) + undo;
|
|
oldlength = le16_to_cpu(action->record.redo_length);
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset);
|
|
if (length == oldlength) {
|
|
if (optv > 1) {
|
|
lcn = le64_to_cpu(action->record.lcn_list[0]);
|
|
printf("-> inode %lld lcn 0x%llx target 0x%x"
|
|
" length %d\n",
|
|
(long long)inode_number(&action->record),
|
|
(long long)lcn, (int)target, (int)length);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> existing record :\n");
|
|
dump(&buffer[target], length);
|
|
}
|
|
if ((target + length) <= mftrecsz) {
|
|
changed = memcmp(buffer + target, data, length);
|
|
err = 0;
|
|
if (changed) {
|
|
memcpy(buffer + target, data, length);
|
|
if (optv > 1) {
|
|
printf("-> new record :\n");
|
|
dump(buffer + target, length);
|
|
}
|
|
err = write_protected(vol, &action->record,
|
|
buffer, mftrecsz);
|
|
}
|
|
if (optv > 1) {
|
|
printf("-> MFT record %s\n",
|
|
(changed ? "updated" : "unchanged"));
|
|
}
|
|
}
|
|
} else {
|
|
if (length > oldlength)
|
|
err = add_non_resident(/*vol, action, data,
|
|
target, length, oldlength*/);
|
|
else
|
|
err = delete_non_resident(/*vol, action, data,
|
|
target, length, oldlength*/);
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
enum ACTION_KIND { ON_NONE, ON_MFT, ON_INDX, ON_RAW } ;
|
|
|
|
static enum ACTION_KIND get_action_kind(const struct ACTION_RECORD *action)
|
|
{
|
|
struct ATTR *pa;
|
|
const char *data;
|
|
enum ACTION_KIND kind;
|
|
/*
|
|
* If we are sure the action was defined by Vista
|
|
* or subsequent, just use attribute_flags.
|
|
* Unfortunately, only on some cases we can determine
|
|
* the action was defined by Win10 (or subsequent).
|
|
*/
|
|
if (action->record.log_record_flags
|
|
& const_cpu_to_le16(RECORD_DELETING | RECORD_ADDING)) {
|
|
if (action->record.attribute_flags
|
|
& const_cpu_to_le16(ACTS_ON_INDX))
|
|
kind = ON_INDX;
|
|
else
|
|
if (action->record.attribute_flags
|
|
& const_cpu_to_le16(ACTS_ON_MFT))
|
|
kind = ON_MFT;
|
|
else
|
|
kind = ON_RAW;
|
|
} else {
|
|
/*
|
|
* In other cases, we have to rely on the attribute
|
|
* definition, but this has defects when undoing.
|
|
*/
|
|
pa = getattrentry(le16_to_cpu(
|
|
action->record.target_attribute),0);
|
|
if (!pa || !pa->type) {
|
|
/*
|
|
* Even when the attribute has not been recorded,
|
|
* we can sometimes tell the record does not apply
|
|
* to MFT or INDX : such records always have a zero
|
|
* record_offset, and if attribute_offset is zero, their
|
|
* magic can be checked. If neither condition is true,
|
|
* the action cannot apply to MFT or INDX.
|
|
* (this is useful for undoing)
|
|
*/
|
|
data = (const char*)&action->record
|
|
+ get_redo_offset(&action->record);
|
|
if (action->record.record_offset
|
|
|| (!action->record.attribute_offset
|
|
&& (le16_to_cpu(action->record.redo_length)
|
|
>= 4)
|
|
&& memcmp(data,"FILE",4)
|
|
&& memcmp(data,"INDX",4))) {
|
|
kind = ON_RAW;
|
|
} else {
|
|
printf("** Error : attribute 0x%x"
|
|
" is not defined\n",
|
|
(int)le16_to_cpu(
|
|
action->record.target_attribute));
|
|
kind = ON_NONE;
|
|
}
|
|
} else {
|
|
if (pa->type == AT_INDEX_ALLOCATION)
|
|
kind = ON_INDX;
|
|
else
|
|
kind = ON_RAW;
|
|
}
|
|
}
|
|
return (kind);
|
|
}
|
|
|
|
|
|
/*
|
|
* Display the redo actions which were executed
|
|
*
|
|
* Useful for getting indications on the coverage of a test
|
|
*/
|
|
|
|
void show_redos(void)
|
|
{
|
|
int i;
|
|
|
|
if (optv && redos_met) {
|
|
printf("Redo actions which were executed :\n");
|
|
for (i=0; i<64; i++)
|
|
if ((((u64)1) << i) & redos_met)
|
|
printf("%s\n", actionname(i));
|
|
}
|
|
}
|
|
|
|
static int distribute_redos(ntfs_volume *vol,
|
|
const struct ACTION_RECORD *action, char *buffer)
|
|
{
|
|
int rop, uop;
|
|
int err;
|
|
|
|
err = 0;
|
|
rop = le16_to_cpu(action->record.redo_operation);
|
|
uop = le16_to_cpu(action->record.undo_operation);
|
|
switch (rop) {
|
|
case AddIndexEntryAllocation :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(DeleteIndexEntryAllocation))
|
|
err = redo_add_index(vol, action, buffer);
|
|
break;
|
|
case AddIndexEntryRoot :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(DeleteIndexEntryRoot))
|
|
err = redo_add_root_index(vol, action, buffer);
|
|
break;
|
|
case ClearBitsInNonResidentBitMap :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(SetBitsInNonResidentBitMap))
|
|
err = redo_force_bits(vol, action, buffer);
|
|
break;
|
|
case CompensationlogRecord :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(Noop))
|
|
err = redo_compensate(vol, action, buffer);
|
|
break;
|
|
case CreateAttribute :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(DeleteAttribute))
|
|
err = redo_create_attribute(vol, action, buffer);
|
|
break;
|
|
case DeallocateFileRecordSegment :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(InitializeFileRecordSegment))
|
|
err = redo_delete_file(vol, action, buffer);
|
|
break;
|
|
case DeleteAttribute :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(CreateAttribute))
|
|
err = redo_delete_attribute(vol, action, buffer);
|
|
break;
|
|
case DeleteIndexEntryAllocation :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(AddIndexEntryAllocation))
|
|
err = redo_delete_index(vol, action, buffer);
|
|
break;
|
|
case DeleteIndexEntryRoot :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(AddIndexEntryRoot))
|
|
err = redo_delete_root_index(vol, action, buffer);
|
|
break;
|
|
case InitializeFileRecordSegment :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(Noop))
|
|
err = redo_create_file(vol, action, buffer);
|
|
break;
|
|
case OpenNonResidentAttribute :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(Noop))
|
|
err = redo_open_attribute(vol, action);
|
|
break;
|
|
case SetBitsInNonResidentBitMap :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(ClearBitsInNonResidentBitMap))
|
|
err = redo_force_bits(vol, action, buffer);
|
|
break;
|
|
case SetIndexEntryVcnAllocation :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(SetIndexEntryVcnAllocation))
|
|
err = redo_update_vcn(vol, action, buffer);
|
|
break;
|
|
case SetIndexEntryVcnRoot :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(SetIndexEntryVcnRoot))
|
|
err = redo_update_root_vcn(vol, action, buffer);
|
|
break;
|
|
case SetNewAttributeSizes :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(SetNewAttributeSizes))
|
|
err = redo_sizes(vol, action, buffer);
|
|
break;
|
|
case UpdateFileNameAllocation :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(UpdateFileNameAllocation))
|
|
err = redo_update_index(vol, action, buffer);
|
|
break;
|
|
case UpdateFileNameRoot :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(UpdateFileNameRoot))
|
|
err = redo_update_root_index(vol, action, buffer);
|
|
break;
|
|
case UpdateMappingPairs :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(UpdateMappingPairs))
|
|
err = redo_update_mapping(vol, action, buffer);
|
|
break;
|
|
case UpdateNonResidentValue :
|
|
switch (get_action_kind(action)) {
|
|
case ON_INDX :
|
|
err = redo_update_index_value(vol, action, buffer);
|
|
break;
|
|
case ON_RAW :
|
|
err = redo_update_value(vol, action, buffer);
|
|
break;
|
|
default :
|
|
printf("** Bad attribute type\n");
|
|
err = 1;
|
|
}
|
|
break;
|
|
case UpdateResidentValue :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(UpdateResidentValue))
|
|
err = redo_update_resident(vol, action, buffer);
|
|
break;
|
|
case Win10Action37 :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(Noop))
|
|
err = redo_action37(vol, action, buffer);
|
|
break;
|
|
case WriteEndofFileRecordSegment :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(WriteEndofFileRecordSegment))
|
|
err = redo_write_end(vol, action, buffer);
|
|
break;
|
|
case WriteEndOfIndexBuffer :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(WriteEndOfIndexBuffer))
|
|
err = redo_write_index(vol, action, buffer);
|
|
break;
|
|
case AttributeNamesDump :
|
|
case DirtyPageTableDump :
|
|
case ForgetTransaction :
|
|
case Noop :
|
|
case OpenAttributeTableDump :
|
|
break;
|
|
default :
|
|
printf("** Unsupported redo %s\n", actionname(rop));
|
|
err = 1;
|
|
break;
|
|
}
|
|
redos_met |= ((u64)1) << rop;
|
|
if (err)
|
|
printf("* Redoing action %d %s (%s) failed\n",
|
|
action->num,actionname(rop), actionname(uop));
|
|
return (err);
|
|
}
|
|
|
|
/*
|
|
* 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 & const_cpu_to_le16(ACTS_ON_MFT)))
|
|
printf("** %s (action %d) not acting on MFT\n",actionname(rop),(int)action->num);
|
|
*/
|
|
/* Check whether data is to be discarded */
|
|
warn = (rop != InitializeFileRecordSegment)
|
|
|| !check_full_mft(action,TRUE);
|
|
buffer = read_protected(vol, &action->record,
|
|
mftrecsz, warn);
|
|
entry = (MFT_RECORD*)buffer;
|
|
if (entry && (entry->magic == magic_FILE)) {
|
|
data_lsn = 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 & const_cpu_to_le16(ACTS_ON_INDX)))
|
|
printf("** %s (action %d) not acting on INDX\n",actionname(rop),(int)action->num);
|
|
*/
|
|
xsize = vol->indx_record_size;
|
|
/* Check whether data is to be discarded */
|
|
warn = (rop != UpdateNonResidentValue)
|
|
|| !check_full_index(action,TRUE);
|
|
buffer = read_protected(vol, &action->record,
|
|
xsize, warn);
|
|
indx = (INDEX_BLOCK*)buffer;
|
|
if (indx && (indx->magic == magic_INDX)) {
|
|
data_lsn = 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
|
|
& (const_cpu_to_le16(ACTS_ON_INDX | ACTS_ON_MFT))) {
|
|
printf("** Error : action %s on MFT"
|
|
" or INDX\n",
|
|
actionname(rop));
|
|
err = 1;
|
|
} else {
|
|
buffer = read_raw(vol, &action->record);
|
|
if (!buffer)
|
|
err = 1;
|
|
}
|
|
break;
|
|
default :
|
|
buffer = (char*)NULL;
|
|
break;
|
|
}
|
|
if (!err && (!executed || !opts)) {
|
|
err = distribute_redos(vol, action, buffer);
|
|
redocount++;
|
|
} else {
|
|
if (optv)
|
|
printf("Action %d %s (%s) not redone\n",
|
|
action->num,
|
|
actionname(rop),
|
|
actionname(uop));
|
|
}
|
|
if (buffer)
|
|
free(buffer);
|
|
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 & const_cpu_to_le16(ACTS_ON_MFT)))
|
|
printf("** %s (action %d) not acting on MFT\n",actionname(rop),(int)action->num);
|
|
*/
|
|
buffer = read_protected(vol, &action->record, mftrecsz, TRUE);
|
|
entry = (MFT_RECORD*)buffer;
|
|
if (entry) {
|
|
if (entry->magic == magic_FILE) {
|
|
executed = !older_record(entry,
|
|
&action->record);
|
|
if (!executed
|
|
&& exception(action->num))
|
|
executed = TRUE;
|
|
if (optv > 1)
|
|
printf("record lsn 0x%llx is %s than action %d lsn 0x%llx\n",
|
|
(long long)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 & const_cpu_to_le16(ACTS_ON_INDX)))
|
|
printf("** %s (action %d) not acting on INDX\n",actionname(rop),(int)action->num);
|
|
*/
|
|
xsize = vol->indx_record_size;
|
|
buffer = read_protected(vol, &action->record, xsize, TRUE);
|
|
indx = (INDEX_BLOCK*)buffer;
|
|
if (indx) {
|
|
if (indx->magic == magic_INDX) {
|
|
executed = !older_record(indx,
|
|
&action->record);
|
|
if (!executed
|
|
&& exception(action->num))
|
|
executed = TRUE;
|
|
if (optv > 1)
|
|
printf("index lsn 0x%llx is %s than action %d lsn 0x%llx\n",
|
|
(long long)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
|
|
& (const_cpu_to_le16(ACTS_ON_INDX | ACTS_ON_MFT))) {
|
|
printf("** Error : action %s on MFT or INDX\n",
|
|
actionname(rop));
|
|
err = 1;
|
|
} else {
|
|
buffer = read_raw(vol, &action->record);
|
|
if (!buffer)
|
|
err = 1;
|
|
}
|
|
executed = TRUE;
|
|
break;
|
|
default :
|
|
executed = TRUE;
|
|
buffer = (char*)NULL;
|
|
break;
|
|
}
|
|
if (!err && executed) {
|
|
err = distribute_undos(vol, action, buffer);
|
|
undocount++;
|
|
}
|
|
if (buffer)
|
|
free(buffer);
|
|
|
|
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);
|
|
}
|