| /** |
| * 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 "mst.h" |
| #include "support.h" |
| #include "utils.h" |
| #include "misc.h" |
| /* #include "version.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", |
| (unsigned long long)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", (long long)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 ", (unsigned long long)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 ", |
| (long long)na1->data_size, |
| (long long)na2->data_size); |
| printf("(count: %lld != %lld)", |
| (long long)count1, (long long)count2); |
| puts(""); |
| return; |
| } |
| |
| if (count1 == -1) { |
| err_printf("%s read error: ", __FUNCTION__); |
| print_na(na1); |
| printf("len = %lld, pos = %lld\n", |
| (long long)na1->data_size, (long long)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", (long long)pos + count1, |
| (long long)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", |
| (long long)na1->data_size, (long long)pos, (long long)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", |
| (long long)na1->data_size, (long long)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. |
| */ |
| goto close_attribs; |
| } |
| |
| 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: ", |
| (unsigned long long)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", |
| (unsigned long long)ctx->ntfs_ino->mft_no, |
| (long long)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", |
| (long long)nr_mft_records, (long long)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-3g)\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); |
| |
| ntfs_umount(vol1, FALSE); |
| ntfs_umount(vol2, FALSE); |
| |
| return (0); |
| } |
| |