Making puts fail

Robert C. Seacord reminds us in “Effective C” that puts can fail:

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    puts("Hello, world!");
    return EXIT_SUCCESS;
}

Although it’s unlikely that the puts function will fail and return EOF for our simple program, it’s possible. Because the call to puts can fail and return EOF, it means that your first C program has a bug.

If we want to be strict about it, he suggests we check for EOF (a macro defined as a negative integer):

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    if (puts("Hello, world!") == EOF) {
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

But how could puts (or indeed printf) fail? Can we force a write error to see it ourselves?

Redirect output to a full or nearly full disk

Let’s make a 1MB disk image, mount it and check available space:

> hdiutil create -size 1M -fs HFS+ -volname SmallDisk ~/Desktop/SmallDisk.dmg
> hdiutil attach ~/Desktop/SmallDisk.dmg
> df -h /Volumes/SmallDisk
Filesystem      Size    Used   Avail Capacity iused ifree %iused  Mounted on
/dev/disk4s1   984Ki   144Ki   840Ki    15%       5  4.3G    0%   /Volumes/SmallDisk

840 Kibibytes (KiB) or 860,160 bytes are left on our disk after overhead.

We’ll amend our C hello world to output something larger than that, say, a million “a” characters:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void) {
    char *str = malloc(1000001);
    if (str == NULL) {
        fprintf(stderr, "Memory allocation failed\n");
        return EXIT_FAILURE;
    }

    memset(str, 'a', 1000000);
    str[1000000] = '\0';

    if (puts(str) == EOF) {
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

Then redirect output from stdout to our little disk at /Volumes/SmallDisk:

> ./hello > /Volumes/SmallDisk/file.txt

No output. Check the exit code:

> echo $?
1

We made puts fail! What about our file contents?

> cat /Volumes/SmallDisk/file.txt

>

Empty! puts returned EOF before buffer contents flushed to disk. (With printf, we might expect file.txt to include some of our “a“s but not all of them.)

Why does this matter?

It usually doesn’t. We often skip the check and accept the risk of failure.

It matters more when you’re depending on the exit code. The same code above without the EOF check will always return a 0 exit code, even when puts fails to write due to disk, permissions, or hardware issues.