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