1 /* $OpenBSD: diff.c,v 1.65 2015/12/29 19:04:46 gsoares Exp $ */ 2 3 /* 4 * Copyright (c) 2003 Todd C. Miller <Todd.Miller@courtesan.com> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 * 18 * Sponsored in part by the Defense Advanced Research Projects 19 * Agency (DARPA) and Air Force Research Laboratory, Air Force 20 * Materiel Command, USAF, under agreement number F39502-99-1-0512. 21 */ 22 23 #include <sys/cdefs.h> 24 __FBSDID("$FreeBSD$"); 25 26 #include <sys/stat.h> 27 28 #include <ctype.h> 29 #include <err.h> 30 #include <errno.h> 31 #include <getopt.h> 32 #include <stdlib.h> 33 #include <stdio.h> 34 #include <stdarg.h> 35 #include <string.h> 36 #include <unistd.h> 37 #include <limits.h> 38 39 #include "diff.h" 40 #include "xmalloc.h" 41 42 int lflag, Nflag, Pflag, rflag, sflag, Tflag, cflag; 43 int diff_format, diff_context, status, ignore_file_case; 44 int tabsize = 8; 45 char *start, *ifdefname, *diffargs, *label[2], *ignore_pats; 46 struct stat stb1, stb2; 47 struct excludes *excludes_list; 48 regex_t ignore_re; 49 50 #define OPTIONS "0123456789aBbC:cdD:efhI:iL:lnNPpqrS:sTtU:uwX:x:" 51 enum { 52 OPT_TSIZE = CHAR_MAX + 1, 53 OPT_STRIPCR, 54 OPT_IGN_FN_CASE, 55 OPT_NO_IGN_FN_CASE, 56 OPT_NORMAL, 57 }; 58 59 static struct option longopts[] = { 60 { "text", no_argument, 0, 'a' }, 61 { "ignore-space-change", no_argument, 0, 'b' }, 62 { "context", optional_argument, 0, 'C' }, 63 { "ifdef", required_argument, 0, 'D' }, 64 { "minimal", no_argument, 0, 'd' }, 65 { "ed", no_argument, 0, 'e' }, 66 { "forward-ed", no_argument, 0, 'f' }, 67 { "ignore-matching-lines", required_argument, 0, 'I' }, 68 { "ignore-case", no_argument, 0, 'i' }, 69 { "paginate", no_argument, NULL, 'l' }, 70 { "label", required_argument, 0, 'L' }, 71 { "new-file", no_argument, 0, 'N' }, 72 { "rcs", no_argument, 0, 'n' }, 73 { "unidirectional-new-file", no_argument, 0, 'P' }, 74 { "show-c-function", no_argument, 0, 'p' }, 75 { "brief", no_argument, 0, 'q' }, 76 { "recursive", no_argument, 0, 'r' }, 77 { "report-identical-files", no_argument, 0, 's' }, 78 { "starting-file", required_argument, 0, 'S' }, 79 { "expand-tabs", no_argument, 0, 't' }, 80 { "initial-tab", no_argument, 0, 'T' }, 81 { "unified", optional_argument, 0, 'U' }, 82 { "ignore-all-space", no_argument, 0, 'w' }, 83 { "exclude", required_argument, 0, 'x' }, 84 { "exclude-from", required_argument, 0, 'X' }, 85 { "ignore-file-name-case", no_argument, NULL, OPT_IGN_FN_CASE }, 86 { "no-ignore-file-name-case", no_argument, NULL, OPT_NO_IGN_FN_CASE }, 87 { "normal", no_argument, NULL, OPT_NORMAL }, 88 { "strip-trailing-cr", no_argument, NULL, OPT_STRIPCR }, 89 { "tabsize", optional_argument, NULL, OPT_TSIZE }, 90 { NULL, 0, 0, '\0'} 91 }; 92 93 void usage(void) __dead2; 94 void push_excludes(char *); 95 void push_ignore_pats(char *); 96 void read_excludes_file(char *file); 97 void set_argstr(char **, char **); 98 99 int 100 main(int argc, char **argv) 101 { 102 const char *errstr = NULL; 103 char *ep, **oargv; 104 long l; 105 int ch, dflags, lastch, gotstdin, prevoptind, newarg; 106 107 oargv = argv; 108 gotstdin = 0; 109 dflags = 0; 110 lastch = '\0'; 111 prevoptind = 1; 112 newarg = 1; 113 diff_context = 3; 114 diff_format = 0; 115 while ((ch = getopt_long(argc, argv, OPTIONS, longopts, NULL)) != -1) { 116 switch (ch) { 117 case '0': case '1': case '2': case '3': case '4': 118 case '5': case '6': case '7': case '8': case '9': 119 if (newarg) 120 usage(); /* disallow -[0-9]+ */ 121 else if (lastch == 'c' || lastch == 'u') 122 diff_context = 0; 123 else if (!isdigit(lastch) || diff_context > INT_MAX / 10) 124 usage(); 125 diff_context = (diff_context * 10) + (ch - '0'); 126 break; 127 case 'a': 128 dflags |= D_FORCEASCII; 129 break; 130 case 'b': 131 dflags |= D_FOLDBLANKS; 132 break; 133 case 'C': 134 case 'c': 135 cflag = 1; 136 diff_format = D_CONTEXT; 137 if (optarg != NULL) { 138 l = strtol(optarg, &ep, 10); 139 if (*ep != '\0' || l < 0 || l >= INT_MAX) 140 usage(); 141 diff_context = (int)l; 142 } 143 break; 144 case 'd': 145 dflags |= D_MINIMAL; 146 break; 147 case 'D': 148 diff_format = D_IFDEF; 149 ifdefname = optarg; 150 break; 151 case 'e': 152 diff_format = D_EDIT; 153 break; 154 case 'f': 155 diff_format = D_REVERSE; 156 break; 157 case 'h': 158 /* silently ignore for backwards compatibility */ 159 break; 160 case 'I': 161 push_ignore_pats(optarg); 162 break; 163 case 'i': 164 dflags |= D_IGNORECASE; 165 break; 166 case 'L': 167 if (label[0] == NULL) 168 label[0] = optarg; 169 else if (label[1] == NULL) 170 label[1] = optarg; 171 else 172 usage(); 173 break; 174 case 'l': 175 lflag = 1; 176 break; 177 case 'N': 178 Nflag = 1; 179 break; 180 case 'n': 181 diff_format = D_NREVERSE; 182 break; 183 case 'p': 184 if (diff_format == 0) 185 diff_format = D_CONTEXT; 186 dflags |= D_PROTOTYPE; 187 break; 188 case 'P': 189 Pflag = 1; 190 break; 191 case 'r': 192 rflag = 1; 193 break; 194 case 'q': 195 diff_format = D_BRIEF; 196 break; 197 case 'S': 198 start = optarg; 199 break; 200 case 's': 201 sflag = 1; 202 break; 203 case 'T': 204 Tflag = 1; 205 break; 206 case 't': 207 dflags |= D_EXPANDTABS; 208 break; 209 case 'U': 210 case 'u': 211 diff_format = D_UNIFIED; 212 if (optarg != NULL) { 213 l = strtol(optarg, &ep, 10); 214 if (*ep != '\0' || l < 0 || l >= INT_MAX) 215 usage(); 216 diff_context = (int)l; 217 } 218 break; 219 case 'w': 220 dflags |= D_IGNOREBLANKS; 221 break; 222 case 'X': 223 read_excludes_file(optarg); 224 break; 225 case 'x': 226 push_excludes(optarg); 227 break; 228 case OPT_IGN_FN_CASE: 229 ignore_file_case = 1; 230 break; 231 case OPT_NO_IGN_FN_CASE: 232 ignore_file_case = 0; 233 break; 234 case OPT_NORMAL: 235 diff_format = D_NORMAL; 236 break; 237 case OPT_TSIZE: 238 tabsize = (int) strtonum(optarg, 1, INT_MAX, &errstr); 239 if (errstr) { 240 warnx("Invalid argument for tabsize"); 241 usage(); 242 } 243 break; 244 case OPT_STRIPCR: 245 dflags |= D_STRIPCR; 246 break; 247 default: 248 usage(); 249 break; 250 } 251 lastch = ch; 252 newarg = optind != prevoptind; 253 prevoptind = optind; 254 } 255 argc -= optind; 256 argv += optind; 257 258 #ifdef __OpenBSD__ 259 if (pledge("stdio rpath tmppath", NULL) == -1) 260 err(2, "pledge"); 261 #endif 262 263 /* 264 * Do sanity checks, fill in stb1 and stb2 and call the appropriate 265 * driver routine. Both drivers use the contents of stb1 and stb2. 266 */ 267 if (argc != 2) 268 usage(); 269 if (ignore_pats != NULL) { 270 char buf[BUFSIZ]; 271 int error; 272 273 if ((error = regcomp(&ignore_re, ignore_pats, 274 REG_NEWLINE | REG_EXTENDED)) != 0) { 275 regerror(error, &ignore_re, buf, sizeof(buf)); 276 if (*ignore_pats != '\0') 277 errx(2, "%s: %s", ignore_pats, buf); 278 else 279 errx(2, "%s", buf); 280 } 281 } 282 if (strcmp(argv[0], "-") == 0) { 283 fstat(STDIN_FILENO, &stb1); 284 gotstdin = 1; 285 } else if (stat(argv[0], &stb1) != 0) 286 err(2, "%s", argv[0]); 287 if (strcmp(argv[1], "-") == 0) { 288 fstat(STDIN_FILENO, &stb2); 289 gotstdin = 1; 290 } else if (stat(argv[1], &stb2) != 0) 291 err(2, "%s", argv[1]); 292 if (gotstdin && (S_ISDIR(stb1.st_mode) || S_ISDIR(stb2.st_mode))) 293 errx(2, "can't compare - to a directory"); 294 set_argstr(oargv, argv); 295 if (S_ISDIR(stb1.st_mode) && S_ISDIR(stb2.st_mode)) { 296 if (diff_format == D_IFDEF) 297 errx(2, "-D option not supported with directories"); 298 diffdir(argv[0], argv[1], dflags); 299 } else { 300 if (S_ISDIR(stb1.st_mode)) { 301 argv[0] = splice(argv[0], argv[1]); 302 if (stat(argv[0], &stb1) < 0) 303 err(2, "%s", argv[0]); 304 } 305 if (S_ISDIR(stb2.st_mode)) { 306 argv[1] = splice(argv[1], argv[0]); 307 if (stat(argv[1], &stb2) < 0) 308 err(2, "%s", argv[1]); 309 } 310 print_status(diffreg(argv[0], argv[1], dflags, 1), argv[0], 311 argv[1], ""); 312 } 313 exit(status); 314 } 315 316 void 317 set_argstr(char **av, char **ave) 318 { 319 size_t argsize; 320 char **ap; 321 322 argsize = 4 + *ave - *av + 1; 323 diffargs = xmalloc(argsize); 324 strlcpy(diffargs, "diff", argsize); 325 for (ap = av + 1; ap < ave; ap++) { 326 if (strcmp(*ap, "--") != 0) { 327 strlcat(diffargs, " ", argsize); 328 strlcat(diffargs, *ap, argsize); 329 } 330 } 331 } 332 333 /* 334 * Read in an excludes file and push each line. 335 */ 336 void 337 read_excludes_file(char *file) 338 { 339 FILE *fp; 340 char *buf, *pattern; 341 size_t len; 342 343 if (strcmp(file, "-") == 0) 344 fp = stdin; 345 else if ((fp = fopen(file, "r")) == NULL) 346 err(2, "%s", file); 347 while ((buf = fgetln(fp, &len)) != NULL) { 348 if (buf[len - 1] == '\n') 349 len--; 350 pattern = xmalloc(len + 1); 351 memcpy(pattern, buf, len); 352 pattern[len] = '\0'; 353 push_excludes(pattern); 354 } 355 if (strcmp(file, "-") != 0) 356 fclose(fp); 357 } 358 359 /* 360 * Push a pattern onto the excludes list. 361 */ 362 void 363 push_excludes(char *pattern) 364 { 365 struct excludes *entry; 366 367 entry = xmalloc(sizeof(*entry)); 368 entry->pattern = pattern; 369 entry->next = excludes_list; 370 excludes_list = entry; 371 } 372 373 void 374 push_ignore_pats(char *pattern) 375 { 376 size_t len; 377 378 if (ignore_pats == NULL) 379 ignore_pats = xstrdup(pattern); 380 else { 381 /* old + "|" + new + NUL */ 382 len = strlen(ignore_pats) + strlen(pattern) + 2; 383 ignore_pats = xreallocarray(ignore_pats, 1, len); 384 strlcat(ignore_pats, "|", len); 385 strlcat(ignore_pats, pattern, len); 386 } 387 } 388 389 void 390 print_only(const char *path, size_t dirlen, const char *entry) 391 { 392 if (dirlen > 1) 393 dirlen--; 394 printf("Only in %.*s: %s\n", (int)dirlen, path, entry); 395 } 396 397 void 398 print_status(int val, char *path1, char *path2, const char *entry) 399 { 400 switch (val) { 401 case D_BINARY: 402 printf("Binary files %s%s and %s%s differ\n", 403 path1, entry, path2, entry); 404 break; 405 case D_DIFFER: 406 if (diff_format == D_BRIEF) 407 printf("Files %s%s and %s%s differ\n", 408 path1, entry, path2, entry); 409 break; 410 case D_SAME: 411 if (sflag) 412 printf("Files %s%s and %s%s are identical\n", 413 path1, entry, path2, entry); 414 break; 415 case D_MISMATCH1: 416 printf("File %s%s is a directory while file %s%s is a regular file\n", 417 path1, entry, path2, entry); 418 break; 419 case D_MISMATCH2: 420 printf("File %s%s is a regular file while file %s%s is a directory\n", 421 path1, entry, path2, entry); 422 break; 423 case D_SKIPPED1: 424 printf("File %s%s is not a regular file or directory and was skipped\n", 425 path1, entry); 426 break; 427 case D_SKIPPED2: 428 printf("File %s%s is not a regular file or directory and was skipped\n", 429 path2, entry); 430 break; 431 } 432 } 433 434 void 435 usage(void) 436 { 437 (void)fprintf(stderr, 438 "usage: diff [-abdilpTtw] [-c | -e | -f | -n | -q | -u] [--ignore-case]\n" 439 " [--no-ignore-case] [--normal] [--strip-trailing-cr] [--tabsize]\n" 440 " [-I pattern] [-L label] file1 file2\n" 441 " diff [-abdilpTtw] [-I pattern] [-L label] [--ignore-case]\n" 442 " [--no-ignore-case] [--normal] [--strip-trailing-cr] [--tabsize]\n" 443 " -C number file1 file2\n" 444 " diff [-abdiltw] [-I pattern] [--ignore-case] [--no-ignore-case]\n" 445 " [--normal] [--strip-trailing-cr] [--tabsize] -D string file1 file2\n" 446 " diff [-abdilpTtw] [-I pattern] [-L label] [--ignore-case]\n" 447 " [--no-ignore-case] [--normal] [--tabsize] [--strip-trailing-cr]\n" 448 " -U number file1 file2\n" 449 " diff [-abdilNPprsTtw] [-c | -e | -f | -n | -q | -u] [--ignore-case]\n" 450 " [--no-ignore-case] [--normal] [--tabsize] [-I pattern] [-L label]\n" 451 " [-S name] [-X file] [-x pattern] dir1 dir2\n"); 452 453 exit(2); 454 } 455