[comp.lang.c] closing stdout

ok@quintus.UUCP (Richard A. O'Keefe) (12/06/87)

In one of my messages, I said that I had learned that I ought to
fclose(stdout) or fflush(stdout) in order to check that the output
had in fact gone out as intended.

Roberto Shironoshita at the University of Pennsylvania
just sent me e-mail pointing out that fclose() generally tries to free()
things, and this might cause trouble for stdout.  As far as I know,
fclose() is smart enough to tell whether the stream you are closing was
one of its initial pool of statically allocated ones.  fclose(stdout)
and fclose(stdin) certainly work perfectly well on a SUN.
freopen(stdin,...) and freopen(stdout,...) are DEFINITELY supposed to work.

He's right nevertheless:  it's probably not a good idea to try to close
any of the preconnected streams, and fflush(stdout) is all the check I need.

rsalz@bbn.com (Rich Salz) (12/06/87)

Richard A. O'Keefe is concerned about being able to fclose(stdout)
and friends, apparently based on mail from Roberto Shironoshita at the
University of Pennsylvania who warns that "fclose() generally tries to free()
things, and this might cause trouble for stdout. ..."

NONSENSE!

If there are no bugs in your program (e.g., corrupted malloc arena, so
that a free would fail), then it is *ALWAYS* safe to fclose() anything
that has been fopen'd for you implicitly, or by you explicitly.  This
include stdin, stdout, and stderr.  If this is not the case, then you
have a severely broken C library, and should be on the phone with your
vendor right away.

First make sure it's not your bug.

If you want to ensure that the data you have fprintf'd or such has been
forced out of any STDIO buffers (and is put out into the appropriate
Unix kernel buffers), then fflush() is all you need.  Doing fclose is
wrong if you have additional output, because:
	fclose(stdout);
	printf("I wonder what will happen?\n");
is guaranteed to be an error, and may in fact cause your program to crash.
-- 
For comp.sources.unix stuff, mail to sources@uunet.uu.net.

ok@quintus.UUCP (Richard A. O'Keefe) (12/07/87)

I thought my message made it clear that I expected fclose(stdout) to work,
but apparently not.  Rich $alz hastened to reassure the world that any
thought that it might not is "NONSENSE!" and that if it does give trouble,
you have a severely broken C library.

There are some interesting points about this.  One is the wording in the
October '86 ANSI C Draft:  "Due to weak implementations of the standard
I/O library ...".  There are other points like "On some operating systems
it is difficult, or impossible, to create a file unless something is
written to the file.  A maximally portable program which relies on a
file being created must write something to the associated stream before
closing it."  (So yes, there could be a problem with fclose() when it
DIDN'T write anything...)

I think the main point is that I can find nothing in the ANSI draft nor
in any of the C manuals I have checked which explicitly says that you
can close the standard streams.  True, there is nothing that says you
can't, but this is the kind of thing that is overlooked.

There's an old joke with the punch-line "We've already established what
you are, madam.  Now we're just haggling over the price."  I'm afraid
stdio is like that.  Here's my favourite example:
	cat <<'EOF' >foo.c
	#include <stdio.h>
	main()
	    {
		extern int errno;
		int result;
		result = getchar();
		errno = 0;
		result = putc(result, stdin);
		printf("result = %d, errno = %d\n", result, errno);
	    }
	EOF
	cc -o foo foo.c
	./foo <<'EOF'
	X12356
	EOF
On every UNIX system where I've tried this (V7, 4.2, V.2, V.3) the
output has been
	result = 88, errno = 0
Now putc() is supposed to return EOF (-1) on error.
Writing to an INPUT stream isn't an error? 
The bug is that depending on where you are in the buffer, putc() MIGHT
notice the mistake, but it usually won't.  With this sort of family
history, a wee bit of suspicion about fclose() is not unwarranted.

There's no point in me getting on the phone to AT&T or SUN about this;
the bug is a pretty fundamental one in the UNIX stdio implementation,
and they'd have to rewrite most of the macros as well as _filbuf and
_flsbuf (no I haven't got sources, but I was able to predict the bug
from looking at /usr/include/stdio.h, and it has never gone away).

chris@mimsy.UUCP (12/07/87)

In article <442@cresswell.quintus.UUCP> ok@quintus.UUCP (Richard
A. O'Keefe) writes:
>... putc() is supposed to return EOF (-1) on error.
>Writing to an INPUT stream isn't an error? 

Unfortunately, putc is supposed to be an efficient macro, and
testing for write permission there slows it down.

In fact, 4.3BSD stdio will allow this:

	FILE *fp = fopen("foo", "r");
	...
	fprintf(fp, ...) ... fputs(..., fp) ... /* etc */
	if (fflush(fp) || ferror(fp) || fclose(fp))
		fprintf(stderr, "something went wrong\n");

Everything works (with all the appropriate text appearing in file
`foo') IF AND ONLY IF FILE foo DID NOT EXIST BEFORE THE fopen!  My
version of 4.3BSD's stdio catches this sort of thing in all function
calls.

My stdio might be in 4.4. . . .
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7690)
Domain:	chris@mimsy.umd.edu	Path:	uunet!mimsy!chris

LEYON_C%DICKINSN.BITNET@CUNYVM.CUNY.EDU (12/07/87)

In _The_Unix_Programming_Environment_ (Kernighan and Pike), they say
explicitly on p. 224 that

        "/dev/tty is opened with mode 2 -- read and write -- and
         then dup'ed to form the standard input and output.  This
         is actually how the system assembles the standard input,
         output and error when you log in.  Therefore, your standard
         input is writable"

-- Chris Leyon
   LEYON_C@DICKINSN.BITNET

karl@haddock.UUCP (12/07/87)

In article <442@cresswell.quintus.UUCP> ok@quintus.UUCP (Richard A. O'Keefe)
writes:
>[I expected fclose(stdout) to work; Rich $alz agrees that it must.  However,
>the dpANS warns that] "A maximally portable program which relies on a file
>being created must write something to the associated stream before closing
>it."  (So yes, there could be a problem with fclose() when it DIDN'T write
>anything...)

The only "problem" with that situation is that you might get a nonexistent
file rather than an empty one.  It will still close the stream and return 0 on
success.

>I think the main point is that I can find nothing in the ANSI draft nor
>in any of the C manuals I have checked which explicitly says that you
>can close the standard streams.

Surely, in the absence of a statement to the contrary, the standard streams
must be assumed to have all the properties of explicitly opened streams.

>[Example showing how   putc(c, stdin)   does not always fail; the conclusion
>is that this is a bug in the UNIX stdio implementation.]

According to the Oct86 dpANS, "putc ... is equivalent to fputc", "fputc ...
writes ... to the output stream pointed to by STREAM".  Since stdin is not an
output stream, you have passed an inappropriate parameter (although the type
is correct).  Library functions are not required to domain-check their
arguments, therefore this is a user bug, not a library bug.  (Cf. passing a
NULL pointer to strcpy.)

Karl W. Z. Heuer (ima!haddock!karl or karl@haddock.isc.com), The Walking Lint

karl@haddock.ISC.COM (Karl Heuer) (12/08/87)

In article <10699@brl-adm.ARPA> LEYON_C%DICKINSN.BITNET@CUNYVM.CUNY.EDU (Leyon, Chris) writes:
>[K&P says that] "/dev/tty is opened with mode 2 -- read and write -- and then
>dup'ed to form the standard input and output.  This is actually how the
>system assembles the standard input, output and error when you log in.
>Therefore, your standard input is writable"

In this context, "standard input" means file descriptor 0, rather than the
stdio stream named "stdin".  The latter is *not* writable.  (For some reason,
stderr is often a RW stream, though.)

Moreover, K&P are talking about the *initial* standard input (your terminal).
The alleged bug occurs even if it has been redirected to a file.

Karl W. Z. Heuer (ima!haddock!karl or karl@haddock.isc.com), The Walking Lint

am@cl.cam.ac.uk (Alan Mycroft) (12/09/87)

In article <442@cresswell.quintus.UUCP> ok@quintus.UUCP (Richard A. O'Keefe) writes:
>There's an old joke with the punch-line "We've already established what
>you are, madam.  Now we're just haggling over the price."
>		result = getchar();
>		errno = 0;
>		result = putc(result, stdin);
>		printf("result = %d, errno = %d\n", result, errno);
>The bug is that depending on where you are in the buffer, putc() MIGHT
>notice the mistake, but it usually won't.
>... the bug is a pretty fundamental one in the UNIX stdio implementation,
Richard, The bug is not in the slightest bit fundamental and could be fixed
in less than 1 day once and for all.  I have done it for a ANSI unix-like I/O
library:
Merely separate the _cnt field
of struct FILE into a _icnt and an _ocnt, change getc/putc to use _icnt/_ocnt.
Fix _filbuf/_flsbuf to use the right one, and to whinge when _icnt/_ocnt
goes -ve when you expect the other one to.
This for free also enables the library to police the "fflush/fseek between
change of direction for I/O" restriction and avoids chaos there.

michael@stb.UUCP (Michael) (12/13/87)

In article <9658@mimsy.UUCP> chris@mimsy.UUCP (Chris Torek) writes:
>In fact, 4.3BSD stdio will allow this:
>
>	FILE *fp = fopen("foo", "r");
>	...
>	fprintf(fp, ...) ... fputs(..., fp) ... /* etc */
>	if (fflush(fp) || ferror(fp) || fclose(fp))
>		fprintf(stderr, "something went wrong\n");
>
>Everything works (with all the appropriate text appearing in file
>`foo') IF AND ONLY IF FILE foo DID NOT EXIST BEFORE THE fopen!  My

The fopen call is trying to open for reading an existing file. If the
file did not exist, it should FAIL, not just creat() it.
-- 
: Michael Gersten		ihnp4!hermix!ucla-an!remsit!stb!michael
:				sdcrdcf!trwrb!scgvaxd!stb!michael
: "Copy Protection? Just say 'Off site backup'. "

chris@mimsy.UUCP (Chris Torek) (12/14/87)

In article <9658@mimsy.UUCP> I claimed:
>In fact, 4.3BSD stdio will allow this:
>
>	FILE *fp = fopen("foo", "r");
	[write, successfully, to file fp]
>	if (fflush(fp) || ferror(fp) || fclose(fp))
>		fprintf(stderr, "something went wrong\n");
>
>Everything works ... iff file foo did not exist before the fopen!

Oops, this was exactly backwards: everything works iff file foo
*did* exist before the fopen.

Thanks to michael@stb.UUCP for noticing this one.
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7690)
Domain:	chris@mimsy.umd.edu	Path:	uunet!mimsy!chris

meissner@xyzzy.UUCP (Michael Meissner) (12/17/87)

In article <1115@jenny.cl.cam.ac.uk> am@cl.cam.ac.uk (Alan Mycroft) writes:
| Merely separate the _cnt field
| of struct FILE into a _icnt and an _ocnt, change getc/putc to use _icnt/_ocnt.
| Fix _filbuf/_flsbuf to use the right one, and to whinge when _icnt/_ocnt
| goes -ve when you expect the other one to.
| This for free also enables the library to police the "fflush/fseek between
| change of direction for I/O" restriction and avoids chaos there.

While this does solve the problem, I have found that there quite a few
bad mannered programs that go rifling through things like _cnt, _ptr, _base,
and _flag within the "standard" UNIX FILE stream.  Sigh.......
-- 
Michael Meissner, Data General.		Uucp: ...!mcnc!rti!xyzzy!meissner
					Arpa/Csnet:  meissner@dg-rtp.DG.COM