mirror of
https://git.code.sf.net/p/ntfs-3g/ntfs-3g.git
synced 2024-11-27 03:53:48 +08:00
3cc22ba329
The declarations related to the log file structure are now grouped in logfile.h, those specific to the recovery process are kept in ntfsrecover.h
4850 lines
126 KiB
C
4850 lines
126 KiB
C
/*
|
|
* Redo or undo a list of logged actions
|
|
*
|
|
* Copyright (c) 2014-2016 Jean-Pierre Andre
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program (in the main directory of the NTFS-3G
|
|
* distribution in the file COPYING); if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#ifdef HAVE_STDLIB_H
|
|
#include <stdlib.h>
|
|
#endif
|
|
#ifdef HAVE_STDIO_H
|
|
#include <stdio.h>
|
|
#endif
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
#ifdef HAVE_FCNTL_H
|
|
#include <fcntl.h>
|
|
#endif
|
|
#ifdef HAVE_ERRNO_H
|
|
#include <errno.h>
|
|
#endif
|
|
#ifdef HAVE_STRING_H
|
|
#include <string.h>
|
|
#endif
|
|
#ifdef HAVE_MALLOC_H
|
|
#include <malloc.h>
|
|
#endif
|
|
#ifdef HAVE_TIME_H
|
|
#include <time.h>
|
|
#endif
|
|
|
|
#include "types.h"
|
|
#include "endians.h"
|
|
#include "support.h"
|
|
#include "layout.h"
|
|
#include "param.h"
|
|
#include "ntfstime.h"
|
|
#include "device_io.h"
|
|
#include "device.h"
|
|
#include "logging.h"
|
|
#include "runlist.h"
|
|
#include "mft.h"
|
|
#include "inode.h"
|
|
#include "attrib.h"
|
|
#include "bitmap.h"
|
|
#include "index.h"
|
|
#include "volume.h"
|
|
#include "unistr.h"
|
|
#include "mst.h"
|
|
#include "logfile.h"
|
|
#include "ntfsrecover.h"
|
|
#include "misc.h"
|
|
|
|
struct STORE {
|
|
struct STORE *upper;
|
|
struct STORE *lower;
|
|
LCN lcn;
|
|
char data[1];
|
|
} ;
|
|
|
|
#define dump hexdump
|
|
|
|
struct STORE *cluster_door = (struct STORE*)NULL;
|
|
|
|
/* check whether a MFT or INDX record is older than action */
|
|
#define older_record(rec, logr) ((s64)(sle64_to_cpu((rec)->lsn) \
|
|
- sle64_to_cpu((logr)->this_lsn)) < 0)
|
|
/* check whether a MFT or INDX record is newer than action */
|
|
#define newer_record(rec, logr) ((s64)(sle64_to_cpu((rec)->lsn) \
|
|
- sle64_to_cpu((logr)->this_lsn)) > 0)
|
|
|
|
/*
|
|
* A few functions for debugging
|
|
*/
|
|
|
|
static int matchcount(const char *d, const char *s, int n)
|
|
{
|
|
int m;
|
|
|
|
m = 0;
|
|
while ((--n >= 0) && (*d++ == *s++)) m++;
|
|
return (m);
|
|
}
|
|
|
|
/*
|
|
static void locate(const char *s, int n, const char *p, int m)
|
|
{
|
|
int i,j;
|
|
|
|
for (i=0; i<=(n - m); i++)
|
|
if (s[i] == *p) {
|
|
j = 1;
|
|
while ((j < m) && (s[i + j] == p[j]))
|
|
j++;
|
|
if (j == m)
|
|
printf("=== found at offset 0x%x %d\n",i,i);
|
|
}
|
|
}
|
|
*/
|
|
|
|
static u64 inode_number(const LOG_RECORD *logr)
|
|
{
|
|
u64 offset;
|
|
|
|
offset = ((u64)le64_to_cpu(logr->target_vcn)
|
|
<< clusterbits)
|
|
+ ((u32)le16_to_cpu(logr->cluster_index)
|
|
<< NTFS_BLOCK_SIZE_BITS);
|
|
return (offset >> mftrecbits);
|
|
}
|
|
|
|
/*
|
|
* Find an in-memory copy of a needed cluster
|
|
*
|
|
* Optionally, allocate a copy.
|
|
*/
|
|
|
|
static struct STORE *getclusterentry(LCN lcn, BOOL create)
|
|
{
|
|
struct STORE **current;
|
|
struct STORE *newone;
|
|
|
|
current = &cluster_door;
|
|
/* A minimal binary tree should be enough */
|
|
while (*current && (lcn != (*current)->lcn)) {
|
|
if (lcn > (*current)->lcn)
|
|
current = &(*current)->upper;
|
|
else
|
|
current = &(*current)->lower;
|
|
}
|
|
if (create && !*current) {
|
|
newone = (struct STORE*)malloc(sizeof(struct STORE)
|
|
+ clustersz);
|
|
if (newone) {
|
|
newone->upper = (struct STORE*)NULL;
|
|
newone->lower = (struct STORE*)NULL;
|
|
newone->lcn = lcn;
|
|
*current = newone;
|
|
}
|
|
}
|
|
return (*current);
|
|
}
|
|
|
|
void freeclusterentry(struct STORE *entry)
|
|
{
|
|
if (!entry) {
|
|
if (cluster_door)
|
|
freeclusterentry(cluster_door);
|
|
cluster_door = (struct STORE*)NULL;
|
|
} else {
|
|
if (optv)
|
|
printf("* cluster 0x%llx %s updated\n",
|
|
(long long)entry->lcn,
|
|
(optn ? "would be" : "was"));
|
|
if (entry->upper)
|
|
freeclusterentry(entry->upper);
|
|
if (entry->lower)
|
|
freeclusterentry(entry->lower);
|
|
free(entry);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check whether an attribute type is a valid one
|
|
*/
|
|
|
|
static BOOL valid_type(ATTR_TYPES type)
|
|
{
|
|
BOOL ok;
|
|
|
|
switch (type) {
|
|
case AT_STANDARD_INFORMATION :
|
|
case AT_ATTRIBUTE_LIST :
|
|
case AT_FILE_NAME :
|
|
case AT_OBJECT_ID :
|
|
case AT_SECURITY_DESCRIPTOR :
|
|
case AT_VOLUME_NAME :
|
|
case AT_VOLUME_INFORMATION :
|
|
case AT_DATA :
|
|
case AT_INDEX_ROOT :
|
|
case AT_INDEX_ALLOCATION :
|
|
case AT_BITMAP :
|
|
case AT_REPARSE_POINT :
|
|
case AT_EA_INFORMATION :
|
|
case AT_EA :
|
|
case AT_PROPERTY_SET :
|
|
case AT_LOGGED_UTILITY_STREAM :
|
|
case AT_FIRST_USER_DEFINED_ATTRIBUTE :
|
|
case AT_END :
|
|
ok = TRUE;
|
|
break;
|
|
default :
|
|
ok = FALSE;
|
|
break;
|
|
}
|
|
return (ok);
|
|
}
|
|
|
|
/*
|
|
* Rough check of sanity of an index list
|
|
*/
|
|
|
|
static int sanity_indx_list(const char *buffer, u32 k, u32 end)
|
|
{
|
|
le64 inode;
|
|
int err;
|
|
int lth;
|
|
BOOL done;
|
|
|
|
err = 0;
|
|
done = FALSE;
|
|
while ((k <= end) && !done) {
|
|
lth = getle16(buffer,k+8);
|
|
if (optv > 1)
|
|
/* Usual indexes can be determined from size */
|
|
switch (lth) {
|
|
case 16 : /* final without subnode */
|
|
case 24 : /* final with subnode */
|
|
printf("index to none lth 0x%x"
|
|
" flags 0x%x pos 0x%x\n",
|
|
(int)lth,
|
|
(int)getle16(buffer,k+12),(int)k);
|
|
break;
|
|
case 32 : /* $R in $Reparse */
|
|
/* Badly aligned */
|
|
memcpy(&inode, &buffer[k + 20], 8);
|
|
printf("index to reparse of 0x%016llx lth 0x%x"
|
|
" flags 0x%x pos 0x%x\n",
|
|
(long long)le64_to_cpu(inode),
|
|
(int)lth,
|
|
(int)getle16(buffer,k+12),(int)k);
|
|
break;
|
|
case 40 : /* $SII in $Secure */
|
|
printf("index to securid 0x%lx lth 0x%x"
|
|
" flags 0x%x pos 0x%x\n",
|
|
(long)getle32(buffer,k + 16),
|
|
(int)lth,
|
|
(int)getle16(buffer,k+12),(int)k);
|
|
break;
|
|
case 48 : /* $SDH in $Secure */
|
|
printf("index to securid 0x%lx lth 0x%x"
|
|
" flags 0x%x pos 0x%x\n",
|
|
(long)getle32(buffer,k + 20),
|
|
(int)lth,
|
|
(int)getle16(buffer,k+12),(int)k);
|
|
break;
|
|
default : /* at least 80 */
|
|
printf("index to inode 0x%016llx lth 0x%x"
|
|
" flags 0x%x pos 0x%x\n",
|
|
(long long)getle64(buffer,k),
|
|
(int)lth,
|
|
(int)getle16(buffer,k+12),(int)k);
|
|
}
|
|
done = (feedle16(buffer,k+12) & INDEX_ENTRY_END) || !lth;
|
|
k += lth;
|
|
}
|
|
if (k != end) {
|
|
printf("** Bad index record length %ld (computed %ld)\n",
|
|
(long)end, (long)k);
|
|
err = 1;
|
|
}
|
|
if (!done) {
|
|
printf("** Missing end of index mark\n");
|
|
err = 1;
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
/*
|
|
* Rough check of sanity of an mft record
|
|
*/
|
|
|
|
static int sanity_mft(const char *buffer)
|
|
{
|
|
const MFT_RECORD *record;
|
|
const ATTR_RECORD *attr;
|
|
u64 instances;
|
|
u32 k;
|
|
u32 type;
|
|
u32 prevtype;
|
|
u16 nextinstance;
|
|
u16 instance;
|
|
int err;
|
|
|
|
err = 0;
|
|
record = (const MFT_RECORD*)buffer;
|
|
nextinstance = le16_to_cpu(record->next_attr_instance);
|
|
instances = 0;
|
|
k = le16_to_cpu(record->attrs_offset);
|
|
attr = (const ATTR_RECORD*)&buffer[k];
|
|
prevtype = 0;
|
|
while ((k < mftrecsz)
|
|
&& (attr->type != AT_END)
|
|
&& valid_type(attr->type)) {
|
|
type = le32_to_cpu(attr->type);
|
|
if (type < prevtype) {
|
|
printf("** Bad type ordering 0x%lx after 0x%lx\n",
|
|
(long)type, (long)prevtype);
|
|
err = 1;
|
|
}
|
|
instance = le16_to_cpu(attr->instance);
|
|
/* Can nextinstance wrap around ? */
|
|
if (instance >= nextinstance) {
|
|
printf("** Bad attr instance %d (max %d)\n",
|
|
(int)instance, (int)nextinstance - 1);
|
|
err = 1;
|
|
}
|
|
if (instance < 64) {
|
|
/* Only check up to 64 */
|
|
if (((u64)1 << instance) & instances) {
|
|
printf("** Duplicated attr instance %d\n",
|
|
(int)instance);
|
|
}
|
|
instances |= (u64)1 << instance;
|
|
}
|
|
if (optv > 1) {
|
|
if ((attr->type == AT_FILE_NAME)
|
|
&& buffer[k + 88]) {
|
|
printf("attr %08lx offs 0x%x nres %d",
|
|
(long)type, (int)k,
|
|
(int)attr->non_resident);
|
|
showname(" ",&buffer[k+90],
|
|
buffer[k + 88] & 255);
|
|
} else
|
|
printf("attr %08lx offs 0x%x nres %d\n",
|
|
(long)type, (int)k,
|
|
(int)attr->non_resident);
|
|
}
|
|
if ((attr->type == AT_INDEX_ROOT)
|
|
&& sanity_indx_list(buffer,
|
|
k + le16_to_cpu(attr->value_offset) + 32,
|
|
k + le32_to_cpu(attr->length))) {
|
|
err = 1;
|
|
}
|
|
k += le32_to_cpu(attr->length);
|
|
attr = (const ATTR_RECORD*)&buffer[k];
|
|
prevtype = type;
|
|
}
|
|
if ((optv > 1) && (attr->type == AT_END))
|
|
printf("attr %08lx offs 0x%x\n",
|
|
(long)le32_to_cpu(attr->type), (int)k);
|
|
if ((attr->type != AT_END)
|
|
|| (le32_to_cpu(record->bytes_in_use) != (k + 8))
|
|
|| (le32_to_cpu(record->bytes_allocated) < (k + 8))) {
|
|
printf("** Bad MFT record length %ld"
|
|
" (computed %ld allocated %ld)\n",
|
|
(long)le32_to_cpu(record->bytes_in_use),
|
|
(long)(k + 8),
|
|
(long)le32_to_cpu(record->bytes_allocated));
|
|
err = 1;
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
/*
|
|
* Rough check of sanity of an index block
|
|
*/
|
|
|
|
static int sanity_indx(ntfs_volume *vol, const char *buffer)
|
|
{
|
|
const INDEX_BLOCK *indx;
|
|
u32 k;
|
|
int err;
|
|
|
|
err = 0;
|
|
indx = (const INDEX_BLOCK*)buffer;
|
|
k = offsetof(INDEX_BLOCK, index) +
|
|
le32_to_cpu(indx->index.entries_offset);
|
|
err = sanity_indx_list(buffer, k,
|
|
le32_to_cpu(indx->index.index_length) + 24);
|
|
if ((le32_to_cpu(indx->index.index_length)
|
|
> le32_to_cpu(indx->index.allocated_size))
|
|
|| (le32_to_cpu(indx->index.allocated_size)
|
|
!= (vol->indx_record_size - 24))) {
|
|
printf("** Bad index length %ld"
|
|
" (usable %ld allocated %ld)\n",
|
|
(long)le32_to_cpu(indx->index.index_length),
|
|
(long)(vol->indx_record_size - 24),
|
|
(long)le32_to_cpu(indx->index.allocated_size));
|
|
err = 1;
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
|
|
/*
|
|
* Allocate a buffer and read a full set of raw clusters
|
|
*
|
|
* Do not use for accessing $LogFile.
|
|
* With option -n reading is first attempted from the memory store
|
|
*/
|
|
|
|
static char *read_raw(ntfs_volume *vol, const LOG_RECORD *logr)
|
|
{
|
|
char *buffer;
|
|
char *target;
|
|
struct STORE *store;
|
|
LCN lcn;
|
|
int count;
|
|
int i;
|
|
BOOL fail;
|
|
|
|
count = le16_to_cpu(logr->lcns_to_follow);
|
|
if (!count) {
|
|
printf("** Error : no lcn to read from\n");
|
|
buffer = (char*)NULL;
|
|
} else
|
|
buffer = (char*)malloc(clustersz*count);
|
|
// TODO error messages
|
|
if (buffer) {
|
|
fail = FALSE;
|
|
for (i=0; (i<count) && !fail; i++) {
|
|
store = (struct STORE*)NULL;
|
|
lcn = 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 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 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,
|
|
le64_to_cpu(logr->target_vcn) + i);
|
|
source = buffer + clustersz*i;
|
|
if ((lcn < 0)
|
|
|| (ntfs_pwrite(vol->dev, lcn << clusterbits,
|
|
clustersz, source) != clustersz)) {
|
|
printf("** Could not write cluster 0x%llx\n",
|
|
(long long)lcn);
|
|
err = 1;
|
|
}
|
|
}
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
/*
|
|
* Allocate a buffer and read a single protected record
|
|
*/
|
|
|
|
static char *read_protected(ntfs_volume *vol, const LOG_RECORD *logr,
|
|
u32 size, BOOL warn)
|
|
{
|
|
char *buffer;
|
|
char *full;
|
|
u32 pos;
|
|
LCN lcn;
|
|
|
|
/* read full clusters */
|
|
buffer = read_raw(vol, logr);
|
|
/*
|
|
* if the record is smaller than a cluster,
|
|
* make a partial copy and free the full buffer
|
|
*/
|
|
if (buffer && (size < clustersz)) {
|
|
full = buffer;
|
|
buffer = (char*)malloc(size);
|
|
if (buffer) {
|
|
pos = le16_to_cpu(logr->cluster_index)
|
|
<< NTFS_BLOCK_SIZE_BITS;
|
|
memcpy(buffer, full + pos, size);
|
|
}
|
|
free(full);
|
|
}
|
|
if (buffer && (ntfs_mst_post_read_fixup_warn(
|
|
(NTFS_RECORD*)buffer, size, FALSE) < 0)) {
|
|
if (warn) {
|
|
lcn = 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 LOG_RECORD *logr,
|
|
char *buffer, u32 size)
|
|
{
|
|
MFT_RECORD *record;
|
|
INDEX_BLOCK *indx;
|
|
char *full;
|
|
u32 pos;
|
|
BOOL mftmirr;
|
|
BOOL checked;
|
|
int err;
|
|
|
|
err = 0;
|
|
mftmirr = FALSE;
|
|
checked = FALSE;
|
|
if ((size == mftrecsz) && !memcmp(buffer,"FILE",4)) {
|
|
record = (MFT_RECORD*)buffer;
|
|
if (optv)
|
|
printf("update inode %ld lsn 0x%llx"
|
|
" (record %s than action 0x%llx)\n",
|
|
(long)le32_to_cpu(record->mft_record_number),
|
|
(long long)sle64_to_cpu(record->lsn),
|
|
((s64)(sle64_to_cpu(record->lsn)
|
|
- sle64_to_cpu(logr->this_lsn)) < 0 ?
|
|
"older" : "newer"),
|
|
(long long)sle64_to_cpu(logr->this_lsn));
|
|
if (optv > 1)
|
|
printf("mft vcn %lld index %d\n",
|
|
(long long)le64_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)le64_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 = le64_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 ATTR_OLD *attr_old;
|
|
const ATTR_NEW *attr_new;
|
|
const char *name;
|
|
le64 inode;
|
|
u32 namelen;
|
|
u32 length;
|
|
u32 extra;
|
|
int err;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
data = ((const char*)&action->record)
|
|
+ get_redo_offset(&action->record);
|
|
length = le16_to_cpu(action->record.redo_length);
|
|
extra = get_extra_offset(&action->record);
|
|
if (action->record.undo_length) {
|
|
name = ((const char*)&action->record) + extra;
|
|
namelen = le32_to_cpu(action->record.client_data_length)
|
|
+ LOG_RECORD_HEAD_SZ - extra;
|
|
/* fix namelen which was aligned modulo 8 */
|
|
namelen = fixnamelen(name, namelen);
|
|
if (optv > 1) {
|
|
printf("-> length %d namelen %d",(int)length,
|
|
(int)namelen);
|
|
showname(", ", name, namelen/2);
|
|
}
|
|
} else {
|
|
name = "";
|
|
namelen = 0;
|
|
}
|
|
pa = getattrentry(le16_to_cpu(action->record.target_attribute),0);
|
|
if (pa) {
|
|
if (optv) {
|
|
/*
|
|
* If the actions have been displayed, the
|
|
* attribute has already been fed. Check
|
|
* whether it matches what we have in store.
|
|
*/
|
|
switch (length) {
|
|
case sizeof(ATTR_OLD) :
|
|
attr_old = (const ATTR_OLD*)data;
|
|
/* Badly aligned */
|
|
memcpy(&inode, &attr_old->inode, 8);
|
|
err = (MREF(le64_to_cpu(inode)) != pa->inode)
|
|
|| (attr_old->type != pa->type);
|
|
break;
|
|
case sizeof(ATTR_NEW) :
|
|
attr_new = (const ATTR_NEW*)data;
|
|
err = (MREF(le64_to_cpu(attr_new->inode))
|
|
!= pa->inode)
|
|
|| (attr_new->type != pa->type);
|
|
break;
|
|
default : err = 1;
|
|
}
|
|
if (!err) {
|
|
err = (namelen != pa->namelen)
|
|
|| (namelen
|
|
&& memcmp(name, pa->name, namelen));
|
|
}
|
|
if (optv > 1)
|
|
printf("-> attribute %s the recorded one\n",
|
|
(err ? "does not match" : "matches"));
|
|
} else {
|
|
copy_attribute(pa, data, length);
|
|
pa->namelen = namelen;
|
|
if (namelen)
|
|
memcpy(pa->name, data, namelen);
|
|
err = 0;
|
|
}
|
|
} else
|
|
if (optv)
|
|
printf("* Unrecorded attribute\n");
|
|
return (err);
|
|
}
|
|
|
|
static int redo_sizes(ntfs_volume *vol, const struct ACTION_RECORD *action,
|
|
char *buffer)
|
|
{
|
|
const char *data;
|
|
u32 target;
|
|
u32 length;
|
|
int err;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
data = ((const char*)&action->record)
|
|
+ get_redo_offset(&action->record);
|
|
length = le16_to_cpu(action->record.redo_length);
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset)
|
|
+ offsetof(ATTR_RECORD, allocated_size);
|
|
err = change_resident(vol, action, buffer,
|
|
data, target, length);
|
|
return (err);
|
|
}
|
|
|
|
static int redo_update_index(ntfs_volume *vol,
|
|
const struct ACTION_RECORD *action, char *buffer)
|
|
{
|
|
const char *data;
|
|
u32 target;
|
|
u32 length;
|
|
int err;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
data = ((const char*)&action->record)
|
|
+ get_redo_offset(&action->record);
|
|
length = le16_to_cpu(action->record.redo_length);
|
|
/* target is left-justified to creation time */
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset)
|
|
+ offsetof(INDEX_ENTRY, key.file_name.creation_time);
|
|
err = update_index(vol, action, buffer, data, target, length);
|
|
return (err);
|
|
}
|
|
|
|
static int redo_update_index_value(ntfs_volume *vol,
|
|
const struct ACTION_RECORD *action, char *buffer)
|
|
{
|
|
const char *data;
|
|
u32 length;
|
|
u32 target;
|
|
int err;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
data = ((const char*)&action->record)
|
|
+ get_redo_offset(&action->record);
|
|
length = le16_to_cpu(action->record.redo_length);
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset);
|
|
err = change_index_value(vol, action, buffer, data, target, length);
|
|
return (err);
|
|
}
|
|
|
|
static int redo_update_mapping(ntfs_volume *vol,
|
|
const struct ACTION_RECORD *action, char *buffer)
|
|
{
|
|
LCN lcn;
|
|
const char *data;
|
|
ATTR_RECORD *attr;
|
|
MFT_RECORD *entry;
|
|
u32 target;
|
|
u32 length;
|
|
u32 source;
|
|
u32 alen;
|
|
u32 newused;
|
|
int resize;
|
|
int err;
|
|
int changed;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
data = ((const char*)&action->record)
|
|
+ get_redo_offset(&action->record);
|
|
length = le16_to_cpu(action->record.redo_length);
|
|
resize = length - le16_to_cpu(action->record.undo_length);
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset);
|
|
if (optv > 1) {
|
|
lcn = 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 ATTR_OLD *attr_old;
|
|
const ATTR_NEW *attr_new;
|
|
const char *name;
|
|
le64 inode;
|
|
u32 namelen;
|
|
u32 length;
|
|
u32 extra;
|
|
int err;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
data = ((const char*)&action->record)
|
|
+ get_redo_offset(&action->record);
|
|
length = le16_to_cpu(action->record.redo_length);
|
|
extra = get_extra_offset(&action->record);
|
|
if (action->record.undo_length) {
|
|
name = ((const char*)&action->record) + extra;
|
|
namelen = le32_to_cpu(action->record.client_data_length)
|
|
+ LOG_RECORD_HEAD_SZ - extra;
|
|
/* fix namelen which was aligned modulo 8 */
|
|
namelen = fixnamelen(name, namelen);
|
|
if (optv > 1) {
|
|
printf("-> length %d namelen %d",(int)length,
|
|
(int)namelen);
|
|
showname(", ", name, namelen/2);
|
|
}
|
|
} else {
|
|
namelen = 0;
|
|
name = "";
|
|
}
|
|
pa = getattrentry(le16_to_cpu(action->record.target_attribute),0);
|
|
// TODO Only process is attr is not older ?
|
|
if (pa) {
|
|
/* check whether the redo attr matches what we have in store */
|
|
switch (length) {
|
|
case sizeof(ATTR_OLD) :
|
|
attr_old = (const ATTR_OLD*)data;
|
|
/* Badly aligned */
|
|
memcpy(&inode, &attr_old->inode, 8);
|
|
err = (MREF(le64_to_cpu(inode)) != pa->inode)
|
|
|| (attr_old->type != pa->type);
|
|
break;
|
|
case sizeof(ATTR_NEW) :
|
|
attr_new = (const ATTR_NEW*)data;
|
|
err = (MREF(le64_to_cpu(attr_new->inode))!= pa->inode)
|
|
|| (attr_new->type != pa->type);
|
|
break;
|
|
default : err = 1;
|
|
}
|
|
if (!err) {
|
|
err = (namelen != pa->namelen)
|
|
|| (namelen
|
|
&& memcmp(name, pa->name, namelen));
|
|
}
|
|
if (optv > 1)
|
|
printf("-> attribute %s the recorded one\n",
|
|
(err ? "does not match" : "matches"));
|
|
} else
|
|
if (optv)
|
|
printf("* Unrecorded attribute\n");
|
|
err = 0;
|
|
return (err);
|
|
}
|
|
|
|
static int undo_sizes(ntfs_volume *vol,
|
|
const struct ACTION_RECORD *action, char *buffer)
|
|
{
|
|
const char *data;
|
|
MFT_RECORD *entry;
|
|
ATTR_RECORD *attr;
|
|
u32 target;
|
|
u32 length;
|
|
u32 offs;
|
|
int err;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
data = ((const char*)&action->record)
|
|
+ get_undo_offset(&action->record);
|
|
length = le16_to_cpu(action->record.undo_length);
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset)
|
|
+ offsetof(ATTR_RECORD, allocated_size);
|
|
entry = (MFT_RECORD*)buffer;
|
|
if (!(entry->flags & MFT_RECORD_IS_DIRECTORY))
|
|
err = change_resident(vol, action, buffer,
|
|
data, target, length);
|
|
else {
|
|
/* On a directory, may have to build an index allocation */
|
|
offs = le16_to_cpu(action->record.record_offset);
|
|
attr = (ATTR_RECORD*)(buffer + offs);
|
|
if (attr->type != AT_INDEX_ALLOCATION) {
|
|
err = insert_index_allocation(vol, buffer, offs);
|
|
if (!err)
|
|
err = change_resident(vol, action, buffer,
|
|
data, target, length);
|
|
} else
|
|
err = change_resident(vol, action, buffer,
|
|
data, target, length);
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
static int undo_update_index(ntfs_volume *vol, const struct ACTION_RECORD *action,
|
|
char *buffer)
|
|
{
|
|
const char *data;
|
|
u32 target;
|
|
u32 length;
|
|
int err;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
data = ((const char*)&action->record)
|
|
+ get_undo_offset(&action->record);
|
|
length = le16_to_cpu(action->record.undo_length);
|
|
/* target is left-justified to creation time */
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset)
|
|
+ offsetof(INDEX_ENTRY, key.file_name.creation_time);
|
|
err = update_index(vol, action, buffer, data, target, length);
|
|
return (err);
|
|
}
|
|
|
|
static int undo_update_index_value(ntfs_volume *vol,
|
|
const struct ACTION_RECORD *action, char *buffer)
|
|
{
|
|
LCN lcn;
|
|
const char *data;
|
|
u32 length;
|
|
u32 target;
|
|
int changed;
|
|
int err;
|
|
|
|
if (optv > 1)
|
|
printf("-> %s()\n",__func__);
|
|
err = 1;
|
|
data = ((const char*)&action->record)
|
|
+ get_undo_offset(&action->record);
|
|
length = le16_to_cpu(action->record.undo_length);
|
|
target = le16_to_cpu(action->record.record_offset)
|
|
+ le16_to_cpu(action->record.attribute_offset);
|
|
if (optv > 1) {
|
|
lcn = 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
|
|
& (LOG_RECORD_DELETING | LOG_RECORD_ADDING)) {
|
|
if (action->record.attribute_flags & ACTS_ON_INDX)
|
|
kind = ON_INDX;
|
|
else
|
|
if (action->record.attribute_flags & ACTS_ON_MFT)
|
|
kind = ON_MFT;
|
|
else
|
|
kind = ON_RAW;
|
|
} else {
|
|
/*
|
|
* In other cases, we have to rely on the attribute
|
|
* definition, but this has defects when undoing.
|
|
*/
|
|
pa = getattrentry(le16_to_cpu(
|
|
action->record.target_attribute),0);
|
|
if (!pa || !pa->type) {
|
|
/*
|
|
* Even when the attribute has not been recorded,
|
|
* we can sometimes tell the record does not apply
|
|
* to MFT or INDX : such records always have a zero
|
|
* record_offset, and if attribute_offset is zero, their
|
|
* magic can be checked. If neither condition is true,
|
|
* the action cannot apply to MFT or INDX.
|
|
* (this is useful for undoing)
|
|
*/
|
|
data = (const char*)&action->record
|
|
+ get_redo_offset(&action->record);
|
|
if (action->record.record_offset
|
|
|| (!action->record.attribute_offset
|
|
&& (le16_to_cpu(action->record.redo_length)
|
|
>= 4)
|
|
&& memcmp(data,"FILE",4)
|
|
&& memcmp(data,"INDX",4))) {
|
|
kind = ON_RAW;
|
|
} else {
|
|
printf("** Error : attribute 0x%x"
|
|
" is not defined\n",
|
|
(int)le16_to_cpu(
|
|
action->record.target_attribute));
|
|
kind = ON_NONE;
|
|
}
|
|
} else {
|
|
if (pa->type == AT_INDEX_ALLOCATION)
|
|
kind = ON_INDX;
|
|
else
|
|
kind = ON_RAW;
|
|
}
|
|
}
|
|
return (kind);
|
|
}
|
|
|
|
|
|
/*
|
|
* Display the redo actions which were executed
|
|
*
|
|
* Useful for getting indications on the coverage of a test
|
|
*/
|
|
|
|
void show_redos(void)
|
|
{
|
|
int i;
|
|
|
|
if (optv && redos_met) {
|
|
printf("Redo actions which were executed :\n");
|
|
for (i=0; i<64; i++)
|
|
if ((((u64)1) << i) & redos_met)
|
|
printf("%s\n", actionname(i));
|
|
}
|
|
}
|
|
|
|
static int distribute_redos(ntfs_volume *vol,
|
|
const struct ACTION_RECORD *action, char *buffer)
|
|
{
|
|
int rop, uop;
|
|
int err;
|
|
|
|
err = 0;
|
|
rop = le16_to_cpu(action->record.redo_operation);
|
|
uop = le16_to_cpu(action->record.undo_operation);
|
|
switch (rop) {
|
|
case AddIndexEntryAllocation :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(DeleteIndexEntryAllocation))
|
|
err = redo_add_index(vol, action, buffer);
|
|
break;
|
|
case AddIndexEntryRoot :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(DeleteIndexEntryRoot))
|
|
err = redo_add_root_index(vol, action, buffer);
|
|
break;
|
|
case ClearBitsInNonResidentBitMap :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(SetBitsInNonResidentBitMap))
|
|
err = redo_force_bits(vol, action, buffer);
|
|
break;
|
|
case CompensationlogRecord :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(Noop))
|
|
err = redo_compensate(vol, action, buffer);
|
|
break;
|
|
case CreateAttribute :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(DeleteAttribute))
|
|
err = redo_create_attribute(vol, action, buffer);
|
|
break;
|
|
case DeallocateFileRecordSegment :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(InitializeFileRecordSegment))
|
|
err = redo_delete_file(vol, action, buffer);
|
|
break;
|
|
case DeleteAttribute :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(CreateAttribute))
|
|
err = redo_delete_attribute(vol, action, buffer);
|
|
break;
|
|
case DeleteIndexEntryAllocation :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(AddIndexEntryAllocation))
|
|
err = redo_delete_index(vol, action, buffer);
|
|
break;
|
|
case DeleteIndexEntryRoot :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(AddIndexEntryRoot))
|
|
err = redo_delete_root_index(vol, action, buffer);
|
|
break;
|
|
case InitializeFileRecordSegment :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(Noop))
|
|
err = redo_create_file(vol, action, buffer);
|
|
break;
|
|
case OpenNonResidentAttribute :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(Noop))
|
|
err = redo_open_attribute(vol, action);
|
|
break;
|
|
case SetBitsInNonResidentBitMap :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(ClearBitsInNonResidentBitMap))
|
|
err = redo_force_bits(vol, action, buffer);
|
|
break;
|
|
case SetIndexEntryVcnAllocation :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(SetIndexEntryVcnAllocation))
|
|
err = redo_update_vcn(vol, action, buffer);
|
|
break;
|
|
case SetIndexEntryVcnRoot :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(SetIndexEntryVcnRoot))
|
|
err = redo_update_root_vcn(vol, action, buffer);
|
|
break;
|
|
case SetNewAttributeSizes :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(SetNewAttributeSizes))
|
|
err = redo_sizes(vol, action, buffer);
|
|
break;
|
|
case UpdateFileNameAllocation :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(UpdateFileNameAllocation))
|
|
err = redo_update_index(vol, action, buffer);
|
|
break;
|
|
case UpdateFileNameRoot :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(UpdateFileNameRoot))
|
|
err = redo_update_root_index(vol, action, buffer);
|
|
break;
|
|
case UpdateMappingPairs :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(UpdateMappingPairs))
|
|
err = redo_update_mapping(vol, action, buffer);
|
|
break;
|
|
case UpdateNonResidentValue :
|
|
switch (get_action_kind(action)) {
|
|
case ON_INDX :
|
|
err = redo_update_index_value(vol, action, buffer);
|
|
break;
|
|
case ON_RAW :
|
|
err = redo_update_value(vol, action, buffer);
|
|
break;
|
|
default :
|
|
printf("** Bad attribute type\n");
|
|
err = 1;
|
|
}
|
|
break;
|
|
case UpdateResidentValue :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(UpdateResidentValue))
|
|
err = redo_update_resident(vol, action, buffer);
|
|
break;
|
|
case Win10Action37 :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(Noop))
|
|
err = redo_action37(vol, action, buffer);
|
|
break;
|
|
case WriteEndofFileRecordSegment :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(WriteEndofFileRecordSegment))
|
|
err = redo_write_end(vol, action, buffer);
|
|
break;
|
|
case WriteEndOfIndexBuffer :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(WriteEndOfIndexBuffer))
|
|
err = redo_write_index(vol, action, buffer);
|
|
break;
|
|
case AttributeNamesDump :
|
|
case DirtyPageTableDump :
|
|
case ForgetTransaction :
|
|
case Noop :
|
|
case OpenAttributeTableDump :
|
|
break;
|
|
default :
|
|
printf("** Unsupported redo %s\n", actionname(rop));
|
|
err = 1;
|
|
break;
|
|
}
|
|
redos_met |= ((u64)1) << rop;
|
|
if (err)
|
|
printf("* Redoing action %d %s (%s) failed\n",
|
|
action->num,actionname(rop), actionname(uop));
|
|
return (err);
|
|
}
|
|
|
|
/*
|
|
* Redo a single action
|
|
*
|
|
* The record the action acts on is read and, when it is an MFT or
|
|
* INDX one, the need for redoing is checked.
|
|
*
|
|
* When this is an action which creates a new MFT or INDX record
|
|
* and the old one cannot be read (usually because it was not
|
|
* initialized), a zeroed buffer is allocated.
|
|
*/
|
|
|
|
static int play_one_redo(ntfs_volume *vol, const struct ACTION_RECORD *action)
|
|
{
|
|
MFT_RECORD *entry;
|
|
INDEX_BLOCK *indx;
|
|
char *buffer;
|
|
s64 this_lsn;
|
|
s64 data_lsn;
|
|
u32 xsize;
|
|
int err;
|
|
BOOL warn;
|
|
BOOL executed;
|
|
enum ACTION_KIND kind;
|
|
u16 rop;
|
|
u16 uop;
|
|
|
|
err = 0;
|
|
rop = le16_to_cpu(action->record.redo_operation);
|
|
uop = le16_to_cpu(action->record.undo_operation);
|
|
this_lsn = sle64_to_cpu(action->record.this_lsn);
|
|
if (optv)
|
|
printf("Redo action %d %s (%s) 0x%llx\n",
|
|
action->num,
|
|
actionname(rop), actionname(uop),
|
|
(long long)sle64_to_cpu(
|
|
action->record.this_lsn));
|
|
buffer = (char*)NULL;
|
|
switch (rop) {
|
|
/* Actions always acting on MFT */
|
|
case AddIndexEntryRoot :
|
|
case CreateAttribute :
|
|
case DeallocateFileRecordSegment :
|
|
case DeleteAttribute :
|
|
case DeleteIndexEntryRoot :
|
|
case InitializeFileRecordSegment :
|
|
case SetIndexEntryVcnRoot :
|
|
case SetNewAttributeSizes :
|
|
case UpdateFileNameRoot :
|
|
case UpdateMappingPairs :
|
|
case UpdateResidentValue :
|
|
case Win10Action37 :
|
|
case WriteEndofFileRecordSegment :
|
|
kind = ON_MFT;
|
|
break;
|
|
/* Actions always acting on INDX */
|
|
case AddIndexEntryAllocation :
|
|
case DeleteIndexEntryAllocation :
|
|
case SetIndexEntryVcnAllocation :
|
|
case UpdateFileNameAllocation :
|
|
case WriteEndOfIndexBuffer :
|
|
kind = ON_INDX;
|
|
break;
|
|
/* Actions never acting on MFT or INDX */
|
|
case ClearBitsInNonResidentBitMap :
|
|
case SetBitsInNonResidentBitMap :
|
|
kind = ON_RAW;
|
|
break;
|
|
/* Actions which may act on MFT */
|
|
case Noop : /* on MFT if DeallocateFileRecordSegment */
|
|
kind = ON_NONE;
|
|
break;
|
|
/* Actions which may act on INDX */
|
|
case UpdateNonResidentValue :
|
|
/* Known cases : INDX, $SDS, ATTR_LIST */
|
|
kind = get_action_kind(action);
|
|
if (kind == ON_NONE)
|
|
err = 1;
|
|
break;
|
|
case CompensationlogRecord :
|
|
case OpenNonResidentAttribute :
|
|
/* probably not important */
|
|
kind = ON_NONE;
|
|
break;
|
|
/* Actions currently ignored */
|
|
case AttributeNamesDump :
|
|
case DirtyPageTableDump :
|
|
case ForgetTransaction :
|
|
case OpenAttributeTableDump :
|
|
case TransactionTableDump :
|
|
kind = ON_NONE;
|
|
break;
|
|
/* Actions with no known use case */
|
|
case CommitTransaction :
|
|
case DeleteDirtyClusters :
|
|
case EndTopLevelAction :
|
|
case HotFix :
|
|
case PrepareTransaction :
|
|
case UpdateRecordDataAllocation :
|
|
case UpdateRecordDataRoot :
|
|
case Win10Action35 :
|
|
case Win10Action36 :
|
|
default :
|
|
err = 1;
|
|
kind = ON_NONE;
|
|
break;
|
|
}
|
|
executed = FALSE;
|
|
switch (kind) {
|
|
case ON_MFT :
|
|
/*
|
|
the check below cannot be used on WinXP
|
|
if (!(action->record.attribute_flags & ACTS_ON_MFT))
|
|
printf("** %s (action %d) not acting on MFT\n",actionname(rop),(int)action->num);
|
|
*/
|
|
/* Check whether data is to be discarded */
|
|
warn = (rop != InitializeFileRecordSegment)
|
|
|| !check_full_mft(action,TRUE);
|
|
buffer = read_protected(vol, &action->record,
|
|
mftrecsz, warn);
|
|
entry = (MFT_RECORD*)buffer;
|
|
if (entry && (entry->magic == magic_FILE)) {
|
|
data_lsn = sle64_to_cpu(entry->lsn);
|
|
/*
|
|
* Beware of records not updated
|
|
* during the last session which may
|
|
* have a stale lsn (consequence
|
|
* of ntfs-3g resetting the log)
|
|
*/
|
|
executed = ((s64)(data_lsn - this_lsn) >= 0)
|
|
&& (((s64)(data_lsn - latest_lsn)) <= 0)
|
|
&& !exception(action->num);
|
|
} else {
|
|
if (!warn) {
|
|
/* Old record not needed */
|
|
if (!buffer)
|
|
buffer = (char*)calloc(1, mftrecsz);
|
|
if (buffer)
|
|
executed = FALSE;
|
|
else
|
|
err = 1;
|
|
} else {
|
|
printf("** %s (action %d) not"
|
|
" acting on MFT\n",
|
|
actionname(rop),
|
|
(int)action->num);
|
|
err = 1;
|
|
}
|
|
}
|
|
break;
|
|
case ON_INDX :
|
|
/*
|
|
the check below cannot be used on WinXP
|
|
if (!(action->record.attribute_flags & ACTS_ON_INDX))
|
|
printf("** %s (action %d) not acting on INDX\n",actionname(rop),(int)action->num);
|
|
*/
|
|
xsize = vol->indx_record_size;
|
|
/* Check whether data is to be discarded */
|
|
warn = (rop != UpdateNonResidentValue)
|
|
|| !check_full_index(action,TRUE);
|
|
buffer = read_protected(vol, &action->record,
|
|
xsize, warn);
|
|
indx = (INDEX_BLOCK*)buffer;
|
|
if (indx && (indx->magic == magic_INDX)) {
|
|
data_lsn = sle64_to_cpu(indx->lsn);
|
|
/*
|
|
* Beware of records not updated
|
|
* during the last session which may
|
|
* have a stale lsn (consequence
|
|
* of ntfs-3g resetting the log)
|
|
*/
|
|
executed = ((s64)(data_lsn - this_lsn) >= 0)
|
|
&& (((s64)(data_lsn - latest_lsn)) <= 0)
|
|
&& ! exception(action->num);
|
|
} else {
|
|
if (!warn) {
|
|
/* Old record not needed */
|
|
if (!buffer)
|
|
buffer = (char*)calloc(1, xsize);
|
|
if (buffer)
|
|
executed = FALSE;
|
|
else
|
|
err = 1;
|
|
} else {
|
|
printf("** %s (action %d) not"
|
|
" acting on INDX\n",
|
|
actionname(rop),
|
|
(int)action->num);
|
|
err = 1;
|
|
}
|
|
}
|
|
break;
|
|
case ON_RAW :
|
|
if (action->record.attribute_flags
|
|
& (ACTS_ON_INDX | ACTS_ON_MFT)) {
|
|
printf("** Error : action %s on MFT"
|
|
" or INDX\n",
|
|
actionname(rop));
|
|
err = 1;
|
|
} else {
|
|
buffer = read_raw(vol, &action->record);
|
|
if (!buffer)
|
|
err = 1;
|
|
}
|
|
break;
|
|
default :
|
|
buffer = (char*)NULL;
|
|
break;
|
|
}
|
|
if (!err && (!executed || !opts)) {
|
|
err = distribute_redos(vol, action, buffer);
|
|
redocount++;
|
|
} else {
|
|
if (optv)
|
|
printf("Action %d %s (%s) not redone\n",
|
|
action->num,
|
|
actionname(rop),
|
|
actionname(uop));
|
|
}
|
|
if (buffer)
|
|
free(buffer);
|
|
return (err);
|
|
}
|
|
|
|
|
|
/*
|
|
* Play the redo actions from earliest to latest
|
|
*
|
|
* Currently we can only redo the last undone transaction,
|
|
* otherwise the attribute table would be out of phase.
|
|
*/
|
|
|
|
int play_redos(ntfs_volume *vol, const struct ACTION_RECORD *firstaction)
|
|
{
|
|
const struct ACTION_RECORD *action;
|
|
int err;
|
|
|
|
err = 0;
|
|
action = firstaction;
|
|
while (action && !err) {
|
|
/* Only committed actions should be redone */
|
|
if ((!optc || within_lcn_range(&action->record))
|
|
&& (action->flags & ACTION_TO_REDO))
|
|
err = play_one_redo(vol, action);
|
|
if (!err)
|
|
action = action->next;
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
static int distribute_undos(ntfs_volume *vol, const struct ACTION_RECORD *action,
|
|
char *buffer)
|
|
{
|
|
int rop, uop;
|
|
int err;
|
|
|
|
err = 0;
|
|
rop = le16_to_cpu(action->record.redo_operation);
|
|
uop = le16_to_cpu(action->record.undo_operation);
|
|
switch (rop) {
|
|
case AddIndexEntryAllocation :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(DeleteIndexEntryAllocation))
|
|
err = undo_add_index(vol, action, buffer);
|
|
break;
|
|
case AddIndexEntryRoot :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(DeleteIndexEntryRoot))
|
|
err = undo_add_root_index(vol, action, buffer);
|
|
break;
|
|
case ClearBitsInNonResidentBitMap :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(SetBitsInNonResidentBitMap))
|
|
err = undo_force_bits(vol, action, buffer);
|
|
break;
|
|
case CreateAttribute :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(DeleteAttribute))
|
|
err = undo_create_attribute(vol, action, buffer);
|
|
break;
|
|
case DeallocateFileRecordSegment :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(InitializeFileRecordSegment))
|
|
err = undo_delete_file(vol, action, buffer);
|
|
break;
|
|
case DeleteAttribute :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(CreateAttribute))
|
|
err = undo_delete_attribute(vol, action, buffer);
|
|
break;
|
|
case DeleteIndexEntryAllocation :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(AddIndexEntryAllocation))
|
|
err = undo_delete_index(vol, action, buffer);
|
|
break;
|
|
case DeleteIndexEntryRoot :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(AddIndexEntryRoot))
|
|
err = undo_delete_root_index(vol, action, buffer);
|
|
break;
|
|
case InitializeFileRecordSegment :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(Noop))
|
|
err = undo_create_file(vol, action, buffer);
|
|
break;
|
|
case OpenNonResidentAttribute :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(Noop))
|
|
err = undo_open_attribute(vol, action);
|
|
break;
|
|
case SetBitsInNonResidentBitMap :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(ClearBitsInNonResidentBitMap))
|
|
err = undo_force_bits(vol, action, buffer);
|
|
break;
|
|
case SetIndexEntryVcnAllocation :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(SetIndexEntryVcnAllocation))
|
|
err = undo_update_vcn(vol, action, buffer);
|
|
break;
|
|
case SetIndexEntryVcnRoot :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(SetIndexEntryVcnRoot))
|
|
err = undo_update_root_vcn(vol, action, buffer);
|
|
break;
|
|
case SetNewAttributeSizes :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(SetNewAttributeSizes))
|
|
err = undo_sizes(vol, action, buffer);
|
|
break;
|
|
case UpdateFileNameAllocation :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(UpdateFileNameAllocation))
|
|
err = undo_update_index(vol, action, buffer);
|
|
break;
|
|
case UpdateFileNameRoot :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(UpdateFileNameRoot))
|
|
err = undo_update_root_index(vol, action, buffer);
|
|
break;
|
|
case UpdateMappingPairs :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(UpdateMappingPairs))
|
|
err = undo_update_mapping(vol, action, buffer);
|
|
break;
|
|
case UpdateNonResidentValue :
|
|
switch (get_action_kind(action)) {
|
|
case ON_INDX :
|
|
err = undo_update_index_value(vol, action, buffer);
|
|
break;
|
|
case ON_RAW :
|
|
err = undo_update_value(vol, action, buffer);
|
|
break;
|
|
default :
|
|
printf("** Bad attribute type\n");
|
|
err = 1;
|
|
}
|
|
break;
|
|
case UpdateResidentValue :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(UpdateResidentValue))
|
|
err = undo_update_resident(vol, action, buffer);
|
|
break;
|
|
case Win10Action37 :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(Noop))
|
|
err = undo_action37(vol, action, buffer);
|
|
break;
|
|
case WriteEndofFileRecordSegment :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(WriteEndofFileRecordSegment))
|
|
err = undo_write_end(vol, action, buffer);
|
|
break;
|
|
case WriteEndOfIndexBuffer :
|
|
if (action->record.undo_operation
|
|
== const_cpu_to_le16(WriteEndOfIndexBuffer))
|
|
err = undo_write_index(vol, action, buffer);
|
|
break;
|
|
case AttributeNamesDump :
|
|
case CompensationlogRecord :
|
|
case DirtyPageTableDump :
|
|
case ForgetTransaction :
|
|
case Noop :
|
|
case OpenAttributeTableDump :
|
|
break;
|
|
default :
|
|
printf("** Unsupported undo %s\n", actionname(rop));
|
|
err = 1;
|
|
break;
|
|
}
|
|
if (err)
|
|
printf("* Undoing action %d %s (%s) failed\n",
|
|
action->num,actionname(rop), actionname(uop));
|
|
return (err);
|
|
}
|
|
|
|
/*
|
|
* Undo a single action
|
|
*
|
|
* The record the action acts on is read and, when it is an MFT or
|
|
* INDX one, the need for undoing is checked.
|
|
*/
|
|
|
|
static int play_one_undo(ntfs_volume *vol, const struct ACTION_RECORD *action)
|
|
{
|
|
MFT_RECORD *entry;
|
|
INDEX_BLOCK *indx;
|
|
char *buffer;
|
|
u32 xsize;
|
|
u16 rop;
|
|
u16 uop;
|
|
int err;
|
|
BOOL executed;
|
|
enum ACTION_KIND kind;
|
|
|
|
err = 0;
|
|
rop = le16_to_cpu(action->record.redo_operation);
|
|
uop = le16_to_cpu(action->record.undo_operation);
|
|
if (optv)
|
|
printf("Undo action %d %s (%s) lsn 0x%llx\n",
|
|
action->num,
|
|
actionname(rop), actionname(uop),
|
|
(long long)sle64_to_cpu(
|
|
action->record.this_lsn));
|
|
buffer = (char*)NULL;
|
|
executed = FALSE;
|
|
kind = ON_NONE;
|
|
switch (rop) {
|
|
/* Actions always acting on MFT */
|
|
case AddIndexEntryRoot :
|
|
case CreateAttribute :
|
|
case DeallocateFileRecordSegment :
|
|
case DeleteAttribute :
|
|
case DeleteIndexEntryRoot :
|
|
case InitializeFileRecordSegment :
|
|
case SetIndexEntryVcnRoot :
|
|
case SetNewAttributeSizes :
|
|
case UpdateFileNameRoot :
|
|
case UpdateMappingPairs :
|
|
case UpdateResidentValue :
|
|
case Win10Action37 :
|
|
case WriteEndofFileRecordSegment :
|
|
kind = ON_MFT;
|
|
break;
|
|
/* Actions always acting on INDX */
|
|
case AddIndexEntryAllocation :
|
|
case DeleteIndexEntryAllocation :
|
|
case SetIndexEntryVcnAllocation :
|
|
case UpdateFileNameAllocation :
|
|
case WriteEndOfIndexBuffer :
|
|
kind = ON_INDX;
|
|
break;
|
|
/* Actions never acting on MFT or INDX */
|
|
case ClearBitsInNonResidentBitMap :
|
|
case SetBitsInNonResidentBitMap :
|
|
kind = ON_RAW;
|
|
break;
|
|
/* Actions which may act on MFT */
|
|
case Noop : /* on MFT if DeallocateFileRecordSegment */
|
|
break;
|
|
/* Actions which may act on INDX */
|
|
case UpdateNonResidentValue :
|
|
/* Known cases : INDX, $SDS, ATTR_LIST */
|
|
kind = get_action_kind(action);
|
|
if (kind == ON_NONE)
|
|
err = 1;
|
|
break;
|
|
case OpenNonResidentAttribute :
|
|
/* probably not important */
|
|
kind = ON_NONE;
|
|
break;
|
|
/* Actions currently ignored */
|
|
case AttributeNamesDump :
|
|
case CommitTransaction :
|
|
case CompensationlogRecord :
|
|
case DeleteDirtyClusters :
|
|
case DirtyPageTableDump :
|
|
case EndTopLevelAction :
|
|
case ForgetTransaction :
|
|
case HotFix :
|
|
case OpenAttributeTableDump :
|
|
case PrepareTransaction :
|
|
case TransactionTableDump :
|
|
case UpdateRecordDataAllocation :
|
|
case UpdateRecordDataRoot :
|
|
case Win10Action35 :
|
|
case Win10Action36 :
|
|
kind = ON_NONE;
|
|
break;
|
|
}
|
|
switch (kind) {
|
|
case ON_MFT :
|
|
/*
|
|
the check below cannot be used on WinXP
|
|
if (!(action->record.attribute_flags & ACTS_ON_MFT))
|
|
printf("** %s (action %d) not acting on MFT\n",actionname(rop),(int)action->num);
|
|
*/
|
|
buffer = read_protected(vol, &action->record, mftrecsz, TRUE);
|
|
entry = (MFT_RECORD*)buffer;
|
|
if (entry) {
|
|
if (entry->magic == magic_FILE) {
|
|
executed = !older_record(entry,
|
|
&action->record);
|
|
if (!executed
|
|
&& exception(action->num))
|
|
executed = TRUE;
|
|
if (optv > 1)
|
|
printf("record lsn 0x%llx is %s than action %d lsn 0x%llx\n",
|
|
(long long)sle64_to_cpu(entry->lsn),
|
|
(executed ? "not older" : "older"),
|
|
(int)action->num,
|
|
(long long)sle64_to_cpu(action->record.this_lsn));
|
|
} else {
|
|
printf("** %s (action %d) not acting on MFT\n",
|
|
actionname(rop), (int)action->num);
|
|
err = 1;
|
|
}
|
|
} else {
|
|
/*
|
|
* Could not read the MFT record :
|
|
* if this is undoing a record create (from scratch)
|
|
* which did not take place, there is nothing to redo,
|
|
* otherwise this is an error.
|
|
*/
|
|
if (check_full_mft(action,TRUE))
|
|
executed = FALSE;
|
|
else
|
|
err = 1;
|
|
}
|
|
break;
|
|
case ON_INDX :
|
|
/*
|
|
the check below cannot be used on WinXP
|
|
if (!(action->record.attribute_flags & ACTS_ON_INDX))
|
|
printf("** %s (action %d) not acting on INDX\n",actionname(rop),(int)action->num);
|
|
*/
|
|
xsize = vol->indx_record_size;
|
|
buffer = read_protected(vol, &action->record, xsize, TRUE);
|
|
indx = (INDEX_BLOCK*)buffer;
|
|
if (indx) {
|
|
if (indx->magic == magic_INDX) {
|
|
executed = !older_record(indx,
|
|
&action->record);
|
|
if (!executed
|
|
&& exception(action->num))
|
|
executed = TRUE;
|
|
if (optv > 1)
|
|
printf("index lsn 0x%llx is %s than action %d lsn 0x%llx\n",
|
|
(long long)sle64_to_cpu(indx->lsn),
|
|
(executed ? "not older" : "older"),
|
|
(int)action->num,
|
|
(long long)sle64_to_cpu(action->record.this_lsn));
|
|
} else {
|
|
printf("** %s (action %d) not acting on INDX\n",
|
|
actionname(rop), (int)action->num);
|
|
err = 1;
|
|
}
|
|
} else {
|
|
/*
|
|
* Could not read the INDX record :
|
|
* if this is undoing a record create (from scratch)
|
|
* which did not take place, there is nothing to redo,
|
|
* otherwise this must be an error.
|
|
* However, after deleting the last index allocation
|
|
* in a block, the block is apparently zeroed
|
|
* and cannot be read. In this case we have to
|
|
* create an initial index block and apply the undo.
|
|
*/
|
|
if (check_full_index(action,TRUE))
|
|
executed = FALSE;
|
|
else {
|
|
err = 1;
|
|
if (uop == AddIndexEntryAllocation) {
|
|
executed = TRUE;
|
|
buffer = (char*)calloc(1, xsize);
|
|
if (buffer)
|
|
err = create_indx(vol,
|
|
action, buffer);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case ON_RAW :
|
|
if (action->record.attribute_flags
|
|
& (ACTS_ON_INDX | ACTS_ON_MFT)) {
|
|
printf("** Error : action %s on MFT or INDX\n",
|
|
actionname(rop));
|
|
err = 1;
|
|
} else {
|
|
buffer = read_raw(vol, &action->record);
|
|
if (!buffer)
|
|
err = 1;
|
|
}
|
|
executed = TRUE;
|
|
break;
|
|
default :
|
|
executed = TRUE;
|
|
buffer = (char*)NULL;
|
|
break;
|
|
}
|
|
if (!err && executed) {
|
|
err = distribute_undos(vol, action, buffer);
|
|
undocount++;
|
|
}
|
|
if (buffer)
|
|
free(buffer);
|
|
|
|
return (err);
|
|
}
|
|
|
|
/*
|
|
* Play the undo actions from latest to earliest
|
|
*
|
|
* For structured record, a check is made on the lsn to only
|
|
* try to undo the actions which were executed. This implies
|
|
* identifying actions on a structured record.
|
|
*
|
|
* Returns 0 if successful
|
|
*/
|
|
|
|
int play_undos(ntfs_volume *vol, const struct ACTION_RECORD *lastaction)
|
|
{
|
|
const struct ACTION_RECORD *action;
|
|
int err;
|
|
|
|
err = 0;
|
|
action = lastaction;
|
|
while (action && !err) {
|
|
if (!optc || within_lcn_range(&action->record))
|
|
err = play_one_undo(vol, action);
|
|
if (!err)
|
|
action = action->prev;
|
|
}
|
|
return (err);
|
|
}
|