1 /* $OpenBSD: sdiff.c,v 1.32 2015/02/05 12:59:58 millert Exp $ */ 2 3 /* 4 * Written by Raymond Lai <ray@cyth.net>. 5 * Public domain. 6 */ 7 8 #include <sys/queue.h> 9 #include <sys/stat.h> 10 #include <sys/types.h> 11 #include <sys/wait.h> 12 13 #include <ctype.h> 14 #include <err.h> 15 #include <errno.h> 16 #include <fcntl.h> 17 #include <getopt.h> 18 #include <limits.h> 19 #include <paths.h> 20 #include <stdint.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((unsigned char)*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((unsigned char)*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((unsigned char)*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((unsigned char)*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 free(line); 744 745 /* Skip to next ed line. */ 746 while (n--) { 747 if (!(line = xfgets(diffpipe))) 748 errx(2, "diff ended early"); 749 free(line); 750 } 751 752 return (0); 753 } 754 755 /* 756 * Queues up a diff line. 757 */ 758 static void 759 enqueue(char *left, char div, char *right) 760 { 761 struct diffline *diffp; 762 763 if (!(diffp = malloc(sizeof(struct diffline)))) 764 err(2, "enqueue"); 765 diffp->left = left; 766 diffp->div = div; 767 diffp->right = right; 768 SIMPLEQ_INSERT_TAIL(&diffhead, diffp, diffentries); 769 } 770 771 /* 772 * Free a diffline structure and its elements. 773 */ 774 static void 775 freediff(struct diffline *diffp) 776 { 777 free(diffp->left); 778 free(diffp->right); 779 free(diffp); 780 } 781 782 /* 783 * Append second string into first. Repeated appends to the same string 784 * are cached, making this an O(n) function, where n = strlen(append). 785 */ 786 static void 787 astrcat(char **s, const char *append) 788 { 789 /* Length of string in previous run. */ 790 static size_t offset = 0; 791 size_t newsiz; 792 /* 793 * String from previous run. Compared to *s to see if we are 794 * dealing with the same string. If so, we can use offset. 795 */ 796 static const char *oldstr = NULL; 797 char *newstr; 798 799 800 /* 801 * First string is NULL, so just copy append. 802 */ 803 if (!*s) { 804 if (!(*s = strdup(append))) 805 err(2, "astrcat"); 806 807 /* Keep track of string. */ 808 offset = strlen(*s); 809 oldstr = *s; 810 811 return; 812 } 813 814 /* 815 * *s is a string so concatenate. 816 */ 817 818 /* Did we process the same string in the last run? */ 819 /* 820 * If this is a different string from the one we just processed 821 * cache new string. 822 */ 823 if (oldstr != *s) { 824 offset = strlen(*s); 825 oldstr = *s; 826 } 827 828 /* Size = strlen(*s) + \n + strlen(append) + '\0'. */ 829 newsiz = offset + 1 + strlen(append) + 1; 830 831 /* Resize *s to fit new string. */ 832 newstr = realloc(*s, newsiz); 833 if (newstr == NULL) 834 err(2, "astrcat"); 835 *s = newstr; 836 837 /* *s + offset should be end of string. */ 838 /* Concatenate. */ 839 strlcpy(*s + offset, "\n", newsiz - offset); 840 strlcat(*s + offset, append, newsiz - offset); 841 842 /* New string length should be exactly newsiz - 1 characters. */ 843 /* Store generated string's values. */ 844 offset = newsiz - 1; 845 oldstr = *s; 846 } 847 848 /* 849 * Process diff set queue, printing, prompting, and saving each diff 850 * line stored in queue. 851 */ 852 static void 853 processq(void) 854 { 855 struct diffline *diffp; 856 char divc, *left, *right; 857 858 /* Don't process empty queue. */ 859 if (SIMPLEQ_EMPTY(&diffhead)) 860 return; 861 862 /* Remember the divider. */ 863 divc = SIMPLEQ_FIRST(&diffhead)->div; 864 865 left = NULL; 866 right = NULL; 867 /* 868 * Go through set of diffs, concatenating each line in left or 869 * right column into two long strings, `left' and `right'. 870 */ 871 SIMPLEQ_FOREACH(diffp, &diffhead, diffentries) { 872 /* 873 * Print changed lines if -s was given, 874 * print all lines if -s was not given. 875 */ 876 if (!sflag || diffp->div == '|' || diffp->div == '<' || 877 diffp->div == '>') 878 println(diffp->left, diffp->div, diffp->right); 879 880 /* Append new lines to diff set. */ 881 if (diffp->left) 882 astrcat(&left, diffp->left); 883 if (diffp->right) 884 astrcat(&right, diffp->right); 885 } 886 887 /* Empty queue and free each diff line and its elements. */ 888 while (!SIMPLEQ_EMPTY(&diffhead)) { 889 diffp = SIMPLEQ_FIRST(&diffhead); 890 SIMPLEQ_REMOVE_HEAD(&diffhead, diffentries); 891 freediff(diffp); 892 } 893 894 /* Write to outfp, prompting user if lines are different. */ 895 if (outfp) 896 switch (divc) { 897 case ' ': case '(': case ')': 898 fprintf(outfp, "%s\n", left); 899 break; 900 case '|': case '<': case '>': 901 prompt(left, right); 902 break; 903 default: 904 errx(2, "invalid divider: %c", divc); 905 } 906 907 /* Free left and right. */ 908 free(left); 909 free(right); 910 } 911 912 /* 913 * Print lines following an (a)ppend command. 914 */ 915 static void 916 printa(FILE *file, size_t line2) 917 { 918 char *line; 919 920 for (; file2ln <= line2; ++file2ln) { 921 if (!(line = xfgets(file))) 922 errx(2, "append ended early"); 923 enqueue(NULL, '>', line); 924 } 925 926 processq(); 927 } 928 929 /* 930 * Print lines following a (c)hange command, from file1ln to file1end 931 * and from file2ln to file2end. 932 */ 933 static void 934 printc(FILE *file1, size_t file1end, FILE *file2, size_t file2end) 935 { 936 struct fileline { 937 SIMPLEQ_ENTRY(fileline) fileentries; 938 char *line; 939 }; 940 SIMPLEQ_HEAD(, fileline) delqhead = SIMPLEQ_HEAD_INITIALIZER(delqhead); 941 942 /* Read lines to be deleted. */ 943 for (; file1ln <= file1end; ++file1ln) { 944 struct fileline *linep; 945 char *line1; 946 947 /* Read lines from both. */ 948 if (!(line1 = xfgets(file1))) 949 errx(2, "error reading file1 in delete in change"); 950 951 /* Add to delete queue. */ 952 if (!(linep = malloc(sizeof(struct fileline)))) 953 err(2, "printc"); 954 linep->line = line1; 955 SIMPLEQ_INSERT_TAIL(&delqhead, linep, fileentries); 956 } 957 958 /* Process changed lines.. */ 959 for (; !SIMPLEQ_EMPTY(&delqhead) && file2ln <= file2end; 960 ++file2ln) { 961 struct fileline *del; 962 char *add; 963 964 /* Get add line. */ 965 if (!(add = xfgets(file2))) 966 errx(2, "error reading add in change"); 967 968 del = SIMPLEQ_FIRST(&delqhead); 969 enqueue(del->line, '|', add); 970 SIMPLEQ_REMOVE_HEAD(&delqhead, fileentries); 971 /* 972 * Free fileline structure but not its elements since 973 * they are queued up. 974 */ 975 free(del); 976 } 977 processq(); 978 979 /* Process remaining lines to add. */ 980 for (; file2ln <= file2end; ++file2ln) { 981 char *add; 982 983 /* Get add line. */ 984 if (!(add = xfgets(file2))) 985 errx(2, "error reading add in change"); 986 987 enqueue(NULL, '>', add); 988 } 989 processq(); 990 991 /* Process remaining lines to delete. */ 992 while (!SIMPLEQ_EMPTY(&delqhead)) { 993 struct fileline *filep; 994 995 filep = SIMPLEQ_FIRST(&delqhead); 996 enqueue(filep->line, '<', NULL); 997 SIMPLEQ_REMOVE_HEAD(&delqhead, fileentries); 998 free(filep); 999 } 1000 processq(); 1001 } 1002 1003 /* 1004 * Print deleted lines from file, from file1ln to file1end. 1005 */ 1006 static void 1007 printd(FILE *file1, size_t file1end) 1008 { 1009 char *line1; 1010 1011 /* Print out lines file1ln to line2. */ 1012 for (; file1ln <= file1end; ++file1ln) { 1013 if (!(line1 = xfgets(file1))) 1014 errx(2, "file1 ended early in delete"); 1015 enqueue(line1, '<', NULL); 1016 } 1017 processq(); 1018 } 1019 1020 /* 1021 * Interactive mode usage. 1022 */ 1023 static void 1024 int_usage(void) 1025 { 1026 puts("e:\tedit blank diff\n" 1027 "eb:\tedit both diffs concatenated\n" 1028 "el:\tedit left diff\n" 1029 "er:\tedit right diff\n" 1030 "l | 1:\tchoose left diff\n" 1031 "r | 2:\tchoose right diff\n" 1032 "s:\tsilent mode--don't print identical lines\n" 1033 "v:\tverbose mode--print identical lines\n" 1034 "q:\tquit"); 1035 } 1036 1037 static void 1038 usage(void) 1039 { 1040 extern char *__progname; 1041 1042 fprintf(stderr, 1043 "usage: %s [-abdilstW] [-I regexp] [-o outfile] [-w width] file1 file2\n", 1044 __progname); 1045 exit(2); 1046 } 1047