xref: /illumos-gate/usr/src/cmd/sdiff/sdiff.c (revision e3ae4b35c024af1196582063ecee3ab79367227d)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
28 /*	  All Rights Reserved  	*/
29 
30 
31 	/*
32 	 *	sdiff [-l] [-s] [-w #] [-o output] file1 file2
33 	 *	does side by side diff listing
34 	 *	-l leftside only for identical lines
35 	 *	-s silent; only print differences
36 	 *	-w # width of output
37 	 *	-o output  interactive creation of new output commands:
38 	 *		s	silent; do not print identical lines
39 	 *		v	turn off silent
40 	 *		l	copy left side to output
41 	 *		r	copy right side to output
42 	 *		e l	call ed with left side
43 	 *		e r	call ed with right side
44 	 *		e b	call ed with cat of left and right
45 	 *		e	call ed with empty file
46 	 *		q	exit from program
47 	 *
48 	 *	functions:
49 	 *	cmd	decode diff commands
50 	 *	put1	output left side
51 	 *	put2	output right side
52 	 *	putmid	output gutter
53 	 *	putline	output n chars to indicated file
54 	 *	getlen	calculate length of strings with tabs
55 	 *	cmdin	read and process interactive cmds
56 	 *	cpp	copy from file to file
57 	 *	edit	call ed with file
58 	 */
59 
60 #include <stdio.h>
61 #include <ctype.h>
62 #include <signal.h>
63 #include <sys/types.h>
64 #include <sys/stat.h>
65 #include <sys/wait.h>
66 #include <unistd.h>
67 #include <stdlib.h>
68 #include <locale.h>
69 #include <limits.h>
70 #include <string.h>
71 #include <wchar.h>
72 
73 #define	LMAX	BUFSIZ
74 #define	BMAX	BUFSIZ
75 #define	STDOUT	1
76 #define	WGUTTER	6
77 #define	WLEN	(WGUTTER * 2 + WGUTTER + 2)
78 #define	PROMPT	'%'
79 
80 static const char	twoblanks[3] = "  ";
81 
82 static const char	*DIFF	= "diff -b ";
83 static char	diffcmd[BMAX];
84 static char	inbuf[10];
85 
86 static int	llen = 130;	/* Default maximum line length written out */
87 static int	hlen;		/* Half line length with space for gutter */
88 static int	len1;		/* Calculated length of left side */
89 static int	nchars;		/* Number of characters in left side - */
90 					/* used for tab expansion */
91 static char	change = ' ';
92 static int	leftonly = 0;	/* if set print left side only for */
93 					/* identical lines */
94 static int	silent = 0;	/* if set do not print identical lines */
95 static int	midflg = 0;	/* set after middle was output */
96 static int	rcode = 0;	/* return code */
97 
98 
99 static char	*file1;
100 static FILE	*fdes1;
101 
102 static char	*file2;
103 static FILE	*fdes2;
104 
105 static FILE	*diffdes;
106 
107 static int oflag;
108 static char	*ofile;
109 static FILE	*odes;
110 
111 static char	*ltemp;
112 static FILE	*left;
113 
114 static char	*rtemp;
115 static FILE	*right;
116 
117 static FILE *tempdes;
118 static char *temp;
119 
120 /* decoded diff cmd- left side from to; right side from, to */
121 
122 static int from1, to1, from2, to2;
123 
124 static int num1, num2;		/* line count for left side file and right */
125 static int tempfd = -1;
126 
127 static char	*filename(char *, char *);
128 static char	*fgetline(FILE *);
129 static int	put1(void);
130 static int	put2(void);
131 static void	putline(FILE *, char *, int);
132 static int	cmd(char *);
133 static int	getlen(int, char *);
134 static void	putmid(int);
135 static void	error(char *, char *);
136 static void	onintr(void);
137 static void	sremove(void);
138 static void	cmdin(void);
139 static void	cpp(char *, FILE *, FILE *);
140 static void	edit(char *);
141 
142 int
143 main(int argc, char **argv)
144 {
145 	int	com;
146 	int	n1, n2, n;
147 	char	*bp;
148 	int	lfd = -1;
149 	int	rfd = -1;
150 
151 	if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
152 		(void) signal((int)SIGHUP, (void (*)(int))onintr);
153 	if (signal(SIGINT, SIG_IGN) != SIG_IGN)
154 		(void) signal((int)SIGINT, (void (*)(int))onintr);
155 	if (signal(SIGPIPE, SIG_IGN) != SIG_IGN)
156 		(void) signal((int)SIGPIPE, (void (*)(int))onintr);
157 	if (signal(SIGTERM, SIG_IGN) != SIG_IGN)
158 		(void) signal((int)SIGTERM, (void (*)(int))onintr);
159 
160 	(void) setlocale(LC_ALL, "");
161 #if	!defined(TEXT_DOMAIN)
162 #define	TEXT_DOMAIN	"SYS_TEST"
163 #endif
164 	(void) textdomain(TEXT_DOMAIN);
165 
166 	while (--argc > 1 && **++argv == '-') {
167 		switch (*++*argv) {
168 
169 		case 'w':
170 			/* -w# instead of -w # */
171 			if (*++*argv)
172 				llen = atoi(*argv);
173 			else {
174 				argc--;
175 				llen = atoi(*++argv);
176 			}
177 			if (llen < WLEN)
178 				error(gettext("Wrong line length %s"), *argv);
179 			if (llen > LMAX)
180 				llen = LMAX;
181 			break;
182 
183 		case 'l':
184 			leftonly++;
185 			break;
186 
187 		case 's':
188 			silent++;
189 			break;
190 		case 'o':
191 			oflag++;
192 			argc--;
193 			ofile = *++argv;
194 			break;
195 		default:
196 			error(gettext("Illegal argument: %s"), *argv);
197 		}
198 	}
199 	if (argc != 2) {
200 		(void) fprintf(stderr, gettext(
201 		"Usage: sdiff [-l] [-s] [-o output] [-w #] file1 file2\n"));
202 		return (2);
203 	}
204 
205 	file1 = *argv++;
206 	file2 = *argv;
207 	file1 = filename(file1, file2);
208 	file2 = filename(file2, file1);
209 	hlen = (llen - WGUTTER +1)/2;
210 
211 	if ((fdes1 = fopen(file1, "r")) == NULL)
212 		error(gettext("Cannot open: %s"), file1);
213 
214 	if ((fdes2 = fopen(file2, "r")) == NULL)
215 		error(gettext("Cannot open: %s"), file2);
216 
217 	if (oflag) {
218 		if (tempfd == -1) {
219 			temp = strdup("/tmp/sdiffXXXXXX");
220 			tempfd = mkstemp(temp);
221 			if (tempfd == -1) {
222 				error(gettext(
223 					"Cannot open/create temp %s"), temp);
224 				free(temp);
225 				temp = 0;
226 			}
227 		}
228 		ltemp = strdup("/tmp/sdifflXXXXXX");
229 		if ((lfd = mkstemp(ltemp)) == -1 ||
230 			(left = fdopen(lfd, "w")) == NULL)
231 				error(gettext(
232 					"Cannot open/create temp %s"),
233 					ltemp);
234 		rtemp = strdup("/tmp/sdiffrXXXXXX");
235 		if ((rfd = mkstemp(rtemp)) == -1 ||
236 			(right = fdopen(rfd, "w")) == NULL)
237 				error(gettext(
238 					"Cannot open/create temp file %s"),
239 					rtemp);
240 		if ((odes = fopen(ofile, "w")) == NULL)
241 			error(gettext("Cannot open output %s"), ofile);
242 	}
243 	/* Call DIFF command */
244 	(void) strcpy(diffcmd, DIFF);
245 	(void) strcat(diffcmd, file1);
246 	(void) strcat(diffcmd, " ");
247 	(void) strcat(diffcmd, file2);
248 	diffdes = popen(diffcmd, "r");
249 
250 	num1 = num2 = 0;
251 
252 	/*
253 	 * Read in diff output and decode commands
254 	 * "change" is used to determine character to put in gutter
255 	 *  num1 and num2 counts the number of lines in file1 and 2
256 	 */
257 
258 	n = 0;
259 	while ((bp = fgetline(diffdes)) != NULL) {
260 		change = ' ';
261 		com = cmd(bp);
262 
263 	/*
264 	 * handles all diff output that is not cmd
265 	 * lines starting with <, >, ., ---
266 	 */
267 		if (com == 0)
268 			continue;
269 
270 	/* Catch up to from1 and from2 */
271 		rcode = 1;
272 		n1 = from1 - num1;
273 		n2 = from2 - num2;
274 		n = n1 > n2 ? n2 : n1;
275 		if (com == 'c' && n > 0)
276 			n--;
277 		if (silent)
278 			(void) fputs(bp, stdout);
279 		while (n-- > 0) {
280 			(void) put1();
281 			(void) put2();
282 			if (!silent)
283 				(void) putc('\n', stdout);
284 			midflg = 0;
285 		}
286 
287 	/* Process diff cmd */
288 		switch (com) {
289 
290 		case 'a':
291 			change = '>';
292 			while (num2 < to2) {
293 				(void) put2();
294 				(void) putc('\n', stdout);
295 				midflg = 0;
296 			}
297 			break;
298 
299 		case 'd':
300 			change = '<';
301 			while (num1 < to1) {
302 				(void) put1();
303 				(void) putc('\n', stdout);
304 				midflg = 0;
305 			}
306 			break;
307 
308 		case 'c':
309 			n1 = to1 - from1;
310 			n2 = to2 - from2;
311 			n = n1 > n2 ? n2 : n1;
312 			change = '|';
313 			do {
314 				(void) put1();
315 				(void) put2();
316 				(void) putc('\n', stdout);
317 				midflg = 0;
318 			} while (n--);
319 
320 			change = '<';
321 			while (num1 < to1) {
322 				(void) put1();
323 				(void) putc('\n', stdout);
324 				midflg = 0;
325 			}
326 
327 			change = '>';
328 			while (num2 < to2) {
329 				(void) put2();
330 				(void) putc('\n', stdout);
331 				midflg = 0;
332 			}
333 			break;
334 
335 		default:
336 			(void) fprintf(stderr, gettext(
337 				"%c: cmd not found\n"), cmd);
338 			break;
339 		}
340 
341 		if (oflag == 1 && com != 0) {
342 			cmdin();
343 			if ((left = fopen(ltemp, "w")) == NULL)
344 				error(gettext(
345 					"main: Cannot open temp %s"), ltemp);
346 			if ((right = fopen(rtemp, "w")) == NULL)
347 				error(gettext(
348 					"main: Cannot open temp %s"), rtemp);
349 		}
350 	}
351 	/* put out remainder of input files */
352 
353 	while (put1()) {
354 		(void) put2();
355 		if (!silent)
356 			(void) putc('\n', stdout);
357 		midflg = 0;
358 	}
359 	if (odes)
360 		(void) fclose(odes);
361 	sremove();
362 	return (rcode);
363 }
364 
365 static int
366 put1(void)
367 {
368 	/* len1 = length of left side */
369 	/* nchars = num of chars including tabs */
370 
371 	char	*bp;
372 
373 
374 	if ((bp = fgetline(fdes1)) != NULL) {
375 		len1 = getlen(0, bp);
376 		if ((!silent || change != ' ') && len1 != 0)
377 			putline(stdout, bp, nchars);
378 
379 		if (oflag) {
380 		/*
381 		 * put left side either to output file
382 		 * if identical to right
383 		 * or left temp file if not
384 		 */
385 
386 			if (change == ' ')
387 				putline(odes, bp, strlen(bp));
388 			else
389 				putline(left, bp, strlen(bp));
390 		}
391 		if (change != ' ')
392 			putmid(1);
393 		num1++;
394 		return (1);
395 	} else
396 		return (0);
397 }
398 
399 static int
400 put2(void)
401 {
402 	char	*bp;
403 
404 	if ((bp = fgetline(fdes2)) != NULL) {
405 		(void) getlen((hlen + WGUTTER) % 8, bp);
406 
407 		/*
408 		 * if the left and right are different they are always
409 		 * printed.
410 		 * If the left and right are identical
411 		 * right is only printed if leftonly is not specified
412 		 * or silent mode is not specified
413 		 * or the right contains other than white space (len1 !=0)
414 		 */
415 		if (change != ' ') {
416 
417 		/*
418 		 * put right side to right temp file only
419 		 * because left side was written to output for
420 		 * identical lines
421 		 */
422 
423 			if (oflag)
424 				putline(right, bp, strlen(bp));
425 
426 			if (midflg == 0)
427 				putmid(1);
428 			putline(stdout, bp, nchars);
429 		} else
430 			if (!silent && !leftonly && len1 != 0) {
431 				if (midflg == 0)
432 					putmid(1);
433 				putline(stdout, bp, nchars);
434 			}
435 		num2++;
436 		len1 = 0;
437 		return (1);
438 	} else {
439 		len1 = 0;
440 		return (0);
441 	}
442 }
443 
444 static void
445 putline(FILE *file, char *start, int num)
446 {
447 	char	*cp, *end;
448 	int	i, len, d_col;
449 	wchar_t	wc;
450 
451 	cp = start;
452 	end = cp + num;
453 	while (cp < end) {
454 		if (isascii(*cp)) {
455 			(void) putc(*cp++, file);
456 			continue;
457 		}
458 
459 		if ((len = end - cp) > MB_LEN_MAX)
460 			len = MB_LEN_MAX;
461 
462 		if ((len = mbtowc(&wc, cp, len)) <= 0) {
463 			(void) putc(*cp++, file);
464 			continue;
465 		}
466 
467 		if ((d_col = wcwidth(wc)) <= 0)
468 			d_col = len;
469 
470 		if ((cp + d_col) > end)
471 			return;
472 
473 		for (i = 0; i < len; i++)
474 			(void) putc(*cp++, file);
475 	}
476 }
477 
478 static int
479 cmd(char *start)
480 {
481 	unsigned char	*cp;
482 	char	*cps;
483 	int	com;
484 
485 	if (*start == '>' || *start == '<' || *start == '-' || *start == '.')
486 		return (0);
487 
488 	cp = (unsigned char *)start;
489 	cps = start;
490 	while (isdigit(*cp))
491 		cp++;
492 	from1 = atoi(cps);
493 	to1 = from1;
494 	if (*cp == ',') {
495 		cp++;
496 		cps = (char *)cp;
497 		while (isdigit(*cp))
498 			cp++;
499 		to1 = atoi(cps);
500 	}
501 
502 	com = *cp++;
503 	cps = (char *)cp;
504 
505 	while (isdigit(*cp))
506 		cp++;
507 	from2 = atoi(cps);
508 	to2 = from2;
509 	if (*cp == ',') {
510 		cp++;
511 		cps = (char *)cp;
512 		while (isdigit(*cp))
513 			cp++;
514 		to2 = atoi(cps);
515 	}
516 	return (com);
517 }
518 
519 static int
520 getlen(int startpos, char *buffer)
521 {
522 	/*
523 	 * get the length of the string in buffer
524 	 *  expand tabs to next multiple of 8
525 	 */
526 	unsigned char	*cp;
527 	int	slen, tlen, len, d_col;
528 	int	notspace;
529 	wchar_t	wc;
530 
531 	nchars = 0;
532 	notspace = 0;
533 	tlen = startpos;
534 	for (cp = (unsigned char *)buffer; (*cp != '\n') && (*cp); cp++) {
535 		if (*cp == '\t') {
536 			slen = tlen;
537 			tlen += 8 - (tlen % 8);
538 			if (tlen >= hlen) {
539 				tlen = slen;
540 				break;
541 			}
542 			nchars++;
543 			continue;
544 		}
545 
546 		if (isascii(*cp)) {
547 			slen = tlen;
548 			tlen++;
549 			if (tlen >= hlen) {
550 				tlen = slen;
551 				break;
552 			}
553 			if (!isspace(*cp))
554 				notspace = 1;
555 			nchars++;
556 			continue;
557 		}
558 
559 		if ((len = mbtowc(&wc, (char *)cp, MB_LEN_MAX)) <= 0) {
560 			slen = tlen;
561 			tlen++;
562 			if (tlen >= hlen) {
563 				tlen = slen;
564 				break;
565 			}
566 			notspace = 1;
567 			nchars++;
568 			continue;
569 		}
570 
571 		if ((d_col = wcwidth(wc)) <= 0)
572 			d_col = len;
573 
574 		slen = tlen;
575 		tlen += d_col;
576 		if (tlen > hlen) {
577 			tlen = slen;
578 			break;
579 		}
580 		notspace = 1;
581 		cp += len - 1;
582 		nchars += len;
583 	}
584 	return (notspace ? tlen : 0);
585 }
586 
587 static void
588 putmid(int bflag)
589 {
590 	int	i;
591 
592 	/*
593 	 * len1 set by getlen to the possibly truncated
594 	 *  length of left side
595 	 *  hlen is length of half line
596 	 */
597 
598 	midflg = 1;
599 	if (bflag) {
600 		for (i = 0; i < hlen - len1; i++)
601 			(void) putc(' ', stdout);
602 	}
603 	(void) fputs(twoblanks, stdout);
604 	(void) putc((int)change, stdout);
605 	(void) fputs(twoblanks, stdout);
606 }
607 
608 static void
609 error(char *s1, char *s2)
610 {
611 	(void) fprintf(stderr, "sdiff: ");
612 	(void) fprintf(stderr, s1, s2);
613 	(void) putc('\n', stderr);
614 	sremove();
615 	exit(2);
616 }
617 
618 static void
619 onintr(void)
620 {
621 	sremove();
622 	exit(rcode);
623 }
624 
625 static void
626 sremove(void)
627 {
628 	if (ltemp) {
629 		(void) unlink(ltemp);
630 		free(ltemp);
631 	}
632 	if (rtemp) {
633 		(void) unlink(rtemp);
634 		free(rtemp);
635 	}
636 	if (temp) {
637 		(void) unlink(temp);
638 		free(temp);
639 	}
640 }
641 
642 static void
643 cmdin(void)
644 {
645 	char	*cp, *ename;
646 	int	notacc;
647 
648 	(void) fclose(left);
649 	(void) fclose(right);
650 	notacc = 1;
651 	while (notacc) {
652 		(void) putc(PROMPT, stdout);
653 		if ((cp = fgets(inbuf, 10, stdin)) == NULL) {
654 			(void) putc('\n', stdout);
655 			break;
656 		}
657 		switch (*cp) {
658 
659 		case 's':
660 			silent = 1;
661 			break;
662 
663 		case 'v':
664 			silent = 0;
665 			break;
666 
667 		case 'q':
668 			sremove();
669 			exit(rcode);
670 			/* NOTREACHED */
671 			break;
672 
673 		case 'l':
674 			cpp(ltemp, left, odes);
675 			notacc = 0;
676 			break;
677 
678 		case 'r':
679 			cpp(rtemp, right, odes);
680 			notacc = 0;
681 			break;
682 
683 		case 'e':
684 			while (*++cp == ' ')
685 				;
686 			switch (*cp) {
687 			case 'l':
688 			case '<':
689 				notacc = 0;
690 				ename = ltemp;
691 				edit(ename);
692 				break;
693 
694 			case 'r':
695 			case '>':
696 				notacc = 0;
697 				ename = rtemp;
698 				edit(ename);
699 				break;
700 
701 			case 'b':
702 			case '|':
703 				if ((tempdes = fopen(temp, "w")) == NULL)
704 					error(gettext(
705 						"Cannot open temp file %s"),
706 						temp);
707 				cpp(ltemp, left, tempdes);
708 				cpp(rtemp, right, tempdes);
709 				(void) fclose(tempdes);
710 				notacc = 0;
711 				ename = temp;
712 				edit(ename);
713 				break;
714 
715 			case '\n':
716 				if ((tempdes = fopen(temp, "w")) == NULL)
717 					error(gettext(
718 						"Cannot open temp file %s"),
719 						temp);
720 				(void) fclose(tempdes);
721 				notacc = 0;
722 				ename = temp;
723 				edit(ename);
724 				break;
725 			default:
726 				(void) fprintf(stderr, gettext(
727 					"Illegal command %s reenter\n"),
728 					cp);
729 				break;
730 			}
731 			if (notacc == 0)
732 				cpp(ename, tempdes, odes);
733 			break;
734 
735 		default:
736 			(void) fprintf(stderr, gettext(
737 				"Illegal command reenter\n"));
738 			break;
739 		}
740 	}
741 }
742 
743 static void
744 cpp(char *from, FILE *fromdes, FILE *todes)
745 {
746 	char	tempbuf[BMAX + 1];
747 
748 	if ((fromdes = fopen(from, "r")) == NULL)
749 		error(gettext(
750 			"cpp: Cannot open %s"), from);
751 	while ((fgets(tempbuf, BMAX, fromdes) != NULL))
752 		(void) fputs(tempbuf, todes);
753 	(void) fclose(fromdes);
754 }
755 
756 static void
757 edit(char *file)
758 {
759 	int	i;
760 	pid_t	pid;
761 	void (*oldintr)(int);
762 
763 	switch (pid = fork()) {
764 	case (pid_t)-1:
765 		error(gettext("Cannot fork"), NULL);
766 		/* NOTREACHED */
767 		break;
768 	case (pid_t)0:
769 		(void) execl("/usr/bin/ed", "ed", file, NULL);
770 	}
771 
772 	oldintr = signal(SIGINT, SIG_IGN);	/* ignore interrupts in ed */
773 	while (pid != wait(&i))
774 		;
775 	/* restore previous interrupt proc */
776 	(void) signal(SIGINT, oldintr);
777 }
778 
779 static char *
780 filename(char *pa1, char *pa2)
781 {
782 	int	c;
783 	char 	*a1, *b1, *a2;
784 	struct stat	stbuf;
785 	a1 = pa1;
786 	a2 = pa2;
787 	if (stat(a1, &stbuf) != -1 && ((stbuf.st_mode&S_IFMT) == S_IFDIR)) {
788 		b1 = pa1 = (char *)malloc(strlen(a1) + strlen(a2) + 2);
789 		while (*b1++ = *a1++);
790 		b1[-1] = '/';
791 		a1 = b1;
792 		while (*a1++ = *a2++)
793 			if (*a2 && *a2 != '/' && a2[-1] == '/')
794 				a1 = b1;
795 	} else if (a1[0] == '-' && a1[1] == 0 && temp == 0) {
796 		if (fstat(fileno(stdin), &stbuf) == -1)
797 			error(gettext("Cannot process stdin"), NULL);
798 		pa1 = temp = strdup("/tmp/sdiffXXXXXX");
799 		if ((tempfd = mkstemp(temp)) == -1 ||
800 			(tempdes = fdopen(tempfd, "w")) == NULL)
801 				error(gettext("Cannot open/create temp %s"),
802 					temp);
803 		while ((c = getc(stdin)) != EOF)
804 			(void) putc(c, tempdes);
805 		(void) fclose(tempdes);
806 	}
807 	return (pa1);
808 }
809 
810 /*
811  * like fgets, but reads upto and including a newline,
812  * the data is stored in a reusable dynamic buffer that grows to fit
813  * the largest line in the file, the buffer is NULL terminated
814  * returns a pointer to the dynamic buffer.
815  */
816 static char *
817 fgetline(FILE *fp)
818 {
819 	static char	*bp = NULL;
820 	static int	blen = 0;
821 	int	sl;
822 
823 	if (bp == NULL) {
824 		/* allocate it for the first time */
825 		bp = (char *)malloc(BUFSIZ);
826 		if (bp == NULL)
827 			error(gettext("fgetline: malloc failed"), NULL);
828 		blen = BUFSIZ;
829 	}
830 
831 	/* check for error or nothing read */
832 	if (fgets(bp, blen, fp) == NULL)
833 		return (NULL);
834 
835 	if (feof(fp))
836 		return (bp);
837 
838 	while ((sl = strlen(bp)) == blen-1 && *(bp+blen-2) != '\n') {
839 		/* still more data, grow the buffer */
840 		blen *= 2;
841 		bp = (char *)realloc(bp, blen);
842 		if (bp == NULL)
843 			error(gettext("fgetline: realloc failed"), NULL);
844 		/* continue reading and add to end of buffer */
845 		(void) fgets(bp+sl, blen-sl, fp);
846 	}
847 	return (bp);
848 }
849