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 "compat.h"
  37 #include "utils.h"
  38 #include "mst.h"
  39 #include "version.h"
  40 #include "support.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->u.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", inumber(ni));
 312                 return -1;
 313         }
 314         return 0;
 315 }
 316 
 317 static inline s64 get_nr_mft_records(ntfs_volume *vol)
 318 {
 319         return vol->mft_na->initialized_size >> vol->mft_record_size_bits;
 320 }
 321 
 322 #define  NTFSCMP_OK                             0
 323 #define  NTFSCMP_INODE_OPEN_ERROR               1
 324 #define  NTFSCMP_INODE_OPEN_IO_ERROR            2
 325 #define  NTFSCMP_INODE_OPEN_ENOENT_ERROR        3
 326 #define  NTFSCMP_EXTENSION_RECORD               4
 327 #define  NTFSCMP_INODE_CLOSE_ERROR              5
 328 
 329 static const char *ntfscmp_errs[] = {
 330         "OK",
 331         "INODE_OPEN_ERROR",
 332         "INODE_OPEN_IO_ERROR",
 333         "INODE_OPEN_ENOENT_ERROR",
 334         "EXTENSION_RECORD",
 335         "INODE_CLOSE_ERROR",
 336         ""
 337 };
 338 
 339 
 340 static const char *err2string(int err)
 341 {
 342         return ntfscmp_errs[err];
 343 }
 344 
 345 static const char *pret2str(void *p)
 346 {
 347         if (p == NULL)
 348                 return "FAILED";
 349         return "OK";
 350 }
 351 
 352 static int inode_open(ntfs_volume *vol, MFT_REF mref, ntfs_inode **ni)
 353 {
 354         *ni = ntfs_inode_open(vol, mref);
 355         if (*ni == NULL) {
 356                 if (errno == EIO)
 357                         return NTFSCMP_INODE_OPEN_IO_ERROR;
 358                 if (errno == ENOENT)
 359                         return NTFSCMP_INODE_OPEN_ENOENT_ERROR;
 360 
 361                 perr_println("Reading inode %lld failed", mref);
 362                 return NTFSCMP_INODE_OPEN_ERROR;
 363         }
 364 
 365         if ((*ni)->mrec->base_mft_record) {
 366 
 367                 if (inode_close(*ni) != 0)
 368                         return NTFSCMP_INODE_CLOSE_ERROR;
 369 
 370                 return NTFSCMP_EXTENSION_RECORD;
 371         }
 372 
 373         return NTFSCMP_OK;
 374 }
 375 
 376 static ntfs_inode *base_inode(ntfs_attr_search_ctx *ctx)
 377 {
 378         if (ctx->base_ntfs_ino)
 379                 return ctx->base_ntfs_ino;
 380 
 381         return ctx->ntfs_ino;
 382 }
 383 
 384 static void print_inode(u64 inum)
 385 {
 386         printf("Inode %llu ", inum);
 387 }
 388 
 389 static void print_inode_ni(ntfs_inode *ni)
 390 {
 391         print_inode(inumber(ni));
 392 }
 393 
 394 static void print_attribute_type(ATTR_TYPES atype)
 395 {
 396         printf("attribute 0x%x", atype);
 397 }
 398 
 399 static void print_attribute_name(char *name)
 400 {
 401         if (name)
 402                 printf(":%s", name);
 403 }
 404 
 405 #define GET_ATTR_NAME(a) \
 406         ((ntfschar *)(((u8 *)(a)) + le16_to_cpu((a)->name_offset))), \
 407         ((a)->name_length)
 408 
 409 static void free_name(char **name)
 410 {
 411         if (*name) {
 412                 free(*name);
 413                 *name = NULL;
 414         }
 415 }
 416 
 417 static char *get_attr_name(u64 mft_no,
 418                            ATTR_TYPES atype,
 419                            const ntfschar *uname,
 420                            const int uname_len)
 421 {
 422         char *name = NULL;
 423         int name_len;
 424 
 425         if (atype == AT_END)
 426                 return NULL;
 427 
 428         name_len = ntfs_ucstombs(uname, uname_len, &name, 0);
 429         if (name_len < 0) {
 430                 perr_print("ntfs_ucstombs");
 431                 print_inode(mft_no);
 432                 print_attribute_type(atype);
 433                 puts("");
 434                 exit(1);
 435 
 436         } else if (name_len > 0)
 437                 return name;
 438 
 439         free_name(&name);
 440         return NULL;
 441 }
 442 
 443 static char *get_attr_name_na(ntfs_attr *na)
 444 {
 445         return get_attr_name(inumber(na->ni), na->type, na->name, na->name_len);
 446 }
 447 
 448 static char *get_attr_name_ctx(ntfs_attr_search_ctx *ctx)
 449 {
 450         u64 mft_no = inumber(ctx->ntfs_ino);
 451         ATTR_TYPES atype = ctx->attr->type;
 452 
 453         return get_attr_name(mft_no, atype, GET_ATTR_NAME(ctx->attr));
 454 }
 455 
 456 static void print_attribute(ATTR_TYPES atype, char *name)
 457 {
 458         print_attribute_type(atype);
 459         print_attribute_name(name);
 460         printf(" ");
 461 }
 462 
 463 static void print_na(ntfs_attr *na)
 464 {
 465         char *name = get_attr_name_na(na);
 466         print_inode_ni(na->ni);
 467         print_attribute(na->type, name);
 468         free_name(&name);
 469 }
 470 
 471 static void print_attribute_ctx(ntfs_attr_search_ctx *ctx)
 472 {
 473         char *name = get_attr_name_ctx(ctx);
 474         print_attribute(ctx->attr->type, name);
 475         free_name(&name);
 476 }
 477 
 478 static void print_ctx(ntfs_attr_search_ctx *ctx)
 479 {
 480         char *name = get_attr_name_ctx(ctx);
 481         print_inode_ni(base_inode(ctx));
 482         print_attribute(ctx->attr->type, name);
 483         free_name(&name);
 484 }
 485 
 486 static void print_differ(ntfs_attr *na)
 487 {
 488         print_na(na);
 489         printf("content:   DIFFER\n");
 490 }
 491 
 492 static int cmp_buffer(u8 *buf1, u8 *buf2, long long int size, ntfs_attr *na)
 493 {
 494         if (memcmp(buf1, buf2, size)) {
 495                 print_differ(na);
 496                 return -1;
 497         }
 498         return 0;
 499 }
 500 
 501 struct cmp_ia {
 502         INDEX_ALLOCATION *ia;
 503         INDEX_ALLOCATION *tmp_ia;
 504         u8 *bitmap;
 505         u8 *byte;
 506         s64 bm_size;
 507 };
 508 
 509 static int setup_cmp_ia(ntfs_attr *na, struct cmp_ia *cia)
 510 {
 511         cia->bitmap = ntfs_attr_readall(na->ni, AT_BITMAP, na->name,
 512                                         na->name_len, &cia->bm_size);
 513         if (!cia->bitmap) {
 514                 perr_println("Failed to readall BITMAP");
 515                 return -1;
 516         }
 517         cia->byte = cia->bitmap;
 518 
 519         cia->tmp_ia = cia->ia = ntfs_malloc(na->data_size);
 520         if (!cia->tmp_ia)
 521                 goto free_bm;
 522 
 523         if (ntfs_attr_pread(na, 0, na->data_size, cia->ia) != na->data_size) {
 524                 perr_println("Failed to pread INDEX_ALLOCATION");
 525                 goto free_ia;
 526         }
 527 
 528         return 0;
 529 free_ia:
 530         free(cia->ia);
 531 free_bm:
 532         free(cia->bitmap);
 533         return -1;
 534 }
 535 
 536 static void cmp_index_allocation(ntfs_attr *na1, ntfs_attr *na2)
 537 {
 538         struct cmp_ia cia1, cia2;
 539         int bit, ret1, ret2;
 540         u32 ib_size;
 541 
 542         if (setup_cmp_ia(na1, &cia1))
 543                 return;
 544         if (setup_cmp_ia(na2, &cia2))
 545                 return;
 546         /*
 547          *  FIXME: ia can be the same even if the bitmap sizes are different.
 548          */
 549         if (cia1.bm_size != cia1.bm_size)
 550                 goto out;
 551 
 552         if (cmp_buffer(cia1.bitmap, cia2.bitmap, cia1.bm_size, na1))
 553                 goto out;
 554 
 555         if (cmp_buffer((u8 *)cia1.ia, (u8 *)cia2.ia, 0x18, na1))
 556                 goto out;
 557 
 558         ib_size = le32_to_cpu(cia1.ia->index.allocated_size) + 0x18;
 559 
 560         bit = 0;
 561         while ((u8 *)cia1.tmp_ia < (u8 *)cia1.ia + na1->data_size) {
 562                 if (*cia1.byte & (1 << bit)) {
 563                         ret1 = ntfs_mst_post_read_fixup((NTFS_RECORD *)
 564                                         cia1.tmp_ia, ib_size);
 565                         ret2 = ntfs_mst_post_read_fixup((NTFS_RECORD *)
 566                                         cia2.tmp_ia, ib_size);
 567                         if (ret1 != ret2) {
 568                                 print_differ(na1);
 569                                 goto out;
 570                         }
 571 
 572                         if (ret1 == -1)
 573                                 continue;
 574 
 575                         if (cmp_buffer(((u8 *)cia1.tmp_ia) + 0x18,
 576                                         ((u8 *)cia2.tmp_ia) + 0x18,
 577                                         le32_to_cpu(cia1.ia->
 578                                         index.index_length), na1))
 579                                 goto out;
 580                 }
 581 
 582                 cia1.tmp_ia = (INDEX_ALLOCATION *)((u8 *)cia1.tmp_ia + ib_size);
 583                 cia2.tmp_ia = (INDEX_ALLOCATION *)((u8 *)cia2.tmp_ia + ib_size);
 584 
 585                 bit++;
 586                 if (bit > 7) {
 587                         bit = 0;
 588                         cia1.byte++;
 589                 }
 590         }
 591 out:
 592         free(cia1.ia);
 593         free(cia2.ia);
 594         free(cia1.bitmap);
 595         free(cia2.bitmap);
 596         return;
 597 }
 598 
 599 static void cmp_attribute_data(ntfs_attr *na1, ntfs_attr *na2)
 600 {
 601         s64 pos;
 602         s64 count1 = 0, count2;
 603         u8  buf1[NTFS_BUF_SIZE];
 604         u8  buf2[NTFS_BUF_SIZE];
 605 
 606         for (pos = 0; pos <= na1->data_size; pos += count1) {
 607 
 608                 count1 = ntfs_attr_pread(na1, pos, NTFS_BUF_SIZE, buf1);
 609                 count2 = ntfs_attr_pread(na2, pos, NTFS_BUF_SIZE, buf2);
 610 
 611                 if (count1 != count2) {
 612                         print_na(na1);
 613                         printf("abrupt length:   %lld  !=  %lld ",
 614                                na1->data_size, na2->data_size);
 615                         printf("(count: %lld  !=  %lld)", count1, count2);
 616                         puts("");
 617                         return;
 618                 }
 619 
 620                 if (count1 == -1) {
 621                         err_printf("%s read error: ", "cmp_attribute_data");
 622                         print_na(na1);
 623                         printf("len = %lld, pos = %lld\n", na1->data_size, pos);
 624                         exit(1);
 625                 }
 626 
 627                 if (count1 == 0) {
 628 
 629                         if (pos + count1 == na1->data_size)
 630                                 return; /* we are ready */
 631 
 632                         err_printf("%s read error before EOF: ", "cmp_attribute_data");
 633                         print_na(na1);
 634                         printf("%lld  !=  %lld\n", pos + count1, na1->data_size);
 635                         exit(1);
 636                 }
 637 
 638                 if (cmp_buffer(buf1, buf2, count1, na1))
 639                         return;
 640         }
 641 
 642         err_printf("%s read overrun: ", "cmp_attribute_data");
 643         print_na(na1);
 644         err_printf("(len = %lld, pos = %lld, count = %lld)\n",
 645                   na1->data_size, pos, count1);
 646         exit(1);
 647 }
 648 
 649 static int cmp_attribute_header(ATTR_RECORD *a1, ATTR_RECORD *a2)
 650 {
 651         u32 header_size = offsetof(ATTR_RECORD, u.res.resident_end);
 652 
 653         if (a1->non_resident != a2->non_resident)
 654                 return 1;
 655 
 656         if (a1->non_resident) {
 657                 /*
 658                  * FIXME: includes paddings which are not handled by ntfsinfo!
 659                  */
 660                 header_size = le32_to_cpu(a1->length);
 661         }
 662 
 663         return memcmp(a1, a2, header_size);
 664 }
 665 
 666 static void cmp_attribute(ntfs_attr_search_ctx *ctx1,
 667                           ntfs_attr_search_ctx *ctx2)
 668 {
 669         ATTR_RECORD *a1 = ctx1->attr;
 670         ATTR_RECORD *a2 = ctx2->attr;
 671         ntfs_attr *na1, *na2;
 672 
 673         if (cmp_attribute_header(a1, a2)) {
 674                 print_ctx(ctx1);
 675                 printf("header:    DIFFER\n");
 676         }
 677 
 678         na1 = ntfs_attr_open(base_inode(ctx1), a1->type, GET_ATTR_NAME(a1));
 679         na2 = ntfs_attr_open(base_inode(ctx2), a2->type, GET_ATTR_NAME(a2));
 680 
 681         if ((!na1 && na2) || (na1 && !na2)) {
 682                 print_ctx(ctx1);
 683                 printf("open:   %s  !=  %s\n", pret2str(na1), pret2str(na2));
 684                 goto close_attribs;
 685         }
 686 
 687         if (na1 == NULL)
 688                 goto close_attribs;
 689 
 690         if (na1->data_size != na2->data_size) {
 691                 print_na(na1);
 692                 printf("length:   %lld  !=  %lld\n", na1->data_size, na2->data_size);
 693                 goto close_attribs;
 694         }
 695 
 696         if (ntfs_inode_badclus_bad(inumber(ctx1->ntfs_ino), ctx1->attr) == 1) {
 697                 /*
 698                  * If difference exists then it's already reported at the
 699                  * attribute header since the mapping pairs must differ.
 700                  */
 701                 return;
 702         }
 703 
 704         if (na1->type == AT_INDEX_ALLOCATION)
 705                 cmp_index_allocation(na1, na2);
 706         else
 707                 cmp_attribute_data(na1, na2);
 708 
 709 close_attribs:
 710         ntfs_attr_close(na1);
 711         ntfs_attr_close(na2);
 712 }
 713 
 714 static void vprint_attribute(ATTR_TYPES atype, char  *name)
 715 {
 716         if (!opt.verbose)
 717                 return;
 718 
 719         printf("0x%x", atype);
 720         if (name)
 721                 printf(":%s", name);
 722         printf(" ");
 723 }
 724 
 725 static void print_attributes(ntfs_inode *ni,
 726                              ATTR_TYPES atype1,
 727                              ATTR_TYPES atype2,
 728                              char  *name1,
 729                              char  *name2)
 730 {
 731         if (!opt.verbose)
 732                 return;
 733 
 734         printf("Walking inode %llu attributes: ", inumber(ni));
 735         vprint_attribute(atype1, name1);
 736         vprint_attribute(atype2, name2);
 737         printf("\n");
 738 }
 739 
 740 static int new_name(ntfs_attr_search_ctx *ctx, char *prev_name)
 741 {
 742         int ret = 0;
 743         char *name = get_attr_name_ctx(ctx);
 744 
 745         if (prev_name && name) {
 746                 if (strcmp(prev_name, name) != 0)
 747                         ret = 1;
 748         } else if (prev_name || name)
 749                 ret = 1;
 750 
 751         free_name(&name);
 752         return ret;
 753 
 754 }
 755 
 756 static int new_attribute(ntfs_attr_search_ctx *ctx,
 757                          ATTR_TYPES prev_atype,
 758                          char *prev_name)
 759 {
 760         if (!prev_atype && !prev_name)
 761                 return 1;
 762 
 763         if (!ctx->attr->non_resident)
 764                 return 1;
 765 
 766         if (prev_atype != ctx->attr->type)
 767                 return 1;
 768 
 769         if (new_name(ctx, prev_name))
 770                 return 1;
 771 
 772         if (opt.verbose) {
 773                 print_inode(base_inode(ctx)->mft_no);
 774                 print_attribute_ctx(ctx);
 775                 printf("record %llu lowest_vcn %lld:    SKIPPED\n",
 776                         ctx->ntfs_ino->mft_no, ctx->attr->u.nonres.lowest_vcn);
 777         }
 778 
 779         return 0;
 780 }
 781 
 782 static void set_prev(char **prev_name, ATTR_TYPES *prev_atype,
 783                      char *name, ATTR_TYPES atype)
 784 {
 785         free_name(prev_name);
 786         if (name) {
 787                 *prev_name = strdup(name);
 788                 if (!*prev_name)
 789                         perr_exit("strdup error");
 790         }
 791 
 792         *prev_atype = atype;
 793 }
 794 
 795 static void set_cmp_attr(ntfs_attr_search_ctx *ctx, ATTR_TYPES *atype, char **name)
 796 {
 797         *atype = ctx->attr->type;
 798 
 799         free_name(name);
 800         *name = get_attr_name_ctx(ctx);
 801 }
 802 
 803 static int next_attr(ntfs_attr_search_ctx *ctx, ATTR_TYPES *atype, char **name,
 804                      int *err)
 805 {
 806         int ret;
 807 
 808         ret = ntfs_attrs_walk(ctx);
 809         *err = errno;
 810         if (ret) {
 811                 *atype = AT_END;
 812                 free_name(name);
 813         } else
 814                 set_cmp_attr(ctx, atype, name);
 815 
 816         return ret;
 817 }
 818 
 819 static int cmp_attributes(ntfs_inode *ni1, ntfs_inode *ni2)
 820 {
 821         int ret = -1;
 822         int old_ret1, ret1 = 0, ret2 = 0;
 823         int errno1 = 0, errno2 = 0;
 824         char  *prev_name = NULL, *name1 = NULL, *name2 = NULL;
 825         ATTR_TYPES old_atype1, prev_atype = 0, atype1, atype2;
 826         ntfs_attr_search_ctx *ctx1, *ctx2;
 827 
 828         if (!(ctx1 = attr_get_search_ctx(ni1)))
 829                 return -1;
 830         if (!(ctx2 = attr_get_search_ctx(ni2)))
 831                 goto out;
 832 
 833         set_cmp_attr(ctx1, &atype1, &name1);
 834         set_cmp_attr(ctx2, &atype2, &name2);
 835 
 836         while (1) {
 837 
 838                 old_atype1 = atype1;
 839                 old_ret1 = ret1;
 840                 if (!ret1 && (le32_to_cpu(atype1) <= le32_to_cpu(atype2) ||
 841                                 ret2))
 842                         ret1 = next_attr(ctx1, &atype1, &name1, &errno1);
 843                 if (!ret2 && (le32_to_cpu(old_atype1) >= le32_to_cpu(atype2) ||
 844                                         old_ret1))
 845                         ret2 = next_attr(ctx2, &atype2, &name2, &errno2);
 846 
 847                 print_attributes(ni1, atype1, atype2, name1, name2);
 848 
 849                 if (ret1 && ret2) {
 850                         if (errno1 != errno2) {
 851                                 print_inode_ni(ni1);
 852                                 printf("attribute walk (errno):   %d  !=  %d\n",
 853                                        errno1, errno2);
 854                         }
 855                         break;
 856                 }
 857 
 858                 if (ret2 || le32_to_cpu(atype1) < le32_to_cpu(atype2)) {
 859                         if (new_attribute(ctx1, prev_atype, prev_name)) {
 860                                 print_ctx(ctx1);
 861                                 printf("presence:   EXISTS   !=   MISSING\n");
 862                                 set_prev(&prev_name, &prev_atype, name1,
 863                                                 atype1);
 864                         }
 865 
 866                 } else if (ret1 || le32_to_cpu(atype1) > le32_to_cpu(atype2)) {
 867                         if (new_attribute(ctx2, prev_atype, prev_name)) {
 868                                 print_ctx(ctx2);
 869                                 printf("presence:   MISSING  !=  EXISTS \n");
 870                                 set_prev(&prev_name, &prev_atype, name2, atype2);
 871                         }
 872 
 873                 } else /* atype1 == atype2 */ {
 874                         if (new_attribute(ctx1, prev_atype, prev_name)) {
 875                                 cmp_attribute(ctx1, ctx2);
 876                                 set_prev(&prev_name, &prev_atype, name1, atype1);
 877                         }
 878                 }
 879         }
 880 
 881         free_name(&prev_name);
 882         ret = 0;
 883         ntfs_attr_put_search_ctx(ctx2);
 884 out:
 885         ntfs_attr_put_search_ctx(ctx1);
 886         return ret;
 887 }
 888 
 889 static int cmp_inodes(ntfs_volume *vol1, ntfs_volume *vol2)
 890 {
 891         u64 inode;
 892         int ret1, ret2;
 893         ntfs_inode *ni1, *ni2;
 894         struct progress_bar progress;
 895         int pb_flags = 0;       /* progress bar flags */
 896         u64 nr_mft_records, nr_mft_records2;
 897 
 898         if (opt.show_progress)
 899                 pb_flags |= NTFS_PROGBAR;
 900 
 901         nr_mft_records  = get_nr_mft_records(vol1);
 902         nr_mft_records2 = get_nr_mft_records(vol2);
 903 
 904         if (nr_mft_records != nr_mft_records2) {
 905 
 906                 printf("Number of mft records:   %lld  !=  %lld\n",
 907                        nr_mft_records, nr_mft_records2);
 908 
 909                 if (nr_mft_records > nr_mft_records2)
 910                         nr_mft_records = nr_mft_records2;
 911         }
 912 
 913         progress_init(&progress, 0, nr_mft_records - 1, pb_flags);
 914         progress_update(&progress, 0);
 915 
 916         for (inode = 0; inode < nr_mft_records; inode++) {
 917 
 918                 ret1 = inode_open(vol1, (MFT_REF)inode, &ni1);
 919                 ret2 = inode_open(vol2, (MFT_REF)inode, &ni2);
 920 
 921                 if (ret1 != ret2) {
 922                         print_inode(inode);
 923                         printf("open:   %s  !=  %s\n",
 924                                err2string(ret1), err2string(ret2));
 925                         goto close_inodes;
 926                 }
 927 
 928                 if (ret1 != NTFSCMP_OK)
 929                         goto close_inodes;
 930 
 931                 if (cmp_attributes(ni1, ni2) != 0) {
 932                         inode_close(ni1);
 933                         inode_close(ni2);
 934                         return -1;
 935                 }
 936 close_inodes:
 937                 if (inode_close(ni1) != 0)
 938                         return -1;
 939                 if (inode_close(ni2) != 0)
 940                         return -1;
 941 
 942                 progress_update(&progress, inode);
 943         }
 944         return 0;
 945 }
 946 
 947 static ntfs_volume *mount_volume(const char *volume)
 948 {
 949         unsigned long mntflag;
 950         ntfs_volume *vol = NULL;
 951 
 952         if (ntfs_check_if_mounted(volume, &mntflag)) {
 953                 perr_println("Failed to check '%s' mount state", volume);
 954                 printf("Probably /etc/mtab is missing. It's too risky to "
 955                        "continue. You might try\nan another Linux distro.\n");
 956                 exit(1);
 957         }
 958         if (mntflag & NTFS_MF_MOUNTED) {
 959                 if (!(mntflag & NTFS_MF_READONLY))
 960                         err_exit("Device '%s' is mounted read-write. "
 961                                  "You must 'umount' it first.\n", volume);
 962         }
 963 
 964         vol = ntfs_mount(volume, NTFS_MNT_RDONLY);
 965         if (vol == NULL) {
 966 
 967                 int err = errno;
 968 
 969                 perr_println("Opening '%s' as NTFS failed", volume);
 970                 if (err == EINVAL)
 971                         printf(invalid_ntfs_msg, volume);
 972                 else if (err == EIO)
 973                         puts(corrupt_volume_msg);
 974                 else if (err == EPERM)
 975                         puts(hibernated_volume_msg);
 976                 exit(1);
 977         }
 978 
 979         return vol;
 980 }
 981 
 982 int main(int argc, char **argv)
 983 {
 984         ntfs_volume *vol1;
 985         ntfs_volume *vol2;
 986 
 987         printf("%s v%s (libntfs %s)\n", EXEC_NAME, VERSION,
 988                         ntfs_libntfs_version());
 989 
 990         parse_options(argc, argv);
 991 
 992         utils_set_locale();
 993 
 994         vol1 = mount_volume(opt.vol1);
 995         vol2 = mount_volume(opt.vol2);
 996 
 997         if (cmp_inodes(vol1, vol2) != 0)
 998                 exit(1);
 999 
1000         ntfs_umount(vol1, FALSE);
1001         ntfs_umount(vol2, FALSE);
1002 
1003         exit(0);
1004 }
1005