1 /* $OpenBSD: diff.c,v 1.33 2003/07/22 01:16:01 millert 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 #ifndef lint 24 static const char rcsid[] = "$OpenBSD: diff.c,v 1.33 2003/07/22 01:16:01 millert Exp $"; 25 #endif /* not lint */ 26 27 #include <sys/param.h> 28 #include <sys/stat.h> 29 30 #include <err.h> 31 #include <errno.h> 32 #include <getopt.h> 33 #include <signal.h> 34 #include <stdlib.h> 35 #include <stdio.h> 36 #include <stdarg.h> 37 #include <string.h> 38 #include <unistd.h> 39 40 #include "diff.h" 41 42 int aflag, bflag, iflag, lflag, Nflag, Pflag, rflag, sflag, tflag, Tflag, 43 wflag; 44 int format, context, status; 45 char *start, *ifdefname, *diffargs, *label; 46 struct stat stb1, stb2; 47 struct excludes *excludes_list; 48 49 #define OPTIONS "abC:cD:efhiL:lnNPqrS:sTtU:uwX:x:" 50 static struct option longopts[] = { 51 { "text", no_argument, 0, 'a' }, 52 { "ignore-space-change", no_argument, 0, 'b' }, 53 { "context", optional_argument, 0, 'C' }, 54 { "ifdef", required_argument, 0, 'D' }, 55 { "ed", no_argument, 0, 'e' }, 56 { "forward-ed", no_argument, 0, 'f' }, 57 { "ignore-case", no_argument, 0, 'i' }, 58 { "paginate", no_argument, 0, 'l' }, 59 { "label", required_argument, 0, 'L' }, 60 { "new-file", no_argument, 0, 'N' }, 61 { "rcs", no_argument, 0, 'n' }, 62 { "unidirectional-new-file", no_argument, 0, 'P' }, 63 { "brief", no_argument, 0, 'q' }, 64 { "recursive", no_argument, 0, 'r' }, 65 { "report-identical-files", no_argument, 0, 's' }, 66 { "starting-file", required_argument, 0, 'S' }, 67 { "expand-tabs", no_argument, 0, 't' }, 68 { "intial-tab", no_argument, 0, 'T' }, 69 { "unified", optional_argument, 0, 'U' }, 70 { "ignore-all-space", no_argument, 0, 'w' }, 71 { "exclude", required_argument, 0, 'x' }, 72 { "exclude-from", required_argument, 0, 'X' }, 73 }; 74 75 __dead void usage(void); 76 void push_excludes(char *); 77 void read_excludes_file(char *file); 78 void set_argstr(char **, char **); 79 80 int 81 main(int argc, char **argv) 82 { 83 char *ep, **oargv; 84 long l; 85 int ch, gotstdin; 86 87 oargv = argv; 88 gotstdin = 0; 89 90 while ((ch = getopt_long(argc, argv, OPTIONS, longopts, NULL)) != -1) { 91 switch (ch) { 92 case 'a': 93 aflag = 1; 94 break; 95 case 'b': 96 bflag = 1; 97 break; 98 case 'C': 99 case 'c': 100 format = D_CONTEXT; 101 if (optarg != NULL) { 102 l = strtol(optarg, &ep, 10); 103 if (*ep != '\0' || l < 0 || l >= INT_MAX) 104 usage(); 105 context = (int)l; 106 } else 107 context = 3; 108 break; 109 case 'D': 110 format = D_IFDEF; 111 ifdefname = optarg; 112 break; 113 case 'e': 114 format = D_EDIT; 115 break; 116 case 'f': 117 format = D_REVERSE; 118 break; 119 case 'h': 120 /* silently ignore for backwards compatibility */ 121 break; 122 case 'i': 123 iflag = 1; 124 break; 125 case 'L': 126 label = optarg; 127 break; 128 case 'l': 129 lflag = 1; 130 signal(SIGPIPE, SIG_IGN); 131 break; 132 case 'N': 133 Nflag = 1; 134 break; 135 case 'n': 136 format = D_NREVERSE; 137 break; 138 case 'P': 139 Pflag = 1; 140 break; 141 case 'r': 142 rflag = 1; 143 break; 144 case 'q': 145 format = D_BRIEF; 146 break; 147 case 'S': 148 start = optarg; 149 break; 150 case 's': 151 sflag = 1; 152 break; 153 case 'T': 154 Tflag = 1; 155 break; 156 case 't': 157 tflag = 1; 158 break; 159 case 'U': 160 case 'u': 161 format = D_UNIFIED; 162 if (optarg != NULL) { 163 l = strtol(optarg, &ep, 10); 164 if (*ep != '\0' || l < 0 || l >= INT_MAX) 165 usage(); 166 context = (int)l; 167 } else 168 context = 3; 169 break; 170 case 'w': 171 wflag = 1; 172 break; 173 case 'X': 174 read_excludes_file(optarg); 175 break; 176 case 'x': 177 push_excludes(optarg); 178 break; 179 default: 180 usage(); 181 break; 182 } 183 } 184 argc -= optind; 185 argv += optind; 186 187 /* 188 * Do sanity checks, fill in stb1 and stb2 and call the appropriate 189 * driver routine. Both drivers use the contents of stb1 and stb2. 190 */ 191 if (argc != 2) 192 usage(); 193 if (strcmp(argv[0], "-") == 0) { 194 fstat(STDIN_FILENO, &stb1); 195 gotstdin = 1; 196 } else if (stat(argv[0], &stb1) != 0) 197 err(2, "%s", argv[0]); 198 if (strcmp(argv[1], "-") == 0) { 199 fstat(STDIN_FILENO, &stb2); 200 gotstdin = 1; 201 } else if (stat(argv[1], &stb2) != 0) 202 err(2, "%s", argv[1]); 203 if (gotstdin && (S_ISDIR(stb1.st_mode) || S_ISDIR(stb2.st_mode))) 204 errx(2, "can't compare - to a directory"); 205 set_argstr(oargv, argv); 206 if (S_ISDIR(stb1.st_mode) && S_ISDIR(stb2.st_mode)) { 207 if (format == D_IFDEF) 208 errx(2, "-D option not supported with directories"); 209 diffdir(argv[0], argv[1]); 210 } else { 211 if (S_ISDIR(stb1.st_mode)) { 212 argv[0] = splice(argv[0], argv[1]); 213 if (stat(argv[0], &stb1) < 0) 214 err(2, "%s", argv[0]); 215 } 216 if (S_ISDIR(stb2.st_mode)) { 217 argv[1] = splice(argv[1], argv[0]); 218 if (stat(argv[1], &stb2) < 0) 219 err(2, "%s", argv[1]); 220 } 221 print_status(diffreg(argv[0], argv[1], 0), argv[0], argv[1], 222 NULL); 223 } 224 exit(status); 225 } 226 227 void * 228 emalloc(size_t n) 229 { 230 void *p; 231 232 if ((p = malloc(n)) == NULL) 233 err(2, NULL); 234 return (p); 235 } 236 237 void * 238 erealloc(void *p, size_t n) 239 { 240 void *q; 241 242 if ((q = realloc(p, n)) == NULL) 243 err(2, NULL); 244 return (q); 245 } 246 247 int 248 easprintf(char **ret, const char *fmt, ...) 249 { 250 int len; 251 va_list ap; 252 253 va_start(ap, fmt); 254 len = vasprintf(ret, fmt, ap); 255 va_end(ap); 256 257 if (len == -1) 258 err(2, NULL); 259 return (len); 260 } 261 262 void 263 set_argstr(char **av, char **ave) 264 { 265 size_t argsize; 266 char **ap; 267 268 argsize = 4 + (char *)ave - (char *)av + 1; 269 diffargs = emalloc(argsize); 270 strlcpy(diffargs, "diff", argsize); 271 for (ap = av + 1; ap < ave; ap++) { 272 if (strcmp(*ap, "--") != 0) { 273 strlcat(diffargs, " ", argsize); 274 strlcat(diffargs, *ap, argsize); 275 } 276 } 277 } 278 279 /* 280 * Read in an excludes file and push each line. 281 */ 282 void 283 read_excludes_file(char *file) 284 { 285 FILE *fp; 286 char *buf, *pattern; 287 size_t len; 288 289 if (strcmp(file, "-") == 0) 290 fp = stdin; 291 else if ((fp = fopen(file, "r")) == NULL) 292 err(2, "%s", file); 293 while ((buf = fgetln(fp, &len)) != NULL) { 294 if (buf[len - 1] == '\n') 295 len--; 296 pattern = emalloc(len + 1); 297 memcpy(pattern, buf, len); 298 pattern[len] = '\0'; 299 push_excludes(pattern); 300 } 301 if (strcmp(file, "-") != 0) 302 fclose(fp); 303 } 304 305 /* 306 * Push a pattern onto the excludes list. 307 */ 308 void 309 push_excludes(char *pattern) 310 { 311 struct excludes *entry; 312 313 entry = emalloc(sizeof(*entry)); 314 entry->pattern = pattern; 315 entry->next = excludes_list; 316 excludes_list = entry; 317 } 318 319 void 320 print_status(int val, char *path1, char *path2, char *entry) 321 { 322 switch (val) { 323 case D_ONLY: 324 /* must strip off the trailing '/' */ 325 printf("Only in %.*s: %s\n", (int)(strlen(path1) - 1), 326 path1, entry); 327 break; 328 case D_COMMON: 329 printf("Common subdirectories: %s%s and %s%s\n", 330 path1, entry ? entry : "", path2, entry ? entry : ""); 331 break; 332 case D_BINARY: 333 printf("Binary files %s%s and %s%s differ\n", 334 path1, entry ? entry : "", path2, entry ? entry : ""); 335 break; 336 case D_DIFFER: 337 if (format == D_BRIEF) 338 printf("Files %s%s and %s%s differ\n", 339 path1, entry ? entry : "", 340 path2, entry ? entry : ""); 341 break; 342 case D_SAME: 343 if (sflag) 344 printf("Files %s%s and %s%s are identical\n", 345 path1, entry ? entry : "", 346 path2, entry ? entry : ""); 347 break; 348 case D_MISMATCH1: 349 printf("File %s%s is a directory while file %s%s is a regular file\n", 350 path1, entry ? entry : "", path2, entry ? entry : ""); 351 break; 352 case D_MISMATCH2: 353 printf("File %s%s is a regular file while file %s%s is a directory\n", 354 path1, entry ? entry : "", path2, entry ? entry : ""); 355 break; 356 } 357 } 358 359 __dead void 360 usage(void) 361 { 362 (void)fprintf(stderr, 363 "usage: diff [-bilqtTw] [-c | -e | -f | -n | -u] [-L label] file1 file2\n" 364 " diff [-bilqtTw] [-L label] -C number file1 file2\n" 365 " diff [-bilqtw] -D string file1 file2\n" 366 " diff [-bilqtTw] [-L label] -U number file1 file2\n" 367 " diff [-bilNPqwtT] [-c | -e | -f | -n | -u ] [-L label] [-r] [-s] [-S name]\n" 368 " [-X file] [-x pattern] dir1 dir2\n"); 369 370 exit(2); 371 } 372