Relay-Version: version B 2.10 5/3/83; site utzoo.UUCP Path: utzoo!watmath!clyde!caip!seismo!ut-sally!im4u!milano!peterson From: peterson@milano.UUCP Newsgroups: net.unix-wizards Subject: Subtle bug in fflush and fix (long) Message-ID: <1781@milano.UUCP> Date: Tue, 8-Jul-86 11:47:32 EDT Article-I.D.: milano.1781 Posted: Tue Jul 8 11:47:32 1986 Date-Received: Wed, 9-Jul-86 03:26:41 EDT Sender: peterson@milano.UUCP Organization: MCC, Austin, TX Lines: 352 Keywords: signals write fflush _flsbuf 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 #include #include 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@mcc.arpa or ...sally!im4u!milano!peterson