1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
|
# timeout — Run a Command with a Time Limit
## Overview
`timeout` runs a command and kills it if it exceeds a time limit. It
supports a two-stage kill strategy: first send a configurable signal
(default `SIGTERM`), then optionally send a second kill signal after
a grace period. Uses `prctl(PR_SET_CHILD_SUBREAPER)` to reliably
reap grandchild processes.
**Source**: `timeout/timeout.c` (single file)
**Origin**: BSD/Project Tick
**License**: BSD-3-Clause
## Synopsis
```
timeout [--preserve-status] [--foreground] [-k duration]
[-s signal] [--verbose] duration command [arg ...]
```
## Options
| Flag | Description |
|------|-------------|
| `-s signal` | Signal to send on timeout (default: `SIGTERM`) |
| `-k duration` | Kill signal to send after grace period |
| `--preserve-status` | Exit with the command's status, not 124 |
| `--foreground` | Don't create a new process group |
| `--verbose` | Print diagnostics when sending signals |
## Source Analysis
### Constants
```c
#define EXIT_TIMEOUT 124 /* Command timed out */
#define EXIT_INVALID 125 /* timeout itself failed */
#define EXIT_CMD_ERROR 126 /* Command found but not executable */
#define EXIT_CMD_NOENT 127 /* Command not found */
```
### Data Structures
```c
struct options {
bool foreground; /* --foreground */
bool preserve; /* --preserve-status */
bool verbose; /* --verbose */
bool kill_after_set; /* -k was specified */
int timeout_signal; /* -s signal (default SIGTERM) */
double duration; /* Primary timeout */
double kill_after; /* Grace period before SIGKILL */
const char *command_name;
char **command_argv;
};
struct child_state {
pid_t pid;
int status;
bool exited;
bool signaled;
};
struct runtime_state {
struct child_state child;
bool first_timeout_sent;
bool kill_sent;
};
enum deadline_kind {
DEADLINE_TIMEOUT, /* Primary timeout */
DEADLINE_KILL, /* Kill-after grace period */
};
```
### Functions
| Function | Purpose |
|----------|---------|
| `main()` | Parse options, fork, wait with timers |
| `parse_duration_or_die()` | Parse duration string (fractional seconds + units) |
| `monotonic_seconds()` | Read `CLOCK_MONOTONIC` |
| `enable_subreaper_or_die()` | Call `prctl(PR_SET_CHILD_SUBREAPER)` |
| `send_signal_to_command()` | Send signal to child/process group |
| `arm_second_timer()` | Set up kill-after timer |
| `reap_children()` | Wait for all descendants |
| `child_exec()` | Child process: exec the command |
### Signal Table
`timeout` shares the same signal table as `kill`:
```c
/* Same SIGNAL_ENTRY() macro and signal_entry table */
/* Supports named signals: TERM, KILL, HUP, INT, etc. */
/* Supports SIGRTMIN+n notation */
```
### Duration Parsing
```c
static double
parse_duration_or_die(const char *str)
{
char *end;
double val = strtod(str, &end);
if (end == str || val < 0)
errx(EXIT_INVALID, "invalid duration: %s", str);
/* Apply unit suffix */
switch (*end) {
case '\0':
case 's': break; /* seconds (default) */
case 'm': val *= 60; break;
case 'h': val *= 3600; break;
case 'd': val *= 86400; break;
default:
errx(EXIT_INVALID, "invalid unit: %c", *end);
}
return val;
}
```
### Subreaper
The Linux-specific `prctl(PR_SET_CHILD_SUBREAPER)` ensures that orphaned
grandchild processes are reparented to `timeout` instead of PID 1:
```c
static void
enable_subreaper_or_die(void)
{
if (prctl(PR_SET_CHILD_SUBREAPER, 1) < 0)
err(EXIT_INVALID, "prctl(PR_SET_CHILD_SUBREAPER)");
}
```
### Two-Stage Kill Strategy
```
┌──────────────────────────────────────────────────┐
│ timeout 30 -k 5 -s TERM ./long_running_task │
│ │
│ 1. Fork and exec ./long_running_task │
│ 2. Wait up to 30 seconds │
│ 3. If still running: send SIGTERM │
│ 4. Wait up to 5 more seconds (-k 5) │
│ 5. If still running: send SIGKILL │
│ 6. Reap all children │
└──────────────────────────────────────────────────┘
```
```c
/* Primary timeout handler */
static void
handle_timeout(struct runtime_state *state,
const struct options *opts)
{
if (opts->verbose)
warnx("sending signal %s to command '%s'",
signal_name_for_number(opts->timeout_signal),
opts->command_name);
send_signal_to_command(state, opts->timeout_signal, opts);
state->first_timeout_sent = true;
/* Arm kill-after timer if specified */
if (opts->kill_after_set)
arm_second_timer(opts->kill_after);
}
/* Kill-after timer handler */
static void
handle_kill_after(struct runtime_state *state,
const struct options *opts)
{
if (opts->verbose)
warnx("sending SIGKILL to command '%s'",
opts->command_name);
send_signal_to_command(state, SIGKILL, opts);
state->kill_sent = true;
}
```
### Process Group Management
```c
static void
send_signal_to_command(struct runtime_state *state,
int sig, const struct options *opts)
{
if (opts->foreground) {
/* Send to child only */
kill(state->child.pid, sig);
} else {
/* Send to entire process group */
kill(-state->child.pid, sig);
}
}
static void
child_exec(const struct options *opts)
{
if (!opts->foreground) {
/* Create new process group */
setpgid(0, 0);
}
execvp(opts->command_name, opts->command_argv);
/* exec failed */
int code = (errno == ENOENT) ? EXIT_CMD_NOENT : EXIT_CMD_ERROR;
err(code, "exec '%s'", opts->command_name);
}
```
### Timer Implementation
Uses `timer_create(2)` with `CLOCK_MONOTONIC`:
```c
static void
arm_timer(double seconds)
{
struct itimerspec its = {
.it_value = {
.tv_sec = (time_t)seconds,
.tv_nsec = (long)((seconds - (time_t)seconds) * 1e9),
},
};
timer_t timerid;
struct sigevent sev = {
.sigev_notify = SIGEV_SIGNAL,
.sigev_signo = SIGALRM,
};
timer_create(CLOCK_MONOTONIC, &sev, &timerid);
timer_settime(timerid, 0, &its, NULL);
}
```
## System Calls Used
| Syscall | Purpose |
|---------|---------|
| `fork(2)` | Create child process |
| `execvp(3)` | Execute the command |
| `kill(2)` | Send signal to child/group |
| `waitpid(2)` | Wait for child/grandchild exit |
| `setpgid(2)` | Create new process group |
| `prctl(2)` | `PR_SET_CHILD_SUBREAPER` |
| `timer_create(2)` | POSIX timer for deadline |
| `timer_settime(2)` | Arm the timer |
| `clock_gettime(2)` | `CLOCK_MONOTONIC` for elapsed time |
| `sigaction(2)` | Signal handler setup |
## Examples
```sh
# Basic timeout (30 seconds)
timeout 30 make -j4
# With kill-after grace period
timeout -k 10 60 ./server
# Custom signal
timeout -s HUP 300 ./daemon
# Verbose
timeout --verbose 5 sleep 100
# timeout: sending signal TERM to command 'sleep'
# Preserve exit status
timeout --preserve-status 10 ./test_runner
echo $? # Exit code from test_runner, not 124
# Fractional seconds
timeout 2.5 curl https://example.com
# Foreground (no process group)
timeout --foreground 30 ./interactive_app
```
## Exit Codes
| Code | Meaning |
|------|---------|
| 124 | Command timed out |
| 125 | `timeout` itself failed |
| 126 | Command found but not executable |
| 127 | Command not found |
| other | Command's exit status (or 128+signal if killed) |
|