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