Subtle bug in fflush and fix (long)
peterson at milano.UUCP
peterson at milano.UUCP
Wed Jul 9 01:47:32 AEST 1986
The stdio package does not seem to properly account for some effects
of signals. In particular, I have had instances of the following:
1. Output by one process into a pipe or socket, using the stdio
package (specifically putc), buffering output and eventually calling
_flsbuf to actually write the buffer to the pipe or socket.
2. Over time, the writing process gets ahead of the reading process,
causing the internal system block buffers for this pipe/socket to
fill. Eventually the "write" system call in _flsbuf or fflush
blocks, waiting for the buffered data to be read so that more
can be written.
3. The reading process was busy because of some interesting other
business and sends a signal to the writing process to let it know
of the interesting events it has had, and then goes back to reading.
4. The signal breaks the write system call (in _flsbuf or fflush)
and returns only a partial write ( 0 < write(...,n) < n) Presumably,
errno is EINTR. The stdio routines interpret this condition as an
error and return EOF. The buffer is set empty however, so that the
application program has no way of knowing how much was (and was not)
written.
Correcting this problem requires checking each write to see if some
bytes have been written, and as long as bytes are being written,
continuing to write more. Eventually, either all bytes will be
written or an error will occur (write(...) <= 0).
The affected code is in flsbuf.c. A revised version follows:
/* @(#)flsbuf.c 4.6 (Berkeley) 6/30/83 */
/* ************************************************************** */
/*
There are four routines in this file:
_flsbuf(c, FILE *) an attempt was made to put the
char c into an apparently full buffer
_cleanup() flush all buffers on exit
fclose(FILE *) flush and close the file
fflush(FILE *) flush the buffer
*/
/*
In general, each FILE * has an I/O buffer allocated for it.
Output charcters are put into the buffer until it is full.
The buffer is flushed when
it is full and another character is to be added to
the buffer (_flsbuf -- called by putc),
the program terminates (_cleanup),
the file is closed (fclose), or
it is explicitly emptied (fflush).
The FILE * structure uses the following variables to keep
track of the state of the buffer:
_base the base address of the buffer; if it is
NULL, then no space has yet been allocated.
_bufsiz the amount of space in the buffer
_ptr pointer to the next empty space in the buffer
_cnt the number of empty spaces still in the buffer
normally we would expect _cnt to be
_bufsiz - (_ptr - _base)
but if the file is unbuffered or line buffered,
_cnt is always set to zero.
_flag various flags: see the list in stdio.h
*/
/* ************************************************************** */
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
char *malloc ();
_flsbuf(c, iop)
register FILE * iop;
{
register int returncode = c;
/* I/O to stdout should go through the stdout buffer */
extern char _sobuf[];
/*
check if the file can be either read or write (IORW) and,
if so, change it to be in a write state (IOWRT). Also clear
the read state (IOREAD) and any EOF flag (IOEOF)
*/
if (iop->_flag & _IORW)
{
iop->_flag |= _IOWRT;
iop->_flag &= ~(_IOEOF | _IOREAD);
}
/* if the file is not in a write state (IOWRT), exit */
if ((iop->_flag & _IOWRT) == 0)
return(EOF);
tryagain:
/* check for line buffering -- flush on buffer full or newline */
if (iop->_flag & _IOLBF)
{
*iop->_ptr++ = c;
if (iop->_ptr >= iop->_base + iop->_bufsiz || c == '\n')
{
if (WriteBuffer(iop) == EOF)
return(EOF);
}
/* setting cnt to zero falsely indicates that there is no room
left in the buffer -- forces putc to call flsbuf on every
character */
iop->_cnt = 0;
return(c);
}
/* check for no buffering -- flush immediately */
if (iop->_flag & _IONBF)
{
/* if no buffering is wanted, output the character immediately
*/
char c1;
int n;
c1 = c;
n = write(fileno(iop), &c1, 1);
iop->_cnt = 0;
/* since only one character is being written, it is either
written, or we have an error */
if (n == 0)
return(EOF);
return(c);
}
/*
normal case: block buffering.
In general, we want to empty the buffer.
The other possibility is that this is the very first
call, and we need to allocate space for this file
*/
if (iop->_base != NULL)
{
/* normal case -- empty the buffer */
/* compute the number of bytes to be written */
if (iop->_ptr > iop->_base)
{
returncode = WriteBuffer(iop);
}
}
else
{
/* no space has been allocated for the buffer for
this file */
int size;
/*
The _base field points at the buffer space; if it
is NULL, we need to allocate space.
We want to have our buffer size match the size of
the blocks used by the operating system for this
file, if possible. Check if the file exists and has
a block size. If not, use a default block size
*/
{
struct stat stbuf;
if (fstat(fileno(iop), &stbuf) < 0 ||
stbuf.st_blksize <= NULL)
size = BUFSIZ;
else
size = stbuf.st_blksize;
}
/* for standard output (stdout) to a tty, we should use line
buffering. Set the line buffering flag (LBF) and,
set the buffer pointers to _sobuf, and start over.
*/
if (iop == stdout)
{
if (isatty(fileno(stdout)))
{
iop->_flag |= _IOLBF;
iop->_base = _sobuf;
iop->_ptr = _sobuf;
iop->_bufsiz = size;
goto tryagain;
}
}
/* now malloc space for the i/o buffer -- if no space
available, try as unbuffered */
if ((iop->_base = malloc(size)) == NULL)
{
iop->_flag |= _IONBF;
goto tryagain;
}
/* space is allocated; set MYBUF to indicate this, and set the
buffer size (bufsiz). No write is needed yet -- just store
the character in the buffer this time and return */
iop->_flag |= _IOMYBUF;
iop->_bufsiz = size;
iop->_ptr = iop->_base;
iop->_cnt = iop->_bufsiz;
returncode = 0;
}
/*
now add the extra character that caused the buffer
to be flushed to the previously empty buffer.
-- _cnt is the number of available spaces
-- _ptr points to the next available buffer byte
*/
*iop->_ptr++ = c;
iop->_cnt -= 1;
if (returncode == EOF)
return(EOF);
/* if no error, return the input character */
return(c);
}
/* ************************************************************** */
fflush(iop)
register struct _iobuf *iop;
{
if ((iop->_flag & (_IONBF | _IOWRT)) == _IOWRT
&& iop->_base != NULL && iop->_ptr > iop->_base)
return(WriteBuffer(iop));
return(0);
}
/* ************************************************************** */
/* write a buffer; return EOF if there was any error */
/*
This code handles the problem that if the
write system call is interrupted, for example by a signal,
the write would return without writing the entire buffer.
In this case, we retry the write until it either fails
or completes.
*/
static WriteBuffer (iop)
register struct _iobuf *iop;
{
register char *base;
register int nBufferBytes;
register int nBytesWritten;
base = iop->_base;
nBufferBytes = iop->_ptr - base;
/* write bytes as long as there are bytes to write, and bytes are
being written */
do
{
nBytesWritten = write(fileno(iop), base, nBufferBytes);
base += nBytesWritten;
nBufferBytes -= nBytesWritten;
} while (nBufferBytes > 0 && nBytesWritten > 0);
/* reset buffer variables for an empty buffer */
iop->_ptr = iop->_base;
if (iop->_flag & (_IOLBF | _IONBF))
iop->_cnt = 0;
else
iop->_cnt = iop->_bufsiz;
/* check for possible error in the write */
if (nBytesWritten == -1)
{
iop->_flag |= _IOERR;
return(EOF);
}
return(0);
}
/* ************************************************************** */
/*
* Flush buffers on exit
*/
_cleanup()
{
register struct _iobuf *iop;
extern struct _iobuf *_lastbuf;
for (iop = _iob; iop < _lastbuf; iop++)
fclose(iop);
}
/* ************************************************************** */
fclose(iop)
register struct _iobuf *iop;
{
register int r;
r = EOF;
if (iop->_flag & (_IOREAD | _IOWRT | _IORW)
&& (iop->_flag & _IOSTRG) == 0)
{
/* flush the buffers and close the file */
r = fflush(iop);
if (close(fileno(iop)) < 0)
r = EOF;
/* if memory was malloc'ed, free it */
if (iop->_flag & _IOMYBUF)
free(iop->_base);
}
/* reinitialize the i/o buffer to its initial state */
iop->_cnt = 0;
iop->_base = (char *) NULL;
iop->_ptr = (char *) NULL;
iop->_bufsiz = 0;
iop->_flag = 0;
iop->_file = 0;
return(r);
}
--
James Peterson
peterson at mcc.arpa or ...sally!im4u!milano!peterson
More information about the Comp.unix.wizards
mailing list