1 /* $OpenBSD: sdiff.c,v 1.36 2015/12/29 19:04:46 gsoares Exp $ */ 2 3 /* 4 * Written by Raymond Lai <ray@cyth.net>. 5 * Public domain. 6 */ 7 8 #include <sys/cdefs.h> 9 __FBSDID("$FreeBSD$"); 10 11 #include <sys/param.h> 12 #include <sys/queue.h> 13 #include <sys/stat.h> 14 #include <sys/types.h> 15 #include <sys/wait.h> 16 17 #include <ctype.h> 18 #include <err.h> 19 #include <errno.h> 20 #include <fcntl.h> 21 #include <getopt.h> 22 #include <limits.h> 23 #include <paths.h> 24 #include <stdint.h> 25 #include <stdio.h> 26 #include <stdlib.h> 27 #include <string.h> 28 #include <unistd.h> 29 #include <libutil.h> 30 31 #include "common.h" 32 #include "extern.h" 33 34 #define DIFF_PATH "/usr/bin/diff" 35 36 #define WIDTH 126 37 /* 38 * Each column must be at least one character wide, plus three 39 * characters between the columns (space, [<|>], space). 40 */ 41 #define WIDTH_MIN 5 42 43 /* 3 kilobytes of chars */ 44 #define MAX_CHECK 768 45 46 /* A single diff line. */ 47 struct diffline { 48 STAILQ_ENTRY(diffline) diffentries; 49 char *left; 50 char div; 51 char *right; 52 }; 53 54 static void astrcat(char **, const char *); 55 static void enqueue(char *, char, char *); 56 static char *mktmpcpy(const char *); 57 static int istextfile(FILE *); 58 static void binexec(char *, char *, char *) __dead2; 59 static void freediff(struct diffline *); 60 static void int_usage(void); 61 static int parsecmd(FILE *, FILE *, FILE *); 62 static void printa(FILE *, size_t); 63 static void printc(FILE *, size_t, FILE *, size_t); 64 static void printcol(const char *, size_t *, const size_t); 65 static void printd(FILE *, size_t); 66 static void println(const char *, const char, const char *); 67 static void processq(void); 68 static void prompt(const char *, const char *); 69 static void usage(void) __dead2; 70 static char *xfgets(FILE *); 71 72 static STAILQ_HEAD(, diffline) diffhead = STAILQ_HEAD_INITIALIZER(diffhead); 73 static size_t line_width; /* width of a line (two columns and divider) */ 74 static size_t width; /* width of each column */ 75 static size_t file1ln, file2ln; /* line number of file1 and file2 */ 76 static int Iflag = 0; /* ignore sets matching regexp */ 77 static int lflag; /* print only left column for identical lines */ 78 static int sflag; /* skip identical lines */ 79 FILE *outfp; /* file to save changes to */ 80 const char *tmpdir; /* TMPDIR or /tmp */ 81 82 enum { 83 HELP_OPT = CHAR_MAX + 1, 84 NORMAL_OPT, 85 FCASE_SENSITIVE_OPT, 86 FCASE_IGNORE_OPT, 87 FROMFILE_OPT, 88 TOFILE_OPT, 89 UNIDIR_OPT, 90 STRIPCR_OPT, 91 HORIZ_OPT, 92 LEFTC_OPT, 93 SUPCL_OPT, 94 LF_OPT, 95 /* the following groupings must be in sequence */ 96 OLDGF_OPT, 97 NEWGF_OPT, 98 UNCGF_OPT, 99 CHGF_OPT, 100 OLDLF_OPT, 101 NEWLF_OPT, 102 UNCLF_OPT, 103 /* end order-sensitive enums */ 104 TSIZE_OPT, 105 HLINES_OPT, 106 LFILES_OPT, 107 DIFFPROG_OPT, 108 PIPE_FD, 109 /* pid from the diff parent (if applicable) */ 110 DIFF_PID, 111 112 NOOP_OPT, 113 }; 114 115 static struct option longopts[] = { 116 /* options only processed in sdiff */ 117 { "left-column", no_argument, NULL, LEFTC_OPT }, 118 { "suppress-common-lines", no_argument, NULL, 's' }, 119 { "width", required_argument, NULL, 'w' }, 120 121 { "output", required_argument, NULL, 'o' }, 122 { "diff-program", required_argument, NULL, DIFFPROG_OPT }, 123 124 { "pipe-fd", required_argument, NULL, PIPE_FD }, 125 { "diff-pid", required_argument, NULL, DIFF_PID }, 126 /* Options processed by diff. */ 127 { "ignore-file-name-case", no_argument, NULL, FCASE_IGNORE_OPT }, 128 { "no-ignore-file-name-case", no_argument, NULL, FCASE_SENSITIVE_OPT }, 129 { "strip-trailing-cr", no_argument, NULL, STRIPCR_OPT }, 130 { "tabsize", required_argument, NULL, TSIZE_OPT }, 131 { "help", no_argument, NULL, HELP_OPT }, 132 { "text", no_argument, NULL, 'a' }, 133 { "ignore-blank-lines", no_argument, NULL, 'B' }, 134 { "ignore-space-change", no_argument, NULL, 'b' }, 135 { "minimal", no_argument, NULL, 'd' }, 136 { "ignore-tab-expansion", no_argument, NULL, 'E' }, 137 { "ignore-matching-lines", required_argument, NULL, 'I' }, 138 { "ignore-case", no_argument, NULL, 'i' }, 139 { "expand-tabs", no_argument, NULL, 't' }, 140 { "speed-large-files", no_argument, NULL, 'H' }, 141 { "ignore-all-space", no_argument, NULL, 'W' }, 142 143 { NULL, 0, NULL, '\0'} 144 }; 145 146 static const char *help_msg[] = { 147 "\nusage: sdiff [-abdilstW] [-I regexp] [-o outfile] [-w width] file1 file2\n", 148 "\t-l, --left-column, Only print the left column for identical lines.", 149 "\t-o OUTFILE, --output=OUTFILE, nteractively merge file1 and file2 into outfile.", 150 "\t-s, --suppress-common-lines, Skip identical lines.", 151 "\t-w WIDTH, --width=WIDTH, Print a maximum of WIDTH characters on each line.", 152 "\tOptions passed to diff(1) are:", 153 "\t\t-a, --text, Treat file1 and file2 as text files.", 154 "\t\t-b, --ignore-trailing-cr, Ignore trailing blank spaces.", 155 "\t\t-d, --minimal, Minimize diff size.", 156 "\t\t-I RE, --ignore-matching-lines=RE, Ignore changes whose line matches RE.", 157 "\t\t-i, --ignore-case, Do a case-insensitive comparison.", 158 "\t\t-t, --expand-tabs Expand tabs to spaces.", 159 "\t\t-W, --ignore-all-spaces, Ignore all spaces.", 160 "\t\t--speed-large-files, Assume large file with scattered changes.", 161 "\t\t--strip-trailing-cr, Strip trailing carriage return.", 162 "\t\t--ignore-file-name-case, Ignore case of file names.", 163 "\t\t--no-ignore-file-name-case, Do not ignore file name case", 164 "\t\t--tabsize NUM, Change size of tabs (default 8.)", 165 166 NULL, 167 }; 168 169 /* 170 * Create temporary file if source_file is not a regular file. 171 * Returns temporary file name if one was malloced, NULL if unnecessary. 172 */ 173 static char * 174 mktmpcpy(const char *source_file) 175 { 176 struct stat sb; 177 ssize_t rcount; 178 int ifd, ofd; 179 u_char buf[BUFSIZ]; 180 char *target_file; 181 182 /* Open input and output. */ 183 ifd = open(source_file, O_RDONLY, 0); 184 /* File was opened successfully. */ 185 if (ifd != -1) { 186 if (fstat(ifd, &sb) == -1) 187 err(2, "error getting file status from %s", source_file); 188 189 /* Regular file. */ 190 if (S_ISREG(sb.st_mode)) { 191 close(ifd); 192 return (NULL); 193 } 194 } else { 195 /* If ``-'' does not exist the user meant stdin. */ 196 if (errno == ENOENT && strcmp(source_file, "-") == 0) 197 ifd = STDIN_FILENO; 198 else 199 err(2, "error opening %s", source_file); 200 } 201 202 /* Not a regular file, so copy input into temporary file. */ 203 if (asprintf(&target_file, "%s/sdiff.XXXXXXXXXX", tmpdir) == -1) 204 err(2, "asprintf"); 205 if ((ofd = mkstemp(target_file)) == -1) { 206 warn("error opening %s", target_file); 207 goto FAIL; 208 } 209 while ((rcount = read(ifd, buf, sizeof(buf))) != -1 && 210 rcount != 0) { 211 ssize_t wcount; 212 213 wcount = write(ofd, buf, (size_t)rcount); 214 if (-1 == wcount || rcount != wcount) { 215 warn("error writing to %s", target_file); 216 goto FAIL; 217 } 218 } 219 if (rcount == -1) { 220 warn("error reading from %s", source_file); 221 goto FAIL; 222 } 223 224 close(ifd); 225 close(ofd); 226 227 return (target_file); 228 229 FAIL: 230 unlink(target_file); 231 exit(2); 232 } 233 234 int 235 main(int argc, char **argv) 236 { 237 FILE *diffpipe=NULL, *file1, *file2; 238 size_t diffargc = 0, wflag = WIDTH; 239 int ch, fd[2] = {-1}, status; 240 pid_t pid=0; pid_t ppid =-1; 241 const char *outfile = NULL; 242 struct option *popt; 243 char **diffargv, *diffprog = DIFF_PATH, *filename1, *filename2, 244 *tmp1, *tmp2, *s1, *s2; 245 int i; 246 247 /* 248 * Process diff flags. 249 */ 250 /* 251 * Allocate memory for diff arguments and NULL. 252 * Each flag has at most one argument, so doubling argc gives an 253 * upper limit of how many diff args can be passed. argv[0], 254 * file1, and file2 won't have arguments so doubling them will 255 * waste some memory; however we need an extra space for the 256 * NULL at the end, so it sort of works out. 257 */ 258 if (!(diffargv = calloc(argc, sizeof(char **) * 2))) 259 err(2, "main"); 260 261 /* Add first argument, the program name. */ 262 diffargv[diffargc++] = diffprog; 263 264 /* create a dynamic string for merging single-switch options */ 265 if ( asprintf(&diffargv[diffargc++], "-") < 0 ) 266 err(2, "main"); 267 268 while ((ch = getopt_long(argc, argv, "aBbdEHI:ilo:stWw:", 269 longopts, NULL)) != -1) { 270 const char *errstr; 271 272 switch (ch) { 273 /* only compatible --long-name-form with diff */ 274 case FCASE_IGNORE_OPT: 275 case FCASE_SENSITIVE_OPT: 276 case STRIPCR_OPT: 277 case TSIZE_OPT: 278 case 'S': 279 break; 280 /* combine no-arg single switches */ 281 case 'a': 282 case 'B': 283 case 'b': 284 case 'd': 285 case 'E': 286 case 'i': 287 case 't': 288 case 'H': 289 case 'W': 290 for(popt = longopts; ch != popt->val && popt->name != NULL; popt++); 291 diffargv[1] = realloc(diffargv[1], sizeof(char) * strlen(diffargv[1]) + 2); 292 /* 293 * In diff, the 'W' option is 'w' and the 'w' is 'W'. 294 */ 295 if (ch == 'W') 296 sprintf(diffargv[1], "%sw", diffargv[1]); 297 else 298 sprintf(diffargv[1], "%s%c", diffargv[1], ch); 299 break; 300 case DIFFPROG_OPT: 301 diffargv[0] = diffprog = optarg; 302 break; 303 case 'I': 304 Iflag = 1; 305 diffargv[diffargc++] = "-I"; 306 diffargv[diffargc++] = optarg; 307 break; 308 case 'l': 309 lflag = 1; 310 break; 311 case 'o': 312 outfile = optarg; 313 break; 314 case 's': 315 sflag = 1; 316 break; 317 case 'w': 318 wflag = strtonum(optarg, WIDTH_MIN, 319 INT_MAX, &errstr); 320 if (errstr) 321 errx(2, "width is %s: %s", errstr, optarg); 322 break; 323 case DIFF_PID: 324 ppid = strtonum(optarg, 0, INT_MAX, &errstr); 325 if (errstr) 326 errx(2, "diff pid value is %s: %s", errstr, optarg); 327 break; 328 case HELP_OPT: 329 for (i = 0; help_msg[i] != NULL; i++) 330 printf("%s\n", help_msg[i]); 331 exit(0); 332 break; 333 default: 334 usage(); 335 break; 336 } 337 } 338 339 /* no single switches were used */ 340 if (strcmp(diffargv[1], "-") == 0 ) { 341 for ( i = 1; i < argc-1; i++) { 342 diffargv[i] = diffargv[i+1]; 343 } 344 diffargv[diffargc-1] = NULL; 345 diffargc--; 346 } 347 348 argc -= optind; 349 argv += optind; 350 351 if (argc != 2) 352 usage(); 353 354 if (outfile && (outfp = fopen(outfile, "w")) == NULL) 355 err(2, "could not open: %s", optarg); 356 357 if ((tmpdir = getenv("TMPDIR")) == NULL || *tmpdir == '\0') 358 tmpdir = _PATH_TMP; 359 360 filename1 = argv[0]; 361 filename2 = argv[1]; 362 363 /* 364 * Create temporary files for diff and sdiff to share if file1 365 * or file2 are not regular files. This allows sdiff and diff 366 * to read the same inputs if one or both inputs are stdin. 367 * 368 * If any temporary files were created, their names would be 369 * saved in tmp1 or tmp2. tmp1 should never equal tmp2. 370 */ 371 tmp1 = tmp2 = NULL; 372 /* file1 and file2 are the same, so copy to same temp file. */ 373 if (strcmp(filename1, filename2) == 0) { 374 if ((tmp1 = mktmpcpy(filename1))) 375 filename1 = filename2 = tmp1; 376 /* Copy file1 and file2 into separate temp files. */ 377 } else { 378 if ((tmp1 = mktmpcpy(filename1))) 379 filename1 = tmp1; 380 if ((tmp2 = mktmpcpy(filename2))) 381 filename2 = tmp2; 382 } 383 384 diffargv[diffargc++] = filename1; 385 diffargv[diffargc++] = filename2; 386 /* Add NULL to end of array to indicate end of array. */ 387 diffargv[diffargc++] = NULL; 388 389 /* Subtract column divider and divide by two. */ 390 width = (wflag - 3) / 2; 391 /* Make sure line_width can fit in size_t. */ 392 if (width > (SIZE_MAX - 3) / 2) 393 errx(2, "width is too large: %zu", width); 394 line_width = width * 2 + 3; 395 396 if (ppid == -1 ) { 397 if (pipe(fd)) 398 err(2, "pipe"); 399 400 switch (pid = fork()) { 401 case 0: 402 /* child */ 403 /* We don't read from the pipe. */ 404 close(fd[0]); 405 if (dup2(fd[1], STDOUT_FILENO) == -1) 406 err(2, "child could not duplicate descriptor"); 407 /* Free unused descriptor. */ 408 close(fd[1]); 409 execvp(diffprog, diffargv); 410 err(2, "could not execute diff: %s", diffprog); 411 break; 412 case -1: 413 err(2, "could not fork"); 414 break; 415 } 416 417 /* parent */ 418 /* We don't write to the pipe. */ 419 close(fd[1]); 420 421 /* Open pipe to diff command. */ 422 if ((diffpipe = fdopen(fd[0], "r")) == NULL) 423 err(2, "could not open diff pipe"); 424 } 425 if ((file1 = fopen(filename1, "r")) == NULL) 426 err(2, "could not open %s", filename1); 427 if ((file2 = fopen(filename2, "r")) == NULL) 428 err(2, "could not open %s", filename2); 429 if (!istextfile(file1) || !istextfile(file2)) { 430 /* Close open files and pipe, delete temps */ 431 fclose(file1); 432 fclose(file2); 433 fclose(diffpipe); 434 if (tmp1) 435 if (unlink(tmp1)) 436 warn("Error deleting %s.", tmp1); 437 if (tmp2) 438 if (unlink(tmp2)) 439 warn("Error deleting %s.", tmp2); 440 free(tmp1); 441 free(tmp2); 442 binexec(diffprog, filename1, filename2); 443 } 444 /* Line numbers start at one. */ 445 file1ln = file2ln = 1; 446 447 /* Read and parse diff output. */ 448 while (parsecmd(diffpipe, file1, file2) != EOF) 449 ; 450 fclose(diffpipe); 451 452 /* Wait for diff to exit. */ 453 if (waitpid(pid, &status, 0) == -1 || !WIFEXITED(status) || 454 WEXITSTATUS(status) >= 2) 455 err(2, "diff exited abnormally."); 456 457 /* Delete and free unneeded temporary files. */ 458 if (tmp1) 459 if (unlink(tmp1)) 460 warn("Error deleting %s.", tmp1); 461 if (tmp2) 462 if (unlink(tmp2)) 463 warn("Error deleting %s.", tmp2); 464 free(tmp1); 465 free(tmp2); 466 filename1 = filename2 = tmp1 = tmp2 = NULL; 467 468 /* No more diffs, so print common lines. */ 469 if (lflag) 470 while ((s1 = xfgets(file1))) 471 enqueue(s1, ' ', NULL); 472 else 473 for (;;) { 474 s1 = xfgets(file1); 475 s2 = xfgets(file2); 476 if (s1 || s2) 477 enqueue(s1, ' ', s2); 478 else 479 break; 480 } 481 fclose(file1); 482 fclose(file2); 483 /* Process unmodified lines. */ 484 processq(); 485 486 /* Return diff exit status. */ 487 return (WEXITSTATUS(status)); 488 } 489 490 /* 491 * When sdiff/zsdiff detects a binary file as input, executes them with 492 * diff/zdiff to maintain the same behavior as GNU sdiff with binary input. 493 */ 494 static void 495 binexec(char *diffprog, char *f1, char *f2) 496 { 497 498 char *args[] = {diffprog, f1, f2, (char *) 0}; 499 execv(diffprog, args); 500 501 /* If execv() fails, sdiff's execution will continue below. */ 502 errx(1, "Could not execute diff process.\n"); 503 } 504 505 /* 506 * Checks whether a file appears to be a text file. 507 */ 508 static int 509 istextfile(FILE *f) 510 { 511 int i; 512 char ch; 513 514 if (f == NULL) 515 return (1); 516 rewind(f); 517 for (i = 0; i <= MAX_CHECK || ch != EOF; i++) { 518 ch = fgetc(f); 519 if (ch == '\0') { 520 rewind(f); 521 return (0); 522 } 523 } 524 rewind(f); 525 return (1); 526 } 527 528 /* 529 * Prints an individual column (left or right), taking into account 530 * that tabs are variable-width. Takes a string, the current column 531 * the cursor is on the screen, and the maximum value of the column. 532 * The column value is updated as we go along. 533 */ 534 static void 535 printcol(const char *s, size_t *col, const size_t col_max) 536 { 537 538 for (; *s && *col < col_max; ++s) { 539 size_t new_col; 540 541 switch (*s) { 542 case '\t': 543 /* 544 * If rounding to next multiple of eight causes 545 * an integer overflow, just return. 546 */ 547 if (*col > SIZE_MAX - 8) 548 return; 549 550 /* Round to next multiple of eight. */ 551 new_col = (*col / 8 + 1) * 8; 552 553 /* 554 * If printing the tab goes past the column 555 * width, don't print it and just quit. 556 */ 557 if (new_col > col_max) 558 return; 559 *col = new_col; 560 break; 561 default: 562 ++(*col); 563 } 564 putchar(*s); 565 } 566 } 567 568 /* 569 * Prompts user to either choose between two strings or edit one, both, 570 * or neither. 571 */ 572 static void 573 prompt(const char *s1, const char *s2) 574 { 575 char *cmd; 576 577 /* Print command prompt. */ 578 putchar('%'); 579 580 /* Get user input. */ 581 for (; (cmd = xfgets(stdin)); free(cmd)) { 582 const char *p; 583 584 /* Skip leading whitespace. */ 585 for (p = cmd; isspace(*p); ++p) 586 ; 587 switch (*p) { 588 case 'e': 589 /* Skip `e'. */ 590 ++p; 591 if (eparse(p, s1, s2) == -1) 592 goto USAGE; 593 break; 594 case 'l': 595 case '1': 596 /* Choose left column as-is. */ 597 if (s1 != NULL) 598 fprintf(outfp, "%s\n", s1); 599 /* End of command parsing. */ 600 break; 601 case 'q': 602 goto QUIT; 603 case 'r': 604 case '2': 605 /* Choose right column as-is. */ 606 if (s2 != NULL) 607 fprintf(outfp, "%s\n", s2); 608 /* End of command parsing. */ 609 break; 610 case 's': 611 sflag = 1; 612 goto PROMPT; 613 case 'v': 614 sflag = 0; 615 /* FALLTHROUGH */ 616 default: 617 /* Interactive usage help. */ 618 USAGE: 619 int_usage(); 620 PROMPT: 621 putchar('%'); 622 623 /* Prompt user again. */ 624 continue; 625 } 626 free(cmd); 627 return; 628 } 629 630 /* 631 * If there was no error, we received an EOF from stdin, so we 632 * should quit. 633 */ 634 QUIT: 635 fclose(outfp); 636 exit(0); 637 } 638 639 /* 640 * Takes two strings, separated by a column divider. NULL strings are 641 * treated as empty columns. If the divider is the ` ' character, the 642 * second column is not printed (-l flag). In this case, the second 643 * string must be NULL. When the second column is NULL, the divider 644 * does not print the trailing space following the divider character. 645 * 646 * Takes into account that tabs can take multiple columns. 647 */ 648 static void 649 println(const char *s1, const char div, const char *s2) 650 { 651 size_t col; 652 653 /* Print first column. Skips if s1 == NULL. */ 654 col = 0; 655 if (s1) { 656 /* Skip angle bracket and space. */ 657 printcol(s1, &col, width); 658 659 } 660 661 /* Otherwise, we pad this column up to width. */ 662 for (; col < width; ++col) 663 putchar(' '); 664 665 /* Only print left column. */ 666 if (div == ' ' && !s2) { 667 printf(" (\n"); 668 return; 669 } 670 671 /* 672 * Print column divider. If there is no second column, we don't 673 * need to add the space for padding. 674 */ 675 if (!s2) { 676 printf(" %c\n", div); 677 return; 678 } 679 printf(" %c ", div); 680 col += 3; 681 682 /* Skip angle bracket and space. */ 683 printcol(s2, &col, line_width); 684 685 putchar('\n'); 686 } 687 688 /* 689 * Reads a line from file and returns as a string. If EOF is reached, 690 * NULL is returned. The returned string must be freed afterwards. 691 */ 692 static char * 693 xfgets(FILE *file) 694 { 695 const char delim[3] = {'\0', '\0', '\0'}; 696 char *s; 697 698 /* XXX - Is this necessary? */ 699 clearerr(file); 700 701 if (!(s = fparseln(file, NULL, NULL, delim, 0)) && 702 ferror(file)) 703 err(2, "error reading file"); 704 705 if (!s) { 706 return (NULL); 707 } 708 709 return (s); 710 } 711 712 /* 713 * Parse ed commands from diffpipe and print lines from file1 (lines 714 * to change or delete) or file2 (lines to add or change). 715 * Returns EOF or 0. 716 */ 717 static int 718 parsecmd(FILE *diffpipe, FILE *file1, FILE *file2) 719 { 720 size_t file1start, file1end, file2start, file2end, n; 721 /* ed command line and pointer to characters in line */ 722 char *line, *p, *q; 723 const char *errstr; 724 char c, cmd; 725 726 /* Read ed command. */ 727 if (!(line = xfgets(diffpipe))) 728 return (EOF); 729 730 p = line; 731 /* Go to character after line number. */ 732 while (isdigit(*p)) 733 ++p; 734 c = *p; 735 *p++ = 0; 736 file1start = strtonum(line, 0, INT_MAX, &errstr); 737 if (errstr) 738 errx(2, "file1 start is %s: %s", errstr, line); 739 740 /* A range is specified for file1. */ 741 if (c == ',') { 742 q = p; 743 /* Go to character after file2end. */ 744 while (isdigit(*p)) 745 ++p; 746 c = *p; 747 *p++ = 0; 748 file1end = strtonum(q, 0, INT_MAX, &errstr); 749 if (errstr) 750 errx(2, "file1 end is %s: %s", errstr, line); 751 if (file1start > file1end) 752 errx(2, "invalid line range in file1: %s", line); 753 } else 754 file1end = file1start; 755 756 cmd = c; 757 /* Check that cmd is valid. */ 758 if (!(cmd == 'a' || cmd == 'c' || cmd == 'd')) 759 errx(2, "ed command not recognized: %c: %s", cmd, line); 760 761 q = p; 762 /* Go to character after line number. */ 763 while (isdigit(*p)) 764 ++p; 765 c = *p; 766 *p++ = 0; 767 file2start = strtonum(q, 0, INT_MAX, &errstr); 768 if (errstr) 769 errx(2, "file2 start is %s: %s", errstr, line); 770 771 /* 772 * There should either be a comma signifying a second line 773 * number or the line should just end here. 774 */ 775 if (c != ',' && c != '\0') 776 errx(2, "invalid line range in file2: %c: %s", c, line); 777 778 if (c == ',') { 779 780 file2end = strtonum(p, 0, INT_MAX, &errstr); 781 if (errstr) 782 errx(2, "file2 end is %s: %s", errstr, line); 783 if (file2start >= file2end) 784 errx(2, "invalid line range in file2: %s", line); 785 } else 786 file2end = file2start; 787 788 /* Appends happen _after_ stated line. */ 789 if (cmd == 'a') { 790 if (file1start != file1end) 791 errx(2, "append cannot have a file1 range: %s", 792 line); 793 if (file1start == SIZE_MAX) 794 errx(2, "file1 line range too high: %s", line); 795 file1start = ++file1end; 796 } 797 /* 798 * I'm not sure what the deal is with the line numbers for 799 * deletes, though. 800 */ 801 else if (cmd == 'd') { 802 if (file2start != file2end) 803 errx(2, "delete cannot have a file2 range: %s", 804 line); 805 if (file2start == SIZE_MAX) 806 errx(2, "file2 line range too high: %s", line); 807 file2start = ++file2end; 808 } 809 810 /* 811 * Continue reading file1 and file2 until we reach line numbers 812 * specified by diff. Should only happen with -I flag. 813 */ 814 for (; file1ln < file1start && file2ln < file2start; 815 ++file1ln, ++file2ln) { 816 char *s1, *s2; 817 818 if (!(s1 = xfgets(file1))) 819 errx(2, "file1 shorter than expected"); 820 if (!(s2 = xfgets(file2))) 821 errx(2, "file2 shorter than expected"); 822 823 /* If the -l flag was specified, print only left column. */ 824 if (lflag) { 825 free(s2); 826 /* 827 * XXX - If -l and -I are both specified, all 828 * unchanged or ignored lines are shown with a 829 * `(' divider. This matches GNU sdiff, but I 830 * believe it is a bug. Just check out: 831 * gsdiff -l -I '^$' samefile samefile. 832 */ 833 if (Iflag) 834 enqueue(s1, '(', NULL); 835 else 836 enqueue(s1, ' ', NULL); 837 } else 838 enqueue(s1, ' ', s2); 839 } 840 /* Ignore deleted lines. */ 841 for (; file1ln < file1start; ++file1ln) { 842 char *s; 843 844 if (!(s = xfgets(file1))) 845 errx(2, "file1 shorter than expected"); 846 847 enqueue(s, '(', NULL); 848 } 849 /* Ignore added lines. */ 850 for (; file2ln < file2start; ++file2ln) { 851 char *s; 852 853 if (!(s = xfgets(file2))) 854 errx(2, "file2 shorter than expected"); 855 856 /* If -l flag was given, don't print right column. */ 857 if (lflag) 858 free(s); 859 else 860 enqueue(NULL, ')', s); 861 } 862 863 /* Process unmodified or skipped lines. */ 864 processq(); 865 866 switch (cmd) { 867 case 'a': 868 printa(file2, file2end); 869 n = file2end - file2start + 1; 870 break; 871 case 'c': 872 printc(file1, file1end, file2, file2end); 873 n = file1end - file1start + 1 + 1 + file2end - file2start + 1; 874 break; 875 case 'd': 876 printd(file1, file1end); 877 n = file1end - file1start + 1; 878 break; 879 default: 880 errx(2, "invalid diff command: %c: %s", cmd, line); 881 } 882 free(line); 883 884 /* Skip to next ed line. */ 885 while (n--) { 886 if (!(line = xfgets(diffpipe))) 887 errx(2, "diff ended early"); 888 free(line); 889 } 890 891 return (0); 892 } 893 894 /* 895 * Queues up a diff line. 896 */ 897 static void 898 enqueue(char *left, char div, char *right) 899 { 900 struct diffline *diffp; 901 902 if (!(diffp = malloc(sizeof(struct diffline)))) 903 err(2, "enqueue"); 904 diffp->left = left; 905 diffp->div = div; 906 diffp->right = right; 907 STAILQ_INSERT_TAIL(&diffhead, diffp, diffentries); 908 } 909 910 /* 911 * Free a diffline structure and its elements. 912 */ 913 static void 914 freediff(struct diffline *diffp) 915 { 916 917 free(diffp->left); 918 free(diffp->right); 919 free(diffp); 920 } 921 922 /* 923 * Append second string into first. Repeated appends to the same string 924 * are cached, making this an O(n) function, where n = strlen(append). 925 */ 926 static void 927 astrcat(char **s, const char *append) 928 { 929 /* Length of string in previous run. */ 930 static size_t offset = 0; 931 size_t newsiz; 932 /* 933 * String from previous run. Compared to *s to see if we are 934 * dealing with the same string. If so, we can use offset. 935 */ 936 static const char *oldstr = NULL; 937 char *newstr; 938 939 /* 940 * First string is NULL, so just copy append. 941 */ 942 if (!*s) { 943 if (!(*s = strdup(append))) 944 err(2, "astrcat"); 945 946 /* Keep track of string. */ 947 offset = strlen(*s); 948 oldstr = *s; 949 950 return; 951 } 952 953 /* 954 * *s is a string so concatenate. 955 */ 956 957 /* Did we process the same string in the last run? */ 958 /* 959 * If this is a different string from the one we just processed 960 * cache new string. 961 */ 962 if (oldstr != *s) { 963 offset = strlen(*s); 964 oldstr = *s; 965 } 966 967 /* Size = strlen(*s) + \n + strlen(append) + '\0'. */ 968 newsiz = offset + 1 + strlen(append) + 1; 969 970 /* Resize *s to fit new string. */ 971 newstr = realloc(*s, newsiz); 972 if (newstr == NULL) 973 err(2, "astrcat"); 974 *s = newstr; 975 976 /* *s + offset should be end of string. */ 977 /* Concatenate. */ 978 strlcpy(*s + offset, "\n", newsiz - offset); 979 strlcat(*s + offset, append, newsiz - offset); 980 981 /* New string length should be exactly newsiz - 1 characters. */ 982 /* Store generated string's values. */ 983 offset = newsiz - 1; 984 oldstr = *s; 985 } 986 987 /* 988 * Process diff set queue, printing, prompting, and saving each diff 989 * line stored in queue. 990 */ 991 static void 992 processq(void) 993 { 994 struct diffline *diffp; 995 char divc, *left, *right; 996 997 /* Don't process empty queue. */ 998 if (STAILQ_EMPTY(&diffhead)) 999 return; 1000 1001 /* Remember the divider. */ 1002 divc = STAILQ_FIRST(&diffhead)->div; 1003 1004 left = NULL; 1005 right = NULL; 1006 /* 1007 * Go through set of diffs, concatenating each line in left or 1008 * right column into two long strings, `left' and `right'. 1009 */ 1010 STAILQ_FOREACH(diffp, &diffhead, diffentries) { 1011 /* 1012 * Print changed lines if -s was given, 1013 * print all lines if -s was not given. 1014 */ 1015 if (!sflag || diffp->div == '|' || diffp->div == '<' || 1016 diffp->div == '>') 1017 println(diffp->left, diffp->div, diffp->right); 1018 1019 /* Append new lines to diff set. */ 1020 if (diffp->left) 1021 astrcat(&left, diffp->left); 1022 if (diffp->right) 1023 astrcat(&right, diffp->right); 1024 } 1025 1026 /* Empty queue and free each diff line and its elements. */ 1027 while (!STAILQ_EMPTY(&diffhead)) { 1028 diffp = STAILQ_FIRST(&diffhead); 1029 STAILQ_REMOVE_HEAD(&diffhead, diffentries); 1030 freediff(diffp); 1031 } 1032 1033 /* Write to outfp, prompting user if lines are different. */ 1034 if (outfp) 1035 switch (divc) { 1036 case ' ': case '(': case ')': 1037 fprintf(outfp, "%s\n", left); 1038 break; 1039 case '|': case '<': case '>': 1040 prompt(left, right); 1041 break; 1042 default: 1043 errx(2, "invalid divider: %c", divc); 1044 } 1045 1046 /* Free left and right. */ 1047 free(left); 1048 free(right); 1049 } 1050 1051 /* 1052 * Print lines following an (a)ppend command. 1053 */ 1054 static void 1055 printa(FILE *file, size_t line2) 1056 { 1057 char *line; 1058 1059 for (; file2ln <= line2; ++file2ln) { 1060 if (!(line = xfgets(file))) 1061 errx(2, "append ended early"); 1062 enqueue(NULL, '>', line); 1063 } 1064 processq(); 1065 } 1066 1067 /* 1068 * Print lines following a (c)hange command, from file1ln to file1end 1069 * and from file2ln to file2end. 1070 */ 1071 static void 1072 printc(FILE *file1, size_t file1end, FILE *file2, size_t file2end) 1073 { 1074 struct fileline { 1075 STAILQ_ENTRY(fileline) fileentries; 1076 char *line; 1077 }; 1078 STAILQ_HEAD(, fileline) delqhead = STAILQ_HEAD_INITIALIZER(delqhead); 1079 1080 /* Read lines to be deleted. */ 1081 for (; file1ln <= file1end; ++file1ln) { 1082 struct fileline *linep; 1083 char *line1; 1084 1085 /* Read lines from both. */ 1086 if (!(line1 = xfgets(file1))) 1087 errx(2, "error reading file1 in delete in change"); 1088 1089 /* Add to delete queue. */ 1090 if (!(linep = malloc(sizeof(struct fileline)))) 1091 err(2, "printc"); 1092 linep->line = line1; 1093 STAILQ_INSERT_TAIL(&delqhead, linep, fileentries); 1094 } 1095 1096 /* Process changed lines.. */ 1097 for (; !STAILQ_EMPTY(&delqhead) && file2ln <= file2end; 1098 ++file2ln) { 1099 struct fileline *del; 1100 char *add; 1101 1102 /* Get add line. */ 1103 if (!(add = xfgets(file2))) 1104 errx(2, "error reading add in change"); 1105 1106 del = STAILQ_FIRST(&delqhead); 1107 enqueue(del->line, '|', add); 1108 STAILQ_REMOVE_HEAD(&delqhead, fileentries); 1109 /* 1110 * Free fileline structure but not its elements since 1111 * they are queued up. 1112 */ 1113 free(del); 1114 } 1115 processq(); 1116 1117 /* Process remaining lines to add. */ 1118 for (; file2ln <= file2end; ++file2ln) { 1119 char *add; 1120 1121 /* Get add line. */ 1122 if (!(add = xfgets(file2))) 1123 errx(2, "error reading add in change"); 1124 1125 enqueue(NULL, '>', add); 1126 } 1127 processq(); 1128 1129 /* Process remaining lines to delete. */ 1130 while (!STAILQ_EMPTY(&delqhead)) { 1131 struct fileline *filep; 1132 1133 filep = STAILQ_FIRST(&delqhead); 1134 enqueue(filep->line, '<', NULL); 1135 STAILQ_REMOVE_HEAD(&delqhead, fileentries); 1136 free(filep); 1137 } 1138 processq(); 1139 } 1140 1141 /* 1142 * Print deleted lines from file, from file1ln to file1end. 1143 */ 1144 static void 1145 printd(FILE *file1, size_t file1end) 1146 { 1147 char *line1; 1148 1149 /* Print out lines file1ln to line2. */ 1150 for (; file1ln <= file1end; ++file1ln) { 1151 if (!(line1 = xfgets(file1))) 1152 errx(2, "file1 ended early in delete"); 1153 enqueue(line1, '<', NULL); 1154 } 1155 processq(); 1156 } 1157 1158 /* 1159 * Interactive mode usage. 1160 */ 1161 static void 1162 int_usage(void) 1163 { 1164 1165 puts("e:\tedit blank diff\n" 1166 "eb:\tedit both diffs concatenated\n" 1167 "el:\tedit left diff\n" 1168 "er:\tedit right diff\n" 1169 "l | 1:\tchoose left diff\n" 1170 "r | 2:\tchoose right diff\n" 1171 "s:\tsilent mode--don't print identical lines\n" 1172 "v:\tverbose mode--print identical lines\n" 1173 "q:\tquit"); 1174 } 1175 1176 static void 1177 usage(void) 1178 { 1179 1180 fprintf(stderr, 1181 "usage: sdiff [-abdilstW] [-I regexp] [-o outfile] [-w width] file1" 1182 " file2\n"); 1183 exit(2); 1184 } 1185