fclose works differently on android and linux
Asked Answered
K

3

5

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
Krak answered 30/5, 2012 at 13:1 Comment(2)
What is the return code of flcose in each instance?Afterdinner
for linux: fclose returns 0, errno == 9 ( EBADF? ), fileno == -1. for android: fclose returns 0, errno == 0, fileno == 2.Krak
S
4

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

Subtlety answered 31/5, 2012 at 18:40 Comment(3)
Sure, this test is rather synthetic, but it 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.Krak
The answer remains the same. Once you have closed a stream you cannot use the pointer for any purpose. C99 s7.19.3 para 4 makes this clear: the pointer becomes indeterminate after closing the stream.Subtlety
Aha! That's what i need - reference to standard. Thank you, Richard.Krak
S
9

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.

Sparkie answered 13/6, 2012 at 16:38 Comment(0)
S
4

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

Subtlety answered 31/5, 2012 at 18:40 Comment(3)
Sure, this test is rather synthetic, but it 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.Krak
The answer remains the same. Once you have closed a stream you cannot use the pointer for any purpose. C99 s7.19.3 para 4 makes this clear: the pointer becomes indeterminate after closing the stream.Subtlety
Aha! That's what i need - reference to standard. Thank you, Richard.Krak
P
3

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.

Parable answered 18/6, 2012 at 20:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.