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