[comp.unix.wizards] Apparent bug in fclose

keith@fstohp.crd.ge.com (Keith D Gregory) (06/17/89)

In short, if one attempts to fclose() files, exec() another program,
and freopen() the same files, the freopen() appears to fail.  In the
specific case, we ran the following programs on an HP-9000 (HP-UX 6.2)
and a Mac-2 (A/UX 1.1 beta).  The second program failed on the second
call to fprintf() (the first completed), leaving beind a core dump.
The _iob for StdErr appeared to be in pretty bad shape (as I recall,
the character pointers were trashed).
 
As a guess, the startup code in P2 is initalizing its _iob array,
and the call to fclose() (as part of freopen()) is thinking that
the structure refers to a valid stream.

Is this what is happening?  Is there any reason (documented) that
we should not close a file before exec()ing a program?  The obvious
workaround (leave them open) is in place, but this application is
to run in the background (after a foreground launch), and I don't
particularly want any ill-behave programs to start sending data to
an unclosed StdFile.

Please mail any responses; you will need to explicitly use CRDGW1
as a gateway.

Thanks

   Keith D Gregory
   keith%fstohp@crdgw1.ge.com


# Program 1 Start, Cut here ###################################################
#include <stdio.h>

void main()
{
    fclose( stdin );
    fclose( stdout );
    fclose( stderr );

    execl( "p2", "p2", NULL );
}
# Program 2 Start, Cut here ###################################################
#include <stdio.h>

void main()
{
    if (freopen( "/dev/tty", "w", stderr) == NULL)
        exit( -1 );
    else
        fprintf( stderr, "Reopened StdErr, Flags = %d, FileNo = %d\n",
                         stderr->_flag, stderr->_file );

    fflush( stderr );

    if (freopen( "/etc/passwd", "r", stdin) == NULL)
        perror( "Unable to freopen StdIn" );
    else
        fprintf( stderr, "Reopened StdIn, Flags = %d, FileNo = %d\n",
                         stdin->_flag, stdin->_file );

    fflush( stderr );
    
    if (freopen( "/tmp/ix", "w", stdout) == NULL)
        perror( "Unable to freopen StdOut" );
    else
        fprintf( stderr, "Reopened StdOut, Flags = %d, FileNo = %d\n",
                         stdout->_flag, stdout->_file );

    fflush( stderr );
}
# Program 2 End, Cut here #####################################################

john@prcrs.UUCP (John C. Schettino Jr.) (06/19/89)

In article <20032@adm.BRL.MIL>, keith@fstohp.crd.ge.com (Keith D Gregory) writes:
> 
> In short, if one attempts to fclose() files, exec() another program,
> and freopen() the same files, the freopen() appears to fail.  In the
....
> #include <stdio.h>
> 
> void main()
> {
>     fclose( stdin );
>     fclose( stdout );
>     fclose( stderr );
> 
>     execl( "p2", "p2", NULL );
> }

When you exec a program, that program takes its stdin, stdout, and stderr from
the current executable image (this is howe the shell supports redirection.) So
in the example above, "p2" is started with stdin, stdout, and stderr closed,
ie there are no valid file pointers or file descriptors available.

> #include <stdio.h>
> 
> void main()
> {
>     if (freopen( "/dev/tty", "w", stderr) == NULL)
>         exit( -1 );
>     else
>         fprintf( stderr, "Reopened StdErr, Flags = %d, FileNo = %d\n",
>                          stderr->_flag, stderr->_file );
> 
>     fflush( stderr );
> 
>     < ADDITIONAL EXAMPLES REMOVED>
> }

Ok, so program p2 trys to freopen a closed file pointer. The first thing it
does is close a file descriptor which is NOT open. This is "not good", and
your behavior is undefined. Your "work around" of leaving the files open
is actually correct.

Remember that an exec() call starts up an executable which takes many
things from the current executable:
	- open file descriptors (without the close-on-exec flag set, see fcntl)
	- COPY of the environment
	- file pointers for stdin, stdout, stderr (and in PC-DOS, stdprn)
	- current userid, groupid, etc.

Hope this helps...
---------------------
John Schettino : uunet!prcrs!john
[no snappy saying or disclaimer]

maart@cs.vu.nl (Maarten Litmaath) (07/22/89)

keith@fstohp.crd.ge.com (Keith D Gregory) writes:
\...
\As a guess, the startup code in P2 is initalizing its _iob array,
\and the call to fclose() (as part of freopen()) is thinking that
\the structure refers to a valid stream.
\
\Is this what is happening?

Yes. See below.

\Is there any reason (documented) that
\we should not close a file before exec()ing a program?

Yes. See below.

\...
\# Program 1 Start, Cut here ###################################################
\#include <stdio.h>
\
\void main()
\{
\    fclose( stdin );
\    fclose( stdout );
\    fclose( stderr );
\
\    execl( "p2", "p2", NULL );
\}

Now file descriptors 0, 1 and 2 are all unused! Read on.

\# Program 2 Start, Cut here ###################################################
\#include <stdio.h>
\
\void main()
\{
\    if (freopen( "/dev/tty", "w", stderr) == NULL)

Which file descriptor will freopen() get for stderr?
Right: number 0 (== the first unused).

\        exit( -1 );
\    else
\        fprintf( stderr, "Reopened StdErr, Flags = %d, FileNo = %d\n",
\                         stderr->_flag, stderr->_file );
\
\    fflush( stderr );
\
\    if (freopen( "/etc/passwd", "r", stdin) == NULL)
\...

Guess what stdin->_file is initialized too?
Right! ZERO.
Guess what the freopen() does first?
Right: a close(stdin->_file).
Guess whose file descriptor we've just managed to destroy?
Right again! stderr->_file is now closed too... :-(
It will be opened again READONLY, due to the "r" argument of freopen().
Hence perror() etc. fail.

One might consider the static initialization of stdin/stdout/stderr a bad
feature in the stdio implementation.
On the other hand, you do some pretty strange things, so you could
expect strange results.
One should provide every program will valid file descriptors 0, 1 and 2.
If you don't want the program to do anything with them, just connect them
to /dev/null.
If the program isn't interested in the pre-connected files, it could close()
the descriptors itself, e.g. implicitly by freopen().