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