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