xref: /freebsd/usr.bin/sdiff/sdiff.c (revision da5c2c4230792220817d9f70f6bc61694c7d1183)
113b5b548SBaptiste Daroussin /*	$OpenBSD: sdiff.c,v 1.36 2015/12/29 19:04:46 gsoares Exp $ */
213b5b548SBaptiste Daroussin 
313b5b548SBaptiste Daroussin /*
413b5b548SBaptiste Daroussin  * Written by Raymond Lai <ray@cyth.net>.
513b5b548SBaptiste Daroussin  * Public domain.
613b5b548SBaptiste Daroussin  */
713b5b548SBaptiste Daroussin 
813b5b548SBaptiste Daroussin #include <sys/cdefs.h>
913b5b548SBaptiste Daroussin __FBSDID("$FreeBSD$");
1013b5b548SBaptiste Daroussin 
1113b5b548SBaptiste Daroussin #include <sys/param.h>
1213b5b548SBaptiste Daroussin #include <sys/queue.h>
1313b5b548SBaptiste Daroussin #include <sys/stat.h>
1413b5b548SBaptiste Daroussin #include <sys/types.h>
1513b5b548SBaptiste Daroussin #include <sys/wait.h>
1613b5b548SBaptiste Daroussin 
1713b5b548SBaptiste Daroussin #include <ctype.h>
1813b5b548SBaptiste Daroussin #include <err.h>
1913b5b548SBaptiste Daroussin #include <errno.h>
2013b5b548SBaptiste Daroussin #include <fcntl.h>
2113b5b548SBaptiste Daroussin #include <getopt.h>
2213b5b548SBaptiste Daroussin #include <limits.h>
2313b5b548SBaptiste Daroussin #include <paths.h>
2413b5b548SBaptiste Daroussin #include <stdint.h>
2513b5b548SBaptiste Daroussin #include <stdio.h>
2613b5b548SBaptiste Daroussin #include <stdlib.h>
2713b5b548SBaptiste Daroussin #include <string.h>
2813b5b548SBaptiste Daroussin #include <unistd.h>
2913b5b548SBaptiste Daroussin #include <libutil.h>
3013b5b548SBaptiste Daroussin 
3113b5b548SBaptiste Daroussin #include "common.h"
3213b5b548SBaptiste Daroussin #include "extern.h"
3313b5b548SBaptiste Daroussin 
3413b5b548SBaptiste Daroussin #define DIFF_PATH	"/usr/bin/diff"
3513b5b548SBaptiste Daroussin 
3613b5b548SBaptiste Daroussin #define WIDTH 126
3713b5b548SBaptiste Daroussin /*
3813b5b548SBaptiste Daroussin  * Each column must be at least one character wide, plus three
3913b5b548SBaptiste Daroussin  * characters between the columns (space, [<|>], space).
4013b5b548SBaptiste Daroussin  */
4113b5b548SBaptiste Daroussin #define WIDTH_MIN 5
4213b5b548SBaptiste Daroussin 
4313b5b548SBaptiste Daroussin /* 3 kilobytes of chars */
4413b5b548SBaptiste Daroussin #define MAX_CHECK 768
4513b5b548SBaptiste Daroussin 
4613b5b548SBaptiste Daroussin /* A single diff line. */
4713b5b548SBaptiste Daroussin struct diffline {
4813b5b548SBaptiste Daroussin 	STAILQ_ENTRY(diffline) diffentries;
4913b5b548SBaptiste Daroussin 	char	*left;
5013b5b548SBaptiste Daroussin 	char	 div;
5113b5b548SBaptiste Daroussin 	char	*right;
5213b5b548SBaptiste Daroussin };
5313b5b548SBaptiste Daroussin 
5413b5b548SBaptiste Daroussin static void astrcat(char **, const char *);
5513b5b548SBaptiste Daroussin static void enqueue(char *, char, char *);
5613b5b548SBaptiste Daroussin static char *mktmpcpy(const char *);
5713b5b548SBaptiste Daroussin static int istextfile(FILE *);
5813b5b548SBaptiste Daroussin static void binexec(char *, char *, char *) __dead2;
5913b5b548SBaptiste Daroussin static void freediff(struct diffline *);
6013b5b548SBaptiste Daroussin static void int_usage(void);
6113b5b548SBaptiste Daroussin static int parsecmd(FILE *, FILE *, FILE *);
6213b5b548SBaptiste Daroussin static void printa(FILE *, size_t);
6313b5b548SBaptiste Daroussin static void printc(FILE *, size_t, FILE *, size_t);
6413b5b548SBaptiste Daroussin static void printcol(const char *, size_t *, const size_t);
6513b5b548SBaptiste Daroussin static void printd(FILE *, size_t);
6613b5b548SBaptiste Daroussin static void println(const char *, const char, const char *);
6713b5b548SBaptiste Daroussin static void processq(void);
6813b5b548SBaptiste Daroussin static void prompt(const char *, const char *);
6913b5b548SBaptiste Daroussin static void usage(void) __dead2;
7013b5b548SBaptiste Daroussin static char *xfgets(FILE *);
7113b5b548SBaptiste Daroussin 
7213b5b548SBaptiste Daroussin static STAILQ_HEAD(, diffline) diffhead = STAILQ_HEAD_INITIALIZER(diffhead);
7313b5b548SBaptiste Daroussin static size_t line_width;	/* width of a line (two columns and divider) */
7413b5b548SBaptiste Daroussin static size_t width;		/* width of each column */
7513b5b548SBaptiste Daroussin static size_t file1ln, file2ln;	/* line number of file1 and file2 */
7613b5b548SBaptiste Daroussin static int Iflag = 0;	/* ignore sets matching regexp */
7713b5b548SBaptiste Daroussin static int	lflag;		/* print only left column for identical lines */
7813b5b548SBaptiste Daroussin static int	sflag;		/* skip identical lines */
7913b5b548SBaptiste Daroussin FILE *outfp;		/* file to save changes to */
8013b5b548SBaptiste Daroussin const char *tmpdir;	/* TMPDIR or /tmp */
8113b5b548SBaptiste Daroussin 
8213b5b548SBaptiste Daroussin enum {
8313b5b548SBaptiste Daroussin 	HELP_OPT = CHAR_MAX + 1,
8413b5b548SBaptiste Daroussin 	NORMAL_OPT,
8513b5b548SBaptiste Daroussin 	FCASE_SENSITIVE_OPT,
8613b5b548SBaptiste Daroussin 	FCASE_IGNORE_OPT,
8713b5b548SBaptiste Daroussin 	FROMFILE_OPT,
8813b5b548SBaptiste Daroussin 	TOFILE_OPT,
8913b5b548SBaptiste Daroussin 	UNIDIR_OPT,
9013b5b548SBaptiste Daroussin 	STRIPCR_OPT,
9113b5b548SBaptiste Daroussin 	HORIZ_OPT,
9213b5b548SBaptiste Daroussin 	LEFTC_OPT,
9313b5b548SBaptiste Daroussin 	SUPCL_OPT,
9413b5b548SBaptiste Daroussin 	LF_OPT,
9513b5b548SBaptiste Daroussin 	/* the following groupings must be in sequence */
9613b5b548SBaptiste Daroussin 	OLDGF_OPT,
9713b5b548SBaptiste Daroussin 	NEWGF_OPT,
9813b5b548SBaptiste Daroussin 	UNCGF_OPT,
9913b5b548SBaptiste Daroussin 	CHGF_OPT,
10013b5b548SBaptiste Daroussin 	OLDLF_OPT,
10113b5b548SBaptiste Daroussin 	NEWLF_OPT,
10213b5b548SBaptiste Daroussin 	UNCLF_OPT,
10313b5b548SBaptiste Daroussin 	/* end order-sensitive enums */
10413b5b548SBaptiste Daroussin 	TSIZE_OPT,
10513b5b548SBaptiste Daroussin 	HLINES_OPT,
10613b5b548SBaptiste Daroussin 	LFILES_OPT,
10713b5b548SBaptiste Daroussin 	DIFFPROG_OPT,
10813b5b548SBaptiste Daroussin 	PIPE_FD,
10913b5b548SBaptiste Daroussin 	/* pid from the diff parent (if applicable) */
11013b5b548SBaptiste Daroussin 	DIFF_PID,
11113b5b548SBaptiste Daroussin 
11213b5b548SBaptiste Daroussin 	NOOP_OPT,
11313b5b548SBaptiste Daroussin };
11413b5b548SBaptiste Daroussin 
11513b5b548SBaptiste Daroussin static struct option longopts[] = {
11613b5b548SBaptiste Daroussin 	/* options only processed in sdiff */
11713b5b548SBaptiste Daroussin 	{ "left-column",		no_argument,		NULL,	LEFTC_OPT },
11813b5b548SBaptiste Daroussin 	{ "suppress-common-lines",	no_argument,		NULL,	's' },
11913b5b548SBaptiste Daroussin 	{ "width",			required_argument,	NULL,	'w' },
12013b5b548SBaptiste Daroussin 
12113b5b548SBaptiste Daroussin 	{ "output",			required_argument,	NULL,	'o' },
12213b5b548SBaptiste Daroussin 	{ "diff-program",		required_argument,	NULL,	DIFFPROG_OPT },
12313b5b548SBaptiste Daroussin 
12413b5b548SBaptiste Daroussin 	{ "pipe-fd",			required_argument,	NULL,	PIPE_FD },
12513b5b548SBaptiste Daroussin 	{ "diff-pid",			required_argument,	NULL,	DIFF_PID },
12613b5b548SBaptiste Daroussin 	/* Options processed by diff. */
12713b5b548SBaptiste Daroussin 	{ "ignore-file-name-case",	no_argument,		NULL,	FCASE_IGNORE_OPT },
12813b5b548SBaptiste Daroussin 	{ "no-ignore-file-name-case",	no_argument,		NULL,	FCASE_SENSITIVE_OPT },
12913b5b548SBaptiste Daroussin 	{ "strip-trailing-cr",		no_argument,		NULL,	STRIPCR_OPT },
13013b5b548SBaptiste Daroussin 	{ "tabsize",			required_argument,	NULL,	TSIZE_OPT },
13113b5b548SBaptiste Daroussin 	{ "help",			no_argument,		NULL,	HELP_OPT },
13213b5b548SBaptiste Daroussin 	{ "text",			no_argument,		NULL,	'a' },
13313b5b548SBaptiste Daroussin 	{ "ignore-blank-lines",		no_argument,		NULL,	'B' },
13413b5b548SBaptiste Daroussin 	{ "ignore-space-change",	no_argument,		NULL,	'b' },
13513b5b548SBaptiste Daroussin 	{ "minimal",			no_argument,		NULL,	'd' },
13613b5b548SBaptiste Daroussin 	{ "ignore-tab-expansion",	no_argument,		NULL,	'E' },
13713b5b548SBaptiste Daroussin 	{ "ignore-matching-lines",	required_argument,	NULL,	'I' },
13813b5b548SBaptiste Daroussin 	{ "ignore-case",		no_argument,		NULL,	'i' },
13913b5b548SBaptiste Daroussin 	{ "expand-tabs",		no_argument,		NULL,	't' },
14013b5b548SBaptiste Daroussin 	{ "speed-large-files",		no_argument,		NULL,	'H' },
14113b5b548SBaptiste Daroussin 	{ "ignore-all-space",		no_argument,		NULL,	'W' },
14213b5b548SBaptiste Daroussin 
14313b5b548SBaptiste Daroussin 	{ NULL,				0,			NULL,	'\0'}
14413b5b548SBaptiste Daroussin };
14513b5b548SBaptiste Daroussin 
14613b5b548SBaptiste Daroussin static const char *help_msg[] = {
14713b5b548SBaptiste Daroussin 	"\nusage: sdiff [-abdilstW] [-I regexp] [-o outfile] [-w width] file1 file2\n",
14813b5b548SBaptiste Daroussin 	"\t-l, --left-column, Only print the left column for identical lines.",
14913b5b548SBaptiste Daroussin 	"\t-o OUTFILE, --output=OUTFILE, nteractively merge file1 and file2 into outfile.",
15013b5b548SBaptiste Daroussin 	"\t-s, --suppress-common-lines, Skip identical lines.",
15113b5b548SBaptiste Daroussin 	"\t-w WIDTH, --width=WIDTH, Print a maximum of WIDTH characters on each line.",
15213b5b548SBaptiste Daroussin 	"\tOptions passed to diff(1) are:",
15313b5b548SBaptiste Daroussin 	"\t\t-a, --text, Treat file1 and file2 as text files.",
15413b5b548SBaptiste Daroussin 	"\t\t-b, --ignore-trailing-cr, Ignore trailing blank spaces.",
15513b5b548SBaptiste Daroussin 	"\t\t-d, --minimal, Minimize diff size.",
15613b5b548SBaptiste Daroussin 	"\t\t-I RE, --ignore-matching-lines=RE, Ignore changes whose line matches RE.",
15713b5b548SBaptiste Daroussin 	"\t\t-i, --ignore-case, Do a case-insensitive comparison.",
15813b5b548SBaptiste Daroussin 	"\t\t-t, --expand-tabs Expand tabs to spaces.",
15913b5b548SBaptiste Daroussin 	"\t\t-W, --ignore-all-spaces, Ignore all spaces.",
16013b5b548SBaptiste Daroussin 	"\t\t--speed-large-files, Assume large file with scattered changes.",
16113b5b548SBaptiste Daroussin 	"\t\t--strip-trailing-cr, Strip trailing carriage return.",
16213b5b548SBaptiste Daroussin 	"\t\t--ignore-file-name-case, Ignore case of file names.",
16313b5b548SBaptiste Daroussin 	"\t\t--no-ignore-file-name-case, Do not ignore file name case",
16413b5b548SBaptiste Daroussin 	"\t\t--tabsize NUM, Change size of tabs (default 8.)",
16513b5b548SBaptiste Daroussin 
16613b5b548SBaptiste Daroussin 	NULL,
16713b5b548SBaptiste Daroussin };
16813b5b548SBaptiste Daroussin 
16913b5b548SBaptiste Daroussin /*
17013b5b548SBaptiste Daroussin  * Create temporary file if source_file is not a regular file.
17113b5b548SBaptiste Daroussin  * Returns temporary file name if one was malloced, NULL if unnecessary.
17213b5b548SBaptiste Daroussin  */
17313b5b548SBaptiste Daroussin static char *
17413b5b548SBaptiste Daroussin mktmpcpy(const char *source_file)
17513b5b548SBaptiste Daroussin {
17613b5b548SBaptiste Daroussin 	struct stat sb;
17713b5b548SBaptiste Daroussin 	ssize_t rcount;
17813b5b548SBaptiste Daroussin 	int ifd, ofd;
17913b5b548SBaptiste Daroussin 	u_char buf[BUFSIZ];
18013b5b548SBaptiste Daroussin 	char *target_file;
18113b5b548SBaptiste Daroussin 
18213b5b548SBaptiste Daroussin 	/* Open input and output. */
18313b5b548SBaptiste Daroussin 	ifd = open(source_file, O_RDONLY, 0);
18413b5b548SBaptiste Daroussin 	/* File was opened successfully. */
18513b5b548SBaptiste Daroussin 	if (ifd != -1) {
18613b5b548SBaptiste Daroussin 		if (fstat(ifd, &sb) == -1)
18713b5b548SBaptiste Daroussin 			err(2, "error getting file status from %s", source_file);
18813b5b548SBaptiste Daroussin 
18913b5b548SBaptiste Daroussin 		/* Regular file. */
19013b5b548SBaptiste Daroussin 		if (S_ISREG(sb.st_mode)) {
19113b5b548SBaptiste Daroussin 			close(ifd);
19213b5b548SBaptiste Daroussin 			return (NULL);
19313b5b548SBaptiste Daroussin 		}
19413b5b548SBaptiste Daroussin 	} else {
19513b5b548SBaptiste Daroussin 		/* If ``-'' does not exist the user meant stdin. */
19613b5b548SBaptiste Daroussin 		if (errno == ENOENT && strcmp(source_file, "-") == 0)
19713b5b548SBaptiste Daroussin 			ifd = STDIN_FILENO;
19813b5b548SBaptiste Daroussin 		else
19913b5b548SBaptiste Daroussin 			err(2, "error opening %s", source_file);
20013b5b548SBaptiste Daroussin 	}
20113b5b548SBaptiste Daroussin 
20213b5b548SBaptiste Daroussin 	/* Not a regular file, so copy input into temporary file. */
20313b5b548SBaptiste Daroussin 	if (asprintf(&target_file, "%s/sdiff.XXXXXXXXXX", tmpdir) == -1)
20413b5b548SBaptiste Daroussin 		err(2, "asprintf");
20513b5b548SBaptiste Daroussin 	if ((ofd = mkstemp(target_file)) == -1) {
20613b5b548SBaptiste Daroussin 		warn("error opening %s", target_file);
20713b5b548SBaptiste Daroussin 		goto FAIL;
20813b5b548SBaptiste Daroussin 	}
20913b5b548SBaptiste Daroussin 	while ((rcount = read(ifd, buf, sizeof(buf))) != -1 &&
21013b5b548SBaptiste Daroussin 	    rcount != 0) {
21113b5b548SBaptiste Daroussin 		ssize_t wcount;
21213b5b548SBaptiste Daroussin 
21313b5b548SBaptiste Daroussin 		wcount = write(ofd, buf, (size_t)rcount);
21413b5b548SBaptiste Daroussin 		if (-1 == wcount || rcount != wcount) {
21513b5b548SBaptiste Daroussin 			warn("error writing to %s", target_file);
21613b5b548SBaptiste Daroussin 			goto FAIL;
21713b5b548SBaptiste Daroussin 		}
21813b5b548SBaptiste Daroussin 	}
21913b5b548SBaptiste Daroussin 	if (rcount == -1) {
22013b5b548SBaptiste Daroussin 		warn("error reading from %s", source_file);
22113b5b548SBaptiste Daroussin 		goto FAIL;
22213b5b548SBaptiste Daroussin 	}
22313b5b548SBaptiste Daroussin 
22413b5b548SBaptiste Daroussin 	close(ifd);
22513b5b548SBaptiste Daroussin 	close(ofd);
22613b5b548SBaptiste Daroussin 
22713b5b548SBaptiste Daroussin 	return (target_file);
22813b5b548SBaptiste Daroussin 
22913b5b548SBaptiste Daroussin FAIL:
23013b5b548SBaptiste Daroussin 	unlink(target_file);
23113b5b548SBaptiste Daroussin 	exit(2);
23213b5b548SBaptiste Daroussin }
23313b5b548SBaptiste Daroussin 
23413b5b548SBaptiste Daroussin int
23513b5b548SBaptiste Daroussin main(int argc, char **argv)
23613b5b548SBaptiste Daroussin {
23713b5b548SBaptiste Daroussin 	FILE *diffpipe=NULL, *file1, *file2;
23813b5b548SBaptiste Daroussin 	size_t diffargc = 0, wflag = WIDTH;
23913b5b548SBaptiste Daroussin 	int ch, fd[2] = {-1}, status;
24013b5b548SBaptiste Daroussin 	pid_t pid=0; pid_t ppid =-1;
24113b5b548SBaptiste Daroussin 	const char *outfile = NULL;
24213b5b548SBaptiste Daroussin 	struct option *popt;
24313b5b548SBaptiste Daroussin 	char **diffargv, *diffprog = DIFF_PATH, *filename1, *filename2,
24413b5b548SBaptiste Daroussin 	     *tmp1, *tmp2, *s1, *s2;
24513b5b548SBaptiste Daroussin 	int i;
24613b5b548SBaptiste Daroussin 
24713b5b548SBaptiste Daroussin 	/*
24813b5b548SBaptiste Daroussin 	 * Process diff flags.
24913b5b548SBaptiste Daroussin 	 */
25013b5b548SBaptiste Daroussin 	/*
25113b5b548SBaptiste Daroussin 	 * Allocate memory for diff arguments and NULL.
25213b5b548SBaptiste Daroussin 	 * Each flag has at most one argument, so doubling argc gives an
25313b5b548SBaptiste Daroussin 	 * upper limit of how many diff args can be passed.  argv[0],
25413b5b548SBaptiste Daroussin 	 * file1, and file2 won't have arguments so doubling them will
25513b5b548SBaptiste Daroussin 	 * waste some memory; however we need an extra space for the
25613b5b548SBaptiste Daroussin 	 * NULL at the end, so it sort of works out.
25713b5b548SBaptiste Daroussin 	 */
25813b5b548SBaptiste Daroussin 	if (!(diffargv = calloc(argc, sizeof(char **) * 2)))
25913b5b548SBaptiste Daroussin 		err(2, "main");
26013b5b548SBaptiste Daroussin 
26113b5b548SBaptiste Daroussin 	/* Add first argument, the program name. */
26213b5b548SBaptiste Daroussin 	diffargv[diffargc++] = diffprog;
26313b5b548SBaptiste Daroussin 
26413b5b548SBaptiste Daroussin 	/* create a dynamic string for merging single-switch options */
26513b5b548SBaptiste Daroussin 	if ( asprintf(&diffargv[diffargc++], "-")  < 0 )
26613b5b548SBaptiste Daroussin 		err(2, "main");
26713b5b548SBaptiste Daroussin 
26813b5b548SBaptiste Daroussin 	while ((ch = getopt_long(argc, argv, "aBbdEHI:ilo:stWw:",
26913b5b548SBaptiste Daroussin 	    longopts, NULL)) != -1) {
27013b5b548SBaptiste Daroussin 		const char *errstr;
27113b5b548SBaptiste Daroussin 
27213b5b548SBaptiste Daroussin 		switch (ch) {
27313b5b548SBaptiste Daroussin 		/* only compatible --long-name-form with diff */
27413b5b548SBaptiste Daroussin 		case FCASE_IGNORE_OPT:
27513b5b548SBaptiste Daroussin 		case FCASE_SENSITIVE_OPT:
27613b5b548SBaptiste Daroussin 		case STRIPCR_OPT:
27713b5b548SBaptiste Daroussin 		case TSIZE_OPT:
27813b5b548SBaptiste Daroussin 		case 'S':
27913b5b548SBaptiste Daroussin 		break;
28013b5b548SBaptiste Daroussin 		/* combine no-arg single switches */
28113b5b548SBaptiste Daroussin 		case 'a':
28213b5b548SBaptiste Daroussin 		case 'B':
28313b5b548SBaptiste Daroussin 		case 'b':
28413b5b548SBaptiste Daroussin 		case 'd':
28513b5b548SBaptiste Daroussin 		case 'E':
28613b5b548SBaptiste Daroussin 		case 'i':
28713b5b548SBaptiste Daroussin 		case 't':
28813b5b548SBaptiste Daroussin 		case 'H':
28913b5b548SBaptiste Daroussin 		case 'W':
29013b5b548SBaptiste Daroussin 			for(popt = longopts; ch != popt->val && popt->name != NULL; popt++);
29113b5b548SBaptiste Daroussin 			diffargv[1]  = realloc(diffargv[1], sizeof(char) * strlen(diffargv[1]) + 2);
29213b5b548SBaptiste Daroussin 			/*
29313b5b548SBaptiste Daroussin 			 * In diff, the 'W' option is 'w' and the 'w' is 'W'.
29413b5b548SBaptiste Daroussin 			 */
29513b5b548SBaptiste Daroussin 			if (ch == 'W')
29613b5b548SBaptiste Daroussin 				sprintf(diffargv[1], "%sw", diffargv[1]);
29713b5b548SBaptiste Daroussin 			else
29813b5b548SBaptiste Daroussin 				sprintf(diffargv[1], "%s%c", diffargv[1], ch);
29913b5b548SBaptiste Daroussin 			break;
30013b5b548SBaptiste Daroussin 		case DIFFPROG_OPT:
30113b5b548SBaptiste Daroussin 			diffargv[0] = diffprog = optarg;
30213b5b548SBaptiste Daroussin 			break;
30313b5b548SBaptiste Daroussin 		case 'I':
30413b5b548SBaptiste Daroussin 			Iflag = 1;
30513b5b548SBaptiste Daroussin 			diffargv[diffargc++] = "-I";
30613b5b548SBaptiste Daroussin 			diffargv[diffargc++] = optarg;
30713b5b548SBaptiste Daroussin 			break;
30813b5b548SBaptiste Daroussin 		case 'l':
30913b5b548SBaptiste Daroussin 			lflag = 1;
31013b5b548SBaptiste Daroussin 			break;
31113b5b548SBaptiste Daroussin 		case 'o':
31213b5b548SBaptiste Daroussin 			outfile = optarg;
31313b5b548SBaptiste Daroussin 			break;
31413b5b548SBaptiste Daroussin 		case 's':
31513b5b548SBaptiste Daroussin 			sflag = 1;
31613b5b548SBaptiste Daroussin 			break;
31713b5b548SBaptiste Daroussin 		case 'w':
31813b5b548SBaptiste Daroussin 			wflag = strtonum(optarg, WIDTH_MIN,
31913b5b548SBaptiste Daroussin 			    INT_MAX, &errstr);
32013b5b548SBaptiste Daroussin 			if (errstr)
32113b5b548SBaptiste Daroussin 				errx(2, "width is %s: %s", errstr, optarg);
32213b5b548SBaptiste Daroussin 			break;
32313b5b548SBaptiste Daroussin 		case DIFF_PID:
32413b5b548SBaptiste Daroussin 			ppid = strtonum(optarg, 0, INT_MAX, &errstr);
32513b5b548SBaptiste Daroussin 			if (errstr)
32613b5b548SBaptiste Daroussin 				errx(2, "diff pid value is %s: %s", errstr, optarg);
32713b5b548SBaptiste Daroussin 			break;
32813b5b548SBaptiste Daroussin 		case HELP_OPT:
32913b5b548SBaptiste Daroussin 			for (i = 0; help_msg[i] != NULL; i++)
33013b5b548SBaptiste Daroussin 				printf("%s\n", help_msg[i]);
33113b5b548SBaptiste Daroussin 			exit(0);
33213b5b548SBaptiste Daroussin 			break;
33313b5b548SBaptiste Daroussin 		default:
33413b5b548SBaptiste Daroussin 			usage();
33513b5b548SBaptiste Daroussin 			break;
33613b5b548SBaptiste Daroussin 		}
33713b5b548SBaptiste Daroussin 	}
33813b5b548SBaptiste Daroussin 
33913b5b548SBaptiste Daroussin 	/* no single switches were used */
34013b5b548SBaptiste Daroussin 	if (strcmp(diffargv[1], "-") == 0 ) {
34113b5b548SBaptiste Daroussin 		for ( i = 1; i < argc-1; i++) {
34213b5b548SBaptiste Daroussin 			diffargv[i] = diffargv[i+1];
34313b5b548SBaptiste Daroussin 		}
34413b5b548SBaptiste Daroussin 		diffargv[diffargc-1] = NULL;
34513b5b548SBaptiste Daroussin 		diffargc--;
34613b5b548SBaptiste Daroussin 	}
34713b5b548SBaptiste Daroussin 
34813b5b548SBaptiste Daroussin 	argc -= optind;
34913b5b548SBaptiste Daroussin 	argv += optind;
35013b5b548SBaptiste Daroussin 
35113b5b548SBaptiste Daroussin 	if (argc != 2)
35213b5b548SBaptiste Daroussin 		usage();
35313b5b548SBaptiste Daroussin 
35413b5b548SBaptiste Daroussin 	if (outfile && (outfp = fopen(outfile, "w")) == NULL)
35513b5b548SBaptiste Daroussin 		err(2, "could not open: %s", optarg);
35613b5b548SBaptiste Daroussin 
35713b5b548SBaptiste Daroussin 	if ((tmpdir = getenv("TMPDIR")) == NULL || *tmpdir == '\0')
35813b5b548SBaptiste Daroussin 		tmpdir = _PATH_TMP;
35913b5b548SBaptiste Daroussin 
36013b5b548SBaptiste Daroussin 	filename1 = argv[0];
36113b5b548SBaptiste Daroussin 	filename2 = argv[1];
36213b5b548SBaptiste Daroussin 
36313b5b548SBaptiste Daroussin 	/*
36413b5b548SBaptiste Daroussin 	 * Create temporary files for diff and sdiff to share if file1
36513b5b548SBaptiste Daroussin 	 * or file2 are not regular files.  This allows sdiff and diff
36613b5b548SBaptiste Daroussin 	 * to read the same inputs if one or both inputs are stdin.
36713b5b548SBaptiste Daroussin 	 *
36813b5b548SBaptiste Daroussin 	 * If any temporary files were created, their names would be
36913b5b548SBaptiste Daroussin 	 * saved in tmp1 or tmp2.  tmp1 should never equal tmp2.
37013b5b548SBaptiste Daroussin 	 */
37113b5b548SBaptiste Daroussin 	tmp1 = tmp2 = NULL;
37213b5b548SBaptiste Daroussin 	/* file1 and file2 are the same, so copy to same temp file. */
37313b5b548SBaptiste Daroussin 	if (strcmp(filename1, filename2) == 0) {
37413b5b548SBaptiste Daroussin 		if ((tmp1 = mktmpcpy(filename1)))
37513b5b548SBaptiste Daroussin 			filename1 = filename2 = tmp1;
37613b5b548SBaptiste Daroussin 	/* Copy file1 and file2 into separate temp files. */
37713b5b548SBaptiste Daroussin 	} else {
37813b5b548SBaptiste Daroussin 		if ((tmp1 = mktmpcpy(filename1)))
37913b5b548SBaptiste Daroussin 			filename1 = tmp1;
38013b5b548SBaptiste Daroussin 		if ((tmp2 = mktmpcpy(filename2)))
38113b5b548SBaptiste Daroussin 			filename2 = tmp2;
38213b5b548SBaptiste Daroussin 	}
38313b5b548SBaptiste Daroussin 
38413b5b548SBaptiste Daroussin 	diffargv[diffargc++] = filename1;
38513b5b548SBaptiste Daroussin 	diffargv[diffargc++] = filename2;
38613b5b548SBaptiste Daroussin 	/* Add NULL to end of array to indicate end of array. */
38713b5b548SBaptiste Daroussin 	diffargv[diffargc++] = NULL;
38813b5b548SBaptiste Daroussin 
38913b5b548SBaptiste Daroussin 	/* Subtract column divider and divide by two. */
39013b5b548SBaptiste Daroussin 	width = (wflag - 3) / 2;
39113b5b548SBaptiste Daroussin 	/* Make sure line_width can fit in size_t. */
39213b5b548SBaptiste Daroussin 	if (width > (SIZE_MAX - 3) / 2)
39313b5b548SBaptiste Daroussin 		errx(2, "width is too large: %zu", width);
39413b5b548SBaptiste Daroussin 	line_width = width * 2 + 3;
39513b5b548SBaptiste Daroussin 
39613b5b548SBaptiste Daroussin 	if (ppid == -1 ) {
39713b5b548SBaptiste Daroussin 		if (pipe(fd))
39813b5b548SBaptiste Daroussin 			err(2, "pipe");
39913b5b548SBaptiste Daroussin 
40013b5b548SBaptiste Daroussin 		switch (pid = fork()) {
40113b5b548SBaptiste Daroussin 		case 0:
40213b5b548SBaptiste Daroussin 			/* child */
40313b5b548SBaptiste Daroussin 			/* We don't read from the pipe. */
40413b5b548SBaptiste Daroussin 			close(fd[0]);
40513b5b548SBaptiste Daroussin 			if (dup2(fd[1], STDOUT_FILENO) == -1)
40613b5b548SBaptiste Daroussin 				err(2, "child could not duplicate descriptor");
40713b5b548SBaptiste Daroussin 			/* Free unused descriptor. */
40813b5b548SBaptiste Daroussin 			close(fd[1]);
40913b5b548SBaptiste Daroussin 			execvp(diffprog, diffargv);
41013b5b548SBaptiste Daroussin 			err(2, "could not execute diff: %s", diffprog);
41113b5b548SBaptiste Daroussin 			break;
41213b5b548SBaptiste Daroussin 		case -1:
41313b5b548SBaptiste Daroussin 			err(2, "could not fork");
41413b5b548SBaptiste Daroussin 			break;
41513b5b548SBaptiste Daroussin 		}
41613b5b548SBaptiste Daroussin 
41713b5b548SBaptiste Daroussin 		/* parent */
41813b5b548SBaptiste Daroussin 		/* We don't write to the pipe. */
41913b5b548SBaptiste Daroussin 		close(fd[1]);
42013b5b548SBaptiste Daroussin 
42113b5b548SBaptiste Daroussin 		/* Open pipe to diff command. */
42213b5b548SBaptiste Daroussin 		if ((diffpipe = fdopen(fd[0], "r")) == NULL)
42313b5b548SBaptiste Daroussin 			err(2, "could not open diff pipe");
42413b5b548SBaptiste Daroussin 	}
42513b5b548SBaptiste Daroussin 	if ((file1 = fopen(filename1, "r")) == NULL)
42613b5b548SBaptiste Daroussin 		err(2, "could not open %s", filename1);
42713b5b548SBaptiste Daroussin 	if ((file2 = fopen(filename2, "r")) == NULL)
42813b5b548SBaptiste Daroussin 		err(2, "could not open %s", filename2);
42913b5b548SBaptiste Daroussin 	if (!istextfile(file1) || !istextfile(file2)) {
43013b5b548SBaptiste Daroussin 		/* Close open files and pipe, delete temps */
43113b5b548SBaptiste Daroussin 		fclose(file1);
43213b5b548SBaptiste Daroussin 		fclose(file2);
43313b5b548SBaptiste Daroussin 		fclose(diffpipe);
43413b5b548SBaptiste Daroussin 		if (tmp1)
43513b5b548SBaptiste Daroussin 			if (unlink(tmp1))
43613b5b548SBaptiste Daroussin 				warn("Error deleting %s.", tmp1);
43713b5b548SBaptiste Daroussin 		if (tmp2)
43813b5b548SBaptiste Daroussin 			if (unlink(tmp2))
43913b5b548SBaptiste Daroussin 				warn("Error deleting %s.", tmp2);
44013b5b548SBaptiste Daroussin 		free(tmp1);
44113b5b548SBaptiste Daroussin 		free(tmp2);
44213b5b548SBaptiste Daroussin 		binexec(diffprog, filename1, filename2);
44313b5b548SBaptiste Daroussin 	}
44413b5b548SBaptiste Daroussin 	/* Line numbers start at one. */
44513b5b548SBaptiste Daroussin 	file1ln = file2ln = 1;
44613b5b548SBaptiste Daroussin 
44713b5b548SBaptiste Daroussin 	/* Read and parse diff output. */
44813b5b548SBaptiste Daroussin 	while (parsecmd(diffpipe, file1, file2) != EOF)
44913b5b548SBaptiste Daroussin 		;
45013b5b548SBaptiste Daroussin 	fclose(diffpipe);
45113b5b548SBaptiste Daroussin 
45213b5b548SBaptiste Daroussin 	/* Wait for diff to exit. */
45313b5b548SBaptiste Daroussin 	if (waitpid(pid, &status, 0) == -1 || !WIFEXITED(status) ||
45413b5b548SBaptiste Daroussin 	    WEXITSTATUS(status) >= 2)
45513b5b548SBaptiste Daroussin 		err(2, "diff exited abnormally.");
45613b5b548SBaptiste Daroussin 
45713b5b548SBaptiste Daroussin 	/* Delete and free unneeded temporary files. */
45813b5b548SBaptiste Daroussin 	if (tmp1)
45913b5b548SBaptiste Daroussin 		if (unlink(tmp1))
46013b5b548SBaptiste Daroussin 			warn("Error deleting %s.", tmp1);
46113b5b548SBaptiste Daroussin 	if (tmp2)
46213b5b548SBaptiste Daroussin 		if (unlink(tmp2))
46313b5b548SBaptiste Daroussin 			warn("Error deleting %s.", tmp2);
46413b5b548SBaptiste Daroussin 	free(tmp1);
46513b5b548SBaptiste Daroussin 	free(tmp2);
46613b5b548SBaptiste Daroussin 	filename1 = filename2 = tmp1 = tmp2 = NULL;
46713b5b548SBaptiste Daroussin 
46813b5b548SBaptiste Daroussin 	/* No more diffs, so print common lines. */
46913b5b548SBaptiste Daroussin 	if (lflag)
47013b5b548SBaptiste Daroussin 		while ((s1 = xfgets(file1)))
47113b5b548SBaptiste Daroussin 			enqueue(s1, ' ', NULL);
47213b5b548SBaptiste Daroussin 	else
47313b5b548SBaptiste Daroussin 		for (;;) {
47413b5b548SBaptiste Daroussin 			s1 = xfgets(file1);
47513b5b548SBaptiste Daroussin 			s2 = xfgets(file2);
47613b5b548SBaptiste Daroussin 			if (s1 || s2)
47713b5b548SBaptiste Daroussin 				enqueue(s1, ' ', s2);
47813b5b548SBaptiste Daroussin 			else
47913b5b548SBaptiste Daroussin 				break;
48013b5b548SBaptiste Daroussin 		}
48113b5b548SBaptiste Daroussin 	fclose(file1);
48213b5b548SBaptiste Daroussin 	fclose(file2);
48313b5b548SBaptiste Daroussin 	/* Process unmodified lines. */
48413b5b548SBaptiste Daroussin 	processq();
48513b5b548SBaptiste Daroussin 
48613b5b548SBaptiste Daroussin 	/* Return diff exit status. */
48713b5b548SBaptiste Daroussin 	return (WEXITSTATUS(status));
48813b5b548SBaptiste Daroussin }
48913b5b548SBaptiste Daroussin 
49013b5b548SBaptiste Daroussin /*
49113b5b548SBaptiste Daroussin  * When sdiff/zsdiff detects a binary file as input, executes them with
49213b5b548SBaptiste Daroussin  * diff/zdiff to maintain the same behavior as GNU sdiff with binary input.
49313b5b548SBaptiste Daroussin  */
49413b5b548SBaptiste Daroussin static void
49513b5b548SBaptiste Daroussin binexec(char *diffprog, char *f1, char *f2)
49613b5b548SBaptiste Daroussin {
49713b5b548SBaptiste Daroussin 
49813b5b548SBaptiste Daroussin 	char *args[] = {diffprog, f1, f2, (char *) 0};
49913b5b548SBaptiste Daroussin 	execv(diffprog, args);
50013b5b548SBaptiste Daroussin 
50113b5b548SBaptiste Daroussin 	/* If execv() fails, sdiff's execution will continue below. */
50213b5b548SBaptiste Daroussin 	errx(1, "Could not execute diff process.\n");
50313b5b548SBaptiste Daroussin }
50413b5b548SBaptiste Daroussin 
50513b5b548SBaptiste Daroussin /*
50613b5b548SBaptiste Daroussin  * Checks whether a file appears to be a text file.
50713b5b548SBaptiste Daroussin  */
50813b5b548SBaptiste Daroussin static int
50913b5b548SBaptiste Daroussin istextfile(FILE *f)
51013b5b548SBaptiste Daroussin {
51113b5b548SBaptiste Daroussin 	int	i;
51213b5b548SBaptiste Daroussin 	char ch;
51313b5b548SBaptiste Daroussin 
51413b5b548SBaptiste Daroussin 	if (f == NULL)
51513b5b548SBaptiste Daroussin 		return (1);
51613b5b548SBaptiste Daroussin 	rewind(f);
517*da5c2c42SBaptiste Daroussin 	for (i = 0; i <= MAX_CHECK; i++) {
51813b5b548SBaptiste Daroussin 		ch = fgetc(f);
51913b5b548SBaptiste Daroussin 		if (ch == '\0') {
52013b5b548SBaptiste Daroussin 			rewind(f);
52113b5b548SBaptiste Daroussin 			return (0);
52213b5b548SBaptiste Daroussin 		}
523*da5c2c42SBaptiste Daroussin 		if (ch == EOF)
524*da5c2c42SBaptiste Daroussin 			break;
52513b5b548SBaptiste Daroussin 	}
52613b5b548SBaptiste Daroussin 	rewind(f);
52713b5b548SBaptiste Daroussin 	return (1);
52813b5b548SBaptiste Daroussin }
52913b5b548SBaptiste Daroussin 
53013b5b548SBaptiste Daroussin /*
53113b5b548SBaptiste Daroussin  * Prints an individual column (left or right), taking into account
53213b5b548SBaptiste Daroussin  * that tabs are variable-width.  Takes a string, the current column
53313b5b548SBaptiste Daroussin  * the cursor is on the screen, and the maximum value of the column.
53413b5b548SBaptiste Daroussin  * The column value is updated as we go along.
53513b5b548SBaptiste Daroussin  */
53613b5b548SBaptiste Daroussin static void
53713b5b548SBaptiste Daroussin printcol(const char *s, size_t *col, const size_t col_max)
53813b5b548SBaptiste Daroussin {
53913b5b548SBaptiste Daroussin 
54013b5b548SBaptiste Daroussin 	for (; *s && *col < col_max; ++s) {
54113b5b548SBaptiste Daroussin 		size_t new_col;
54213b5b548SBaptiste Daroussin 
54313b5b548SBaptiste Daroussin 		switch (*s) {
54413b5b548SBaptiste Daroussin 		case '\t':
54513b5b548SBaptiste Daroussin 			/*
54613b5b548SBaptiste Daroussin 			 * If rounding to next multiple of eight causes
54713b5b548SBaptiste Daroussin 			 * an integer overflow, just return.
54813b5b548SBaptiste Daroussin 			 */
54913b5b548SBaptiste Daroussin 			if (*col > SIZE_MAX - 8)
55013b5b548SBaptiste Daroussin 				return;
55113b5b548SBaptiste Daroussin 
55213b5b548SBaptiste Daroussin 			/* Round to next multiple of eight. */
55313b5b548SBaptiste Daroussin 			new_col = (*col / 8 + 1) * 8;
55413b5b548SBaptiste Daroussin 
55513b5b548SBaptiste Daroussin 			/*
55613b5b548SBaptiste Daroussin 			 * If printing the tab goes past the column
55713b5b548SBaptiste Daroussin 			 * width, don't print it and just quit.
55813b5b548SBaptiste Daroussin 			 */
55913b5b548SBaptiste Daroussin 			if (new_col > col_max)
56013b5b548SBaptiste Daroussin 				return;
56113b5b548SBaptiste Daroussin 			*col = new_col;
56213b5b548SBaptiste Daroussin 			break;
56313b5b548SBaptiste Daroussin 		default:
56413b5b548SBaptiste Daroussin 			++(*col);
56513b5b548SBaptiste Daroussin 		}
56613b5b548SBaptiste Daroussin 		putchar(*s);
56713b5b548SBaptiste Daroussin 	}
56813b5b548SBaptiste Daroussin }
56913b5b548SBaptiste Daroussin 
57013b5b548SBaptiste Daroussin /*
57113b5b548SBaptiste Daroussin  * Prompts user to either choose between two strings or edit one, both,
57213b5b548SBaptiste Daroussin  * or neither.
57313b5b548SBaptiste Daroussin  */
57413b5b548SBaptiste Daroussin static void
57513b5b548SBaptiste Daroussin prompt(const char *s1, const char *s2)
57613b5b548SBaptiste Daroussin {
57713b5b548SBaptiste Daroussin 	char *cmd;
57813b5b548SBaptiste Daroussin 
57913b5b548SBaptiste Daroussin 	/* Print command prompt. */
58013b5b548SBaptiste Daroussin 	putchar('%');
58113b5b548SBaptiste Daroussin 
58213b5b548SBaptiste Daroussin 	/* Get user input. */
58313b5b548SBaptiste Daroussin 	for (; (cmd = xfgets(stdin)); free(cmd)) {
58413b5b548SBaptiste Daroussin 		const char *p;
58513b5b548SBaptiste Daroussin 
58613b5b548SBaptiste Daroussin 		/* Skip leading whitespace. */
58713b5b548SBaptiste Daroussin 		for (p = cmd; isspace(*p); ++p)
58813b5b548SBaptiste Daroussin 			;
58913b5b548SBaptiste Daroussin 		switch (*p) {
59013b5b548SBaptiste Daroussin 		case 'e':
59113b5b548SBaptiste Daroussin 			/* Skip `e'. */
59213b5b548SBaptiste Daroussin 			++p;
59313b5b548SBaptiste Daroussin 			if (eparse(p, s1, s2) == -1)
59413b5b548SBaptiste Daroussin 				goto USAGE;
59513b5b548SBaptiste Daroussin 			break;
59613b5b548SBaptiste Daroussin 		case 'l':
59713b5b548SBaptiste Daroussin 		case '1':
59813b5b548SBaptiste Daroussin 			/* Choose left column as-is. */
59913b5b548SBaptiste Daroussin 			if (s1 != NULL)
60013b5b548SBaptiste Daroussin 				fprintf(outfp, "%s\n", s1);
60113b5b548SBaptiste Daroussin 			/* End of command parsing. */
60213b5b548SBaptiste Daroussin 			break;
60313b5b548SBaptiste Daroussin 		case 'q':
60413b5b548SBaptiste Daroussin 			goto QUIT;
60513b5b548SBaptiste Daroussin 		case 'r':
60613b5b548SBaptiste Daroussin 		case '2':
60713b5b548SBaptiste Daroussin 			/* Choose right column as-is. */
60813b5b548SBaptiste Daroussin 			if (s2 != NULL)
60913b5b548SBaptiste Daroussin 				fprintf(outfp, "%s\n", s2);
61013b5b548SBaptiste Daroussin 			/* End of command parsing. */
61113b5b548SBaptiste Daroussin 			break;
61213b5b548SBaptiste Daroussin 		case 's':
61313b5b548SBaptiste Daroussin 			sflag = 1;
61413b5b548SBaptiste Daroussin 			goto PROMPT;
61513b5b548SBaptiste Daroussin 		case 'v':
61613b5b548SBaptiste Daroussin 			sflag = 0;
61713b5b548SBaptiste Daroussin 			/* FALLTHROUGH */
61813b5b548SBaptiste Daroussin 		default:
61913b5b548SBaptiste Daroussin 			/* Interactive usage help. */
62013b5b548SBaptiste Daroussin USAGE:
62113b5b548SBaptiste Daroussin 			int_usage();
62213b5b548SBaptiste Daroussin PROMPT:
62313b5b548SBaptiste Daroussin 			putchar('%');
62413b5b548SBaptiste Daroussin 
62513b5b548SBaptiste Daroussin 			/* Prompt user again. */
62613b5b548SBaptiste Daroussin 			continue;
62713b5b548SBaptiste Daroussin 		}
62813b5b548SBaptiste Daroussin 		free(cmd);
62913b5b548SBaptiste Daroussin 		return;
63013b5b548SBaptiste Daroussin 	}
63113b5b548SBaptiste Daroussin 
63213b5b548SBaptiste Daroussin 	/*
63313b5b548SBaptiste Daroussin 	 * If there was no error, we received an EOF from stdin, so we
63413b5b548SBaptiste Daroussin 	 * should quit.
63513b5b548SBaptiste Daroussin 	 */
63613b5b548SBaptiste Daroussin QUIT:
63713b5b548SBaptiste Daroussin 	fclose(outfp);
63813b5b548SBaptiste Daroussin 	exit(0);
63913b5b548SBaptiste Daroussin }
64013b5b548SBaptiste Daroussin 
64113b5b548SBaptiste Daroussin /*
64213b5b548SBaptiste Daroussin  * Takes two strings, separated by a column divider.  NULL strings are
64313b5b548SBaptiste Daroussin  * treated as empty columns.  If the divider is the ` ' character, the
64413b5b548SBaptiste Daroussin  * second column is not printed (-l flag).  In this case, the second
64513b5b548SBaptiste Daroussin  * string must be NULL.  When the second column is NULL, the divider
64613b5b548SBaptiste Daroussin  * does not print the trailing space following the divider character.
64713b5b548SBaptiste Daroussin  *
64813b5b548SBaptiste Daroussin  * Takes into account that tabs can take multiple columns.
64913b5b548SBaptiste Daroussin  */
65013b5b548SBaptiste Daroussin static void
65113b5b548SBaptiste Daroussin println(const char *s1, const char div, const char *s2)
65213b5b548SBaptiste Daroussin {
65313b5b548SBaptiste Daroussin 	size_t col;
65413b5b548SBaptiste Daroussin 
65513b5b548SBaptiste Daroussin 	/* Print first column.  Skips if s1 == NULL. */
65613b5b548SBaptiste Daroussin 	col = 0;
65713b5b548SBaptiste Daroussin 	if (s1) {
65813b5b548SBaptiste Daroussin 		/* Skip angle bracket and space. */
65913b5b548SBaptiste Daroussin 		printcol(s1, &col, width);
66013b5b548SBaptiste Daroussin 
66113b5b548SBaptiste Daroussin 	}
66213b5b548SBaptiste Daroussin 
66313b5b548SBaptiste Daroussin 	/* Otherwise, we pad this column up to width. */
66413b5b548SBaptiste Daroussin 	for (; col < width; ++col)
66513b5b548SBaptiste Daroussin 		putchar(' ');
66613b5b548SBaptiste Daroussin 
66713b5b548SBaptiste Daroussin 	/* Only print left column. */
66813b5b548SBaptiste Daroussin 	if (div == ' ' && !s2) {
66913b5b548SBaptiste Daroussin 		printf(" (\n");
67013b5b548SBaptiste Daroussin 		return;
67113b5b548SBaptiste Daroussin 	}
67213b5b548SBaptiste Daroussin 
67313b5b548SBaptiste Daroussin 	/*
67413b5b548SBaptiste Daroussin 	 * Print column divider.  If there is no second column, we don't
67513b5b548SBaptiste Daroussin 	 * need to add the space for padding.
67613b5b548SBaptiste Daroussin 	 */
67713b5b548SBaptiste Daroussin 	if (!s2) {
67813b5b548SBaptiste Daroussin 		printf(" %c\n", div);
67913b5b548SBaptiste Daroussin 		return;
68013b5b548SBaptiste Daroussin 	}
68113b5b548SBaptiste Daroussin 	printf(" %c ", div);
68213b5b548SBaptiste Daroussin 	col += 3;
68313b5b548SBaptiste Daroussin 
68413b5b548SBaptiste Daroussin 	/* Skip angle bracket and space. */
68513b5b548SBaptiste Daroussin 	printcol(s2, &col, line_width);
68613b5b548SBaptiste Daroussin 
68713b5b548SBaptiste Daroussin 	putchar('\n');
68813b5b548SBaptiste Daroussin }
68913b5b548SBaptiste Daroussin 
69013b5b548SBaptiste Daroussin /*
69113b5b548SBaptiste Daroussin  * Reads a line from file and returns as a string.  If EOF is reached,
69213b5b548SBaptiste Daroussin  * NULL is returned.  The returned string must be freed afterwards.
69313b5b548SBaptiste Daroussin  */
69413b5b548SBaptiste Daroussin static char *
69513b5b548SBaptiste Daroussin xfgets(FILE *file)
69613b5b548SBaptiste Daroussin {
69713b5b548SBaptiste Daroussin 	const char delim[3] = {'\0', '\0', '\0'};
69813b5b548SBaptiste Daroussin 	char *s;
69913b5b548SBaptiste Daroussin 
70013b5b548SBaptiste Daroussin 	/* XXX - Is this necessary? */
70113b5b548SBaptiste Daroussin 	clearerr(file);
70213b5b548SBaptiste Daroussin 
70313b5b548SBaptiste Daroussin 	if (!(s = fparseln(file, NULL, NULL, delim, 0)) &&
70413b5b548SBaptiste Daroussin 	    ferror(file))
70513b5b548SBaptiste Daroussin 		err(2, "error reading file");
70613b5b548SBaptiste Daroussin 
70713b5b548SBaptiste Daroussin 	if (!s) {
70813b5b548SBaptiste Daroussin 		return (NULL);
70913b5b548SBaptiste Daroussin 	}
71013b5b548SBaptiste Daroussin 
71113b5b548SBaptiste Daroussin 	return (s);
71213b5b548SBaptiste Daroussin }
71313b5b548SBaptiste Daroussin 
71413b5b548SBaptiste Daroussin /*
71513b5b548SBaptiste Daroussin  * Parse ed commands from diffpipe and print lines from file1 (lines
71613b5b548SBaptiste Daroussin  * to change or delete) or file2 (lines to add or change).
71713b5b548SBaptiste Daroussin  * Returns EOF or 0.
71813b5b548SBaptiste Daroussin  */
71913b5b548SBaptiste Daroussin static int
72013b5b548SBaptiste Daroussin parsecmd(FILE *diffpipe, FILE *file1, FILE *file2)
72113b5b548SBaptiste Daroussin {
72213b5b548SBaptiste Daroussin 	size_t file1start, file1end, file2start, file2end, n;
72313b5b548SBaptiste Daroussin 	/* ed command line and pointer to characters in line */
72413b5b548SBaptiste Daroussin 	char *line, *p, *q;
72513b5b548SBaptiste Daroussin 	const char *errstr;
72613b5b548SBaptiste Daroussin 	char c, cmd;
72713b5b548SBaptiste Daroussin 
72813b5b548SBaptiste Daroussin 	/* Read ed command. */
72913b5b548SBaptiste Daroussin 	if (!(line = xfgets(diffpipe)))
73013b5b548SBaptiste Daroussin 		return (EOF);
73113b5b548SBaptiste Daroussin 
73213b5b548SBaptiste Daroussin 	p = line;
73313b5b548SBaptiste Daroussin 	/* Go to character after line number. */
73413b5b548SBaptiste Daroussin 	while (isdigit(*p))
73513b5b548SBaptiste Daroussin 		++p;
73613b5b548SBaptiste Daroussin 	c = *p;
73713b5b548SBaptiste Daroussin 	*p++ = 0;
73813b5b548SBaptiste Daroussin 	file1start = strtonum(line, 0, INT_MAX, &errstr);
73913b5b548SBaptiste Daroussin 	if (errstr)
74013b5b548SBaptiste Daroussin 		errx(2, "file1 start is %s: %s", errstr, line);
74113b5b548SBaptiste Daroussin 
74213b5b548SBaptiste Daroussin 	/* A range is specified for file1. */
74313b5b548SBaptiste Daroussin 	if (c == ',') {
74413b5b548SBaptiste Daroussin 		q = p;
74513b5b548SBaptiste Daroussin 		/* Go to character after file2end. */
74613b5b548SBaptiste Daroussin 		while (isdigit(*p))
74713b5b548SBaptiste Daroussin 			++p;
74813b5b548SBaptiste Daroussin 		c = *p;
74913b5b548SBaptiste Daroussin 		*p++ = 0;
75013b5b548SBaptiste Daroussin 		file1end = strtonum(q, 0, INT_MAX, &errstr);
75113b5b548SBaptiste Daroussin 		if (errstr)
75213b5b548SBaptiste Daroussin 			errx(2, "file1 end is %s: %s", errstr, line);
75313b5b548SBaptiste Daroussin 		if (file1start > file1end)
75413b5b548SBaptiste Daroussin 			errx(2, "invalid line range in file1: %s", line);
75513b5b548SBaptiste Daroussin 	} else
75613b5b548SBaptiste Daroussin 		file1end = file1start;
75713b5b548SBaptiste Daroussin 
75813b5b548SBaptiste Daroussin 	cmd = c;
75913b5b548SBaptiste Daroussin 	/* Check that cmd is valid. */
76013b5b548SBaptiste Daroussin 	if (!(cmd == 'a' || cmd == 'c' || cmd == 'd'))
76113b5b548SBaptiste Daroussin 		errx(2, "ed command not recognized: %c: %s", cmd, line);
76213b5b548SBaptiste Daroussin 
76313b5b548SBaptiste Daroussin 	q = p;
76413b5b548SBaptiste Daroussin 	/* Go to character after line number. */
76513b5b548SBaptiste Daroussin 	while (isdigit(*p))
76613b5b548SBaptiste Daroussin 		++p;
76713b5b548SBaptiste Daroussin 	c = *p;
76813b5b548SBaptiste Daroussin 	*p++ = 0;
76913b5b548SBaptiste Daroussin 	file2start = strtonum(q, 0, INT_MAX, &errstr);
77013b5b548SBaptiste Daroussin 	if (errstr)
77113b5b548SBaptiste Daroussin 		errx(2, "file2 start is %s: %s", errstr, line);
77213b5b548SBaptiste Daroussin 
77313b5b548SBaptiste Daroussin 	/*
77413b5b548SBaptiste Daroussin 	 * There should either be a comma signifying a second line
77513b5b548SBaptiste Daroussin 	 * number or the line should just end here.
77613b5b548SBaptiste Daroussin 	 */
77713b5b548SBaptiste Daroussin 	if (c != ',' && c != '\0')
77813b5b548SBaptiste Daroussin 		errx(2, "invalid line range in file2: %c: %s", c, line);
77913b5b548SBaptiste Daroussin 
78013b5b548SBaptiste Daroussin 	if (c == ',') {
78113b5b548SBaptiste Daroussin 
78213b5b548SBaptiste Daroussin 		file2end = strtonum(p, 0, INT_MAX, &errstr);
78313b5b548SBaptiste Daroussin 		if (errstr)
78413b5b548SBaptiste Daroussin 			errx(2, "file2 end is %s: %s", errstr, line);
78513b5b548SBaptiste Daroussin 		if (file2start >= file2end)
78613b5b548SBaptiste Daroussin 			errx(2, "invalid line range in file2: %s", line);
78713b5b548SBaptiste Daroussin 	} else
78813b5b548SBaptiste Daroussin 		file2end = file2start;
78913b5b548SBaptiste Daroussin 
79013b5b548SBaptiste Daroussin 	/* Appends happen _after_ stated line. */
79113b5b548SBaptiste Daroussin 	if (cmd == 'a') {
79213b5b548SBaptiste Daroussin 		if (file1start != file1end)
79313b5b548SBaptiste Daroussin 			errx(2, "append cannot have a file1 range: %s",
79413b5b548SBaptiste Daroussin 			    line);
79513b5b548SBaptiste Daroussin 		if (file1start == SIZE_MAX)
79613b5b548SBaptiste Daroussin 			errx(2, "file1 line range too high: %s", line);
79713b5b548SBaptiste Daroussin 		file1start = ++file1end;
79813b5b548SBaptiste Daroussin 	}
79913b5b548SBaptiste Daroussin 	/*
80013b5b548SBaptiste Daroussin 	 * I'm not sure what the deal is with the line numbers for
80113b5b548SBaptiste Daroussin 	 * deletes, though.
80213b5b548SBaptiste Daroussin 	 */
80313b5b548SBaptiste Daroussin 	else if (cmd == 'd') {
80413b5b548SBaptiste Daroussin 		if (file2start != file2end)
80513b5b548SBaptiste Daroussin 			errx(2, "delete cannot have a file2 range: %s",
80613b5b548SBaptiste Daroussin 			    line);
80713b5b548SBaptiste Daroussin 		if (file2start == SIZE_MAX)
80813b5b548SBaptiste Daroussin 			errx(2, "file2 line range too high: %s", line);
80913b5b548SBaptiste Daroussin 		file2start = ++file2end;
81013b5b548SBaptiste Daroussin 	}
81113b5b548SBaptiste Daroussin 
81213b5b548SBaptiste Daroussin 	/*
81313b5b548SBaptiste Daroussin 	 * Continue reading file1 and file2 until we reach line numbers
81413b5b548SBaptiste Daroussin 	 * specified by diff.  Should only happen with -I flag.
81513b5b548SBaptiste Daroussin 	 */
81613b5b548SBaptiste Daroussin 	for (; file1ln < file1start && file2ln < file2start;
81713b5b548SBaptiste Daroussin 	    ++file1ln, ++file2ln) {
81813b5b548SBaptiste Daroussin 		char *s1, *s2;
81913b5b548SBaptiste Daroussin 
82013b5b548SBaptiste Daroussin 		if (!(s1 = xfgets(file1)))
82113b5b548SBaptiste Daroussin 			errx(2, "file1 shorter than expected");
82213b5b548SBaptiste Daroussin 		if (!(s2 = xfgets(file2)))
82313b5b548SBaptiste Daroussin 			errx(2, "file2 shorter than expected");
82413b5b548SBaptiste Daroussin 
82513b5b548SBaptiste Daroussin 		/* If the -l flag was specified, print only left column. */
82613b5b548SBaptiste Daroussin 		if (lflag) {
82713b5b548SBaptiste Daroussin 			free(s2);
82813b5b548SBaptiste Daroussin 			/*
82913b5b548SBaptiste Daroussin 			 * XXX - If -l and -I are both specified, all
83013b5b548SBaptiste Daroussin 			 * unchanged or ignored lines are shown with a
83113b5b548SBaptiste Daroussin 			 * `(' divider.  This matches GNU sdiff, but I
83213b5b548SBaptiste Daroussin 			 * believe it is a bug.  Just check out:
83313b5b548SBaptiste Daroussin 			 * gsdiff -l -I '^$' samefile samefile.
83413b5b548SBaptiste Daroussin 			 */
83513b5b548SBaptiste Daroussin 			if (Iflag)
83613b5b548SBaptiste Daroussin 				enqueue(s1, '(', NULL);
83713b5b548SBaptiste Daroussin 			else
83813b5b548SBaptiste Daroussin 				enqueue(s1, ' ', NULL);
83913b5b548SBaptiste Daroussin 		} else
84013b5b548SBaptiste Daroussin 			enqueue(s1, ' ', s2);
84113b5b548SBaptiste Daroussin 	}
84213b5b548SBaptiste Daroussin 	/* Ignore deleted lines. */
84313b5b548SBaptiste Daroussin 	for (; file1ln < file1start; ++file1ln) {
84413b5b548SBaptiste Daroussin 		char *s;
84513b5b548SBaptiste Daroussin 
84613b5b548SBaptiste Daroussin 		if (!(s = xfgets(file1)))
84713b5b548SBaptiste Daroussin 			errx(2, "file1 shorter than expected");
84813b5b548SBaptiste Daroussin 
84913b5b548SBaptiste Daroussin 		enqueue(s, '(', NULL);
85013b5b548SBaptiste Daroussin 	}
85113b5b548SBaptiste Daroussin 	/* Ignore added lines. */
85213b5b548SBaptiste Daroussin 	for (; file2ln < file2start; ++file2ln) {
85313b5b548SBaptiste Daroussin 		char *s;
85413b5b548SBaptiste Daroussin 
85513b5b548SBaptiste Daroussin 		if (!(s = xfgets(file2)))
85613b5b548SBaptiste Daroussin 			errx(2, "file2 shorter than expected");
85713b5b548SBaptiste Daroussin 
85813b5b548SBaptiste Daroussin 		/* If -l flag was given, don't print right column. */
85913b5b548SBaptiste Daroussin 		if (lflag)
86013b5b548SBaptiste Daroussin 			free(s);
86113b5b548SBaptiste Daroussin 		else
86213b5b548SBaptiste Daroussin 			enqueue(NULL, ')', s);
86313b5b548SBaptiste Daroussin 	}
86413b5b548SBaptiste Daroussin 
86513b5b548SBaptiste Daroussin 	/* Process unmodified or skipped lines. */
86613b5b548SBaptiste Daroussin 	processq();
86713b5b548SBaptiste Daroussin 
86813b5b548SBaptiste Daroussin 	switch (cmd) {
86913b5b548SBaptiste Daroussin 	case 'a':
87013b5b548SBaptiste Daroussin 		printa(file2, file2end);
87113b5b548SBaptiste Daroussin 		n = file2end - file2start + 1;
87213b5b548SBaptiste Daroussin 		break;
87313b5b548SBaptiste Daroussin 	case 'c':
87413b5b548SBaptiste Daroussin 		printc(file1, file1end, file2, file2end);
87513b5b548SBaptiste Daroussin 		n = file1end - file1start + 1 + 1 + file2end - file2start + 1;
87613b5b548SBaptiste Daroussin 		break;
87713b5b548SBaptiste Daroussin 	case 'd':
87813b5b548SBaptiste Daroussin 		printd(file1, file1end);
87913b5b548SBaptiste Daroussin 		n = file1end - file1start + 1;
88013b5b548SBaptiste Daroussin 		break;
88113b5b548SBaptiste Daroussin 	default:
88213b5b548SBaptiste Daroussin 		errx(2, "invalid diff command: %c: %s", cmd, line);
88313b5b548SBaptiste Daroussin 	}
88413b5b548SBaptiste Daroussin 	free(line);
88513b5b548SBaptiste Daroussin 
88613b5b548SBaptiste Daroussin 	/* Skip to next ed line. */
88713b5b548SBaptiste Daroussin 	while (n--) {
88813b5b548SBaptiste Daroussin 		if (!(line = xfgets(diffpipe)))
88913b5b548SBaptiste Daroussin 			errx(2, "diff ended early");
89013b5b548SBaptiste Daroussin 		free(line);
89113b5b548SBaptiste Daroussin 	}
89213b5b548SBaptiste Daroussin 
89313b5b548SBaptiste Daroussin 	return (0);
89413b5b548SBaptiste Daroussin }
89513b5b548SBaptiste Daroussin 
89613b5b548SBaptiste Daroussin /*
89713b5b548SBaptiste Daroussin  * Queues up a diff line.
89813b5b548SBaptiste Daroussin  */
89913b5b548SBaptiste Daroussin static void
90013b5b548SBaptiste Daroussin enqueue(char *left, char div, char *right)
90113b5b548SBaptiste Daroussin {
90213b5b548SBaptiste Daroussin 	struct diffline *diffp;
90313b5b548SBaptiste Daroussin 
90413b5b548SBaptiste Daroussin 	if (!(diffp = malloc(sizeof(struct diffline))))
90513b5b548SBaptiste Daroussin 		err(2, "enqueue");
90613b5b548SBaptiste Daroussin 	diffp->left = left;
90713b5b548SBaptiste Daroussin 	diffp->div = div;
90813b5b548SBaptiste Daroussin 	diffp->right = right;
90913b5b548SBaptiste Daroussin 	STAILQ_INSERT_TAIL(&diffhead, diffp, diffentries);
91013b5b548SBaptiste Daroussin }
91113b5b548SBaptiste Daroussin 
91213b5b548SBaptiste Daroussin /*
91313b5b548SBaptiste Daroussin  * Free a diffline structure and its elements.
91413b5b548SBaptiste Daroussin  */
91513b5b548SBaptiste Daroussin static void
91613b5b548SBaptiste Daroussin freediff(struct diffline *diffp)
91713b5b548SBaptiste Daroussin {
91813b5b548SBaptiste Daroussin 
91913b5b548SBaptiste Daroussin 	free(diffp->left);
92013b5b548SBaptiste Daroussin 	free(diffp->right);
92113b5b548SBaptiste Daroussin 	free(diffp);
92213b5b548SBaptiste Daroussin }
92313b5b548SBaptiste Daroussin 
92413b5b548SBaptiste Daroussin /*
92513b5b548SBaptiste Daroussin  * Append second string into first.  Repeated appends to the same string
92613b5b548SBaptiste Daroussin  * are cached, making this an O(n) function, where n = strlen(append).
92713b5b548SBaptiste Daroussin  */
92813b5b548SBaptiste Daroussin static void
92913b5b548SBaptiste Daroussin astrcat(char **s, const char *append)
93013b5b548SBaptiste Daroussin {
93113b5b548SBaptiste Daroussin 	/* Length of string in previous run. */
93213b5b548SBaptiste Daroussin 	static size_t offset = 0;
93313b5b548SBaptiste Daroussin 	size_t newsiz;
93413b5b548SBaptiste Daroussin 	/*
93513b5b548SBaptiste Daroussin 	 * String from previous run.  Compared to *s to see if we are
93613b5b548SBaptiste Daroussin 	 * dealing with the same string.  If so, we can use offset.
93713b5b548SBaptiste Daroussin 	 */
93813b5b548SBaptiste Daroussin 	static const char *oldstr = NULL;
93913b5b548SBaptiste Daroussin 	char *newstr;
94013b5b548SBaptiste Daroussin 
94113b5b548SBaptiste Daroussin 	/*
94213b5b548SBaptiste Daroussin 	 * First string is NULL, so just copy append.
94313b5b548SBaptiste Daroussin 	 */
94413b5b548SBaptiste Daroussin 	if (!*s) {
94513b5b548SBaptiste Daroussin 		if (!(*s = strdup(append)))
94613b5b548SBaptiste Daroussin 			err(2, "astrcat");
94713b5b548SBaptiste Daroussin 
94813b5b548SBaptiste Daroussin 		/* Keep track of string. */
94913b5b548SBaptiste Daroussin 		offset = strlen(*s);
95013b5b548SBaptiste Daroussin 		oldstr = *s;
95113b5b548SBaptiste Daroussin 
95213b5b548SBaptiste Daroussin 		return;
95313b5b548SBaptiste Daroussin 	}
95413b5b548SBaptiste Daroussin 
95513b5b548SBaptiste Daroussin 	/*
95613b5b548SBaptiste Daroussin 	 * *s is a string so concatenate.
95713b5b548SBaptiste Daroussin 	 */
95813b5b548SBaptiste Daroussin 
95913b5b548SBaptiste Daroussin 	/* Did we process the same string in the last run? */
96013b5b548SBaptiste Daroussin 	/*
96113b5b548SBaptiste Daroussin 	 * If this is a different string from the one we just processed
96213b5b548SBaptiste Daroussin 	 * cache new string.
96313b5b548SBaptiste Daroussin 	 */
96413b5b548SBaptiste Daroussin 	if (oldstr != *s) {
96513b5b548SBaptiste Daroussin 		offset = strlen(*s);
96613b5b548SBaptiste Daroussin 		oldstr = *s;
96713b5b548SBaptiste Daroussin 	}
96813b5b548SBaptiste Daroussin 
96913b5b548SBaptiste Daroussin 	/* Size = strlen(*s) + \n + strlen(append) + '\0'. */
97013b5b548SBaptiste Daroussin 	newsiz = offset + 1 + strlen(append) + 1;
97113b5b548SBaptiste Daroussin 
97213b5b548SBaptiste Daroussin 	/* Resize *s to fit new string. */
97313b5b548SBaptiste Daroussin 	newstr = realloc(*s, newsiz);
97413b5b548SBaptiste Daroussin 	if (newstr == NULL)
97513b5b548SBaptiste Daroussin 		err(2, "astrcat");
97613b5b548SBaptiste Daroussin 	*s = newstr;
97713b5b548SBaptiste Daroussin 
97813b5b548SBaptiste Daroussin 	/* *s + offset should be end of string. */
97913b5b548SBaptiste Daroussin 	/* Concatenate. */
98013b5b548SBaptiste Daroussin 	strlcpy(*s + offset, "\n", newsiz - offset);
98113b5b548SBaptiste Daroussin 	strlcat(*s + offset, append, newsiz - offset);
98213b5b548SBaptiste Daroussin 
98313b5b548SBaptiste Daroussin 	/* New string length should be exactly newsiz - 1 characters. */
98413b5b548SBaptiste Daroussin 	/* Store generated string's values. */
98513b5b548SBaptiste Daroussin 	offset = newsiz - 1;
98613b5b548SBaptiste Daroussin 	oldstr = *s;
98713b5b548SBaptiste Daroussin }
98813b5b548SBaptiste Daroussin 
98913b5b548SBaptiste Daroussin /*
99013b5b548SBaptiste Daroussin  * Process diff set queue, printing, prompting, and saving each diff
99113b5b548SBaptiste Daroussin  * line stored in queue.
99213b5b548SBaptiste Daroussin  */
99313b5b548SBaptiste Daroussin static void
99413b5b548SBaptiste Daroussin processq(void)
99513b5b548SBaptiste Daroussin {
99613b5b548SBaptiste Daroussin 	struct diffline *diffp;
99713b5b548SBaptiste Daroussin 	char divc, *left, *right;
99813b5b548SBaptiste Daroussin 
99913b5b548SBaptiste Daroussin 	/* Don't process empty queue. */
100013b5b548SBaptiste Daroussin 	if (STAILQ_EMPTY(&diffhead))
100113b5b548SBaptiste Daroussin 		return;
100213b5b548SBaptiste Daroussin 
100313b5b548SBaptiste Daroussin 	/* Remember the divider. */
100413b5b548SBaptiste Daroussin 	divc = STAILQ_FIRST(&diffhead)->div;
100513b5b548SBaptiste Daroussin 
100613b5b548SBaptiste Daroussin 	left = NULL;
100713b5b548SBaptiste Daroussin 	right = NULL;
100813b5b548SBaptiste Daroussin 	/*
100913b5b548SBaptiste Daroussin 	 * Go through set of diffs, concatenating each line in left or
101013b5b548SBaptiste Daroussin 	 * right column into two long strings, `left' and `right'.
101113b5b548SBaptiste Daroussin 	 */
101213b5b548SBaptiste Daroussin 	STAILQ_FOREACH(diffp, &diffhead, diffentries) {
101313b5b548SBaptiste Daroussin 		/*
101413b5b548SBaptiste Daroussin 		 * Print changed lines if -s was given,
101513b5b548SBaptiste Daroussin 		 * print all lines if -s was not given.
101613b5b548SBaptiste Daroussin 		 */
101713b5b548SBaptiste Daroussin 		if (!sflag || diffp->div == '|' || diffp->div == '<' ||
101813b5b548SBaptiste Daroussin 		    diffp->div == '>')
101913b5b548SBaptiste Daroussin 			println(diffp->left, diffp->div, diffp->right);
102013b5b548SBaptiste Daroussin 
102113b5b548SBaptiste Daroussin 		/* Append new lines to diff set. */
102213b5b548SBaptiste Daroussin 		if (diffp->left)
102313b5b548SBaptiste Daroussin 			astrcat(&left, diffp->left);
102413b5b548SBaptiste Daroussin 		if (diffp->right)
102513b5b548SBaptiste Daroussin 			astrcat(&right, diffp->right);
102613b5b548SBaptiste Daroussin 	}
102713b5b548SBaptiste Daroussin 
102813b5b548SBaptiste Daroussin 	/* Empty queue and free each diff line and its elements. */
102913b5b548SBaptiste Daroussin 	while (!STAILQ_EMPTY(&diffhead)) {
103013b5b548SBaptiste Daroussin 		diffp = STAILQ_FIRST(&diffhead);
103113b5b548SBaptiste Daroussin 		STAILQ_REMOVE_HEAD(&diffhead, diffentries);
103213b5b548SBaptiste Daroussin 		freediff(diffp);
103313b5b548SBaptiste Daroussin 	}
103413b5b548SBaptiste Daroussin 
103513b5b548SBaptiste Daroussin 	/* Write to outfp, prompting user if lines are different. */
103613b5b548SBaptiste Daroussin 	if (outfp)
103713b5b548SBaptiste Daroussin 		switch (divc) {
103813b5b548SBaptiste Daroussin 		case ' ': case '(': case ')':
103913b5b548SBaptiste Daroussin 			fprintf(outfp, "%s\n", left);
104013b5b548SBaptiste Daroussin 			break;
104113b5b548SBaptiste Daroussin 		case '|': case '<': case '>':
104213b5b548SBaptiste Daroussin 			prompt(left, right);
104313b5b548SBaptiste Daroussin 			break;
104413b5b548SBaptiste Daroussin 		default:
104513b5b548SBaptiste Daroussin 			errx(2, "invalid divider: %c", divc);
104613b5b548SBaptiste Daroussin 		}
104713b5b548SBaptiste Daroussin 
104813b5b548SBaptiste Daroussin 	/* Free left and right. */
104913b5b548SBaptiste Daroussin 	free(left);
105013b5b548SBaptiste Daroussin 	free(right);
105113b5b548SBaptiste Daroussin }
105213b5b548SBaptiste Daroussin 
105313b5b548SBaptiste Daroussin /*
105413b5b548SBaptiste Daroussin  * Print lines following an (a)ppend command.
105513b5b548SBaptiste Daroussin  */
105613b5b548SBaptiste Daroussin static void
105713b5b548SBaptiste Daroussin printa(FILE *file, size_t line2)
105813b5b548SBaptiste Daroussin {
105913b5b548SBaptiste Daroussin 	char *line;
106013b5b548SBaptiste Daroussin 
106113b5b548SBaptiste Daroussin 	for (; file2ln <= line2; ++file2ln) {
106213b5b548SBaptiste Daroussin 		if (!(line = xfgets(file)))
106313b5b548SBaptiste Daroussin 			errx(2, "append ended early");
106413b5b548SBaptiste Daroussin 		enqueue(NULL, '>', line);
106513b5b548SBaptiste Daroussin 	}
106613b5b548SBaptiste Daroussin 	processq();
106713b5b548SBaptiste Daroussin }
106813b5b548SBaptiste Daroussin 
106913b5b548SBaptiste Daroussin /*
107013b5b548SBaptiste Daroussin  * Print lines following a (c)hange command, from file1ln to file1end
107113b5b548SBaptiste Daroussin  * and from file2ln to file2end.
107213b5b548SBaptiste Daroussin  */
107313b5b548SBaptiste Daroussin static void
107413b5b548SBaptiste Daroussin printc(FILE *file1, size_t file1end, FILE *file2, size_t file2end)
107513b5b548SBaptiste Daroussin {
107613b5b548SBaptiste Daroussin 	struct fileline {
107713b5b548SBaptiste Daroussin 		STAILQ_ENTRY(fileline)	 fileentries;
107813b5b548SBaptiste Daroussin 		char			*line;
107913b5b548SBaptiste Daroussin 	};
108013b5b548SBaptiste Daroussin 	STAILQ_HEAD(, fileline) delqhead = STAILQ_HEAD_INITIALIZER(delqhead);
108113b5b548SBaptiste Daroussin 
108213b5b548SBaptiste Daroussin 	/* Read lines to be deleted. */
108313b5b548SBaptiste Daroussin 	for (; file1ln <= file1end; ++file1ln) {
108413b5b548SBaptiste Daroussin 		struct fileline *linep;
108513b5b548SBaptiste Daroussin 		char *line1;
108613b5b548SBaptiste Daroussin 
108713b5b548SBaptiste Daroussin 		/* Read lines from both. */
108813b5b548SBaptiste Daroussin 		if (!(line1 = xfgets(file1)))
108913b5b548SBaptiste Daroussin 			errx(2, "error reading file1 in delete in change");
109013b5b548SBaptiste Daroussin 
109113b5b548SBaptiste Daroussin 		/* Add to delete queue. */
109213b5b548SBaptiste Daroussin 		if (!(linep = malloc(sizeof(struct fileline))))
109313b5b548SBaptiste Daroussin 			err(2, "printc");
109413b5b548SBaptiste Daroussin 		linep->line = line1;
109513b5b548SBaptiste Daroussin 		STAILQ_INSERT_TAIL(&delqhead, linep, fileentries);
109613b5b548SBaptiste Daroussin 	}
109713b5b548SBaptiste Daroussin 
109813b5b548SBaptiste Daroussin 	/* Process changed lines.. */
109913b5b548SBaptiste Daroussin 	for (; !STAILQ_EMPTY(&delqhead) && file2ln <= file2end;
110013b5b548SBaptiste Daroussin 	    ++file2ln) {
110113b5b548SBaptiste Daroussin 		struct fileline *del;
110213b5b548SBaptiste Daroussin 		char *add;
110313b5b548SBaptiste Daroussin 
110413b5b548SBaptiste Daroussin 		/* Get add line. */
110513b5b548SBaptiste Daroussin 		if (!(add = xfgets(file2)))
110613b5b548SBaptiste Daroussin 			errx(2, "error reading add in change");
110713b5b548SBaptiste Daroussin 
110813b5b548SBaptiste Daroussin 		del = STAILQ_FIRST(&delqhead);
110913b5b548SBaptiste Daroussin 		enqueue(del->line, '|', add);
111013b5b548SBaptiste Daroussin 		STAILQ_REMOVE_HEAD(&delqhead, fileentries);
111113b5b548SBaptiste Daroussin 		/*
111213b5b548SBaptiste Daroussin 		 * Free fileline structure but not its elements since
111313b5b548SBaptiste Daroussin 		 * they are queued up.
111413b5b548SBaptiste Daroussin 		 */
111513b5b548SBaptiste Daroussin 		free(del);
111613b5b548SBaptiste Daroussin 	}
111713b5b548SBaptiste Daroussin 	processq();
111813b5b548SBaptiste Daroussin 
111913b5b548SBaptiste Daroussin 	/* Process remaining lines to add. */
112013b5b548SBaptiste Daroussin 	for (; file2ln <= file2end; ++file2ln) {
112113b5b548SBaptiste Daroussin 		char *add;
112213b5b548SBaptiste Daroussin 
112313b5b548SBaptiste Daroussin 		/* Get add line. */
112413b5b548SBaptiste Daroussin 		if (!(add = xfgets(file2)))
112513b5b548SBaptiste Daroussin 			errx(2, "error reading add in change");
112613b5b548SBaptiste Daroussin 
112713b5b548SBaptiste Daroussin 		enqueue(NULL, '>', add);
112813b5b548SBaptiste Daroussin 	}
112913b5b548SBaptiste Daroussin 	processq();
113013b5b548SBaptiste Daroussin 
113113b5b548SBaptiste Daroussin 	/* Process remaining lines to delete. */
113213b5b548SBaptiste Daroussin 	while (!STAILQ_EMPTY(&delqhead)) {
113313b5b548SBaptiste Daroussin 		struct fileline *filep;
113413b5b548SBaptiste Daroussin 
113513b5b548SBaptiste Daroussin 		filep = STAILQ_FIRST(&delqhead);
113613b5b548SBaptiste Daroussin 		enqueue(filep->line, '<', NULL);
113713b5b548SBaptiste Daroussin 		STAILQ_REMOVE_HEAD(&delqhead, fileentries);
113813b5b548SBaptiste Daroussin 		free(filep);
113913b5b548SBaptiste Daroussin 	}
114013b5b548SBaptiste Daroussin 	processq();
114113b5b548SBaptiste Daroussin }
114213b5b548SBaptiste Daroussin 
114313b5b548SBaptiste Daroussin /*
114413b5b548SBaptiste Daroussin  * Print deleted lines from file, from file1ln to file1end.
114513b5b548SBaptiste Daroussin  */
114613b5b548SBaptiste Daroussin static void
114713b5b548SBaptiste Daroussin printd(FILE *file1, size_t file1end)
114813b5b548SBaptiste Daroussin {
114913b5b548SBaptiste Daroussin 	char *line1;
115013b5b548SBaptiste Daroussin 
115113b5b548SBaptiste Daroussin 	/* Print out lines file1ln to line2. */
115213b5b548SBaptiste Daroussin 	for (; file1ln <= file1end; ++file1ln) {
115313b5b548SBaptiste Daroussin 		if (!(line1 = xfgets(file1)))
115413b5b548SBaptiste Daroussin 			errx(2, "file1 ended early in delete");
115513b5b548SBaptiste Daroussin 		enqueue(line1, '<', NULL);
115613b5b548SBaptiste Daroussin 	}
115713b5b548SBaptiste Daroussin 	processq();
115813b5b548SBaptiste Daroussin }
115913b5b548SBaptiste Daroussin 
116013b5b548SBaptiste Daroussin /*
116113b5b548SBaptiste Daroussin  * Interactive mode usage.
116213b5b548SBaptiste Daroussin  */
116313b5b548SBaptiste Daroussin static void
116413b5b548SBaptiste Daroussin int_usage(void)
116513b5b548SBaptiste Daroussin {
116613b5b548SBaptiste Daroussin 
116713b5b548SBaptiste Daroussin 	puts("e:\tedit blank diff\n"
116813b5b548SBaptiste Daroussin 	    "eb:\tedit both diffs concatenated\n"
116913b5b548SBaptiste Daroussin 	    "el:\tedit left diff\n"
117013b5b548SBaptiste Daroussin 	    "er:\tedit right diff\n"
117113b5b548SBaptiste Daroussin 	    "l | 1:\tchoose left diff\n"
117213b5b548SBaptiste Daroussin 	    "r | 2:\tchoose right diff\n"
117313b5b548SBaptiste Daroussin 	    "s:\tsilent mode--don't print identical lines\n"
117413b5b548SBaptiste Daroussin 	    "v:\tverbose mode--print identical lines\n"
117513b5b548SBaptiste Daroussin 	    "q:\tquit");
117613b5b548SBaptiste Daroussin }
117713b5b548SBaptiste Daroussin 
117813b5b548SBaptiste Daroussin static void
117913b5b548SBaptiste Daroussin usage(void)
118013b5b548SBaptiste Daroussin {
118113b5b548SBaptiste Daroussin 
118213b5b548SBaptiste Daroussin 	fprintf(stderr,
118313b5b548SBaptiste Daroussin 	    "usage: sdiff [-abdilstW] [-I regexp] [-o outfile] [-w width] file1"
118413b5b548SBaptiste Daroussin 	    " file2\n");
118513b5b548SBaptiste Daroussin 	exit(2);
118613b5b548SBaptiste Daroussin }
1187