/*	@(#)flsbuf.c	2.8	*/
/*LINTLIBRARY*/
#include <stdio.h>
#include "stdiom.h"
#include <errno.h>

extern void free();
extern int errno, write(), close(), isatty();
extern char *malloc();
extern FILE *_lastbuf;
#if !u370
extern unsigned char *_stdbuf[];
#endif
extern unsigned char _smbuf[][_SBFSIZ];

/*
 * Flush buffers on exit
 */

void
_cleanup()
{
	register FILE *iop;

	for(iop = _iob; iop < _lastbuf; iop++)
		(void) fclose(iop);
}
/*
	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(iop)
register FILE *iop;
{
	register int rtn=EOF;

	if(iop == NULL)
		return(rtn);
	if(iop->_flag & (_IOREAD | _IOWRT | _IORW)) {
		rtn = (iop->_flag & _IONBF)? 0: fflush(iop);
		if(close(fileno(iop)) < 0) {
			rtn = EOF;
			errno = ENOENT;
		}
	}
	if(iop->_flag & _IOMYBUF) {
		free((char*)iop->_base);
		iop->_base = NULL;
	}
	iop->_flag = 0;
	iop->_cnt = 0;
	iop->_ptr = iop->_base;
	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(iop)
register FILE *iop;
{
	if (!(iop->_flag & _IOWRT)) {
		iop->_cnt = 0;
		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-buffer pointer
 * _bufend(iop).  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(c, iop)
unsigned char c;
register 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 >= _bufend(iop) )  /* 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(iop)
register FILE *iop;
{
	register unsigned char *base;
	register int n;

	n = iop->_ptr - (base = iop->_base);
	iop->_ptr = base;
	iop->_cnt = (iop->_flag &(_IONBF | _IOLBF)) ? 0 : _bufsiz(iop);
	_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(iop)
register 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->_base == NULL)    /* this is first I/O to file--get buffer */
		_findbuf(iop);
	if (iop->_ptr == iop->_base && !(iop->_flag & (_IONBF | _IOLBF)) )  {
		iop->_cnt = _bufsiz(iop); /* 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.
 */

_findbuf(iop)
register FILE *iop;
{	extern int errno;
	int sverr;
	register int fno = fileno(iop); /* file number */

	/* allocate a small block for unbuffered, large for buffered */
	if (iop->_flag & _IONBF)  {
		_bufend(iop) = (iop->_base = _smbuf[fno]) + _SBFSIZ;
	}  else  {
#if !u370
		if (fno < 2)  /* use existing bufs for stdin, stdout */
			_bufend(iop) = (iop->_base = _stdbuf[fno]) + BUFSIZ;
		else 
#endif
		if ((iop->_base = (unsigned char *) malloc(BUFSIZ+8)) != NULL) {
			/* if  we got a buffer */
			iop->_flag |= _IOMYBUF;
			_bufend(iop) = iop->_base + BUFSIZ;
		} else
			/* if no room for buffer, use small buffer */
			_bufend(iop) = (iop->_base = _smbuf[fno]) + _SBFSIZ;
	}
	iop->_ptr = iop->_base;
	sverr = errno;
	if ( isatty(fno) && !(iop->_flag & _IONBF) )
		iop->_flag |= _IOLBF;
	errno = sverr;		/* isatty may set errno to ENOTTY */
}

/* 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.
 */

_bufsync(iop)
register FILE *iop;
{
	register int spaceleft;

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