/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 1989 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */
/*      Copyright (c) 1984 AT&T */
/*        All Rights Reserved   */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*LINTLIBRARY*/
#include <stdio.h>
#include "../common/stdiom.h"
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <malloc.h>
#include <unistd.h>

extern int	fclose();
extern unsigned char (*_smbuf)[_SBFSIZ];

void	_findbuf(FILE *);
void	_bufsync(FILE *);

/*
 * Flush buffers on exit
 */

void
_cleanup(void)
{

	_fwalk(fclose);
}
/*
 *	fclose() will flush (output) buffers for a buffered open
 *	FILE and then issue a system close on the _fileno.  The
 *	_base field will be reset to NULL for any but stdin and
 *	stdout, the _ptr field will be set the same as the _base
 *	field. The _flags and the _cnt field will be zeroed.
 *	If buffers had been obtained via malloc(), the space will
 *	be free()'d.  In case the FILE was not open, or fflush()
 *	or close() failed, an EOF will be returned, otherwise the
 *	return value is 0.
 */

int
fclose(FILE *iop)
{
	int rtn=EOF;

	if(iop == NULL)
		return(rtn);
	if(iop->_flag & (_IOREAD | _IOWRT | _IORW)
	   && (iop->_flag & _IOSTRG) == 0) {
		rtn = (iop->_flag & _IONBF)? 0: fflush(iop);
		if(close(fileno(iop)) < 0)
			rtn = EOF;
	}
	if(iop->_flag & _IOMYBUF) {
		free((char*)iop->_base);
		iop->_base = NULL;
	}
	iop->_flag = 0;
	iop->_cnt = 0;
	iop->_ptr = iop->_base;
	iop->_bufsiz = 0;
	return(rtn);
}

/*
 *	The fflush() routine must take care because of the
 *	possibility for recursion. The calling program might
 *	do IO in an interupt catching routine that is likely
 *	to interupt the write() call within fflush()
 */

int
fflush(FILE *iop)
{
	if (!(iop->_flag & _IOWRT)) {
		return(0);
	}
	while(!(iop->_flag & _IONBF) && (iop->_flag & _IOWRT) &&
			(iop->_base != NULL) && (iop->_ptr > iop->_base) )
		(void) _xflsbuf(iop);
	return(ferror(iop) ? EOF : 0);
}

/* The routine _flsbuf may or may not actually flush the output buffer.  If
 * the file is line-buffered, the fact that iop->_cnt has run below zero
 * is meaningless: it is always kept below zero so that invocations of putc
 * will consistently give control to _flsbuf, even if the buffer is far from
 * full.  _flsbuf, on seeing the "line-buffered" flag, determines whether the
 * buffer is actually full by comparing iop->_ptr to the end of the buffer
 * iop->_base + iop->_bufsiz.  If it is full, or if an output line is
 * completed (with a newline), the buffer is flushed.  (Note: the character
 * argument to _flsbuf is not flushed with the current buffer if the buffer
 * is actually full -- it goes into the buffer after flushing.)
 */

int
_flsbuf(unsigned char c, FILE *iop)
{
    unsigned char c1;

    do {
	/* check for linebuffered with write perm, but no EOF */
	if ( (iop->_flag & (_IOLBF | _IOWRT | _IOEOF)) == (_IOLBF | _IOWRT) ) {
		if ( iop->_ptr >= iop->_base + iop->_bufsiz )  /* if buffer full, */
			break;		    /* exit do-while, and flush buf. */
		if ( (*iop->_ptr++ = c) != '\n' )
			return(c);
		return(_xflsbuf(iop) == EOF ? EOF : c);
	}
	/* write out an unbuffered file, if have write perm, but no EOF */
	if ( (iop->_flag & (_IONBF | _IOWRT | _IOEOF)) == (_IONBF | _IOWRT) ) {
		c1 = c;
		iop->_cnt = 0;
		if (write(fileno(iop), (char *) &c1, 1) == 1)
			return(c);
		iop->_flag |= _IOERR;
		return(EOF);
	}
	/* The _wrtchk call is here rather than at the top of _flsbuf to re- */
	/* duce overhead for line-buffered I/O under normal circumstances.  */

	if (_WRTCHK(iop))			/* is writing legitimate? */
		return(EOF);
    } while ( (iop->_flag & (_IONBF | _IOLBF)) );


    (void) _xflsbuf(iop);   /* full buffer:  flush buffer */
    (void) putc((char) c, iop);  /* then put "c" in newly emptied buf */
			/* (which, because of signals, may NOT be empty) */
    return( ferror(iop) ? EOF : c);
}

/* The function _xflsbuf writes out the current contents of the output
 * buffer delimited by iop->_base and iop->_ptr.
 * iop->_cnt is reset appropriately, but its value on entry to _xflsbuf
 * is ignored.
 *
 * The following code is not strictly correct.  If a signal is raised,
 * invoking a signal-handler which generates output into the same buffer
 * being flushed, a peculiar output sequence may result (for example,
 * the output generated by the signal-handler may appear twice).  At
 * present no means has been found to guarantee correct behavior without
 * resorting to the disabling of signals, a means considered too expensive.
 * For now the code has been written with the intent of reducing the
 * probability of strange effects and, when they do occur, of confining
 * the damage.  Except under extremely pathological circumstances, this
 * code should be expected to respect buffer boundaries even in the face
 * of interrupts and other signals.
 */

int
_xflsbuf(FILE *iop)
{
	unsigned char *base;
	int n;

	n = iop->_ptr - (base = iop->_base);
	iop->_ptr = base;
	iop->_cnt = (iop->_flag &(_IONBF | _IOLBF)) ? 0 : iop->_bufsiz;
	_BUFSYNC(iop);
	if (n > 0 && n != write(fileno(iop),(char*)base,(unsigned)n) )  {
		iop->_flag |= _IOERR;
		return(EOF);
	}
	return(0);
}

/* The function _wrtchk checks to see whether it is legitimate to write
 * to the specified device.  If it is, _wrtchk sets flags in iop->_flag for
 * writing, assures presence of a buffer, and returns 0.  If writing is not
 * legitimate, EOF is returned.
 */

int
_wrtchk(FILE *iop)
{
	if ( (iop->_flag & (_IOWRT | _IOEOF)) != _IOWRT ) {
		if (!(iop->_flag & (_IOWRT | _IORW)))
			return(EOF);  /* bogus call--read-only file */
		iop->_flag = iop->_flag & ~_IOEOF | _IOWRT; /* fix flags */
	}
	if (iop->_flag & _IOSTRG)
		return(0);	/* not our business to monkey with buffers or counts */
	if (iop->_base == NULL)    /* this is first I/O to file--get buffer */
		_findbuf(iop);
	if (iop->_ptr == iop->_base && !(iop->_flag & (_IONBF | _IOLBF)) )  {
		iop->_cnt = iop->_bufsiz; /* first write since seek--set cnt */
		_BUFSYNC(iop);
	}
	return(0);
}

/*
 * _findbuf, called only when iop->_base == NULL, locates a predefined buffer
 * or allocates a buffer using malloc.  If a buffer is obtained from malloc,
 * the _IOMYBUF flag is set in iop->_flag.
 */

void
_findbuf(FILE *iop)
{
	int fno = fileno(iop); /* file number */
	struct stat statb;
	int size;

	/* allocate a small block for unbuffered, large for buffered */
	if (iop->_flag & _IONBF)  {
		iop->_base = _smbuf[fno];
		iop->_bufsiz = _SBFSIZ;
	}  else  {

		if ( isatty(fno) ) {
			iop->_flag |= _IOLBF;
			size = 128;
		} else {
			if (fstat(fno, &statb) < 0)
				size = BUFSIZ;
			else {
				if ((size = statb.st_blksize) <= 0)
					size = BUFSIZ;
			}
		}
		if ((iop->_base = (unsigned char *) malloc(size+8)) != NULL) {
			/* if  we got a buffer */
			iop->_flag |= _IOMYBUF;
			iop->_bufsiz = size;
		} else {
			/* if no room for buffer, use small buffer */
			iop->_base = _smbuf[fno];
			iop->_bufsiz = _SBFSIZ;
			iop->_flag &= ~_IOLBF;
			iop->_flag |= _IONBF;
		}
	}
	iop->_ptr = iop->_base;
}

/*
 * The function _bufsync is called because interrupts and other signals
 * which occur in between the decrementing of iop->_cnt and the incrementing
 * of iop->_ptr, or in other contexts as well, may upset the synchronization
 * of iop->_cnt and iop->ptr.  If this happens, calling _bufsync should
 * resynchronize the two quantities (this is not always possible).  Resyn-
 * chronization guarantees that putc invocations will not write beyond
 * the end of the buffer.  Note that signals during _bufsync can cause
 * _bufsync to do the wrong thing, but usually with benign effects.
 */

void
_bufsync(FILE *iop)
{
	int spaceleft;
	unsigned char *bufend = iop->_base + iop->_bufsiz;

	if ((spaceleft = bufend - iop->_ptr) < 0)
		iop->_ptr = bufend;
	else if (spaceleft < iop->_cnt)
		iop->_cnt = spaceleft;
}