C / FILES AND ERRORS
errno, perror and reporting failures usefully
Turn a failed library call into a message that names the operation, the file and the reason, using errno, perror and strerror without losing the real error.
What you will learn
- Read errno only after the return value has already proved the call failed.
- Copy errno into a local int as the first statement of the failure branch.
- Use perror for a one-word prefix, strerror(err) inside a message of your own.
- Report operation, object and reason, and exit nonzero so callers can react.
Understanding errno, perror and reporting failures usefully
Every C library function that can fail says so through its return value: fopen returns NULL, fgetc returns EOF, fseek returns -1. errno, declared in <errno.h>, is a separate int lvalue that such a function writes a reason code into when it fails. The two are not interchangeable. A function must set errno when it fails, but nothing requires it to clear errno when it succeeds, and it is even permitted to set errno while succeeding, so the value only means anything once the return value has told you the call failed.
perror is the short path: perror("fopen") writes your prefix, a colon and a space, the text for the current errno, and a newline to stderr. strerror(err) hands you that same text as a string, which is what you want the moment you have more to say than one word, such as the path that could not be opened. Both texts are chosen by the implementation, so they are for a human to read and not for your code to parse. Both go to stderr, which is unbuffered and unaffected by redirecting stdout, so the diagnostic still reaches the screen when the program's real output is piped into a file.
Because errno is a single location per thread, anything you call after the failing function can overwrite it: a cleanup fclose, an fprintf, even a call that succeeds. The habit that avoids this is to copy errno into a local int on the first line of the failure branch and build the message from the copy. What makes the message useful is what it lets the reader do next, so name the operation that failed, the object it was acting on (the path, the line, the key), and the reason from errno, then return a nonzero status so whoever ran the program knows it did not do its job.
<stdio.h>
<string.h>
<errno.h>
int main(void)
{
const char *path = "/no/such/file.txt";
FILE *f = fopen(path, "r");
if (f == NULL) {
int err = errno; /* save it before any other call runs */
perror("fopen"); /* prefix, then the text for errno, on stderr */
fprintf(stderr, "cannot read %s: %s (errno %d)\n",
path, strerror(err), err);
return 1;
}
printf("opened %s\n", path);
fclose(f);
return 0;
}
errno only explains a failure you have already detected from a return value, so check the return value, save errno immediately, and turn it into a message that names the operation, the object and the reason.
Worked examples
errno is never cleared for you
strtol reports overflow only through errno, which shows why a stale errno value must be cleared before the call and why errno alone is not a failure test.
<stdio.h>
<stdlib.h>
<string.h>
<errno.h>
<limits.h>
int main(void)
{
char *end;
long big, stale, clean;
int e1, e2, e3;
errno = 0;
big = strtol("99999999999999999999", &end, 10);
e1 = errno; /* ERANGE: the value does not fit in a long */
stale = strtol("42", &end, 10);
e2 = errno; /* this call succeeded, yet nothing reset errno */
errno = 0;
clean = strtol("42", &end, 10);
e3 = errno;
printf("big == LONG_MAX: %s, errno %d (%s)\n",
big == LONG_MAX ? "yes" : "no", e1, strerror(e1));
printf("stale = %ld, errno %d\n", stale, e2);
printf("clean = %ld, errno %d\n", clean, e3);
return 0;
}
Example explained
Line 1errno = 0 before the first strtol is mandatory, because an overflowed conversion is indistinguishable from a real LONG_MAX by return value alone.
Line 2e1 is copied out on the next line, so the two printf calls later cannot disturb the reason code.
Line 3e2 is still 34 even though that strtol succeeded, which is exactly why testing errno instead of a return value reports failures that never happened.
Line 4e3 reads 0 only because the program assigned errno = 0 itself; the library never does that on your behalf.
A failure report the user can act on
Branching on a specific errno value to give advice, while still falling back to strerror for every other reason.
<stdio.h>
<stdlib.h>
<string.h>
<errno.h>
static FILE *open_config(const char *prog, const char *path)
{
FILE *f = fopen(path, "r");
if (f == NULL) {
int err = errno;
if (err == ENOENT)
fprintf(stderr, "%s: %s: no config file yet, run '%s --init'\n",
prog, path, prog);
else
fprintf(stderr, "%s: %s: %s\n", prog, path, strerror(err));
}
return f;
}
int main(void)
{
FILE *f = open_config("readconf", "settings.cfg");
if (f == NULL)
return EXIT_FAILURE;
puts("config opened");
fclose(f);
return EXIT_SUCCESS;
}
Example explained
Line 1The NULL return is what proves the failure; errno is consulted only inside that branch.
Line 2err == ENOENT compares against the macro name, not the number 2, because the numbers are chosen by the platform.
Line 3The else branch keeps strerror(err), so a permission problem is still reported honestly instead of being mislabelled as a missing file.
Line 4main returns EXIT_FAILURE so the exit status agrees with the line printed on stderr.
Important notes
Only EDOM, ERANGE and EILSEQ are guaranteed by the C standard; ENOENT, EACCES and the rest come from POSIX. Compare against the names, never the numbers, and never parse the message text.
strerror may hand back a pointer to a static buffer that the next strerror call overwrites, so print or copy it immediately; in threaded code use strerror_r (POSIX) or strerror_s (C11 Annex K).
Common mistakes
Testing errno instead of the return value: a value left behind by an earlier failed call makes a perfectly good fopen look broken, and a successful call is allowed to set errno anyway.
Doing something between the failing call and perror, such as an fclose or a log printf: errno may already be overwritten, so the message blames the wrong reason and sometimes prints Success.
Reaching for perror after a function that does not use errno at all, such as sscanf returning fewer conversions than requested: errno is still 0 and the diagnostic reads 'parse failed: Success'.
Try it yourself
Change, predict, then run
Write a program that opens "notes.txt" in mode "r" and, on failure, prints one stderr line of the form notes: notes.txt: <reason> built from a saved errno and strerror, with a different message when errno == ENOENT, then returns EXIT_FAILURE. Change the mode to "w" and confirm the success path prints nothing to stderr.
Open the C workspaceCheck your understanding
A program calls fopen, the call succeeds, and the code then does: if (errno != 0) { perror("fopen"); return 1; }. What is wrong with this check?
- A successful call can leave errno at a nonzero value set by some earlier failed call, so the program reports an error that never happened.
- Every successful library call resets errno to 0, so the condition can never be true and the check is merely dead code.
- perror may only be called once in a program, so the branch would fail if any earlier code had already called it.
- errno cannot be read directly after a successful call; it has to be fetched with strerror first.
Show answer
Library functions are only required to set errno when they fail; nothing clears it when they succeed, and a successful call may even set it, so errno is meaningless unless the return value has already established a failure. Option 2 is the tempting misconception: if something did reset errno on success, functions like strtol would not need you to write errno = 0 before calling them.