Question

Following program:

#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>

int main() {
  fclose( stderr );
  printf( "%d\n", fileno( stderr ) );
  return 0;
}

shows -1 on ubuntu 11.04 and 2 on ICS 4.0.3 emulator. Can't find any info about this issue - can i make this code work similar on both platforms? freopen on stderr has same problem.

Update:

Previous small program demonstrates the cause of actual problem i faced with: if i try to freopen stderr to file in inexistent directory, on linux stderr is closed but on android it stays opened! And even more - if i write smth in this opened stderr file and then do fopen on some other file, text i printed to stderr is written to this opened file.

So, this program:

#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>

# define LOGD( ... ) printf( __VA_ARGS__ ); printf( "\n" )

# ifdef ANDROID
#   define HOMEDIR "/data/data/com.myapp/" // for android
# else
#   define HOMEDIR "/home/darkmist/" // for linux
# endif

# define _T( x ) x

void TestFreopen_mkdir() {
  int mkdirres = mkdir( HOMEDIR "1.d", 0777 );
  LOGD(_T("TestFreopen mkdirres=0x%08x"),mkdirres);
}

void TestFreopen() {
  LOGD(_T("TestFreopen begin"));

  LOGD(_T("TestFreopen stderr=0x%08x"),fileno(stderr));
  fprintf(stderr,"fprintf_1 to stderr\n");

  // TestFreopen_mkdir(); // case 1

  if ( NULL == freopen( HOMEDIR "1.d/1", "w", stderr ) ) {
    LOGD( "freopen failed" );
    if ( -1 != fileno( stderr ) ) {
      fclose( stderr );
      LOGD( "freopen closed" );
    }
  }

  LOGD(_T("TestFreopen stderr=0x%08x"),fileno(stderr));
  fprintf(stderr,"fprintf_2 to stderr\n");

  TestFreopen_mkdir(); // case 2

  FILE* fopen_file = fopen( HOMEDIR "1.d/2", _T( "wb" ) );

  LOGD(_T("TestFreopen fopen_file=0x%08x"),fileno(fopen_file)); // same as for reopened stderr!!

  fprintf(stderr,"fprintf_3 to stderr\n");
  fprintf(fopen_file,"fprintf_1 to fopen_file\n");
  fflush(fopen_file);

  LOGD(_T("TestFreopen end"));
}

int main() {
  TestFreopen();
  return 0;
}

shows this on linux:

$ ./a.out
TestFreopen begin
TestFreopen stderr=0x00000002
fprintf_1 to stderr
freopen failed
TestFreopen stderr=0xffffffff
TestFreopen mkdirres=0x00000000
TestFreopen fopen_file=0x00000002
TestFreopen end

$ cat ~/1.d/2 
fprintf_1 to fopen_file

and this on android:

$ adb push ./a.out /data/data/com.myapp
573 KB/s (34635 bytes in 0.058s)

$ adb shell run-as com.myapp /data/data/com.myapp/a.out
TestFreopen begin
TestFreopen stderr=0x00000002
fprintf_1 to stderr
freopen failed
freopen closed
TestFreopen stderr=0x00000002
TestFreopen mkdirres=0x00000000
TestFreopen fopen_file=0x00000002
TestFreopen end

$ adb shell run-as com.myapp cat /data/data/com.myapp/1.d/2
fprintf_3 to stderr
fprintf_1 to fopen_file
Was it helpful?

Solution

It doesn’t make any sense to try to use stderr after closing it, on any platform.

OTHER TIPS

It seems to me that you are expecting what is formally declared to be undefined behavior of a file pointer subsequent to its closing to be the same on devices which have very different C libraries.

Once you call freopen() in a way that is designed to fail, future attempts to use that file pointer are not something you can depend on having a consistent outcome.

You have found some interesting results whereby left over pieces can still be used with unexpected outcome, but the problem is not that they don't cause some sort of exception, the problem is that you are attempting to use an invalid file pointer in a language that does not advertise itself as having safeguards.

The problem is not with the system, but rather with your program's misuse of the system.

fclose() is a function provided by libc, so different implementations of libc may have different behaviour as the state of the file descriptor after fclose() is not determined

Ubuntu uses eglibc, while android uses bionic as standard libc.


eglibc

Looking at eglibc 2.15 source code for fclose() we have:

from iofclose.c :

...
_IO_acquire_lock (fp);
if (fp->_IO_file_flags & _IO_IS_FILEBUF)
    status = INTUSE(_IO_file_close_it) (fp);
else
    status = fp->_flags & _IO_ERR_SEEN ? -1 : 0;
...

You are closing strerr, the first statement will be executed. Looking at the following function we have:

from fileops.c:

fp->_flags = _IO_MAGIC|CLOSED_FILEBUF_FLAGS;
fp->_fileno = -1;
fp->_offset = _IO_pos_BAD;

return close_status ? close_status : write_status;

As you can see, eglibc will explicitly set fileno to -1.


bionic

Bionic handles files in its own way, starting at the fileno(), which returns the fp->_file:

from stdio.h

#define __sfileno(p)    ((p)->_file)

from fileno.c

int
fileno(FILE *fp)
{
        int ret;

    FLOCKFILE(fp);
        ret = __sfileno(fp);
        FUNLOCKFILE(fp);
        return (ret);
}

Looking at bionic source code for fclose() we have:

from fclose.c

int
fclose(FILE *fp)
{
        int r;

        if (fp->_flags == 0) {  /* not open! */
                errno = EBADF;
                return (EOF);
        }
        FLOCKFILE(fp);
        WCIO_FREE(fp);
        r = fp->_flags & __SWR ? __sflush(fp) : 0;
        if (fp->_close != NULL && (*fp->_close)(fp->_cookie) < 0)
                r = EOF;
        if (fp->_flags & __SMBF)
                free((char *)fp->_bf._base);
        if (HASUB(fp))
                FREEUB(fp);
        if (HASLB(fp))
                FREELB(fp);
        fp->_r = fp->_w = 0;    /* Mess up if reaccessed. */
        fp->_flags = 0;         /* Release this FILE for reuse. */
        FUNLOCKFILE(fp);
        return (r);
}

from wcio.h

#define WCIO_FREE(fp) ((void)(0))

from local.h

#define HASUB(fp) (_UB(fp)._base != NULL)
#define FREEUB(fp) { \
        if (_UB(fp)._base != (fp)->_ubuf) \
                free(_UB(fp)._base); \
        _UB(fp)._base = NULL; \
}

As you can see, fp->_file is left untouched during the fclose() process. This is not actually a bug since the state of the file descriptor after fclose() is not determined, but this should clarify the difference between executing your program in Ubuntu and in Android.

To make your code portable, you should avoid closing stderr or reopen it to something like /dev/null after closing it.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top