1*cb9b5491Smillert /* $OpenBSD: diff.c,v 1.42 2003/09/07 22:05:30 millert 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*cb9b5491Smillert static const char rcsid[] = "$OpenBSD: diff.c,v 1.42 2003/09/07 22:05:30 millert Exp $"; 254ec4b3d5Smillert #endif /* not lint */ 264ec4b3d5Smillert 274ec4b3d5Smillert #include <sys/param.h> 284ec4b3d5Smillert #include <sys/stat.h> 294ec4b3d5Smillert 30*cb9b5491Smillert #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" 42ae8d569bSderaadt 43a65ef95eSderaadt int aflag, bflag, dflag, iflag, lflag, Nflag, Pflag, rflag; 44a65ef95eSderaadt int sflag, tflag, Tflag, wflag; 454ec4b3d5Smillert int format, context, status; 461f9aa9e0Smillert char *start, *ifdefname, *diffargs, *label; 47d5d5ac6cStedu struct stat stb1, stb2; 484ec4b3d5Smillert struct excludes *excludes_list; 494ec4b3d5Smillert 5068cd7c43Stedu #define OPTIONS "0123456789abC:cdD:efhiL:lnNPqrS:sTtU:uwX:x:" 514ec4b3d5Smillert static struct option longopts[] = { 524ec4b3d5Smillert { "text", no_argument, 0, 'a' }, 534ec4b3d5Smillert { "ignore-space-change", no_argument, 0, 'b' }, 544ec4b3d5Smillert { "context", optional_argument, 0, 'C' }, 554ec4b3d5Smillert { "ifdef", required_argument, 0, 'D' }, 566e18f850Sotto { "minimal", no_argument, 0, 'd' }, 574ec4b3d5Smillert { "ed", no_argument, 0, 'e' }, 584ec4b3d5Smillert { "forward-ed", no_argument, 0, 'f' }, 594ec4b3d5Smillert { "ignore-case", no_argument, 0, 'i' }, 60b4bca33fSmillert { "paginate", no_argument, 0, 'l' }, 611f9aa9e0Smillert { "label", required_argument, 0, 'L' }, 624ec4b3d5Smillert { "new-file", no_argument, 0, 'N' }, 634ec4b3d5Smillert { "rcs", no_argument, 0, 'n' }, 64aeb82612Smillert { "unidirectional-new-file", no_argument, 0, 'P' }, 65cab5d83cSmillert { "brief", no_argument, 0, 'q' }, 664ec4b3d5Smillert { "recursive", no_argument, 0, 'r' }, 674ec4b3d5Smillert { "report-identical-files", no_argument, 0, 's' }, 684ec4b3d5Smillert { "starting-file", required_argument, 0, 'S' }, 694ec4b3d5Smillert { "expand-tabs", no_argument, 0, 't' }, 70049b39f6Sdavid { "initial-tab", no_argument, 0, 'T' }, 714ec4b3d5Smillert { "unified", optional_argument, 0, 'U' }, 724ec4b3d5Smillert { "ignore-all-space", no_argument, 0, 'w' }, 734ec4b3d5Smillert { "exclude", required_argument, 0, 'x' }, 744ec4b3d5Smillert { "exclude-from", required_argument, 0, 'X' }, 75d6c18fb8Smillert { NULL, 0, 0, '\0'} 764ec4b3d5Smillert }; 77ae8d569bSderaadt 78c42aed39Smillert __dead void usage(void); 794ec4b3d5Smillert void push_excludes(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; 88*cb9b5491Smillert int ch, lastch, gotstdin, prevoptind, newarg; 8926da422aStedu 904ec4b3d5Smillert oargv = argv; 914ec4b3d5Smillert gotstdin = 0; 92c42aed39Smillert 93*cb9b5491Smillert lastch = '\0'; 94*cb9b5491Smillert prevoptind = 1; 95*cb9b5491Smillert 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': 100*cb9b5491Smillert if (newarg) 101*cb9b5491Smillert usage(); /* disallow -[0-9]+ */ 102*cb9b5491Smillert else if (lastch == 'c' || lastch == 'u') 10368cd7c43Stedu context = 0; 104*cb9b5491Smillert else if (!isdigit(lastch) || context > INT_MAX / 10) 105*cb9b5491Smillert usage(); 106*cb9b5491Smillert context = (context * 10) + (ch - '0'); 10768cd7c43Stedu break; 108d5d5ac6cStedu case 'a': 1094ec4b3d5Smillert aflag = 1; 110d5d5ac6cStedu break; 111ae8d569bSderaadt case 'b': 1124ec4b3d5Smillert bflag = 1; 113c42aed39Smillert break; 114c42aed39Smillert case 'C': 115ae8d569bSderaadt case 'c': 1164ec4b3d5Smillert format = D_CONTEXT; 1174ec4b3d5Smillert if (optarg != NULL) { 1184ec4b3d5Smillert l = strtol(optarg, &ep, 10); 1194ec4b3d5Smillert if (*ep != '\0' || l < 0 || l >= INT_MAX) 1204ec4b3d5Smillert usage(); 1214ec4b3d5Smillert context = (int)l; 1224ec4b3d5Smillert } else 123ae8d569bSderaadt context = 3; 124c42aed39Smillert break; 1256e18f850Sotto case 'd': 1266e18f850Sotto dflag = 1; 1276e18f850Sotto break; 128c42aed39Smillert case 'D': 1294ec4b3d5Smillert format = D_IFDEF; 13090f56ad8Smillert ifdefname = optarg; 131c42aed39Smillert break; 132c42aed39Smillert case 'e': 1334ec4b3d5Smillert format = D_EDIT; 134c42aed39Smillert break; 135c42aed39Smillert case 'f': 1364ec4b3d5Smillert format = D_REVERSE; 137c42aed39Smillert break; 138a0daf5ccSmillert case 'h': 139a0daf5ccSmillert /* silently ignore for backwards compatibility */ 140a0daf5ccSmillert break; 141c42aed39Smillert case 'i': 1424ec4b3d5Smillert iflag = 1; 1434ec4b3d5Smillert break; 1441f9aa9e0Smillert case 'L': 1451f9aa9e0Smillert label = optarg; 1461f9aa9e0Smillert break; 147b4bca33fSmillert case 'l': 148b4bca33fSmillert lflag = 1; 1497b6ec9e4Smillert signal(SIGPIPE, SIG_IGN); 150b4bca33fSmillert break; 1514ec4b3d5Smillert case 'N': 1524ec4b3d5Smillert Nflag = 1; 153c42aed39Smillert break; 154c42aed39Smillert case 'n': 1554ec4b3d5Smillert format = D_NREVERSE; 156c42aed39Smillert break; 157aeb82612Smillert case 'P': 158aeb82612Smillert Pflag = 1; 159aeb82612Smillert break; 160c42aed39Smillert case 'r': 1614ec4b3d5Smillert rflag = 1; 162c42aed39Smillert break; 163cab5d83cSmillert case 'q': 164cab5d83cSmillert format = D_BRIEF; 165cab5d83cSmillert break; 166c42aed39Smillert case 'S': 167c42aed39Smillert start = optarg; 168c42aed39Smillert break; 169c42aed39Smillert case 's': 1704ec4b3d5Smillert sflag = 1; 171c42aed39Smillert break; 1721f9aa9e0Smillert case 'T': 1731f9aa9e0Smillert Tflag = 1; 1741f9aa9e0Smillert break; 175c42aed39Smillert case 't': 1764ec4b3d5Smillert tflag = 1; 177c42aed39Smillert break; 1789de32c1bSmillert case 'U': 1799de32c1bSmillert case 'u': 1804ec4b3d5Smillert format = D_UNIFIED; 1814ec4b3d5Smillert if (optarg != NULL) { 1824ec4b3d5Smillert l = strtol(optarg, &ep, 10); 1834ec4b3d5Smillert if (*ep != '\0' || l < 0 || l >= INT_MAX) 1844ec4b3d5Smillert usage(); 1854ec4b3d5Smillert context = (int)l; 1864ec4b3d5Smillert } else 1879de32c1bSmillert context = 3; 1889de32c1bSmillert break; 189c42aed39Smillert case 'w': 1904ec4b3d5Smillert wflag = 1; 1914ec4b3d5Smillert break; 1924ec4b3d5Smillert case 'X': 1934ec4b3d5Smillert read_excludes_file(optarg); 1944ec4b3d5Smillert break; 1954ec4b3d5Smillert case 'x': 1964ec4b3d5Smillert push_excludes(optarg); 197c42aed39Smillert break; 198ae8d569bSderaadt default: 199c42aed39Smillert usage(); 200c42aed39Smillert break; 201ae8d569bSderaadt } 20268cd7c43Stedu lastch = ch; 203*cb9b5491Smillert newarg = optind != prevoptind; 204*cb9b5491Smillert prevoptind = optind; 205ae8d569bSderaadt } 206c42aed39Smillert argc -= optind; 207c42aed39Smillert argv += optind; 208c42aed39Smillert 2094ec4b3d5Smillert /* 2104ec4b3d5Smillert * Do sanity checks, fill in stb1 and stb2 and call the appropriate 2114ec4b3d5Smillert * driver routine. Both drivers use the contents of stb1 and stb2. 2124ec4b3d5Smillert */ 213c42aed39Smillert if (argc != 2) 2144ec4b3d5Smillert usage(); 2154ec4b3d5Smillert if (strcmp(argv[0], "-") == 0) { 216b1a26502Smillert fstat(STDIN_FILENO, &stb1); 2174ec4b3d5Smillert gotstdin = 1; 2184ec4b3d5Smillert } else if (stat(argv[0], &stb1) != 0) 2197b6ec9e4Smillert err(2, "%s", argv[0]); 2204ec4b3d5Smillert if (strcmp(argv[1], "-") == 0) { 221b1a26502Smillert fstat(STDIN_FILENO, &stb2); 2224ec4b3d5Smillert gotstdin = 1; 2234ec4b3d5Smillert } else if (stat(argv[1], &stb2) != 0) 2247b6ec9e4Smillert err(2, "%s", argv[1]); 2254ec4b3d5Smillert if (gotstdin && (S_ISDIR(stb1.st_mode) || S_ISDIR(stb2.st_mode))) 2267b6ec9e4Smillert errx(2, "can't compare - to a directory"); 22777aa65d5Smillert set_argstr(oargv + 1, argv); 2284ec4b3d5Smillert if (S_ISDIR(stb1.st_mode) && S_ISDIR(stb2.st_mode)) { 2294ec4b3d5Smillert if (format == D_IFDEF) 2307b6ec9e4Smillert errx(2, "-D option not supported with directories"); 2314ec4b3d5Smillert diffdir(argv[0], argv[1]); 232b4bca33fSmillert } else { 2337b6ec9e4Smillert if (S_ISDIR(stb1.st_mode)) { 2347b6ec9e4Smillert argv[0] = splice(argv[0], argv[1]); 2357b6ec9e4Smillert if (stat(argv[0], &stb1) < 0) 2367b6ec9e4Smillert err(2, "%s", argv[0]); 2377b6ec9e4Smillert } 2387b6ec9e4Smillert if (S_ISDIR(stb2.st_mode)) { 2397b6ec9e4Smillert argv[1] = splice(argv[1], argv[0]); 2407b6ec9e4Smillert if (stat(argv[1], &stb2) < 0) 2417b6ec9e4Smillert err(2, "%s", argv[1]); 2427b6ec9e4Smillert } 243b4bca33fSmillert print_status(diffreg(argv[0], argv[1], 0), argv[0], argv[1], 244b4bca33fSmillert NULL); 245b4bca33fSmillert } 2464ec4b3d5Smillert exit(status); 247ae8d569bSderaadt } 248ae8d569bSderaadt 24926da422aStedu void * 25049dffe13Smillert emalloc(size_t n) 251ae8d569bSderaadt { 25226da422aStedu void *p; 253ae8d569bSderaadt 25426da422aStedu if ((p = malloc(n)) == NULL) 2557b6ec9e4Smillert err(2, NULL); 25626da422aStedu return (p); 25726da422aStedu } 25826da422aStedu 25926da422aStedu void * 26049dffe13Smillert erealloc(void *p, size_t n) 26126da422aStedu { 26226da422aStedu void *q; 26326da422aStedu 26426da422aStedu if ((q = realloc(p, n)) == NULL) 2657b6ec9e4Smillert err(2, NULL); 266ae8d569bSderaadt return (q); 267ae8d569bSderaadt } 268ae8d569bSderaadt 269b4bca33fSmillert int 270b4bca33fSmillert easprintf(char **ret, const char *fmt, ...) 271b4bca33fSmillert { 272b4bca33fSmillert int len; 273b4bca33fSmillert va_list ap; 274b4bca33fSmillert 275b4bca33fSmillert va_start(ap, fmt); 276b4bca33fSmillert len = vasprintf(ret, fmt, ap); 277b4bca33fSmillert va_end(ap); 278b4bca33fSmillert 279b4bca33fSmillert if (len == -1) 2807b6ec9e4Smillert err(2, NULL); 281b4bca33fSmillert return (len); 282b4bca33fSmillert } 283b4bca33fSmillert 2844ec4b3d5Smillert void 2854ec4b3d5Smillert set_argstr(char **av, char **ave) 2864ec4b3d5Smillert { 2874ec4b3d5Smillert size_t argsize; 2884ec4b3d5Smillert char **ap; 2894ec4b3d5Smillert 29077aa65d5Smillert argsize = 4 + *ave - *av + 1; 2914ec4b3d5Smillert diffargs = emalloc(argsize); 2924ec4b3d5Smillert strlcpy(diffargs, "diff", argsize); 2934ec4b3d5Smillert for (ap = av + 1; ap < ave; ap++) { 2944ec4b3d5Smillert if (strcmp(*ap, "--") != 0) { 2954ec4b3d5Smillert strlcat(diffargs, " ", argsize); 2964ec4b3d5Smillert strlcat(diffargs, *ap, argsize); 2974ec4b3d5Smillert } 2984ec4b3d5Smillert } 2994ec4b3d5Smillert } 3004ec4b3d5Smillert 3014ec4b3d5Smillert /* 3024ec4b3d5Smillert * Read in an excludes file and push each line. 3034ec4b3d5Smillert */ 3044ec4b3d5Smillert void 3054ec4b3d5Smillert read_excludes_file(char *file) 3064ec4b3d5Smillert { 3074ec4b3d5Smillert FILE *fp; 3084ec4b3d5Smillert char *buf, *pattern; 3094ec4b3d5Smillert size_t len; 3104ec4b3d5Smillert 3114ec4b3d5Smillert if (strcmp(file, "-") == 0) 3124ec4b3d5Smillert fp = stdin; 3134ec4b3d5Smillert else if ((fp = fopen(file, "r")) == NULL) 3147b6ec9e4Smillert err(2, "%s", file); 3154ec4b3d5Smillert while ((buf = fgetln(fp, &len)) != NULL) { 3164ec4b3d5Smillert if (buf[len - 1] == '\n') 3174ec4b3d5Smillert len--; 3184ec4b3d5Smillert pattern = emalloc(len + 1); 3194ec4b3d5Smillert memcpy(pattern, buf, len); 3204ec4b3d5Smillert pattern[len] = '\0'; 3214ec4b3d5Smillert push_excludes(pattern); 3224ec4b3d5Smillert } 3234ec4b3d5Smillert if (strcmp(file, "-") != 0) 3244ec4b3d5Smillert fclose(fp); 3254ec4b3d5Smillert } 3264ec4b3d5Smillert 3274ec4b3d5Smillert /* 3284ec4b3d5Smillert * Push a pattern onto the excludes list. 3294ec4b3d5Smillert */ 3304ec4b3d5Smillert void 3314ec4b3d5Smillert push_excludes(char *pattern) 3324ec4b3d5Smillert { 3334ec4b3d5Smillert struct excludes *entry; 3344ec4b3d5Smillert 3354ec4b3d5Smillert entry = emalloc(sizeof(*entry)); 3364ec4b3d5Smillert entry->pattern = pattern; 3374ec4b3d5Smillert entry->next = excludes_list; 3384ec4b3d5Smillert excludes_list = entry; 3394ec4b3d5Smillert } 3404ec4b3d5Smillert 341b4bca33fSmillert void 342b4bca33fSmillert print_status(int val, char *path1, char *path2, char *entry) 343b4bca33fSmillert { 344b4bca33fSmillert switch (val) { 345b4bca33fSmillert case D_ONLY: 34637e8779fSmillert /* must strip off the trailing '/' */ 34737e8779fSmillert printf("Only in %.*s: %s\n", (int)(strlen(path1) - 1), 34837e8779fSmillert path1, entry); 349b4bca33fSmillert break; 350b4bca33fSmillert case D_COMMON: 351b4bca33fSmillert printf("Common subdirectories: %s%s and %s%s\n", 352b4bca33fSmillert path1, entry ? entry : "", path2, entry ? entry : ""); 353b4bca33fSmillert break; 354b4bca33fSmillert case D_BINARY: 355b4bca33fSmillert printf("Binary files %s%s and %s%s differ\n", 356b4bca33fSmillert path1, entry ? entry : "", path2, entry ? entry : ""); 357b4bca33fSmillert break; 358b4bca33fSmillert case D_DIFFER: 359b4bca33fSmillert if (format == D_BRIEF) 360b4bca33fSmillert printf("Files %s%s and %s%s differ\n", 361b4bca33fSmillert path1, entry ? entry : "", 362b4bca33fSmillert path2, entry ? entry : ""); 363b4bca33fSmillert break; 364b4bca33fSmillert case D_SAME: 365b4bca33fSmillert if (sflag) 366b4bca33fSmillert printf("Files %s%s and %s%s are identical\n", 367b4bca33fSmillert path1, entry ? entry : "", 368b4bca33fSmillert path2, entry ? entry : ""); 369b4bca33fSmillert break; 370fed3a06dSmillert case D_MISMATCH1: 371de414158Smillert printf("File %s%s is a directory while file %s%s is a regular file\n", 372fed3a06dSmillert path1, entry ? entry : "", path2, entry ? entry : ""); 373fed3a06dSmillert break; 374fed3a06dSmillert case D_MISMATCH2: 375de414158Smillert printf("File %s%s is a regular file while file %s%s is a directory\n", 3767b6ec9e4Smillert path1, entry ? entry : "", path2, entry ? entry : ""); 3777b6ec9e4Smillert break; 378b4bca33fSmillert } 379b4bca33fSmillert } 380b4bca33fSmillert 381c42aed39Smillert __dead void 382c42aed39Smillert usage(void) 383c42aed39Smillert { 384c012fe98Sderaadt (void)fprintf(stderr, 385f5537e38Sjmc "usage: diff [-abdilqtTw] [-c | -e | -f | -n | -u] [-L label] file1 file2\n" 386f5537e38Sjmc " diff [-abdilqtTw] [-L label] -C number file1 file2\n" 387f5537e38Sjmc " diff [-abdilqtw] -D string file1 file2\n" 388f5537e38Sjmc " diff [-abdilqtTw] [-L label] -U number file1 file2\n" 389f5537e38Sjmc " diff [-abdilNPqtTw] [-c | -e | -f | -n | -u ] [-L label] [-r] [-s]\n" 390f5537e38Sjmc " [-S name] [-X file] [-x pattern] dir1 dir2\n"); 391c42aed39Smillert 39266e5764eSmillert exit(2); 393c42aed39Smillert } 394