summaryrefslogtreecommitdiffstats
path: root/src/common/fork_function.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/common/fork_function.h')
-rw-r--r--src/common/fork_function.h176
1 files changed, 176 insertions, 0 deletions
diff --git a/src/common/fork_function.h b/src/common/fork_function.h
new file mode 100644
index 000000000..3a4f2f29c
--- /dev/null
+++ b/src/common/fork_function.h
@@ -0,0 +1,176 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+// Run a function in a forked child, with a timeout.
+
+#pragma once
+
+#include <functional>
+#include <iostream>
+#include <ostream>
+
+#include <signal.h>
+#ifndef _WIN32
+#include <sys/wait.h>
+#endif
+#include <sys/types.h>
+
+#include "include/ceph_assert.h"
+#include "common/errno.h"
+
+#ifndef _WIN32
+static void _fork_function_dummy_sighandler(int sig) {}
+
+// Run a function post-fork, with a timeout. Function can return
+// int8_t only due to unix exit code limitations. Returns -ETIMEDOUT
+// if timeout is reached.
+static inline int fork_function(
+ int timeout,
+ std::ostream& errstr,
+ std::function<int8_t(void)> f)
+{
+ // first fork the forker.
+ pid_t forker_pid = fork();
+ if (forker_pid) {
+ // just wait
+ int status;
+ while (waitpid(forker_pid, &status, 0) == -1) {
+ ceph_assert(errno == EINTR);
+ }
+ if (WIFSIGNALED(status)) {
+ errstr << ": got signal: " << WTERMSIG(status) << "\n";
+ return 128 + WTERMSIG(status);
+ }
+ if (WIFEXITED(status)) {
+ int8_t r = WEXITSTATUS(status);
+ errstr << ": exit status: " << (int)r << "\n";
+ return r;
+ }
+ errstr << ": waitpid: unknown status returned\n";
+ return -1;
+ }
+
+ // we are forker (first child)
+
+ // close all fds
+ int maxfd = sysconf(_SC_OPEN_MAX);
+ if (maxfd == -1)
+ maxfd = 16384;
+ for (int fd = 0; fd <= maxfd; fd++) {
+ if (fd == STDIN_FILENO)
+ continue;
+ if (fd == STDOUT_FILENO)
+ continue;
+ if (fd == STDERR_FILENO)
+ continue;
+ ::close(fd);
+ }
+
+ sigset_t mask, oldmask;
+ int pid;
+
+ // Restore default action for SIGTERM in case the parent process decided
+ // to ignore it.
+ if (signal(SIGTERM, SIG_DFL) == SIG_ERR) {
+ std::cerr << ": signal failed: " << cpp_strerror(errno) << "\n";
+ goto fail_exit;
+ }
+ // Because SIGCHLD is ignored by default, setup dummy handler for it,
+ // so we can mask it.
+ if (signal(SIGCHLD, _fork_function_dummy_sighandler) == SIG_ERR) {
+ std::cerr << ": signal failed: " << cpp_strerror(errno) << "\n";
+ goto fail_exit;
+ }
+ // Setup timeout handler.
+ if (signal(SIGALRM, timeout_sighandler) == SIG_ERR) {
+ std::cerr << ": signal failed: " << cpp_strerror(errno) << "\n";
+ goto fail_exit;
+ }
+ // Block interesting signals.
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGINT);
+ sigaddset(&mask, SIGTERM);
+ sigaddset(&mask, SIGCHLD);
+ sigaddset(&mask, SIGALRM);
+ if (sigprocmask(SIG_SETMASK, &mask, &oldmask) == -1) {
+ std::cerr << ": sigprocmask failed: "
+ << cpp_strerror(errno) << "\n";
+ goto fail_exit;
+ }
+
+ pid = fork();
+
+ if (pid == -1) {
+ std::cerr << ": fork failed: " << cpp_strerror(errno) << "\n";
+ goto fail_exit;
+ }
+
+ if (pid == 0) { // we are second child
+ // Restore old sigmask.
+ if (sigprocmask(SIG_SETMASK, &oldmask, NULL) == -1) {
+ std::cerr << ": sigprocmask failed: "
+ << cpp_strerror(errno) << "\n";
+ goto fail_exit;
+ }
+ (void)setpgid(0, 0); // Become process group leader.
+ int8_t r = f();
+ _exit((uint8_t)r);
+ }
+
+ // Parent
+ (void)alarm(timeout);
+
+ for (;;) {
+ int signo;
+ if (sigwait(&mask, &signo) == -1) {
+ std::cerr << ": sigwait failed: " << cpp_strerror(errno) << "\n";
+ goto fail_exit;
+ }
+ switch (signo) {
+ case SIGCHLD:
+ int status;
+ if (waitpid(pid, &status, WNOHANG) == -1) {
+ std::cerr << ": waitpid failed: " << cpp_strerror(errno) << "\n";
+ goto fail_exit;
+ }
+ if (WIFEXITED(status))
+ _exit(WEXITSTATUS(status));
+ if (WIFSIGNALED(status))
+ _exit(128 + WTERMSIG(status));
+ std::cerr << ": unknown status returned\n";
+ goto fail_exit;
+ case SIGINT:
+ case SIGTERM:
+ // Pass SIGINT and SIGTERM, which are usually used to terminate
+ // a process, to the child.
+ if (::kill(pid, signo) == -1) {
+ std::cerr << ": kill failed: " << cpp_strerror(errno) << "\n";
+ goto fail_exit;
+ }
+ continue;
+ case SIGALRM:
+ std::cerr << ": timed out (" << timeout << " sec)\n";
+ if (::killpg(pid, SIGKILL) == -1) {
+ std::cerr << ": kill failed: " << cpp_strerror(errno) << "\n";
+ goto fail_exit;
+ }
+ _exit(-ETIMEDOUT);
+ default:
+ std::cerr << ": sigwait: invalid signal: " << signo << "\n";
+ goto fail_exit;
+ }
+ }
+ return 0;
+fail_exit:
+ _exit(EXIT_FAILURE);
+}
+#else
+static inline int fork_function(
+ int timeout,
+ std::ostream& errstr,
+ std::function<int8_t(void)> f)
+{
+ errstr << "Forking is not available on Windows.\n";
+ return -1;
+}
+#endif