1 /* @(#)udiff.c	1.42 21/08/20 Copyright 1985-2021 J. Schilling */
2 #include <schily/mconfig.h>
3 #ifndef lint
4 static	UConst char sccsid[] =
5 	"@(#)udiff.c	1.42 21/08/20 Copyright 1985-2021 J. Schilling";
6 #endif
7 /*
8  *	line by line diff for two files
9  *
10  *	Copyright (c) 1985-2021 J. Schilling
11  */
12 /*
13  * The contents of this file are subject to the terms of the
14  * Common Development and Distribution License, Version 1.0 only
15  * (the "License").  You may not use this file except in compliance
16  * with the License.
17  *
18  * See the file CDDL.Schily.txt in this distribution for details.
19  * A copy of the CDDL is also available via the Internet at
20  * http://www.opensource.org/licenses/cddl1.txt
21  *
22  * When distributing Covered Code, include this CDDL HEADER in each
23  * file and include the License file CDDL.Schily.txt from this distribution.
24  */
25 
26 /*
27  *	Remarks:
28  *
29  *	The amount of memory "in use" largely depends on the allocation
30  *	algorithm as the code intensively uses realloc().
31  *
32  *	If the files differ, we always allocate
33  *	(sizeof (off_t) + sizeof (long)) * (lines(old) + lines(new))
34  *	In largefile mode we typically allocate ~ 50% more
35  *	than in non-largefile mode. It seems that in largefile mode, the
36  *	amount of space need is typically ~
37  *		(sizeof (long long) + sizeof (long)) *
38  *		(lines(old) + lines(new)).
39  *
40  *	Largefile mode is also neeeded in order to be able to deal with files
41  *	in the 64 bit inode # range on ZFS.
42  *
43  *	If the code currently uses "long" for line numbers, which is sufficient
44  *	It would be possible to have real large file mode in case that udiff is
45  *	compiled in 64 bit mode.
46  *	In 32 bit mode, there is no need to check for an integer overflow
47  *	as the process will run "out of memory" before.
48  */
49 
50 #include <schily/stdio.h>
51 #include <schily/stdlib.h>
52 #include <schily/unistd.h>	/* Include sys/types.h, makes off_t available */
53 #include <schily/standard.h>
54 #include <schily/string.h>
55 #include <schily/stat.h>
56 #define	GT_COMERR		/* #define comerr gtcomerr */
57 #define	GT_ERROR		/* #define error gterror   */
58 #include <schily/schily.h>
59 #include <schily/nlsdefs.h>
60 
61 #define	MAXLINE		32768
62 
63 typedef struct line {
64 	off_t	off;
65 	long	hash;
66 } line;
67 
68 LOCAL	char	*olbf;		/* "old" line buffer		*/
69 LOCAL	char	*nlbf;		/* "new" line buffer		*/
70 LOCAL	size_t	olsz;		/* "old" line buffer size	*/
71 LOCAL	size_t	nlsz;		/* "new" line buffer size	*/
72 
73 LOCAL	BOOL	posix;		/* -posix flag */
74 LOCAL	int	nmatch = 2;
75 LOCAL	BOOL	bdiffmode = FALSE;
76 
77 LOCAL	line	*oldfile;	/* Offsets and hashes for old file */
78 LOCAL	char	*oldname;	/* Old file name */
79 LOCAL	FILE	*of = 0;	/* File pointer for old file */
80 LOCAL	long	olc = 0;	/* Line count for old file */
81 LOCAL	line	*newfile;	/* Offsets and hashes for new file */
82 LOCAL	char	*newname;	/* New file name */
83 LOCAL	FILE	*nf = 0;	/* File pointer for new file */
84 LOCAL	long	nlc = 0;	/* Line count for new file */
85 LOCAL	long	clc = 0;	/* Common line count */
86 LOCAL	off_t	ooff;		/* Saved seek offset for old file */
87 LOCAL	off_t	noff;		/* Saved seek offset for new file */
88 
89 #define	glinep(i, a)	(&((a)[(i)]))
90 #define	compare(o, n)	(linecmp(glinep((o), oldfile), glinep((n), newfile)))
91 
92 LOCAL	void	usage		__PR((int exitcode));
93 EXPORT	int	main		__PR((int ac, char **av));
94 LOCAL	const char *filename	__PR((const char *name));
95 LOCAL	off_t	readcommon	__PR((FILE *ofp, char *oname,
96 				    FILE *nfp, char *nname));
97 LOCAL	long	readfile	__PR((FILE *f, char *fname, line **hline));
98 LOCAL	void	addline		__PR((char *s, size_t ll, off_t loff, long lc,
99 				    line *hline));
100 LOCAL	long	hash		__PR((char *s, size_t ll));
101 LOCAL	BOOL	linecmp		__PR((line *olp, line *nlp));
102 LOCAL	int	diff		__PR((void));
103 LOCAL	void	showdel		__PR((long o, long n, long c));
104 LOCAL	void	showadd		__PR((long o, long n, long c));
105 LOCAL	void	showchange	__PR((long o, long n, long c));
106 LOCAL	void	showxchange	__PR((long o, long n, long oc, long nc));
107 
108 LOCAL	FILE	*xfileopen	__PR((int idx, char *name, char	*mode));
109 LOCAL	int	xfileseek	__PR((int idx, off_t off));
110 LOCAL	off_t	xfilepos	__PR((int idx));
111 LOCAL	ssize_t	xgetdelim	__PR((char **lineptr, size_t *n,
112 				    int delim, int idx));
113 LOCAL	ssize_t	xfileread	__PR((int idx, char **lineptr, size_t n));
114 
115 LOCAL void
usage(exitcode)116 usage(exitcode)
117 	int	exitcode;
118 {
119 	error("Usage:	udiff [options] file1 file2\n");
120 	error("	-help	Print this help.\n");
121 	error("	-version Print version number.\n");
122 	error("	-posix	Print diffs in POSIX mode.\n");
123 	error("	nmatch=# Set number of matching lines for resync (default = %d).\n",
124 		nmatch);
125 	exit(exitcode);
126 }
127 
128 
129 EXPORT int
main(ac,av)130 main(ac, av)
131 	int	ac;
132 	char	**av;
133 {
134 	char	*options = "posix,nmatch#,help,version";
135 	BOOL	help = FALSE;
136 	BOOL	prversion = FALSE;
137 	int	ret = 0;
138 	int	fac;
139 	char	* const *fav;
140 	const char *av0 = filename(av[0]);
141 	struct stat sb1;
142 	struct stat sb2;
143 	off_t	lineoff;
144 
145 	save_args(ac, av);
146 	if (av0[0] != 'u') {
147 		nmatch = 1;
148 		posix = 1;
149 	}
150 	if (av0[0] == 'f' && av0[1] == 's') {
151 		bdiffmode = TRUE;
152 	}
153 
154 	(void) setlocale(LC_ALL, "");
155 
156 #ifdef  USE_NLS
157 #if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
158 #define	TEXT_DOMAIN "udiff"	/* Use this only if it weren't */
159 #endif
160 	{ char	*dir;
161 	dir = searchfileinpath("share/locale", F_OK,
162 					SIP_ANY_FILE|SIP_NO_PATH, NULL);
163 	if (dir)
164 		(void) bindtextdomain(TEXT_DOMAIN, dir);
165 	else
166 #if defined(PROTOTYPES) && defined(INS_BASE)
167 	(void) bindtextdomain(TEXT_DOMAIN, INS_BASE "/share/locale");
168 #else
169 	(void) bindtextdomain(TEXT_DOMAIN, "/usr/share/locale");
170 #endif
171 	(void) textdomain(TEXT_DOMAIN);
172 	}
173 #endif 	/* USE_NLS */
174 
175 	fac = --ac;
176 	fav = ++av;
177 	if (getallargs(&fac, &fav, options, &posix, &nmatch,
178 	    &help, &prversion) < 0) {
179 		errmsgno(EX_BAD, "Bad option: '%s'\n", fav[0]);
180 		usage(EX_BAD);
181 	}
182 	if (help)
183 		usage(0);
184 	if (nmatch <= 0) {
185 		errmsgno(EX_BAD, "Bad nmatch value: %d\n", nmatch);
186 		usage(EX_BAD);
187 	}
188 	if (prversion) {
189 		gtprintf("Udiff release %s %s (%s-%s-%s) Copyright (C) 1985-2021 %s\n",
190 				"1.42", "2021/08/20",
191 				HOST_CPU, HOST_VENDOR, HOST_OS,
192 				_("J�rg Schilling"));
193 		exit(0);
194 	}
195 
196 	fac = ac;
197 	fav = av;
198 	if (getfiles(&fac, &fav, options) <= 0) {
199 		error("No files given.\n");
200 		usage(EX_BAD);
201 	}
202 	oldname = fav[0];
203 	if ((of = xfileopen(0, fav[0], "rb")) == NULL)
204 		comerr("Cannot open file %s\n", fav[0]);
205 	fac--, fav++;
206 
207 	if (getfiles(&fac, &fav, options) <= 0) {
208 		error("Only one file given.\n");
209 		usage(EX_BAD);
210 	}
211 	newname = fav[0];
212 	if ((nf = xfileopen(1, fav[0], "rb")) == NULL)
213 		comerr("Cannot open file %s\n", fav[0]);
214 	fac--, fav++;
215 
216 	if (getfiles(&fac, &fav, options) > 0) {
217 		error("Too many files given.\n");
218 		usage(EX_BAD);
219 	}
220 	fac--, fav++;
221 
222 	if (filestat(of, &sb1) < 0)
223 		comerr("Cannot stat '%s'\n", oldname);
224 	if (filestat(nf, &sb2) < 0)
225 		comerr("Cannot stat '%s'\n", newname);
226 	if (sb1.st_ino == sb2.st_ino && sb1.st_dev == sb2.st_dev)
227 		goto same;
228 
229 #ifdef	HAVE_SETVBUF
230 	setvbuf(of, NULL, _IOFBF, MAXLINE);
231 	setvbuf(nf, NULL, _IOFBF, MAXLINE);
232 #endif
233 	if (sb1.st_size == sb2.st_size) {
234 		long	ob[MAXLINE / sizeof (long)];
235 		long	nb[MAXLINE / sizeof (long)];
236 		char	*op = (char *)ob;
237 		char	*np = (char *)nb;
238 		int	n1;
239 		int	n2;
240 
241 		while ((n1 = xfileread(0, &op, sizeof (ob))) > 0) {
242 			n2 = xfileread(1, &np, sizeof (nb));
243 
244 			if (n1 != n2)
245 				goto notsame;
246 			if (cmpbytes(op, np, n1) < n1)
247 				goto notsame;
248 		}
249 		if (n1 < 0)
250 			goto notsame;
251 		goto same;
252 	}
253 notsame:
254 	if (!bdiffmode)
255 		ret = 1;
256 	if (xfileseek(0, (off_t)0) == (off_t)-1 ||
257 	    xfileseek(1, (off_t)0) == (off_t)-1)
258 		comerr("Cannot seek.\n");
259 
260 	lineoff = readcommon(of, oldname, nf, newname);
261 	xfileseek(0, lineoff);
262 	xfileseek(1, lineoff);
263 
264 	olc = readfile(of, oldname, &oldfile);
265 	nlc = readfile(nf, newname, &newfile);
266 
267 	(void) diff();
268 same:
269 	fclose(of);
270 	fclose(nf);
271 	return (ret);
272 }
273 
274 
275 LOCAL const char *
filename(name)276 filename(name)
277 	const char	*name;
278 {
279 	char	*p;
280 
281 	if ((p = strrchr(name, '/')) == NULL)
282 		return (name);
283 	return (++p);
284 }
285 
286 LOCAL off_t
readcommon(ofp,oname,nfp,nname)287 readcommon(ofp, oname, nfp, nname)
288 	FILE	*ofp;
289 	char	*oname;
290 	FILE	*nfp;
291 	char	*nname;
292 {
293 	off_t	lineoff = 0;
294 	off_t	readoff = 0;
295 
296 	clc = 0;
297 	for (;;) {
298 		long	ob[MAXLINE / sizeof (long)];
299 		long	nb[MAXLINE / sizeof (long)];
300 		char	*op = (char *)ob;
301 		char	*np = (char *)nb;
302 		int	n;
303 		int	n1;
304 		int	n2;
305 		char	*oop;
306 		char	*nl;
307 
308 		n1 = xfileread(0, &op, sizeof (ob));
309 		n2 = xfileread(1, &np, sizeof (nb));
310 		if (n1 <= 0 || n2 <= 0)
311 			break;
312 		if (n2 < n1)
313 			n1 = n2;
314 		n = n2 = cmpbytes(op, np, n1);
315 
316 		/*
317 		 * Count newlines in common part.
318 		 */
319 		oop = op;
320 		while (n > 0 && (nl = findbytes(op, n, '\n'))) {
321 			lineoff = readoff + 1 + (nl - (char *)oop);
322 			n -= nl - op + 1;
323 			op = nl + 1;
324 			clc++;
325 		}
326 		if (n2 != n1)	/* cmpbytes() signalled a difference	*/
327 			break;	/* or n1 was less than n2		*/
328 		readoff += n2;
329 	}
330 	return (lineoff);
331 }
332 
333 LOCAL long
readfile(f,fname,hlinep)334 readfile(f, fname, hlinep)
335 	register FILE	*f;
336 		char	*fname;
337 		line	**hlinep;
338 {
339 	register ssize_t len;
340 	register int	lch = -1;	/* Last character from last read line */
341 		off_t	loff = 0;
342 		long	lc = 0;
343 		long	lsize = 0;
344 static	int		xgrow = 1024;
345 #define	MAX_GROW		16384
346 
347 	/*
348 	 * Get current posision as we skipped over the common parts.
349 	 */
350 	loff = xfilepos(f == of ? 0:1);
351 
352 	/*
353 	 * Use getdelim() to include the newline in the hash.
354 	 * This allows to correctly deal with files that do not end in a newline
355 	 * and this allows to handle nul bytes in the line.
356 	 */
357 	if (olsz == 0)		/* If first file is mmap()d and 2nd is not */
358 		olbf = NULL;	/* could be != NULL from mmap() space */
359 	for (;;) {
360 		if ((len = xgetdelim(&olbf, &olsz, '\n', f == of ? 0:1)) < 0) {
361 			if (ferror(f))
362 				comerr("Cannot read '%s'.\n", fname);
363 			break;
364 		}
365 		lch = ((unsigned char *)olbf)[len-1];
366 		if (lc >= lsize) {
367 			lsize += xgrow;
368 			*hlinep = ___realloc(*hlinep,
369 					(lsize) * sizeof (line),
370 					"new line");
371 			if (xgrow < MAX_GROW)
372 				xgrow *= 2;
373 		}
374 		addline(olbf, len, loff, lc++, *hlinep);
375 #ifdef	USE_CRLF
376 		loff = filepos(f);
377 #else	/* USE_CRLF */
378 		loff += len;
379 #endif	/* USE_CRLF */
380 	}
381 	if (lch >= 0 && lch != '\n') {
382 		error("Warning: missing newline at end of file %s\n", fname);
383 	}
384 	return (lc);
385 }
386 
387 LOCAL void
addline(s,ll,loff,lc,hline)388 addline(s, ll, loff, lc, hline)
389 	register char	*s;
390 	size_t	ll;
391 	off_t	loff;
392 	long	lc;
393 	line	*hline;
394 {
395 	line	*lp;
396 
397 	lp = glinep(lc, hline);
398 	lp->off = loff;
399 	lp->hash = hash(s, ll);
400 }
401 
402 
403 LOCAL long
hash(s,ll)404 hash(s, ll)
405 	register char	*s;
406 	register size_t	ll;
407 {
408 	register long	h;
409 
410 	for (h = 0; ll-- > 0; ) {
411 		if (h < 0) {
412 			h <<= 1;
413 			h += (*s++ & 255)+1;	/* rotate sign bit */
414 		} else {
415 			h <<= 1;
416 			h += (*s++ & 255);
417 		}
418 	}
419 	return (h);
420 }
421 
422 
423 LOCAL BOOL
linecmp(olp,nlp)424 linecmp(olp, nlp)
425 	line	*olp;
426 	line	*nlp;
427 {
428 	ssize_t	lo;
429 	ssize_t	ln;
430 
431 	if (olp->hash != nlp->hash)
432 		return (FALSE);
433 
434 	if (ooff != olp->off) {
435 		xfileseek(0, olp->off);
436 		ooff = olp->off;
437 	}
438 	lo = xgetdelim(&olbf, &olsz, '\n', 0);
439 	if (lo < 0)
440 		return (FALSE);
441 	ooff += lo;
442 #ifdef	USE_CRLF
443 	ooff = filepos(of);
444 #endif
445 
446 	if (noff != nlp->off) {
447 		xfileseek(1, nlp->off);
448 		noff = nlp->off;
449 	}
450 	ln = xgetdelim(&nlbf, &nlsz, '\n', 1);
451 	if (ln < 0)
452 		return (FALSE);
453 	noff += ln;
454 #ifdef	USE_CRLF
455 	ooff = filepos(nf);
456 #endif
457 
458 	if (lo != ln)
459 		return (FALSE);
460 	return (cmpbytes(olbf, nlbf, lo) >= lo);
461 }
462 
463 
464 LOCAL int
diff()465 diff()
466 {
467 	register long	oln;
468 	register long	nln;
469 	register long	k;
470 	register long	l;
471 	register long	i;
472 	register long	j;
473 		long	mx;
474 		BOOL	b;
475 		BOOL	m;
476 		int	ret = 0;
477 
478 	ooff = noff = -1;
479 	oln = 0, nln = 0;
480 	while (oln < olc && nln < nlc) {
481 		if (compare(oln, nln)) {
482 			oln++;
483 			nln++;
484 			continue;	/* Nothing changed */
485 		}
486 
487 		ret = 1;
488 		mx = (olc-oln)+(nlc-nln);
489 		m = FALSE;
490 		for (k = 1; k < mx; k++)
491 		    for (l = 0; l <= k; l++) {
492 			if (oln+l >= olc)
493 				break;
494 			if (nln+k-l >= nlc) {
495 				l = nln+k - nlc;
496 				continue;
497 			}
498 			if (compare((long)(oln+l), (long)(nln+k-l))) {
499 				for (j = 1, b = FALSE;
500 					(j < nmatch) &&
501 					(oln+l+j < olc) && (nln+k-l+j < nlc);
502 								j++) {
503 					if (!compare((long)(oln+l+j), (long)(nln+k-l+j))) {
504 						b = TRUE;
505 						break;
506 					}
507 				}
508 				if (!b) {
509 					if (l == 0)
510 						showadd(oln, nln, k);
511 					else if (k-l == 0)
512 						showdel(oln, nln, l);
513 					else if (l == k-l)
514 						showchange(oln, nln, l);
515 					else
516 						showxchange(oln, nln, l, (long)(k-l));
517 					oln += l;
518 					nln += k-l;
519 					m = TRUE;
520 					goto out;
521 				}
522 			}
523 		}
524 	    out:
525 		if (!m)
526 			break;
527 	}
528 	i = olc-oln;
529 	j = nlc-nln;
530 
531 	if (i == 0 && j == 0)
532 		return (ret);
533 	else if (i == j)
534 		showchange(oln, nln, i);
535 	else if (j && i)
536 		showxchange(oln, nln, i, j);
537 	else if (i)
538 		showdel(oln, nln, i);
539 	else if (j)
540 		showadd(oln, nln, j);
541 	ret = 1;
542 	return (ret);
543 }
544 
545 
546 LOCAL void
showdel(o,n,c)547 showdel(o, n, c)
548 	long	o;
549 	long	n;
550 	long	c;
551 {
552 	long	i;
553 	line	*lp;
554 	ssize_t	lo;
555 
556 	o += clc;
557 	n += clc;
558 	if (posix) {
559 		if (c == 1)
560 			printf("%ldd%ld\n", o+1, n);
561 		else
562 			printf("%ld,%ldd%ld\n", o+1, o+c, n);
563 	} else if (c == 1)
564 		gtprintf("\n-------- 1 line deleted at %ld:\n", o);
565 	else
566 		gtprintf("\n-------- %ld lines deleted at %ld:\n", c, o);
567 	o -= clc;
568 	n -= clc;
569 
570 	for (i = 0; i < c; i++) {
571 		lp = glinep((long)(o+i), oldfile);
572 		if (ooff != lp->off) {
573 			xfileseek(0, lp->off);
574 			ooff = lp->off;
575 		}
576 		lo = xgetdelim(&olbf, &olsz, '\n', 0);
577 		ooff += lo;
578 #ifdef	USE_CRLF
579 		ooff = filepos(of);
580 #endif
581 		if (posix)
582 			printf("< ");
583 		filewrite(stdout, olbf, lo);
584 		if (olbf[lo-1] !=  '\n')
585 			putchar('\n');
586 	}
587 }
588 
589 
590 LOCAL void
showadd(o,n,c)591 showadd(o, n, c)
592 	long	o;
593 	long 	n;
594 	long	c;
595 {
596 	long	i;
597 	line	*lp;
598 	ssize_t	ln;
599 
600 	o += clc;
601 	n += clc;
602 	if (posix) {
603 		if (c == 1)
604 			printf("%lda%ld\n", o, n+1);
605 		else
606 			printf("%lda%ld,%ld\n", o, n+1, n+c);
607 	} else if (c == 1)
608 		gtprintf("\n-------- 1 line added at %ld:\n", o);
609 	else
610 		gtprintf("\n-------- %ld lines added at %ld:\n", c, o);
611 	o -= clc;
612 	n -= clc;
613 
614 	for (i = 0; i < c; i++) {
615 		lp = glinep((long)(n+i), newfile);
616 		if (noff != lp->off) {
617 			xfileseek(1, lp->off);
618 			noff = lp->off;
619 		}
620 		ln = xgetdelim(&nlbf, &nlsz, '\n', 1);
621 		noff += ln;
622 #ifdef	USE_CRLF
623 		noff = filepos(nf);
624 #endif
625 		if (posix)
626 			printf("> ");
627 		filewrite(stdout, nlbf, ln);
628 		if (nlbf[ln-1] !=  '\n')
629 			putchar('\n');
630 	}
631 }
632 
633 
634 LOCAL void
showchange(o,n,c)635 showchange(o, n, c)
636 	long	o;
637 	long	n;
638 	long	c;
639 {
640 	long	i;
641 	line	*lp;
642 	ssize_t	lo;
643 	ssize_t	ln;
644 
645 	o += clc;
646 	n += clc;
647 	if (posix) {
648 		if (c == 1)
649 			printf("%ldc%ld\n", o+1, n+1);
650 		else
651 			printf("%ld,%ldc%ld,%ld\n", o+1, o+c, n+1, n+c);
652 	} else if (c == 1)
653 		gtprintf("\n-------- 1 line changed at %ld from:\n", o);
654 	else
655 		gtprintf("\n-------- %ld lines changed at %ld-%ld from:\n",
656 							c, o, o+c-1);
657 	o -= clc;
658 	n -= clc;
659 
660 	for (i = 0; i < c; i++) {
661 		lp = glinep((long)(o+i), oldfile);
662 		if (ooff != lp->off) {
663 			xfileseek(0, lp->off);
664 			ooff = lp->off;
665 		}
666 		lo = xgetdelim(&olbf, &olsz, '\n', 0);
667 		ooff += lo;
668 #ifdef	USE_CRLF
669 		ooff = filepos(of);
670 #endif
671 		if (posix)
672 			printf("< ");
673 		filewrite(stdout, olbf, lo);
674 		if (olbf[lo-1] !=  '\n')
675 			putchar('\n');
676 	}
677 	if (posix)
678 		printf("---\n");
679 	else
680 		gtprintf("-------- to:\n");
681 	for (i = 0; i < c; i++) {
682 		lp = glinep((long)(n+i), newfile);
683 		if (noff != lp->off) {
684 			xfileseek(1, lp->off);
685 			noff = lp->off;
686 		}
687 		ln = xgetdelim(&nlbf, &nlsz, '\n', 1);
688 		noff += ln;
689 #ifdef	USE_CRLF
690 		noff = filepos(nf);
691 #endif
692 		if (posix)
693 			printf("> ");
694 		filewrite(stdout, nlbf, ln);
695 		if (nlbf[ln-1] !=  '\n')
696 			putchar('\n');
697 	}
698 }
699 
700 
701 LOCAL void
showxchange(o,n,oc,nc)702 showxchange(o, n, oc, nc)
703 	long	o;
704 	long	n;
705 	long	oc;
706 	long	nc;
707 {
708 	long	i;
709 	line	*lp;
710 	ssize_t	lo;
711 	ssize_t	ln;
712 
713 	o += clc;
714 	n += clc;
715 	if (posix) {
716 		if (oc == 1)
717 			printf("%ldc%ld,%ld\n", o+1, n+1, n+nc);
718 		else if (nc == 1)
719 			printf("%ld,%ldc%ld\n", o+1, o+oc, n+1);
720 		else
721 			printf("%ld,%ldc%ld,%ld\n", o+1, o+oc, n+1, n+nc);
722 	} else if (oc == 1)
723 		gtprintf("\n-------- 1 line changed to %ld lines at %ld from:\n",
724 							nc, o);
725 	else if (nc == 1)
726 		gtprintf("\n-------- %ld lines changed to 1 line at %ld-%ld from:\n",
727 							oc, o, o+oc-1);
728 	else
729 		gtprintf("\n-------- %ld lines changed to %ld lines at %ld-%ld from:\n",
730 							oc, nc, o, o+oc-1);
731 	o -= clc;
732 	n -= clc;
733 
734 	for (i = 0; i < oc; i++) {
735 		lp = glinep((long)(o+i), oldfile);
736 		if (ooff != lp->off) {
737 			xfileseek(0, lp->off);
738 			ooff = lp->off;
739 		}
740 		lo = xgetdelim(&olbf, &olsz, '\n', 0);
741 		ooff += lo;
742 #ifdef	USE_CRLF
743 		ooff = filepos(of);
744 #endif
745 		if (posix)
746 			printf("< ");
747 		filewrite(stdout, olbf, lo);
748 		if (olbf[lo-1] !=  '\n')
749 			putchar('\n');
750 	}
751 	if (posix)
752 		printf("---\n");
753 	else
754 		gtprintf("-------- to:\n");
755 	for (i = 0; i < nc; i++) {
756 		lp = glinep((long)(n+i), newfile);
757 		if (noff != lp->off) {
758 			xfileseek(1, lp->off);
759 			noff = lp->off;
760 		}
761 		ln = xgetdelim(&nlbf, &nlsz, '\n', 1);
762 		noff += ln;
763 #ifdef	USE_CRLF
764 		noff = filepos(nf);
765 #endif
766 		if (posix)
767 			printf("> ");
768 		filewrite(stdout, nlbf, ln);
769 		if (nlbf[ln-1] !=  '\n')
770 			putchar('\n');
771 	}
772 }
773 
774 #include <schily/mman.h>
775 #include <schily/errno.h>
776 
777 LOCAL	FILE	*xf[2];
778 LOCAL	off_t	mmsize[2];
779 LOCAL	char	*mmbase[2];
780 LOCAL	char	*mmend[2];
781 LOCAL	char	*mmnext[2];
782 
783 LOCAL FILE *
xfileopen(idx,name,mode)784 xfileopen(idx, name, mode)
785 	int	idx;
786 	char	*name;
787 	char	*mode;
788 {
789 	FILE	*f;
790 	struct stat	sb;
791 
792 	f = fileopen(name, mode);
793 	if (f == NULL)
794 		return (NULL);
795 	xf[idx] = f;
796 
797 #ifdef	HAVE_MMAP
798 	if (fstat(fileno(f), &sb) < 0) {
799 		fclose(f);
800 		return (NULL);
801 	}
802 	mmsize[idx] = sb.st_size;
803 	if (sb.st_size > (64*1024*1024))
804 		return (f);
805 
806 	if (!S_ISREG(sb.st_mode))	/* FIFO has st_size == 0 */
807 		return (f);		/* so cannot use mmap()  */
808 
809 	if (sb.st_size == 0)
810 		mmbase[idx] = "";
811 	else
812 		mmbase[idx] = mmap((void *)0, mmsize[idx],
813 			PROT_READ, MAP_PRIVATE, fileno(f), (off_t)0);
814 
815 	if (mmbase[idx] == MAP_FAILED) {
816 		/*
817 		 * Silently fall back to the read method.
818 		 */
819 		mmbase[idx] = NULL;
820 	} else if (mmbase[idx]) {
821 		mmnext[idx] = mmbase[idx];
822 		mmend[idx] = mmbase[idx] + mmsize[idx];
823 	}
824 #endif
825 	return (f);
826 }
827 
828 LOCAL int
xfileseek(idx,off)829 xfileseek(idx, off)
830 	int	idx;
831 	off_t	off;
832 {
833 #ifndef	HAVE_MMAP
834 	return (fileseek(xf[idx], off));
835 #else
836 	if (mmbase[idx] == NULL)
837 		return (fileseek(xf[idx], off));
838 
839 	mmnext[idx] = mmbase[idx] + off;
840 
841 	if (mmnext[idx] > (mmbase[idx] + mmsize[idx])) {
842 		mmnext[idx] = (mmbase[idx] + mmsize[idx]);
843 		seterrno(EINVAL);
844 		return (-1);
845 	}
846 	if (mmnext[idx] < mmbase[idx]) {
847 		mmnext[idx] = mmbase[idx];
848 		seterrno(EINVAL);
849 		return (-1);
850 	}
851 	return (0);
852 #endif
853 }
854 
855 LOCAL off_t
xfilepos(idx)856 xfilepos(idx)
857 	int	idx;
858 {
859 #ifndef	HAVE_MMAP
860 	return (filepos(xf[idx]));
861 #else
862 	if (mmbase[idx] == NULL)
863 		return (filepos(xf[idx]));
864 
865 	return (mmnext[idx] - mmbase[idx]);
866 #endif
867 }
868 
869 LOCAL ssize_t
xgetdelim(lineptr,n,delim,idx)870 xgetdelim(lineptr, n, delim, idx)
871 	char	**lineptr;
872 	size_t	*n;
873 	int	delim;
874 	int	idx;
875 {
876 #ifndef	HAVE_MMAP
877 	return (getdelim(lineptr, n, delim, xf[idx]));
878 #else
879 	char	*p;
880 	ssize_t	siz;
881 	ssize_t	amt;
882 
883 	if (mmbase[idx] == NULL)
884 		return (getdelim(lineptr, n, delim, xf[idx]));
885 
886 	*lineptr = mmnext[idx];
887 	siz = mmend[idx] - mmnext[idx];
888 	if (siz == 0) {
889 		*lineptr = "";
890 		return (-1);
891 	}
892 	p = findbytes(mmnext[idx], siz, delim);
893 	if (p == NULL) {
894 		amt = siz;
895 		mmnext[idx] = mmend[idx];
896 	} else {
897 		amt = ++p - mmnext[idx];
898 		mmnext[idx] = p;
899 	}
900 	if (amt == 0) {
901 		*lineptr = "";
902 		return (-1);
903 	}
904 	return (amt);
905 #endif
906 }
907 
908 LOCAL ssize_t
xfileread(idx,lineptr,n)909 xfileread(idx, lineptr, n)
910 	int	idx;
911 	char	**lineptr;
912 	size_t	n;
913 {
914 #ifndef	HAVE_MMAP
915 	return (fileread(xf[idx], *lineptr, n));
916 #else
917 	ssize_t	siz;
918 	ssize_t	amt;
919 
920 	if (mmbase[idx] == NULL)
921 		return (fileread(xf[idx], *lineptr, n));
922 
923 	*lineptr = mmnext[idx];
924 	siz = mmend[idx] - mmnext[idx];
925 	amt = n;
926 	if (amt > siz) {
927 		amt = siz;
928 		mmnext[idx] = mmend[idx];
929 	} else {
930 		mmnext[idx] += amt;
931 	}
932 	if (amt == 0)
933 		*lineptr = "";
934 	return (amt);
935 #endif
936 }
937