Fix some bugs when testing opensds ansible
[stor4nfv.git] / src / ceph / src / common / fork_function.h
1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
3
4 // Run a function in a forked child, with a timeout.
5
6 #pragma once
7
8 #include <functional>
9 #include <signal.h>
10 #include <sys/wait.h>
11 #include <sys/types.h>
12 #include <common/errno.h>
13 #include <ostream>
14 #include "common/errno.h"
15
16 static void _fork_function_dummy_sighandler(int sig) {}
17
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.
21
22 static inline int fork_function(
23   int timeout,
24   std::ostream& errstr,
25   std::function<int8_t(void)> f)
26 {
27   // first fork the forker.
28   pid_t forker_pid = fork();
29   if (forker_pid) {
30     // just wait
31     int status;
32     while (waitpid(forker_pid, &status, 0) == -1) {
33       assert(errno == EINTR);
34     }
35     if (WIFSIGNALED(status)) {
36       errstr << ": got signal: " << WTERMSIG(status) << "\n";
37       return 128 + WTERMSIG(status);
38     }
39     if (WIFEXITED(status)) {
40       int8_t r = WEXITSTATUS(status);
41       errstr << ": exit status: " << (int)r << "\n";
42       return r;
43     }
44     errstr << ": waitpid: unknown status returned\n";
45     return -1;
46   }
47
48   // we are forker (first child)
49
50   // close all fds
51   int maxfd = sysconf(_SC_OPEN_MAX);
52   if (maxfd == -1)
53     maxfd = 16384;
54   for (int fd = 0; fd <= maxfd; fd++) {
55     if (fd == STDIN_FILENO)
56       continue;
57     if (fd == STDOUT_FILENO)
58       continue;
59     if (fd == STDERR_FILENO)
60       continue;
61     ::close(fd);
62   }
63
64   sigset_t mask, oldmask;
65   int pid;
66
67   // Restore default action for SIGTERM in case the parent process decided
68   // to ignore it.
69   if (signal(SIGTERM, SIG_DFL) == SIG_ERR) {
70     std::cerr << ": signal failed: " << cpp_strerror(errno) << "\n";
71     goto fail_exit;
72   }
73   // Because SIGCHLD is ignored by default, setup dummy handler for it,
74   // so we can mask it.
75   if (signal(SIGCHLD, _fork_function_dummy_sighandler) == SIG_ERR) {
76     std::cerr << ": signal failed: " << cpp_strerror(errno) << "\n";
77     goto fail_exit;
78   }
79   // Setup timeout handler.
80   if (signal(SIGALRM, timeout_sighandler) == SIG_ERR) {
81     std::cerr << ": signal failed: " << cpp_strerror(errno) << "\n";
82     goto fail_exit;
83   }
84   // Block interesting signals.
85   sigemptyset(&mask);
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";
93     goto fail_exit;
94   }
95
96   pid = fork();
97
98   if (pid == -1) {
99     std::cerr << ": fork failed: " << cpp_strerror(errno) << "\n";
100     goto fail_exit;
101   }
102
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";
108       goto fail_exit;
109     }
110     (void)setpgid(0, 0); // Become process group leader.
111     int8_t r = f();
112     _exit((uint8_t)r);
113   }
114
115   // Parent
116   (void)alarm(timeout);
117
118   for (;;) {
119     int signo;
120     if (sigwait(&mask, &signo) == -1) {
121       std::cerr << ": sigwait failed: " << cpp_strerror(errno) << "\n";
122       goto fail_exit;
123     }
124     switch (signo) {
125     case SIGCHLD:
126       int status;
127       if (waitpid(pid, &status, WNOHANG) == -1) {
128         std::cerr << ": waitpid failed: " << cpp_strerror(errno) << "\n";
129         goto fail_exit;
130       }
131       if (WIFEXITED(status))
132         _exit(WEXITSTATUS(status));
133       if (WIFSIGNALED(status))
134         _exit(128 + WTERMSIG(status));
135       std::cerr << ": unknown status returned\n";
136       goto fail_exit;
137     case SIGINT:
138     case SIGTERM:
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";
143         goto fail_exit;
144       }
145       continue;
146     case SIGALRM:
147       std::cerr << ": timed out (" << timeout << " sec)\n";
148       if (::killpg(pid, SIGKILL) == -1) {
149         std::cerr << ": kill failed: " << cpp_strerror(errno) << "\n";
150         goto fail_exit;
151       }
152       _exit(-ETIMEDOUT);
153     default:
154       std::cerr << ": sigwait: invalid signal: " << signo << "\n";
155       goto fail_exit;
156     }
157   }
158   return 0;
159 fail_exit:
160   _exit(EXIT_FAILURE);
161 }