diff --git a/TODO.ntfsprogs b/TODO.ntfsprogs index a802fb36..25807171 100644 --- a/TODO.ntfsprogs +++ b/TODO.ntfsprogs @@ -39,6 +39,19 @@ Thanks, - check whether the O_WRONLY -> O_RDWR change made effect on performance +*********** +* ntfscmp * +*********** + +- more exact details about the differences +- unnamed resident attributes with same type are ignored +- special $BadClus:$Bad handling, now $BadClus is skipped +- new option: --metadata mode +- code cleanup, remove many cross-util duplicates +- write manual +- performance: perhaps special handling for sparse, compressed, encrypted + + ********** * ntfscp * ********** diff --git a/ntfsprogs/Makefile.am b/ntfsprogs/Makefile.am index 1b3b6f74..fc2d5da7 100644 --- a/ntfsprogs/Makefile.am +++ b/ntfsprogs/Makefile.am @@ -15,7 +15,7 @@ bin_PROGRAMS = ntfsfix ntfsinfo ntfscluster ntfsls ntfscat sbin_PROGRAMS = mkntfs ntfslabel ntfsundelete ntfsresize ntfsclone \ ntfscp EXTRA_PROGRAMS = ntfsdump_logfile ntfswipe ntfstruncate ntfsmove \ - ntfsrm ntfsmftalloc + ntfsrm ntfsmftalloc ntfscmp man_MANS = mkntfs.8 ntfsfix.8 ntfslabel.8 ntfsinfo.8 \ ntfsundelete.8 ntfsresize.8 ntfsprogs.8 ntfsls.8 \ @@ -101,6 +101,10 @@ ntfstruncate_SOURCES = attrdef.c ntfstruncate.c utils.c utils.h ntfstruncate_LDADD = $(AM_LIBS) ntfstruncate_LDFLAGS = $(AM_LFLAGS) +ntfscmp_SOURCES = ntfscmp.c utils.c utils.h +ntfscmp_LDADD = $(AM_LIBS) +ntfscmp_LDFLAGS = $(AM_LFLAGS) + ntfsmftalloc_SOURCES = ntfsmftalloc.c utils.c utils.h ntfsmftalloc_LDADD = $(AM_LIBS) ntfsmftalloc_LDFLAGS = $(AM_LFLAGS) diff --git a/ntfsprogs/ntfscmp.c b/ntfsprogs/ntfscmp.c new file mode 100644 index 00000000..ba59af34 --- /dev/null +++ b/ntfsprogs/ntfscmp.c @@ -0,0 +1,795 @@ +/** + * ntfscmp - compare two NTFS volumes. + * + * Copyright (c) 2005 Szabolcs Szakacsits + * + * This utility is part of the Linux-NTFS project. + * + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "utils.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"; + + +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; + u8 padding[4]; /* Unused: padding to 64 bit. */ +}; + +/* 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 ": " + +GEN_PRINTF(Eprintf, stderr, NULL, FALSE) +GEN_PRINTF(Vprintf, stdout, &opt.verbose, TRUE) +GEN_PRINTF(Qprintf, stdout, NULL, FALSE) + +static void perr_printf(int newline, const char *fmt, ...) + __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__) + +static void err_printf(const char *fmt, ...) + __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. + */ +static int err_exit(const char *fmt, ...) + __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 + */ +static int perr_exit(const char *fmt, ...) + __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 + */ +static void usage(void) __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 } + }; + + char c; + + memset(&opt, 0, sizeof(opt)); + opt.show_progress = 1; + + while ((c = getopt_long (argc, argv, sopt, lopt, NULL)) != (char)-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(); + } + + stderr = stdout; + +#ifdef DEBUG + if (!opt.debug) + if (!(stderr = fopen("/dev/null", "rw"))) + perr_exit("Couldn't open /dev/null"); + +#endif +} + +static ntfs_attr_search_ctx *attr_get_search_ctx(ntfs_inode *ni, MFT_RECORD *mrec) +{ + ntfs_attr_search_ctx *ret; + + if ((ret = ntfs_attr_get_search_ctx(ni, mrec)) == 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 + +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 *ret2string(int ret) +{ + if (ret == -1) + return "FAILED"; + else if (ret != 0) + err_exit("Unhandled return code: %d\n", ret); + return "OK"; +} + +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)) + ((a)->name_offset))), ((a)->name_length) + +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; + + 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) +{ + print_inode_ni(na->ni); + print_attribute(na->type, get_attr_name_na(na)); +} + +static void print_attribute_ctx(ntfs_attr_search_ctx *ctx) +{ + print_attribute(ctx->attr->type, get_attr_name_ctx(ctx)); +} + +static void print_ctx(ntfs_attr_search_ctx *ctx) +{ + print_inode_ni(base_inode(ctx)); + print_attribute(ctx->attr->type, get_attr_name_ctx(ctx)); +} + +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); + Vprintf("(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 (memcmp(buf1, buf2, count1)) { + print_na(na1); + printf("content"); + Vprintf(" (len = %lld)", count1); + printf(": DIFFER\n"); + 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 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; + + 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; + } + + cmp_attribute_data(na1, na2); + +close_attribs: + ntfs_attr_close(na1); + ntfs_attr_close(na2); +} + +static void vprint_attribute(ATTR_TYPES atype, char *name) +{ + Vprintf("0x%x", atype); + if (name) + Vprintf(":%s", name); + Vprintf(" "); +} + +static void print_attributes(ntfs_inode *ni, + ATTR_TYPES atype1, + ATTR_TYPES atype2, + char *name1, + char *name2) +{ + Vprintf("Walking inode %llu attributes: ", inumber(ni)); + vprint_attribute(atype1, name1); + vprint_attribute(atype2, name2); + Vprintf("\n"); +} + +static int new_attribute(ntfs_attr_search_ctx *ctx, + ATTR_TYPES prev_atype, + char *prev_name) +{ + char *name = get_attr_name_ctx(ctx); + + if (!ctx->attr->non_resident) + return 1; + + if (prev_atype != ctx->attr->type) + return 1; + + if (prev_name && name) { + if (strcmp(prev_name, name) != 0) + return 1; + } else if (prev_name || name) + return 1; + + print_inode(base_inode(ctx)->mft_no); + print_attribute_ctx(ctx); + Vprintf("extent %llu lowest_vcn %lld: SKIPPED\n", + ctx->ntfs_ino->mft_no, ctx->attr->lowest_vcn); + + return 0; +} + +static void set_prev(char **prev_name, char *name, char *name_unused, + ATTR_TYPES *prev_atype, ATTR_TYPES atype) +{ + if (*prev_name) + free(*prev_name); + *prev_name = name; + + free(name_unused); + + *prev_atype = atype; +} + +static int cmp_attributes(ntfs_inode *ni1, ntfs_inode *ni2) +{ + int ret = -1; + int ret1 = 0, ret2 = 0; + int prev_first = 1; + char *prev_name = NULL, *name1, *name2; + ATTR_TYPES prev_atype, atype1, atype2; + ntfs_attr_search_ctx *ctx1, *ctx2; + + if (!(ctx1 = attr_get_search_ctx(ni1, NULL))) + return -1; + if (!(ctx2 = attr_get_search_ctx(ni2, NULL))) + goto out; + + atype1 = ctx1->attr->type; + atype2 = ctx2->attr->type; + + while (1) { + if (atype1 <= atype2) + ret1 = ntfs_attrs_walk(ctx1); + if (atype1 >= atype2) + ret2 = ntfs_attrs_walk(ctx2); + + atype1 = ctx1->attr->type; + atype2 = ctx2->attr->type; + name1 = get_attr_name_ctx(ctx1); + name2 = get_attr_name_ctx(ctx2); + + print_attributes(ni1, atype1, atype2, name1, name2); + + if (atype1 != AT_END && atype2 != AT_END && ret1 != ret2) { + print_inode_ni(ni1); + printf("attribute_walk: %s != %s\n", + ret2string(ret1), ret2string(ret2)); + break; + } + + if (atype1 == atype2) { + + if (atype1 == AT_END) + break; + + if (prev_first || new_attribute(ctx1, prev_atype, prev_name)) { + prev_first = 0; + cmp_attribute(ctx1, ctx2); + set_prev(&prev_name, name1, name2, &prev_atype, atype1); + } + + } else if (atype2 == AT_END || atype1 < atype2) { + if (prev_first || new_attribute(ctx1, prev_atype, prev_name)) { + prev_first = 0; + print_ctx(ctx1); + printf("presence: EXISTS != MISSING\n"); + set_prev(&prev_name, name1, name2, &prev_atype, atype1); + } + + } else /* atype1 == AT_END || atype1 > atype2) */ { + if (prev_first || new_attribute(ctx2, prev_atype, prev_name)) { + prev_first = 0; + print_ctx(ctx2); + printf("presence: MISSING != EXISTS \n"); + set_prev(&prev_name, name2, name1, &prev_atype, atype2); + } + } + } + + 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++) { + + /* FIXME: needs special handling */ + if (inode == 8) + continue; + + 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, MS_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) + printf(corrupt_volume_msg); + else if (err == EPERM) + printf(hibernated_volume_msg); + exit(1); + } + + return vol; +} + +int main(int argc, char **argv) +{ + ntfs_volume *vol1; + ntfs_volume *vol2; + + printf("%s v%s\n", EXEC_NAME, 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); + + exit(0); +} +