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