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