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