1*b9fc9a72Sderaadt /* $OpenBSD: diff.c,v 1.58 2015/01/16 06:40:07 deraadt Exp $ */ 2d0c3f575Sderaadt 3d0c3f575Sderaadt /* 44ec4b3d5Smillert * Copyright (c) 2003 Todd C. Miller <Todd.Miller@courtesan.com> 5d0c3f575Sderaadt * 64ec4b3d5Smillert * Permission to use, copy, modify, and distribute this software for any 74ec4b3d5Smillert * purpose with or without fee is hereby granted, provided that the above 84ec4b3d5Smillert * copyright notice and this permission notice appear in all copies. 9d0c3f575Sderaadt * 104ec4b3d5Smillert * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 114ec4b3d5Smillert * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 124ec4b3d5Smillert * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 134ec4b3d5Smillert * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 144ec4b3d5Smillert * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 154ec4b3d5Smillert * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 164ec4b3d5Smillert * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 174ec4b3d5Smillert * 184ec4b3d5Smillert * Sponsored in part by the Defense Advanced Research Projects 194ec4b3d5Smillert * Agency (DARPA) and Air Force Research Laboratory, Air Force 204ec4b3d5Smillert * Materiel Command, USAF, under agreement number F39502-99-1-0512. 21d0c3f575Sderaadt */ 22d0c3f575Sderaadt 234ec4b3d5Smillert #include <sys/stat.h> 244ec4b3d5Smillert 25cb9b5491Smillert #include <ctype.h> 264ec4b3d5Smillert #include <err.h> 2766e5764eSmillert #include <errno.h> 284ec4b3d5Smillert #include <getopt.h> 297b6ec9e4Smillert #include <signal.h> 3026da422aStedu #include <stdlib.h> 314ec4b3d5Smillert #include <stdio.h> 3266e5764eSmillert #include <stdarg.h> 33e582024bSdavid #include <string.h> 3426da422aStedu #include <unistd.h> 35*b9fc9a72Sderaadt #include <limits.h> 36ae8d569bSderaadt 37ae8d569bSderaadt #include "diff.h" 384a034c3aSray #include "xmalloc.h" 39ae8d569bSderaadt 40dba1d6eaSray int lflag, Nflag, Pflag, rflag, sflag, Tflag; 4157003866Sray int diff_format, diff_context, status; 427bdb251cSmillert char *start, *ifdefname, *diffargs, *label[2], *ignore_pats; 43d5d5ac6cStedu struct stat stb1, stb2; 444ec4b3d5Smillert struct excludes *excludes_list; 45ccd55a2cSotto regex_t ignore_re; 464ec4b3d5Smillert 47ccd55a2cSotto #define OPTIONS "0123456789abC:cdD:efhI:iL:lnNPpqrS:sTtU:uwX:x:" 484ec4b3d5Smillert static struct option longopts[] = { 494ec4b3d5Smillert { "text", no_argument, 0, 'a' }, 504ec4b3d5Smillert { "ignore-space-change", no_argument, 0, 'b' }, 514ec4b3d5Smillert { "context", optional_argument, 0, 'C' }, 524ec4b3d5Smillert { "ifdef", required_argument, 0, 'D' }, 536e18f850Sotto { "minimal", no_argument, 0, 'd' }, 544ec4b3d5Smillert { "ed", no_argument, 0, 'e' }, 554ec4b3d5Smillert { "forward-ed", no_argument, 0, 'f' }, 56ccd55a2cSotto { "ignore-matching-lines", required_argument, 0, 'I' }, 574ec4b3d5Smillert { "ignore-case", no_argument, 0, 'i' }, 58b4bca33fSmillert { "paginate", no_argument, 0, 'l' }, 591f9aa9e0Smillert { "label", required_argument, 0, 'L' }, 604ec4b3d5Smillert { "new-file", no_argument, 0, 'N' }, 614ec4b3d5Smillert { "rcs", no_argument, 0, 'n' }, 62aeb82612Smillert { "unidirectional-new-file", no_argument, 0, 'P' }, 6396e45528Sotto { "show-c-function", no_argument, 0, 'p' }, 64cab5d83cSmillert { "brief", no_argument, 0, 'q' }, 654ec4b3d5Smillert { "recursive", no_argument, 0, 'r' }, 664ec4b3d5Smillert { "report-identical-files", no_argument, 0, 's' }, 674ec4b3d5Smillert { "starting-file", required_argument, 0, 'S' }, 684ec4b3d5Smillert { "expand-tabs", no_argument, 0, 't' }, 69049b39f6Sdavid { "initial-tab", no_argument, 0, 'T' }, 704ec4b3d5Smillert { "unified", optional_argument, 0, 'U' }, 714ec4b3d5Smillert { "ignore-all-space", no_argument, 0, 'w' }, 724ec4b3d5Smillert { "exclude", required_argument, 0, 'x' }, 734ec4b3d5Smillert { "exclude-from", required_argument, 0, 'X' }, 74d6c18fb8Smillert { NULL, 0, 0, '\0'} 754ec4b3d5Smillert }; 76ae8d569bSderaadt 77c42aed39Smillert __dead void usage(void); 784ec4b3d5Smillert void push_excludes(char *); 79ccd55a2cSotto void push_ignore_pats(char *); 804ec4b3d5Smillert void read_excludes_file(char *file); 814ec4b3d5Smillert void set_argstr(char **, char **); 82ae8d569bSderaadt 8326da422aStedu int 8426da422aStedu main(int argc, char **argv) 8526da422aStedu { 864ec4b3d5Smillert char *ep, **oargv; 874ec4b3d5Smillert long l; 88dba1d6eaSray int ch, dflags, lastch, gotstdin, prevoptind, newarg; 8926da422aStedu 904ec4b3d5Smillert oargv = argv; 914ec4b3d5Smillert gotstdin = 0; 92dba1d6eaSray dflags = 0; 93cb9b5491Smillert lastch = '\0'; 94cb9b5491Smillert prevoptind = 1; 95cb9b5491Smillert newarg = 1; 964ec4b3d5Smillert while ((ch = getopt_long(argc, argv, OPTIONS, longopts, NULL)) != -1) { 97c42aed39Smillert switch (ch) { 9868cd7c43Stedu case '0': case '1': case '2': case '3': case '4': 9968cd7c43Stedu case '5': case '6': case '7': case '8': case '9': 100cb9b5491Smillert if (newarg) 101cb9b5491Smillert usage(); /* disallow -[0-9]+ */ 102cb9b5491Smillert else if (lastch == 'c' || lastch == 'u') 10357003866Sray diff_context = 0; 10457003866Sray else if (!isdigit(lastch) || diff_context > INT_MAX / 10) 105cb9b5491Smillert usage(); 10657003866Sray diff_context = (diff_context * 10) + (ch - '0'); 10768cd7c43Stedu break; 108d5d5ac6cStedu case 'a': 109dba1d6eaSray dflags |= D_FORCEASCII; 110d5d5ac6cStedu break; 111ae8d569bSderaadt case 'b': 112dba1d6eaSray dflags |= D_FOLDBLANKS; 113c42aed39Smillert break; 114c42aed39Smillert case 'C': 115ae8d569bSderaadt case 'c': 11657003866Sray diff_format = D_CONTEXT; 1174ec4b3d5Smillert if (optarg != NULL) { 1184ec4b3d5Smillert l = strtol(optarg, &ep, 10); 1194ec4b3d5Smillert if (*ep != '\0' || l < 0 || l >= INT_MAX) 1204ec4b3d5Smillert usage(); 12157003866Sray diff_context = (int)l; 1224ec4b3d5Smillert } else 12357003866Sray diff_context = 3; 124c42aed39Smillert break; 1256e18f850Sotto case 'd': 126dba1d6eaSray dflags |= D_MINIMAL; 1276e18f850Sotto break; 128c42aed39Smillert case 'D': 12957003866Sray diff_format = D_IFDEF; 13090f56ad8Smillert ifdefname = optarg; 131c42aed39Smillert break; 132c42aed39Smillert case 'e': 13357003866Sray diff_format = D_EDIT; 134c42aed39Smillert break; 135c42aed39Smillert case 'f': 13657003866Sray diff_format = D_REVERSE; 137c42aed39Smillert break; 138a0daf5ccSmillert case 'h': 139a0daf5ccSmillert /* silently ignore for backwards compatibility */ 140a0daf5ccSmillert break; 141ccd55a2cSotto case 'I': 142ccd55a2cSotto push_ignore_pats(optarg); 143ccd55a2cSotto break; 144c42aed39Smillert case 'i': 145dba1d6eaSray dflags |= D_IGNORECASE; 1464ec4b3d5Smillert break; 1471f9aa9e0Smillert case 'L': 1487bdb251cSmillert if (label[0] == NULL) 1497bdb251cSmillert label[0] = optarg; 1507bdb251cSmillert else if (label[1] == NULL) 1517bdb251cSmillert label[1] = optarg; 1527bdb251cSmillert else 1537bdb251cSmillert usage(); 1541f9aa9e0Smillert break; 155b4bca33fSmillert case 'l': 156b4bca33fSmillert lflag = 1; 1577b6ec9e4Smillert signal(SIGPIPE, SIG_IGN); 158b4bca33fSmillert break; 1594ec4b3d5Smillert case 'N': 1604ec4b3d5Smillert Nflag = 1; 161c42aed39Smillert break; 162c42aed39Smillert case 'n': 16357003866Sray diff_format = D_NREVERSE; 164c42aed39Smillert break; 16596e45528Sotto case 'p': 166dba1d6eaSray dflags |= D_PROTOTYPE; 16796e45528Sotto break; 168aeb82612Smillert case 'P': 169aeb82612Smillert Pflag = 1; 170aeb82612Smillert break; 171c42aed39Smillert case 'r': 1724ec4b3d5Smillert rflag = 1; 173c42aed39Smillert break; 174cab5d83cSmillert case 'q': 17557003866Sray diff_format = D_BRIEF; 176cab5d83cSmillert break; 177c42aed39Smillert case 'S': 178c42aed39Smillert start = optarg; 179c42aed39Smillert break; 180c42aed39Smillert case 's': 1814ec4b3d5Smillert sflag = 1; 182c42aed39Smillert break; 1831f9aa9e0Smillert case 'T': 1841f9aa9e0Smillert Tflag = 1; 1851f9aa9e0Smillert break; 186c42aed39Smillert case 't': 187dba1d6eaSray dflags |= D_EXPANDTABS; 188c42aed39Smillert break; 1899de32c1bSmillert case 'U': 1909de32c1bSmillert case 'u': 19157003866Sray diff_format = D_UNIFIED; 1924ec4b3d5Smillert if (optarg != NULL) { 1934ec4b3d5Smillert l = strtol(optarg, &ep, 10); 1944ec4b3d5Smillert if (*ep != '\0' || l < 0 || l >= INT_MAX) 1954ec4b3d5Smillert usage(); 19657003866Sray diff_context = (int)l; 1974ec4b3d5Smillert } else 19857003866Sray diff_context = 3; 1999de32c1bSmillert break; 200c42aed39Smillert case 'w': 201dba1d6eaSray dflags |= D_IGNOREBLANKS; 2024ec4b3d5Smillert break; 2034ec4b3d5Smillert case 'X': 2044ec4b3d5Smillert read_excludes_file(optarg); 2054ec4b3d5Smillert break; 2064ec4b3d5Smillert case 'x': 2074ec4b3d5Smillert push_excludes(optarg); 208c42aed39Smillert break; 209ae8d569bSderaadt default: 210c42aed39Smillert usage(); 211c42aed39Smillert break; 212ae8d569bSderaadt } 21368cd7c43Stedu lastch = ch; 214cb9b5491Smillert newarg = optind != prevoptind; 215cb9b5491Smillert prevoptind = optind; 216ae8d569bSderaadt } 217c42aed39Smillert argc -= optind; 218c42aed39Smillert argv += optind; 219c42aed39Smillert 2204ec4b3d5Smillert /* 2214ec4b3d5Smillert * Do sanity checks, fill in stb1 and stb2 and call the appropriate 2224ec4b3d5Smillert * driver routine. Both drivers use the contents of stb1 and stb2. 2234ec4b3d5Smillert */ 224c42aed39Smillert if (argc != 2) 2254ec4b3d5Smillert usage(); 226ccd55a2cSotto if (ignore_pats != NULL) { 227ccd55a2cSotto char buf[BUFSIZ]; 228ccd55a2cSotto int error; 229ccd55a2cSotto 230ccd55a2cSotto if ((error = regcomp(&ignore_re, ignore_pats, 231ccd55a2cSotto REG_NEWLINE | REG_EXTENDED)) != 0) { 232ccd55a2cSotto regerror(error, &ignore_re, buf, sizeof(buf)); 233ccd55a2cSotto if (*ignore_pats != '\0') 234ccd55a2cSotto errx(2, "%s: %s", ignore_pats, buf); 235ccd55a2cSotto else 236ccd55a2cSotto errx(2, "%s", buf); 237ccd55a2cSotto } 238ccd55a2cSotto } 2394ec4b3d5Smillert if (strcmp(argv[0], "-") == 0) { 240b1a26502Smillert fstat(STDIN_FILENO, &stb1); 2414ec4b3d5Smillert gotstdin = 1; 2424ec4b3d5Smillert } else if (stat(argv[0], &stb1) != 0) 2437b6ec9e4Smillert err(2, "%s", argv[0]); 2444ec4b3d5Smillert if (strcmp(argv[1], "-") == 0) { 245b1a26502Smillert fstat(STDIN_FILENO, &stb2); 2464ec4b3d5Smillert gotstdin = 1; 2474ec4b3d5Smillert } else if (stat(argv[1], &stb2) != 0) 2487b6ec9e4Smillert err(2, "%s", argv[1]); 2494ec4b3d5Smillert if (gotstdin && (S_ISDIR(stb1.st_mode) || S_ISDIR(stb2.st_mode))) 2507b6ec9e4Smillert errx(2, "can't compare - to a directory"); 2515e50de09Sespie set_argstr(oargv, argv); 2524ec4b3d5Smillert if (S_ISDIR(stb1.st_mode) && S_ISDIR(stb2.st_mode)) { 25357003866Sray if (diff_format == D_IFDEF) 2547b6ec9e4Smillert errx(2, "-D option not supported with directories"); 2553f8e756bSray diffdir(argv[0], argv[1], dflags); 256b4bca33fSmillert } else { 2577b6ec9e4Smillert if (S_ISDIR(stb1.st_mode)) { 2587b6ec9e4Smillert argv[0] = splice(argv[0], argv[1]); 2597b6ec9e4Smillert if (stat(argv[0], &stb1) < 0) 2607b6ec9e4Smillert err(2, "%s", argv[0]); 2617b6ec9e4Smillert } 2627b6ec9e4Smillert if (S_ISDIR(stb2.st_mode)) { 2637b6ec9e4Smillert argv[1] = splice(argv[1], argv[0]); 2647b6ec9e4Smillert if (stat(argv[1], &stb2) < 0) 2657b6ec9e4Smillert err(2, "%s", argv[1]); 2667b6ec9e4Smillert } 2673f8e756bSray print_status(diffreg(argv[0], argv[1], dflags), argv[0], argv[1], 268d2ea36f5Sray ""); 269b4bca33fSmillert } 2704ec4b3d5Smillert exit(status); 271ae8d569bSderaadt } 272ae8d569bSderaadt 2734ec4b3d5Smillert void 2744ec4b3d5Smillert set_argstr(char **av, char **ave) 2754ec4b3d5Smillert { 2764ec4b3d5Smillert size_t argsize; 2774ec4b3d5Smillert char **ap; 2784ec4b3d5Smillert 27977aa65d5Smillert argsize = 4 + *ave - *av + 1; 2804a034c3aSray diffargs = xmalloc(argsize); 2814ec4b3d5Smillert strlcpy(diffargs, "diff", argsize); 2824ec4b3d5Smillert for (ap = av + 1; ap < ave; ap++) { 2834ec4b3d5Smillert if (strcmp(*ap, "--") != 0) { 2844ec4b3d5Smillert strlcat(diffargs, " ", argsize); 2854ec4b3d5Smillert strlcat(diffargs, *ap, argsize); 2864ec4b3d5Smillert } 2874ec4b3d5Smillert } 2884ec4b3d5Smillert } 2894ec4b3d5Smillert 2904ec4b3d5Smillert /* 2914ec4b3d5Smillert * Read in an excludes file and push each line. 2924ec4b3d5Smillert */ 2934ec4b3d5Smillert void 2944ec4b3d5Smillert read_excludes_file(char *file) 2954ec4b3d5Smillert { 2964ec4b3d5Smillert FILE *fp; 2974ec4b3d5Smillert char *buf, *pattern; 2984ec4b3d5Smillert size_t len; 2994ec4b3d5Smillert 3004ec4b3d5Smillert if (strcmp(file, "-") == 0) 3014ec4b3d5Smillert fp = stdin; 3024ec4b3d5Smillert else if ((fp = fopen(file, "r")) == NULL) 3037b6ec9e4Smillert err(2, "%s", file); 3044ec4b3d5Smillert while ((buf = fgetln(fp, &len)) != NULL) { 3054ec4b3d5Smillert if (buf[len - 1] == '\n') 3064ec4b3d5Smillert len--; 3074a034c3aSray pattern = xmalloc(len + 1); 3084ec4b3d5Smillert memcpy(pattern, buf, len); 3094ec4b3d5Smillert pattern[len] = '\0'; 3104ec4b3d5Smillert push_excludes(pattern); 3114ec4b3d5Smillert } 3124ec4b3d5Smillert if (strcmp(file, "-") != 0) 3134ec4b3d5Smillert fclose(fp); 3144ec4b3d5Smillert } 3154ec4b3d5Smillert 3164ec4b3d5Smillert /* 3174ec4b3d5Smillert * Push a pattern onto the excludes list. 3184ec4b3d5Smillert */ 3194ec4b3d5Smillert void 3204ec4b3d5Smillert push_excludes(char *pattern) 3214ec4b3d5Smillert { 3224ec4b3d5Smillert struct excludes *entry; 3234ec4b3d5Smillert 3244a034c3aSray entry = xmalloc(sizeof(*entry)); 3254ec4b3d5Smillert entry->pattern = pattern; 3264ec4b3d5Smillert entry->next = excludes_list; 3274ec4b3d5Smillert excludes_list = entry; 3284ec4b3d5Smillert } 3294ec4b3d5Smillert 330b4bca33fSmillert void 331ccd55a2cSotto push_ignore_pats(char *pattern) 332ccd55a2cSotto { 333ccd55a2cSotto size_t len; 334ccd55a2cSotto 3354a034c3aSray if (ignore_pats == NULL) 3364a034c3aSray ignore_pats = xstrdup(pattern); 3374a034c3aSray else { 338ccd55a2cSotto /* old + "|" + new + NUL */ 339ccd55a2cSotto len = strlen(ignore_pats) + strlen(pattern) + 2; 3404a034c3aSray ignore_pats = xrealloc(ignore_pats, 1, len); 341ccd55a2cSotto strlcat(ignore_pats, "|", len); 342ccd55a2cSotto strlcat(ignore_pats, pattern, len); 343ccd55a2cSotto } 344ccd55a2cSotto } 345ccd55a2cSotto 346ccd55a2cSotto void 3474893e147Smillert print_only(const char *path, size_t dirlen, const char *entry) 3484893e147Smillert { 3494893e147Smillert if (dirlen > 1) 3504893e147Smillert dirlen--; 3514893e147Smillert printf("Only in %.*s: %s\n", (int)dirlen, path, entry); 3524893e147Smillert } 3534893e147Smillert 3544893e147Smillert void 355b4bca33fSmillert print_status(int val, char *path1, char *path2, char *entry) 356b4bca33fSmillert { 357b4bca33fSmillert switch (val) { 358b4bca33fSmillert case D_ONLY: 3594893e147Smillert print_only(path1, strlen(path1), entry); 360b4bca33fSmillert break; 361b4bca33fSmillert case D_COMMON: 362b4bca33fSmillert printf("Common subdirectories: %s%s and %s%s\n", 363d2ea36f5Sray path1, entry, path2, entry); 364b4bca33fSmillert break; 365b4bca33fSmillert case D_BINARY: 366b4bca33fSmillert printf("Binary files %s%s and %s%s differ\n", 367d2ea36f5Sray path1, entry, path2, entry); 368b4bca33fSmillert break; 369b4bca33fSmillert case D_DIFFER: 37057003866Sray if (diff_format == D_BRIEF) 371b4bca33fSmillert printf("Files %s%s and %s%s differ\n", 372d2ea36f5Sray path1, entry, path2, entry); 373b4bca33fSmillert break; 374b4bca33fSmillert case D_SAME: 375b4bca33fSmillert if (sflag) 376b4bca33fSmillert printf("Files %s%s and %s%s are identical\n", 377d2ea36f5Sray path1, entry, path2, entry); 378b4bca33fSmillert break; 379fed3a06dSmillert case D_MISMATCH1: 380de414158Smillert printf("File %s%s is a directory while file %s%s is a regular file\n", 381d2ea36f5Sray path1, entry, path2, entry); 382fed3a06dSmillert break; 383fed3a06dSmillert case D_MISMATCH2: 384de414158Smillert printf("File %s%s is a regular file while file %s%s is a directory\n", 385d2ea36f5Sray path1, entry, path2, entry); 3867b6ec9e4Smillert break; 3875f4c3fa8Smillert case D_SKIPPED1: 3885f4c3fa8Smillert printf("File %s%s is not a regular file or directory and was skipped\n", 389d2ea36f5Sray path1, entry); 3905f4c3fa8Smillert break; 3915f4c3fa8Smillert case D_SKIPPED2: 3925f4c3fa8Smillert printf("File %s%s is not a regular file or directory and was skipped\n", 393d2ea36f5Sray path2, entry); 3945f4c3fa8Smillert break; 395b4bca33fSmillert } 396b4bca33fSmillert } 397b4bca33fSmillert 398c42aed39Smillert __dead void 399c42aed39Smillert usage(void) 400c42aed39Smillert { 401c012fe98Sderaadt (void)fprintf(stderr, 4029d40d95cSsobrado "usage: diff [-abdilpTtw] [-c | -e | -f | -n | -q | -u] [-I pattern] [-L label]\n" 4039d40d95cSsobrado " file1 file2\n" 4049d40d95cSsobrado " diff [-abdilpTtw] [-I pattern] [-L label] -C number file1 file2\n" 4059d40d95cSsobrado " diff [-abdiltw] [-I pattern] -D string file1 file2\n" 4069d40d95cSsobrado " diff [-abdilpTtw] [-I pattern] [-L label] -U number file1 file2\n" 4079d40d95cSsobrado " diff [-abdilNPprsTtw] [-c | -e | -f | -n | -q | -u] [-I pattern]\n" 4089d40d95cSsobrado " [-L label] [-S name] [-X file] [-x pattern] dir1 dir2\n"); 409c42aed39Smillert 41066e5764eSmillert exit(2); 411c42aed39Smillert } 412