xref: /openbsd-src/usr.bin/sdiff/sdiff.c (revision b7041c0781c8668129da8084451ded41b0c43954)
1*b7041c07Sderaadt /*	$OpenBSD: sdiff.c,v 1.39 2021/10/24 21:24:17 deraadt Exp $ */
2228e906dStedu 
3228e906dStedu /*
4228e906dStedu  * Written by Raymond Lai <ray@cyth.net>.
5228e906dStedu  * Public domain.
6228e906dStedu  */
7228e906dStedu 
8228e906dStedu #include <sys/queue.h>
91e22bbb6Sotto #include <sys/stat.h>
10228e906dStedu #include <sys/types.h>
11228e906dStedu #include <sys/wait.h>
12228e906dStedu 
13228e906dStedu #include <ctype.h>
14228e906dStedu #include <err.h>
151e22bbb6Sotto #include <errno.h>
161e22bbb6Sotto #include <fcntl.h>
17228e906dStedu #include <getopt.h>
18228e906dStedu #include <limits.h>
1931342e88Sray #include <paths.h>
201357284aSmillert #include <stdint.h>
21228e906dStedu #include <stdio.h>
22228e906dStedu #include <stdlib.h>
23228e906dStedu #include <string.h>
24228e906dStedu #include <unistd.h>
25228e906dStedu #include <util.h>
26228e906dStedu 
271e22bbb6Sotto #include "common.h"
28228e906dStedu #include "extern.h"
29228e906dStedu 
30228e906dStedu #define WIDTH 130
31228e906dStedu /*
32228e906dStedu  * Each column must be at least one character wide, plus three
33228e906dStedu  * characters between the columns (space, [<|>], space).
34228e906dStedu  */
35228e906dStedu #define WIDTH_MIN 5
36228e906dStedu 
37228e906dStedu /* A single diff line. */
38228e906dStedu struct diffline {
39228e906dStedu 	SIMPLEQ_ENTRY(diffline) diffentries;
40951d632eSotto 	char	*left;
41228e906dStedu 	char	 div;
42951d632eSotto 	char	*right;
43228e906dStedu };
44228e906dStedu 
45228e906dStedu static void astrcat(char **, const char *);
46951d632eSotto static void enqueue(char *, char, char *);
471e22bbb6Sotto static char *mktmpcpy(const char *);
48951d632eSotto static void freediff(struct diffline *);
49228e906dStedu static void int_usage(void);
5049e0b549Sotto static int parsecmd(FILE *, FILE *, FILE *);
51228e906dStedu static void printa(FILE *, size_t);
52228e906dStedu static void printc(FILE *, size_t, FILE *, size_t);
53228e906dStedu static void printcol(const char *, size_t *, const size_t);
5449e0b549Sotto static void printd(FILE *, size_t);
55228e906dStedu static void println(const char *, const char, const char *);
56228e906dStedu static void processq(void);
57228e906dStedu static void prompt(const char *, const char *);
58228e906dStedu __dead static void usage(void);
59228e906dStedu static char *xfgets(FILE *);
60228e906dStedu 
61228e906dStedu SIMPLEQ_HEAD(, diffline) diffhead = SIMPLEQ_HEAD_INITIALIZER(diffhead);
62228e906dStedu size_t	 line_width;	/* width of a line (two columns and divider) */
63228e906dStedu size_t	 width;		/* width of each column */
64228e906dStedu size_t	 file1ln, file2ln;	/* line number of file1 and file2 */
6549e0b549Sotto int	 Iflag = 0;	/* ignore sets matching regexp */
66228e906dStedu int	 lflag;		/* print only left column for identical lines */
67228e906dStedu int	 sflag;		/* skip identical lines */
68d4ba4722Sray FILE	*outfp;		/* file to save changes to */
6931342e88Sray const char *tmpdir;	/* TMPDIR or /tmp */
70228e906dStedu 
71228e906dStedu static struct option longopts[] = {
72228e906dStedu 	{ "text",			no_argument,		NULL,	'a' },
73228e906dStedu 	{ "ignore-blank-lines",		no_argument,		NULL,	'B' },
74228e906dStedu 	{ "ignore-space-change",	no_argument,		NULL,	'b' },
75228e906dStedu 	{ "minimal",			no_argument,		NULL,	'd' },
76228e906dStedu 	{ "ignore-tab-expansion",	no_argument,		NULL,	'E' },
77228e906dStedu 	{ "diff-program",		required_argument,	NULL,	'F' },
78228e906dStedu 	{ "speed-large-files",		no_argument,		NULL,	'H' },
79228e906dStedu 	{ "ignore-matching-lines",	required_argument,	NULL,	'I' },
80ece621c6Sray 	{ "ignore-case",		no_argument,		NULL,	'i' },
81228e906dStedu 	{ "left-column",		no_argument,		NULL,	'l' },
82228e906dStedu 	{ "output",			required_argument,	NULL,	'o' },
83228e906dStedu 	{ "strip-trailing-cr",		no_argument,		NULL,	'S' },
84228e906dStedu 	{ "suppress-common-lines",	no_argument,		NULL,	's' },
85228e906dStedu 	{ "expand-tabs",		no_argument,		NULL,	't' },
86228e906dStedu 	{ "ignore-all-space",		no_argument,		NULL,	'W' },
87228e906dStedu 	{ "width",			required_argument,	NULL,	'w' },
88228e906dStedu 	{ NULL,				0,			NULL,	 0  }
89228e906dStedu };
90228e906dStedu 
911e22bbb6Sotto /*
921e22bbb6Sotto  * Create temporary file if source_file is not a regular file.
931e22bbb6Sotto  * Returns temporary file name if one was malloced, NULL if unnecessary.
941e22bbb6Sotto  */
951e22bbb6Sotto static char *
mktmpcpy(const char * source_file)961e22bbb6Sotto mktmpcpy(const char *source_file)
971e22bbb6Sotto {
981e22bbb6Sotto 	struct stat sb;
991e22bbb6Sotto 	ssize_t rcount;
1001e22bbb6Sotto 	int ifd, ofd;
1011e22bbb6Sotto 	u_char buf[BUFSIZ];
1021e22bbb6Sotto 	char *target_file;
1031e22bbb6Sotto 
1041e22bbb6Sotto 	/* Open input and output. */
105*b7041c07Sderaadt 	ifd = open(source_file, O_RDONLY);
1061e22bbb6Sotto 	/* File was opened successfully. */
1071e22bbb6Sotto 	if (ifd != -1) {
1081e22bbb6Sotto 		if (fstat(ifd, &sb) == -1)
1091e22bbb6Sotto 			err(2, "error getting file status from %s", source_file);
1101e22bbb6Sotto 
1111e22bbb6Sotto 		/* Regular file. */
112675d97c0Sray 		if (S_ISREG(sb.st_mode)) {
113675d97c0Sray 			close(ifd);
1141e22bbb6Sotto 			return (NULL);
115675d97c0Sray 		}
1161e22bbb6Sotto 	} else {
1171e22bbb6Sotto 		/* If ``-'' does not exist the user meant stdin. */
1181e22bbb6Sotto 		if (errno == ENOENT && strcmp(source_file, "-") == 0)
1191e22bbb6Sotto 			ifd = STDIN_FILENO;
1201e22bbb6Sotto 		else
1211e22bbb6Sotto 			err(2, "error opening %s", source_file);
1221e22bbb6Sotto 	}
1231e22bbb6Sotto 
1241e22bbb6Sotto 	/* Not a regular file, so copy input into temporary file. */
12531342e88Sray 	if (asprintf(&target_file, "%s/sdiff.XXXXXXXXXX", tmpdir) == -1)
12631342e88Sray 		err(2, "asprintf");
12731342e88Sray 	if ((ofd = mkstemp(target_file)) == -1) {
1281e22bbb6Sotto 		warn("error opening %s", target_file);
1291e22bbb6Sotto 		goto FAIL;
1301e22bbb6Sotto 	}
1311e22bbb6Sotto 	while ((rcount = read(ifd, buf, sizeof(buf))) != -1 &&
1321e22bbb6Sotto 	    rcount != 0) {
1331e22bbb6Sotto 		ssize_t wcount;
1341e22bbb6Sotto 
1351e22bbb6Sotto 		wcount = write(ofd, buf, (size_t)rcount);
1361e22bbb6Sotto 		if (-1 == wcount || rcount != wcount) {
1371e22bbb6Sotto 			warn("error writing to %s", target_file);
1381e22bbb6Sotto 			goto FAIL;
1391e22bbb6Sotto 		}
1401e22bbb6Sotto 	}
1411e22bbb6Sotto 	if (rcount == -1) {
1421e22bbb6Sotto 		warn("error reading from %s", source_file);
1431e22bbb6Sotto 		goto FAIL;
1441e22bbb6Sotto 	}
1451e22bbb6Sotto 
1461e22bbb6Sotto 	close(ifd);
1471e22bbb6Sotto 	close(ofd);
1481e22bbb6Sotto 
1491e22bbb6Sotto 	return (target_file);
1501e22bbb6Sotto 
1511e22bbb6Sotto FAIL:
1521e22bbb6Sotto 	unlink(target_file);
1531e22bbb6Sotto 	exit(2);
1541e22bbb6Sotto }
1551e22bbb6Sotto 
156228e906dStedu int
main(int argc,char ** argv)157228e906dStedu main(int argc, char **argv)
158228e906dStedu {
15949e0b549Sotto 	FILE *diffpipe, *file1, *file2;
1606bccf554Sderaadt 	size_t diffargc = 0, wflag = WIDTH;
161228e906dStedu 	int ch, fd[2], status;
162228e906dStedu 	pid_t pid;
163d4ba4722Sray 	const char *outfile = NULL;
1641e22bbb6Sotto 	char **diffargv, *diffprog = "diff", *filename1, *filename2,
1651e22bbb6Sotto 	    *tmp1, *tmp2, *s1, *s2;
16632a0d318Smestre 	unsigned int Fflag = 0;
16762dc4a9eSderaadt 
168228e906dStedu 	/*
169228e906dStedu 	 * Process diff flags.
170228e906dStedu 	 */
171228e906dStedu 	/*
172228e906dStedu 	 * Allocate memory for diff arguments and NULL.
173228e906dStedu 	 * Each flag has at most one argument, so doubling argc gives an
174228e906dStedu 	 * upper limit of how many diff args can be passed.  argv[0],
175228e906dStedu 	 * file1, and file2 won't have arguments so doubling them will
176228e906dStedu 	 * waste some memory; however we need an extra space for the
177228e906dStedu 	 * NULL at the end, so it sort of works out.
178228e906dStedu 	 */
1791ed98fdfSderaadt 	if (!(diffargv = calloc(argc, sizeof(char **) * 2)))
180a9ba38d6Stedu 		err(2, "main");
181228e906dStedu 
182228e906dStedu 	/* Add first argument, the program name. */
183228e906dStedu 	diffargv[diffargc++] = diffprog;
184228e906dStedu 
185618b0722Stedu 	while ((ch = getopt_long(argc, argv, "aBbdEHI:ilo:stWw:",
186228e906dStedu 	    longopts, NULL)) != -1) {
187228e906dStedu 		const char *errstr;
188228e906dStedu 
189228e906dStedu 		switch (ch) {
190228e906dStedu 		case 'a':
191228e906dStedu 			diffargv[diffargc++] = "-a";
192228e906dStedu 			break;
193228e906dStedu 		case 'B':
194228e906dStedu 			diffargv[diffargc++] = "-B";
195228e906dStedu 			break;
196228e906dStedu 		case 'b':
197228e906dStedu 			diffargv[diffargc++] = "-b";
198228e906dStedu 			break;
199228e906dStedu 		case 'd':
200228e906dStedu 			diffargv[diffargc++] = "-d";
201228e906dStedu 			break;
202228e906dStedu 		case 'E':
203228e906dStedu 			diffargv[diffargc++] = "-E";
204228e906dStedu 			break;
205228e906dStedu 		case 'F':
20646d91ad5Stedu 			diffargv[0] = diffprog = optarg;
20732a0d318Smestre 			Fflag = 1;
208228e906dStedu 			break;
209228e906dStedu 		case 'H':
210228e906dStedu 			diffargv[diffargc++] = "-H";
211228e906dStedu 			break;
212228e906dStedu 		case 'I':
21349e0b549Sotto 			Iflag = 1;
214228e906dStedu 			diffargv[diffargc++] = "-I";
215228e906dStedu 			diffargv[diffargc++] = optarg;
216228e906dStedu 			break;
217228e906dStedu 		case 'i':
218228e906dStedu 			diffargv[diffargc++] = "-i";
219228e906dStedu 			break;
220228e906dStedu 		case 'l':
221228e906dStedu 			lflag = 1;
222228e906dStedu 			break;
223228e906dStedu 		case 'o':
224d4ba4722Sray 			outfile = optarg;
225228e906dStedu 			break;
226228e906dStedu 		case 'S':
227228e906dStedu 			diffargv[diffargc++] = "--strip-trailing-cr";
228228e906dStedu 			break;
229228e906dStedu 		case 's':
230228e906dStedu 			sflag = 1;
231228e906dStedu 			break;
232228e906dStedu 		case 't':
233228e906dStedu 			diffargv[diffargc++] = "-t";
234228e906dStedu 			break;
235228e906dStedu 		case 'W':
236228e906dStedu 			diffargv[diffargc++] = "-w";
237228e906dStedu 			break;
238228e906dStedu 		case 'w':
239228e906dStedu 			wflag = strtonum(optarg, WIDTH_MIN,
240dc7cd303Stedu 			    INT_MAX, &errstr);
241228e906dStedu 			if (errstr)
242228e906dStedu 				errx(2, "width is %s: %s", errstr, optarg);
243228e906dStedu 			break;
244228e906dStedu 		default:
245228e906dStedu 			usage();
246228e906dStedu 		}
247228e906dStedu 
248228e906dStedu 	}
249228e906dStedu 	argc -= optind;
250228e906dStedu 	argv += optind;
251228e906dStedu 
2527d4ecb0aSray 	if (argc != 2)
25346d91ad5Stedu 		usage();
25446d91ad5Stedu 
255d4ba4722Sray 	if (outfile && (outfp = fopen(outfile, "w")) == NULL)
256d4ba4722Sray 		err(2, "could not open: %s", optarg);
257d4ba4722Sray 
258127c5611Sray 	if ((tmpdir = getenv("TMPDIR")) == NULL || *tmpdir == '\0')
25931342e88Sray 		tmpdir = _PATH_TMP;
26031342e88Sray 
2611e22bbb6Sotto 	filename1 = argv[0];
2621e22bbb6Sotto 	filename2 = argv[1];
2631e22bbb6Sotto 
26432a0d318Smestre 	if (!Fflag) {
26532a0d318Smestre 		if (unveil(filename1, "r") == -1)
266bc5a8259Sbeck 			err(2, "unveil %s", filename1);
26732a0d318Smestre 		if (unveil(filename2, "r") == -1)
268bc5a8259Sbeck 			err(2, "unveil %s", filename2);
26932a0d318Smestre 		if (unveil(tmpdir, "rwc") == -1)
270bc5a8259Sbeck 			err(2, "unveil %s", tmpdir);
27132a0d318Smestre 		if (unveil("/usr/bin/diff", "x") == -1)
272bc5a8259Sbeck 			err(2, "unveil /usr/bin/diff");
27332a0d318Smestre 		if (unveil(_PATH_BSHELL, "x") == -1)
274bc5a8259Sbeck 			err(2, "unveil %s", _PATH_BSHELL);
27532a0d318Smestre 	}
27632a0d318Smestre 	if (pledge("stdio rpath wpath cpath proc exec", NULL) == -1)
27732a0d318Smestre 		err(2, "pledge");
27832a0d318Smestre 
2791e22bbb6Sotto 	/*
2801e22bbb6Sotto 	 * Create temporary files for diff and sdiff to share if file1
2811e22bbb6Sotto 	 * or file2 are not regular files.  This allows sdiff and diff
2821e22bbb6Sotto 	 * to read the same inputs if one or both inputs are stdin.
2831e22bbb6Sotto 	 *
2841e22bbb6Sotto 	 * If any temporary files were created, their names would be
2851e22bbb6Sotto 	 * saved in tmp1 or tmp2.  tmp1 should never equal tmp2.
2861e22bbb6Sotto 	 */
2871e22bbb6Sotto 	tmp1 = tmp2 = NULL;
2881e22bbb6Sotto 	/* file1 and file2 are the same, so copy to same temp file. */
2891e22bbb6Sotto 	if (strcmp(filename1, filename2) == 0) {
2901e22bbb6Sotto 		if ((tmp1 = mktmpcpy(filename1)))
2911e22bbb6Sotto 			filename1 = filename2 = tmp1;
2921e22bbb6Sotto 	/* Copy file1 and file2 into separate temp files. */
2931e22bbb6Sotto 	} else {
2941e22bbb6Sotto 		if ((tmp1 = mktmpcpy(filename1)))
2951e22bbb6Sotto 			filename1 = tmp1;
2961e22bbb6Sotto 		if ((tmp2 = mktmpcpy(filename2)))
2971e22bbb6Sotto 			filename2 = tmp2;
2981e22bbb6Sotto 	}
2991e22bbb6Sotto 
3001e22bbb6Sotto 	diffargv[diffargc++] = filename1;
3011e22bbb6Sotto 	diffargv[diffargc++] = filename2;
302228e906dStedu 	/* Add NULL to end of array to indicate end of array. */
303228e906dStedu 	diffargv[diffargc++] = NULL;
304228e906dStedu 
305228e906dStedu 	/* Subtract column divider and divide by two. */
306228e906dStedu 	width = (wflag - 3) / 2;
307228e906dStedu 	/* Make sure line_width can fit in size_t. */
30899c13856Sray 	if (width > (SIZE_MAX - 3) / 2)
309228e906dStedu 		errx(2, "width is too large: %zu", width);
310228e906dStedu 	line_width = width * 2 + 3;
311228e906dStedu 
312228e906dStedu 	if (pipe(fd))
313228e906dStedu 		err(2, "pipe");
314228e906dStedu 
315228e906dStedu 	switch(pid = fork()) {
316228e906dStedu 	case 0:
317228e906dStedu 		/* child */
318228e906dStedu 		/* We don't read from the pipe. */
319df0f73baStedu 		close(fd[0]);
320228e906dStedu 		if (dup2(fd[1], STDOUT_FILENO) == -1)
321228e906dStedu 			err(2, "child could not duplicate descriptor");
322228e906dStedu 		/* Free unused descriptor. */
323df0f73baStedu 		close(fd[1]);
324228e906dStedu 
325951d632eSotto 		execvp(diffprog, diffargv);
326228e906dStedu 		err(2, "could not execute diff: %s", diffprog);
327228e906dStedu 	case -1:
328228e906dStedu 		err(2, "could not fork");
329228e906dStedu 	}
330228e906dStedu 
331228e906dStedu 	/* parent */
332228e906dStedu 	/* We don't write to the pipe. */
333df0f73baStedu 	close(fd[1]);
334228e906dStedu 
335228e906dStedu 	/* Open pipe to diff command. */
33649e0b549Sotto 	if ((diffpipe = fdopen(fd[0], "r")) == NULL)
337228e906dStedu 		err(2, "could not open diff pipe");
3381e22bbb6Sotto 	if ((file1 = fopen(filename1, "r")) == NULL)
3391e22bbb6Sotto 		err(2, "could not open %s", filename1);
3401e22bbb6Sotto 	if ((file2 = fopen(filename2, "r")) == NULL)
3411e22bbb6Sotto 		err(2, "could not open %s", filename2);
3421e22bbb6Sotto 
343228e906dStedu 	/* Line numbers start at one. */
344228e906dStedu 	file1ln = file2ln = 1;
345228e906dStedu 
346228e906dStedu 	/* Read and parse diff output. */
34749e0b549Sotto 	while (parsecmd(diffpipe, file1, file2) != EOF)
348228e906dStedu 		;
34949e0b549Sotto 	fclose(diffpipe);
350228e906dStedu 
351228e906dStedu 	/* Wait for diff to exit. */
352228e906dStedu 	if (waitpid(pid, &status, 0) == -1 || !WIFEXITED(status) ||
353228e906dStedu 	    WEXITSTATUS(status) >= 2)
354228e906dStedu 		err(2, "diff exited abnormally");
355228e906dStedu 
3561e22bbb6Sotto 	/* Delete and free unneeded temporary files. */
3571e22bbb6Sotto 	if (tmp1)
3581e22bbb6Sotto 		if (unlink(tmp1))
3591e22bbb6Sotto 			warn("error deleting %s", tmp1);
3601e22bbb6Sotto 	if (tmp2)
3611e22bbb6Sotto 		if (unlink(tmp2))
3621e22bbb6Sotto 			warn("error deleting %s", tmp2);
3631e22bbb6Sotto 	free(tmp1);
3641e22bbb6Sotto 	free(tmp2);
3651e22bbb6Sotto 	filename1 = filename2 = tmp1 = tmp2 = NULL;
3661e22bbb6Sotto 
367228e906dStedu 	/* No more diffs, so print common lines. */
36849e0b549Sotto 	if (lflag)
36949e0b549Sotto 		while ((s1 = xfgets(file1)))
37049e0b549Sotto 			enqueue(s1, ' ', NULL);
37149e0b549Sotto 	else
37249e0b549Sotto 		for (;;) {
37349e0b549Sotto 			s1 = xfgets(file1);
37449e0b549Sotto 			s2 = xfgets(file2);
37549e0b549Sotto 			if (s1 || s2)
37649e0b549Sotto 				enqueue(s1, ' ', s2);
37749e0b549Sotto 			else
37849e0b549Sotto 				break;
37949e0b549Sotto 		}
38049e0b549Sotto 	fclose(file1);
38149e0b549Sotto 	fclose(file2);
382228e906dStedu 	/* Process unmodified lines. */
383228e906dStedu 	processq();
384228e906dStedu 
385228e906dStedu 	/* Return diff exit status. */
386228e906dStedu 	return (WEXITSTATUS(status));
387228e906dStedu }
388228e906dStedu 
389228e906dStedu /*
390228e906dStedu  * Prints an individual column (left or right), taking into account
391228e906dStedu  * that tabs are variable-width.  Takes a string, the current column
392228e906dStedu  * the cursor is on the screen, and the maximum value of the column.
393228e906dStedu  * The column value is updated as we go along.
394228e906dStedu  */
395228e906dStedu static void
printcol(const char * s,size_t * col,const size_t col_max)396228e906dStedu printcol(const char *s, size_t *col, const size_t col_max)
397228e906dStedu {
398228e906dStedu 	for (; *s && *col < col_max; ++s) {
399228e906dStedu 		size_t new_col;
400228e906dStedu 
401228e906dStedu 		switch (*s) {
402228e906dStedu 		case '\t':
403228e906dStedu 			/*
404228e906dStedu 			 * If rounding to next multiple of eight causes
405228e906dStedu 			 * an integer overflow, just return.
406228e906dStedu 			 */
40799c13856Sray 			if (*col > SIZE_MAX - 8)
408228e906dStedu 				return;
409228e906dStedu 
410228e906dStedu 			/* Round to next multiple of eight. */
411228e906dStedu 			new_col = (*col / 8 + 1) * 8;
412228e906dStedu 
413228e906dStedu 			/*
414228e906dStedu 			 * If printing the tab goes past the column
415228e906dStedu 			 * width, don't print it and just quit.
416228e906dStedu 			 */
417228e906dStedu 			if (new_col > col_max)
418228e906dStedu 				return;
419228e906dStedu 			*col = new_col;
420228e906dStedu 			break;
421228e906dStedu 
422228e906dStedu 		default:
423228e906dStedu 			++(*col);
424228e906dStedu 		}
425228e906dStedu 
426228e906dStedu 		putchar(*s);
427228e906dStedu 	}
428228e906dStedu }
429228e906dStedu 
430228e906dStedu /*
431228e906dStedu  * Prompts user to either choose between two strings or edit one, both,
432228e906dStedu  * or neither.
433228e906dStedu  */
434228e906dStedu static void
prompt(const char * s1,const char * s2)435228e906dStedu prompt(const char *s1, const char *s2)
436228e906dStedu {
437951d632eSotto 	char *cmd;
438228e906dStedu 
439228e906dStedu 	/* Print command prompt. */
440228e906dStedu 	putchar('%');
441228e906dStedu 
442228e906dStedu 	/* Get user input. */
443951d632eSotto 	for (; (cmd = xfgets(stdin)); free(cmd)) {
444228e906dStedu 		const char *p;
445228e906dStedu 
446228e906dStedu 		/* Skip leading whitespace. */
44728ce9ad7Sderaadt 		for (p = cmd; isspace((unsigned char)*p); ++p)
448228e906dStedu 			;
449228e906dStedu 
450228e906dStedu 		switch (*p) {
451228e906dStedu 		case 'e':
452228e906dStedu 			/* Skip `e'. */
453228e906dStedu 			++p;
454228e906dStedu 
455228e906dStedu 			if (eparse(p, s1, s2) == -1)
456228e906dStedu 				goto USAGE;
457228e906dStedu 			break;
458228e906dStedu 
459228e906dStedu 		case 'l':
4605e5270dbSray 		case '1':
461228e906dStedu 			/* Choose left column as-is. */
462228e906dStedu 			if (s1 != NULL)
463d4ba4722Sray 				fprintf(outfp, "%s\n", s1);
464228e906dStedu 
465228e906dStedu 			/* End of command parsing. */
466228e906dStedu 			break;
467228e906dStedu 
468228e906dStedu 		case 'q':
469228e906dStedu 			goto QUIT;
470228e906dStedu 
471228e906dStedu 		case 'r':
4725e5270dbSray 		case '2':
473228e906dStedu 			/* Choose right column as-is. */
474228e906dStedu 			if (s2 != NULL)
475d4ba4722Sray 				fprintf(outfp, "%s\n", s2);
476228e906dStedu 
477228e906dStedu 			/* End of command parsing. */
478228e906dStedu 			break;
479228e906dStedu 
480228e906dStedu 		case 's':
481228e906dStedu 			sflag = 1;
482228e906dStedu 			goto PROMPT;
483228e906dStedu 
484228e906dStedu 		case 'v':
485228e906dStedu 			sflag = 0;
486228e906dStedu 			/* FALLTHROUGH */
487228e906dStedu 
488228e906dStedu 		default:
489228e906dStedu 			/* Interactive usage help. */
490228e906dStedu USAGE:
491228e906dStedu 			int_usage();
492228e906dStedu PROMPT:
493228e906dStedu 			putchar('%');
494228e906dStedu 
495228e906dStedu 			/* Prompt user again. */
496228e906dStedu 			continue;
497228e906dStedu 		}
498228e906dStedu 
499951d632eSotto 		free(cmd);
500228e906dStedu 		return;
501228e906dStedu 	}
502228e906dStedu 
503228e906dStedu 	/*
504228e906dStedu 	 * If there was no error, we received an EOF from stdin, so we
505228e906dStedu 	 * should quit.
506228e906dStedu 	 */
507228e906dStedu QUIT:
508d4ba4722Sray 	fclose(outfp);
509228e906dStedu 	exit(0);
510228e906dStedu }
511228e906dStedu 
512228e906dStedu /*
513228e906dStedu  * Takes two strings, separated by a column divider.  NULL strings are
514228e906dStedu  * treated as empty columns.  If the divider is the ` ' character, the
515228e906dStedu  * second column is not printed (-l flag).  In this case, the second
516228e906dStedu  * string must be NULL.  When the second column is NULL, the divider
517228e906dStedu  * does not print the trailing space following the divider character.
518228e906dStedu  *
519228e906dStedu  * Takes into account that tabs can take multiple columns.
520228e906dStedu  */
521228e906dStedu static void
println(const char * s1,const char div,const char * s2)522228e906dStedu println(const char *s1, const char div, const char *s2)
523228e906dStedu {
524228e906dStedu 	size_t col;
525228e906dStedu 
526228e906dStedu 	/* Print first column.  Skips if s1 == NULL. */
527228e906dStedu 	col = 0;
528228e906dStedu 	if (s1) {
529228e906dStedu 		/* Skip angle bracket and space. */
530228e906dStedu 		printcol(s1, &col, width);
531228e906dStedu 
532228e906dStedu 	}
533228e906dStedu 
534228e906dStedu 	/* Only print left column. */
535228e906dStedu 	if (div == ' ' && !s2) {
536228e906dStedu 		putchar('\n');
537228e906dStedu 		return;
538228e906dStedu 	}
539228e906dStedu 
540228e906dStedu 	/* Otherwise, we pad this column up to width. */
541228e906dStedu 	for (; col < width; ++col)
542228e906dStedu 		putchar(' ');
543228e906dStedu 
544228e906dStedu 	/*
545228e906dStedu 	 * Print column divider.  If there is no second column, we don't
546228e906dStedu 	 * need to add the space for padding.
547228e906dStedu 	 */
548228e906dStedu 	if (!s2) {
549228e906dStedu 		printf(" %c\n", div);
550228e906dStedu 		return;
551228e906dStedu 	}
552228e906dStedu 	printf(" %c ", div);
553228e906dStedu 	col += 3;
554228e906dStedu 
555228e906dStedu 	/* Skip angle bracket and space. */
556228e906dStedu 	printcol(s2, &col, line_width);
557228e906dStedu 
558228e906dStedu 	putchar('\n');
559228e906dStedu }
560228e906dStedu 
561228e906dStedu /*
562228e906dStedu  * Reads a line from file and returns as a string.  If EOF is reached,
563228e906dStedu  * NULL is returned.  The returned string must be freed afterwards.
564228e906dStedu  */
565228e906dStedu static char *
xfgets(FILE * file)566228e906dStedu xfgets(FILE *file)
567228e906dStedu {
568228e906dStedu 	const char delim[3] = {'\0', '\0', '\0'};
569228e906dStedu 	char *s;
570228e906dStedu 
571228e906dStedu 	/* XXX - Is this necessary? */
572228e906dStedu 	clearerr(file);
573228e906dStedu 
574228e906dStedu 	if (!(s = fparseln(file, NULL, NULL, delim, 0)) &&
575228e906dStedu 	    ferror(file))
576228e906dStedu 		err(2, "error reading file");
577228e906dStedu 
578228e906dStedu 	if (!s) {
579228e906dStedu 		return (NULL);
580228e906dStedu 	}
581228e906dStedu 
582228e906dStedu 	return (s);
583228e906dStedu }
584228e906dStedu 
585228e906dStedu /*
58649e0b549Sotto  * Parse ed commands from diffpipe and print lines from file1 (lines
58749e0b549Sotto  * to change or delete) or file2 (lines to add or change).
58849e0b549Sotto  * Returns EOF or 0.
589228e906dStedu  */
590228e906dStedu static int
parsecmd(FILE * diffpipe,FILE * file1,FILE * file2)59149e0b549Sotto parsecmd(FILE *diffpipe, FILE *file1, FILE *file2)
592228e906dStedu {
59349e0b549Sotto 	size_t file1start, file1end, file2start, file2end, n;
594228e906dStedu 	/* ed command line and pointer to characters in line */
5955cd71eb4Stedu 	char *line, *p, *q;
5965cd71eb4Stedu 	const char *errstr;
5975cd71eb4Stedu 	char c, cmd;
598228e906dStedu 
599228e906dStedu 	/* Read ed command. */
60049e0b549Sotto 	if (!(line = xfgets(diffpipe)))
601228e906dStedu 		return (EOF);
602228e906dStedu 
603228e906dStedu 	p = line;
604228e906dStedu 	/* Go to character after line number. */
60528ce9ad7Sderaadt 	while (isdigit((unsigned char)*p))
606228e906dStedu 		++p;
6075cd71eb4Stedu 	c = *p;
6085cd71eb4Stedu 	*p++ = 0;
6095cd71eb4Stedu 	file1start = strtonum(line, 0, INT_MAX, &errstr);
6105cd71eb4Stedu 	if (errstr)
6115cd71eb4Stedu 		errx(2, "file1 start is %s: %s", errstr, line);
612228e906dStedu 
613228e906dStedu 	/* A range is specified for file1. */
6145cd71eb4Stedu 	if (c == ',') {
615228e906dStedu 
6165cd71eb4Stedu 		q = p;
617228e906dStedu 		/* Go to character after file2end. */
61828ce9ad7Sderaadt 		while (isdigit((unsigned char)*p))
619228e906dStedu 			++p;
6205cd71eb4Stedu 		c = *p;
6215cd71eb4Stedu 		*p++ = 0;
6225cd71eb4Stedu 		file1end = strtonum(q, 0, INT_MAX, &errstr);
6235cd71eb4Stedu 		if (errstr)
6245cd71eb4Stedu 			errx(2, "file1 end is %s: %s", errstr, line);
6255cd71eb4Stedu 		if (file1start > file1end)
6265cd71eb4Stedu 			errx(2, "invalid line range in file1: %s", line);
6275cd71eb4Stedu 
628228e906dStedu 	} else
629228e906dStedu 		file1end = file1start;
630228e906dStedu 
6315cd71eb4Stedu 	cmd = c;
632228e906dStedu 	/* Check that cmd is valid. */
633228e906dStedu 	if (!(cmd == 'a' || cmd == 'c' || cmd == 'd'))
634228e906dStedu 		errx(2, "ed command not recognized: %c: %s", cmd, line);
635228e906dStedu 
6365cd71eb4Stedu 	q = p;
637228e906dStedu 	/* Go to character after line number. */
63828ce9ad7Sderaadt 	while (isdigit((unsigned char)*p))
639228e906dStedu 		++p;
6405cd71eb4Stedu 	c = *p;
6415cd71eb4Stedu 	*p++ = 0;
6425cd71eb4Stedu 	file2start = strtonum(q, 0, INT_MAX, &errstr);
6435cd71eb4Stedu 	if (errstr)
6445cd71eb4Stedu 		errx(2, "file2 start is %s: %s", errstr, line);
645228e906dStedu 
646228e906dStedu 	/*
647228e906dStedu 	 * There should either be a comma signifying a second line
648228e906dStedu 	 * number or the line should just end here.
649228e906dStedu 	 */
6505cd71eb4Stedu 	if (c != ',' && c != '\0')
6515cd71eb4Stedu 		errx(2, "invalid line range in file2: %c: %s", c, line);
652228e906dStedu 
6535cd71eb4Stedu 	if (c == ',') {
654228e906dStedu 
6555cd71eb4Stedu 		file2end = strtonum(p, 0, INT_MAX, &errstr);
6565cd71eb4Stedu 		if (errstr)
6575cd71eb4Stedu 			errx(2, "file2 end is %s: %s", errstr, line);
658228e906dStedu 		if (file2start >= file2end)
659228e906dStedu 			errx(2, "invalid line range in file2: %s", line);
660228e906dStedu 	} else
661228e906dStedu 		file2end = file2start;
662228e906dStedu 
663228e906dStedu 	/* Appends happen _after_ stated line. */
664228e906dStedu 	if (cmd == 'a') {
665228e906dStedu 		if (file1start != file1end)
666228e906dStedu 			errx(2, "append cannot have a file1 range: %s",
667228e906dStedu 			    line);
66899c13856Sray 		if (file1start == SIZE_MAX)
669228e906dStedu 			errx(2, "file1 line range too high: %s", line);
670228e906dStedu 		file1start = ++file1end;
671228e906dStedu 	}
672228e906dStedu 	/*
673228e906dStedu 	 * I'm not sure what the deal is with the line numbers for
674228e906dStedu 	 * deletes, though.
675228e906dStedu 	 */
676228e906dStedu 	else if (cmd == 'd') {
677228e906dStedu 		if (file2start != file2end)
678228e906dStedu 			errx(2, "delete cannot have a file2 range: %s",
679228e906dStedu 			    line);
68099c13856Sray 		if (file2start == SIZE_MAX)
681228e906dStedu 			errx(2, "file2 line range too high: %s", line);
682228e906dStedu 		file2start = ++file2end;
683228e906dStedu 	}
684228e906dStedu 
68549e0b549Sotto 	/*
68649e0b549Sotto 	 * Continue reading file1 and file2 until we reach line numbers
68749e0b549Sotto 	 * specified by diff.  Should only happen with -I flag.
68849e0b549Sotto 	 */
68949e0b549Sotto 	for (; file1ln < file1start && file2ln < file2start;
69049e0b549Sotto 	    ++file1ln, ++file2ln) {
691951d632eSotto 		char *s1, *s2;
692228e906dStedu 
69349e0b549Sotto 		if (!(s1 = xfgets(file1)))
694228e906dStedu 			errx(2, "file1 shorter than expected");
69549e0b549Sotto 		if (!(s2 = xfgets(file2)))
69649e0b549Sotto 			errx(2, "file2 shorter than expected");
697228e906dStedu 
698228e906dStedu 		/* If the -l flag was specified, print only left column. */
69949e0b549Sotto 		if (lflag) {
700951d632eSotto 			free(s2);
70149e0b549Sotto 			/*
70249e0b549Sotto 			 * XXX - If -l and -I are both specified, all
70349e0b549Sotto 			 * unchanged or ignored lines are shown with a
70449e0b549Sotto 			 * `(' divider.  This matches GNU sdiff, but I
70549e0b549Sotto 			 * believe it is a bug.  Just check out:
70649e0b549Sotto 			 * gsdiff -l -I '^$' samefile samefile.
70749e0b549Sotto 			 */
70849e0b549Sotto 			if (Iflag)
70949e0b549Sotto 				enqueue(s1, '(', NULL);
71049e0b549Sotto 			else
71149e0b549Sotto 				enqueue(s1, ' ', NULL);
71249e0b549Sotto 		} else
71349e0b549Sotto 			enqueue(s1, ' ', s2);
714228e906dStedu 	}
71549e0b549Sotto 	/* Ignore deleted lines. */
71649e0b549Sotto 	for (; file1ln < file1start; ++file1ln) {
717951d632eSotto 		char *s;
71849e0b549Sotto 
71949e0b549Sotto 		if (!(s = xfgets(file1)))
72049e0b549Sotto 			errx(2, "file1 shorter than expected");
72149e0b549Sotto 
72249e0b549Sotto 		enqueue(s, '(', NULL);
72349e0b549Sotto 	}
72449e0b549Sotto 	/* Ignore added lines. */
72549e0b549Sotto 	for (; file2ln < file2start; ++file2ln) {
726951d632eSotto 		char *s;
72749e0b549Sotto 
72849e0b549Sotto 		if (!(s = xfgets(file2)))
72949e0b549Sotto 			errx(2, "file2 shorter than expected");
73049e0b549Sotto 
73149e0b549Sotto 		/* If -l flag was given, don't print right column. */
73249e0b549Sotto 		if (lflag)
733951d632eSotto 			free(s);
73449e0b549Sotto 		else
73549e0b549Sotto 			enqueue(NULL, ')', s);
73649e0b549Sotto 	}
73749e0b549Sotto 
73849e0b549Sotto 	/* Process unmodified or skipped lines. */
739228e906dStedu 	processq();
740228e906dStedu 
741228e906dStedu 	switch (cmd) {
742228e906dStedu 	case 'a':
74349e0b549Sotto 		printa(file2, file2end);
74449e0b549Sotto 		n = file2end - file2start + 1;
745228e906dStedu 		break;
746228e906dStedu 
747228e906dStedu 	case 'c':
74849e0b549Sotto 		printc(file1, file1end, file2, file2end);
74949e0b549Sotto 		n = file1end - file1start + 1 + 1 + file2end - file2start + 1;
750228e906dStedu 		break;
751228e906dStedu 
752228e906dStedu 	case 'd':
75349e0b549Sotto 		printd(file1, file1end);
75449e0b549Sotto 		n = file1end - file1start + 1;
755228e906dStedu 		break;
756228e906dStedu 
757228e906dStedu 	default:
758228e906dStedu 		errx(2, "invalid diff command: %c: %s", cmd, line);
759228e906dStedu 	}
7607f148183Stobias 	free(line);
761228e906dStedu 
76249e0b549Sotto 	/* Skip to next ed line. */
7637f148183Stobias 	while (n--) {
7647f148183Stobias 		if (!(line = xfgets(diffpipe)))
76549e0b549Sotto 			errx(2, "diff ended early");
7667f148183Stobias 		free(line);
7677f148183Stobias 	}
76849e0b549Sotto 
76949a17b89Stedu 	return (0);
770228e906dStedu }
771228e906dStedu 
772228e906dStedu /*
773228e906dStedu  * Queues up a diff line.
774228e906dStedu  */
775228e906dStedu static void
enqueue(char * left,char div,char * right)776951d632eSotto enqueue(char *left, char div, char *right)
777228e906dStedu {
778228e906dStedu 	struct diffline *diffp;
779228e906dStedu 
780228e906dStedu 	if (!(diffp = malloc(sizeof(struct diffline))))
781a9ba38d6Stedu 		err(2, "enqueue");
782228e906dStedu 	diffp->left = left;
783228e906dStedu 	diffp->div = div;
784228e906dStedu 	diffp->right = right;
785228e906dStedu 	SIMPLEQ_INSERT_TAIL(&diffhead, diffp, diffentries);
786228e906dStedu }
787228e906dStedu 
788228e906dStedu /*
789228e906dStedu  * Free a diffline structure and its elements.
790228e906dStedu  */
791228e906dStedu static void
freediff(struct diffline * diffp)792951d632eSotto freediff(struct diffline *diffp)
793228e906dStedu {
794951d632eSotto 	free(diffp->left);
795951d632eSotto 	free(diffp->right);
796951d632eSotto 	free(diffp);
797228e906dStedu }
798228e906dStedu 
799228e906dStedu /*
800228e906dStedu  * Append second string into first.  Repeated appends to the same string
801228e906dStedu  * are cached, making this an O(n) function, where n = strlen(append).
802228e906dStedu  */
803228e906dStedu static void
astrcat(char ** s,const char * append)804228e906dStedu astrcat(char **s, const char *append)
805228e906dStedu {
806228e906dStedu 	/* Length of string in previous run. */
807228e906dStedu 	static size_t offset = 0;
80815c8080bSotto 	size_t newsiz;
809228e906dStedu 	/*
810228e906dStedu 	 * String from previous run.  Compared to *s to see if we are
811228e906dStedu 	 * dealing with the same string.  If so, we can use offset.
812228e906dStedu 	 */
813951d632eSotto 	static const char *oldstr = NULL;
814228e906dStedu 	char *newstr;
815228e906dStedu 
816228e906dStedu 
817228e906dStedu 	/*
818228e906dStedu 	 * First string is NULL, so just copy append.
819228e906dStedu 	 */
820228e906dStedu 	if (!*s) {
821228e906dStedu 		if (!(*s = strdup(append)))
822a9ba38d6Stedu 			err(2, "astrcat");
823228e906dStedu 
824228e906dStedu 		/* Keep track of string. */
825228e906dStedu 		offset = strlen(*s);
826228e906dStedu 		oldstr = *s;
827228e906dStedu 
828228e906dStedu 		return;
829228e906dStedu 	}
830228e906dStedu 
831228e906dStedu 	/*
832228e906dStedu 	 * *s is a string so concatenate.
833228e906dStedu 	 */
834228e906dStedu 
835228e906dStedu 	/* Did we process the same string in the last run? */
836228e906dStedu 	/*
837228e906dStedu 	 * If this is a different string from the one we just processed
838228e906dStedu 	 * cache new string.
839228e906dStedu 	 */
840228e906dStedu 	if (oldstr != *s) {
841228e906dStedu 		offset = strlen(*s);
842228e906dStedu 		oldstr = *s;
843228e906dStedu 	}
844228e906dStedu 
84515c8080bSotto 	/* Size = strlen(*s) + \n + strlen(append) + '\0'. */
84615c8080bSotto 	newsiz = offset + 1 + strlen(append) + 1;
847228e906dStedu 
848228e906dStedu 	/* Resize *s to fit new string. */
84915c8080bSotto 	newstr = realloc(*s, newsiz);
850228e906dStedu 	if (newstr == NULL)
851a9ba38d6Stedu 		err(2, "astrcat");
852228e906dStedu 	*s = newstr;
853228e906dStedu 
85415c8080bSotto 	/* *s + offset should be end of string. */
855228e906dStedu 	/* Concatenate. */
85615c8080bSotto 	strlcpy(*s + offset, "\n", newsiz - offset);
85715c8080bSotto 	strlcat(*s + offset, append, newsiz - offset);
858228e906dStedu 
85915c8080bSotto 	/* New string length should be exactly newsiz - 1 characters. */
860228e906dStedu 	/* Store generated string's values. */
86115c8080bSotto 	offset = newsiz - 1;
862228e906dStedu 	oldstr = *s;
863228e906dStedu }
864228e906dStedu 
865228e906dStedu /*
866228e906dStedu  * Process diff set queue, printing, prompting, and saving each diff
867228e906dStedu  * line stored in queue.
868228e906dStedu  */
869228e906dStedu static void
processq(void)870228e906dStedu processq(void)
871228e906dStedu {
872228e906dStedu 	struct diffline *diffp;
873951d632eSotto 	char divc, *left, *right;
874228e906dStedu 
875228e906dStedu 	/* Don't process empty queue. */
876228e906dStedu 	if (SIMPLEQ_EMPTY(&diffhead))
877228e906dStedu 		return;
878228e906dStedu 
8797bf59f2eSotto 	/* Remember the divider. */
8807bf59f2eSotto 	divc = SIMPLEQ_FIRST(&diffhead)->div;
8817bf59f2eSotto 
882228e906dStedu 	left = NULL;
883228e906dStedu 	right = NULL;
884228e906dStedu 	/*
885228e906dStedu 	 * Go through set of diffs, concatenating each line in left or
886228e906dStedu 	 * right column into two long strings, `left' and `right'.
887228e906dStedu 	 */
888228e906dStedu 	SIMPLEQ_FOREACH(diffp, &diffhead, diffentries) {
889228e906dStedu 		/*
89049e0b549Sotto 		 * Print changed lines if -s was given,
89149e0b549Sotto 		 * print all lines if -s was not given.
892228e906dStedu 		 */
8937bf59f2eSotto 		if (!sflag || diffp->div == '|' || diffp->div == '<' ||
8947bf59f2eSotto 		    diffp->div == '>')
895228e906dStedu 			println(diffp->left, diffp->div, diffp->right);
896228e906dStedu 
897228e906dStedu 		/* Append new lines to diff set. */
898228e906dStedu 		if (diffp->left)
899228e906dStedu 			astrcat(&left, diffp->left);
900228e906dStedu 		if (diffp->right)
901228e906dStedu 			astrcat(&right, diffp->right);
902228e906dStedu 	}
903228e906dStedu 
904228e906dStedu 	/* Empty queue and free each diff line and its elements. */
905228e906dStedu 	while (!SIMPLEQ_EMPTY(&diffhead)) {
906228e906dStedu 		diffp = SIMPLEQ_FIRST(&diffhead);
907228e906dStedu 		SIMPLEQ_REMOVE_HEAD(&diffhead, diffentries);
90849e0b549Sotto 		freediff(diffp);
909228e906dStedu 	}
910228e906dStedu 
911d4ba4722Sray 	/* Write to outfp, prompting user if lines are different. */
912d4ba4722Sray 	if (outfp)
913951d632eSotto 		switch (divc) {
91449e0b549Sotto 		case ' ': case '(': case ')':
915d4ba4722Sray 			fprintf(outfp, "%s\n", left);
91649e0b549Sotto 			break;
91749e0b549Sotto 		case '|': case '<': case '>':
918228e906dStedu 			prompt(left, right);
91949e0b549Sotto 			break;
92049e0b549Sotto 		default:
921951d632eSotto 			errx(2, "invalid divider: %c", divc);
922228e906dStedu 		}
923228e906dStedu 
924228e906dStedu 	/* Free left and right. */
925228e906dStedu 	free(left);
926228e906dStedu 	free(right);
927228e906dStedu }
928228e906dStedu 
929228e906dStedu /*
930228e906dStedu  * Print lines following an (a)ppend command.
931228e906dStedu  */
932228e906dStedu static void
printa(FILE * file,size_t line2)933228e906dStedu printa(FILE *file, size_t line2)
934228e906dStedu {
935228e906dStedu 	char *line;
936228e906dStedu 
937228e906dStedu 	for (; file2ln <= line2; ++file2ln) {
938228e906dStedu 		if (!(line = xfgets(file)))
939228e906dStedu 			errx(2, "append ended early");
940228e906dStedu 		enqueue(NULL, '>', line);
941228e906dStedu 	}
942228e906dStedu 
943228e906dStedu 	processq();
944228e906dStedu }
945228e906dStedu 
946228e906dStedu /*
947228e906dStedu  * Print lines following a (c)hange command, from file1ln to file1end
948228e906dStedu  * and from file2ln to file2end.
949228e906dStedu  */
950228e906dStedu static void
printc(FILE * file1,size_t file1end,FILE * file2,size_t file2end)951228e906dStedu printc(FILE *file1, size_t file1end, FILE *file2, size_t file2end)
952228e906dStedu {
953228e906dStedu 	struct fileline {
954228e906dStedu 		SIMPLEQ_ENTRY(fileline)	 fileentries;
955951d632eSotto 		char			*line;
956228e906dStedu 	};
957228e906dStedu 	SIMPLEQ_HEAD(, fileline) delqhead = SIMPLEQ_HEAD_INITIALIZER(delqhead);
958228e906dStedu 
959228e906dStedu 	/* Read lines to be deleted. */
960228e906dStedu 	for (; file1ln <= file1end; ++file1ln) {
961228e906dStedu 		struct fileline *linep;
962951d632eSotto 		char *line1;
963228e906dStedu 
964228e906dStedu 		/* Read lines from both. */
965228e906dStedu 		if (!(line1 = xfgets(file1)))
966228e906dStedu 			errx(2, "error reading file1 in delete in change");
967228e906dStedu 
968228e906dStedu 		/* Add to delete queue. */
969228e906dStedu 		if (!(linep = malloc(sizeof(struct fileline))))
970a9ba38d6Stedu 			err(2, "printc");
971228e906dStedu 		linep->line = line1;
972228e906dStedu 		SIMPLEQ_INSERT_TAIL(&delqhead, linep, fileentries);
973228e906dStedu 	}
974228e906dStedu 
975228e906dStedu 	/* Process changed lines.. */
976228e906dStedu 	for (; !SIMPLEQ_EMPTY(&delqhead) && file2ln <= file2end;
977228e906dStedu 	    ++file2ln) {
978228e906dStedu 		struct fileline *del;
979228e906dStedu 		char *add;
980228e906dStedu 
981228e906dStedu 		/* Get add line. */
98249e0b549Sotto 		if (!(add = xfgets(file2)))
98349e0b549Sotto 			errx(2, "error reading add in change");
984228e906dStedu 
985228e906dStedu 		del = SIMPLEQ_FIRST(&delqhead);
986228e906dStedu 		enqueue(del->line, '|', add);
987228e906dStedu 		SIMPLEQ_REMOVE_HEAD(&delqhead, fileentries);
988228e906dStedu 		/*
989228e906dStedu 		 * Free fileline structure but not its elements since
990228e906dStedu 		 * they are queued up.
991228e906dStedu 		 */
992228e906dStedu 		free(del);
993228e906dStedu 	}
994228e906dStedu 	processq();
995228e906dStedu 
996228e906dStedu 	/* Process remaining lines to add. */
997228e906dStedu 	for (; file2ln <= file2end; ++file2ln) {
998228e906dStedu 		char *add;
999228e906dStedu 
1000228e906dStedu 		/* Get add line. */
100149e0b549Sotto 		if (!(add = xfgets(file2)))
100249e0b549Sotto 			errx(2, "error reading add in change");
1003228e906dStedu 
1004228e906dStedu 		enqueue(NULL, '>', add);
1005228e906dStedu 	}
1006228e906dStedu 	processq();
1007228e906dStedu 
1008228e906dStedu 	/* Process remaining lines to delete. */
1009228e906dStedu 	while (!SIMPLEQ_EMPTY(&delqhead)) {
1010228e906dStedu 		struct fileline *filep;
1011228e906dStedu 
1012228e906dStedu 		filep = SIMPLEQ_FIRST(&delqhead);
1013228e906dStedu 		enqueue(filep->line, '<', NULL);
1014228e906dStedu 		SIMPLEQ_REMOVE_HEAD(&delqhead, fileentries);
1015228e906dStedu 		free(filep);
1016228e906dStedu 	}
1017228e906dStedu 	processq();
1018228e906dStedu }
1019228e906dStedu 
1020228e906dStedu /*
1021228e906dStedu  * Print deleted lines from file, from file1ln to file1end.
1022228e906dStedu  */
1023228e906dStedu static void
printd(FILE * file1,size_t file1end)102449e0b549Sotto printd(FILE *file1, size_t file1end)
1025228e906dStedu {
1026951d632eSotto 	char *line1;
1027228e906dStedu 
1028228e906dStedu 	/* Print out lines file1ln to line2. */
1029228e906dStedu 	for (; file1ln <= file1end; ++file1ln) {
1030228e906dStedu 		if (!(line1 = xfgets(file1)))
1031228e906dStedu 			errx(2, "file1 ended early in delete");
1032228e906dStedu 		enqueue(line1, '<', NULL);
1033228e906dStedu 	}
1034228e906dStedu 	processq();
1035228e906dStedu }
1036228e906dStedu 
1037228e906dStedu /*
1038228e906dStedu  * Interactive mode usage.
1039228e906dStedu  */
1040228e906dStedu static void
int_usage(void)1041228e906dStedu int_usage(void)
1042228e906dStedu {
1043228e906dStedu 	puts("e:\tedit blank diff\n"
1044228e906dStedu 	    "eb:\tedit both diffs concatenated\n"
1045228e906dStedu 	    "el:\tedit left diff\n"
1046228e906dStedu 	    "er:\tedit right diff\n"
10475e5270dbSray 	    "l | 1:\tchoose left diff\n"
10485e5270dbSray 	    "r | 2:\tchoose right diff\n"
1049228e906dStedu 	    "s:\tsilent mode--don't print identical lines\n"
1050228e906dStedu 	    "v:\tverbose mode--print identical lines\n"
1051228e906dStedu 	    "q:\tquit");
1052228e906dStedu }
1053228e906dStedu 
1054228e906dStedu static void
usage(void)1055228e906dStedu usage(void)
1056228e906dStedu {
1057228e906dStedu 	extern char *__progname;
1058228e906dStedu 
1059228e906dStedu 	fprintf(stderr,
1060618b0722Stedu 	    "usage: %s [-abdilstW] [-I regexp] [-o outfile] [-w width] file1 file2\n",
1061228e906dStedu 	    __progname);
1062228e906dStedu 	exit(2);
1063228e906dStedu }
1064