google-site-verification: google61fe8ba583a51912.html
Building Resilient Systems: A Complete Guide to Error Handling with errno in C

Error handling is the bedrock of robust C programming. Unlike modern languages with exception mechanisms, C relies on a simple but powerful system centered around errno. Mastering errno is essential for writing reliable, production-grade C code that gracefully handles failures. This comprehensive guide explores every facet of errno-based error handling, from basic usage to advanced patterns.

What is errno?

errno (error number) is a global integer variable defined in <errno.h> that system calls and library functions set to indicate what went wrong. When a function fails, it typically returns a sentinel value (like -1 or NULL) and sets errno to a specific error code.

#include <errno.h> #include <stdio.h> #include <string.h> #include <stdlib.h> extern int errno; // Actually defined in errno.h

The errno Landscape

1. Common errno Values

#include <errno.h> #include <stdio.h> void print_common_errno_values() { printf("Common errno values:\n"); printf(" EPERM : %d - Operation not permitted\n", EPERM); printf(" ENOENT : %d - No such file or directory\n", ENOENT); printf(" ESRCH : %d - No such process\n", ESRCH); printf(" EINTR : %d - Interrupted system call\n", EINTR); printf(" EIO : %d - I/O error\n", EIO); printf(" ENXIO : %d - No such device or address\n", ENXIO); printf(" E2BIG : %d - Argument list too long\n", E2BIG); printf(" ENOEXEC : %d - Exec format error\n", ENOEXEC); printf(" EBADF : %d - Bad file number\n", EBADF); printf(" ECHILD : %d - No child processes\n", ECHILD); printf(" EAGAIN : %d - Try again\n", EAGAIN); printf(" ENOMEM : %d - Out of memory\n", ENOMEM); printf(" EACCES : %d - Permission denied\n", EACCES); printf(" EFAULT : %d - Bad address\n", EFAULT); printf(" EBUSY : %d - Device or resource busy\n", EBUSY); printf(" EEXIST : %d - File exists\n", EEXIST); printf(" EXDEV : %d - Cross-device link\n", EXDEV); printf(" ENODEV : %d - No such device\n", ENODEV); printf(" ENOTDIR : %d - Not a directory\n", ENOTDIR); printf(" EISDIR : %d - Is a directory\n", EISDIR); printf(" EINVAL : %d - Invalid argument\n", EINVAL); printf(" ENFILE : %d - File table overflow\n", ENFILE); printf(" EMFILE : %d - Too many open files\n", EMFILE); printf(" ENOTTY : %d - Not a typewriter\n", ENOTTY); printf(" EFBIG : %d - File too large\n", EFBIG); printf(" ENOSPC : %d - No space left on device\n", ENOSPC); printf(" ESPIPE : %d - Illegal seek\n", ESPIPE); printf(" EROFS : %d - Read-only file system\n", EROFS); printf(" EPIPE : %d - Broken pipe\n", EPIPE); printf(" EDOM : %d - Math argument out of domain\n", EDOM); printf(" ERANGE : %d - Math result not representable\n", ERANGE); }

2. errno Categories

// Error categories by domain void categorize_errno(int err) { printf("errno %d: ", err); // File system errors if (err == ENOENT || err == EACCES || err == EPERM || err == ENOTDIR || err == EISDIR || err == EROFS || err == ENOSPC || err == EFBIG || err == EMLINK) { printf("File system error\n"); } // Resource errors else if (err == ENOMEM || err == ENFILE || err == EMFILE || err == ENOSPC || err == EBUSY) { printf("Resource error\n"); } // I/O errors else if (err == EIO || err == EAGAIN || err == EWOULDBLOCK || err == EPIPE || err == ENXIO) { printf("I/O error\n"); } // Process errors else if (err == ECHILD || err == ESRCH || err == EPERM) { printf("Process error\n"); } // Argument errors else if (err == EINVAL || err == EFAULT || err == E2BIG) { printf("Argument error\n"); } // Math errors else if (err == EDOM || err == ERANGE) { printf("Math error\n"); } // Network errors else if (err == ECONNREFUSED || err == ETIMEDOUT || err == ENETUNREACH) { printf("Network error\n"); } else { printf("Other error\n"); } }

Basic errno Usage Patterns

1. The Essential Pattern

#include <stdio.h> #include <errno.h> #include <string.h> #include <stdlib.h> void basic_errno_pattern() { FILE *file = fopen("nonexistent.txt", "r"); if (file == NULL) { // Check errno immediately after failure int saved_errno = errno; // Save immediately fprintf(stderr, "Error opening file: %s (errno=%d)\n", strerror(saved_errno), saved_errno); // Handle specific errors switch (saved_errno) { case ENOENT: fprintf(stderr, " -> File doesn't exist. Creating default...\n"); // Create default file break; case EACCES: fprintf(stderr, " -> Permission denied. Check file permissions.\n"); break; case ENOMEM: fprintf(stderr, " -> Out of memory. Cannot proceed.\n"); exit(1); default: fprintf(stderr, " -> Unknown error occurred.\n"); } } else { fclose(file); } }

2. Clearing errno Before Critical Calls

void clear_errno_before_call() { errno = 0; // Clear before call long result = strtol("123abc", NULL, 10); if (errno != 0) { // Error occurred during conversion fprintf(stderr, "Conversion error: %s\n", strerror(errno)); } else { printf("Conversion successful: %ld\n", result); } }

3. perror() for Quick Error Messages

#include <stdio.h> #include <errno.h> void perror_demo() { FILE *file = fopen("/root/secret.txt", "r"); if (file == NULL) { // Prints: "Cannot open file: Permission denied" perror("Cannot open file"); // Equivalent to: // fprintf(stderr, "Cannot open file: %s\n", strerror(errno)); } }

Thread Safety and errno

1. Thread-Local Storage

#include <pthread.h> #include <errno.h> #include <stdio.h> #include <unistd.h> void* thread_worker(void* arg) { int thread_num = *(int*)arg; // Each thread has its own errno FILE *file = fopen("/nonexistent", "r"); if (file == NULL) { printf("Thread %d: errno = %d (%s)\n", thread_num, errno, strerror(errno)); } // Simulate work sleep(1); // Another operation that might set errno int result = write(-1, "test", 4); // Invalid file descriptor if (result == -1) { printf("Thread %d: errno = %d (%s)\n", thread_num, errno, strerror(errno)); } return NULL; } void demonstrate_thread_safety() { pthread_t threads[3]; int ids[3] = {1, 2, 3}; for (int i = 0; i < 3; i++) { pthread_create(&threads[i], NULL, thread_worker, &ids[i]); } for (int i = 0; i < 3; i++) { pthread_join(threads[i], NULL); } }

2. errno as a Macro (POSIX)

// On modern systems, errno is a macro that expands to a thread-local value // __thread int errno; // GCC thread-local storage // #define errno (*__errno_location()) // Typical implementation #include <stdio.h> void demonstrate_errno_location() { // Get pointer to thread-local errno int *errno_ptr = __errno_location(); errno = ENOENT; printf("errno = %d, *errno_ptr = %d\n", errno, *errno_ptr); printf("Same location? %s\n", &errno == errno_ptr ? "Yes" : "No"); }

Advanced Error Handling Patterns

1. The Check and Save Pattern

#include <errno.h> #include <stdio.h> #include <stdlib.h> typedef struct { int errnum; char message[256]; char function[64]; int line; } ErrorInfo; ErrorInfo last_error; void save_error(int errnum, const char *func, int line, const char *msg) { last_error.errnum = errnum; last_error.line = line; strncpy(last_error.function, func, sizeof(last_error.function) - 1); snprintf(last_error.message, sizeof(last_error.message), "%s", msg); } #define TRY(expr) \ do { \ errno = 0; \ if ((expr) < 0) { \ save_error(errno, __func__, __LINE__, "Failed at " #expr); \ return -1; \ } \ } while(0) #define TRY_PTR(expr) \ do { \ errno = 0; \ void *result = (expr); \ if (result == NULL) { \ save_error(errno, __func__, __LINE__, "Failed at " #expr); \ return NULL; \ } \ } while(0) // Example usage int copy_file_safe(const char *src, const char *dst) { FILE *source, *dest; char buffer[4096]; size_t bytes; TRY_PTR(source = fopen(src, "rb")); TRY_PTR(dest = fopen(dst, "wb")); while ((bytes = fread(buffer, 1, sizeof(buffer), source)) > 0) { if (fwrite(buffer, 1, bytes, dest) != bytes) { save_error(errno, __func__, __LINE__, "Write failed"); fclose(source); fclose(dest); return -1; } } fclose(source); fclose(dest); return 0; } void print_last_error() { if (last_error.errnum != 0) { fprintf(stderr, "Error [%s:%d]: %s\n", last_error.function, last_error.line, last_error.message); fprintf(stderr, " System error: %s (errno=%d)\n", strerror(last_error.errnum), last_error.errnum); } }

2. Error Propagation with Cleanup

#include <errno.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> typedef enum { ERR_SUCCESS = 0, ERR_OPEN, ERR_READ, ERR_WRITE, ERR_CLOSE, ERR_MEMORY } ErrorCode; typedef struct { ErrorCode code; int sys_errno; char file[64]; int line; } DetailedError; DetailedError g_error; void clear_error() { memset(&g_error, 0, sizeof(DetailedError)); } void set_error(ErrorCode code, const char *file, int line) { g_error.code = code; g_error.sys_errno = errno; strncpy(g_error.file, file, sizeof(g_error.file) - 1); g_error.line = line; } #define SET_ERROR(code) set_error(code, __FILE__, __LINE__) // Resource cleanup with error handling int process_file(const char *filename) { int fd = -1; char *buffer = NULL; ssize_t bytes_read; int ret = -1; clear_error(); // Open file fd = open(filename, O_RDONLY); if (fd == -1) { SET_ERROR(ERR_OPEN); goto cleanup; } // Allocate buffer buffer = malloc(4096); if (buffer == NULL) { SET_ERROR(ERR_MEMORY); goto cleanup; } // Read from file bytes_read = read(fd, buffer, 4096); if (bytes_read == -1) { SET_ERROR(ERR_READ); goto cleanup; } // Process data (simulate) if (bytes_read == 0) { // Empty file - not an error ret = 0; goto cleanup; } // Write to stdout if (write(STDOUT_FILENO, buffer, bytes_read) != bytes_read) { SET_ERROR(ERR_WRITE); goto cleanup; } ret = 0; cleanup: // Always clean up resources if (buffer) free(buffer); if (fd != -1) close(fd); return ret; } void print_detailed_error() { if (g_error.code != ERR_SUCCESS) { const char *error_names[] = { "SUCCESS", "OPEN", "READ", "WRITE", "CLOSE", "MEMORY" }; fprintf(stderr, "Error: %s at %s:%d\n", error_names[g_error.code], g_error.file, g_error.line); if (g_error.sys_errno != 0) { fprintf(stderr, "System error: %s (errno=%d)\n", strerror(g_error.sys_errno), g_error.sys_errno); } } }

3. Retry Logic for Transient Errors

#include <errno.h> #include <unistd.h> #include <stdio.h> #define MAX_RETRIES 5 #define RETRY_DELAY_US 100000 // 100ms // Safe write with retry on EINTR and EAGAIN ssize_t safe_write(int fd, const void *buf, size_t count) { size_t total_written = 0; const char *ptr = (const char*)buf; int retries = 0; while (total_written < count && retries < MAX_RETRIES) { ssize_t written = write(fd, ptr + total_written, count - total_written); if (written == -1) { if (errno == EINTR) { // Interrupted by signal, retry immediately continue; } else if (errno == EAGAIN || errno == EWOULDBLOCK) { // Resource temporarily unavailable, wait and retry retries++; usleep(RETRY_DELAY_US); continue; } else { // Real error return -1; } } total_written += written; retries = 0; // Reset retry count on successful write } return total_written; } // Safe read with retry ssize_t safe_read(int fd, void *buf, size_t count) { size_t total_read = 0; char *ptr = (char*)buf; int retries = 0; while (total_read < count && retries < MAX_RETRIES) { ssize_t bytes_read = read(fd, ptr + total_read, count - total_read); if (bytes_read == -1) { if (errno == EINTR) { continue; } else if (errno == EAGAIN || errno == EWOULDBLOCK) { retries++; usleep(RETRY_DELAY_US); continue; } else { return -1; } } else if (bytes_read == 0) { // EOF break; } total_read += bytes_read; retries = 0; } return total_read; }

System Call Error Handling

1. File Operations

#include <fcntl.h> #include <unistd.h> #include <sys/stat.h> #include <errno.h> int robust_file_copy(const char *src, const char *dst) { int src_fd = -1, dst_fd = -1; char buffer[8192]; ssize_t bytes; int ret = -1; // Open source file src_fd = open(src, O_RDONLY); if (src_fd == -1) { fprintf(stderr, "Cannot open source '%s': %s\n", src, strerror(errno)); goto cleanup; } // Open destination file with proper permissions dst_fd = open(dst, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (dst_fd == -1) { fprintf(stderr, "Cannot open destination '%s': %s\n", dst, strerror(errno)); goto cleanup; } // Copy data while ((bytes = read(src_fd, buffer, sizeof(buffer))) > 0) { if (safe_write(dst_fd, buffer, bytes) != bytes) { fprintf(stderr, "Write error: %s\n", strerror(errno)); goto cleanup; } } if (bytes == -1) { fprintf(stderr, "Read error: %s\n", strerror(errno)); goto cleanup; } // Ensure data is written to disk if (fsync(dst_fd) == -1) { fprintf(stderr, "fsync error: %s\n", strerror(errno)); // Continue anyway } ret = 0; cleanup: if (src_fd != -1) close(src_fd); if (dst_fd != -1) close(dst_fd); return ret; }

2. Network Operations

#include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <errno.h> #include <stdio.h> int create_server_socket(int port) { int sock = socket(AF_INET, SOCK_STREAM, 0); if (sock == -1) { perror("socket"); return -1; } // Set socket options to reuse address int opt = 1; if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1) { perror("setsockopt"); close(sock); return -1; } struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = INADDR_ANY; if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1) { perror("bind"); close(sock); return -1; } if (listen(sock, 10) == -1) { perror("listen"); close(sock); return -1; } return sock; } int accept_with_timeout(int sock, int timeout_sec) { struct timeval tv; fd_set readfds; FD_ZERO(&readfds); FD_SET(sock, &readfds); tv.tv_sec = timeout_sec; tv.tv_usec = 0; int ret = select(sock + 1, &readfds, NULL, NULL, &tv); if (ret == -1) { if (errno == EINTR) { fprintf(stderr, "select interrupted by signal\n"); return -2; } perror("select"); return -1; } else if (ret == 0) { fprintf(stderr, "Timeout waiting for connection\n"); return -3; } int client_sock = accept(sock, NULL, NULL); if (client_sock == -1) { perror("accept"); return -1; } return client_sock; }

3. Process Management

#include <sys/wait.h> #include <signal.h> #include <errno.h> pid_t robust_fork() { pid_t pid = fork(); if (pid == -1) { if (errno == EAGAIN) { fprintf(stderr, "System resource limit reached, cannot fork\n"); } else if (errno == ENOMEM) { fprintf(stderr, "Out of memory, cannot fork\n"); } else { perror("fork"); } return -1; } return pid; } int robust_waitpid(pid_t pid, int *status, int options) { int ret; while ((ret = waitpid(pid, status, options)) == -1) { if (errno == EINTR) { // Interrupted by signal, retry continue; } perror("waitpid"); return -1; } return ret; }

Custom Error Handling Framework

#include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdarg.h> // Error levels typedef enum { ERROR_LEVEL_DEBUG = 0, ERROR_LEVEL_INFO, ERROR_LEVEL_WARNING, ERROR_LEVEL_ERROR, ERROR_LEVEL_FATAL } ErrorLevel; // Error context typedef struct { ErrorLevel level; int errnum; char file[64]; char function[64]; int line; char message[512]; struct ErrorContext *next; } ErrorContext; // Error handler function type typedef void (*ErrorHandler)(const ErrorContext *ctx); // Global error handling state static ErrorHandler global_handler = NULL; static ErrorContext *error_stack = NULL; // Default error handler void default_error_handler(const ErrorContext *ctx) { const char *level_names[] = {"DEBUG", "INFO", "WARNING", "ERROR", "FATAL"}; fprintf(stderr, "[%s] %s:%d in %s: %s\n", level_names[ctx->level], ctx->file, ctx->line, ctx->function, ctx->message); if (ctx->errnum != 0) { fprintf(stderr, " System error: %s (errno=%d)\n", strerror(ctx->errnum), ctx->errnum); } } // Set custom error handler void set_error_handler(ErrorHandler handler) { global_handler = handler ? handler : default_error_handler; } // Push error onto stack void push_error(ErrorLevel level, const char *file, const char *func, int line, int errnum, const char *format, ...) { ErrorContext *ctx = malloc(sizeof(ErrorContext)); if (!ctx) return; ctx->level = level; ctx->errnum = errnum; strncpy(ctx->file, file, sizeof(ctx->file) - 1); strncpy(ctx->function, func, sizeof(ctx->function) - 1); ctx->line = line; va_list args; va_start(args, format); vsnprintf(ctx->message, sizeof(ctx->message), format, args); va_end(args); ctx->next = error_stack; error_stack = ctx; // Call handler for non-debug errors if (level >= ERROR_LEVEL_WARNING) { ErrorHandler handler = global_handler ? global_handler : default_error_handler; handler(ctx); } } // Pop and report errors void pop_error() { if (error_stack) { ErrorContext *ctx = error_stack; error_stack = ctx->next; free(ctx); } } // Clear all errors void clear_errors() { while (error_stack) { pop_error(); } } // Macros for easy error reporting #define LOG_DEBUG(...) \ push_error(ERROR_LEVEL_DEBUG, __FILE__, __func__, __LINE__, 0, __VA_ARGS__) #define LOG_INFO(...) \ push_error(ERROR_LEVEL_INFO, __FILE__, __func__, __LINE__, 0, __VA_ARGS__) #define LOG_WARNING(...) \ push_error(ERROR_LEVEL_WARNING, __FILE__, __func__, __LINE__, errno, __VA_ARGS__) #define LOG_ERROR(...) \ push_error(ERROR_LEVEL_ERROR, __FILE__, __func__, __LINE__, errno, __VA_ARGS__) #define LOG_FATAL(...) \ do { \ push_error(ERROR_LEVEL_FATAL, __FILE__, __func__, __LINE__, errno, __VA_ARGS__); \ exit(EXIT_FAILURE); \ } while(0) // Guard macro for function entry/exit #define FUNCTION_ENTER() LOG_DEBUG("Entering %s", __func__) #define FUNCTION_EXIT() LOG_DEBUG("Exiting %s", __func__) // Example usage int divide_numbers(int a, int b) { FUNCTION_ENTER(); if (b == 0) { LOG_ERROR("Division by zero attempted: %d / %d", a, b); FUNCTION_EXIT(); return -1; } int result = a / b; LOG_INFO("Division result: %d / %d = %d", a, b, result); FUNCTION_EXIT(); return result; } void demonstrate_error_framework() { set_error_handler(NULL); // Use default handler divide_numbers(10, 2); divide_numbers(10, 0); // This will log an error clear_errors(); }

Debugging with errno

1. errno Tracing

#include <errno.h> #include <stdio.h> // Trace errno changes void trace_errno(const char *file, int line, const char *func) { static int last_errno = 0; if (errno != last_errno) { fprintf(stderr, "TRACE: %s:%d in %s - errno changed from %d (%s) to %d (%s)\n", file, line, func, last_errno, last_errno ? strerror(last_errno) : "none", errno, errno ? strerror(errno) : "none"); last_errno = errno; } } #define TRACE_ERRNO() trace_errno(__FILE__, __LINE__, __func__) void function_with_operations() { TRACE_ERRNO(); FILE *f = fopen("/nonexistent", "r"); TRACE_ERRNO(); if (f) fclose(f); int *p = malloc(1000000000); // May fail TRACE_ERRNO(); free(p); }

2. errno Assertions

#include <assert.h> #include <errno.h> #define ASSERT_ERRNO(expected) \ do { \ int _err = errno; \ if (_err != (expected)) { \ fprintf(stderr, "ASSERT_ERRNO failed at %s:%d: expected %d (%s), got %d (%s)\n", \ __FILE__, __LINE__, \ expected, expected ? strerror(expected) : "none", \ _err, _err ? strerror(_err) : "none"); \ assert(0); \ } \ } while(0) // Example void test_strtol() { errno = 0; long val = strtol("123abc", NULL, 10); // strtol sets errno to 0 on success (no error) ASSERT_ERRNO(0); errno = 0; val = strtol("999999999999999999999999", NULL, 10); ASSERT_ERRNO(ERANGE); // Should be out of range }

Common Pitfalls and Solutions

1. Don't Check errno Without Checking Return Value

// WRONG errno = 0; fopen("file.txt", "r"); if (errno != 0) { // This might trigger even if fopen succeeded! perror("fopen"); } // CORRECT FILE *file = fopen("file.txt", "r"); if (file == NULL) { int saved_errno = errno; // Save immediately fprintf(stderr, "fopen failed: %s\n", strerror(saved_errno)); } else { fclose(file); }

2. Don't Use errno After Other Functions

// WRONG FILE *file = fopen("file.txt", "r"); if (file == NULL) { printf("Error: "); // printf may change errno! perror("fopen"); } // CORRECT FILE *file = fopen("file.txt", "r"); if (file == NULL) { int saved_errno = errno; // Save immediately printf("Error: %s\n", strerror(saved_errno)); } else { fclose(file); }

3. Preserve errno in Signal Handlers

#include <signal.h> #include <errno.h> #include <unistd.h> void signal_handler(int sig) { int saved_errno = errno; // Save // Do signal-safe operations const char msg[] = "Signal received\n"; write(STDERR_FILENO, msg, sizeof(msg) - 1); errno = saved_errno; // Restore }

4. Thread-Safe errno Access

// On modern systems, errno is thread-local, but be careful // with library functions that may not be thread-safe #include <pthread.h> void* thread_func(void* arg) { // Each thread has its own errno errno = 0; FILE *f = fopen("/nonexistent", "r"); if (f == NULL) { // This errno is specific to this thread printf("Thread %ld: %s\n", (long)pthread_self(), strerror(errno)); } return NULL; }

Complete Example: Robust File Processing

#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #define BUFFER_SIZE 4096 #define MAX_RETRIES 3 typedef struct { char *data; size_t size; int error; int sys_errno; } FileResult; // Clear error state void file_result_clear(FileResult *result) { result->data = NULL; result->size = 0; result->error = 0; result->sys_errno = 0; } // Read entire file with robust error handling FileResult read_file_robust(const char *filename) { FileResult result; file_result_clear(&result); int fd = -1; char *buffer = NULL; size_t total = 0; size_t capacity = BUFFER_SIZE; int retries = 0; // Open file fd = open(filename, O_RDONLY); if (fd == -1) { result.error = 1; result.sys_errno = errno; return result; } // Allocate initial buffer buffer = malloc(capacity); if (buffer == NULL) { result.error = 1; result.sys_errno = ENOMEM; close(fd); return result; } // Read loop with retry on interrupt while (1) { ssize_t bytes_read = read(fd, buffer + total, capacity - total); if (bytes_read == -1) { if (errno == EINTR && retries < MAX_RETRIES) { retries++; continue; } result.error = 1; result.sys_errno = errno; free(buffer); close(fd); return result; } if (bytes_read == 0) { break; // EOF } total += bytes_read; retries = 0; // Reset retries on successful read // Expand buffer if needed if (total == capacity) { capacity *= 2; char *new_buffer = realloc(buffer, capacity); if (new_buffer == NULL) { result.error = 1; result.sys_errno = ENOMEM; free(buffer); close(fd); return result; } buffer = new_buffer; } } // Trim to actual size char *final_buffer = realloc(buffer, total + 1); if (final_buffer == NULL) { final_buffer = buffer; // Keep original if realloc fails } final_buffer[total] = '\0'; result.data = final_buffer; result.size = total; result.error = 0; close(fd); return result; } // Write file with fsync guarantee int write_file_robust(const char *filename, const char *data, size_t size) { int fd = -1; ssize_t written; size_t total = 0; int ret = -1; fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd == -1) { fprintf(stderr, "Cannot open '%s': %s\n", filename, strerror(errno)); return -1; } while (total < size) { written = write(fd, data + total, size - total); if (written == -1) { if (errno == EINTR) { continue; } fprintf(stderr, "Write error: %s\n", strerror(errno)); goto cleanup; } total += written; } // Ensure data is on disk if (fsync(fd) == -1) { fprintf(stderr, "fsync error: %s\n", strerror(errno)); // Continue anyway - data may still be written } ret = 0; cleanup: close(fd); return ret; } // Process a file with comprehensive error handling int process_file(const char *input, const char *output) { FileResult read_result; int ret = -1; // Read input read_result = read_file_robust(input); if (read_result.error) { fprintf(stderr, "Failed to read '%s': ", input); if (read_result.sys_errno) { fprintf(stderr, "%s", strerror(read_result.sys_errno)); } else { fprintf(stderr, "Unknown error"); } fprintf(stderr, "\n"); return -1; } printf("Read %zu bytes from '%s'\n", read_result.size, input); // Process data (simple transformation - convert to uppercase) for (size_t i = 0; i < read_result.size; i++) { if (read_result.data[i] >= 'a' && read_result.data[i] <= 'z') { read_result.data[i] -= 32; } } // Write output if (write_file_robust(output, read_result.data, read_result.size) == -1) { fprintf(stderr, "Failed to write to '%s'\n", output); goto cleanup; } printf("Wrote %zu bytes to '%s'\n", read_result.size, output); ret = 0; cleanup: free(read_result.data); return ret; } int main(int argc, char *argv[]) { if (argc != 3) { fprintf(stderr, "Usage: %s <input> <output>\n", argv[0]); return 1; } if (process_file(argv[1], argv[2]) == -1) { return 1; } printf("File processed successfully\n"); return 0; }

Best Practices Summary

PracticeWhy It Matters
Check return values before errnoerrno may be set even on success
Save errno immediatelyOther functions may change it
Clear errno before critical callsAvoid false positives
Use thread-local errnoModern systems are thread-safe
Provide context in error messagesInclude filename, operation details
Use perror() for quick debuggingSimple, standardized output
Handle EINTR properlySystem calls can be interrupted
Implement retry logicTransient errors can succeed later
Clean up resources on errorPrevent resource leaks
Document error conditionsHelp API users

Conclusion

Error handling with errno in C is a fundamental skill that separates novice from expert C programmers. While the mechanism is simple, using it effectively requires understanding:

  • Which functions set errno and how they indicate failure
  • Thread-safety considerations
  • Proper patterns for saving, checking, and restoring errno
  • Handling transient errors with retry logic
  • Building comprehensive error reporting frameworks

By following the patterns and practices outlined in this guide, you can build C applications that not only handle errors gracefully but also provide meaningful diagnostics for debugging and maintenance. Remember: robust error handling isn't an afterthought—it's a core design consideration that should be integrated from the earliest stages of development.

Complete C Programming Guide + Compilers Collection


1. C srand() Function – Understanding Seed Initialization

https://macronepal.com/understanding-the-c-srand-function
Explains how srand() initializes the pseudo-random number generator in C by setting a seed value. Using the same seed produces the same sequence, while time(NULL) gives different results each run.


2. C rand() Function Mechanics and Limitations

https://macronepal.com/c-rand-function-mechanics-and-limitations
Explains how rand() generates pseudo-random numbers between 0 and RAND_MAX, its deterministic nature, and limitations for security use cases.


3. C log() Function

https://macronepal.com/c-log-function-2
Covers natural logarithm calculation using <math.h> and its applications.


4. Mastering Date and Time in C

https://macronepal.com/mastering-date-and-time-in-c
Explains <time.h> functions like time(), clock(), difftime(), and struct tm.


5. Mastering time_t Type in C

https://macronepal.com/mastering-the-c-time_t-type-for-time-management
Explains time representation as seconds since Unix epoch and conversion functions.


6. C exp() Function

https://macronepal.com/c-exp-function-mechanics-and-implementation
Explains exponential function exp(x) and its scientific applications.


7. C log() Function (Alternate Guide)

https://macronepal.com/c-log-function
Comparison of log() and log10() with usage examples.


8. C log10() Function

https://macronepal.com/mastering-the-log10-function-in-c
Explains base-10 logarithm for engineering and scientific applications.


9. C tan() Function

https://macronepal.com/understanding-the-c-tan-function
Explains tangent function and radian-based calculations.


10. Random Numbers in C (Secure vs Predictable)

https://macronepal.com/mastering-c-random-numbers-for-secure-and-predictable-applications
Explains difference between rand() and secure randomness methods.


11. Free Online C Compiler

https://macronepal.com/free-online-c-code-compiler-2
Browser-based compiler for testing C programs instantly.


C Functions, Arguments, Parameters & Flow

Mastering Functions in C – Complete Guide

https://macronepal.com/c/mastering-functions-in-c-a-complete-guide/
Covers function structure, modular programming, and real-world usage.


Function Arguments in C

https://macronepal.com/c-function-arguments/
Explains how arguments are passed and used in function calls.


Function Parameters in C

https://macronepal.com/c-function-parameters/
Explains defining inputs for functions and matching them with arguments.


Function Declarations in C

https://macronepal.com/c-function-declarations-syntax-rules-and-best-practices/
Covers prototypes, syntax rules, and best practices.


Function Calls in C

https://macronepal.com/understanding-function-calls-in-c-syntax-mechanics-and-best-practices/
Explains execution flow and parameter handling during function calls.


Void Functions in C

https://macronepal.com/understanding-void-functions-in-c-syntax-patterns-and-best-practices/
Explains functions that do not return values.


Return Values in C

https://macronepal.com/c-return-values-mechanics-types-and-best-practices/
Explains different return types and how functions return results.


Pass-by-Value in C

https://macronepal.com/aws/understanding-pass-by-value-in-c-mechanics-implications-and-best-practices/
Explains how copies of variables are passed into functions.


Pass-by-Reference in C

https://macronepal.com/c/understanding-pass-by-reference-in-c-pointers-semantics-and-safe-practices/
Explains using pointers to modify original variables.


C strstr() Function

https://macronepal.com/aws/c-strstr-function/
Explains substring search inside strings in C.


C Preprocessor & Macros

https://macronepal.com/mastering-c-variadic-macros-for-flexible-debugging/
https://macronepal.com/mastering-the-stdc-macro-in-c/
https://macronepal.com/c-time-macro-mechanics-and-usage/
https://macronepal.com/understanding-the-c-date-macro/
https://macronepal.com/c-file-type/
https://macronepal.com/mastering-c-line-macro-for-debugging-and-diagnostics/
https://macronepal.com/mastering-predefined-macros-in-c/
https://macronepal.com/c-error-directive-mechanics-and-usage/
https://macronepal.com/understanding-the-c-pragma-directive/
https://macronepal.com/c-include-directive/


C Structures, Memory, Scope & Linkage

https://macronepal.com/mastering-structures-in-c/
https://macronepal.com/c-structure-declaration-mechanics-and-usage/
https://macronepal.com/c-structure-initialization-mechanics-and-best-practices/
https://macronepal.com/mastering-c-structure-member-access-for-reliable-data-handling/
https://macronepal.com/c-nested-structures/
https://macronepal.com/mastering-arrays-of-structures-in-c/
https://macronepal.com/c-structure-pointers-mechanics-and-implementation/
https://macronepal.com/understanding-c-structure-parameter-passing-mechanics/
https://macronepal.com/mastering-c-returning-structures-for-efficient-data-flow/
https://macronepal.com/c-self-referential-structures/
https://macronepal.com/mastering-structure-alignment-in-c/
https://macronepal.com/c-structure-padding-mechanics-and-optimization/
https://macronepal.com/understanding-c-flexible-array-members-mechanics-and-usage/
https://macronepal.com/mastering-c-anonymous-structures-for-flattened-data-layouts/
https://macronepal.com/c-unions/
https://macronepal.com/mastering-c-name-mangling-and-symbol-decoration/
https://macronepal.com/c-no-linkage-mechanics-and-scope-isolation/
https://macronepal.com/understanding-c-internal-linkage-mechanics-and-architecture/


C Scope, Storage Classes & Typedef

https://macronepal.com/mastering-function-prototype-scope-in-c/
https://macronepal.com/c-function-scope-mechanics-and-visibility/
https://macronepal.com/understanding-c-file-scope-mechanics-and-architecture/
https://macronepal.com/mastering-c-scope-rules-for-predictable-name-resolution/
https://macronepal.com/c-scope-rules/
https://macronepal.com/mastering-c-register-storage-class-for-historical-context-and-modern-alternatives/
https://macronepal.com/mastering-_thread_local-in-c/
https://macronepal.com/c-extern-storage-class-mechanics-and-usage/
https://macronepal.com/understanding-the-c-static-storage-class-mechanics-and-usage/
https://macronepal.com/c-auto-storage-class/
https://macronepal.com/c-typedef-with-pointers/


Extra Articles

https://macronepal.com/13757-2/
https://macronepal.com/13748-2/
https://macronepal.com/13747-2/
https://macronepal.com/13746-2/
https://macronepal.com/13745-2/
https://macronepal.com/13708-2/
https://macronepal.com/13707-2/
https://macronepal.com/13702-2/


Online Compilers

https://macronepal.com/free-html-online-code-compiler/
https://macronepal.com/free-online-python-code-compiler/
https://macronepal.com/free-online-python2-code-compiler/
https://macronepal.com/free-online-java-code-compiler/
https://macronepal.com/free-online-javascript-code-compiler/
https://macronepal.com/free-online-node-js-code-compiler/
https://macronepal.com/free-online-c-code-compiler/
https://macronepal.com/free-online-c-code-compiler-2/
https://macronepal.com/free-online-c-code-compiler-3/
https://macronepal.com/free-online-php-code-compiler/
https://macronepal.com/free-online-ruby-code-compiler/
https://macronepal.com/free-online-perl-code-compiler/
https://macronepal.com/free-online-lua-code-compiler/
https://macronepal.com/free-online-tcl-code-compiler/
https://macronepal.com/free-online-groovy-code-compiler/
https://macronepal.com/free-online-j-shell-code-compiler/
https://macronepal.com/free-online-haskell-code-compiler/
https://macronepal.com/free-online-scala-code-compiler/
https://macronepal.com/free-online-common-lisp-code-compiler/
https://macronepal.com/free-online-d-code-compiler/
https://macronepal.com/free-online-ada-code-compiler/
https://macronepal.com/free-erlang-code-compiler/
https://macronepal.com/free-online-assembly-code-compiler/

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper