C File Error Handling, EOF Detection, ferror() & errno Masterclass
Welcome to Phase 14 (Chapter 38): C File Error Handling, EOF Detection, ferror() & errno Masterclass! Production C software must gracefully handle runtime I/O failures (missing files, full disks, invalid permissions, hardware disconnects). In this guide, you will master system error reporting with <errno.h>, perror(), feof(), and ferror().
When an I/O operation returns an end-of-file condition or failure indicator, C provides two distinct diagnostic status checkers:
feof() vs ferror() Matrix:
โข feof(FILE *fp): Returns non-zero (true) if stream reached End-Of-File normally.
โข ferror(FILE *fp): Returns non-zero (true) if stream encountered a Hardware or System I/O Error.
โข clearerr(FILE *fp): Clears both EOF and error flags for the given stream.
โ ๏ธ The feof() Loop Bug Trap:
DO NOT write while (!feof(fp))! feof() becomes true ONLY AFTER a read operation attempts to read past the end of the file and fails.
When standard library functions fail, they set a thread-global integer variable errno defined in <errno.h> to an OS-specific error code:
| errno Constant | Integer Code | Meaning |
|---|---|---|
ENOENT | 2 | No such file or directory. |
EACCES | 13 | Permission denied (Read/Write forbidden). |
EEXIST | 17 | File already exists. |
ENOSPC | 28 | No space left on device (Disk Full). |
C provides 2 helper functions to convert errno integers into human-readable messages:
โข perror("Custom Prefix"): Prints prefix + descriptive system message to stderr.
โข strerror(errno): Returns const char* string message from <string.h>.
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#define BUFFER_SIZE 4096
int copyFile(const char *srcPath, const char *destPath) {
FILE *src = fopen(srcPath, "rb");
if (!src) {
fprintf(stderr, "Error opening source '%s': %s (Errno %d)\n", srcPath, strerror(errno), errno);
return -1;
}
FILE *dest = fopen(destPath, "wb");
if (!dest) {
fprintf(stderr, "Error opening destination '%s': %s\n", destPath, strerror(errno));
fclose(src);
return -1;
}
unsigned char buffer[BUFFER_SIZE];
size_t bytesRead, bytesWritten;
while ((bytesRead = fread(buffer, 1, BUFFER_SIZE, src)) > 0) {
bytesWritten = fwrite(buffer, 1, bytesRead, dest);
if (bytesWritten < bytesRead) {
perror("Disk Write Failure");
fclose(src); fclose(dest);
return -1;
}
}
if (ferror(src)) {
fprintf(stderr, "Hardware Read Failure on '%s'\n", srcPath);
fclose(src); fclose(dest);
return -1;
}
if (feof(src)) {
printf("File copy successful! All bytes transferred safely.\n");
}
fclose(src); fclose(dest);
return 0;
}
int main(void) {
copyFile("non_existent_input.bin", "output_copy.bin");
return 0;
} Q1: Why does perror() print to stderr instead of stdout?
Printing error logs to stderr ensures diagnostic messages are visible in console output even when standard stdout is redirected to a file or pipe.
Q2: Must we clear errno before calling a library function?
Yes! Library functions set errno on failure but DO NOT reset errno to 0 on success.
Q3: What function clears stream error flags?
Calling clearerr(fp) resets both feof and ferror indicators for the specified stream handle.
Q4: How do you append timestamped logs safely to disk?
Open the file with fopen("app.log", "a"). Append operations atomically jump to the end of file before every write.
Q5: What is the return value of strerror_s or strerror_r?
They are thread-safe versions of strerror() introduced to prevent race conditions in multithreaded applications.