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