Re: Closing the FILE object.

From: Khimenko Victor (khim@sch57.msk.ru)
Date: Fri Jul 14 2000 - 14:14:00 EST


In <Pine.LNX.3.95.1000714124433.1379A-100000@chaos.analogic.com> Richard B. Johnson (root@chaos.analogic.com) wrote:

> This is in reference to the reported seg-faults when attempting to
> fclose() an invalid file pointer.

Yes. It's just design rule: "crash early whenever something is wrong since all
workarounds never have a 100% chance to survive".

> The following shows that my current 'C' runtime library does not
> adhere to any known standard when referencing the FILE object during
> fclose(). This is gcc 2.7.2.3 (libc 5.3.12)

Hmm. "Do not adhere to any known standard" and "do not adhere to documentation"
are different things. WHICH standard is violated by this behaviour ?

BTW libc 5.x is obsoleted and should not be used anyway.

> According to existing Linux documentation, this should return
> EBADF. It should NOT seg-fault.

No. There are NO reference to EBADF in existing GLibC documentation:
-- cut --
[khim@localhost khim]$ info --apropos=fclose
"(libc)Closing Streams" -- fclose
"(libc)Closing Streams" -- fcloseall
[khim@localhost khim]$ info "(libc)Closing Streams" -o-
File: libc.info, Node: Closing Streams, Next: Simple Output, Prev: Opening Streams, Up: I/O on Streams

Closing Streams
===============

   When a stream is closed with `fclose', the connection between the
stream and the file is cancelled. After you have closed a stream, you
cannot perform any additional operations on it.

 - Function: int fclose (FILE *STREAM)
     This function causes STREAM to be closed and the connection to the
     corresponding file to be broken. Any buffered output is written
     and any buffered input is discarded. The `fclose' function returns
     a value of `0' if the file was closed successfully, and `EOF' if
     an error was detected.

     It is important to check for errors when you call `fclose' to close
     an output stream, because real, everyday errors can be detected at
     this time. For example, when `fclose' writes the remaining
     buffered output, it might get an error because the disk is full.
     Even if you know the buffer is empty, errors can still occur when
     closing a file if you are using NFS.

     The function `fclose' is declared in `stdio.h'.

   To close all streams currently available the GNU C Library provides
another function.

 - Function: int fcloseall (void)
     This function causes all open streams of the process to be closed
     and the connection to corresponding files to be broken. All
     buffered data is written and any buffered input is discarded. The
     `fcloseall' function returns a value of `0' if all the files were
     closed successfully, and `EOF' if an error was detected.

     This function should be used only in special situation, e.g., when
     an error occurred and the program must be aborted. Normally each
     single stream should be closed separately so that problems with
     one stream can be identified. It is also problematic since the
     standard streams (*note Standard Streams::.) will also be closed.

     The function `fcloseall' is declared in `stdio.h'.

   If the `main' function to your program returns, or if you call the
`exit' function (*note Normal Termination::.), all open streams are
automatically closed properly. If your program terminates in any other
manner, such as by calling the `abort' function (*note Aborting a
Program::.) or from a fatal signal (*note Signal Handling::.), open
streams might not be closed properly. Buffered output might not be
flushed and files may be incomplete. For more information on buffering
of streams, see *Note Stream Buffering::.

info: Done.
[khim@localhost khim]$
-- cut --
What's written in man page is not relevant to GLibC - you can as well try to
use manual for BSD, HP-UX or even MS VC++.

> #include <stdio.h>
> #include <malloc.h>

> int main ()
> {
> FILE *fp;
> fp = (FILE *)malloc(sizeof(FILE));/* Make sure the pointer is valid */
> fprintf(stderr, "%p\n", fp); /* Display valid address */
> fclose(fp);
> puts("It worked??"); /* Will not occur */
> return 0;
> }

You gave it not corectly created file stream but some garbage. SIGSEGV is MOST
APPROPRIATE thing to do in such case. It's seriuos bug in your application and
it must be treated as such. Any other approach is just invitation for subtle
bugs.

> I reported a similar problem several years ago when I way trying to
> substitute during runtime, a FILE pointer obtained from a fopen() and
> the default FILE pointer(s) of stdin, stdout, stderr. In other words,
> if the program didn't have a terminal, stdout and stderr would go to
> a file.

> Because the FILE *object didn't work as a real pointer, I could not
> do:
> FILE *fp;

> fp = fopen("filename", "w");
> fflush(stdout);
> fflush(stderr);
> stdout = fp;
> stderr = fp;

You could:
-- cut --
[khim@localhost khim]$ info --node="(libc)Standard Streams" -o-
File: libc.info, Node: Standard Streams, Next: Opening Streams, Prev: Streams, Up: I/O on Streams

Standard Streams
================

   When the `main' function of your program is invoked, it already has
three predefined streams open and available for use. These represent
the "standard" input and output channels that have been established for
the process.

   These streams are declared in the header file `stdio.h'.

 - Variable: FILE * stdin
     The "standard input" stream, which is the normal source of input
     for the program.

 - Variable: FILE * stdout
     The "standard output" stream, which is used for normal output from
     the program.

 - Variable: FILE * stderr
     The "standard error" stream, which is used for error messages and
     diagnostics issued by the program.

   In the GNU system, you can specify what files or processes
correspond to these streams using the pipe and redirection facilities
provided by the shell. (The primitives shells use to implement these
facilities are described in *Note File System Interface::.) Most other
operating systems provide similar mechanisms, but the details of how to
use them can vary.

   In the GNU C library, `stdin', `stdout', and `stderr' are normal
variables which you can set just like any others. For example, to
redirect the standard output to a file, you could do:

     fclose (stdout);
     stdout = fopen ("standard-output-file", "w");

   Note however, that in other systems `stdin', `stdout', and `stderr'
are macros that you cannot assign to in the normal way. But you can
use `freopen' to get the effect of closing one and reopening it. *Note
Opening Streams::.

info: Done.
[khim@localhost khim]$
-- cut --

> The next write to stdout would segfault. This behavior was never seen
> on any other Unix system, including Ultrix and Sun(s).

And still it's not supported by ANY standard. You can do this with GLibC
but it's non-portable extension - use freopen instead.

> In fact, the interplay of the FILE object was a feature of Unix 'C'
> programming.

It was result of first implementation. It NEVER been documented as "safe to
use API".

> In the example above, I could have saved the original FILE *, and
> restored it later in the program. With Linux, you can't even compile
> such a program because the assignments will result in 'invalid lvalue'
> errors. This is because Linux libc's stdout is not a FILE * at all!

Linux's GLibC's stdout IS FILE *.
-- /usr/include/stdio.h --
[skipped]
/* Standard streams. */
extern FILE *stdin; /* Standard input stream. */
extern FILE *stdout; /* Standard output stream. */
extern FILE *stderr; /* Standard error output stream. */
/* C89/C99 say they're macros. Make them happy. */
#define stdin stdin
#define stdout stdout
#define stderr stderr
[skipped]
-- cut --

> This breaks compatability rules because, for instance fprintf() takes,
> as it's first parameter, a pointer of type FILE.

Yes. And MACROS (NOT VARIBLE!!!!) should give you such a pointer suitable
to use as fprintf's first parameter.

> So we have the case of fprintf(stderr, ...) compiling and working because
> stderr is a FILE pointer, but I can't make a legal assignment to it like
> any other FILE pointers.

Yes. Just like ANSI C standard says. GLibC extended standard and you CAN
assign to stdin, stdout and stderr. But it NEVER was portable and never
will (Ok, since it's documented it'll be portable between Linux and Hurd - not
a big portability level to me).

P.S. Anyway: this discussion is NOT relevant to linux at all. If you want to
talk about GLibC desing - go to glibc's mailing list. If you want to use
libc 5.x you lose - no cure for this.

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.rutgers.edu
Please read the FAQ at http://www.tux.org/lkml/



This archive was generated by hypermail 2b29 : Sat Jul 15 2000 - 21:00:20 EST