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