mirror of
https://git.code.sf.net/p/ntfs-3g/ntfs-3g.git
synced 2024-11-27 12:03:42 +08:00
1005 lines
21 KiB
C
1005 lines
21 KiB
C
/**
|
|
* ntfscmp - Part of the Linux-NTFS project.
|
|
*
|
|
* Copyright (c) 2005-2006 Szabolcs Szakacsits
|
|
* Copyright (c) 2005 Anton Altaparmakov
|
|
* Copyright (c) 2007 Yura Pakhuchiy
|
|
*
|
|
* This utility compare two NTFS volumes.
|
|
*
|
|
* 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 Linux-NTFS
|
|
* 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"
|
|
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <getopt.h>
|
|
|
|
#include "utils.h"
|
|
#include "mst.h"
|
|
#include "version.h"
|
|
#include "support.h"
|
|
|
|
static const char *EXEC_NAME = "ntfscmp";
|
|
|
|
static const char *invalid_ntfs_msg =
|
|
"Apparently device '%s' doesn't have a valid NTFS.\n"
|
|
"Maybe you selected the wrong partition? Or the whole disk instead of a\n"
|
|
"partition (e.g. /dev/hda, not /dev/hda1)?\n";
|
|
|
|
static const char *corrupt_volume_msg =
|
|
"Apparently you have a corrupted NTFS. Please run the filesystem checker\n"
|
|
"on Windows by invoking chkdsk /f. Don't forget the /f (force) parameter,\n"
|
|
"it's important! You probably also need to reboot Windows to take effect.\n";
|
|
|
|
static const char *hibernated_volume_msg =
|
|
"Apparently the NTFS partition is hibernated. Windows must be resumed and\n"
|
|
"turned off properly\n";
|
|
|
|
|
|
static struct {
|
|
int debug;
|
|
int show_progress;
|
|
int verbose;
|
|
char *vol1;
|
|
char *vol2;
|
|
} opt;
|
|
|
|
|
|
#define NTFS_PROGBAR 0x0001
|
|
#define NTFS_PROGBAR_SUPPRESS 0x0002
|
|
|
|
struct progress_bar {
|
|
u64 start;
|
|
u64 stop;
|
|
int resolution;
|
|
int flags;
|
|
float unit;
|
|
};
|
|
|
|
/* WARNING: don't modify the text, external tools grep for it */
|
|
#define ERR_PREFIX "ERROR"
|
|
#define PERR_PREFIX ERR_PREFIX "(%d): "
|
|
#define NERR_PREFIX ERR_PREFIX ": "
|
|
|
|
__attribute__((format(printf, 2, 3)))
|
|
static void perr_printf(int newline, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
int eo = errno;
|
|
|
|
fprintf(stdout, PERR_PREFIX, eo);
|
|
va_start(ap, fmt);
|
|
vfprintf(stdout, fmt, ap);
|
|
va_end(ap);
|
|
fprintf(stdout, ": %s", strerror(eo));
|
|
if (newline)
|
|
fprintf(stdout, "\n");
|
|
fflush(stdout);
|
|
fflush(stderr);
|
|
}
|
|
|
|
#define perr_print(...) perr_printf(0, __VA_ARGS__)
|
|
#define perr_println(...) perr_printf(1, __VA_ARGS__)
|
|
|
|
__attribute__((format(printf, 1, 2)))
|
|
static void err_printf(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
fprintf(stdout, NERR_PREFIX);
|
|
va_start(ap, fmt);
|
|
vfprintf(stdout, fmt, ap);
|
|
va_end(ap);
|
|
fflush(stdout);
|
|
fflush(stderr);
|
|
}
|
|
|
|
/**
|
|
* err_exit
|
|
*
|
|
* Print and error message and exit the program.
|
|
*/
|
|
__attribute__((noreturn))
|
|
__attribute__((format(printf, 1, 2)))
|
|
static int err_exit(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
fprintf(stdout, NERR_PREFIX);
|
|
va_start(ap, fmt);
|
|
vfprintf(stdout, fmt, ap);
|
|
va_end(ap);
|
|
fflush(stdout);
|
|
fflush(stderr);
|
|
exit(1);
|
|
}
|
|
|
|
/**
|
|
* perr_exit
|
|
*
|
|
* Print and error message and exit the program
|
|
*/
|
|
__attribute__((noreturn))
|
|
__attribute__((format(printf, 1, 2)))
|
|
static int perr_exit(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
int eo = errno;
|
|
|
|
fprintf(stdout, PERR_PREFIX, eo);
|
|
va_start(ap, fmt);
|
|
vfprintf(stdout, fmt, ap);
|
|
va_end(ap);
|
|
printf(": %s\n", strerror(eo));
|
|
fflush(stdout);
|
|
fflush(stderr);
|
|
exit(1);
|
|
}
|
|
|
|
/**
|
|
* usage - Print a list of the parameters to the program
|
|
*
|
|
* Print a list of the parameters and options for the program.
|
|
*
|
|
* Return: none
|
|
*/
|
|
__attribute__((noreturn))
|
|
static void usage(void)
|
|
{
|
|
|
|
printf("\nUsage: %s [OPTIONS] DEVICE1 DEVICE2\n"
|
|
" Compare two NTFS volumes and tell the differences.\n"
|
|
"\n"
|
|
" -P, --no-progress-bar Don't show progress bar\n"
|
|
" -v, --verbose More output\n"
|
|
" -h, --help Display this help\n"
|
|
#ifdef DEBUG
|
|
" -d, --debug Show debug information\n"
|
|
#endif
|
|
"\n", EXEC_NAME);
|
|
printf("%s%s", ntfs_bugs, ntfs_home);
|
|
exit(1);
|
|
}
|
|
|
|
|
|
static void parse_options(int argc, char **argv)
|
|
{
|
|
static const char *sopt = "-dhPv";
|
|
static const struct option lopt[] = {
|
|
#ifdef DEBUG
|
|
{ "debug", no_argument, NULL, 'd' },
|
|
#endif
|
|
{ "help", no_argument, NULL, 'h' },
|
|
{ "no-progress-bar", no_argument, NULL, 'P' },
|
|
{ "verbose", no_argument, NULL, 'v' },
|
|
{ NULL, 0, NULL, 0 }
|
|
};
|
|
|
|
int c;
|
|
|
|
memset(&opt, 0, sizeof(opt));
|
|
opt.show_progress = 1;
|
|
|
|
while ((c = getopt_long(argc, argv, sopt, lopt, NULL)) != -1) {
|
|
switch (c) {
|
|
case 1: /* A non-option argument */
|
|
if (!opt.vol1) {
|
|
opt.vol1 = argv[optind - 1];
|
|
} else if (!opt.vol2) {
|
|
opt.vol2 = argv[optind - 1];
|
|
} else {
|
|
err_printf("Too many arguments!\n");
|
|
usage();
|
|
}
|
|
break;
|
|
#ifdef DEBUG
|
|
case 'd':
|
|
opt.debug++;
|
|
break;
|
|
#endif
|
|
case 'h':
|
|
case '?':
|
|
usage();
|
|
case 'P':
|
|
opt.show_progress = 0;
|
|
break;
|
|
case 'v':
|
|
opt.verbose++;
|
|
break;
|
|
default:
|
|
err_printf("Unknown option '%s'.\n", argv[optind - 1]);
|
|
usage();
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (opt.vol1 == NULL || opt.vol2 == NULL) {
|
|
err_printf("You must specify exactly 2 volumes.\n");
|
|
usage();
|
|
}
|
|
|
|
/* Redirect stderr to stdout, note fflush()es are essential! */
|
|
fflush(stdout);
|
|
fflush(stderr);
|
|
if (dup2(STDOUT_FILENO, STDERR_FILENO) == -1) {
|
|
perror("Failed to redirect stderr to stdout");
|
|
exit(1);
|
|
}
|
|
fflush(stdout);
|
|
fflush(stderr);
|
|
|
|
#ifdef DEBUG
|
|
if (!opt.debug)
|
|
if (!freopen("/dev/null", "w", stderr))
|
|
perr_exit("Failed to redirect stderr to /dev/null");
|
|
#endif
|
|
}
|
|
|
|
static ntfs_attr_search_ctx *attr_get_search_ctx(ntfs_inode *ni)
|
|
{
|
|
ntfs_attr_search_ctx *ret;
|
|
|
|
if ((ret = ntfs_attr_get_search_ctx(ni, NULL)) == NULL)
|
|
perr_println("ntfs_attr_get_search_ctx");
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void progress_init(struct progress_bar *p, u64 start, u64 stop, int flags)
|
|
{
|
|
p->start = start;
|
|
p->stop = stop;
|
|
p->unit = 100.0 / (stop - start);
|
|
p->resolution = 100;
|
|
p->flags = flags;
|
|
}
|
|
|
|
static void progress_update(struct progress_bar *p, u64 current)
|
|
{
|
|
float percent;
|
|
|
|
if (!(p->flags & NTFS_PROGBAR))
|
|
return;
|
|
if (p->flags & NTFS_PROGBAR_SUPPRESS)
|
|
return;
|
|
|
|
/* WARNING: don't modify the texts, external tools grep for them */
|
|
percent = p->unit * current;
|
|
if (current != p->stop) {
|
|
if ((current - p->start) % p->resolution)
|
|
return;
|
|
printf("%6.2f percent completed\r", percent);
|
|
} else
|
|
printf("100.00 percent completed\n");
|
|
fflush(stdout);
|
|
}
|
|
|
|
static u64 inumber(ntfs_inode *ni)
|
|
{
|
|
if (ni->nr_extents >= 0)
|
|
return ni->mft_no;
|
|
|
|
return ni->base_ni->mft_no;
|
|
}
|
|
|
|
static int inode_close(ntfs_inode *ni)
|
|
{
|
|
if (ni == NULL)
|
|
return 0;
|
|
|
|
if (ntfs_inode_close(ni)) {
|
|
perr_println("ntfs_inode_close: inode %llu", inumber(ni));
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static inline s64 get_nr_mft_records(ntfs_volume *vol)
|
|
{
|
|
return vol->mft_na->initialized_size >> vol->mft_record_size_bits;
|
|
}
|
|
|
|
#define NTFSCMP_OK 0
|
|
#define NTFSCMP_INODE_OPEN_ERROR 1
|
|
#define NTFSCMP_INODE_OPEN_IO_ERROR 2
|
|
#define NTFSCMP_INODE_OPEN_ENOENT_ERROR 3
|
|
#define NTFSCMP_EXTENSION_RECORD 4
|
|
#define NTFSCMP_INODE_CLOSE_ERROR 5
|
|
|
|
static const char *ntfscmp_errs[] = {
|
|
"OK",
|
|
"INODE_OPEN_ERROR",
|
|
"INODE_OPEN_IO_ERROR",
|
|
"INODE_OPEN_ENOENT_ERROR",
|
|
"EXTENSION_RECORD",
|
|
"INODE_CLOSE_ERROR",
|
|
""
|
|
};
|
|
|
|
|
|
static const char *err2string(int err)
|
|
{
|
|
return ntfscmp_errs[err];
|
|
}
|
|
|
|
static const char *pret2str(void *p)
|
|
{
|
|
if (p == NULL)
|
|
return "FAILED";
|
|
return "OK";
|
|
}
|
|
|
|
static int inode_open(ntfs_volume *vol, MFT_REF mref, ntfs_inode **ni)
|
|
{
|
|
*ni = ntfs_inode_open(vol, mref);
|
|
if (*ni == NULL) {
|
|
if (errno == EIO)
|
|
return NTFSCMP_INODE_OPEN_IO_ERROR;
|
|
if (errno == ENOENT)
|
|
return NTFSCMP_INODE_OPEN_ENOENT_ERROR;
|
|
|
|
perr_println("Reading inode %lld failed", mref);
|
|
return NTFSCMP_INODE_OPEN_ERROR;
|
|
}
|
|
|
|
if ((*ni)->mrec->base_mft_record) {
|
|
|
|
if (inode_close(*ni) != 0)
|
|
return NTFSCMP_INODE_CLOSE_ERROR;
|
|
|
|
return NTFSCMP_EXTENSION_RECORD;
|
|
}
|
|
|
|
return NTFSCMP_OK;
|
|
}
|
|
|
|
static ntfs_inode *base_inode(ntfs_attr_search_ctx *ctx)
|
|
{
|
|
if (ctx->base_ntfs_ino)
|
|
return ctx->base_ntfs_ino;
|
|
|
|
return ctx->ntfs_ino;
|
|
}
|
|
|
|
static void print_inode(u64 inum)
|
|
{
|
|
printf("Inode %llu ", inum);
|
|
}
|
|
|
|
static void print_inode_ni(ntfs_inode *ni)
|
|
{
|
|
print_inode(inumber(ni));
|
|
}
|
|
|
|
static void print_attribute_type(ATTR_TYPES atype)
|
|
{
|
|
printf("attribute 0x%x", atype);
|
|
}
|
|
|
|
static void print_attribute_name(char *name)
|
|
{
|
|
if (name)
|
|
printf(":%s", name);
|
|
}
|
|
|
|
#define GET_ATTR_NAME(a) \
|
|
((ntfschar *)(((u8 *)(a)) + le16_to_cpu((a)->name_offset))), \
|
|
((a)->name_length)
|
|
|
|
static void free_name(char **name)
|
|
{
|
|
if (*name) {
|
|
free(*name);
|
|
*name = NULL;
|
|
}
|
|
}
|
|
|
|
static char *get_attr_name(u64 mft_no,
|
|
ATTR_TYPES atype,
|
|
const ntfschar *uname,
|
|
const int uname_len)
|
|
{
|
|
char *name = NULL;
|
|
int name_len;
|
|
|
|
if (atype == AT_END)
|
|
return NULL;
|
|
|
|
name_len = ntfs_ucstombs(uname, uname_len, &name, 0);
|
|
if (name_len < 0) {
|
|
perr_print("ntfs_ucstombs");
|
|
print_inode(mft_no);
|
|
print_attribute_type(atype);
|
|
puts("");
|
|
exit(1);
|
|
|
|
} else if (name_len > 0)
|
|
return name;
|
|
|
|
free_name(&name);
|
|
return NULL;
|
|
}
|
|
|
|
static char *get_attr_name_na(ntfs_attr *na)
|
|
{
|
|
return get_attr_name(inumber(na->ni), na->type, na->name, na->name_len);
|
|
}
|
|
|
|
static char *get_attr_name_ctx(ntfs_attr_search_ctx *ctx)
|
|
{
|
|
u64 mft_no = inumber(ctx->ntfs_ino);
|
|
ATTR_TYPES atype = ctx->attr->type;
|
|
|
|
return get_attr_name(mft_no, atype, GET_ATTR_NAME(ctx->attr));
|
|
}
|
|
|
|
static void print_attribute(ATTR_TYPES atype, char *name)
|
|
{
|
|
print_attribute_type(atype);
|
|
print_attribute_name(name);
|
|
printf(" ");
|
|
}
|
|
|
|
static void print_na(ntfs_attr *na)
|
|
{
|
|
char *name = get_attr_name_na(na);
|
|
print_inode_ni(na->ni);
|
|
print_attribute(na->type, name);
|
|
free_name(&name);
|
|
}
|
|
|
|
static void print_attribute_ctx(ntfs_attr_search_ctx *ctx)
|
|
{
|
|
char *name = get_attr_name_ctx(ctx);
|
|
print_attribute(ctx->attr->type, name);
|
|
free_name(&name);
|
|
}
|
|
|
|
static void print_ctx(ntfs_attr_search_ctx *ctx)
|
|
{
|
|
char *name = get_attr_name_ctx(ctx);
|
|
print_inode_ni(base_inode(ctx));
|
|
print_attribute(ctx->attr->type, name);
|
|
free_name(&name);
|
|
}
|
|
|
|
static void print_differ(ntfs_attr *na)
|
|
{
|
|
print_na(na);
|
|
printf("content: DIFFER\n");
|
|
}
|
|
|
|
static int cmp_buffer(u8 *buf1, u8 *buf2, long long int size, ntfs_attr *na)
|
|
{
|
|
if (memcmp(buf1, buf2, size)) {
|
|
print_differ(na);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
struct cmp_ia {
|
|
INDEX_ALLOCATION *ia;
|
|
INDEX_ALLOCATION *tmp_ia;
|
|
u8 *bitmap;
|
|
u8 *byte;
|
|
s64 bm_size;
|
|
};
|
|
|
|
static int setup_cmp_ia(ntfs_attr *na, struct cmp_ia *cia)
|
|
{
|
|
cia->bitmap = ntfs_attr_readall(na->ni, AT_BITMAP, na->name,
|
|
na->name_len, &cia->bm_size);
|
|
if (!cia->bitmap) {
|
|
perr_println("Failed to readall BITMAP");
|
|
return -1;
|
|
}
|
|
cia->byte = cia->bitmap;
|
|
|
|
cia->tmp_ia = cia->ia = ntfs_malloc(na->data_size);
|
|
if (!cia->tmp_ia)
|
|
goto free_bm;
|
|
|
|
if (ntfs_attr_pread(na, 0, na->data_size, cia->ia) != na->data_size) {
|
|
perr_println("Failed to pread INDEX_ALLOCATION");
|
|
goto free_ia;
|
|
}
|
|
|
|
return 0;
|
|
free_ia:
|
|
free(cia->ia);
|
|
free_bm:
|
|
free(cia->bitmap);
|
|
return -1;
|
|
}
|
|
|
|
static void cmp_index_allocation(ntfs_attr *na1, ntfs_attr *na2)
|
|
{
|
|
struct cmp_ia cia1, cia2;
|
|
int bit, ret1, ret2;
|
|
u32 ib_size;
|
|
|
|
if (setup_cmp_ia(na1, &cia1))
|
|
return;
|
|
if (setup_cmp_ia(na2, &cia2))
|
|
return;
|
|
/*
|
|
* FIXME: ia can be the same even if the bitmap sizes are different.
|
|
*/
|
|
if (cia1.bm_size != cia1.bm_size)
|
|
goto out;
|
|
|
|
if (cmp_buffer(cia1.bitmap, cia2.bitmap, cia1.bm_size, na1))
|
|
goto out;
|
|
|
|
if (cmp_buffer((u8 *)cia1.ia, (u8 *)cia2.ia, 0x18, na1))
|
|
goto out;
|
|
|
|
ib_size = le32_to_cpu(cia1.ia->index.allocated_size) + 0x18;
|
|
|
|
bit = 0;
|
|
while ((u8 *)cia1.tmp_ia < (u8 *)cia1.ia + na1->data_size) {
|
|
if (*cia1.byte & (1 << bit)) {
|
|
ret1 = ntfs_mst_post_read_fixup((NTFS_RECORD *)
|
|
cia1.tmp_ia, ib_size);
|
|
ret2 = ntfs_mst_post_read_fixup((NTFS_RECORD *)
|
|
cia2.tmp_ia, ib_size);
|
|
if (ret1 != ret2) {
|
|
print_differ(na1);
|
|
goto out;
|
|
}
|
|
|
|
if (ret1 == -1)
|
|
continue;
|
|
|
|
if (cmp_buffer(((u8 *)cia1.tmp_ia) + 0x18,
|
|
((u8 *)cia2.tmp_ia) + 0x18,
|
|
le32_to_cpu(cia1.ia->
|
|
index.index_length), na1))
|
|
goto out;
|
|
}
|
|
|
|
cia1.tmp_ia = (INDEX_ALLOCATION *)((u8 *)cia1.tmp_ia + ib_size);
|
|
cia2.tmp_ia = (INDEX_ALLOCATION *)((u8 *)cia2.tmp_ia + ib_size);
|
|
|
|
bit++;
|
|
if (bit > 7) {
|
|
bit = 0;
|
|
cia1.byte++;
|
|
}
|
|
}
|
|
out:
|
|
free(cia1.ia);
|
|
free(cia2.ia);
|
|
free(cia1.bitmap);
|
|
free(cia2.bitmap);
|
|
return;
|
|
}
|
|
|
|
static void cmp_attribute_data(ntfs_attr *na1, ntfs_attr *na2)
|
|
{
|
|
s64 pos;
|
|
s64 count1 = 0, count2;
|
|
u8 buf1[NTFS_BUF_SIZE];
|
|
u8 buf2[NTFS_BUF_SIZE];
|
|
|
|
for (pos = 0; pos <= na1->data_size; pos += count1) {
|
|
|
|
count1 = ntfs_attr_pread(na1, pos, NTFS_BUF_SIZE, buf1);
|
|
count2 = ntfs_attr_pread(na2, pos, NTFS_BUF_SIZE, buf2);
|
|
|
|
if (count1 != count2) {
|
|
print_na(na1);
|
|
printf("abrupt length: %lld != %lld ",
|
|
na1->data_size, na2->data_size);
|
|
printf("(count: %lld != %lld)", count1, count2);
|
|
puts("");
|
|
return;
|
|
}
|
|
|
|
if (count1 == -1) {
|
|
err_printf("%s read error: ", __FUNCTION__);
|
|
print_na(na1);
|
|
printf("len = %lld, pos = %lld\n", na1->data_size, pos);
|
|
exit(1);
|
|
}
|
|
|
|
if (count1 == 0) {
|
|
|
|
if (pos + count1 == na1->data_size)
|
|
return; /* we are ready */
|
|
|
|
err_printf("%s read error before EOF: ", __FUNCTION__);
|
|
print_na(na1);
|
|
printf("%lld != %lld\n", pos + count1, na1->data_size);
|
|
exit(1);
|
|
}
|
|
|
|
if (cmp_buffer(buf1, buf2, count1, na1))
|
|
return;
|
|
}
|
|
|
|
err_printf("%s read overrun: ", __FUNCTION__);
|
|
print_na(na1);
|
|
err_printf("(len = %lld, pos = %lld, count = %lld)\n",
|
|
na1->data_size, pos, count1);
|
|
exit(1);
|
|
}
|
|
|
|
static int cmp_attribute_header(ATTR_RECORD *a1, ATTR_RECORD *a2)
|
|
{
|
|
u32 header_size = offsetof(ATTR_RECORD, resident_end);
|
|
|
|
if (a1->non_resident != a2->non_resident)
|
|
return 1;
|
|
|
|
if (a1->non_resident) {
|
|
/*
|
|
* FIXME: includes paddings which are not handled by ntfsinfo!
|
|
*/
|
|
header_size = le32_to_cpu(a1->length);
|
|
}
|
|
|
|
return memcmp(a1, a2, header_size);
|
|
}
|
|
|
|
static void cmp_attribute(ntfs_attr_search_ctx *ctx1,
|
|
ntfs_attr_search_ctx *ctx2)
|
|
{
|
|
ATTR_RECORD *a1 = ctx1->attr;
|
|
ATTR_RECORD *a2 = ctx2->attr;
|
|
ntfs_attr *na1, *na2;
|
|
|
|
if (cmp_attribute_header(a1, a2)) {
|
|
print_ctx(ctx1);
|
|
printf("header: DIFFER\n");
|
|
}
|
|
|
|
na1 = ntfs_attr_open(base_inode(ctx1), a1->type, GET_ATTR_NAME(a1));
|
|
na2 = ntfs_attr_open(base_inode(ctx2), a2->type, GET_ATTR_NAME(a2));
|
|
|
|
if ((!na1 && na2) || (na1 && !na2)) {
|
|
print_ctx(ctx1);
|
|
printf("open: %s != %s\n", pret2str(na1), pret2str(na2));
|
|
goto close_attribs;
|
|
}
|
|
|
|
if (na1 == NULL)
|
|
goto close_attribs;
|
|
|
|
if (na1->data_size != na2->data_size) {
|
|
print_na(na1);
|
|
printf("length: %lld != %lld\n", na1->data_size, na2->data_size);
|
|
goto close_attribs;
|
|
}
|
|
|
|
if (ntfs_inode_badclus_bad(inumber(ctx1->ntfs_ino), ctx1->attr) == 1) {
|
|
/*
|
|
* If difference exists then it's already reported at the
|
|
* attribute header since the mapping pairs must differ.
|
|
*/
|
|
return;
|
|
}
|
|
|
|
if (na1->type == AT_INDEX_ALLOCATION)
|
|
cmp_index_allocation(na1, na2);
|
|
else
|
|
cmp_attribute_data(na1, na2);
|
|
|
|
close_attribs:
|
|
ntfs_attr_close(na1);
|
|
ntfs_attr_close(na2);
|
|
}
|
|
|
|
static void vprint_attribute(ATTR_TYPES atype, char *name)
|
|
{
|
|
if (!opt.verbose)
|
|
return;
|
|
|
|
printf("0x%x", atype);
|
|
if (name)
|
|
printf(":%s", name);
|
|
printf(" ");
|
|
}
|
|
|
|
static void print_attributes(ntfs_inode *ni,
|
|
ATTR_TYPES atype1,
|
|
ATTR_TYPES atype2,
|
|
char *name1,
|
|
char *name2)
|
|
{
|
|
if (!opt.verbose)
|
|
return;
|
|
|
|
printf("Walking inode %llu attributes: ", inumber(ni));
|
|
vprint_attribute(atype1, name1);
|
|
vprint_attribute(atype2, name2);
|
|
printf("\n");
|
|
}
|
|
|
|
static int new_name(ntfs_attr_search_ctx *ctx, char *prev_name)
|
|
{
|
|
int ret = 0;
|
|
char *name = get_attr_name_ctx(ctx);
|
|
|
|
if (prev_name && name) {
|
|
if (strcmp(prev_name, name) != 0)
|
|
ret = 1;
|
|
} else if (prev_name || name)
|
|
ret = 1;
|
|
|
|
free_name(&name);
|
|
return ret;
|
|
|
|
}
|
|
|
|
static int new_attribute(ntfs_attr_search_ctx *ctx,
|
|
ATTR_TYPES prev_atype,
|
|
char *prev_name)
|
|
{
|
|
if (!prev_atype && !prev_name)
|
|
return 1;
|
|
|
|
if (!ctx->attr->non_resident)
|
|
return 1;
|
|
|
|
if (prev_atype != ctx->attr->type)
|
|
return 1;
|
|
|
|
if (new_name(ctx, prev_name))
|
|
return 1;
|
|
|
|
if (opt.verbose) {
|
|
print_inode(base_inode(ctx)->mft_no);
|
|
print_attribute_ctx(ctx);
|
|
printf("record %llu lowest_vcn %lld: SKIPPED\n",
|
|
ctx->ntfs_ino->mft_no, ctx->attr->lowest_vcn);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void set_prev(char **prev_name, ATTR_TYPES *prev_atype,
|
|
char *name, ATTR_TYPES atype)
|
|
{
|
|
free_name(prev_name);
|
|
if (name) {
|
|
*prev_name = strdup(name);
|
|
if (!*prev_name)
|
|
perr_exit("strdup error");
|
|
}
|
|
|
|
*prev_atype = atype;
|
|
}
|
|
|
|
static void set_cmp_attr(ntfs_attr_search_ctx *ctx, ATTR_TYPES *atype, char **name)
|
|
{
|
|
*atype = ctx->attr->type;
|
|
|
|
free_name(name);
|
|
*name = get_attr_name_ctx(ctx);
|
|
}
|
|
|
|
static int next_attr(ntfs_attr_search_ctx *ctx, ATTR_TYPES *atype, char **name,
|
|
int *err)
|
|
{
|
|
int ret;
|
|
|
|
ret = ntfs_attrs_walk(ctx);
|
|
*err = errno;
|
|
if (ret) {
|
|
*atype = AT_END;
|
|
free_name(name);
|
|
} else
|
|
set_cmp_attr(ctx, atype, name);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int cmp_attributes(ntfs_inode *ni1, ntfs_inode *ni2)
|
|
{
|
|
int ret = -1;
|
|
int old_ret1, ret1 = 0, ret2 = 0;
|
|
int errno1 = 0, errno2 = 0;
|
|
char *prev_name = NULL, *name1 = NULL, *name2 = NULL;
|
|
ATTR_TYPES old_atype1, prev_atype = 0, atype1, atype2;
|
|
ntfs_attr_search_ctx *ctx1, *ctx2;
|
|
|
|
if (!(ctx1 = attr_get_search_ctx(ni1)))
|
|
return -1;
|
|
if (!(ctx2 = attr_get_search_ctx(ni2)))
|
|
goto out;
|
|
|
|
set_cmp_attr(ctx1, &atype1, &name1);
|
|
set_cmp_attr(ctx2, &atype2, &name2);
|
|
|
|
while (1) {
|
|
|
|
old_atype1 = atype1;
|
|
old_ret1 = ret1;
|
|
if (!ret1 && (le32_to_cpu(atype1) <= le32_to_cpu(atype2) ||
|
|
ret2))
|
|
ret1 = next_attr(ctx1, &atype1, &name1, &errno1);
|
|
if (!ret2 && (le32_to_cpu(old_atype1) >= le32_to_cpu(atype2) ||
|
|
old_ret1))
|
|
ret2 = next_attr(ctx2, &atype2, &name2, &errno2);
|
|
|
|
print_attributes(ni1, atype1, atype2, name1, name2);
|
|
|
|
if (ret1 && ret2) {
|
|
if (errno1 != errno2) {
|
|
print_inode_ni(ni1);
|
|
printf("attribute walk (errno): %d != %d\n",
|
|
errno1, errno2);
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (ret2 || le32_to_cpu(atype1) < le32_to_cpu(atype2)) {
|
|
if (new_attribute(ctx1, prev_atype, prev_name)) {
|
|
print_ctx(ctx1);
|
|
printf("presence: EXISTS != MISSING\n");
|
|
set_prev(&prev_name, &prev_atype, name1,
|
|
atype1);
|
|
}
|
|
|
|
} else if (ret1 || le32_to_cpu(atype1) > le32_to_cpu(atype2)) {
|
|
if (new_attribute(ctx2, prev_atype, prev_name)) {
|
|
print_ctx(ctx2);
|
|
printf("presence: MISSING != EXISTS \n");
|
|
set_prev(&prev_name, &prev_atype, name2, atype2);
|
|
}
|
|
|
|
} else /* atype1 == atype2 */ {
|
|
if (new_attribute(ctx1, prev_atype, prev_name)) {
|
|
cmp_attribute(ctx1, ctx2);
|
|
set_prev(&prev_name, &prev_atype, name1, atype1);
|
|
}
|
|
}
|
|
}
|
|
|
|
free_name(&prev_name);
|
|
ret = 0;
|
|
ntfs_attr_put_search_ctx(ctx2);
|
|
out:
|
|
ntfs_attr_put_search_ctx(ctx1);
|
|
return ret;
|
|
}
|
|
|
|
static int cmp_inodes(ntfs_volume *vol1, ntfs_volume *vol2)
|
|
{
|
|
u64 inode;
|
|
int ret1, ret2;
|
|
ntfs_inode *ni1, *ni2;
|
|
struct progress_bar progress;
|
|
int pb_flags = 0; /* progress bar flags */
|
|
u64 nr_mft_records, nr_mft_records2;
|
|
|
|
if (opt.show_progress)
|
|
pb_flags |= NTFS_PROGBAR;
|
|
|
|
nr_mft_records = get_nr_mft_records(vol1);
|
|
nr_mft_records2 = get_nr_mft_records(vol2);
|
|
|
|
if (nr_mft_records != nr_mft_records2) {
|
|
|
|
printf("Number of mft records: %lld != %lld\n",
|
|
nr_mft_records, nr_mft_records2);
|
|
|
|
if (nr_mft_records > nr_mft_records2)
|
|
nr_mft_records = nr_mft_records2;
|
|
}
|
|
|
|
progress_init(&progress, 0, nr_mft_records - 1, pb_flags);
|
|
progress_update(&progress, 0);
|
|
|
|
for (inode = 0; inode < nr_mft_records; inode++) {
|
|
|
|
ret1 = inode_open(vol1, (MFT_REF)inode, &ni1);
|
|
ret2 = inode_open(vol2, (MFT_REF)inode, &ni2);
|
|
|
|
if (ret1 != ret2) {
|
|
print_inode(inode);
|
|
printf("open: %s != %s\n",
|
|
err2string(ret1), err2string(ret2));
|
|
goto close_inodes;
|
|
}
|
|
|
|
if (ret1 != NTFSCMP_OK)
|
|
goto close_inodes;
|
|
|
|
if (cmp_attributes(ni1, ni2) != 0) {
|
|
inode_close(ni1);
|
|
inode_close(ni2);
|
|
return -1;
|
|
}
|
|
close_inodes:
|
|
if (inode_close(ni1) != 0)
|
|
return -1;
|
|
if (inode_close(ni2) != 0)
|
|
return -1;
|
|
|
|
progress_update(&progress, inode);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static ntfs_volume *mount_volume(const char *volume)
|
|
{
|
|
unsigned long mntflag;
|
|
ntfs_volume *vol = NULL;
|
|
|
|
if (ntfs_check_if_mounted(volume, &mntflag)) {
|
|
perr_println("Failed to check '%s' mount state", volume);
|
|
printf("Probably /etc/mtab is missing. It's too risky to "
|
|
"continue. You might try\nan another Linux distro.\n");
|
|
exit(1);
|
|
}
|
|
if (mntflag & NTFS_MF_MOUNTED) {
|
|
if (!(mntflag & NTFS_MF_READONLY))
|
|
err_exit("Device '%s' is mounted read-write. "
|
|
"You must 'umount' it first.\n", volume);
|
|
}
|
|
|
|
vol = ntfs_mount(volume, NTFS_MNT_RDONLY);
|
|
if (vol == NULL) {
|
|
|
|
int err = errno;
|
|
|
|
perr_println("Opening '%s' as NTFS failed", volume);
|
|
if (err == EINVAL)
|
|
printf(invalid_ntfs_msg, volume);
|
|
else if (err == EIO)
|
|
puts(corrupt_volume_msg);
|
|
else if (err == EPERM)
|
|
puts(hibernated_volume_msg);
|
|
exit(1);
|
|
}
|
|
|
|
return vol;
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
ntfs_volume *vol1;
|
|
ntfs_volume *vol2;
|
|
|
|
printf("%s v%s (libntfs %s)\n", EXEC_NAME, VERSION,
|
|
ntfs_libntfs_version());
|
|
|
|
parse_options(argc, argv);
|
|
|
|
utils_set_locale();
|
|
|
|
vol1 = mount_volume(opt.vol1);
|
|
vol2 = mount_volume(opt.vol2);
|
|
|
|
if (cmp_inodes(vol1, vol2) != 0)
|
|
exit(1);
|
|
|
|
ntfs_umount(vol1, FALSE);
|
|
ntfs_umount(vol2, FALSE);
|
|
|
|
exit(0);
|
|
}
|
|
|