Chapter 26 — Part 3: Wait Status Macros

Chapter 26 — Part 3: Wait Status Macros
Decoding Child Termination Status with W* Macros
Topic
Status Macros
Level
Intermediate
Examples
3 Programs

What is Wait Status?

When wait() or waitpid() fills in the status integer, it encodes information about how the child changed state. You should never inspect the raw bits directly — always use the standard W* macros from <sys/wait.h>.

Four events are possible:

  • Child called _exit() or exit() — normal termination
  • Child was killed by an unhandled signal
  • Child was stopped by a signal (with WUNTRACED)
  • Child was resumed by SIGCONT (with WCONTINUED)

How the 16 Status Bits are Laid Out (Linux x86)
Bit 15-8 Bit 7 Bit 6-0 Meaning
exit status (0-255) 0 0x00 Normal exit via _exit()
0 core flag signal no. ≠ 0 Killed by signal
stop signal 0 0x7F Stopped by signal
0xFFFF Continued by SIGCONT

Note: Always use macros — never inspect these bits directly. The layout is implementation-specific.

Complete W* Macro Reference
Macro Returns true when… Companion macro
WIFEXITED(status) Child called _exit() / exit() WEXITSTATUS(status) — exit code (0-255)
WIFSIGNALED(status) Child killed by unhandled signal WTERMSIG(status) — signal number
WCOREDUMP(status) — core dump produced?
WIFSTOPPED(status) Child stopped by a signal WSTOPSIG(status) — stop signal number
WIFCONTINUED(status) Stopped child resumed via SIGCONT (no companion; just the flag itself)

Important: Only ONE of the WIFEXITED / WIFSIGNALED / WIFSTOPPED / WIFCONTINUED macros will be true for any given status value.

Example 1: printWaitStatus() — Universal Status Printer

This utility function decodes any wait status and prints a human-readable description. Based on the standard pattern from TLPI.

#define _GNU_SOURCE  /* for strsignal() */
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>

void print_wait_status(const char *msg, int status)
{
    if (msg != NULL)
        printf("%s: ", msg);

    if (WIFEXITED(status)) {
        printf("child exited normally, exit status = %d\n",
               WEXITSTATUS(status));

    } else if (WIFSIGNALED(status)) {
        printf("child killed by signal %d (%s)",
               WTERMSIG(status),
               strsignal(WTERMSIG(status)));

#ifdef WCOREDUMP
        if (WCOREDUMP(status))
            printf(" [core dumped]");
#endif
        printf("\n");

    } else if (WIFSTOPPED(status)) {
        printf("child stopped by signal %d (%s)\n",
               WSTOPSIG(status),
               strsignal(WSTOPSIG(status)));

#ifdef WIFCONTINUED
    } else if (WIFCONTINUED(status)) {
        printf("child continued (SIGCONT received)\n");
#endif

    } else {
        /* Should never happen */
        printf("unknown status: 0x%x\n", (unsigned int)status);
    }
}

/* --- Test harness --- */
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>

int main(int argc, char *argv[])
{
    pid_t child;
    int status;

    child = fork();
    if (child == 0) {
        if (argc > 1) {
            /* exit with given code */
            exit(atoi(argv[1]));
        } else {
            /* wait for signals */
            for (;;) pause();
        }
    }

    /* Parent waits, handling all state changes */
    for (;;) {
        child = waitpid(-1, &status,
                        WUNTRACED
#ifdef WCONTINUED
                        | WCONTINUED
#endif
                       );
        if (child == -1) {
            perror("waitpid");
            break;
        }
        print_wait_status("Status", status);
        if (WIFEXITED(status) || WIFSIGNALED(status))
            break;
    }
    return 0;
}
/*
 * Test runs:
 *   ./a.out 0       -> child exited normally, exit status = 0
 *   ./a.out 42      -> child exited normally, exit status = 42
 *   ./a.out &       -> kill -STOP <child>  -> child stopped
 *                   -> kill -CONT <child>  -> child continued
 *                   -> kill -TERM <child>  -> child killed by signal 15
 */

Example 2: Checking Exit Status in Shell-Style

Mimics how shells check exit status — success (0) vs failure (non-zero) vs signal death.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

/* Returns: 0 if child exited OK, -1 if error/signal, positive = exit code */
int run_child(int exit_code, int send_signal)
{
    pid_t child;
    int status;

    child = fork();
    if (child == 0) {
        if (send_signal)
            raise(send_signal); /* kill ourselves */
        exit(exit_code);
    }

    waitpid(child, &status, 0);

    if (WIFEXITED(status)) {
        int code = WEXITSTATUS(status);
        printf("Process exited with code %d — %s\n",
               code, code == 0 ? "SUCCESS" : "FAILURE");
        return code;
    } else if (WIFSIGNALED(status)) {
        printf("Process killed by signal %d — ABNORMAL TERMINATION\n",
               WTERMSIG(status));
        return -1;
    }
    return -1;
}

int main(void)
{
    printf("--- Test 1: normal success ---\n");
    run_child(0, 0);

    printf("--- Test 2: normal failure ---\n");
    run_child(1, 0);

    printf("--- Test 3: killed by SIGTERM ---\n");
    run_child(0, 15); /* SIGTERM = 15 */

    printf("--- Test 4: exit code 127 (command not found) ---\n");
    run_child(127, 0);

    return 0;
}

Example 3: Core Dump Detection

Shows how to detect if a child produced a core dump file after being killed by a signal.

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/resource.h>

int main(void)
{
    pid_t child;
    int status;
    struct rlimit rl;

    /* Enable core dumps (set limit to unlimited) */
    rl.rlim_cur = RLIM_INFINITY;
    rl.rlim_max = RLIM_INFINITY;
    setrlimit(RLIMIT_CORE, &rl);

    child = fork();
    if (child == 0) {
        /* Cause SIGABRT which by default dumps core */
        abort(); /* sends SIGABRT to self */
    }

    waitpid(child, &status, 0);

    if (WIFSIGNALED(status)) {
        printf("Child killed by signal: %d (%s)\n",
               WTERMSIG(status),
               strsignal(WTERMSIG(status)));

#ifdef WCOREDUMP
        if (WCOREDUMP(status))
            printf("Core dump was produced — check 'core' file\n");
        else
            printf("No core dump (may be disabled by ulimit)\n");
#endif
    }

    return 0;
}
/*
 * Compile and run:
 *   gcc core_demo.c -o core_demo
 *   ./core_demo
 * Output:
 *   Child killed by signal: 6 (Aborted)
 *   Core dump was produced — check 'core' file
 */

Important Detail: Only Bottom 2 Bytes Used

Although status is declared as int (typically 4 bytes), only the bottom 16 bits (2 bytes) are actually used by the kernel to encode child state. The upper 2 bytes are always zero.

This is why you should never directly inspect or compare the raw status integer. Always use the macros — they are portable and correctly mask the relevant bits.

/* BAD — never do this */
if (status == 0) { ... }       /* wrong way */

/* GOOD — always use macros */
if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { ... }

Key Terms:

WIFEXITED WEXITSTATUS WIFSIGNALED WTERMSIG WCOREDUMP WIFSTOPPED WSTOPSIG WIFCONTINUED strsignal

Interview Questions

Q1. Why should you use W* macros instead of directly inspecting the status integer?

The layout of status bits is implementation-specific. Macros provide a portable, standard interface that works across all POSIX-compliant systems.

Q2. What does WEXITSTATUS(status) return if the child was killed by a signal?

It’s undefined. You must first verify WIFEXITED(status) == true before calling WEXITSTATUS().

Q3. Can more than one W*IF* macro be true at the same time for the same status?

No. Only one of WIFEXITED, WIFSIGNALED, WIFSTOPPED, or WIFCONTINUED will be true for any given status value.

Q4. How does the shell set $? based on wait status?

If WIFEXITED: $? = WEXITSTATUS(status). If WIFSIGNALED: $? = 128 + signal_number.

Q5. What is WCOREDUMP and why is it not in SUSv3?

WCOREDUMP returns true if the child produced a core dump. It’s not in SUSv3 because core dump behavior is not standardized — it’s available on most Unix implementations including Linux.

Q6. A child calls exit(256). What does WEXITSTATUS() return?

0. Only the least significant 8 bits of the exit status are available to the parent. 256 & 0xFF == 0.

Q7. What is the difference between termination status and wait status?

Termination status refers only to normal exit or signal death. Wait status is broader — it includes stopped and continued states too.

Q8. Write code to detect if a child exited with an error (non-zero exit code).

waitpid(child, &status, 0);
if (WIFEXITED(status) && WEXITSTATUS(status) != 0)
    fprintf(stderr, "Child failed with exit code %d\n",
            WEXITSTATUS(status));

Next: waitid(), wait3(), wait4() — Advanced Variants

Part 4: waitid() → EmbeddedPathashala Home

Leave a Reply

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