ntfscmp.c revision 9663:ace9a2ac3683
1/**
2 * ntfscmp - Part of the Linux-NTFS project.
3 *
4 * Copyright (c) 2005-2006 Szabolcs Szakacsits
5 * Copyright (c) 2005      Anton Altaparmakov
6 * Copyright (c) 2007      Yura Pakhuchiy
7 *
8 * This utility compare two NTFS volumes.
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program (in the main directory of the Linux-NTFS
22 * distribution in the file COPYING); if not, write to the Free Software
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
24 */
25
26#include "config.h"
27
28#include <unistd.h>
29#include <stdlib.h>
30#include <stdio.h>
31#include <stdarg.h>
32#include <string.h>
33#include <errno.h>
34#include <getopt.h>
35
36#include "utils.h"
37#include "mst.h"
38#include "version.h"
39#include "support.h"
40
41static const char *EXEC_NAME = "ntfscmp";
42
43static const char *invalid_ntfs_msg =
44"Apparently device '%s' doesn't have a valid NTFS.\n"
45"Maybe you selected the wrong partition? Or the whole disk instead of a\n"
46"partition (e.g. /dev/hda, not /dev/hda1)?\n";
47
48static const char *corrupt_volume_msg =
49"Apparently you have a corrupted NTFS. Please run the filesystem checker\n"
50"on Windows by invoking chkdsk /f. Don't forget the /f (force) parameter,\n"
51"it's important! You probably also need to reboot Windows to take effect.\n";
52
53static const char *hibernated_volume_msg =
54"Apparently the NTFS partition is hibernated. Windows must be resumed and\n"
55"turned off properly\n";
56
57
58static struct {
59	int debug;
60	int show_progress;
61	int verbose;
62	char *vol1;
63	char *vol2;
64} opt;
65
66
67#define NTFS_PROGBAR		0x0001
68#define NTFS_PROGBAR_SUPPRESS	0x0002
69
70struct progress_bar {
71	u64 start;
72	u64 stop;
73	int resolution;
74	int flags;
75	float unit;
76};
77
78/* WARNING: don't modify the text, external tools grep for it */
79#define ERR_PREFIX   "ERROR"
80#define PERR_PREFIX  ERR_PREFIX "(%d): "
81#define NERR_PREFIX  ERR_PREFIX ": "
82
83__attribute__((format(printf, 2, 3)))
84static void perr_printf(int newline, const char *fmt, ...)
85{
86	va_list ap;
87	int eo = errno;
88
89	fprintf(stdout, PERR_PREFIX, eo);
90	va_start(ap, fmt);
91	vfprintf(stdout, fmt, ap);
92	va_end(ap);
93	fprintf(stdout, ": %s", strerror(eo));
94	if (newline)
95		fprintf(stdout, "\n");
96	fflush(stdout);
97	fflush(stderr);
98}
99
100#define perr_print(...)     perr_printf(0, __VA_ARGS__)
101#define perr_println(...)   perr_printf(1, __VA_ARGS__)
102
103__attribute__((format(printf, 1, 2)))
104static void err_printf(const char *fmt, ...)
105{
106	va_list ap;
107
108	fprintf(stdout, NERR_PREFIX);
109	va_start(ap, fmt);
110	vfprintf(stdout, fmt, ap);
111	va_end(ap);
112	fflush(stdout);
113	fflush(stderr);
114}
115
116/**
117 * err_exit
118 *
119 * Print and error message and exit the program.
120 */
121__attribute__((noreturn))
122__attribute__((format(printf, 1, 2)))
123static int err_exit(const char *fmt, ...)
124{
125	va_list ap;
126
127	fprintf(stdout, NERR_PREFIX);
128	va_start(ap, fmt);
129	vfprintf(stdout, fmt, ap);
130	va_end(ap);
131	fflush(stdout);
132	fflush(stderr);
133	exit(1);
134}
135
136/**
137 * perr_exit
138 *
139 * Print and error message and exit the program
140 */
141__attribute__((noreturn))
142__attribute__((format(printf, 1, 2)))
143static int perr_exit(const char *fmt, ...)
144{
145	va_list ap;
146	int eo = errno;
147
148	fprintf(stdout, PERR_PREFIX, eo);
149	va_start(ap, fmt);
150	vfprintf(stdout, fmt, ap);
151	va_end(ap);
152	printf(": %s\n", strerror(eo));
153	fflush(stdout);
154	fflush(stderr);
155	exit(1);
156}
157
158/**
159 * usage - Print a list of the parameters to the program
160 *
161 * Print a list of the parameters and options for the program.
162 *
163 * Return:  none
164 */
165__attribute__((noreturn))
166static void usage(void)
167{
168
169	printf("\nUsage: %s [OPTIONS] DEVICE1 DEVICE2\n"
170		"    Compare two NTFS volumes and tell the differences.\n"
171		"\n"
172		"    -P, --no-progress-bar  Don't show progress bar\n"
173		"    -v, --verbose          More output\n"
174		"    -h, --help             Display this help\n"
175#ifdef DEBUG
176		"    -d, --debug            Show debug information\n"
177#endif
178		"\n", EXEC_NAME);
179	printf("%s%s", ntfs_bugs, ntfs_home);
180	exit(1);
181}
182
183
184static void parse_options(int argc, char **argv)
185{
186	static const char *sopt = "-dhPv";
187	static const struct option lopt[] = {
188#ifdef DEBUG
189		{ "debug",		no_argument,	NULL, 'd' },
190#endif
191		{ "help",		no_argument,	NULL, 'h' },
192		{ "no-progress-bar",	no_argument,	NULL, 'P' },
193		{ "verbose",		no_argument,	NULL, 'v' },
194		{ NULL, 0, NULL, 0 }
195	};
196
197	int c;
198
199	memset(&opt, 0, sizeof(opt));
200	opt.show_progress = 1;
201
202	while ((c = getopt_long(argc, argv, sopt, lopt, NULL)) != -1) {
203		switch (c) {
204		case 1:	/* A non-option argument */
205			if (!opt.vol1) {
206				opt.vol1 = argv[optind - 1];
207			} else if (!opt.vol2) {
208				opt.vol2 = argv[optind - 1];
209			} else {
210				err_printf("Too many arguments!\n");
211				usage();
212			}
213			break;
214#ifdef DEBUG
215		case 'd':
216			opt.debug++;
217			break;
218#endif
219		case 'h':
220		case '?':
221			usage();
222		case 'P':
223			opt.show_progress = 0;
224			break;
225		case 'v':
226			opt.verbose++;
227			break;
228		default:
229			err_printf("Unknown option '%s'.\n", argv[optind - 1]);
230			usage();
231			break;
232		}
233	}
234
235	if (opt.vol1 == NULL || opt.vol2 == NULL) {
236		err_printf("You must specify exactly 2 volumes.\n");
237		usage();
238	}
239
240	/* Redirect stderr to stdout, note fflush()es are essential! */
241	fflush(stdout);
242	fflush(stderr);
243	if (dup2(STDOUT_FILENO, STDERR_FILENO) == -1) {
244		perror("Failed to redirect stderr to stdout");
245		exit(1);
246	}
247	fflush(stdout);
248	fflush(stderr);
249
250#ifdef DEBUG
251	 if (!opt.debug)
252		if (!freopen("/dev/null", "w", stderr))
253			perr_exit("Failed to redirect stderr to /dev/null");
254#endif
255}
256
257static ntfs_attr_search_ctx *attr_get_search_ctx(ntfs_inode *ni)
258{
259	ntfs_attr_search_ctx *ret;
260
261	if ((ret = ntfs_attr_get_search_ctx(ni, NULL)) == NULL)
262		perr_println("ntfs_attr_get_search_ctx");
263
264	return ret;
265}
266
267static void progress_init(struct progress_bar *p, u64 start, u64 stop, int flags)
268{
269	p->start = start;
270	p->stop = stop;
271	p->unit = 100.0 / (stop - start);
272	p->resolution = 100;
273	p->flags = flags;
274}
275
276static void progress_update(struct progress_bar *p, u64 current)
277{
278	float percent;
279
280	if (!(p->flags & NTFS_PROGBAR))
281		return;
282	if (p->flags & NTFS_PROGBAR_SUPPRESS)
283		return;
284
285	/* WARNING: don't modify the texts, external tools grep for them */
286	percent = p->unit * current;
287	if (current != p->stop) {
288		if ((current - p->start) % p->resolution)
289			return;
290		printf("%6.2f percent completed\r", percent);
291	} else
292		printf("100.00 percent completed\n");
293	fflush(stdout);
294}
295
296static u64 inumber(ntfs_inode *ni)
297{
298	if (ni->nr_extents >= 0)
299		return ni->mft_no;
300
301	return ni->u.base_ni->mft_no;
302}
303
304static int inode_close(ntfs_inode *ni)
305{
306	if (ni == NULL)
307		return 0;
308
309	if (ntfs_inode_close(ni)) {
310		perr_println("ntfs_inode_close: inode %llu", inumber(ni));
311		return -1;
312	}
313	return 0;
314}
315
316static inline s64 get_nr_mft_records(ntfs_volume *vol)
317{
318	return vol->mft_na->initialized_size >> vol->mft_record_size_bits;
319}
320
321#define  NTFSCMP_OK				0
322#define  NTFSCMP_INODE_OPEN_ERROR		1
323#define  NTFSCMP_INODE_OPEN_IO_ERROR		2
324#define  NTFSCMP_INODE_OPEN_ENOENT_ERROR	3
325#define  NTFSCMP_EXTENSION_RECORD		4
326#define  NTFSCMP_INODE_CLOSE_ERROR		5
327
328static const char *ntfscmp_errs[] = {
329	"OK",
330	"INODE_OPEN_ERROR",
331	"INODE_OPEN_IO_ERROR",
332	"INODE_OPEN_ENOENT_ERROR",
333	"EXTENSION_RECORD",
334	"INODE_CLOSE_ERROR",
335	""
336};
337
338
339static const char *err2string(int err)
340{
341	return ntfscmp_errs[err];
342}
343
344static const char *pret2str(void *p)
345{
346	if (p == NULL)
347		return "FAILED";
348	return "OK";
349}
350
351static int inode_open(ntfs_volume *vol, MFT_REF mref, ntfs_inode **ni)
352{
353	*ni = ntfs_inode_open(vol, mref);
354	if (*ni == NULL) {
355		if (errno == EIO)
356			return NTFSCMP_INODE_OPEN_IO_ERROR;
357		if (errno == ENOENT)
358			return NTFSCMP_INODE_OPEN_ENOENT_ERROR;
359
360		perr_println("Reading inode %lld failed", mref);
361		return NTFSCMP_INODE_OPEN_ERROR;
362	}
363
364	if ((*ni)->mrec->base_mft_record) {
365
366		if (inode_close(*ni) != 0)
367			return NTFSCMP_INODE_CLOSE_ERROR;
368
369		return NTFSCMP_EXTENSION_RECORD;
370	}
371
372	return NTFSCMP_OK;
373}
374
375static ntfs_inode *base_inode(ntfs_attr_search_ctx *ctx)
376{
377	if (ctx->base_ntfs_ino)
378		return ctx->base_ntfs_ino;
379
380	return ctx->ntfs_ino;
381}
382
383static void print_inode(u64 inum)
384{
385	printf("Inode %llu ", inum);
386}
387
388static void print_inode_ni(ntfs_inode *ni)
389{
390	print_inode(inumber(ni));
391}
392
393static void print_attribute_type(ATTR_TYPES atype)
394{
395	printf("attribute 0x%x", atype);
396}
397
398static void print_attribute_name(char *name)
399{
400	if (name)
401		printf(":%s", name);
402}
403
404#define	GET_ATTR_NAME(a) \
405	((ntfschar *)(((u8 *)(a)) + le16_to_cpu((a)->name_offset))), \
406	((a)->name_length)
407
408static void free_name(char **name)
409{
410	if (*name) {
411		free(*name);
412		*name = NULL;
413	}
414}
415
416static char *get_attr_name(u64 mft_no,
417			   ATTR_TYPES atype,
418			   const ntfschar *uname,
419			   const int uname_len)
420{
421	char *name = NULL;
422	int name_len;
423
424	if (atype == AT_END)
425		return NULL;
426
427	name_len = ntfs_ucstombs(uname, uname_len, &name, 0);
428	if (name_len < 0) {
429		perr_print("ntfs_ucstombs");
430		print_inode(mft_no);
431		print_attribute_type(atype);
432		puts("");
433		exit(1);
434
435	} else if (name_len > 0)
436		return name;
437
438	free_name(&name);
439	return NULL;
440}
441
442static char *get_attr_name_na(ntfs_attr *na)
443{
444	return get_attr_name(inumber(na->ni), na->type, na->name, na->name_len);
445}
446
447static char *get_attr_name_ctx(ntfs_attr_search_ctx *ctx)
448{
449	u64 mft_no = inumber(ctx->ntfs_ino);
450	ATTR_TYPES atype = ctx->attr->type;
451
452	return get_attr_name(mft_no, atype, GET_ATTR_NAME(ctx->attr));
453}
454
455static void print_attribute(ATTR_TYPES atype, char *name)
456{
457	print_attribute_type(atype);
458	print_attribute_name(name);
459	printf(" ");
460}
461
462static void print_na(ntfs_attr *na)
463{
464	char *name = get_attr_name_na(na);
465	print_inode_ni(na->ni);
466	print_attribute(na->type, name);
467	free_name(&name);
468}
469
470static void print_attribute_ctx(ntfs_attr_search_ctx *ctx)
471{
472	char *name = get_attr_name_ctx(ctx);
473	print_attribute(ctx->attr->type, name);
474	free_name(&name);
475}
476
477static void print_ctx(ntfs_attr_search_ctx *ctx)
478{
479	char *name = get_attr_name_ctx(ctx);
480	print_inode_ni(base_inode(ctx));
481	print_attribute(ctx->attr->type, name);
482	free_name(&name);
483}
484
485static void print_differ(ntfs_attr *na)
486{
487	print_na(na);
488	printf("content:   DIFFER\n");
489}
490
491static int cmp_buffer(u8 *buf1, u8 *buf2, long long int size, ntfs_attr *na)
492{
493	if (memcmp(buf1, buf2, size)) {
494		print_differ(na);
495		return -1;
496	}
497	return 0;
498}
499
500struct cmp_ia {
501	INDEX_ALLOCATION *ia;
502	INDEX_ALLOCATION *tmp_ia;
503	u8 *bitmap;
504	u8 *byte;
505	s64 bm_size;
506};
507
508static int setup_cmp_ia(ntfs_attr *na, struct cmp_ia *cia)
509{
510	cia->bitmap = ntfs_attr_readall(na->ni, AT_BITMAP, na->name,
511					na->name_len, &cia->bm_size);
512	if (!cia->bitmap) {
513		perr_println("Failed to readall BITMAP");
514		return -1;
515	}
516	cia->byte = cia->bitmap;
517
518	cia->tmp_ia = cia->ia = ntfs_malloc(na->data_size);
519	if (!cia->tmp_ia)
520		goto free_bm;
521
522	if (ntfs_attr_pread(na, 0, na->data_size, cia->ia) != na->data_size) {
523		perr_println("Failed to pread INDEX_ALLOCATION");
524		goto free_ia;
525	}
526
527	return 0;
528free_ia:
529	free(cia->ia);
530free_bm:
531	free(cia->bitmap);
532	return -1;
533}
534
535static void cmp_index_allocation(ntfs_attr *na1, ntfs_attr *na2)
536{
537	struct cmp_ia cia1, cia2;
538	int bit, ret1, ret2;
539	u32 ib_size;
540
541	if (setup_cmp_ia(na1, &cia1))
542		return;
543	if (setup_cmp_ia(na2, &cia2))
544		return;
545	/*
546	 *  FIXME: ia can be the same even if the bitmap sizes are different.
547	 */
548	if (cia1.bm_size != cia1.bm_size)
549		goto out;
550
551	if (cmp_buffer(cia1.bitmap, cia2.bitmap, cia1.bm_size, na1))
552		goto out;
553
554	if (cmp_buffer((u8 *)cia1.ia, (u8 *)cia2.ia, 0x18, na1))
555		goto out;
556
557	ib_size = le32_to_cpu(cia1.ia->index.allocated_size) + 0x18;
558
559	bit = 0;
560	while ((u8 *)cia1.tmp_ia < (u8 *)cia1.ia + na1->data_size) {
561		if (*cia1.byte & (1 << bit)) {
562			ret1 = ntfs_mst_post_read_fixup((NTFS_RECORD *)
563					cia1.tmp_ia, ib_size);
564			ret2 = ntfs_mst_post_read_fixup((NTFS_RECORD *)
565					cia2.tmp_ia, ib_size);
566			if (ret1 != ret2) {
567				print_differ(na1);
568				goto out;
569			}
570
571			if (ret1 == -1)
572				continue;
573
574			if (cmp_buffer(((u8 *)cia1.tmp_ia) + 0x18,
575					((u8 *)cia2.tmp_ia) + 0x18,
576					le32_to_cpu(cia1.ia->
577					index.index_length), na1))
578				goto out;
579		}
580
581		cia1.tmp_ia = (INDEX_ALLOCATION *)((u8 *)cia1.tmp_ia + ib_size);
582		cia2.tmp_ia = (INDEX_ALLOCATION *)((u8 *)cia2.tmp_ia + ib_size);
583
584		bit++;
585		if (bit > 7) {
586			bit = 0;
587			cia1.byte++;
588		}
589	}
590out:
591	free(cia1.ia);
592	free(cia2.ia);
593	free(cia1.bitmap);
594	free(cia2.bitmap);
595	return;
596}
597
598static void cmp_attribute_data(ntfs_attr *na1, ntfs_attr *na2)
599{
600	s64 pos;
601	s64 count1 = 0, count2;
602	u8  buf1[NTFS_BUF_SIZE];
603	u8  buf2[NTFS_BUF_SIZE];
604
605	for (pos = 0; pos <= na1->data_size; pos += count1) {
606
607		count1 = ntfs_attr_pread(na1, pos, NTFS_BUF_SIZE, buf1);
608		count2 = ntfs_attr_pread(na2, pos, NTFS_BUF_SIZE, buf2);
609
610		if (count1 != count2) {
611			print_na(na1);
612			printf("abrupt length:   %lld  !=  %lld ",
613			       na1->data_size, na2->data_size);
614			printf("(count: %lld  !=  %lld)", count1, count2);
615			puts("");
616			return;
617		}
618
619		if (count1 == -1) {
620			err_printf("%s read error: ", "cmp_attribute_data");
621			print_na(na1);
622			printf("len = %lld, pos = %lld\n", na1->data_size, pos);
623			exit(1);
624		}
625
626		if (count1 == 0) {
627
628			if (pos + count1 == na1->data_size)
629				return; /* we are ready */
630
631			err_printf("%s read error before EOF: ", "cmp_attribute_data");
632			print_na(na1);
633			printf("%lld  !=  %lld\n", pos + count1, na1->data_size);
634			exit(1);
635		}
636
637		if (cmp_buffer(buf1, buf2, count1, na1))
638			return;
639	}
640
641	err_printf("%s read overrun: ", "cmp_attribute_data");
642	print_na(na1);
643	err_printf("(len = %lld, pos = %lld, count = %lld)\n",
644		  na1->data_size, pos, count1);
645	exit(1);
646}
647
648static int cmp_attribute_header(ATTR_RECORD *a1, ATTR_RECORD *a2)
649{
650	u32 header_size = offsetof(ATTR_RECORD, u.res.resident_end);
651
652	if (a1->non_resident != a2->non_resident)
653		return 1;
654
655	if (a1->non_resident) {
656		/*
657		 * FIXME: includes paddings which are not handled by ntfsinfo!
658		 */
659		header_size = le32_to_cpu(a1->length);
660	}
661
662	return memcmp(a1, a2, header_size);
663}
664
665static void cmp_attribute(ntfs_attr_search_ctx *ctx1,
666			  ntfs_attr_search_ctx *ctx2)
667{
668	ATTR_RECORD *a1 = ctx1->attr;
669	ATTR_RECORD *a2 = ctx2->attr;
670	ntfs_attr *na1, *na2;
671
672	if (cmp_attribute_header(a1, a2)) {
673		print_ctx(ctx1);
674		printf("header:    DIFFER\n");
675	}
676
677	na1 = ntfs_attr_open(base_inode(ctx1), a1->type, GET_ATTR_NAME(a1));
678	na2 = ntfs_attr_open(base_inode(ctx2), a2->type, GET_ATTR_NAME(a2));
679
680	if ((!na1 && na2) || (na1 && !na2)) {
681		print_ctx(ctx1);
682		printf("open:   %s  !=  %s\n", pret2str(na1), pret2str(na2));
683		goto close_attribs;
684	}
685
686	if (na1 == NULL)
687		goto close_attribs;
688
689	if (na1->data_size != na2->data_size) {
690		print_na(na1);
691		printf("length:   %lld  !=  %lld\n", na1->data_size, na2->data_size);
692		goto close_attribs;
693	}
694
695	if (ntfs_inode_badclus_bad(inumber(ctx1->ntfs_ino), ctx1->attr) == 1) {
696		/*
697		 * If difference exists then it's already reported at the
698		 * attribute header since the mapping pairs must differ.
699		 */
700		return;
701	}
702
703	if (na1->type == AT_INDEX_ALLOCATION)
704		cmp_index_allocation(na1, na2);
705	else
706		cmp_attribute_data(na1, na2);
707
708close_attribs:
709	ntfs_attr_close(na1);
710	ntfs_attr_close(na2);
711}
712
713static void vprint_attribute(ATTR_TYPES atype, char  *name)
714{
715	if (!opt.verbose)
716		return;
717
718	printf("0x%x", atype);
719	if (name)
720		printf(":%s", name);
721	printf(" ");
722}
723
724static void print_attributes(ntfs_inode *ni,
725			     ATTR_TYPES atype1,
726			     ATTR_TYPES atype2,
727			     char  *name1,
728			     char  *name2)
729{
730	if (!opt.verbose)
731		return;
732
733	printf("Walking inode %llu attributes: ", inumber(ni));
734	vprint_attribute(atype1, name1);
735	vprint_attribute(atype2, name2);
736	printf("\n");
737}
738
739static int new_name(ntfs_attr_search_ctx *ctx, char *prev_name)
740{
741	int ret = 0;
742	char *name = get_attr_name_ctx(ctx);
743
744	if (prev_name && name) {
745		if (strcmp(prev_name, name) != 0)
746			ret = 1;
747	} else if (prev_name || name)
748		ret = 1;
749
750	free_name(&name);
751	return ret;
752
753}
754
755static int new_attribute(ntfs_attr_search_ctx *ctx,
756			 ATTR_TYPES prev_atype,
757			 char *prev_name)
758{
759	if (!prev_atype && !prev_name)
760		return 1;
761
762	if (!ctx->attr->non_resident)
763		return 1;
764
765	if (prev_atype != ctx->attr->type)
766		return 1;
767
768	if (new_name(ctx, prev_name))
769		return 1;
770
771	if (opt.verbose) {
772		print_inode(base_inode(ctx)->mft_no);
773		print_attribute_ctx(ctx);
774		printf("record %llu lowest_vcn %lld:    SKIPPED\n",
775			ctx->ntfs_ino->mft_no, ctx->attr->u.nonres.lowest_vcn);
776	}
777
778	return 0;
779}
780
781static void set_prev(char **prev_name, ATTR_TYPES *prev_atype,
782		     char *name, ATTR_TYPES atype)
783{
784	free_name(prev_name);
785	if (name) {
786		*prev_name = strdup(name);
787		if (!*prev_name)
788			perr_exit("strdup error");
789	}
790
791	*prev_atype = atype;
792}
793
794static void set_cmp_attr(ntfs_attr_search_ctx *ctx, ATTR_TYPES *atype, char **name)
795{
796	*atype = ctx->attr->type;
797
798	free_name(name);
799	*name = get_attr_name_ctx(ctx);
800}
801
802static int next_attr(ntfs_attr_search_ctx *ctx, ATTR_TYPES *atype, char **name,
803		     int *err)
804{
805	int ret;
806
807	ret = ntfs_attrs_walk(ctx);
808	*err = errno;
809	if (ret) {
810		*atype = AT_END;
811		free_name(name);
812	} else
813		set_cmp_attr(ctx, atype, name);
814
815	return ret;
816}
817
818static int cmp_attributes(ntfs_inode *ni1, ntfs_inode *ni2)
819{
820	int ret = -1;
821	int old_ret1, ret1 = 0, ret2 = 0;
822	int errno1 = 0, errno2 = 0;
823	char  *prev_name = NULL, *name1 = NULL, *name2 = NULL;
824	ATTR_TYPES old_atype1, prev_atype = 0, atype1, atype2;
825	ntfs_attr_search_ctx *ctx1, *ctx2;
826
827	if (!(ctx1 = attr_get_search_ctx(ni1)))
828		return -1;
829	if (!(ctx2 = attr_get_search_ctx(ni2)))
830		goto out;
831
832	set_cmp_attr(ctx1, &atype1, &name1);
833	set_cmp_attr(ctx2, &atype2, &name2);
834
835	while (1) {
836
837		old_atype1 = atype1;
838		old_ret1 = ret1;
839		if (!ret1 && (le32_to_cpu(atype1) <= le32_to_cpu(atype2) ||
840				ret2))
841			ret1 = next_attr(ctx1, &atype1, &name1, &errno1);
842		if (!ret2 && (le32_to_cpu(old_atype1) >= le32_to_cpu(atype2) ||
843					old_ret1))
844			ret2 = next_attr(ctx2, &atype2, &name2, &errno2);
845
846		print_attributes(ni1, atype1, atype2, name1, name2);
847
848		if (ret1 && ret2) {
849			if (errno1 != errno2) {
850				print_inode_ni(ni1);
851				printf("attribute walk (errno):   %d  !=  %d\n",
852				       errno1, errno2);
853			}
854			break;
855		}
856
857		if (ret2 || le32_to_cpu(atype1) < le32_to_cpu(atype2)) {
858			if (new_attribute(ctx1, prev_atype, prev_name)) {
859				print_ctx(ctx1);
860				printf("presence:   EXISTS   !=   MISSING\n");
861				set_prev(&prev_name, &prev_atype, name1,
862						atype1);
863			}
864
865		} else if (ret1 || le32_to_cpu(atype1) > le32_to_cpu(atype2)) {
866			if (new_attribute(ctx2, prev_atype, prev_name)) {
867				print_ctx(ctx2);
868				printf("presence:   MISSING  !=  EXISTS \n");
869				set_prev(&prev_name, &prev_atype, name2, atype2);
870			}
871
872		} else /* atype1 == atype2 */ {
873			if (new_attribute(ctx1, prev_atype, prev_name)) {
874				cmp_attribute(ctx1, ctx2);
875				set_prev(&prev_name, &prev_atype, name1, atype1);
876			}
877		}
878	}
879
880	free_name(&prev_name);
881	ret = 0;
882	ntfs_attr_put_search_ctx(ctx2);
883out:
884	ntfs_attr_put_search_ctx(ctx1);
885	return ret;
886}
887
888static int cmp_inodes(ntfs_volume *vol1, ntfs_volume *vol2)
889{
890	u64 inode;
891	int ret1, ret2;
892	ntfs_inode *ni1, *ni2;
893	struct progress_bar progress;
894	int pb_flags = 0;	/* progress bar flags */
895	u64 nr_mft_records, nr_mft_records2;
896
897	if (opt.show_progress)
898		pb_flags |= NTFS_PROGBAR;
899
900	nr_mft_records  = get_nr_mft_records(vol1);
901	nr_mft_records2 = get_nr_mft_records(vol2);
902
903	if (nr_mft_records != nr_mft_records2) {
904
905		printf("Number of mft records:   %lld  !=  %lld\n",
906		       nr_mft_records, nr_mft_records2);
907
908		if (nr_mft_records > nr_mft_records2)
909			nr_mft_records = nr_mft_records2;
910	}
911
912	progress_init(&progress, 0, nr_mft_records - 1, pb_flags);
913	progress_update(&progress, 0);
914
915	for (inode = 0; inode < nr_mft_records; inode++) {
916
917		ret1 = inode_open(vol1, (MFT_REF)inode, &ni1);
918		ret2 = inode_open(vol2, (MFT_REF)inode, &ni2);
919
920		if (ret1 != ret2) {
921			print_inode(inode);
922			printf("open:   %s  !=  %s\n",
923			       err2string(ret1), err2string(ret2));
924			goto close_inodes;
925		}
926
927		if (ret1 != NTFSCMP_OK)
928			goto close_inodes;
929
930		if (cmp_attributes(ni1, ni2) != 0) {
931			inode_close(ni1);
932			inode_close(ni2);
933			return -1;
934		}
935close_inodes:
936		if (inode_close(ni1) != 0)
937			return -1;
938		if (inode_close(ni2) != 0)
939			return -1;
940
941		progress_update(&progress, inode);
942	}
943	return 0;
944}
945
946static ntfs_volume *mount_volume(const char *volume)
947{
948	unsigned long mntflag;
949	ntfs_volume *vol = NULL;
950
951	if (ntfs_check_if_mounted(volume, &mntflag)) {
952		perr_println("Failed to check '%s' mount state", volume);
953		printf("Probably /etc/mtab is missing. It's too risky to "
954		       "continue. You might try\nan another Linux distro.\n");
955		exit(1);
956	}
957	if (mntflag & NTFS_MF_MOUNTED) {
958		if (!(mntflag & NTFS_MF_READONLY))
959			err_exit("Device '%s' is mounted read-write. "
960				 "You must 'umount' it first.\n", volume);
961	}
962
963	vol = ntfs_mount(volume, NTFS_MNT_RDONLY);
964	if (vol == NULL) {
965
966		int err = errno;
967
968		perr_println("Opening '%s' as NTFS failed", volume);
969		if (err == EINVAL)
970			printf(invalid_ntfs_msg, volume);
971		else if (err == EIO)
972			puts(corrupt_volume_msg);
973		else if (err == EPERM)
974			puts(hibernated_volume_msg);
975		exit(1);
976	}
977
978	return vol;
979}
980
981int main(int argc, char **argv)
982{
983	ntfs_volume *vol1;
984	ntfs_volume *vol2;
985
986	printf("%s v%s (libntfs %s)\n", EXEC_NAME, VERSION,
987			ntfs_libntfs_version());
988
989	parse_options(argc, argv);
990
991	utils_set_locale();
992
993	vol1 = mount_volume(opt.vol1);
994        vol2 = mount_volume(opt.vol2);
995
996	if (cmp_inodes(vol1, vol2) != 0)
997		exit(1);
998
999	ntfs_umount(vol1, FALSE);
1000	ntfs_umount(vol2, FALSE);
1001
1002	exit(0);
1003}
1004
1005