-
+ 908272F9A1C8A15CAC809A9C6DBF96F7CD60B40BC3EBAD0F1DF368F6D5DFA10DA38E7E6F8CD09DC994E2291B95334C7768D885633536302E3C47181452A2B839
vtools/src/diff.c
(0 . 0)(1 . 454)
3431 #define GDIFF_MAIN
3432
3433 #include "diff.h"
3434 #include <dirname.h>
3435 #include <error.h>
3436 #include <filenamecat.h>
3437 #include <progname.h>
3438 #include <xalloc.h>
3439 #include <getopt.h>
3440 #include <fcntl.h>
3441 #include <stdlib.h>
3442 #include <inttypes.h>
3443 #include <filetype.h>
3444
3445 #define STREQ(a, b) (strcmp (a, b) == 0)
3446 #define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9)
3447
3448 static int compare_files(struct comparison const *, char const *, char const *);
3449
3450 static void try_help(char const *, char const *);
3451
3452 static void check_stdout(void);
3453
3454 static char const shortopts[] = "0123456789aU:dHL:qurN";
3455
3456 /* Return a string containing the command options with which diff was
3457 invoked. Spaces appear between what were separate |argv|-elements.
3458 There is a space at the beginning but none at the end. If there
3459 were no options, the result is an empty string.
3460
3461 Arguments: |optionvec|, a vector containing separate
3462 |argv|-elements, and |count|, the length of that vector.
3463
3464 todo phf: Implicitly add the -uNr since that's the defaults, and
3465 this combination of flags is what the original vdiff has.*/
3466
3467 static char *
3468 option_list(char **optionvec, int count) {
3469 char prefix[] = " -uNr";
3470 int i;
3471 size_t size = 1 + sizeof prefix;
3472 char *result;
3473 char *p;
3474
3475 for (i = 0; i < count; i++)
3476 size += strlen(optionvec[i]);
3477
3478 p = result = xmalloc(size);
3479 p = stpncpy(p, prefix, sizeof prefix - 1);
3480
3481 for (i = 0; i < count; i++) {
3482 *p++ = ' ';
3483 p = stpncpy(p, optionvec[i], strlen(optionvec[i])-1);
3484 }
3485
3486 *p = '\0';
3487 return result;
3488 }
3489
3490 int
3491 main(int argc, char **argv) {
3492 int exit_status = EXIT_SUCCESS;
3493 int c;
3494 int prev = -1;
3495 lin ocontext = -1;
3496 bool explicit_context = false;
3497 char const *from_file = NULL;
3498 char const *to_file = NULL;
3499 uintmax_t numval;
3500 char *numend;
3501 set_program_name(argv[0]);
3502
3503 /* Decode the options. */
3504
3505 while ((c = getopt(argc, argv, shortopts)) != -1) {
3506 switch (c) {
3507 case 0:
3508 break;
3509
3510 case '0':
3511 case '1':
3512 case '2':
3513 case '3':
3514 case '4':
3515 case '5':
3516 case '6':
3517 case '7':
3518 case '8':
3519 case '9':
3520 ocontext = (!ISDIGIT (prev)
3521 ? c - '0'
3522 : (ocontext - (c - '0' <= CONTEXT_MAX % 10)
3523 < CONTEXT_MAX / 10)
3524 ? 10 * ocontext + (c - '0')
3525 : CONTEXT_MAX);
3526 break;
3527
3528 case 'a':
3529 text = true;
3530 break;
3531
3532 case 'U': {
3533 if (optarg) {
3534 numval = strtoumax(optarg, &numend, 10);
3535 if (*numend)
3536 try_help("invalid context length '%s'", optarg);
3537 if (CONTEXT_MAX < numval)
3538 numval = CONTEXT_MAX;
3539 } else
3540 numval = 3;
3541
3542 if (context < numval)
3543 context = numval;
3544 explicit_context = true;
3545 }
3546 break;
3547
3548 case 'd':
3549 minimal = true;
3550 break;
3551
3552 case 'H':
3553 speed_large_files = true;
3554 break;
3555
3556 case 'L':
3557 if (!file_label[0])
3558 file_label[0] = optarg;
3559 else if (!file_label[1])
3560 file_label[1] = optarg;
3561 else
3562 fatal("too many file label options");
3563 break;
3564
3565 case 'q':
3566 brief = true;
3567 break;
3568
3569 case 'u':
3570 case 'r':
3571 case 'N':
3572 /* compat */
3573 break;
3574
3575 default:
3576 try_help(NULL, NULL);
3577 }
3578 prev = c;
3579 }
3580
3581 if (context < 3)
3582 context = 3;
3583 suppress_blank_empty = false;
3584
3585 if (0 <= ocontext
3586 && (context < ocontext
3587 || (ocontext < context && !explicit_context)))
3588 context = ocontext;
3589
3590 /* Make the horizon at least as large as the context, so that
3591 |shift_boundaries| has more freedom to shift the first and last
3592 hunks. */
3593 if (horizon_lines < context)
3594 horizon_lines = context;
3595
3596 files_can_be_treated_as_binary = false;
3597
3598 switch_string = option_list(argv + 1, optind - 1);
3599
3600 if (from_file) {
3601 if (to_file)
3602 fatal("--from-file and --to-file both specified");
3603 else
3604 for (; optind < argc; optind++) {
3605 int status = compare_files(NULL, from_file, argv[optind]);
3606 if (exit_status < status)
3607 exit_status = status;
3608 }
3609 } else {
3610 if (to_file)
3611 for (; optind < argc; optind++) {
3612 int status = compare_files(NULL, argv[optind], to_file);
3613 if (exit_status < status)
3614 exit_status = status;
3615 }
3616 else {
3617 if (argc - optind != 2) {
3618 if (argc - optind < 2)
3619 try_help("missing operand after '%s'", argv[argc - 1]);
3620 else
3621 try_help("extra operand '%s'", argv[optind + 2]);
3622 }
3623
3624 exit_status = compare_files(NULL, argv[optind], argv[optind + 1]);
3625 }
3626 }
3627
3628 check_stdout();
3629 exit(exit_status);
3630 return exit_status;
3631 }
3632
3633 static void
3634 try_help(char const *reason_msgid, char const *operand) {
3635 if (reason_msgid)
3636 error(0, 0, reason_msgid, operand);
3637 exit(EXIT_TROUBLE);
3638 }
3639
3640 static void
3641 check_stdout(void) {
3642 if (ferror(stdout))
3643 fatal("write failed");
3644 else if (fclose(stdout) != 0)
3645 pfatal_with_name("standard output");
3646 }
3647
3648 /* Compare two files (or dirs) with parent comparison |parent| and
3649 names |name0| and |name1|. (If |parent| is null, then the first
3650 name is just |name0|, etc.) This is self-contained; it opens the
3651 files and closes them.
3652
3653 Value is |EXIT_SUCCESS| if files are the same, |EXIT_FAILURE| if
3654 different, |EXIT_TROUBLE| if there is a problem opening them. */
3655
3656 static int
3657 compare_files(struct comparison const *parent,
3658 char const *name0,
3659 char const *name1) {
3660 struct comparison cmp;
3661 #define DIR_P(f) (S_ISDIR (cmp.file[f].stat.st_mode) != 0)
3662 register int f;
3663 int status = EXIT_SUCCESS;
3664 bool same_files;
3665 char *free0;
3666 char *free1;
3667
3668 memset (cmp.file, 0, sizeof cmp.file);
3669 cmp.parent = parent;
3670
3671 /* |cmp.file[f].desc| markers */
3672 #define NONEXISTENT (-1) /* nonexistent file */
3673 #define UNOPENED (-2) /* unopened file (e.g. directory) */
3674 #define ERRNO_ENCODE(errno) (-3 - (errno)) /* encoded |errno| value */
3675 #define ERRNO_DECODE(desc) (-3 - (desc)) /* inverse of |ERRNO_ENCODE| */
3676
3677 cmp.file[0].desc = name0 ? UNOPENED : NONEXISTENT;
3678 cmp.file[1].desc = name1 ? UNOPENED : NONEXISTENT;
3679
3680 /* Now record the full name of each file, including nonexistent ones. */
3681
3682 if (!name0)
3683 name0 = name1;
3684 if (!name1)
3685 name1 = name0;
3686
3687 if (!parent) {
3688 free0 = NULL;
3689 free1 = NULL;
3690 cmp.file[0].name = name0;
3691 cmp.file[1].name = name1;
3692 } else {
3693 cmp.file[0].name = free0
3694 = file_name_concat(parent->file[0].name, name0, NULL);
3695 cmp.file[1].name = free1
3696 = file_name_concat(parent->file[1].name, name1, NULL);
3697 }
3698
3699 /* Stat the files. */
3700
3701 for (f = 0; f < 2; f++) {
3702 if (cmp.file[f].desc != NONEXISTENT) {
3703 if (f && file_name_cmp(cmp.file[f].name, cmp.file[0].name) == 0) {
3704 cmp.file[f].desc = cmp.file[0].desc;
3705 cmp.file[f].stat = cmp.file[0].stat;
3706 } else if (STREQ (cmp.file[f].name, "-")) {
3707 cmp.file[f].desc = STDIN_FILENO;
3708 if (fstat(STDIN_FILENO, &cmp.file[f].stat) != 0)
3709 cmp.file[f].desc = ERRNO_ENCODE (errno);
3710 else {
3711 if (S_ISREG (cmp.file[f].stat.st_mode)) {
3712 off_t pos = lseek(STDIN_FILENO, 0, SEEK_CUR);
3713 if (pos < 0)
3714 cmp.file[f].desc = ERRNO_ENCODE (errno);
3715 else
3716 cmp.file[f].stat.st_size =
3717 MAX (0, cmp.file[f].stat.st_size - pos);
3718 }
3719 }
3720 } else if (stat(cmp.file[f].name, &cmp.file[f].stat) != 0)
3721 cmp.file[f].desc = ERRNO_ENCODE (errno);
3722 }
3723 }
3724
3725 /* Mark files as nonexistent as needed for |-N| and |-P|, if they
3726 are inaccessible empty regular files (the kind of files that
3727 |patch| creates to indicate nonexistent backups), or if they
3728 are top-level files that do not exist but their counterparts do
3729 exist. */
3730 for (f = 0; f < 2; f++)
3731 if (cmp.file[f].desc == UNOPENED
3732 ? (S_ISREG (cmp.file[f].stat.st_mode)
3733 && !(cmp.file[f].stat.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))
3734 && cmp.file[f].stat.st_size == 0)
3735 : ((cmp.file[f].desc == ERRNO_ENCODE (ENOENT)
3736 || cmp.file[f].desc == ERRNO_ENCODE (EBADF))
3737 && !parent
3738 && (cmp.file[1 - f].desc == UNOPENED
3739 || cmp.file[1 - f].desc == STDIN_FILENO)))
3740 cmp.file[f].desc = NONEXISTENT;
3741
3742 for (f = 0; f < 2; f++)
3743 if (cmp.file[f].desc == NONEXISTENT) {
3744 memset (&cmp.file[f].stat, 0, sizeof cmp.file[f].stat);
3745 cmp.file[f].stat.st_mode = cmp.file[1 - f].stat.st_mode;
3746 }
3747
3748 for (f = 0; f < 2; f++) {
3749 int e = ERRNO_DECODE (cmp.file[f].desc);
3750 if (0 <= e) {
3751 errno = e;
3752 perror_with_name(cmp.file[f].name);
3753 status = EXIT_TROUBLE;
3754 }
3755 }
3756
3757 if (status == EXIT_SUCCESS && !parent && DIR_P (0) != DIR_P (1)) {
3758 /* If one is a directory, and it was specified in the command
3759 line, use the file in that dir with the other file's
3760 basename. */
3761
3762 int fnm_arg = DIR_P (0);
3763 int dir_arg = 1 - fnm_arg;
3764 char const *fnm = cmp.file[fnm_arg].name;
3765 char const *dir = cmp.file[dir_arg].name;
3766 char const *filename = cmp.file[dir_arg].name = free0
3767 = find_dir_file_pathname(dir, last_component(fnm));
3768
3769 if (STREQ (fnm, "-"))
3770 fatal("cannot compare '-' to a directory");
3771
3772 if (stat(filename, &cmp.file[dir_arg].stat)
3773 != 0) {
3774 perror_with_name(filename);
3775 status = EXIT_TROUBLE;
3776 }
3777 }
3778
3779 if (status != EXIT_SUCCESS) {
3780 /* One of the files should exist but does not. */
3781 } else if (cmp.file[0].desc == NONEXISTENT
3782 && cmp.file[1].desc == NONEXISTENT) {
3783 /* Neither file "exists", so there's nothing to compare. */
3784 } else if ((same_files
3785 = (cmp.file[0].desc != NONEXISTENT
3786 && cmp.file[1].desc != NONEXISTENT
3787 && 0 < same_file (&cmp.file[0].stat, &cmp.file[1].stat)
3788 && same_file_attributes (&cmp.file[0].stat,
3789 &cmp.file[1].stat)))) {
3790 /* The two named files are actually the same physical file. We
3791 know they are identical without actually reading them. */
3792 } else if (DIR_P (0) & DIR_P (1)) {
3793 /* If both are directories, compare the files in them. */
3794
3795 status = diff_dirs(&cmp, compare_files);
3796 } else if ((DIR_P (0) | DIR_P (1))
3797 || (parent
3798 && !((S_ISREG (cmp.file[0].stat.st_mode)
3799 || S_ISLNK (cmp.file[0].stat.st_mode))
3800 && (S_ISREG (cmp.file[1].stat.st_mode)
3801 || S_ISLNK (cmp.file[1].stat.st_mode))))) {
3802 if (cmp.file[0].desc == NONEXISTENT || cmp.file[1].desc == NONEXISTENT) {
3803 /* We have a subdirectory that exists only in one directory. */
3804
3805 status = diff_dirs(&cmp, compare_files);
3806 } else {
3807 /* We have two files that are not to be compared. */
3808
3809 message5("File %s is a %s while file %s is a %s\n",
3810 file_label[0] ? file_label[0] : cmp.file[0].name,
3811 file_type(&cmp.file[0].stat),
3812 file_label[1] ? file_label[1] : cmp.file[1].name,
3813 file_type(&cmp.file[1].stat));
3814
3815 /* This is a difference. */
3816 status = EXIT_FAILURE;
3817 }
3818 } else if (S_ISLNK (cmp.file[0].stat.st_mode)
3819 || S_ISLNK (cmp.file[1].stat.st_mode)) {
3820 /* We get here only if we use |lstat()|, not |stat()|. */
3821 status = EXIT_FAILURE;
3822 } else if (files_can_be_treated_as_binary
3823 && S_ISREG (cmp.file[0].stat.st_mode)
3824 && S_ISREG (cmp.file[1].stat.st_mode)
3825 && cmp.file[0].stat.st_size != cmp.file[1].stat.st_size
3826 && 0 < cmp.file[0].stat.st_size
3827 && 0 < cmp.file[1].stat.st_size) {
3828 message("Files %s and %s differ\n",
3829 file_label[0] ? file_label[0] : cmp.file[0].name,
3830 file_label[1] ? file_label[1] : cmp.file[1].name);
3831 status = EXIT_FAILURE;
3832 } else {
3833 /* Both exist and neither is a directory. */
3834
3835 /* Open the files and record their descriptors. */
3836
3837 if (cmp.file[0].desc == UNOPENED)
3838 if ((cmp.file[0].desc = open(cmp.file[0].name, O_RDONLY, 0)) < 0) {
3839 perror_with_name(cmp.file[0].name);
3840 status = EXIT_TROUBLE;
3841 }
3842 if (cmp.file[1].desc == UNOPENED) {
3843 if (same_files)
3844 cmp.file[1].desc = cmp.file[0].desc;
3845 else if ((cmp.file[1].desc = open(cmp.file[1].name, O_RDONLY, 0)) < 0) {
3846 perror_with_name(cmp.file[1].name);
3847 status = EXIT_TROUBLE;
3848 }
3849 }
3850
3851 /* Compare the files, if no error was found. */
3852
3853 if (status == EXIT_SUCCESS) {
3854 status = diff_2_files(&cmp);
3855 }
3856
3857 /* Close the file descriptors. */
3858
3859 if (0 <= cmp.file[0].desc && close(cmp.file[0].desc) != 0) {
3860 perror_with_name(cmp.file[0].name);
3861 status = EXIT_TROUBLE;
3862 }
3863 if (0 <= cmp.file[1].desc && cmp.file[0].desc != cmp.file[1].desc
3864 && close(cmp.file[1].desc) != 0) {
3865 perror_with_name(cmp.file[1].name);
3866 status = EXIT_TROUBLE;
3867 }
3868 }
3869
3870 /* Now the comparison has been done, if no error prevented it, and
3871 |status| is the value this function will return. */
3872
3873 if (status != EXIT_SUCCESS) {
3874 /* Flush stdout so that the user sees differences immediately.
3875 This can hurt performance, unfortunately. */
3876 if (fflush(stdout) != 0)
3877 pfatal_with_name("standard output");
3878 }
3879
3880 free(free0);
3881 free(free1);
3882
3883 return status;
3884 }