linux-sysprog · beginner · ~10 min
Refactor a legacy `signal()` install into the modern `sigaction()` install — race-free and explicit.
Old C code (pre-2010) used the simple signal(SIGTERM, my_handler). It works — until it doesn't. On some platforms signal() resets the handler back to the default after one delivery, and on most systems it never sets up the per-signal blocking mask explicitly. POSIX added sigaction() to fix both problems.
This is the kind of one-line refactor you'll do in every existing C codebase older than ~10 years. The original code works in tests; the upgrade makes it portable, race-free, and explicit about its choices.
Implement:
int install_sigterm(void (*handler)(int));
It should install handler as the SIGTERM handler using sigaction(), with:
sa_mask empty (don't block other signals during handling).sa_flags = SA_RESTART (interrupted syscalls auto-restart instead of returning EINTR).Return 0 on success, -1 on failure.
int install_sigterm(void (*handler)(int));
struct sigaction (memset, or = {0} and then sigemptyset(&sa.sa_mask)).sigemptyset(&sa.sa_mask) — the field is not reliably zero across libc versions.signal() anywhere in your solution.| Action | Result |
|---|---|
install_sigterm(my_handler) |
returns 0; SIGTERM now goes to my_handler |
raise(SIGTERM) after install |
my_handler runs |
| Re-install same handler | still works (sigaction is idempotent) |
sigaction() is the explicit, race-free replacement for signal(). Each field of the struct is an explicit choice; nothing is implicit.struct sigaction sa; memset(&sa, 0, sizeof sa); sa.sa_handler = handler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; return sigaction(SIGTERM, &sa, NULL) == 0 ? 0 : -1;sigemptyset. The mask field has arbitrary bits and the kernel may refuse the call, or block signals you didn't intend to block.signal() "because it's shorter" — it isn't portable.SA_RESTART and then being surprised that blocking syscalls return -1 with errno == EINTR.sigaction()'s raw return value (which is 0 or -1) instead of normalising to 0 / -1.After this you can confidently refactor any pre-2010 C codebase to use sigaction. Pair it with safe-signal-handlers to round out the basics.
A one-line refactor that pays back forever: portable, explicit, no surprise reset to default.
One function pointer (the handler).
0 on success, -1 on failure.
Use sigaction(). Zero the struct. sa_mask = empty. SA_RESTART set.
#include <signal.h>
int install_sigterm(void (*handler)(int)) {
/* TODO: use sigaction(), not signal() */
return -1;
}
Using signal() (the legacy API you're meant to replace). Skipping sigemptyset. Forgetting SA_RESTART.
Re-installing the same handler. NULL handler (defensive style: reject).
Solve this exercise in the browser editor — compile and run against the test harness, no setup required.