X-Git-Url: https://gerrit.opnfv.org/gerrit/gitweb?a=blobdiff_plain;f=src%2Fceph%2Fsrc%2Fcommon%2Ffork_function.h;fp=src%2Fceph%2Fsrc%2Fcommon%2Ffork_function.h;h=fa214f788750265c7d1ded102eeec7709564b86b;hb=812ff6ca9fcd3e629e49d4328905f33eee8ca3f5;hp=0000000000000000000000000000000000000000;hpb=15280273faafb77777eab341909a3f495cf248d9;p=stor4nfv.git diff --git a/src/ceph/src/common/fork_function.h b/src/ceph/src/common/fork_function.h new file mode 100644 index 0000000..fa214f7 --- /dev/null +++ b/src/ceph/src/common/fork_function.h @@ -0,0 +1,161 @@ +// -*- 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 +#include +#include +#include +#include +#include +#include "common/errno.h" + +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 f) +{ + // first fork the forker. + pid_t forker_pid = fork(); + if (forker_pid) { + // just wait + int status; + while (waitpid(forker_pid, &status, 0) == -1) { + 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); +}