Annotation of src/usr.bin/sdiff/sdiff.c, Revision 1.35
1.35 ! deraadt 1: /* $OpenBSD: sdiff.c,v 1.34 2015/10/15 23:06:46 deraadt 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: }