1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
4 // Run a function in a forked child, with a timeout.
11 #include <sys/types.h>
12 #include <common/errno.h>
14 #include "common/errno.h"
16 static void _fork_function_dummy_sighandler(int sig) {}
18 // Run a function post-fork, with a timeout. Function can return
19 // int8_t only due to unix exit code limitations. Returns -ETIMEDOUT
20 // if timeout is reached.
22 static inline int fork_function(
25 std::function<int8_t(void)> f)
27 // first fork the forker.
28 pid_t forker_pid = fork();
32 while (waitpid(forker_pid, &status, 0) == -1) {
33 assert(errno == EINTR);
35 if (WIFSIGNALED(status)) {
36 errstr << ": got signal: " << WTERMSIG(status) << "\n";
37 return 128 + WTERMSIG(status);
39 if (WIFEXITED(status)) {
40 int8_t r = WEXITSTATUS(status);
41 errstr << ": exit status: " << (int)r << "\n";
44 errstr << ": waitpid: unknown status returned\n";
48 // we are forker (first child)
51 int maxfd = sysconf(_SC_OPEN_MAX);
54 for (int fd = 0; fd <= maxfd; fd++) {
55 if (fd == STDIN_FILENO)
57 if (fd == STDOUT_FILENO)
59 if (fd == STDERR_FILENO)
64 sigset_t mask, oldmask;
67 // Restore default action for SIGTERM in case the parent process decided
69 if (signal(SIGTERM, SIG_DFL) == SIG_ERR) {
70 std::cerr << ": signal failed: " << cpp_strerror(errno) << "\n";
73 // Because SIGCHLD is ignored by default, setup dummy handler for it,
75 if (signal(SIGCHLD, _fork_function_dummy_sighandler) == SIG_ERR) {
76 std::cerr << ": signal failed: " << cpp_strerror(errno) << "\n";
79 // Setup timeout handler.
80 if (signal(SIGALRM, timeout_sighandler) == SIG_ERR) {
81 std::cerr << ": signal failed: " << cpp_strerror(errno) << "\n";
84 // Block interesting signals.
86 sigaddset(&mask, SIGINT);
87 sigaddset(&mask, SIGTERM);
88 sigaddset(&mask, SIGCHLD);
89 sigaddset(&mask, SIGALRM);
90 if (sigprocmask(SIG_SETMASK, &mask, &oldmask) == -1) {
91 std::cerr << ": sigprocmask failed: "
92 << cpp_strerror(errno) << "\n";
99 std::cerr << ": fork failed: " << cpp_strerror(errno) << "\n";
103 if (pid == 0) { // we are second child
104 // Restore old sigmask.
105 if (sigprocmask(SIG_SETMASK, &oldmask, NULL) == -1) {
106 std::cerr << ": sigprocmask failed: "
107 << cpp_strerror(errno) << "\n";
110 (void)setpgid(0, 0); // Become process group leader.
116 (void)alarm(timeout);
120 if (sigwait(&mask, &signo) == -1) {
121 std::cerr << ": sigwait failed: " << cpp_strerror(errno) << "\n";
127 if (waitpid(pid, &status, WNOHANG) == -1) {
128 std::cerr << ": waitpid failed: " << cpp_strerror(errno) << "\n";
131 if (WIFEXITED(status))
132 _exit(WEXITSTATUS(status));
133 if (WIFSIGNALED(status))
134 _exit(128 + WTERMSIG(status));
135 std::cerr << ": unknown status returned\n";
139 // Pass SIGINT and SIGTERM, which are usually used to terminate
140 // a process, to the child.
141 if (::kill(pid, signo) == -1) {
142 std::cerr << ": kill failed: " << cpp_strerror(errno) << "\n";
147 std::cerr << ": timed out (" << timeout << " sec)\n";
148 if (::killpg(pid, SIGKILL) == -1) {
149 std::cerr << ": kill failed: " << cpp_strerror(errno) << "\n";
154 std::cerr << ": sigwait: invalid signal: " << signo << "\n";