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