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