Notes 3: Standard I/O Tricks

Corresponds to Chapter 5 in Advanced Programming in the Unix Environment.

Most of the time, you won't be dealing with the lower level Unix I/O calls--rather you'll use the normal ANSI library calls such as fopen(). Since you learn most of these in a normal C class, I'm only going to talk about the weird and useful ones. Keep in mind that these are not Unix-specific; they appear as normal ANSI <stdio.h> functions.

int setbuf( FILE *stream, char *buf);
int setvbuf( FILE *stream, char *buf, int mode , size_t size);

Normal standard library calls are buffered; that is, they hold on to the data they collect for some time before passing it on to write(). There are three levels of buffering: unbuffered, line-buffered, and fully buffered. By default, stdin and stdout are fully buffered, unless they refer to a terminal, in which case they are line buffered. stderr is unbuffered.

You can change the buffering of a stream with the setvbuf() call. (setbuf() is just a simple case of setvbuf() which you can use to set a stream to fully buffered or unbuffered.)

Some conditions: the setvbuf() call must occur before any actual I/O takes place. (So says Stevens--the Linux man page says that you can do it then, or immediately after you fflush() the stream.) Also, if you changing a stream to be buffered with setbuf(), the length of the buf parameter must be BUFSIZ bytes long (as defined in <stdio.h>).

With setvbuf(), buf points to the new buffer, which is size bytes in length. mode tells setvbuf() which type of buffering is to take place, and should be one of the following:

_IONBFSet stream to non-buffered
_IOLBFSet stream to line-buffered
_IOFBFSet stream to fully buffered
Table 1. Buffering mode choices.

To set stdout to unbuffered, the following calls will work:

    setvbuf(stdout, NULL, _IONBF, 0);  /* this */
    setbuf(stdout, NULL);              /* or this */

int ungetc(int c, FILE *stream);

Sometimes you read a character off a stream and you want to push it back onto the stream so you can read it again at a later time. Maybe you need the next character to give the current character context or something like that.

ungetc() will push the character c back into stream for a later read. ANSI specifies that at least one character will be pushed back, so sequential calls to ungetc() will not necessarily work. You can only be sure that one character can be pushed back successfully at a time.

int vprintf(const char *format, va_list ap);

This function works just like printf() except it takes a variable argument list type as the second parameter. This can be useful for constructing your own error reporting routines, as follows:

    #include <stdio.h>
    #include <stdarg.h>

    main()
    {
        void error_printf(char *s, ...);

        error_printf("Error %d occurred: %s\n", 29, "Bogosity Error");
    }

    void error_printf(char *s, ...)
    {
        va_list ap;

        va_start(ap, s);
        vfprintf(stderr, s, ap);
        va_end(ap);
    }

The above example uses vfprintf() which works basically like fprintf(). There is also an vsprintf() function at your disposal. To make the example more true-to-life, you could add a date stamp within the error_printf() function that would automatically tell the time of the error. Or, if you're parsing something, you could have it automatically print the line number, etc.

Temporary Files

There are a gazillion ways to make temp files (well, about five, actually). Remember that you can unlink() a temp file right after it's opened and this will prevent them from sticking around after a crash.

char *tmpnam(char *s);

This function generates a unique temp file name (but doesn't open it). If s is not NULL, it stores in the name in that string. Otherwise, it stores the name in an internal static string and returns that. This function uses the value P_tmpdir which is defined in <stdio.h> as a directory prefix for the temp file name.

FILE *tmpfile (void);

Works like tmpnam(), except that it actually fopen()s the file (mode "w+b") and returns a FILE* to you. It also unlink()s the file for you so it is automatically destroyed when it is fclose()d or the program terminates.

char *tempnam(const char *dir, const char *pfx);

Like tmpnam(), except that it gives you a little more control of the name of the temp file. Also like tmpnam(), this function only generates a filename; it doesn't open it.

The parameter dir should be set to the directory prefix you want to use for the temp file, or NULL if you want to use a predetermined directory (like /usr/tmp or /tmp). A unique filename is then generated using up to 5 characters of the string pfx.

As for the directory prefix that is actually used, well, the following choices are tried in this order (from the Linux man page):

  1. The directory specified in the TMPDIR environment variable. (Use unsetenv() to unset this value if you need to.)
  2. The directory specified in the dir argument, if it's not NULL.
  3. The directory specified by P_tmpdir.
  4. The directory /tmp.

Finally, note that this function uses malloc() to store space for the string. Use free() when you're done with the returned file name so you don't have any memory leaks.

char *mktemp(char *template);

This function generates a unique filename based on template, but doesn't open it. The last six characters of template must be "XXXXXX" and these are replaced by the function to make a unique filename, which is returned:

    char filename[300];
    sprintf(filename, "%s.gif", mktemp("pizza-XXXXXX"));

int mkstemp(char *template);

Works just like mktemp, except that it actually creates a file with 0666 permissions, opens it O_RDWR, and returns a file descriptor to it.