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