/**
 * 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;


/* 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 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", le32_to_cpu(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 != cia2.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", le32_to_cpu(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)sle64_to_cpu(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 = const_cpu_to_le32(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, 100, 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);
}
