[comp.bugs.4bsd] fprintf, putc, _IOLBF incompatible in Mt. Xinu 4.3

keith@reed.UUCP (06/29/87)

DESCRIPTION:

And I thought my lisp interpreter was at fault!  The snazzy new 4.3 putc
macro is supposed to speed up output to _IOLBF descriptors, and it uses the
fields in _iob in non-standard ways.  More specifically, it uses -(_cnt) to
count the number of characters in the buffer and doesn't _flsbuf until
-(_cnt) == _bufsiz (or putc'ing a \n).

A cute idea, but _doprnt doesn't agree.  _doprnt *always* sets the _cnt
field to 0 on an _IOLBF descriptor.  This is not normally a problem unless
the buffer is filled up (i.e. sent no \n).  In this case, putc merrily
tromps through memory past the end of the buffer.

REPEAT BY:

execute this program and observe the abort().

/*
 * demonstrates a bug in _IOLBF mode between putc and _doprnt
 * in Mt. Xinu's 4.3BSD for VAX 11/780
 */

# include	<stdio.h>

main ()
{
	FILE	*test;

	test = fopen ("/dev/null", "w");
	setlinebuf (test);

	/*
	 * here's the magic: _doprnt doesn't decrement
	 * test->_cnt by the number of characters printed
	 * if IOLBF is set.  putc is expecting it to and
	 * steps beyond the end of the buffer 7 times
	 */
	fprintf (test, " hello ");
	for (;;) {
		putc ('*', test);
		/*
		 * a test to see if _ptr is magically beyond
		 * the end of the buffer.  This can *never*
		 * happen :-)
		 */
		if (test->_ptr >= test->_base + test->_bufsiz) {
			printf ("test->_ptr: 0x%x\n", test->_ptr);
			printf ("test->_base: 0x%x\n", test->_base);
			printf ("test->_bufsiz: 0x%x\n", test->_bufsiz);
			abort ();
		}
	}
}


FIX:

I'd love to fix this inside _doprnt, but _doprnt is rather adamant about
having _cnt always >= 0.  Fortunately, _doprnt returns the number of
characters printed, and so fprintf and printf can set up the FILE for doprnt
and restore it to correctness on return:

*** /usr/src/lib/libc/stdio/fprintf.c	Sun Mar  9 20:50:32 1986
--- fprintf.c	Sun Jun 28 18:08:30 1987
***************
*** 26,31 ****
--- 26,38 ----
  		iop->_base = NULL;
  		iop->_bufsiz = NULL;
  		iop->_cnt = 0;
+ 	} else if (iop->_flag & _IOLBF) {
+ 		int	old_cnt;
+ 
+ 		old_cnt = iop->_cnt;
+ 		iop->_cnt = 0;
+ 		old_cnt -= _doprnt(fmt, &args, iop);
+ 		iop->_cnt = old_cnt;
  	} else
  		_doprnt(fmt, &args, iop);
  	return(ferror(iop)? EOF: 0);


*** /usr/src/lib/libc/stdio/printf.c	Sun Mar  9 20:52:47 1986
--- printf.c	Sun Jun 28 18:09:14 1987
***************
*** 7,12 ****
  printf(fmt, args)
  char *fmt;
  {
! 	_doprnt(fmt, &args, stdout);
  	return(ferror(stdout)? EOF: 0);
  }
--- 7,20 ----
  printf(fmt, args)
  char *fmt;
  {
! 	if (stdout->_flag & _IOLBF) {
! 		int	old_cnt;
! 
! 		old_cnt = stdout->_cnt;
! 		stdout->_cnt = 0;
! 		old_cnt -= _doprnt(fmt, &args, stdout);
! 		stdout->_cnt = old_cnt;
! 	} else
! 		_doprnt(fmt, &args, stdout);
  	return(ferror(stdout)? EOF: 0);
  }

----------------------------------------------
Keith Packard
tektronix!reed!keith