// timeout.c // OMKOV coreutils timeout // Copyright (C) 2022, Jakob Wakeling // MIT Licence #define VERSION "0.1.0" #include "util/error.h" #include "util/optget.h" #include "util/util.h" #include #include #include #include #include #include #include static struct lop lops[] = { { "help", ARG_NUL, 256 }, { "version", ARG_NUL, 257 }, { NULL, 0, 0 } }; static f64 itime = 0, ktime = 0; static int tosig = SIGTERM; static pid_t pid = 0; static bool tout = false; static f64 time_parse(const char *s); static int timeout(char *av[]); static void signal_install(int sig); static void signal_block(int sig, sigset_t *oset); static void handle_sigalrm(int sig); static void handle_sigchld(int sig); static int signal_send(pid_t pid, int sig); static void timer(f64 duration); static struct timespec f64totimespec(f64 seconds); static void hlp(void); static void ver(void); int main(int ac, char *av[]) { A0 = av[0]; struct opt opt = OPTGET_INIT; opt.str = "k:s:"; opt.lops = lops; for (int o; (o = optget(&opt, av, 1)) != -1;) switch (o) { case 'k': { ktime = time_parse(opt.arg); } break; case 's': { /* TODO */ return 1; } break; case 256: { hlp(); } return 0; case 257: { ver(); } return 0; default: {} return 125; } if ((ac - opt.ind) < 2) { error(125, "missing operand(s)"); } itime = time_parse(av[opt.ind]); return timeout(&av[opt.ind + 1]); } /* Parse a duration string. */ static inline f64 time_parse(const char *s) { char *ep = NULL; errno = 0; f64 time = strtod(s, &ep); if (errno != 0) { error(1, "%s: %s", s, SERR); } if (time < 0) { error(1, "%s: duration cannot be negative", s); } /* Check for an apply any valid suffix */ if (ep != NULL && ep[0] != '\0') { switch (ep[0]) { case 'D': { time *= 86400; } break; case 'h': { time *= 3600; } break; case 'm': { time *= 60; } break; case 's': { time *= 1; } break; default: { error(1, "%s: invalid suffix", s); } break; } if (ep[1] != '\0') { error(1, "%s: invalid suffix", s); } } return time; } static int timeout(char *av[]) { signal_install(tosig); signal(SIGTTIN, SIG_IGN); signal(SIGTTOU, SIG_IGN); switch ((pid = fork())) { case -1: { error(125, "%s", SERR); } case 0: { /* Child */ signal(SIGTTIN, SIG_DFL); signal(SIGTTOU, SIG_DFL); execvp(av[0], av); error((errno = ENOENT) ? 127 : 126, "%s: %s", av[0], SERR); } break; default: { /* Parent */ int status; pid_t wpid; sigset_t sset; /* Unblock and start the timeout timer */ /* TODO unblock */ timer(itime); signal_block(tosig, &sset); /* Wait for the timer to expire, or the child to die */ for (; (wpid = waitpid(pid, &status, WNOHANG)) == 0;) { sigsuspend(&sset); } if (wpid == -1) { error(125, "%s", SERR); } /* If the child process exited normally */ if (WIFEXITED(status)) { status = WEXITSTATUS(status); } /* If the child process exited abnormally */ else if (WIFSIGNALED(status)) { int sig = WTERMSIG(status); status = 128 + sig; } else { error(125, "%s", SERR); } return tout ? 124 : status; } break; } } /* Configure SIGALRM handling. */ static void signal_install(int sig) { struct sigaction sa0 = { .sa_handler = handle_sigalrm, .sa_flags = SA_RESTART }; struct sigaction sa1 = { .sa_handler = handle_sigchld, .sa_flags = SA_RESTART }; sigemptyset(&sa0.sa_mask); sigaction(SIGALRM, &sa0, NULL); /* Alarm signal, from the timeout timer */ sigaction(SIGHUP, &sa0, NULL); /* Hangup signal, i.e. upon tty closing */ sigaction(SIGINT, &sa0, NULL); /* Interrupt signal, i.e. from Ctrl+C */ sigaction(SIGQUIT, &sa0, NULL); /* Quit signal, i.e. from Ctrl+\ */ sigaction(SIGTERM, &sa0, NULL); /* Terminate signal, i.e. upon death */ sigaction(sig, &sa0, NULL); /* User specified signal */ sigemptyset(&sa1.sa_mask); sigaction(SIGCHLD, &sa1, NULL); /* Child signal, i.e. upon child death */ } /* Block all appropriate signals, as per signal_install(). */ static void signal_block(int sig, sigset_t *oset) { sigset_t set; sigemptyset(&set); sigaddset(&set, SIGALRM); sigaddset(&set, SIGHUP); sigaddset(&set, SIGINT); sigaddset(&set, SIGQUIT); sigaddset(&set, SIGTERM); sigaddset(&set, sig); sigaddset(&set, SIGCHLD); if (sigprocmask(SIG_BLOCK, &set, oset) == -1) { warn("%s", SERR); } } /* Handle SIGALRM. */ static void handle_sigalrm(int sig) { if (sig == SIGALRM) { tout = true; sig = tosig; } /* If we are the child, or the child is not yet born, then exit */ if (pid == 0) { exit(128 + sig); } if (ktime) { /* Start a new timeout, after which SIGKILL will be sent */ int ern = errno; tosig = SIGKILL; timer(ktime); ktime = 0; errno = ern; } signal_send(pid, sig); } /* Handle SIGCHLD. */ static void handle_sigchld(int sig) {} /* Send a signal to a process, ignoring signals in the case of a group. */ static int signal_send(pid_t pid, int sig) { if (pid == 0) { signal(sig, SIG_IGN); } return kill(pid, sig); } /* Start the timeout timer, after which we'll recieve a SIGALRM. */ static void timer(f64 duration) { struct timespec ts = f64totimespec(duration); struct itimerspec its = { { 0, 0 }, ts }; timer_t tid; /* Create and set a timer with timer_create and timer_settime */ if (timer_create(CLOCK_REALTIME, NULL, &tid) == 0) { if (timer_settime(tid, 0, &its, NULL) == 0) { return; } else { warn("timer_settime: %s", SERR); timer_delete(tid); } } else { warn("timer_create: %s", SERR); } /* Fallback to alarm() with single second precision upon failure */ if (duration >= (f64)U32_MAX) { alarm(U32_MAX); } else { alarm((u32)duration + ((u32)duration < duration)); } } /* Convert an f64 value to a timespec struct. */ static struct timespec f64totimespec(f64 seconds) { struct timespec ts; seconds += 0.5e-9; ts.tv_sec = (time_t)seconds; ts.tv_nsec = (seconds - ts.tv_sec) * 1.0e+9; return ts; } /* Print help information. */ static void hlp(void) { puts("timeout - Invoke a command with a timelimit\n"); puts("Usage:"); puts(" timeout [-ks] duration command [argument...]\n"); puts("Options:"); puts(" -k duration Send a KILL signal after the specified duration"); puts(" -s signal Send the specified signal instead of SIGINT"); puts(" --help Display help information"); puts(" --version Display version information"); } /* Print version information. */ static void ver(void) { puts("OMKOV cryptutils timeout, version " VERSION); puts("Copyright (C) 2022, Jakob Wakeling"); puts("MIT Licence (https://opensource.org/licenses/MIT)"); }