Whilst porting code between Linux and macOS I have come across two issues which make assumptions about how something works but in reality different implementations of libc handle them differently. In this post I’ll talk about recent issues I faced with fopen() and fclose() in codebases.

fopen()

In the code I was porting from Linux to macOS there was a section which opened a file in “a+” mode, read the contents and then as time went on appended more data to the file. Everything was going great, but in macOS the file wasn’t being read at all. The reason is undefined behaviour for the read pointer as outlined in the Linux man page for open:

POSIX is silent on what the
initial read position is when using this mode. For glibc, the
initial file position for reading is at the beginning of the
file, but for Android/BSD/MacOS, the initial file position for
reading is at the end of the file.

http://man7.org/linux/man-pages/man3/fopen.3.html

The Linux code was assuming that when the file was opened in “a+” mode the read pointer was always at the start of the file. But in macOS the read pointer is right at the end of the file when opened. Fixing this was as simple as a basic seek but it was difficult to find this behaviour difference documented outside of the Linux man pages.

fclose()

This issue comes from porting code the other way around, from macOS to Linux. The code in question did this:

fclose(fileptr);

Which is fine, but it was possible that this code could be executed when there is no file open and fileptr is NULL. In macOS this causes fclose() to just return with no operation executed. But in Linux fclose(NULL) will crash with a segmentation fault.

I couldn’t find any explicit documentation on this, but many instances of other people hitting this crash. POSIX doesn’t define what should happen if a NULL pointer is passed to fclose() so this becomes undefined behaviour. Again this was easy to fix with a simple ‘if’ statement.

The moral of the story

The key takeaway here is “undefined behaviour” may not always be explicitly defined in documentation. If there is no explicit documentation for a given input to or usage of a function it is likely that different operating systems will implement it in different ways.