Code of the Day
IntermediateFile I/O

Error handling with errno

Understand how C reports errors through errno and return values — using perror, strerror, and building robust error-handling patterns.

CIntermediate9 min read
Recommended first
By the end of this lesson you will be able to:
  • Explain what errno is and when it is set
  • Use perror and strerror to print human-readable error messages
  • Apply the check-and-return pattern to library calls
  • List the most common errno values for file operations

C does not have exceptions. Errors in the standard library and system calls are reported through return values — a function returns a special value (NULL, -1, or 0) to indicate failure. The reason for the failure is communicated through errno, a global integer set by library functions when they fail.

What is errno?

errno is declared in <errno.h> as a thread-local integer (on modern systems). When a library function fails, it sets errno to a code that identifies why:

#include <stdio.h>
#include <errno.h>

int main(void) {
    FILE *fp = fopen("nonexistent.txt", "r");
    if (fp == NULL) {
        printf("errno = %d\n", errno); /* e.g. 2 */
    }
    return 0;
}

The number 2 maps to ENOENT (Error: NO ENTry), meaning "no such file or directory." Numeric codes are not portable — use the symbolic names from <errno.h>.

Common errno values

ConstantValueMeaning
ENOENT2No such file or directory
EACCES13Permission denied
EEXIST17File exists (when you tried to create exclusively)
EISDIR21Is a directory
ENOSPC28No space left on device
ENOMEM12Not enough memory (malloc)
EBADF9Bad file descriptor

perror — print the error

perror(prefix) prints prefix: followed by the human-readable description of the current errno:

FILE *fp = fopen("secret.txt", "r");
if (!fp) {
    perror("fopen"); /* prints: "fopen: Permission denied" */
    return 1;
}

Always call perror immediately after the failing call — other calls may reset errno.

strerror — get the error string

strerror(errno) returns a pointer to the human-readable error string without printing it:

#include <string.h>

FILE *fp = fopen("secret.txt", "r");
if (!fp) {
    fprintf(stderr, "Error opening file: %s\n", strerror(errno));
}

Use strerror when you need to incorporate the error description into a larger message.

A robust error-handling pattern

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

int copy_file(const char *src_path, const char *dst_path) {
    FILE *src = fopen(src_path, "rb");
    if (!src) {
        fprintf(stderr, "Cannot open source '%s': %s\n",
                src_path, strerror(errno));
        return -1;
    }

    FILE *dst = fopen(dst_path, "wb");
    if (!dst) {
        fprintf(stderr, "Cannot open destination '%s': %s\n",
                dst_path, strerror(errno));
        fclose(src);
        return -1;
    }

    char buf[4096];
    size_t n;
    while ((n = fread(buf, 1, sizeof(buf), src)) > 0) {
        if (fwrite(buf, 1, n, dst) != n) {
            fprintf(stderr, "Write error: %s\n", strerror(errno));
            fclose(src);
            fclose(dst);
            return -1;
        }
    }

    if (ferror(src)) {
        fprintf(stderr, "Read error: %s\n", strerror(errno));
    }

    fclose(src);
    fclose(dst);
    return 0;
}

int main(void) {
    if (copy_file("input.txt", "output.txt") != 0) {
        return EXIT_FAILURE;
    }
    printf("Copy successful\n");
    return EXIT_SUCCESS;
}

Notice: every exit path closes the open files. When the second fopen fails, we close src before returning.

errno and thread safety

In a multi-threaded program, errno is per-thread (on POSIX systems). Each thread has its own errno — setting it in one thread does not affect another. However, within a single thread, errno can be overwritten by any failing library call, so always save or check it immediately after a failure.

errno is only meaningful after a failure. Functions typically set errno only when they fail. Checking errno after a successful call gives meaningless results. Always check the return value first; only then check errno if the return indicates failure.

Where to go next

Next: the CSV parser lab — you will use fgets, sscanf, and error handling to parse a simple CSV file, applying everything from this module.

Finished reading? Mark it complete to track your progress.

On this page