1*dba1d6eaSray /* $OpenBSD: diff.c,v 1.51 2009/06/06 15:00:27 ray 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 #ifndef lint 24*dba1d6eaSray static const char rcsid[] = "$OpenBSD: diff.c,v 1.51 2009/06/06 15:00:27 ray Exp $"; 254ec4b3d5Smillert #endif /* not lint */ 264ec4b3d5Smillert 274ec4b3d5Smillert #include <sys/param.h> 284ec4b3d5Smillert #include <sys/stat.h> 294ec4b3d5Smillert 30cb9b5491Smillert #include <ctype.h> 314ec4b3d5Smillert #include <err.h> 3266e5764eSmillert #include <errno.h> 334ec4b3d5Smillert #include <getopt.h> 347b6ec9e4Smillert #include <signal.h> 3526da422aStedu #include <stdlib.h> 364ec4b3d5Smillert #include <stdio.h> 3766e5764eSmillert #include <stdarg.h> 38e582024bSdavid #include <string.h> 3926da422aStedu #include <unistd.h> 40ae8d569bSderaadt 41ae8d569bSderaadt #include "diff.h" 424a034c3aSray #include "xmalloc.h" 43ae8d569bSderaadt 44*dba1d6eaSray int lflag, Nflag, Pflag, rflag, sflag, Tflag; 454ec4b3d5Smillert int format, context, status; 467bdb251cSmillert char *start, *ifdefname, *diffargs, *label[2], *ignore_pats; 47d5d5ac6cStedu struct stat stb1, stb2; 484ec4b3d5Smillert struct excludes *excludes_list; 49ccd55a2cSotto regex_t ignore_re; 504ec4b3d5Smillert 51ccd55a2cSotto #define OPTIONS "0123456789abC:cdD:efhI:iL:lnNPpqrS:sTtU:uwX:x:" 524ec4b3d5Smillert static struct option longopts[] = { 534ec4b3d5Smillert { "text", no_argument, 0, 'a' }, 544ec4b3d5Smillert { "ignore-space-change", no_argument, 0, 'b' }, 554ec4b3d5Smillert { "context", optional_argument, 0, 'C' }, 564ec4b3d5Smillert { "ifdef", required_argument, 0, 'D' }, 576e18f850Sotto { "minimal", no_argument, 0, 'd' }, 584ec4b3d5Smillert { "ed", no_argument, 0, 'e' }, 594ec4b3d5Smillert { "forward-ed", no_argument, 0, 'f' }, 60ccd55a2cSotto { "ignore-matching-lines", required_argument, 0, 'I' }, 614ec4b3d5Smillert { "ignore-case", no_argument, 0, 'i' }, 62b4bca33fSmillert { "paginate", no_argument, 0, 'l' }, 631f9aa9e0Smillert { "label", required_argument, 0, 'L' }, 644ec4b3d5Smillert { "new-file", no_argument, 0, 'N' }, 654ec4b3d5Smillert { "rcs", no_argument, 0, 'n' }, 66aeb82612Smillert { "unidirectional-new-file", no_argument, 0, 'P' }, 6796e45528Sotto { "show-c-function", no_argument, 0, 'p' }, 68cab5d83cSmillert { "brief", no_argument, 0, 'q' }, 694ec4b3d5Smillert { "recursive", no_argument, 0, 'r' }, 704ec4b3d5Smillert { "report-identical-files", no_argument, 0, 's' }, 714ec4b3d5Smillert { "starting-file", required_argument, 0, 'S' }, 724ec4b3d5Smillert { "expand-tabs", no_argument, 0, 't' }, 73049b39f6Sdavid { "initial-tab", no_argument, 0, 'T' }, 744ec4b3d5Smillert { "unified", optional_argument, 0, 'U' }, 754ec4b3d5Smillert { "ignore-all-space", no_argument, 0, 'w' }, 764ec4b3d5Smillert { "exclude", required_argument, 0, 'x' }, 774ec4b3d5Smillert { "exclude-from", required_argument, 0, 'X' }, 78d6c18fb8Smillert { NULL, 0, 0, '\0'} 794ec4b3d5Smillert }; 80ae8d569bSderaadt 81c42aed39Smillert __dead void usage(void); 824ec4b3d5Smillert void push_excludes(char *); 83ccd55a2cSotto void push_ignore_pats(char *); 844ec4b3d5Smillert void read_excludes_file(char *file); 854ec4b3d5Smillert void set_argstr(char **, char **); 86ae8d569bSderaadt 8726da422aStedu int 8826da422aStedu main(int argc, char **argv) 8926da422aStedu { 904ec4b3d5Smillert char *ep, **oargv; 914ec4b3d5Smillert long l; 92*dba1d6eaSray int ch, dflags, lastch, gotstdin, prevoptind, newarg; 9326da422aStedu 944ec4b3d5Smillert oargv = argv; 954ec4b3d5Smillert gotstdin = 0; 96*dba1d6eaSray dflags = 0; 97cb9b5491Smillert lastch = '\0'; 98cb9b5491Smillert prevoptind = 1; 99cb9b5491Smillert newarg = 1; 1004ec4b3d5Smillert while ((ch = getopt_long(argc, argv, OPTIONS, longopts, NULL)) != -1) { 101c42aed39Smillert switch (ch) { 10268cd7c43Stedu case '0': case '1': case '2': case '3': case '4': 10368cd7c43Stedu case '5': case '6': case '7': case '8': case '9': 104cb9b5491Smillert if (newarg) 105cb9b5491Smillert usage(); /* disallow -[0-9]+ */ 106cb9b5491Smillert else if (lastch == 'c' || lastch == 'u') 10768cd7c43Stedu context = 0; 108cb9b5491Smillert else if (!isdigit(lastch) || context > INT_MAX / 10) 109cb9b5491Smillert usage(); 110cb9b5491Smillert context = (context * 10) + (ch - '0'); 11168cd7c43Stedu break; 112d5d5ac6cStedu case 'a': 113*dba1d6eaSray dflags |= D_FORCEASCII; 114d5d5ac6cStedu break; 115ae8d569bSderaadt case 'b': 116*dba1d6eaSray dflags |= D_FOLDBLANKS; 117c42aed39Smillert break; 118c42aed39Smillert case 'C': 119ae8d569bSderaadt case 'c': 1204ec4b3d5Smillert format = D_CONTEXT; 1214ec4b3d5Smillert if (optarg != NULL) { 1224ec4b3d5Smillert l = strtol(optarg, &ep, 10); 1234ec4b3d5Smillert if (*ep != '\0' || l < 0 || l >= INT_MAX) 1244ec4b3d5Smillert usage(); 1254ec4b3d5Smillert context = (int)l; 1264ec4b3d5Smillert } else 127ae8d569bSderaadt context = 3; 128c42aed39Smillert break; 1296e18f850Sotto case 'd': 130*dba1d6eaSray dflags |= D_MINIMAL; 1316e18f850Sotto break; 132c42aed39Smillert case 'D': 1334ec4b3d5Smillert format = D_IFDEF; 13490f56ad8Smillert ifdefname = optarg; 135c42aed39Smillert break; 136c42aed39Smillert case 'e': 1374ec4b3d5Smillert format = D_EDIT; 138c42aed39Smillert break; 139c42aed39Smillert case 'f': 1404ec4b3d5Smillert format = D_REVERSE; 141c42aed39Smillert break; 142a0daf5ccSmillert case 'h': 143a0daf5ccSmillert /* silently ignore for backwards compatibility */ 144a0daf5ccSmillert break; 145ccd55a2cSotto case 'I': 146ccd55a2cSotto push_ignore_pats(optarg); 147ccd55a2cSotto break; 148c42aed39Smillert case 'i': 149*dba1d6eaSray dflags |= D_IGNORECASE; 1504ec4b3d5Smillert break; 1511f9aa9e0Smillert case 'L': 1527bdb251cSmillert if (label[0] == NULL) 1537bdb251cSmillert label[0] = optarg; 1547bdb251cSmillert else if (label[1] == NULL) 1557bdb251cSmillert label[1] = optarg; 1567bdb251cSmillert else 1577bdb251cSmillert usage(); 1581f9aa9e0Smillert break; 159b4bca33fSmillert case 'l': 160b4bca33fSmillert lflag = 1; 1617b6ec9e4Smillert signal(SIGPIPE, SIG_IGN); 162b4bca33fSmillert break; 1634ec4b3d5Smillert case 'N': 1644ec4b3d5Smillert Nflag = 1; 165c42aed39Smillert break; 166c42aed39Smillert case 'n': 1674ec4b3d5Smillert format = D_NREVERSE; 168c42aed39Smillert break; 16996e45528Sotto case 'p': 170*dba1d6eaSray dflags |= D_PROTOTYPE; 17196e45528Sotto break; 172aeb82612Smillert case 'P': 173aeb82612Smillert Pflag = 1; 174aeb82612Smillert break; 175c42aed39Smillert case 'r': 1764ec4b3d5Smillert rflag = 1; 177c42aed39Smillert break; 178cab5d83cSmillert case 'q': 179cab5d83cSmillert format = D_BRIEF; 180cab5d83cSmillert break; 181c42aed39Smillert case 'S': 182c42aed39Smillert start = optarg; 183c42aed39Smillert break; 184c42aed39Smillert case 's': 1854ec4b3d5Smillert sflag = 1; 186c42aed39Smillert break; 1871f9aa9e0Smillert case 'T': 1881f9aa9e0Smillert Tflag = 1; 1891f9aa9e0Smillert break; 190c42aed39Smillert case 't': 191*dba1d6eaSray dflags |= D_EXPANDTABS; 192c42aed39Smillert break; 1939de32c1bSmillert case 'U': 1949de32c1bSmillert case 'u': 1954ec4b3d5Smillert format = D_UNIFIED; 1964ec4b3d5Smillert if (optarg != NULL) { 1974ec4b3d5Smillert l = strtol(optarg, &ep, 10); 1984ec4b3d5Smillert if (*ep != '\0' || l < 0 || l >= INT_MAX) 1994ec4b3d5Smillert usage(); 2004ec4b3d5Smillert context = (int)l; 2014ec4b3d5Smillert } else 2029de32c1bSmillert context = 3; 2039de32c1bSmillert break; 204c42aed39Smillert case 'w': 205*dba1d6eaSray dflags |= D_IGNOREBLANKS; 2064ec4b3d5Smillert break; 2074ec4b3d5Smillert case 'X': 2084ec4b3d5Smillert read_excludes_file(optarg); 2094ec4b3d5Smillert break; 2104ec4b3d5Smillert case 'x': 2114ec4b3d5Smillert push_excludes(optarg); 212c42aed39Smillert break; 213ae8d569bSderaadt default: 214c42aed39Smillert usage(); 215c42aed39Smillert break; 216ae8d569bSderaadt } 21768cd7c43Stedu lastch = ch; 218cb9b5491Smillert newarg = optind != prevoptind; 219cb9b5491Smillert prevoptind = optind; 220ae8d569bSderaadt } 221c42aed39Smillert argc -= optind; 222c42aed39Smillert argv += optind; 223c42aed39Smillert 2244ec4b3d5Smillert /* 2254ec4b3d5Smillert * Do sanity checks, fill in stb1 and stb2 and call the appropriate 2264ec4b3d5Smillert * driver routine. Both drivers use the contents of stb1 and stb2. 2274ec4b3d5Smillert */ 228c42aed39Smillert if (argc != 2) 2294ec4b3d5Smillert usage(); 230ccd55a2cSotto if (ignore_pats != NULL) { 231ccd55a2cSotto char buf[BUFSIZ]; 232ccd55a2cSotto int error; 233ccd55a2cSotto 234ccd55a2cSotto if ((error = regcomp(&ignore_re, ignore_pats, 235ccd55a2cSotto REG_NEWLINE | REG_EXTENDED)) != 0) { 236ccd55a2cSotto regerror(error, &ignore_re, buf, sizeof(buf)); 237ccd55a2cSotto if (*ignore_pats != '\0') 238ccd55a2cSotto errx(2, "%s: %s", ignore_pats, buf); 239ccd55a2cSotto else 240ccd55a2cSotto errx(2, "%s", buf); 241ccd55a2cSotto } 242ccd55a2cSotto } 2434ec4b3d5Smillert if (strcmp(argv[0], "-") == 0) { 244b1a26502Smillert fstat(STDIN_FILENO, &stb1); 2454ec4b3d5Smillert gotstdin = 1; 2464ec4b3d5Smillert } else if (stat(argv[0], &stb1) != 0) 2477b6ec9e4Smillert err(2, "%s", argv[0]); 2484ec4b3d5Smillert if (strcmp(argv[1], "-") == 0) { 249b1a26502Smillert fstat(STDIN_FILENO, &stb2); 2504ec4b3d5Smillert gotstdin = 1; 2514ec4b3d5Smillert } else if (stat(argv[1], &stb2) != 0) 2527b6ec9e4Smillert err(2, "%s", argv[1]); 2534ec4b3d5Smillert if (gotstdin && (S_ISDIR(stb1.st_mode) || S_ISDIR(stb2.st_mode))) 2547b6ec9e4Smillert errx(2, "can't compare - to a directory"); 2555e50de09Sespie set_argstr(oargv, argv); 2564ec4b3d5Smillert if (S_ISDIR(stb1.st_mode) && S_ISDIR(stb2.st_mode)) { 2574ec4b3d5Smillert if (format == D_IFDEF) 2587b6ec9e4Smillert errx(2, "-D option not supported with directories"); 2594ec4b3d5Smillert diffdir(argv[0], argv[1]); 260b4bca33fSmillert } else { 2617b6ec9e4Smillert if (S_ISDIR(stb1.st_mode)) { 2627b6ec9e4Smillert argv[0] = splice(argv[0], argv[1]); 2637b6ec9e4Smillert if (stat(argv[0], &stb1) < 0) 2647b6ec9e4Smillert err(2, "%s", argv[0]); 2657b6ec9e4Smillert } 2667b6ec9e4Smillert if (S_ISDIR(stb2.st_mode)) { 2677b6ec9e4Smillert argv[1] = splice(argv[1], argv[0]); 2687b6ec9e4Smillert if (stat(argv[1], &stb2) < 0) 2697b6ec9e4Smillert err(2, "%s", argv[1]); 2707b6ec9e4Smillert } 271b4bca33fSmillert print_status(diffreg(argv[0], argv[1], 0), argv[0], argv[1], 272b4bca33fSmillert NULL); 273b4bca33fSmillert } 2744ec4b3d5Smillert exit(status); 275ae8d569bSderaadt } 276ae8d569bSderaadt 2774ec4b3d5Smillert void 2784ec4b3d5Smillert set_argstr(char **av, char **ave) 2794ec4b3d5Smillert { 2804ec4b3d5Smillert size_t argsize; 2814ec4b3d5Smillert char **ap; 2824ec4b3d5Smillert 28377aa65d5Smillert argsize = 4 + *ave - *av + 1; 2844a034c3aSray diffargs = xmalloc(argsize); 2854ec4b3d5Smillert strlcpy(diffargs, "diff", argsize); 2864ec4b3d5Smillert for (ap = av + 1; ap < ave; ap++) { 2874ec4b3d5Smillert if (strcmp(*ap, "--") != 0) { 2884ec4b3d5Smillert strlcat(diffargs, " ", argsize); 2894ec4b3d5Smillert strlcat(diffargs, *ap, argsize); 2904ec4b3d5Smillert } 2914ec4b3d5Smillert } 2924ec4b3d5Smillert } 2934ec4b3d5Smillert 2944ec4b3d5Smillert /* 2954ec4b3d5Smillert * Read in an excludes file and push each line. 2964ec4b3d5Smillert */ 2974ec4b3d5Smillert void 2984ec4b3d5Smillert read_excludes_file(char *file) 2994ec4b3d5Smillert { 3004ec4b3d5Smillert FILE *fp; 3014ec4b3d5Smillert char *buf, *pattern; 3024ec4b3d5Smillert size_t len; 3034ec4b3d5Smillert 3044ec4b3d5Smillert if (strcmp(file, "-") == 0) 3054ec4b3d5Smillert fp = stdin; 3064ec4b3d5Smillert else if ((fp = fopen(file, "r")) == NULL) 3077b6ec9e4Smillert err(2, "%s", file); 3084ec4b3d5Smillert while ((buf = fgetln(fp, &len)) != NULL) { 3094ec4b3d5Smillert if (buf[len - 1] == '\n') 3104ec4b3d5Smillert len--; 3114a034c3aSray pattern = xmalloc(len + 1); 3124ec4b3d5Smillert memcpy(pattern, buf, len); 3134ec4b3d5Smillert pattern[len] = '\0'; 3144ec4b3d5Smillert push_excludes(pattern); 3154ec4b3d5Smillert } 3164ec4b3d5Smillert if (strcmp(file, "-") != 0) 3174ec4b3d5Smillert fclose(fp); 3184ec4b3d5Smillert } 3194ec4b3d5Smillert 3204ec4b3d5Smillert /* 3214ec4b3d5Smillert * Push a pattern onto the excludes list. 3224ec4b3d5Smillert */ 3234ec4b3d5Smillert void 3244ec4b3d5Smillert push_excludes(char *pattern) 3254ec4b3d5Smillert { 3264ec4b3d5Smillert struct excludes *entry; 3274ec4b3d5Smillert 3284a034c3aSray entry = xmalloc(sizeof(*entry)); 3294ec4b3d5Smillert entry->pattern = pattern; 3304ec4b3d5Smillert entry->next = excludes_list; 3314ec4b3d5Smillert excludes_list = entry; 3324ec4b3d5Smillert } 3334ec4b3d5Smillert 334b4bca33fSmillert void 335ccd55a2cSotto push_ignore_pats(char *pattern) 336ccd55a2cSotto { 337ccd55a2cSotto size_t len; 338ccd55a2cSotto 3394a034c3aSray if (ignore_pats == NULL) 3404a034c3aSray ignore_pats = xstrdup(pattern); 3414a034c3aSray else { 342ccd55a2cSotto /* old + "|" + new + NUL */ 343ccd55a2cSotto len = strlen(ignore_pats) + strlen(pattern) + 2; 3444a034c3aSray ignore_pats = xrealloc(ignore_pats, 1, len); 345ccd55a2cSotto strlcat(ignore_pats, "|", len); 346ccd55a2cSotto strlcat(ignore_pats, pattern, len); 347ccd55a2cSotto } 348ccd55a2cSotto } 349ccd55a2cSotto 350ccd55a2cSotto void 3514893e147Smillert print_only(const char *path, size_t dirlen, const char *entry) 3524893e147Smillert { 3534893e147Smillert if (dirlen > 1) 3544893e147Smillert dirlen--; 3554893e147Smillert printf("Only in %.*s: %s\n", (int)dirlen, path, entry); 3564893e147Smillert } 3574893e147Smillert 3584893e147Smillert void 359b4bca33fSmillert print_status(int val, char *path1, char *path2, char *entry) 360b4bca33fSmillert { 361b4bca33fSmillert switch (val) { 362b4bca33fSmillert case D_ONLY: 3634893e147Smillert print_only(path1, strlen(path1), entry); 364b4bca33fSmillert break; 365b4bca33fSmillert case D_COMMON: 366b4bca33fSmillert printf("Common subdirectories: %s%s and %s%s\n", 367b4bca33fSmillert path1, entry ? entry : "", path2, entry ? entry : ""); 368b4bca33fSmillert break; 369b4bca33fSmillert case D_BINARY: 370b4bca33fSmillert printf("Binary files %s%s and %s%s differ\n", 371b4bca33fSmillert path1, entry ? entry : "", path2, entry ? entry : ""); 372b4bca33fSmillert break; 373b4bca33fSmillert case D_DIFFER: 374b4bca33fSmillert if (format == D_BRIEF) 375b4bca33fSmillert printf("Files %s%s and %s%s differ\n", 376b4bca33fSmillert path1, entry ? entry : "", 377b4bca33fSmillert path2, entry ? entry : ""); 378b4bca33fSmillert break; 379b4bca33fSmillert case D_SAME: 380b4bca33fSmillert if (sflag) 381b4bca33fSmillert printf("Files %s%s and %s%s are identical\n", 382b4bca33fSmillert path1, entry ? entry : "", 383b4bca33fSmillert path2, entry ? entry : ""); 384b4bca33fSmillert break; 385fed3a06dSmillert case D_MISMATCH1: 386de414158Smillert printf("File %s%s is a directory while file %s%s is a regular file\n", 387fed3a06dSmillert path1, entry ? entry : "", path2, entry ? entry : ""); 388fed3a06dSmillert break; 389fed3a06dSmillert case D_MISMATCH2: 390de414158Smillert printf("File %s%s is a regular file while file %s%s is a directory\n", 3917b6ec9e4Smillert path1, entry ? entry : "", path2, entry ? entry : ""); 3927b6ec9e4Smillert break; 3935f4c3fa8Smillert case D_SKIPPED1: 3945f4c3fa8Smillert printf("File %s%s is not a regular file or directory and was skipped\n", 3955f4c3fa8Smillert path1, entry ? entry : ""); 3965f4c3fa8Smillert break; 3975f4c3fa8Smillert case D_SKIPPED2: 3985f4c3fa8Smillert printf("File %s%s is not a regular file or directory and was skipped\n", 3995f4c3fa8Smillert path2, entry ? entry : ""); 4005f4c3fa8Smillert break; 401b4bca33fSmillert } 402b4bca33fSmillert } 403b4bca33fSmillert 404c42aed39Smillert __dead void 405c42aed39Smillert usage(void) 406c42aed39Smillert { 407c012fe98Sderaadt (void)fprintf(stderr, 408d4b26a09Sjmc "usage: diff [-abdilpqTtw] [-I pattern] [-c | -e | -f | -n | -u]\n" 409ccd55a2cSotto " [-L label] file1 file2\n" 410d4b26a09Sjmc " diff [-abdilpqTtw] [-I pattern] [-L label] -C number file1 file2\n" 411ccd55a2cSotto " diff [-abdilqtw] [-I pattern] -D string file1 file2\n" 412d4b26a09Sjmc " diff [-abdilpqTtw] [-I pattern] [-L label] -U number file1 file2\n" 413d4b26a09Sjmc " diff [-abdilNPpqrsTtw] [-I pattern] [-c | -e | -f | -n | -u]\n" 414d4b26a09Sjmc " [-L label] [-S name] [-X file] [-x pattern] dir1 dir2\n"); 415c42aed39Smillert 41666e5764eSmillert exit(2); 417c42aed39Smillert } 418