#define GDIFF_MAIN #include "diff.h" #include #include #include #include #include #include #include #include #include #include #define STREQ(a, b) (strcmp (a, b) == 0) #define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9) static int compare_files(struct comparison const *, char const *, char const *); static void try_help(char const *, char const *); static void check_stdout(void); static char const shortopts[] = "0123456789aU:dHL:qurN"; /* Return a string containing the command options with which diff was invoked. Spaces appear between what were separate |argv|-elements. There is a space at the beginning but none at the end. If there were no options, the result is an empty string. Arguments: |optionvec|, a vector containing separate |argv|-elements, and |count|, the length of that vector. todo phf: Implicitly add the -uNr since that's the defaults, and this combination of flags is what the original vdiff has.*/ static char * option_list(char **optionvec, int count) { char prefix[] = " -uNr"; int i; size_t size = 1 + sizeof prefix; char *result; char *p; for (i = 0; i < count; i++) size += strlen(optionvec[i]); p = result = xmalloc(size); p = stpncpy(p, prefix, sizeof prefix - 1); for (i = 0; i < count; i++) { *p++ = ' '; p = stpncpy(p, optionvec[i], strlen(optionvec[i])-1); } *p = '\0'; return result; } int main(int argc, char **argv) { int exit_status = EXIT_SUCCESS; int c; int prev = -1; lin ocontext = -1; bool explicit_context = false; char const *from_file = NULL; char const *to_file = NULL; uintmax_t numval; char *numend; set_program_name(argv[0]); /* Decode the options. */ while ((c = getopt(argc, argv, shortopts)) != -1) { switch (c) { case 0: break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': ocontext = (!ISDIGIT (prev) ? c - '0' : (ocontext - (c - '0' <= CONTEXT_MAX % 10) < CONTEXT_MAX / 10) ? 10 * ocontext + (c - '0') : CONTEXT_MAX); break; case 'a': text = true; break; case 'U': { if (optarg) { numval = strtoumax(optarg, &numend, 10); if (*numend) try_help("invalid context length '%s'", optarg); if (CONTEXT_MAX < numval) numval = CONTEXT_MAX; } else numval = 3; if (context < numval) context = numval; explicit_context = true; } break; case 'd': minimal = true; break; case 'H': speed_large_files = true; break; case 'L': if (!file_label[0]) file_label[0] = optarg; else if (!file_label[1]) file_label[1] = optarg; else fatal("too many file label options"); break; case 'q': brief = true; break; case 'u': case 'r': case 'N': /* compat */ break; default: try_help(NULL, NULL); } prev = c; } if (context < 3) context = 3; suppress_blank_empty = false; if (0 <= ocontext && (context < ocontext || (ocontext < context && !explicit_context))) context = ocontext; /* Make the horizon at least as large as the context, so that |shift_boundaries| has more freedom to shift the first and last hunks. */ if (horizon_lines < context) horizon_lines = context; files_can_be_treated_as_binary = false; switch_string = option_list(argv + 1, optind - 1); if (from_file) { if (to_file) fatal("--from-file and --to-file both specified"); else for (; optind < argc; optind++) { int status = compare_files(NULL, from_file, argv[optind]); if (exit_status < status) exit_status = status; } } else { if (to_file) for (; optind < argc; optind++) { int status = compare_files(NULL, argv[optind], to_file); if (exit_status < status) exit_status = status; } else { if (argc - optind != 2) { if (argc - optind < 2) try_help("missing operand after '%s'", argv[argc - 1]); else try_help("extra operand '%s'", argv[optind + 2]); } exit_status = compare_files(NULL, argv[optind], argv[optind + 1]); } } check_stdout(); exit(exit_status); return exit_status; } static void try_help(char const *reason_msgid, char const *operand) { if (reason_msgid) error(0, 0, reason_msgid, operand); exit(EXIT_TROUBLE); } static void check_stdout(void) { if (ferror(stdout)) fatal("write failed"); else if (fclose(stdout) != 0) pfatal_with_name("standard output"); } /* Compare two files (or dirs) with parent comparison |parent| and names |name0| and |name1|. (If |parent| is null, then the first name is just |name0|, etc.) This is self-contained; it opens the files and closes them. Value is |EXIT_SUCCESS| if files are the same, |EXIT_FAILURE| if different, |EXIT_TROUBLE| if there is a problem opening them. */ static int compare_files(struct comparison const *parent, char const *name0, char const *name1) { struct comparison cmp; #define DIR_P(f) (S_ISDIR (cmp.file[f].stat.st_mode) != 0) register int f; int status = EXIT_SUCCESS; bool same_files; char *free0; char *free1; memset (cmp.file, 0, sizeof cmp.file); cmp.parent = parent; /* |cmp.file[f].desc| markers */ #define NONEXISTENT (-1) /* nonexistent file */ #define UNOPENED (-2) /* unopened file (e.g. directory) */ #define ERRNO_ENCODE(errno) (-3 - (errno)) /* encoded |errno| value */ #define ERRNO_DECODE(desc) (-3 - (desc)) /* inverse of |ERRNO_ENCODE| */ cmp.file[0].desc = name0 ? UNOPENED : NONEXISTENT; cmp.file[1].desc = name1 ? UNOPENED : NONEXISTENT; /* Now record the full name of each file, including nonexistent ones. */ if (!name0) name0 = name1; if (!name1) name1 = name0; if (!parent) { free0 = NULL; free1 = NULL; cmp.file[0].name = name0; cmp.file[1].name = name1; } else { cmp.file[0].name = free0 = file_name_concat(parent->file[0].name, name0, NULL); cmp.file[1].name = free1 = file_name_concat(parent->file[1].name, name1, NULL); } /* Stat the files. */ for (f = 0; f < 2; f++) { if (cmp.file[f].desc != NONEXISTENT) { if (f && file_name_cmp(cmp.file[f].name, cmp.file[0].name) == 0) { cmp.file[f].desc = cmp.file[0].desc; cmp.file[f].stat = cmp.file[0].stat; } else if (STREQ (cmp.file[f].name, "-")) { cmp.file[f].desc = STDIN_FILENO; if (fstat(STDIN_FILENO, &cmp.file[f].stat) != 0) cmp.file[f].desc = ERRNO_ENCODE (errno); else { if (S_ISREG (cmp.file[f].stat.st_mode)) { off_t pos = lseek(STDIN_FILENO, 0, SEEK_CUR); if (pos < 0) cmp.file[f].desc = ERRNO_ENCODE (errno); else cmp.file[f].stat.st_size = MAX (0, cmp.file[f].stat.st_size - pos); } } } else if (stat(cmp.file[f].name, &cmp.file[f].stat) != 0) cmp.file[f].desc = ERRNO_ENCODE (errno); } } /* Mark files as nonexistent as needed for |-N| and |-P|, if they are inaccessible empty regular files (the kind of files that |patch| creates to indicate nonexistent backups), or if they are top-level files that do not exist but their counterparts do exist. */ for (f = 0; f < 2; f++) if (cmp.file[f].desc == UNOPENED ? (S_ISREG (cmp.file[f].stat.st_mode) && !(cmp.file[f].stat.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)) && cmp.file[f].stat.st_size == 0) : ((cmp.file[f].desc == ERRNO_ENCODE (ENOENT) || cmp.file[f].desc == ERRNO_ENCODE (EBADF)) && !parent && (cmp.file[1 - f].desc == UNOPENED || cmp.file[1 - f].desc == STDIN_FILENO))) cmp.file[f].desc = NONEXISTENT; for (f = 0; f < 2; f++) if (cmp.file[f].desc == NONEXISTENT) { memset (&cmp.file[f].stat, 0, sizeof cmp.file[f].stat); cmp.file[f].stat.st_mode = cmp.file[1 - f].stat.st_mode; } for (f = 0; f < 2; f++) { int e = ERRNO_DECODE (cmp.file[f].desc); if (0 <= e) { errno = e; perror_with_name(cmp.file[f].name); status = EXIT_TROUBLE; } } if (status == EXIT_SUCCESS && !parent && DIR_P (0) != DIR_P (1)) { /* If one is a directory, and it was specified in the command line, use the file in that dir with the other file's basename. */ int fnm_arg = DIR_P (0); int dir_arg = 1 - fnm_arg; char const *fnm = cmp.file[fnm_arg].name; char const *dir = cmp.file[dir_arg].name; char const *filename = cmp.file[dir_arg].name = free0 = find_dir_file_pathname(dir, last_component(fnm)); if (STREQ (fnm, "-")) fatal("cannot compare '-' to a directory"); if (stat(filename, &cmp.file[dir_arg].stat) != 0) { perror_with_name(filename); status = EXIT_TROUBLE; } } if (status != EXIT_SUCCESS) { /* One of the files should exist but does not. */ } else if (cmp.file[0].desc == NONEXISTENT && cmp.file[1].desc == NONEXISTENT) { /* Neither file "exists", so there's nothing to compare. */ } else if ((same_files = (cmp.file[0].desc != NONEXISTENT && cmp.file[1].desc != NONEXISTENT && 0 < same_file (&cmp.file[0].stat, &cmp.file[1].stat) && same_file_attributes (&cmp.file[0].stat, &cmp.file[1].stat)))) { /* The two named files are actually the same physical file. We know they are identical without actually reading them. */ } else if (DIR_P (0) & DIR_P (1)) { /* If both are directories, compare the files in them. */ status = diff_dirs(&cmp, compare_files); } else if ((DIR_P (0) | DIR_P (1)) || (parent && !((S_ISREG (cmp.file[0].stat.st_mode) || S_ISLNK (cmp.file[0].stat.st_mode)) && (S_ISREG (cmp.file[1].stat.st_mode) || S_ISLNK (cmp.file[1].stat.st_mode))))) { if (cmp.file[0].desc == NONEXISTENT || cmp.file[1].desc == NONEXISTENT) { /* We have a subdirectory that exists only in one directory. */ status = diff_dirs(&cmp, compare_files); } else { /* We have two files that are not to be compared. */ message5("File %s is a %s while file %s is a %s\n", file_label[0] ? file_label[0] : cmp.file[0].name, file_type(&cmp.file[0].stat), file_label[1] ? file_label[1] : cmp.file[1].name, file_type(&cmp.file[1].stat)); /* This is a difference. */ status = EXIT_FAILURE; } } else if (S_ISLNK (cmp.file[0].stat.st_mode) || S_ISLNK (cmp.file[1].stat.st_mode)) { /* We get here only if we use |lstat()|, not |stat()|. */ status = EXIT_FAILURE; } else if (files_can_be_treated_as_binary && S_ISREG (cmp.file[0].stat.st_mode) && S_ISREG (cmp.file[1].stat.st_mode) && cmp.file[0].stat.st_size != cmp.file[1].stat.st_size && 0 < cmp.file[0].stat.st_size && 0 < cmp.file[1].stat.st_size) { message("Files %s and %s differ\n", file_label[0] ? file_label[0] : cmp.file[0].name, file_label[1] ? file_label[1] : cmp.file[1].name); status = EXIT_FAILURE; } else { /* Both exist and neither is a directory. */ /* Open the files and record their descriptors. */ if (cmp.file[0].desc == UNOPENED) if ((cmp.file[0].desc = open(cmp.file[0].name, O_RDONLY, 0)) < 0) { perror_with_name(cmp.file[0].name); status = EXIT_TROUBLE; } if (cmp.file[1].desc == UNOPENED) { if (same_files) cmp.file[1].desc = cmp.file[0].desc; else if ((cmp.file[1].desc = open(cmp.file[1].name, O_RDONLY, 0)) < 0) { perror_with_name(cmp.file[1].name); status = EXIT_TROUBLE; } } /* Compare the files, if no error was found. */ if (status == EXIT_SUCCESS) { status = diff_2_files(&cmp); } /* Close the file descriptors. */ if (0 <= cmp.file[0].desc && close(cmp.file[0].desc) != 0) { perror_with_name(cmp.file[0].name); status = EXIT_TROUBLE; } if (0 <= cmp.file[1].desc && cmp.file[0].desc != cmp.file[1].desc && close(cmp.file[1].desc) != 0) { perror_with_name(cmp.file[1].name); status = EXIT_TROUBLE; } } /* Now the comparison has been done, if no error prevented it, and |status| is the value this function will return. */ if (status != EXIT_SUCCESS) { /* Flush stdout so that the user sees differences immediately. This can hurt performance, unfortunately. */ if (fflush(stdout) != 0) pfatal_with_name("standard output"); } free(free0); free(free1); return status; }