[BACK]Return to sdiff.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / sdiff

Annotation of src/usr.bin/sdiff/sdiff.c, Revision 1.14

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