timeout.c (5388B)
- // utils-std: Collection of commonly available Unix tools
- // SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
- // SPDX-License-Identifier: MPL-2.0
- #define _POSIX_C_SOURCE 200809L
- #define _DEFAULT_SOURCE // For NSIG in sys_signame.h, thanks glibc
- #include "../lib/strtodur.h"
- #include "../lib/sys_signame.h"
- #include <assert.h>
- #include <ctype.h>
- #include <errno.h>
- #include <signal.h> // kill
- #include <spawn.h> // posix_spawn
- #include <stdbool.h>
- #include <stdio.h> // fprintf
- #include <stdlib.h> // exit
- #include <string.h> // strerror
- #include <sys/wait.h>
- #include <time.h> // nanosleep
- #include <unistd.h> // getopt
- #define CMD_EXIT_TIMEOUT 124
- #define CMD_EXIT_FAILURE 125
- #define CMD_EXIT_E_EXEC 126
- #define CMD_EXIT_ENOENT 127
- #define CMD_EXIT_KILL 137
- extern char **environ;
- pid_t child = 0;
- static void
- handle_sigchld(int sig)
- {
- (void)sig;
- int stat_loc = 0;
- waitpid(child, &stat_loc, WNOHANG);
- // Not exit() to avoid running the atexit handlers
- // one of which being gcc --coverage handler which causes a hang
- _Exit(WEXITSTATUS(stat_loc));
- }
- static void
- usage(void)
- {
- fprintf(stderr,
- "Usage: timeout [-fp] [-k duration] [-s SIGNAL] duration command [arguments...]\n");
- }
- int
- main(int argc, char *argv[])
- {
- struct timespec time_kill = {.tv_sec = 0, .tv_nsec = 0};
- int term_sig = SIGTERM;
- const char *term_signame = "SIGTERM";
- bool kill_child = true;
- int cmd_exit_timeout = CMD_EXIT_TIMEOUT;
- char *arg = NULL;
- int c = -1;
- while((c = getopt(argc, argv, ":fk:ps:")) != -1)
- {
- switch(c)
- {
- case 'f':
- kill_child = false;
- break;
- case 'k':
- if(strtodur(argv[0], &time_kill) < 0) return 1;
- if(time_kill.tv_sec == 0 && time_kill.tv_nsec == 0)
- {
- fprintf(stderr, "timeout: error: Got a duration of 0 for SIGKILL timeout.\n");
- return CMD_EXIT_FAILURE;
- }
- break;
- case 'p':
- cmd_exit_timeout = 0;
- break;
- case 's':
- if(isdigit(optarg[0]))
- {
- assert(errno == 0);
- char *endptr = NULL;
- unsigned long optoul = strtoul(optarg, &endptr, 10);
- if(!(errno == 0 && endptr != NULL && *endptr == '\0'))
- {
- fprintf(
- stderr,
- "timeout: error: Mix of digit and non-digit characters passed to -s option '%s'\n",
- endptr);
- return 1;
- }
- if(errno == 0 && optoul == 0) errno = EINVAL;
- if(errno == 0 && optoul >= util_sys_signame_len) errno = ERANGE;
- if(errno != 0)
- {
- fprintf(stderr,
- "timeout: error: Invalid number passed to -s '%s' (got: %lu): %s\n",
- optarg,
- optoul,
- strerror(errno));
- return 1;
- }
- if(util_sys_signame[optoul] == NULL)
- {
- fprintf(stderr, "timeout: error: Unknown signal number %lu\n", optoul);
- return 1;
- }
- term_sig = optoul;
- }
- else
- {
- arg = optarg;
- if(arg[0] == 'S' && arg[1] == 'I' && arg[2] == 'G') arg += 3;
- size_t i = 0;
- for(; i < (util_sys_signame_len); i++)
- {
- if(util_sys_signame[i] == NULL) continue;
- if(strcmp(arg, util_sys_signame[i]) == 0)
- {
- term_sig = i;
- term_signame = optarg;
- break;
- }
- }
- if(i >= util_sys_signame_len)
- {
- fprintf(stderr, "timeout: error: Unknown signal name '%s'\n", optarg);
- return CMD_EXIT_FAILURE;
- }
- }
- break;
- case ':':
- fprintf(stderr, "timeout: error: Missing operand for option: '-%c'\n", optopt);
- usage();
- return 1;
- case '?':
- fprintf(stderr, "timeout: error: Unrecognised option: '-%c'\n", optopt);
- usage();
- return 1;
- }
- }
- assert(errno == 0);
- argv += optind;
- argc -= optind;
- struct timespec term = {.tv_sec = 0, .tv_nsec = 0};
- if(strtodur(argv[0], &term) < 0) return 1;
- if(term.tv_sec == 0 && term.tv_nsec == 0)
- {
- fprintf(stderr, "timeout: error: Got a duration of 0 for (%s) timeout.\n", term_signame);
- return CMD_EXIT_FAILURE;
- }
- argv++;
- argc--;
- assert(errno == 0);
- if(signal(SIGCHLD, handle_sigchld) == SIG_ERR)
- {
- fprintf(
- stderr, "timeout: error: Failed registering handler for SIGCHLD: %s\n", strerror(errno));
- return CMD_EXIT_FAILURE;
- }
- if(posix_spawnp(&child, argv[0], NULL, NULL, argv, environ) != 0)
- {
- fprintf(stderr, "timeout: error: Failed executing '%s': %s\n", argv[0], strerror(errno));
- return CMD_EXIT_E_EXEC;
- }
- // Because PATH-finding typically relies on trial-and-error
- errno = 0;
- if(nanosleep(&term, &term) < 0)
- {
- if(errno != EINTR)
- {
- fprintf(stderr, "timeout: error: Failed sleeping: %s\n", strerror(errno));
- return CMD_EXIT_FAILURE;
- }
- }
- if(!kill_child)
- {
- return cmd_exit_timeout;
- }
- assert(errno == 0);
- if(kill(child, term_sig) != 0)
- {
- fprintf(stderr,
- "timeout: error: Failed sending %s to child %d after timeout: %s\n",
- term_signame,
- child,
- strerror(errno));
- return CMD_EXIT_FAILURE;
- }
- if(time_kill.tv_sec == 0 && time_kill.tv_nsec == 0)
- {
- return cmd_exit_timeout;
- }
- if(nanosleep(&time_kill, &time_kill) < 0)
- {
- if(errno != EINTR)
- {
- fprintf(stderr, "timeout: error: Failed sleeping: %s\n", strerror(errno));
- return CMD_EXIT_FAILURE;
- }
- }
- assert(errno == 0);
- if(kill(child, SIGKILL) != 0)
- {
- fprintf(stderr,
- "timeout: error: Failed sending SIGKILL to child %d after timeout: %s\n",
- child,
- strerror(errno));
- return CMD_EXIT_FAILURE;
- }
- return cmd_exit_timeout;
- }