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