Handling binary files via standard I/O

In a portable C program, text files are opened using one of the modes "r", "w", "a", etc., as in the example:
    FILE *ftext = fopen(fname, "r");
On the other hand, binary files are opened using one of the modes "rb", "wb", "ab", etc.
    FILE *fbin = fopen(fname, "rb");
On non-Unix systems, like DOS, OS/2 and Windows, text files are treated differently than binary files, and the use of the 'b' code letter is essential. Unfortunately, Standard C provides no way to change the mode on standard I/O (stdin, stdout, stderr). This is necessary when handling binary files in a pipeline; e.g.
    echo Hello | gzip -c > hello.gz

Solution 1

There is no standard solution, but there are portable solutions. It is possible to detect some features of the compiler and/or operating system, by checking predefined compiler macros. Here is a quick and dirty solution that works on Windows32 (9X, NT, 2K, XP, ...)
    #define STDIN  0
    #define STDOUT 1
    #define STDERR 2
    #ifdef _WIN32
    # include <io.h>
    # include <fcntl.h>
    # define SET_BINARY_MODE(handle) setmode(handle, O_BINARY)
    #else
    # define SET_BINARY_MODE(handle) ((void)0)
    #endif
Before performing any other operation on the stream that is supposed to handle binary data, invoke the SET_BINARY_MODE macro. For example:
    /* Prepare the standard output for writing binary data. */
    /* This must be done before writing anything to stdout. */
    SET_BINARY_MODE(STDOUT);
The vanilla definition of SET_BINARY_MODE allows the code to be successfully compiled and executed on Unix. However, the problem remains on other (non-Windows && non-Unix) systems.

Solution 2

For a more portable solution, other operating systems must also be considered. The "sysconf.h" header file provides the information necessary for the definition of SET_BINARY_MODE.
    #include "sysconf.h"
    #ifdef HAVE_SETMODE
    # define SET_BINARY_MODE(handle) setmode(handle, O_BINARY)
    #else
    # define SET_BINARY_MODE(handle) ((void)0)
    #endif
Download: sysconf.h

Extras

For advanced C programmers

If you are using "sysconf.h", you can also rely on HAVE_ISATTY to detect the presence of isatty(). Writing binary data to non-redirected output results in displaying meaningless symbols on the screen; this happens frequently in older Unix programs, and it is highly unelegant from the user's point of view. Reading any kind of data from non-redirected input results in the program waiting for the user to type the input, although the user doesn't know what to do, except to press Ctrl+C.

isatty() is a function that returns TRUE if the handle is associated with a TTY (e.g. keyboard, screen, printer, serial port), and FALSE in the other circumstances (e.g. the handle is associated with a regular file, or is a part of a pipeline). For example, if the standard output is redirected to a file, or piped to another program, then isatty(STDOUT) returns FALSE.

If a command-line program is waiting for data from the standard input, and the user is not supposed to type it, then it would be nice to check for isatty() first:

    #ifdef HAVE_ISATTY
        if (isatty(STDIN))
        {
            fprintf(stderr, "** Error: missing input\n");
            fprintf(stderr, help_message);
            exit(EXIT_FAILURE);
        }
    #endif
Similarly, if the program is supposed to write binary data to the standard output:
    #ifdef HAVE_ISATTY
        if (isatty(STDOUT))
        {
            fprintf(stderr, "** Error: can't write binary data\n");
            fprintf(stderr, help_message);
            exit(EXIT_FAILURE);
        }
    #endif

Note: isatty() needs not be checked more than once.


Cosmin Truta
Last updated: 14 March 2004