xref: /freebsd-14.2/usr.bin/diff/diffreg.c (revision e68edb8c)
1 /*	$OpenBSD: diffreg.c,v 1.91 2016/03/01 20:57:35 natano Exp $	*/
2 
3 /*-
4  * SPDX-License-Identifier: BSD-4-Clause
5  *
6  * Copyright (C) Caldera International Inc.  2001-2002.
7  * All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code and documentation must retain the above
13  *    copyright notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  * 3. All advertising materials mentioning features or use of this software
18  *    must display the following acknowledgement:
19  *	This product includes software developed or owned by Caldera
20  *	International, Inc.
21  * 4. Neither the name of Caldera International, Inc. nor the names of other
22  *    contributors may be used to endorse or promote products derived from
23  *    this software without specific prior written permission.
24  *
25  * USE OF THE SOFTWARE PROVIDED FOR UNDER THIS LICENSE BY CALDERA
26  * INTERNATIONAL, INC. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
27  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
28  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
29  * IN NO EVENT SHALL CALDERA INTERNATIONAL, INC. BE LIABLE FOR ANY DIRECT,
30  * INDIRECT INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
31  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
32  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
34  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
35  * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36  * POSSIBILITY OF SUCH DAMAGE.
37  */
38 /*-
39  * Copyright (c) 1991, 1993
40  *	The Regents of the University of California.  All rights reserved.
41  *
42  * Redistribution and use in source and binary forms, with or without
43  * modification, are permitted provided that the following conditions
44  * are met:
45  * 1. Redistributions of source code must retain the above copyright
46  *    notice, this list of conditions and the following disclaimer.
47  * 2. Redistributions in binary form must reproduce the above copyright
48  *    notice, this list of conditions and the following disclaimer in the
49  *    documentation and/or other materials provided with the distribution.
50  * 3. Neither the name of the University nor the names of its contributors
51  *    may be used to endorse or promote products derived from this software
52  *    without specific prior written permission.
53  *
54  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
55  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
56  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
57  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
58  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
59  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
60  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
61  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
62  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
63  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
64  * SUCH DAMAGE.
65  *
66  *	@(#)diffreg.c   8.1 (Berkeley) 6/6/93
67  */
68 
69 #include <sys/cdefs.h>
70 __FBSDID("$FreeBSD$");
71 
72 #include <sys/capsicum.h>
73 #include <sys/stat.h>
74 
75 #include <capsicum_helpers.h>
76 #include <ctype.h>
77 #include <err.h>
78 #include <errno.h>
79 #include <fcntl.h>
80 #include <paths.h>
81 #include <regex.h>
82 #include <stdbool.h>
83 #include <stddef.h>
84 #include <stdint.h>
85 #include <stdio.h>
86 #include <stdlib.h>
87 #include <string.h>
88 
89 #include "pr.h"
90 #include "diff.h"
91 #include "xmalloc.h"
92 
93 /*
94  * diff - compare two files.
95  */
96 
97 /*
98  *	Uses an algorithm due to Harold Stone, which finds
99  *	a pair of longest identical subsequences in the two
100  *	files.
101  *
102  *	The major goal is to generate the match vector J.
103  *	J[i] is the index of the line in file1 corresponding
104  *	to line i file0. J[i] = 0 if there is no
105  *	such line in file1.
106  *
107  *	Lines are hashed so as to work in core. All potential
108  *	matches are located by sorting the lines of each file
109  *	on the hash (called ``value''). In particular, this
110  *	collects the equivalence classes in file1 together.
111  *	Subroutine equiv replaces the value of each line in
112  *	file0 by the index of the first element of its
113  *	matching equivalence in (the reordered) file1.
114  *	To save space equiv squeezes file1 into a single
115  *	array member in which the equivalence classes
116  *	are simply concatenated, except that their first
117  *	members are flagged by changing sign.
118  *
119  *	Next the indices that point into member are unsorted into
120  *	array class according to the original order of file0.
121  *
122  *	The cleverness lies in routine stone. This marches
123  *	through the lines of file0, developing a vector klist
124  *	of "k-candidates". At step i a k-candidate is a matched
125  *	pair of lines x,y (x in file0 y in file1) such that
126  *	there is a common subsequence of length k
127  *	between the first i lines of file0 and the first y
128  *	lines of file1, but there is no such subsequence for
129  *	any smaller y. x is the earliest possible mate to y
130  *	that occurs in such a subsequence.
131  *
132  *	Whenever any of the members of the equivalence class of
133  *	lines in file1 matable to a line in file0 has serial number
134  *	less than the y of some k-candidate, that k-candidate
135  *	with the smallest such y is replaced. The new
136  *	k-candidate is chained (via pred) to the current
137  *	k-1 candidate so that the actual subsequence can
138  *	be recovered. When a member has serial number greater
139  *	that the y of all k-candidates, the klist is extended.
140  *	At the end, the longest subsequence is pulled out
141  *	and placed in the array J by unravel
142  *
143  *	With J in hand, the matches there recorded are
144  *	check'ed against reality to assure that no spurious
145  *	matches have crept in due to hashing. If they have,
146  *	they are broken, and "jackpot" is recorded--a harmless
147  *	matter except that a true match for a spuriously
148  *	mated line may now be unnecessarily reported as a change.
149  *
150  *	Much of the complexity of the program comes simply
151  *	from trying to minimize core utilization and
152  *	maximize the range of doable problems by dynamically
153  *	allocating what is needed and reusing what is not.
154  *	The core requirements for problems larger than somewhat
155  *	are (in words) 2*length(file0) + length(file1) +
156  *	3*(number of k-candidates installed),  typically about
157  *	6n words for files of length n.
158  */
159 
160 struct cand {
161 	int	x;
162 	int	y;
163 	int	pred;
164 };
165 
166 static struct line {
167 	int	serial;
168 	int	value;
169 } *file[2];
170 
171 /*
172  * The following struct is used to record change information when
173  * doing a "context" or "unified" diff.  (see routine "change" to
174  * understand the highly mnemonic field names)
175  */
176 struct context_vec {
177 	int	a;		/* start line in old file */
178 	int	b;		/* end line in old file */
179 	int	c;		/* start line in new file */
180 	int	d;		/* end line in new file */
181 };
182 
183 #define	diff_output	printf
184 static FILE	*opentemp(const char *);
185 static void	 output(char *, FILE *, char *, FILE *, int);
186 static void	 check(FILE *, FILE *, int);
187 static void	 range(int, int, const char *);
188 static void	 uni_range(int, int);
189 static void	 dump_context_vec(FILE *, FILE *, int);
190 static void	 dump_unified_vec(FILE *, FILE *, int);
191 static void	 prepare(int, FILE *, size_t, int);
192 static void	 prune(void);
193 static void	 equiv(struct line *, int, struct line *, int, int *);
194 static void	 unravel(int);
195 static void	 unsort(struct line *, int, int *);
196 static void	 change(char *, FILE *, char *, FILE *, int, int, int, int, int *);
197 static void	 sort(struct line *, int);
198 static void	 print_header(const char *, const char *);
199 static int	 ignoreline(char *);
200 static int	 asciifile(FILE *);
201 static int	 fetch(long *, int, int, FILE *, int, int, int);
202 static int	 newcand(int, int, int);
203 static int	 search(int *, int, int);
204 static int	 skipline(FILE *);
205 static int	 isqrt(int);
206 static int	 stone(int *, int, int *, int *, int);
207 static int	 readhash(FILE *, int);
208 static int	 files_differ(FILE *, FILE *, int);
209 static char	*match_function(const long *, int, FILE *);
210 static char	*preadline(int, size_t, off_t);
211 
212 static int  *J;			/* will be overlaid on class */
213 static int  *class;		/* will be overlaid on file[0] */
214 static int  *klist;		/* will be overlaid on file[0] after class */
215 static int  *member;		/* will be overlaid on file[1] */
216 static int   clen;
217 static int   inifdef;		/* whether or not we are in a #ifdef block */
218 static int   len[2];
219 static int   pref, suff;	/* length of prefix and suffix */
220 static int   slen[2];
221 static int   anychange;
222 static long *ixnew;		/* will be overlaid on file[1] */
223 static long *ixold;		/* will be overlaid on klist */
224 static struct cand *clist;	/* merely a free storage pot for candidates */
225 static int   clistlen;		/* the length of clist */
226 static struct line *sfile[2];	/* shortened by pruning common prefix/suffix */
227 static int (*chrtran)(int);	/* translation table for case-folding */
228 static struct context_vec *context_vec_start;
229 static struct context_vec *context_vec_end;
230 static struct context_vec *context_vec_ptr;
231 
232 #define FUNCTION_CONTEXT_SIZE	55
233 static char lastbuf[FUNCTION_CONTEXT_SIZE];
234 static int lastline;
235 static int lastmatchline;
236 
237 static int
238 clow2low(int c)
239 {
240 
241 	return (c);
242 }
243 
244 static int
245 cup2low(int c)
246 {
247 
248 	return tolower(c);
249 }
250 
251 int
252 diffreg(char *file1, char *file2, int flags, int capsicum)
253 {
254 	FILE *f1, *f2;
255 	int i, rval;
256 	struct pr *pr = NULL;
257 	cap_rights_t rights_ro;
258 
259 	f1 = f2 = NULL;
260 	rval = D_SAME;
261 	anychange = 0;
262 	lastline = 0;
263 	lastmatchline = 0;
264 	context_vec_ptr = context_vec_start - 1;
265 	if (flags & D_IGNORECASE)
266 		chrtran = cup2low;
267 	else
268 		chrtran = clow2low;
269 	if (S_ISDIR(stb1.st_mode) != S_ISDIR(stb2.st_mode))
270 		return (S_ISDIR(stb1.st_mode) ? D_MISMATCH1 : D_MISMATCH2);
271 	if (strcmp(file1, "-") == 0 && strcmp(file2, "-") == 0)
272 		goto closem;
273 
274 	if (flags & D_EMPTY1)
275 		f1 = fopen(_PATH_DEVNULL, "r");
276 	else {
277 		if (!S_ISREG(stb1.st_mode)) {
278 			if ((f1 = opentemp(file1)) == NULL ||
279 			    fstat(fileno(f1), &stb1) < 0) {
280 				warn("%s", file1);
281 				status |= 2;
282 				goto closem;
283 			}
284 		} else if (strcmp(file1, "-") == 0)
285 			f1 = stdin;
286 		else
287 			f1 = fopen(file1, "r");
288 	}
289 	if (f1 == NULL) {
290 		warn("%s", file1);
291 		status |= 2;
292 		goto closem;
293 	}
294 
295 	if (flags & D_EMPTY2)
296 		f2 = fopen(_PATH_DEVNULL, "r");
297 	else {
298 		if (!S_ISREG(stb2.st_mode)) {
299 			if ((f2 = opentemp(file2)) == NULL ||
300 			    fstat(fileno(f2), &stb2) < 0) {
301 				warn("%s", file2);
302 				status |= 2;
303 				goto closem;
304 			}
305 		} else if (strcmp(file2, "-") == 0)
306 			f2 = stdin;
307 		else
308 			f2 = fopen(file2, "r");
309 	}
310 	if (f2 == NULL) {
311 		warn("%s", file2);
312 		status |= 2;
313 		goto closem;
314 	}
315 
316 	if (lflag)
317 		pr = start_pr(file1, file2);
318 
319 	if (capsicum) {
320 		cap_rights_init(&rights_ro, CAP_READ, CAP_FSTAT, CAP_SEEK);
321 		if (cap_rights_limit(fileno(f1), &rights_ro) < 0
322 		    && errno != ENOSYS)
323 			err(2, "unable to limit rights on: %s", file1);
324 		if (cap_rights_limit(fileno(f2), &rights_ro) < 0 &&
325 		    errno != ENOSYS)
326 			err(2, "unable to limit rights on: %s", file2);
327 		if (fileno(f1) == STDIN_FILENO || fileno(f2) == STDIN_FILENO) {
328 			/* stding has already been limited */
329 			if (caph_limit_stderr() == -1)
330 				err(2, "unable to limit stderr");
331 			if (caph_limit_stdout() == -1)
332 				err(2, "unable to limit stdout");
333 		} else if (caph_limit_stdio() == -1)
334 				err(2, "unable to limit stdio");
335 
336 		caph_cache_catpages();
337 		caph_cache_tzdata();
338 		if (caph_enter() < 0)
339 			err(2, "unable to enter capability mode");
340 	}
341 
342 	switch (files_differ(f1, f2, flags)) {
343 	case 0:
344 		goto closem;
345 	case 1:
346 		break;
347 	default:
348 		/* error */
349 		status |= 2;
350 		goto closem;
351 	}
352 
353 	if ((flags & D_FORCEASCII) == 0 &&
354 	    (!asciifile(f1) || !asciifile(f2))) {
355 		rval = D_BINARY;
356 		status |= 1;
357 		goto closem;
358 	}
359 	prepare(0, f1, stb1.st_size, flags);
360 	prepare(1, f2, stb2.st_size, flags);
361 
362 	prune();
363 	sort(sfile[0], slen[0]);
364 	sort(sfile[1], slen[1]);
365 
366 	member = (int *)file[1];
367 	equiv(sfile[0], slen[0], sfile[1], slen[1], member);
368 	member = xreallocarray(member, slen[1] + 2, sizeof(*member));
369 
370 	class = (int *)file[0];
371 	unsort(sfile[0], slen[0], class);
372 	class = xreallocarray(class, slen[0] + 2, sizeof(*class));
373 
374 	klist = xcalloc(slen[0] + 2, sizeof(*klist));
375 	clen = 0;
376 	clistlen = 100;
377 	clist = xcalloc(clistlen, sizeof(*clist));
378 	i = stone(class, slen[0], member, klist, flags);
379 	free(member);
380 	free(class);
381 
382 	J = xreallocarray(J, len[0] + 2, sizeof(*J));
383 	unravel(klist[i]);
384 	free(clist);
385 	free(klist);
386 
387 	ixold = xreallocarray(ixold, len[0] + 2, sizeof(*ixold));
388 	ixnew = xreallocarray(ixnew, len[1] + 2, sizeof(*ixnew));
389 	check(f1, f2, flags);
390 	output(file1, f1, file2, f2, flags);
391 	if (pr != NULL)
392 		stop_pr(pr);
393 
394 closem:
395 	if (anychange) {
396 		status |= 1;
397 		if (rval == D_SAME)
398 			rval = D_DIFFER;
399 	}
400 	if (f1 != NULL)
401 		fclose(f1);
402 	if (f2 != NULL)
403 		fclose(f2);
404 
405 	return (rval);
406 }
407 
408 /*
409  * Check to see if the given files differ.
410  * Returns 0 if they are the same, 1 if different, and -1 on error.
411  * XXX - could use code from cmp(1) [faster]
412  */
413 static int
414 files_differ(FILE *f1, FILE *f2, int flags)
415 {
416 	char buf1[BUFSIZ], buf2[BUFSIZ];
417 	size_t i, j;
418 
419 	if ((flags & (D_EMPTY1|D_EMPTY2)) || stb1.st_size != stb2.st_size ||
420 	    (stb1.st_mode & S_IFMT) != (stb2.st_mode & S_IFMT))
421 		return (1);
422 	for (;;) {
423 		i = fread(buf1, 1, sizeof(buf1), f1);
424 		j = fread(buf2, 1, sizeof(buf2), f2);
425 		if ((!i && ferror(f1)) || (!j && ferror(f2)))
426 			return (-1);
427 		if (i != j)
428 			return (1);
429 		if (i == 0)
430 			return (0);
431 		if (memcmp(buf1, buf2, i) != 0)
432 			return (1);
433 	}
434 }
435 
436 static FILE *
437 opentemp(const char *f)
438 {
439 	char buf[BUFSIZ], tempfile[PATH_MAX];
440 	ssize_t nread;
441 	int ifd, ofd;
442 
443 	if (strcmp(f, "-") == 0)
444 		ifd = STDIN_FILENO;
445 	else if ((ifd = open(f, O_RDONLY, 0644)) < 0)
446 		return (NULL);
447 
448 	(void)strlcpy(tempfile, _PATH_TMP "/diff.XXXXXXXX", sizeof(tempfile));
449 
450 	if ((ofd = mkstemp(tempfile)) < 0) {
451 		close(ifd);
452 		return (NULL);
453 	}
454 	unlink(tempfile);
455 	while ((nread = read(ifd, buf, BUFSIZ)) > 0) {
456 		if (write(ofd, buf, nread) != nread) {
457 			close(ifd);
458 			close(ofd);
459 			return (NULL);
460 		}
461 	}
462 	close(ifd);
463 	lseek(ofd, (off_t)0, SEEK_SET);
464 	return (fdopen(ofd, "r"));
465 }
466 
467 char *
468 splice(char *dir, char *path)
469 {
470 	char *tail, *buf;
471 	size_t dirlen;
472 
473 	dirlen = strlen(dir);
474 	while (dirlen != 0 && dir[dirlen - 1] == '/')
475 	    dirlen--;
476 	if ((tail = strrchr(path, '/')) == NULL)
477 		tail = path;
478 	else
479 		tail++;
480 	xasprintf(&buf, "%.*s/%s", (int)dirlen, dir, tail);
481 	return (buf);
482 }
483 
484 static void
485 prepare(int i, FILE *fd, size_t filesize, int flags)
486 {
487 	struct line *p;
488 	int h;
489 	size_t sz, j;
490 
491 	rewind(fd);
492 
493 	sz = MIN(filesize, SIZE_MAX) / 25;
494 	if (sz < 100)
495 		sz = 100;
496 
497 	p = xcalloc(sz + 3, sizeof(*p));
498 	for (j = 0; (h = readhash(fd, flags));) {
499 		if (j == sz) {
500 			sz = sz * 3 / 2;
501 			p = xreallocarray(p, sz + 3, sizeof(*p));
502 		}
503 		p[++j].value = h;
504 	}
505 	len[i] = j;
506 	file[i] = p;
507 }
508 
509 static void
510 prune(void)
511 {
512 	int i, j;
513 
514 	for (pref = 0; pref < len[0] && pref < len[1] &&
515 	    file[0][pref + 1].value == file[1][pref + 1].value;
516 	    pref++)
517 		;
518 	for (suff = 0; suff < len[0] - pref && suff < len[1] - pref &&
519 	    file[0][len[0] - suff].value == file[1][len[1] - suff].value;
520 	    suff++)
521 		;
522 	for (j = 0; j < 2; j++) {
523 		sfile[j] = file[j] + pref;
524 		slen[j] = len[j] - pref - suff;
525 		for (i = 0; i <= slen[j]; i++)
526 			sfile[j][i].serial = i;
527 	}
528 }
529 
530 static void
531 equiv(struct line *a, int n, struct line *b, int m, int *c)
532 {
533 	int i, j;
534 
535 	i = j = 1;
536 	while (i <= n && j <= m) {
537 		if (a[i].value < b[j].value)
538 			a[i++].value = 0;
539 		else if (a[i].value == b[j].value)
540 			a[i++].value = j;
541 		else
542 			j++;
543 	}
544 	while (i <= n)
545 		a[i++].value = 0;
546 	b[m + 1].value = 0;
547 	j = 0;
548 	while (++j <= m) {
549 		c[j] = -b[j].serial;
550 		while (b[j + 1].value == b[j].value) {
551 			j++;
552 			c[j] = b[j].serial;
553 		}
554 	}
555 	c[j] = -1;
556 }
557 
558 /* Code taken from ping.c */
559 static int
560 isqrt(int n)
561 {
562 	int y, x = 1;
563 
564 	if (n == 0)
565 		return (0);
566 
567 	do { /* newton was a stinker */
568 		y = x;
569 		x = n / x;
570 		x += y;
571 		x /= 2;
572 	} while ((x - y) > 1 || (x - y) < -1);
573 
574 	return (x);
575 }
576 
577 static int
578 stone(int *a, int n, int *b, int *c, int flags)
579 {
580 	int i, k, y, j, l;
581 	int oldc, tc, oldl, sq;
582 	u_int numtries, bound;
583 
584 	if (flags & D_MINIMAL)
585 		bound = UINT_MAX;
586 	else {
587 		sq = isqrt(n);
588 		bound = MAX(256, sq);
589 	}
590 
591 	k = 0;
592 	c[0] = newcand(0, 0, 0);
593 	for (i = 1; i <= n; i++) {
594 		j = a[i];
595 		if (j == 0)
596 			continue;
597 		y = -b[j];
598 		oldl = 0;
599 		oldc = c[0];
600 		numtries = 0;
601 		do {
602 			if (y <= clist[oldc].y)
603 				continue;
604 			l = search(c, k, y);
605 			if (l != oldl + 1)
606 				oldc = c[l - 1];
607 			if (l <= k) {
608 				if (clist[c[l]].y <= y)
609 					continue;
610 				tc = c[l];
611 				c[l] = newcand(i, y, oldc);
612 				oldc = tc;
613 				oldl = l;
614 				numtries++;
615 			} else {
616 				c[l] = newcand(i, y, oldc);
617 				k++;
618 				break;
619 			}
620 		} while ((y = b[++j]) > 0 && numtries < bound);
621 	}
622 	return (k);
623 }
624 
625 static int
626 newcand(int x, int y, int pred)
627 {
628 	struct cand *q;
629 
630 	if (clen == clistlen) {
631 		clistlen = clistlen * 11 / 10;
632 		clist = xreallocarray(clist, clistlen, sizeof(*clist));
633 	}
634 	q = clist + clen;
635 	q->x = x;
636 	q->y = y;
637 	q->pred = pred;
638 	return (clen++);
639 }
640 
641 static int
642 search(int *c, int k, int y)
643 {
644 	int i, j, l, t;
645 
646 	if (clist[c[k]].y < y)	/* quick look for typical case */
647 		return (k + 1);
648 	i = 0;
649 	j = k + 1;
650 	for (;;) {
651 		l = (i + j) / 2;
652 		if (l <= i)
653 			break;
654 		t = clist[c[l]].y;
655 		if (t > y)
656 			j = l;
657 		else if (t < y)
658 			i = l;
659 		else
660 			return (l);
661 	}
662 	return (l + 1);
663 }
664 
665 static void
666 unravel(int p)
667 {
668 	struct cand *q;
669 	int i;
670 
671 	for (i = 0; i <= len[0]; i++)
672 		J[i] = i <= pref ? i :
673 		    i > len[0] - suff ? i + len[1] - len[0] : 0;
674 	for (q = clist + p; q->y != 0; q = clist + q->pred)
675 		J[q->x + pref] = q->y + pref;
676 }
677 
678 /*
679  * Check does double duty:
680  *  1.	ferret out any fortuitous correspondences due
681  *	to confounding by hashing (which result in "jackpot")
682  *  2.  collect random access indexes to the two files
683  */
684 static void
685 check(FILE *f1, FILE *f2, int flags)
686 {
687 	int i, j, jackpot, c, d;
688 	long ctold, ctnew;
689 
690 	rewind(f1);
691 	rewind(f2);
692 	j = 1;
693 	ixold[0] = ixnew[0] = 0;
694 	jackpot = 0;
695 	ctold = ctnew = 0;
696 	for (i = 1; i <= len[0]; i++) {
697 		if (J[i] == 0) {
698 			ixold[i] = ctold += skipline(f1);
699 			continue;
700 		}
701 		while (j < J[i]) {
702 			ixnew[j] = ctnew += skipline(f2);
703 			j++;
704 		}
705 		if (flags & (D_FOLDBLANKS|D_IGNOREBLANKS|D_IGNORECASE|D_STRIPCR)) {
706 			for (;;) {
707 				c = getc(f1);
708 				d = getc(f2);
709 				/*
710 				 * GNU diff ignores a missing newline
711 				 * in one file for -b or -w.
712 				 */
713 				if (flags & (D_FOLDBLANKS|D_IGNOREBLANKS)) {
714 					if (c == EOF && d == '\n') {
715 						ctnew++;
716 						break;
717 					} else if (c == '\n' && d == EOF) {
718 						ctold++;
719 						break;
720 					}
721 				}
722 				ctold++;
723 				ctnew++;
724 				if (flags & D_STRIPCR && (c == '\r' || d == '\r')) {
725 					if (c == '\r') {
726 						if ((c = getc(f1)) == '\n') {
727 							ctold++;
728 						} else {
729 							ungetc(c, f1);
730 						}
731 					}
732 					if (d == '\r') {
733 						if ((d = getc(f2)) == '\n') {
734 							ctnew++;
735 						} else {
736 							ungetc(d, f2);
737 						}
738 					}
739 					break;
740 				}
741 				if ((flags & D_FOLDBLANKS) && isspace(c) &&
742 				    isspace(d)) {
743 					do {
744 						if (c == '\n')
745 							break;
746 						ctold++;
747 					} while (isspace(c = getc(f1)));
748 					do {
749 						if (d == '\n')
750 							break;
751 						ctnew++;
752 					} while (isspace(d = getc(f2)));
753 				} else if ((flags & D_IGNOREBLANKS)) {
754 					while (isspace(c) && c != '\n') {
755 						c = getc(f1);
756 						ctold++;
757 					}
758 					while (isspace(d) && d != '\n') {
759 						d = getc(f2);
760 						ctnew++;
761 					}
762 				}
763 				if (chrtran(c) != chrtran(d)) {
764 					jackpot++;
765 					J[i] = 0;
766 					if (c != '\n' && c != EOF)
767 						ctold += skipline(f1);
768 					if (d != '\n' && c != EOF)
769 						ctnew += skipline(f2);
770 					break;
771 				}
772 				if (c == '\n' || c == EOF)
773 					break;
774 			}
775 		} else {
776 			for (;;) {
777 				ctold++;
778 				ctnew++;
779 				if ((c = getc(f1)) != (d = getc(f2))) {
780 					/* jackpot++; */
781 					J[i] = 0;
782 					if (c != '\n' && c != EOF)
783 						ctold += skipline(f1);
784 					if (d != '\n' && c != EOF)
785 						ctnew += skipline(f2);
786 					break;
787 				}
788 				if (c == '\n' || c == EOF)
789 					break;
790 			}
791 		}
792 		ixold[i] = ctold;
793 		ixnew[j] = ctnew;
794 		j++;
795 	}
796 	for (; j <= len[1]; j++) {
797 		ixnew[j] = ctnew += skipline(f2);
798 	}
799 	/*
800 	 * if (jackpot)
801 	 *	fprintf(stderr, "jackpot\n");
802 	 */
803 }
804 
805 /* shellsort CACM #201 */
806 static void
807 sort(struct line *a, int n)
808 {
809 	struct line *ai, *aim, w;
810 	int j, m = 0, k;
811 
812 	if (n == 0)
813 		return;
814 	for (j = 1; j <= n; j *= 2)
815 		m = 2 * j - 1;
816 	for (m /= 2; m != 0; m /= 2) {
817 		k = n - m;
818 		for (j = 1; j <= k; j++) {
819 			for (ai = &a[j]; ai > a; ai -= m) {
820 				aim = &ai[m];
821 				if (aim < ai)
822 					break;	/* wraparound */
823 				if (aim->value > ai[0].value ||
824 				    (aim->value == ai[0].value &&
825 					aim->serial > ai[0].serial))
826 					break;
827 				w.value = ai[0].value;
828 				ai[0].value = aim->value;
829 				aim->value = w.value;
830 				w.serial = ai[0].serial;
831 				ai[0].serial = aim->serial;
832 				aim->serial = w.serial;
833 			}
834 		}
835 	}
836 }
837 
838 static void
839 unsort(struct line *f, int l, int *b)
840 {
841 	int *a, i;
842 
843 	a = xcalloc(l + 1, sizeof(*a));
844 	for (i = 1; i <= l; i++)
845 		a[f[i].serial] = f[i].value;
846 	for (i = 1; i <= l; i++)
847 		b[i] = a[i];
848 	free(a);
849 }
850 
851 static int
852 skipline(FILE *f)
853 {
854 	int i, c;
855 
856 	for (i = 1; (c = getc(f)) != '\n' && c != EOF; i++)
857 		continue;
858 	return (i);
859 }
860 
861 static void
862 output(char *file1, FILE *f1, char *file2, FILE *f2, int flags)
863 {
864 	int m, i0, i1, j0, j1;
865 
866 	rewind(f1);
867 	rewind(f2);
868 	m = len[0];
869 	J[0] = 0;
870 	J[m + 1] = len[1] + 1;
871 	if (diff_format != D_EDIT) {
872 		for (i0 = 1; i0 <= m; i0 = i1 + 1) {
873 			while (i0 <= m && J[i0] == J[i0 - 1] + 1)
874 				i0++;
875 			j0 = J[i0 - 1] + 1;
876 			i1 = i0 - 1;
877 			while (i1 < m && J[i1 + 1] == 0)
878 				i1++;
879 			j1 = J[i1 + 1] - 1;
880 			J[i1] = j1;
881 			change(file1, f1, file2, f2, i0, i1, j0, j1, &flags);
882 		}
883 	} else {
884 		for (i0 = m; i0 >= 1; i0 = i1 - 1) {
885 			while (i0 >= 1 && J[i0] == J[i0 + 1] - 1 && J[i0] != 0)
886 				i0--;
887 			j0 = J[i0 + 1] - 1;
888 			i1 = i0 + 1;
889 			while (i1 > 1 && J[i1 - 1] == 0)
890 				i1--;
891 			j1 = J[i1 - 1] + 1;
892 			J[i1] = j1;
893 			change(file1, f1, file2, f2, i1, i0, j1, j0, &flags);
894 		}
895 	}
896 	if (m == 0)
897 		change(file1, f1, file2, f2, 1, 0, 1, len[1], &flags);
898 	if (diff_format == D_IFDEF || diff_format == D_GFORMAT) {
899 		for (;;) {
900 #define	c i0
901 			if ((c = getc(f1)) == EOF)
902 				return;
903 			diff_output("%c", c);
904 		}
905 #undef c
906 	}
907 	if (anychange != 0) {
908 		if (diff_format == D_CONTEXT)
909 			dump_context_vec(f1, f2, flags);
910 		else if (diff_format == D_UNIFIED)
911 			dump_unified_vec(f1, f2, flags);
912 	}
913 }
914 
915 static void
916 range(int a, int b, const char *separator)
917 {
918 	diff_output("%d", a > b ? b : a);
919 	if (a < b)
920 		diff_output("%s%d", separator, b);
921 }
922 
923 static void
924 uni_range(int a, int b)
925 {
926 	if (a < b)
927 		diff_output("%d,%d", a, b - a + 1);
928 	else if (a == b)
929 		diff_output("%d", b);
930 	else
931 		diff_output("%d,0", b);
932 }
933 
934 static char *
935 preadline(int fd, size_t rlen, off_t off)
936 {
937 	char *line;
938 	ssize_t nr;
939 
940 	line = xmalloc(rlen + 1);
941 	if ((nr = pread(fd, line, rlen, off)) < 0)
942 		err(2, "preadline");
943 	if (nr > 0 && line[nr-1] == '\n')
944 		nr--;
945 	line[nr] = '\0';
946 	return (line);
947 }
948 
949 static int
950 ignoreline(char *line)
951 {
952 	int ret;
953 
954 	ret = regexec(&ignore_re, line, 0, NULL, 0);
955 	free(line);
956 	return (ret == 0);	/* if it matched, it should be ignored. */
957 }
958 
959 /*
960  * Indicate that there is a difference between lines a and b of the from file
961  * to get to lines c to d of the to file.  If a is greater then b then there
962  * are no lines in the from file involved and this means that there were
963  * lines appended (beginning at b).  If c is greater than d then there are
964  * lines missing from the to file.
965  */
966 static void
967 change(char *file1, FILE *f1, char *file2, FILE *f2, int a, int b, int c, int d,
968     int *pflags)
969 {
970 	static size_t max_context = 64;
971 	long curpos;
972 	int i, nc, f;
973 	const char *walk;
974 
975 restart:
976 	if ((diff_format != D_IFDEF || diff_format == D_GFORMAT) &&
977 	    a > b && c > d)
978 		return;
979 	if (ignore_pats != NULL) {
980 		char *line;
981 		/*
982 		 * All lines in the change, insert, or delete must
983 		 * match an ignore pattern for the change to be
984 		 * ignored.
985 		 */
986 		if (a <= b) {		/* Changes and deletes. */
987 			for (i = a; i <= b; i++) {
988 				line = preadline(fileno(f1),
989 				    ixold[i] - ixold[i - 1], ixold[i - 1]);
990 				if (!ignoreline(line))
991 					goto proceed;
992 			}
993 		}
994 		if (a > b || c <= d) {	/* Changes and inserts. */
995 			for (i = c; i <= d; i++) {
996 				line = preadline(fileno(f2),
997 				    ixnew[i] - ixnew[i - 1], ixnew[i - 1]);
998 				if (!ignoreline(line))
999 					goto proceed;
1000 			}
1001 		}
1002 		return;
1003 	}
1004 	if (*pflags & D_SKIPBLANKLINES) {
1005 		char *line;
1006 		/*
1007 		 * All lines in the change, insert, or delete must not be
1008 		 * empty for the change to be ignored.
1009 		 */
1010 		if (a <= b) {		/* Changes and deletes. */
1011 			for (i = a; i <= b; i++) {
1012 				line = preadline(fileno(f1),
1013 				    ixold[i] - ixold[i - 1], ixold[i - 1]);
1014 				if (*line != '\0')
1015 					goto proceed;
1016 			}
1017 		}
1018 		if (a > b || c <= d) {	/* Changes and inserts. */
1019 			for (i = c; i <= d; i++) {
1020 				line = preadline(fileno(f2),
1021 				    ixnew[i] - ixnew[i - 1], ixnew[i - 1]);
1022 				if (*line != '\0')
1023 					goto proceed;
1024 			}
1025 		}
1026 		return;
1027 
1028 	}
1029 proceed:
1030 	if (*pflags & D_HEADER && diff_format != D_BRIEF) {
1031 		diff_output("%s %s %s\n", diffargs, file1, file2);
1032 		*pflags &= ~D_HEADER;
1033 	}
1034 	if (diff_format == D_CONTEXT || diff_format == D_UNIFIED) {
1035 		/*
1036 		 * Allocate change records as needed.
1037 		 */
1038 		if (context_vec_ptr == context_vec_end - 1) {
1039 			ptrdiff_t offset = context_vec_ptr - context_vec_start;
1040 			max_context <<= 1;
1041 			context_vec_start = xreallocarray(context_vec_start,
1042 			    max_context, sizeof(*context_vec_start));
1043 			context_vec_end = context_vec_start + max_context;
1044 			context_vec_ptr = context_vec_start + offset;
1045 		}
1046 		if (anychange == 0) {
1047 			/*
1048 			 * Print the context/unidiff header first time through.
1049 			 */
1050 			print_header(file1, file2);
1051 			anychange = 1;
1052 		} else if (a > context_vec_ptr->b + (2 * diff_context) + 1 &&
1053 		    c > context_vec_ptr->d + (2 * diff_context) + 1) {
1054 			/*
1055 			 * If this change is more than 'diff_context' lines from the
1056 			 * previous change, dump the record and reset it.
1057 			 */
1058 			if (diff_format == D_CONTEXT)
1059 				dump_context_vec(f1, f2, *pflags);
1060 			else
1061 				dump_unified_vec(f1, f2, *pflags);
1062 		}
1063 		context_vec_ptr++;
1064 		context_vec_ptr->a = a;
1065 		context_vec_ptr->b = b;
1066 		context_vec_ptr->c = c;
1067 		context_vec_ptr->d = d;
1068 		return;
1069 	}
1070 	if (anychange == 0)
1071 		anychange = 1;
1072 	switch (diff_format) {
1073 	case D_BRIEF:
1074 		return;
1075 	case D_NORMAL:
1076 	case D_EDIT:
1077 		range(a, b, ",");
1078 		diff_output("%c", a > b ? 'a' : c > d ? 'd' : 'c');
1079 		if (diff_format == D_NORMAL)
1080 			range(c, d, ",");
1081 		diff_output("\n");
1082 		break;
1083 	case D_REVERSE:
1084 		diff_output("%c", a > b ? 'a' : c > d ? 'd' : 'c');
1085 		range(a, b, " ");
1086 		diff_output("\n");
1087 		break;
1088 	case D_NREVERSE:
1089 		if (a > b)
1090 			diff_output("a%d %d\n", b, d - c + 1);
1091 		else {
1092 			diff_output("d%d %d\n", a, b - a + 1);
1093 			if (!(c > d))
1094 				/* add changed lines */
1095 				diff_output("a%d %d\n", b, d - c + 1);
1096 		}
1097 		break;
1098 	}
1099 	if (diff_format == D_GFORMAT) {
1100 		curpos = ftell(f1);
1101 		/* print through if append (a>b), else to (nb: 0 vs 1 orig) */
1102 		nc = ixold[a > b ? b : a - 1] - curpos;
1103 		for (i = 0; i < nc; i++)
1104 			diff_output("%c", getc(f1));
1105 		for (walk = group_format; *walk != '\0'; walk++) {
1106 			if (*walk == '%') {
1107 				walk++;
1108 				switch (*walk) {
1109 				case '<':
1110 					fetch(ixold, a, b, f1, '<', 1, *pflags);
1111 					break;
1112 				case '>':
1113 					fetch(ixnew, c, d, f2, '>', 0, *pflags);
1114 					break;
1115 				default:
1116 					diff_output("%%%c", *walk);
1117 					break;
1118 				}
1119 				continue;
1120 			}
1121 			diff_output("%c", *walk);
1122 		}
1123 	}
1124 	if (diff_format == D_NORMAL || diff_format == D_IFDEF) {
1125 		fetch(ixold, a, b, f1, '<', 1, *pflags);
1126 		if (a <= b && c <= d && diff_format == D_NORMAL)
1127 			diff_output("---\n");
1128 	}
1129 	f = 0;
1130 	if (diff_format != D_GFORMAT)
1131 		f = fetch(ixnew, c, d, f2, diff_format == D_NORMAL ? '>' : '\0', 0, *pflags);
1132 	if (f != 0 && diff_format == D_EDIT) {
1133 		/*
1134 		 * A non-zero return value for D_EDIT indicates that the
1135 		 * last line printed was a bare dot (".") that has been
1136 		 * escaped as ".." to prevent ed(1) from misinterpreting
1137 		 * it.  We have to add a substitute command to change this
1138 		 * back and restart where we left off.
1139 		 */
1140 		diff_output(".\n");
1141 		diff_output("%ds/.//\n", a + f - 1);
1142 		b = a + f - 1;
1143 		a = b + 1;
1144 		c += f;
1145 		goto restart;
1146 	}
1147 	if ((diff_format == D_EDIT || diff_format == D_REVERSE) && c <= d)
1148 		diff_output(".\n");
1149 	if (inifdef) {
1150 		diff_output("#endif /* %s */\n", ifdefname);
1151 		inifdef = 0;
1152 	}
1153 }
1154 
1155 static int
1156 fetch(long *f, int a, int b, FILE *lb, int ch, int oldfile, int flags)
1157 {
1158 	int i, j, c, lastc, col, nc;
1159 	int	newcol;
1160 
1161 	/*
1162 	 * When doing #ifdef's, copy down to current line
1163 	 * if this is the first file, so that stuff makes it to output.
1164 	 */
1165 	if ((diff_format == D_IFDEF) && oldfile) {
1166 		long curpos = ftell(lb);
1167 		/* print through if append (a>b), else to (nb: 0 vs 1 orig) */
1168 		nc = f[a > b ? b : a - 1] - curpos;
1169 		for (i = 0; i < nc; i++)
1170 			diff_output("%c", getc(lb));
1171 	}
1172 	if (a > b)
1173 		return (0);
1174 	if (diff_format == D_IFDEF) {
1175 		if (inifdef) {
1176 			diff_output("#else /* %s%s */\n",
1177 			    oldfile == 1 ? "!" : "", ifdefname);
1178 		} else {
1179 			if (oldfile)
1180 				diff_output("#ifndef %s\n", ifdefname);
1181 			else
1182 				diff_output("#ifdef %s\n", ifdefname);
1183 		}
1184 		inifdef = 1 + oldfile;
1185 	}
1186 	for (i = a; i <= b; i++) {
1187 		fseek(lb, f[i - 1], SEEK_SET);
1188 		nc = f[i] - f[i - 1];
1189 		if ((diff_format != D_IFDEF && diff_format != D_GFORMAT) &&
1190 		    ch != '\0') {
1191 			diff_output("%c", ch);
1192 			if (Tflag && (diff_format == D_NORMAL || diff_format == D_CONTEXT
1193 			    || diff_format == D_UNIFIED))
1194 				diff_output("\t");
1195 			else if (diff_format != D_UNIFIED)
1196 				diff_output(" ");
1197 		}
1198 		col = 0;
1199 		for (j = 0, lastc = '\0'; j < nc; j++, lastc = c) {
1200 			if ((c = getc(lb)) == EOF) {
1201 				if (diff_format == D_EDIT || diff_format == D_REVERSE ||
1202 				    diff_format == D_NREVERSE)
1203 					warnx("No newline at end of file");
1204 				else
1205 					diff_output("\n\\ No newline at end of "
1206 					    "file\n");
1207 				return (0);
1208 			}
1209 			if (c == '\t' && (flags & D_EXPANDTABS)) {
1210 				newcol = ((col/tabsize)+1)*tabsize;
1211 				do {
1212 					diff_output(" ");
1213 				} while (++col < newcol);
1214 			} else {
1215 				if (diff_format == D_EDIT && j == 1 && c == '\n'
1216 				    && lastc == '.') {
1217 					/*
1218 					 * Don't print a bare "." line
1219 					 * since that will confuse ed(1).
1220 					 * Print ".." instead and return,
1221 					 * giving the caller an offset
1222 					 * from which to restart.
1223 					 */
1224 					diff_output(".\n");
1225 					return (i - a + 1);
1226 				}
1227 				diff_output("%c", c);
1228 				col++;
1229 			}
1230 		}
1231 	}
1232 	return (0);
1233 }
1234 
1235 /*
1236  * Hash function taken from Robert Sedgewick, Algorithms in C, 3d ed., p 578.
1237  */
1238 static int
1239 readhash(FILE *f, int flags)
1240 {
1241 	int i, t, space;
1242 	int sum;
1243 
1244 	sum = 1;
1245 	space = 0;
1246 	if ((flags & (D_FOLDBLANKS|D_IGNOREBLANKS)) == 0) {
1247 		if (flags & D_IGNORECASE)
1248 			for (i = 0; (t = getc(f)) != '\n'; i++) {
1249 				if (flags & D_STRIPCR && t == '\r') {
1250 					t = getc(f);
1251 					if (t == '\n')
1252 						break;
1253 					ungetc(t, f);
1254 				}
1255 				if (t == EOF) {
1256 					if (i == 0)
1257 						return (0);
1258 					break;
1259 				}
1260 				sum = sum * 127 + chrtran(t);
1261 			}
1262 		else
1263 			for (i = 0; (t = getc(f)) != '\n'; i++) {
1264 				if (flags & D_STRIPCR && t == '\r') {
1265 					t = getc(f);
1266 					if (t == '\n')
1267 						break;
1268 					ungetc(t, f);
1269 				}
1270 				if (t == EOF) {
1271 					if (i == 0)
1272 						return (0);
1273 					break;
1274 				}
1275 				sum = sum * 127 + t;
1276 			}
1277 	} else {
1278 		for (i = 0;;) {
1279 			switch (t = getc(f)) {
1280 			case '\r':
1281 			case '\t':
1282 			case '\v':
1283 			case '\f':
1284 			case ' ':
1285 				space++;
1286 				continue;
1287 			default:
1288 				if (space && (flags & D_IGNOREBLANKS) == 0) {
1289 					i++;
1290 					space = 0;
1291 				}
1292 				sum = sum * 127 + chrtran(t);
1293 				i++;
1294 				continue;
1295 			case EOF:
1296 				if (i == 0)
1297 					return (0);
1298 				/* FALLTHROUGH */
1299 			case '\n':
1300 				break;
1301 			}
1302 			break;
1303 		}
1304 	}
1305 	/*
1306 	 * There is a remote possibility that we end up with a zero sum.
1307 	 * Zero is used as an EOF marker, so return 1 instead.
1308 	 */
1309 	return (sum == 0 ? 1 : sum);
1310 }
1311 
1312 static int
1313 asciifile(FILE *f)
1314 {
1315 	unsigned char buf[BUFSIZ];
1316 	size_t cnt;
1317 
1318 	if (f == NULL)
1319 		return (1);
1320 
1321 	rewind(f);
1322 	cnt = fread(buf, 1, sizeof(buf), f);
1323 	return (memchr(buf, '\0', cnt) == NULL);
1324 }
1325 
1326 #define begins_with(s, pre) (strncmp(s, pre, sizeof(pre)-1) == 0)
1327 
1328 static char *
1329 match_function(const long *f, int pos, FILE *fp)
1330 {
1331 	unsigned char buf[FUNCTION_CONTEXT_SIZE];
1332 	size_t nc;
1333 	int last = lastline;
1334 	const char *state = NULL;
1335 
1336 	lastline = pos;
1337 	while (pos > last) {
1338 		fseek(fp, f[pos - 1], SEEK_SET);
1339 		nc = f[pos] - f[pos - 1];
1340 		if (nc >= sizeof(buf))
1341 			nc = sizeof(buf) - 1;
1342 		nc = fread(buf, 1, nc, fp);
1343 		if (nc > 0) {
1344 			buf[nc] = '\0';
1345 			buf[strcspn(buf, "\n")] = '\0';
1346 			if (isalpha(buf[0]) || buf[0] == '_' || buf[0] == '$') {
1347 				if (begins_with(buf, "private:")) {
1348 					if (!state)
1349 						state = " (private)";
1350 				} else if (begins_with(buf, "protected:")) {
1351 					if (!state)
1352 						state = " (protected)";
1353 				} else if (begins_with(buf, "public:")) {
1354 					if (!state)
1355 						state = " (public)";
1356 				} else {
1357 					strlcpy(lastbuf, buf, sizeof lastbuf);
1358 					if (state)
1359 						strlcat(lastbuf, state,
1360 						    sizeof lastbuf);
1361 					lastmatchline = pos;
1362 					return lastbuf;
1363 				}
1364 			}
1365 		}
1366 		pos--;
1367 	}
1368 	return lastmatchline > 0 ? lastbuf : NULL;
1369 }
1370 
1371 /* dump accumulated "context" diff changes */
1372 static void
1373 dump_context_vec(FILE *f1, FILE *f2, int flags)
1374 {
1375 	struct context_vec *cvp = context_vec_start;
1376 	int lowa, upb, lowc, upd, do_output;
1377 	int a, b, c, d;
1378 	char ch, *f;
1379 
1380 	if (context_vec_start > context_vec_ptr)
1381 		return;
1382 
1383 	b = d = 0;		/* gcc */
1384 	lowa = MAX(1, cvp->a - diff_context);
1385 	upb = MIN(len[0], context_vec_ptr->b + diff_context);
1386 	lowc = MAX(1, cvp->c - diff_context);
1387 	upd = MIN(len[1], context_vec_ptr->d + diff_context);
1388 
1389 	diff_output("***************");
1390 	if ((flags & D_PROTOTYPE)) {
1391 		f = match_function(ixold, lowa-1, f1);
1392 		if (f != NULL)
1393 			diff_output(" %s", f);
1394 	}
1395 	diff_output("\n*** ");
1396 	range(lowa, upb, ",");
1397 	diff_output(" ****\n");
1398 
1399 	/*
1400 	 * Output changes to the "old" file.  The first loop suppresses
1401 	 * output if there were no changes to the "old" file (we'll see
1402 	 * the "old" lines as context in the "new" list).
1403 	 */
1404 	do_output = 0;
1405 	for (; cvp <= context_vec_ptr; cvp++)
1406 		if (cvp->a <= cvp->b) {
1407 			cvp = context_vec_start;
1408 			do_output++;
1409 			break;
1410 		}
1411 	if (do_output) {
1412 		while (cvp <= context_vec_ptr) {
1413 			a = cvp->a;
1414 			b = cvp->b;
1415 			c = cvp->c;
1416 			d = cvp->d;
1417 
1418 			if (a <= b && c <= d)
1419 				ch = 'c';
1420 			else
1421 				ch = (a <= b) ? 'd' : 'a';
1422 
1423 			if (ch == 'a')
1424 				fetch(ixold, lowa, b, f1, ' ', 0, flags);
1425 			else {
1426 				fetch(ixold, lowa, a - 1, f1, ' ', 0, flags);
1427 				fetch(ixold, a, b, f1,
1428 				    ch == 'c' ? '!' : '-', 0, flags);
1429 			}
1430 			lowa = b + 1;
1431 			cvp++;
1432 		}
1433 		fetch(ixold, b + 1, upb, f1, ' ', 0, flags);
1434 	}
1435 	/* output changes to the "new" file */
1436 	diff_output("--- ");
1437 	range(lowc, upd, ",");
1438 	diff_output(" ----\n");
1439 
1440 	do_output = 0;
1441 	for (cvp = context_vec_start; cvp <= context_vec_ptr; cvp++)
1442 		if (cvp->c <= cvp->d) {
1443 			cvp = context_vec_start;
1444 			do_output++;
1445 			break;
1446 		}
1447 	if (do_output) {
1448 		while (cvp <= context_vec_ptr) {
1449 			a = cvp->a;
1450 			b = cvp->b;
1451 			c = cvp->c;
1452 			d = cvp->d;
1453 
1454 			if (a <= b && c <= d)
1455 				ch = 'c';
1456 			else
1457 				ch = (a <= b) ? 'd' : 'a';
1458 
1459 			if (ch == 'd')
1460 				fetch(ixnew, lowc, d, f2, ' ', 0, flags);
1461 			else {
1462 				fetch(ixnew, lowc, c - 1, f2, ' ', 0, flags);
1463 				fetch(ixnew, c, d, f2,
1464 				    ch == 'c' ? '!' : '+', 0, flags);
1465 			}
1466 			lowc = d + 1;
1467 			cvp++;
1468 		}
1469 		fetch(ixnew, d + 1, upd, f2, ' ', 0, flags);
1470 	}
1471 	context_vec_ptr = context_vec_start - 1;
1472 }
1473 
1474 /* dump accumulated "unified" diff changes */
1475 static void
1476 dump_unified_vec(FILE *f1, FILE *f2, int flags)
1477 {
1478 	struct context_vec *cvp = context_vec_start;
1479 	int lowa, upb, lowc, upd;
1480 	int a, b, c, d;
1481 	char ch, *f;
1482 
1483 	if (context_vec_start > context_vec_ptr)
1484 		return;
1485 
1486 	b = d = 0;		/* gcc */
1487 	lowa = MAX(1, cvp->a - diff_context);
1488 	upb = MIN(len[0], context_vec_ptr->b + diff_context);
1489 	lowc = MAX(1, cvp->c - diff_context);
1490 	upd = MIN(len[1], context_vec_ptr->d + diff_context);
1491 
1492 	diff_output("@@ -");
1493 	uni_range(lowa, upb);
1494 	diff_output(" +");
1495 	uni_range(lowc, upd);
1496 	diff_output(" @@");
1497 	if ((flags & D_PROTOTYPE)) {
1498 		f = match_function(ixold, lowa-1, f1);
1499 		if (f != NULL)
1500 			diff_output(" %s", f);
1501 	}
1502 	diff_output("\n");
1503 
1504 	/*
1505 	 * Output changes in "unified" diff format--the old and new lines
1506 	 * are printed together.
1507 	 */
1508 	for (; cvp <= context_vec_ptr; cvp++) {
1509 		a = cvp->a;
1510 		b = cvp->b;
1511 		c = cvp->c;
1512 		d = cvp->d;
1513 
1514 		/*
1515 		 * c: both new and old changes
1516 		 * d: only changes in the old file
1517 		 * a: only changes in the new file
1518 		 */
1519 		if (a <= b && c <= d)
1520 			ch = 'c';
1521 		else
1522 			ch = (a <= b) ? 'd' : 'a';
1523 
1524 		switch (ch) {
1525 		case 'c':
1526 			fetch(ixold, lowa, a - 1, f1, ' ', 0, flags);
1527 			fetch(ixold, a, b, f1, '-', 0, flags);
1528 			fetch(ixnew, c, d, f2, '+', 0, flags);
1529 			break;
1530 		case 'd':
1531 			fetch(ixold, lowa, a - 1, f1, ' ', 0, flags);
1532 			fetch(ixold, a, b, f1, '-', 0, flags);
1533 			break;
1534 		case 'a':
1535 			fetch(ixnew, lowc, c - 1, f2, ' ', 0, flags);
1536 			fetch(ixnew, c, d, f2, '+', 0, flags);
1537 			break;
1538 		}
1539 		lowa = b + 1;
1540 		lowc = d + 1;
1541 	}
1542 	fetch(ixnew, d + 1, upd, f2, ' ', 0, flags);
1543 
1544 	context_vec_ptr = context_vec_start - 1;
1545 }
1546 
1547 static void
1548 print_header(const char *file1, const char *file2)
1549 {
1550 	const char *time_format;
1551 	char buf1[256];
1552 	char buf2[256];
1553 	char end1[10];
1554 	char end2[10];
1555 	struct tm tm1, tm2, *tm_ptr1, *tm_ptr2;
1556 	int nsec1 = stb1.st_mtim.tv_nsec;
1557 	int nsec2 = stb2.st_mtim.tv_nsec;
1558 
1559 	time_format = "%Y-%m-%d %H:%M:%S";
1560 
1561 	if (cflag)
1562 		time_format = "%c";
1563 	tm_ptr1 = localtime_r(&stb1.st_mtime, &tm1);
1564 	tm_ptr2 = localtime_r(&stb2.st_mtime, &tm2);
1565 	strftime(buf1, 256, time_format, tm_ptr1);
1566 	strftime(buf2, 256, time_format, tm_ptr2);
1567 	if (!cflag) {
1568 		strftime(end1, 10, "%z", tm_ptr1);
1569 		strftime(end2, 10, "%z", tm_ptr2);
1570 		sprintf(buf1, "%s.%.9d %s", buf1, nsec1, end1);
1571 		sprintf(buf2, "%s.%.9d %s", buf2, nsec2, end2);
1572 	}
1573 	if (label[0] != NULL)
1574 		diff_output("%s %s\n", diff_format == D_CONTEXT ? "***" : "---",
1575 		    label[0]);
1576 	else
1577 		diff_output("%s %s\t%s\n", diff_format == D_CONTEXT ? "***" : "---",
1578 		    file1, buf1);
1579 	if (label[1] != NULL)
1580 		diff_output("%s %s\n", diff_format == D_CONTEXT ? "---" : "+++",
1581 		    label[1]);
1582 	else
1583 		diff_output("%s %s\t%s\n", diff_format == D_CONTEXT ? "---" : "+++",
1584 		    file2, buf2);
1585 }
1586