Example Programs and Common Functions

 

Example Programs and Common Functions
Header Files, Error-Diagnostic Functions, and Command-Line Argument Parsing
3.5
Chapter Section
7
Error Functions
2
Argument Functions

Overview

The example programs in “The Linux Programming Interface” share a common set of conventions and utility functions to keep the code concise and consistent. These include a shared header file that handles common includes and definitions, a set of error-diagnostic functions, and helper functions for parsing command-line arguments.

Understanding these reusable building blocks is essential for reading and writing real-world system programs efficiently.

Key Concepts

tlpi_hdr.h errMsg() errExit() err_exit() errExitEN() fatal() usageErr() cmdLineErr() getInt() getLong() getopt() Boolean typedef

Section 3.5.1: Command-Line Options and Arguments

Parsing Command-Line Options with getopt()
UNIX Conventions getopt() GNU Extensions Help Facility

Many system programs rely on command-line options and arguments to determine their behavior. Traditional UNIX command-line options consist of:

  • An initial hyphen (-)
  • A single letter that identifies the option
  • An optional argument following the letter

GNU utilities extend this with two initial hyphens followed by a descriptive word (e.g., --help, --verbose). The standard getopt() library function (described in Appendix B) is used to parse these options.

/* Example of UNIX-style command-line options */
$ myprog -x -y arg1 arg2       /* Options: -x and -y (no arguments) */
$ myprog -o outfile input.txt  /* Option: -o with argument "outfile" */

/* GNU long-form options */
$ ls --all --human-readable     /* GNU extended style */

/* Usage message pattern (shown when --help is specified) */
$ myprog --help
Usage: myprog [-x] [-y] [-o outfile] file...
  -x    Enable feature x
  -y    Enable feature y
  -o    Specify output file

Section 3.5.2: Common Header File (tlpi_hdr.h)

The tlpi_hdr.h Header File
sys/types.h stdio.h stdlib.h unistd.h errno.h Boolean type

Nearly every example program includes a common header file called tlpi_hdr.h. This header includes other commonly needed headers, defines a Boolean data type, and defines macros for computing the minimum and maximum of two values.

/* ─────────────── lib/tlpi_hdr.h ─────────────── */
#ifndef TLPI_HDR_H
#define TLPI_HDR_H  /* Prevent accidental double inclusion */

#include <sys/types.h>  /* Type definitions used by many programs */
#include <stdio.h>      /* Standard I/O functions */
#include <stdlib.h>     /* Prototypes of commonly used library functions,
                            plus EXIT_SUCCESS and EXIT_FAILURE constants */
#include <unistd.h>     /* Prototypes for many system calls */
#include <errno.h>      /* Declares errno and defines error constants */
#include <string.h>     /* Commonly used string-handling functions */

#include "get_num.h"         /* Declares getInt(), getLong() */
#include "error_functions.h" /* Declares error-handling functions */

typedef enum { FALSE, TRUE } Boolean;

#define min(m,n) ((m) < (n) ? (m) : (n))
#define max(m,n) ((m) > (n) ? (m) : (n))

#endif
/* ─────────────────────────────────────────────── */

Using this single header file allows example programs to remain shorter and more focused on the concept being demonstrated, rather than on boilerplate includes.

Error-Diagnostic Functions

Function Declarations (error_functions.h)
/* ─────────────── lib/error_functions.h ─────────────── */
#ifndef ERROR_FUNCTIONS_H
#define ERROR_FUNCTIONS_H

void errMsg(const char *format, ...);

#ifdef __GNUC__
/* This macro stops 'gcc -Wall' complaining that "control reaches
   end of non-void function" if we use the following functions to
   terminate main() or some other non-void function. */
#define NORETURN __attribute__ ((__noreturn__))
#else
#define NORETURN
#endif

void errExit(const char *format, ...)    NORETURN;
void err_exit(const char *format, ...)   NORETURN;
void errExitEN(int errnum, const char *format, ...) NORETURN;
void fatal(const char *format, ...)      NORETURN;
void usageErr(const char *format, ...)   NORETURN;
void cmdLineErr(const char *format, ...) NORETURN;

#endif
/* ──────────────────────────────────────────────────── */

Error-Diagnostic Functions – Summary
Function errno Used? Terminates? Purpose
errMsg() Yes (current) No Print error message to stderr using current errno
errExit() Yes (current) Yes (exit/abort) Print error and exit; core dumps if EF_DUMPCORE set
err_exit() Yes (current) Yes (_exit) Like errExit() but no flush/exit handlers (for child processes)
errExitEN() Yes (errnum arg) Yes Like errExit() but uses explicit error number (for POSIX threads)
fatal() No Yes For errors from library functions that don’t set errno
usageErr() No Yes Diagnose command-line usage errors; prints “Usage: …”
cmdLineErr() No Yes Diagnose specific command-line argument errors

errMsg() and errExit() – Detailed Explanation

errMsg() prints a message to standard error. Its argument list is the same as printf(), except a terminating newline is automatically appended. It prints the error name (e.g., EPERM) and description from strerror(), followed by the formatted message.

#include "tlpi_hdr.h"

/* errMsg() — prints error and returns (does not terminate) */
int fd = open("file.txt", O_RDONLY);
if (fd == -1)
    errMsg("open %s", "file.txt");
/* Output: ERROR [ENOENT No such file or directory] open file.txt */

/* errExit() — prints error and terminates the program */
int fd2 = open("critical.txt", O_RDONLY);
if (fd2 == -1)
    errExit("open %s", "critical.txt");
/* Output: ERROR [ENOENT No such file or directory] open critical.txt
   Then exits or calls abort() if EF_DUMPCORE is set */
Core Dump Control: If the environment variable EF_DUMPCORE is defined and nonempty, errExit() calls abort() to produce a core dump file. This is useful for post-mortem debugging with a debugger.
err_exit() – For Child Processes After fork()

err_exit() differs from errExit() in two important ways that are specifically relevant when used inside a child process created by fork():

  • It does not flush standard output before printing the error message.
  • It terminates the process by calling _exit() instead of exit(). This means it terminates without flushing stdio buffers or invoking exit handlers registered by the parent process.
/* err_exit() is especially useful in child processes
   to avoid double-flushing the parent's stdio buffers */
pid_t pid = fork();
if (pid == -1)
    errExit("fork");

if (pid == 0) {
    /* Child process */
    int fd = open("file.txt", O_RDONLY);
    if (fd == -1)
        err_exit("child: open");    /* Uses _exit() — safe in child */
}
errExitEN() – For POSIX Threads API

Unlike traditional UNIX system calls which return -1 on error, POSIX threads functions diagnose errors by returning a positive error number as their function result. errExitEN() accepts an explicit error number instead of using the current value of errno.

#include <pthread.h>

/* Inefficient approach — unnecessary errno function call overhead */
errno = pthread_create(&thread, NULL, func, &arg);
if (errno != 0)
    errExit("pthread_create");

/* Efficient approach using errExitEN() */
int s;
s = pthread_create(&thread, NULL, func, &arg);
if (s != 0)
    errExitEN(s, "pthread_create");
/* errExitEN() prints the error message for error number s */
Why This Matters: In threaded programs, errno is defined as a macro that expands into a function call returning a pointer to thread-specific storage. Each use of errno results in a function call. errExitEN() avoids this overhead by accepting the error number directly.

Functions for Parsing Numeric Command-Line Arguments

getInt() and getLong()
Integer Validation Range Checks Base Selection Error Messages

Two frequently-used functions for parsing integer command-line arguments are getInt() and getLong(). The primary advantage over atoi(), atol(), and strtol() is that these functions provide basic validity checking of numeric arguments.

#include "tlpi_hdr.h"

int  getInt(const char *arg, int flags, const char *name);
long getLong(const char *arg, int flags, const char *name);
/* Both return arg converted to numeric form */

/* Parameters:
   arg   — pointer to the string containing the number
   flags — controls base and range (see GN_* constants below)
   name  — name of the argument (included in error messages)
*/

If arg does not contain a valid integer string (only digits and the + and - characters), these functions print an error message and terminate the program.

/* ─────────────── lib/get_num.h ─────────────── */
#ifndef GET_NUM_H
#define GET_NUM_H

#define GN_NONNEG    01    /* Value must be >= 0 */
#define GN_GT_0      02    /* Value must be > 0  */
/* By default, integers are parsed as decimal */
#define GN_ANY_BASE  0100  /* Can use any base — like strtol(3) */
#define GN_BASE_8    0200  /* Value is expressed in octal       */
#define GN_BASE_16   0400  /* Value is expressed in hexadecimal */

long getLong(const char *arg, int flags, const char *name);
int  getInt(const char *arg, int flags, const char *name);

#endif
/* ─────────────────────────────────────────── */
Using getInt() and getLong() – Examples
#include "tlpi_hdr.h"

int main(int argc, char *argv[]) {
    int count;
    long size;

    if (argc < 3)
        usageErr("%s count size\n", argv[0]);

    /* Parse a positive decimal integer */
    count = getInt(argv[1], GN_GT_0, "count");
    /* If argv[1] is not a valid positive integer,
       getInt() prints an error and exits */

    /* Parse a non-negative long integer */
    size = getLong(argv[2], GN_NONNEG, "size");

    /* Parse a hexadecimal integer (e.g., "0xff") */
    int hex_val = getInt(argv[3], GN_BASE_16, "hex-value");

    /* OR combine flags: positive octal integer */
    int oct_pos = getInt(argv[4], GN_GT_0 | GN_BASE_8, "octal-value");

    return 0;
}
Design Note: In some example programs, range checks are deliberately omitted even where they might seem logical. This allows experimentation with invalid arguments to observe the resulting system call errors (e.g., passing a negative semaphore initial value to trigger ERANGE). Real-world applications would impose stronger validation.

The ename Array – Symbolic errno Names

The error-handling functions use an array called ename (defined in ename.c.inc) to print the symbolic name corresponding to a particular error number. This helps programmers quickly match error output to manual page entries.

For example, strerror() returns “No such file or directory” but does not tell you the symbolic constant is ENOENT. The ename array bridges this gap by mapping numeric error codes to their symbolic names.

/* Excerpt from lib/ename.c.inc (Linux 2.6 / x86-32) */
static char *ename[] = {
    /* 0  */ "",
    /* 1  */ "EPERM", "ENOENT", "ESRCH", "EINTR", "EIO", "ENXIO", "E2BIG",
    /* 8  */ "ENOEXEC", "EBADF", "ECHILD", "EAGAIN/EWOULDBLOCK", "ENOMEM",
    /* 13 */ "EACCES", "EFAULT", "ENOTBLK", "EBUSY", "EEXIST", "EXDEV",
    /* 19 */ "ENODEV", "ENOTDIR", "EISDIR", "EINVAL", "ENFILE", "EMFILE",
    /* 25 */ "ENOTTY", "ETXTBSY", "EFBIG", "ENOSPC", "ESPIPE", "EROFS",
    /* 31 */ "EMLINK", "EPIPE", "EDOM", "ERANGE", "EDEADLK/EDEADLOCK",
    /* ... additional entries omitted for brevity ... */
};
#define MAX_ENAME 132

/* Note: Some entries are empty strings — these correspond to unused
   error values. Some entries contain two names separated by a slash
   when two symbolic names map to the same numeric value (e.g.,
   "EAGAIN/EWOULDBLOCK" and "EDEADLK/EDEADLOCK"). */
Architecture-Specific: The content of ename.c.inc is specific to each Linux hardware architecture because errno values vary between architectures. The version shown is for Linux 2.6 on x86-32. A script (lib/Build_ename.sh) is included in the source distribution to generate the correct file for any platform and kernel version.

Summary of Key Points
Common Header Error Functions Argument Parsing
  • Example programs use a shared tlpi_hdr.h header that consolidates common includes and utility definitions.
  • errMsg() prints errors without terminating; errExit() prints and terminates using exit().
  • err_exit() is designed for child processes — it uses _exit() to avoid flushing parent stdio buffers.
  • errExitEN() accepts an explicit error number, making it ideal for use with the POSIX threads API.
  • fatal() handles errors from library functions that do not set errno.
  • usageErr() and cmdLineErr() print usage/argument errors and exit.
  • getInt() and getLong() provide validated integer parsing with range and base control.
  • The ename array maps numeric error codes to their symbolic names for better diagnostics.

Next: Portability – Feature Test Macros and System Data Types

Learn how to write portable system programs using feature test macros and standard data types.

Portability Issues → ← Error Handling

Leave a Reply

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