Error Handling in System Programming

 

Error Handling in System Programming
errno, perror(), strerror() and Proper Error Checking Techniques
3.4
Chapter Section
3
Error Categories
-1
Typical Error Return

Why Error Checking Matters

Almost every system call and library function returns some type of status value indicating whether the call succeeded or failed. This return status should always be checked to determine whether the call was successful. If it was not, appropriate action must be taken — at the very least, the program should display an error message warning that something unexpected occurred.

Although it is tempting to skip these checks to save typing time, this is a false economy. Many hours of debugging time can be wasted because a check was not made on the return status of a system call or library function that “couldn’t possibly fail.”

Key Concepts

errno perror() strerror() EINTR errno.h EXIT_FAILURE Error Codes getpriority() F_GETOWN return -1 Status Value

Handling System Call Errors

The -1 Return Value Convention
Return -1 on Error Check Every Call errno Set on Failure

The manual page for each system call documents the possible return values of the call, showing which value indicates an error. Usually, an error is indicated by a return of -1. A system call can be checked with the following pattern:

fd = open(pathname, flags, mode); /* system call to open a file */
if (fd == -1) {
    /* Code to handle the error */
}
/* ... rest of program ... */

When a system call fails, it sets the global integer variable errno to a positive value that identifies the specific error. Including the <errno.h> header file provides a declaration of errno and a set of constants for the various error numbers. All symbolic error names begin with the letter E (for example, EACCES, ENOENT, EINTR).

Key Rule: Successful system calls and library functions never reset errno to 0. The variable may contain a nonzero value from a previous failed call. Always check the function return value first, and only examine errno if the return value indicates an error.

Using errno to Diagnose Errors
#include <errno.h>
#include <stdio.h>
#include <unistd.h>

/* Example: diagnosing a read() system call error using errno */
ssize_t cnt;
cnt = read(fd, buf, numbytes);
if (cnt == -1) {
    if (errno == EINTR)
        fprintf(stderr, "read was interrupted by a signal\n");
    else {
        /* Some other error occurred */
        fprintf(stderr, "read error: errno = %d\n", errno);
    }
}

/* CORRECT order: always check return value FIRST, then errno */
fd = open(pathname, flags, mode);
if (fd == -1) {
    /* Now it is safe to examine errno */
    if (errno == ENOENT)
        fprintf(stderr, "File not found: %s\n", pathname);
    else if (errno == EACCES)
        fprintf(stderr, "Permission denied: %s\n", pathname);
}

Special Case: System Calls That Return -1 on Success
getpriority() Set errno to 0 First

A few system calls (such as getpriority()) can legitimately return -1 on success. To determine whether an error occurs in such calls, you must:

  1. Set errno to 0 before making the system call.
  2. After the call, check if it returned -1 and errno is nonzero.
  3. If both conditions are true, an error occurred.
#include <errno.h>
#include <sys/resource.h>

int prio;
errno = 0;                          /* Step 1: reset errno to 0 */
prio = getpriority(PRIO_PROCESS, 0);
if (prio == -1 && errno != 0) {    /* Step 2: check both conditions */
    /* An error occurred */
    perror("getpriority");
}
/* If prio == -1 and errno == 0, the call succeeded with return value -1 */
SUSv3 Note: SUSv3 permits a successful function call to set errno to a nonzero value, though few functions do this. The safest approach is always to check the function return value before examining errno.

Error-Reporting Functions

perror() – Print Error Message Based on errno
stdio.h Locale-Sensitive Writes to stderr

The perror() function prints the string pointed to by its msg argument, followed by a message corresponding to the current value of errno.

#include <stdio.h>

void perror(const char *msg);

/* Prototype: prints to stderr: "msg: error description\n"
   The error description corresponds to the current errno value */

A typical pattern for handling errors from system calls using perror():

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

int fd;
fd = open(pathname, flags, mode);
if (fd == -1) {
    perror("open");             /* Prints: "open: No such file or directory" */
    exit(EXIT_FAILURE);
}

/* General pattern for any system call */
if (some_syscall(args) == -1) {
    perror("syscall name");    /* Prints descriptive error to stderr */
    exit(EXIT_FAILURE);
}
Locale-Sensitive: Both perror() and strerror() are locale-sensitive. Error descriptions are displayed in the local language configured for the system.

strerror() – Get Error String for a Specific errno Value
string.h Custom Error Messages Static Buffer

The strerror() function returns the error string corresponding to the error number given in its errnum argument. This gives you more control over how the error message is formatted and where it is displayed.

#include <string.h>

char *strerror(int errnum);
/* Returns pointer to error string corresponding to errnum
   The string may be statically allocated — it could be overwritten
   by subsequent calls to strerror() */
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>

int fd;
fd = open(pathname, O_RDONLY);
if (fd == -1) {
    /* More flexible than perror() — can format as needed */
    fprintf(stderr, "Failed to open %s: %s\n", pathname, strerror(errno));
}

/* For an unrecognized error number, strerror() returns:
   "Unknown error nnn"  (Linux/glibc behavior)
   NULL                 (some other implementations) */
Thread Safety: The string returned by strerror() may be statically allocated. This means it could be overwritten by subsequent calls to strerror(). In multithreaded programs, use strerror_r() instead, which writes into a caller-provided buffer.

Error Handling for Library Functions

Three Categories of Library Function Error Behavior

Library functions do not all report errors in the same way. They fall into three distinct categories:

Category 1: Return -1 and Set errno (Same as System Calls)

Some library functions return error information in exactly the same way as system calls: a -1 return value with errno indicating the specific error. The remove() function is an example — it removes a file using unlink() or a directory using rmdir().

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

/* remove() behaves like a system call for error reporting */
if (remove(pathname) == -1) {
    perror("remove");         /* Works exactly like system call errors */
    exit(EXIT_FAILURE);
}
Category 2: Return Non-(-1) Error Value but Still Set errno

Some library functions return a value other than -1 on error, but still set errno to indicate the specific error condition. For example, fopen() returns a NULL pointer on error, and the setting of errno depends on which underlying system call failed. The perror() and strerror() functions can still be used to diagnose these errors.

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

FILE *fp;
fp = fopen(pathname, "r");
if (fp == NULL) {             /* Returns NULL (not -1) on error */
    perror("fopen");          /* But errno is still set — perror() works */
    exit(EXIT_FAILURE);
}
Category 3: Do Not Use errno at All

Other library functions do not use errno at all. The method for determining the existence and cause of errors depends on the particular function and is documented in the function’s manual page. For these functions, it is a mistake to use errno, perror(), or strerror() to diagnose errors.

Important: Always consult the manual page of each library function to determine its specific error-reporting convention. Do not assume all functions behave like system calls.

Error Handling Quick Reference
Function Type Error Indicator errno Set? How to Diagnose
Most system calls Return -1 Yes perror() or strerror(errno)
Library functions (Category 1) Return -1 Yes perror() or strerror(errno)
Library functions (Category 2) Return NULL or other Yes perror() or strerror(errno)
Library functions (Category 3) Function-specific No Check the manual page
Calls returning -1 on success Return -1 + errno != 0 Yes (on error) Set errno=0 before; check both
Always-succeed calls (getpid(), _exit()) N/A No No check needed

Golden Rules for Error Handling
Always Check Returns Check Return First Reset errno When Needed
  • Always check the return status of every system call and library function — never skip error checks.
  • Check the return value first, then examine errno only when the return value indicates failure.
  • Do not assume errno is 0 at the start of an operation — it may carry over from a previous call.
  • For calls that can return -1 on success, reset errno to 0 before calling and check both the return value and errno.
  • Use perror() for quick, simple error reporting to stderr.
  • Use strerror() when you need more control over the format of the error message.
  • For library functions in Category 3, consult the manual page — errno-based diagnostics will not work.

Next: Example Programs and Error-Diagnostic Functions

Explore the common header files and error-handling functions used in real system programming projects.

Example Programs → ← Library Functions

Leave a Reply

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