[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.33

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