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