| 1 | /* -*-c-*- |
| 2 | * |
| 3 | * Run a command with a timeout. |
| 4 | * |
| 5 | * (c) 2011 Mark Wooding |
| 6 | */ |
| 7 | |
| 8 | /*----- Licensing notice --------------------------------------------------* |
| 9 | * |
| 10 | * This file is part of the Toys utilties collection. |
| 11 | * |
| 12 | * Toys is free software; you can redistribute it and/or modify |
| 13 | * it under the terms of the GNU General Public License as published by |
| 14 | * the Free Software Foundation; either version 2 of the License, or |
| 15 | * (at your option) any later version. |
| 16 | * |
| 17 | * Toys is distributed in the hope that it will be useful, |
| 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 20 | * GNU General Public License for more details. |
| 21 | * |
| 22 | * You should have received a copy of the GNU General Public License |
| 23 | * along with Toys; if not, write to the Free Software Foundation, |
| 24 | * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
| 25 | */ |
| 26 | |
| 27 | /*----- Basic strategy ----------------------------------------------------* |
| 28 | * |
| 29 | * There's an obvious and simple way to make a process stop after a certain |
| 30 | * amount of time: set an alarm. Alarms are inherited, so this will take |
| 31 | * down the whole subprocess tree. Unfortunately, processes use alarms for |
| 32 | * all sorts of things. And this relies on the process not fiddling with its |
| 33 | * @SIGALRM@ handler. |
| 34 | * |
| 35 | * The other possibility is to have a separate process which kills our target |
| 36 | * program when the time's up. In order to do this effectively, we need to |
| 37 | * trap the whole thing in a process group. Then we need to wait until the |
| 38 | * time is up or the process finished. We'll assume that the top-level |
| 39 | * process finishing is sufficient indication that we don't need to hang on |
| 40 | * any longer. |
| 41 | * |
| 42 | * This isn't perfect. It won't catch stragglers if the target process |
| 43 | * creates its own sub-process groups -- so, in particular, nested timeouts |
| 44 | * won't behave in an obvious way. And, possibly most annoyingly, it |
| 45 | * interferes with the way terminal I/O works. I'm sorry: you can't have |
| 46 | * everything. |
| 47 | */ |
| 48 | |
| 49 | /*----- Header files ------------------------------------------------------*/ |
| 50 | |
| 51 | #include <ctype.h> |
| 52 | #include <errno.h> |
| 53 | #include <math.h> |
| 54 | #include <signal.h> |
| 55 | #include <stdio.h> |
| 56 | #include <stdlib.h> |
| 57 | #include <string.h> |
| 58 | |
| 59 | #include <sys/types.h> |
| 60 | #include <sys/time.h> |
| 61 | #include <fcntl.h> |
| 62 | #include <unistd.h> |
| 63 | #include <sys/select.h> |
| 64 | #include <sys/wait.h> |
| 65 | |
| 66 | #include <mLib/fdflags.h> |
| 67 | #include <mLib/macros.h> |
| 68 | #include <mLib/mdwopt.h> |
| 69 | #include <mLib/quis.h> |
| 70 | #include <mLib/report.h> |
| 71 | #include <mLib/sel.h> |
| 72 | #include <mLib/sig.h> |
| 73 | #include <mLib/tv.h> |
| 74 | |
| 75 | /*----- Static variables --------------------------------------------------*/ |
| 76 | |
| 77 | static sel_state sel; |
| 78 | static int state; |
| 79 | |
| 80 | enum { |
| 81 | ST_WAIT, |
| 82 | ST_ABORT, |
| 83 | ST_DONE |
| 84 | }; |
| 85 | |
| 86 | /*----- Argument conversion functions -------------------------------------*/ |
| 87 | |
| 88 | /* --- @namesig@ --- * |
| 89 | * |
| 90 | * Arguments: @const char *p@ = pointer to string |
| 91 | * |
| 92 | * Returns: Signal number, or @-1@ for no match. |
| 93 | * |
| 94 | * Use: Converts a signal name into its number. |
| 95 | */ |
| 96 | |
| 97 | struct namesig { |
| 98 | const char *name; |
| 99 | int sig; |
| 100 | }; |
| 101 | |
| 102 | int cmp_namesig(const void *k, const void *v) |
| 103 | { const struct namesig *ns = v; return (strcmp(k, ns->name)); } |
| 104 | |
| 105 | static int namesig(const char *p) |
| 106 | { |
| 107 | const static struct namesig tab[] = { |
| 108 | /* |
| 109 | ;;; The signal name table is very boring to type. To make life less |
| 110 | ;;; awful, put the signal names in this list and evaluate the code to |
| 111 | ;;; get Emacs to regenerate it. We use @bsearch@ on it, so it's |
| 112 | ;;; important that it be sorted: Emacs does this for us. |
| 113 | |
| 114 | (let ((signals '(HUP INT QUIT ILL ABRT FPE KILL SEGV PIPE ALRM TERM |
| 115 | USR1 USR2 CHLD CONT STOP TSTP TTIN TTOU BUS POLL |
| 116 | PROF SYS TRAP URG VTALRM XCPU XFSZ IOT EMT STKFLT |
| 117 | IO CLD PWR INFO LOST WINCH))) |
| 118 | (save-excursion |
| 119 | (goto-char (point-min)) |
| 120 | (search-forward (concat "***" "BEGIN siglist" "***")) |
| 121 | (beginning-of-line 2) |
| 122 | (delete-region (point) |
| 123 | (progn |
| 124 | (search-forward "***END***") |
| 125 | (beginning-of-line) |
| 126 | (point))) |
| 127 | (dolist (sig (sort (copy-list signals) #'string<)) |
| 128 | (insert (format "#ifdef SIG%s\n { \"%s\", SIG%s },\n#endif\n" |
| 129 | sig sig sig))))) |
| 130 | */ |
| 131 | |
| 132 | /***BEGIN siglist***/ |
| 133 | #ifdef SIGABRT |
| 134 | { "ABRT", SIGABRT }, |
| 135 | #endif |
| 136 | #ifdef SIGALRM |
| 137 | { "ALRM", SIGALRM }, |
| 138 | #endif |
| 139 | #ifdef SIGBUS |
| 140 | { "BUS", SIGBUS }, |
| 141 | #endif |
| 142 | #ifdef SIGCHLD |
| 143 | { "CHLD", SIGCHLD }, |
| 144 | #endif |
| 145 | #ifdef SIGCLD |
| 146 | { "CLD", SIGCLD }, |
| 147 | #endif |
| 148 | #ifdef SIGCONT |
| 149 | { "CONT", SIGCONT }, |
| 150 | #endif |
| 151 | #ifdef SIGEMT |
| 152 | { "EMT", SIGEMT }, |
| 153 | #endif |
| 154 | #ifdef SIGFPE |
| 155 | { "FPE", SIGFPE }, |
| 156 | #endif |
| 157 | #ifdef SIGHUP |
| 158 | { "HUP", SIGHUP }, |
| 159 | #endif |
| 160 | #ifdef SIGILL |
| 161 | { "ILL", SIGILL }, |
| 162 | #endif |
| 163 | #ifdef SIGINFO |
| 164 | { "INFO", SIGINFO }, |
| 165 | #endif |
| 166 | #ifdef SIGINT |
| 167 | { "INT", SIGINT }, |
| 168 | #endif |
| 169 | #ifdef SIGIO |
| 170 | { "IO", SIGIO }, |
| 171 | #endif |
| 172 | #ifdef SIGIOT |
| 173 | { "IOT", SIGIOT }, |
| 174 | #endif |
| 175 | #ifdef SIGKILL |
| 176 | { "KILL", SIGKILL }, |
| 177 | #endif |
| 178 | #ifdef SIGLOST |
| 179 | { "LOST", SIGLOST }, |
| 180 | #endif |
| 181 | #ifdef SIGPIPE |
| 182 | { "PIPE", SIGPIPE }, |
| 183 | #endif |
| 184 | #ifdef SIGPOLL |
| 185 | { "POLL", SIGPOLL }, |
| 186 | #endif |
| 187 | #ifdef SIGPROF |
| 188 | { "PROF", SIGPROF }, |
| 189 | #endif |
| 190 | #ifdef SIGPWR |
| 191 | { "PWR", SIGPWR }, |
| 192 | #endif |
| 193 | #ifdef SIGQUIT |
| 194 | { "QUIT", SIGQUIT }, |
| 195 | #endif |
| 196 | #ifdef SIGSEGV |
| 197 | { "SEGV", SIGSEGV }, |
| 198 | #endif |
| 199 | #ifdef SIGSTKFLT |
| 200 | { "STKFLT", SIGSTKFLT }, |
| 201 | #endif |
| 202 | #ifdef SIGSTOP |
| 203 | { "STOP", SIGSTOP }, |
| 204 | #endif |
| 205 | #ifdef SIGSYS |
| 206 | { "SYS", SIGSYS }, |
| 207 | #endif |
| 208 | #ifdef SIGTERM |
| 209 | { "TERM", SIGTERM }, |
| 210 | #endif |
| 211 | #ifdef SIGTRAP |
| 212 | { "TRAP", SIGTRAP }, |
| 213 | #endif |
| 214 | #ifdef SIGTSTP |
| 215 | { "TSTP", SIGTSTP }, |
| 216 | #endif |
| 217 | #ifdef SIGTTIN |
| 218 | { "TTIN", SIGTTIN }, |
| 219 | #endif |
| 220 | #ifdef SIGTTOU |
| 221 | { "TTOU", SIGTTOU }, |
| 222 | #endif |
| 223 | #ifdef SIGURG |
| 224 | { "URG", SIGURG }, |
| 225 | #endif |
| 226 | #ifdef SIGUSR1 |
| 227 | { "USR1", SIGUSR1 }, |
| 228 | #endif |
| 229 | #ifdef SIGUSR2 |
| 230 | { "USR2", SIGUSR2 }, |
| 231 | #endif |
| 232 | #ifdef SIGVTALRM |
| 233 | { "VTALRM", SIGVTALRM }, |
| 234 | #endif |
| 235 | #ifdef SIGWINCH |
| 236 | { "WINCH", SIGWINCH }, |
| 237 | #endif |
| 238 | #ifdef SIGXCPU |
| 239 | { "XCPU", SIGXCPU }, |
| 240 | #endif |
| 241 | #ifdef SIGXFSZ |
| 242 | { "XFSZ", SIGXFSZ }, |
| 243 | #endif |
| 244 | /***END***/ |
| 245 | }; |
| 246 | |
| 247 | const struct namesig *ns = bsearch(p, tab, N(tab), sizeof(tab[0]), |
| 248 | cmp_namesig); |
| 249 | if (ns) return (ns->sig); |
| 250 | else if (isdigit((unsigned char)*p)) return (atoi(p)); |
| 251 | else return (-1); |
| 252 | } |
| 253 | |
| 254 | /* --- @strtotime@ --- * |
| 255 | * |
| 256 | * Arguments: @const char *p@ = pointer to string |
| 257 | * @struct timeval *tv@ = where to put the result |
| 258 | * |
| 259 | * Returns: --- |
| 260 | * |
| 261 | * Use: Converts a string representation of a duration into an |
| 262 | * internal version. Understands various time units. |
| 263 | */ |
| 264 | |
| 265 | static void strtotime(const char *p, struct timeval *tv) |
| 266 | { |
| 267 | char *q = (/*unconst*/ char *)p; |
| 268 | double t, i, f; |
| 269 | |
| 270 | while (isspace((unsigned char)*q)) q++; |
| 271 | t = strtod(q, &q); |
| 272 | while (isspace((unsigned char)*q)) q++; |
| 273 | switch (*q) { |
| 274 | case 'd': case 'D': t *= 24; |
| 275 | case 'h': case 'H': t *= 60; |
| 276 | case 'm': case 'M': t *= 60; |
| 277 | case 's': case 'S': |
| 278 | q++; |
| 279 | while (isspace((unsigned char)*q)) q++; |
| 280 | } |
| 281 | if (*q) die(253, "bad time value `%s'", p); |
| 282 | f = modf(t, &i); |
| 283 | tv->tv_sec = i; |
| 284 | tv->tv_usec = f * 1000000; |
| 285 | } |
| 286 | |
| 287 | /*----- Help functions ----------------------------------------------------*/ |
| 288 | |
| 289 | static void usage(FILE *fp) |
| 290 | { |
| 291 | pquis(fp, |
| 292 | "Usage: $ [-K] [-b TIME] [-k TIME] [-s SIG] " |
| 293 | "TIME COMMAND [ARGUMENTS ...]\n"); |
| 294 | } |
| 295 | |
| 296 | static void version(FILE *fp) |
| 297 | { pquis(fp, "$ (version " VERSION ")\n"); } |
| 298 | |
| 299 | static void help(FILE *fp) |
| 300 | { |
| 301 | version(fp); fputc('\n', stdout); |
| 302 | usage(fp); |
| 303 | pquis(fp, "\n\ |
| 304 | Run COMMAND, giving it the ARGUMENTS. If it fails to complete within the\n\ |
| 305 | specified number of TIME, kill it. Otherwise exit with the status it\n\ |
| 306 | returns.\n\ |
| 307 | \n\ |
| 308 | A TIME is a possibly fractional number followed by an optional unit\n\ |
| 309 | designator, which may be `s', `m', `h', or `d', for seconds, minutes,\n\ |
| 310 | hours, or days, respectively. The default units are seconds.\n\ |
| 311 | \n\ |
| 312 | Options:\n\ |
| 313 | -h, --help Show this help text.\n\ |
| 314 | -v, --version Show version string.\n\ |
| 315 | -u, --usage Show a terse usage summary.\n\ |
| 316 | \n\ |
| 317 | -b, --bored-after=TIME Wait for TIME after sending SIGKILL.\n\ |
| 318 | -k, --kill-after=TIME Wait for TIME after signal before sending SIGKILL.\n\ |
| 319 | -K, --no-kill Don't send SIGKILL; just give up when bored.\n\ |
| 320 | -s, --signal=SIG Send signal SIG to the command.\n\ |
| 321 | "); |
| 322 | } |
| 323 | |
| 324 | /*----- Timeout handling --------------------------------------------------*/ |
| 325 | |
| 326 | /* --- @timeout@ --- * |
| 327 | * |
| 328 | * The timeout sequencing stuff is complicated, so here's a simple machine to |
| 329 | * make it work. |
| 330 | */ |
| 331 | |
| 332 | enum { |
| 333 | TA_MOAN, /* Report message to user */ |
| 334 | #define TAARG_MOAN s |
| 335 | |
| 336 | TA_KILL, /* Send a signal */ |
| 337 | #define TAARG_KILL i |
| 338 | |
| 339 | TA_GOTO, /* Goto different state */ |
| 340 | #define TAARG_GOTO i |
| 341 | |
| 342 | TA_WAIT, /* Wait for more time */ |
| 343 | #define TAARG_WAIT tv |
| 344 | |
| 345 | TA_STATE, /* Alter the internal state */ |
| 346 | #define TAARG_STATE i |
| 347 | }; |
| 348 | |
| 349 | struct tmact { |
| 350 | unsigned act; |
| 351 | union { |
| 352 | const char *s; /* String parameter */ |
| 353 | int i; /* Integer parameter */ |
| 354 | struct timeval tv; /* Time parameter */ |
| 355 | } u; |
| 356 | }; |
| 357 | |
| 358 | struct timeout { |
| 359 | sel_timer t; /* Timer intrusion */ |
| 360 | struct tmact *ta; /* Instruction vector */ |
| 361 | int ip; /* Instruction pointer */ |
| 362 | pid_t kid; |
| 363 | }; |
| 364 | |
| 365 | static void timeout(struct timeval *now, void *p) |
| 366 | { |
| 367 | struct timeout *t = p; |
| 368 | struct tmact *ta; |
| 369 | struct timeval tv; |
| 370 | |
| 371 | for (;;) { |
| 372 | ta = &t->ta[t->ip++]; |
| 373 | switch (ta->act) { |
| 374 | case TA_MOAN: |
| 375 | moan("%s", ta->u.s); |
| 376 | break; |
| 377 | case TA_KILL: |
| 378 | kill(-t->kid, ta->u.i); |
| 379 | break; |
| 380 | case TA_GOTO: |
| 381 | t->ip = ta->u.i; |
| 382 | break; |
| 383 | case TA_STATE: |
| 384 | state = ta->u.i; |
| 385 | return; |
| 386 | case TA_WAIT: |
| 387 | TV_ADD(&tv, now, &ta->u.tv); |
| 388 | sel_addtimer(&sel, &t->t, &tv, timeout, t); |
| 389 | return; |
| 390 | default: |
| 391 | moan("unexpected tmact %u", ta->act); |
| 392 | abort(); |
| 393 | } |
| 394 | } |
| 395 | } |
| 396 | |
| 397 | /*----- Signal handling ---------------------------------------------------*/ |
| 398 | |
| 399 | /* --- @sigchld@ --- * |
| 400 | * |
| 401 | * If it's our child that died, fetch its exit status and stop. |
| 402 | */ |
| 403 | |
| 404 | struct sigchld { |
| 405 | pid_t kid; |
| 406 | int rc; |
| 407 | }; |
| 408 | |
| 409 | static void sigchld(int sig, void *p) |
| 410 | { |
| 411 | struct sigchld *s = p; |
| 412 | int rc; |
| 413 | pid_t pid; |
| 414 | |
| 415 | if ((pid = waitpid(s->kid, &rc, WNOHANG)) < 0) |
| 416 | die(254, "waitpid failed: %s", strerror(errno)); |
| 417 | if (pid == s->kid) { |
| 418 | s->rc = rc; |
| 419 | state = ST_DONE; |
| 420 | } |
| 421 | } |
| 422 | |
| 423 | /* --- @sigpropagate@ --- * |
| 424 | * |
| 425 | * Propagate various signals to the child process group and then maybe act on |
| 426 | * them. |
| 427 | */ |
| 428 | |
| 429 | static void sigpropagate(int sig, void *p) |
| 430 | { |
| 431 | struct sigchld *s = p; |
| 432 | |
| 433 | kill(-s->kid, sig); |
| 434 | switch (sig) { |
| 435 | case SIGTSTP: raise(SIGSTOP); break; |
| 436 | } |
| 437 | } |
| 438 | |
| 439 | #define PROPAGATE_SIGNALS(_) \ |
| 440 | _(TSTP) _(CONT) _(INT) _(HUP) _(QUIT) |
| 441 | |
| 442 | /*----- Main program ------------------------------------------------------*/ |
| 443 | |
| 444 | /* --- @main@ --- */ |
| 445 | |
| 446 | int main(int argc, char *const argv[]) |
| 447 | { |
| 448 | pid_t kid; |
| 449 | struct timeout to; |
| 450 | struct timeval now; |
| 451 | |
| 452 | #define PAIR(x, y) { x, y } |
| 453 | #define TACODE(I) \ |
| 454 | I(sigwait, WAIT, PAIR(0, 0)) \ |
| 455 | I(_a, MOAN, "timed out: killing child process") \ |
| 456 | I(sig, KILL, SIGTERM) \ |
| 457 | I(killwait, WAIT, PAIR(5, 0)) \ |
| 458 | I(_b, MOAN, "child hasn't responded: killing harder") \ |
| 459 | I(_c, KILL, SIGKILL) \ |
| 460 | I(boredwait, WAIT, PAIR(5, 0)) \ |
| 461 | I(_d, MOAN, "child still undead: giving up") \ |
| 462 | I(_e, STATE, ST_ABORT) |
| 463 | |
| 464 | enum { |
| 465 | #define TALBL(label, op, arg) taoff_##label, |
| 466 | TACODE(TALBL) |
| 467 | #undef TALBL |
| 468 | taoff_end |
| 469 | }; |
| 470 | |
| 471 | static struct tmact ta[] = { |
| 472 | #define TAASM(label, op, arg) { TA_##op, { .TAARG_##op = arg } }, |
| 473 | TACODE(TAASM) |
| 474 | #undef TAASM |
| 475 | }; |
| 476 | |
| 477 | struct sigchld sc; |
| 478 | sig sig_CHLD; |
| 479 | #define DEFSIG(tag) sig sig_##tag; |
| 480 | PROPAGATE_SIGNALS(DEFSIG) |
| 481 | #undef DEFSIG |
| 482 | unsigned f = 0; |
| 483 | #define F_BOGUS 1u |
| 484 | |
| 485 | /* --- Option parsing --- */ |
| 486 | |
| 487 | ego(argv[0]); |
| 488 | |
| 489 | for (;;) { |
| 490 | static const struct option opts[] = { |
| 491 | { "help", 0, 0, 'h' }, |
| 492 | { "version", 0, 0, 'v' }, |
| 493 | { "usage", 0, 0, 'u' }, |
| 494 | { "no-kill", 0, 0, 'K' }, |
| 495 | { "kill-after", OPTF_ARGREQ, 0, 'k' }, |
| 496 | { "bored-after", OPTF_ARGREQ, 0, 'b' }, |
| 497 | { "signal", OPTF_ARGREQ, 0, 's' }, |
| 498 | { 0, 0, 0, 0 } |
| 499 | }; |
| 500 | |
| 501 | int i = mdwopt(argc, argv, "+hvuKb:k:s:", opts, 0, 0, 0); |
| 502 | if (i < 0) break; |
| 503 | switch (i) { |
| 504 | case 'h': help(stdout); exit(0); |
| 505 | case 'v': version(stdout); exit(0); |
| 506 | case 'u': usage(stdout); exit(0); |
| 507 | case 'b': |
| 508 | ta[taoff_boredwait].act = TA_WAIT; |
| 509 | strtotime(optarg, &ta[taoff_boredwait].u.tv); |
| 510 | break; |
| 511 | case 'k': |
| 512 | ta[taoff_killwait].act = TA_WAIT; |
| 513 | strtotime(optarg, &ta[taoff_killwait].u.tv); |
| 514 | break; |
| 515 | case 'K': |
| 516 | ta[taoff_killwait].act = TA_GOTO; |
| 517 | ta[taoff_killwait].u.i = taoff_boredwait; |
| 518 | break; |
| 519 | case 's': |
| 520 | if ((ta[taoff_sig].u.i = namesig(optarg)) < 0) |
| 521 | die(253, "bad signal spec `%s'", optarg); |
| 522 | break; |
| 523 | default: f |= F_BOGUS; break; |
| 524 | } |
| 525 | } |
| 526 | argc -= optind; argv += optind; |
| 527 | if ((f & F_BOGUS) || argc < 2) { usage(stderr); exit(253); } |
| 528 | strtotime(argv[0], &ta[taoff_sigwait].u.tv); |
| 529 | |
| 530 | /* --- Get things set up --- */ |
| 531 | |
| 532 | state = ST_WAIT; |
| 533 | sel_init(&sel); |
| 534 | |
| 535 | /* --- Set up signal handling --- * |
| 536 | * |
| 537 | * Doing anything with asynchronous signals is a mug's game, so we bring |
| 538 | * them in-band. Do this before starting the child process, because |
| 539 | * otherwise we might miss an immediate @SIGCHLD@. |
| 540 | */ |
| 541 | |
| 542 | sig_init(&sel); |
| 543 | sc.rc = 0; |
| 544 | sig_add(&sig_CHLD, SIGCHLD, sigchld, &sc); |
| 545 | #define ADDSIG(tag) sig_add(&sig_##tag, SIG##tag, sigpropagate, &sc); |
| 546 | PROPAGATE_SIGNALS(ADDSIG) |
| 547 | #undef ADDSIG |
| 548 | |
| 549 | /* --- Now start the child process --- */ |
| 550 | |
| 551 | if ((kid = fork()) < 0) die(2, "fork failed: %s", strerror(errno)); |
| 552 | if (!kid) { |
| 553 | setpgid(0, 0); |
| 554 | execvp(argv[1], argv + 1); |
| 555 | die(252, "exec(%s) failed: %s", argv[1], strerror(errno)); |
| 556 | } |
| 557 | sc.kid = kid; |
| 558 | |
| 559 | /* --- Set up the timer --- */ |
| 560 | |
| 561 | gettimeofday(&now, 0); |
| 562 | to.kid = kid; |
| 563 | to.ta = ta; |
| 564 | to.ip = 0; |
| 565 | timeout(&now, &to); |
| 566 | |
| 567 | /* --- Main @select@ loop */ |
| 568 | |
| 569 | while (state == ST_WAIT) { |
| 570 | if (sel_select(&sel)) { |
| 571 | if (errno == EINTR) continue; |
| 572 | die(254, "select failed: %s", strerror(errno)); |
| 573 | } |
| 574 | } |
| 575 | |
| 576 | /* --- Check and translate the exit code --- */ |
| 577 | |
| 578 | switch (state) { |
| 579 | case ST_ABORT: |
| 580 | exit(251); |
| 581 | case ST_DONE: |
| 582 | if (WIFEXITED(sc.rc)) |
| 583 | exit(WEXITSTATUS(sc.rc)); |
| 584 | else if (WIFSIGNALED(sc.rc)) |
| 585 | exit(128 | WTERMSIG(sc.rc)); |
| 586 | else |
| 587 | exit(128); |
| 588 | default: |
| 589 | moan("FATAL: unexpected state %d", state); |
| 590 | abort(); |
| 591 | } |
| 592 | } |
| 593 | |
| 594 | /*----- That's all, folks -------------------------------------------------*/ |