xref: /openbsd-src/usr.bin/sdiff/sdiff.c (revision daf88648c0e349d5c02e1504293082072c981640)
1 /*	$OpenBSD: sdiff.c,v 1.20 2006/09/19 05:52:23 otto Exp $ */
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/stat.h>
11 #include <sys/types.h>
12 #include <sys/wait.h>
13 
14 #include <ctype.h>
15 #include <err.h>
16 #include <errno.h>
17 #include <fcntl.h>
18 #include <getopt.h>
19 #include <limits.h>
20 #include <paths.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <unistd.h>
25 #include <util.h>
26 
27 #include "common.h"
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;
40 	char	*left;
41 	char	 div;
42 	char	*right;
43 };
44 
45 static void astrcat(char **, const char *);
46 static void enqueue(char *, char, char *);
47 static char *mktmpcpy(const char *);
48 static void freediff(struct diffline *);
49 static void int_usage(void);
50 static int parsecmd(FILE *, FILE *, FILE *);
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);
54 static void printd(FILE *, size_t);
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 */
65 int	 Iflag = 0;	/* ignore sets matching regexp */
66 int	 lflag;		/* print only left column for identical lines */
67 int	 sflag;		/* skip identical lines */
68 FILE	*outfile;	/* file to save changes to */
69 const char *tmpdir;	/* TMPDIR or /tmp */
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 
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. */
111 		if (S_ISREG(sb.st_mode))
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. */
122 	if (asprintf(&target_file, "%s/sdiff.XXXXXXXXXX", tmpdir) == -1)
123 		err(2, "asprintf");
124 	if ((ofd = mkstemp(target_file)) == -1) {
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 
153 int
154 main(int argc, char **argv)
155 {
156 	FILE *diffpipe, *file1, *file2;
157 	size_t diffargc = 0, wflag = WIDTH;
158 	int ch, fd[2], status;
159 	pid_t pid;
160 	char **diffargv, *diffprog = "diff", *filename1, *filename2,
161 	    *tmp1, *tmp2, *s1, *s2;
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 	 */
174 	if (!(diffargv = malloc(sizeof(char **) * argc * 2)))
175 		err(2, "main");
176 
177 	/* Add first argument, the program name. */
178 	diffargv[diffargc++] = diffprog;
179 
180 	while ((ch = getopt_long(argc, argv, "aBbdEHI:ilo:stWw:",
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':
201 			diffargv[0] = diffprog = optarg;
202 			break;
203 		case 'H':
204 			diffargv[diffargc++] = "-H";
205 			break;
206 		case 'I':
207 			Iflag = 1;
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,
235 			    INT_MAX, &errstr);
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 
247 	if (argc != 2)
248 		usage();
249 
250 	if ((tmpdir = getenv("TMPDIR")) == NULL)
251 		tmpdir = _PATH_TMP;
252 
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;
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. */
296 		close(fd[0]);
297 		if (dup2(fd[1], STDOUT_FILENO) == -1)
298 			err(2, "child could not duplicate descriptor");
299 		/* Free unused descriptor. */
300 		close(fd[1]);
301 
302 		execvp(diffprog, diffargv);
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. */
310 	close(fd[1]);
311 
312 	/* Open pipe to diff command. */
313 	if ((diffpipe = fdopen(fd[0], "r")) == NULL)
314 		err(2, "could not open diff pipe");
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 
320 	/* Line numbers start at one. */
321 	file1ln = file2ln = 1;
322 
323 	/* Read and parse diff output. */
324 	while (parsecmd(diffpipe, file1, file2) != EOF)
325 		;
326 	fclose(diffpipe);
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");
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;
343 
344 	/* No more diffs, so print common lines. */
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);
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
369  * the cursor is on the screen, and the maximum value of the column.
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 {
415 	char *cmd;
416 
417 	/* Print command prompt. */
418 	putchar('%');
419 
420 	/* Get user input. */
421 	for (; (cmd = xfgets(stdin)); free(cmd)) {
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 
475 		free(cmd);
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:
484 	fclose(outfile);
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 /*
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.
565  */
566 static int
567 parsecmd(FILE *diffpipe, FILE *file1, FILE *file2)
568 {
569 	size_t file1start, file1end, file2start, file2end, n;
570 	/* ed command line and pointer to characters in line */
571 	char *line, *p, *q;
572 	const char *errstr;
573 	char c, cmd;
574 
575 	/* Read ed command. */
576 	if (!(line = xfgets(diffpipe)))
577 		return (EOF);
578 
579 	p = line;
580 	/* Go to character after line number. */
581 	while (isdigit(*p))
582 		++p;
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);
588 
589 	/* A range is specified for file1. */
590 	if (c == ',') {
591 
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);
601 		if (file1start > file1end)
602 			errx(2, "invalid line range in file1: %s", line);
603 
604 	} else
605 		file1end = file1start;
606 
607 	cmd = c;
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 
612 	q = p;
613 	/* Go to character after line number. */
614 	while (isdigit(*p))
615 		++p;
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);
621 
622 	/*
623 	 * There should either be a comma signifying a second line
624 	 * number or the line should just end here.
625 	 */
626 	if (c != ',' && c != '\0')
627 		errx(2, "invalid line range in file2: %c: %s", c, line);
628 
629 	if (c == ',') {
630 
631 		file2end = strtonum(p, 0, INT_MAX, &errstr);
632 		if (errstr)
633 			errx(2, "file2 end is %s: %s", errstr, line);
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 
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) {
667 		char *s1, *s2;
668 
669 		if (!(s1 = xfgets(file1)))
670 			errx(2, "file1 shorter than expected");
671 		if (!(s2 = xfgets(file2)))
672 			errx(2, "file2 shorter than expected");
673 
674 		/* If the -l flag was specified, print only left column. */
675 		if (lflag) {
676 			free(s2);
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) {
693 		char *s;
694 
695 		if (!(s = xfgets(file1)))
696 			errx(2, "file1 shorter than expected");
697 
698 		enqueue(s, '(', NULL);
699 	}
700 	/* Ignore added lines. */
701 	for (; file2ln < file2start; ++file2ln) {
702 		char *s;
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)
709 			free(s);
710 		else
711 			enqueue(NULL, ')', s);
712 	}
713 
714 	/* Process unmodified or skipped lines. */
715 	processq();
716 
717 	switch (cmd) {
718 	case 'a':
719 		printa(file2, file2end);
720 		n = file2end - file2start + 1;
721 		break;
722 
723 	case 'c':
724 		printc(file1, file1end, file2, file2end);
725 		n = file1end - file1start + 1 + 1 + file2end - file2start + 1;
726 		break;
727 
728 	case 'd':
729 		printd(file1, file1end);
730 		n = file1end - file1start + 1;
731 		break;
732 
733 	default:
734 		errx(2, "invalid diff command: %c: %s", cmd, line);
735 	}
736 
737 	/* Skip to next ed line. */
738 	while (n--)
739 		if (!xfgets(diffpipe))
740 			errx(2, "diff ended early");
741 
742 	return (0);
743 }
744 
745 /*
746  * Queues up a diff line.
747  */
748 static void
749 enqueue(char *left, char div, char *right)
750 {
751 	struct diffline *diffp;
752 
753 	if (!(diffp = malloc(sizeof(struct diffline))))
754 		err(2, "enqueue");
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
765 freediff(struct diffline *diffp)
766 {
767 	free(diffp->left);
768 	free(diffp->right);
769 	free(diffp);
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;
781 	size_t newsiz;
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 	 */
786 	static const char *oldstr = NULL;
787 	char *newstr;
788 
789 
790 	/*
791 	 * First string is NULL, so just copy append.
792 	 */
793 	if (!*s) {
794 		if (!(*s = strdup(append)))
795 			err(2, "astrcat");
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 
818 	/* Size = strlen(*s) + \n + strlen(append) + '\0'. */
819 	newsiz = offset + 1 + strlen(append) + 1;
820 
821 	/* Resize *s to fit new string. */
822 	newstr = realloc(*s, newsiz);
823 	if (newstr == NULL)
824 		err(2, "astrcat");
825 	*s = newstr;
826 
827 	/* *s + offset should be end of string. */
828 	/* Concatenate. */
829 	strlcpy(*s + offset, "\n", newsiz - offset);
830 	strlcat(*s + offset, append, newsiz - offset);
831 
832 	/* New string length should be exactly newsiz - 1 characters. */
833 	/* Store generated string's values. */
834 	offset = newsiz - 1;
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;
846 	char divc, *left, *right;
847 
848 	/* Don't process empty queue. */
849 	if (SIMPLEQ_EMPTY(&diffhead))
850 		return;
851 
852 	/* Remember the divider. */
853 	divc = SIMPLEQ_FIRST(&diffhead)->div;
854 
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 		/*
863 		 * Print changed lines if -s was given,
864 		 * print all lines if -s was not given.
865 		 */
866 		if (!sflag || diffp->div == '|' || diffp->div == '<' ||
867 		    diffp->div == '>')
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);
880 		SIMPLEQ_REMOVE_HEAD(&diffhead, diffentries);
881 		freediff(diffp);
882 	}
883 
884 	/* Write to outfile, prompting user if lines are different. */
885 	if (outfile)
886 		switch (divc) {
887 		case ' ': case '(': case ')':
888 			fprintf(outfile, "%s\n", left);
889 			break;
890 		case '|': case '<': case '>':
891 			prompt(left, right);
892 			break;
893 		default:
894 			errx(2, "invalid divider: %c", divc);
895 		}
896 
897 	/* Free left and right. */
898 	free(left);
899 	free(right);
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 {
927 		SIMPLEQ_ENTRY(fileline)	 fileentries;
928 		char			*line;
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;
935 		char *line1;
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))))
943 			err(2, "printc");
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. */
955 		if (!(add = xfgets(file2)))
956 			errx(2, "error reading add in change");
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. */
974 		if (!(add = xfgets(file2)))
975 			errx(2, "error reading add in change");
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
997 printd(FILE *file1, size_t file1end)
998 {
999 	char *line1;
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,
1034 	    "usage: %s [-abdilstW] [-I regexp] [-o outfile] [-w width] file1 file2\n",
1035 	    __progname);
1036 	exit(2);
1037 }
1038