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
Section 3.5.1: Command-Line Options and Arguments
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)
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
/* ─────────────── 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
/* ──────────────────────────────────────────────────── */
| 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() 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 */
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() 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 ofexit(). 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 */
}
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 */
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
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
/* ─────────────────────────────────────────── */
#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;
}
ERANGE). Real-world applications would impose stronger validation.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"). */
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.- Example programs use a shared
tlpi_hdr.hheader that consolidates common includes and utility definitions. errMsg()prints errors without terminating;errExit()prints and terminates usingexit().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 seterrno.usageErr()andcmdLineErr()print usage/argument errors and exit.getInt()andgetLong()provide validated integer parsing with range and base control.- The
enamearray 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.
