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