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 <iostream>
10 #include <ostream>
11
12 #include <signal.h>
13 #include <sys/wait.h>
14 #include <sys/types.h>
15
16 #include "include/ceph_assert.h"
17 #include "common/errno.h"
18
19 static void _fork_function_dummy_sighandler(int sig) {}
20
21 // Run a function post-fork, with a timeout. Function can return
22 // int8_t only due to unix exit code limitations. Returns -ETIMEDOUT
23 // if timeout is reached.
24
25 static inline int fork_function(
26 int timeout,
27 std::ostream& errstr,
28 std::function<int8_t(void)> f)
29 {
30 // first fork the forker.
31 pid_t forker_pid = fork();
32 if (forker_pid) {
33 // just wait
34 int status;
35 while (waitpid(forker_pid, &status, 0) == -1) {
36 ceph_assert(errno == EINTR);
37 }
38 if (WIFSIGNALED(status)) {
39 errstr << ": got signal: " << WTERMSIG(status) << "\n";
40 return 128 + WTERMSIG(status);
41 }
42 if (WIFEXITED(status)) {
43 int8_t r = WEXITSTATUS(status);
44 errstr << ": exit status: " << (int)r << "\n";
45 return r;
46 }
47 errstr << ": waitpid: unknown status returned\n";
48 return -1;
49 }
50
51 // we are forker (first child)
52
53 // close all fds
54 int maxfd = sysconf(_SC_OPEN_MAX);
55 if (maxfd == -1)
56 maxfd = 16384;
57 for (int fd = 0; fd <= maxfd; fd++) {
58 if (fd == STDIN_FILENO)
59 continue;
60 if (fd == STDOUT_FILENO)
61 continue;
62 if (fd == STDERR_FILENO)
63 continue;
64 ::close(fd);
65 }
66
67 sigset_t mask, oldmask;
68 int pid;
69
70 // Restore default action for SIGTERM in case the parent process decided
71 // to ignore it.
72 if (signal(SIGTERM, SIG_DFL) == SIG_ERR) {
73 std::cerr << ": signal failed: " << cpp_strerror(errno) << "\n";
74 goto fail_exit;
75 }
76 // Because SIGCHLD is ignored by default, setup dummy handler for it,
77 // so we can mask it.
78 if (signal(SIGCHLD, _fork_function_dummy_sighandler) == SIG_ERR) {
79 std::cerr << ": signal failed: " << cpp_strerror(errno) << "\n";
80 goto fail_exit;
81 }
82 // Setup timeout handler.
83 if (signal(SIGALRM, timeout_sighandler) == SIG_ERR) {
84 std::cerr << ": signal failed: " << cpp_strerror(errno) << "\n";
85 goto fail_exit;
86 }
87 // Block interesting signals.
88 sigemptyset(&mask);
89 sigaddset(&mask, SIGINT);
90 sigaddset(&mask, SIGTERM);
91 sigaddset(&mask, SIGCHLD);
92 sigaddset(&mask, SIGALRM);
93 if (sigprocmask(SIG_SETMASK, &mask, &oldmask) == -1) {
94 std::cerr << ": sigprocmask failed: "
95 << cpp_strerror(errno) << "\n";
96 goto fail_exit;
97 }
98
99 pid = fork();
100
101 if (pid == -1) {
102 std::cerr << ": fork failed: " << cpp_strerror(errno) << "\n";
103 goto fail_exit;
104 }
105
106 if (pid == 0) { // we are second child
107 // Restore old sigmask.
108 if (sigprocmask(SIG_SETMASK, &oldmask, NULL) == -1) {
109 std::cerr << ": sigprocmask failed: "
110 << cpp_strerror(errno) << "\n";
111 goto fail_exit;
112 }
113 (void)setpgid(0, 0); // Become process group leader.
114 int8_t r = f();
115 _exit((uint8_t)r);
116 }
117
118 // Parent
119 (void)alarm(timeout);
120
121 for (;;) {
122 int signo;
123 if (sigwait(&mask, &signo) == -1) {
124 std::cerr << ": sigwait failed: " << cpp_strerror(errno) << "\n";
125 goto fail_exit;
126 }
127 switch (signo) {
128 case SIGCHLD:
129 int status;
130 if (waitpid(pid, &status, WNOHANG) == -1) {
131 std::cerr << ": waitpid failed: " << cpp_strerror(errno) << "\n";
132 goto fail_exit;
133 }
134 if (WIFEXITED(status))
135 _exit(WEXITSTATUS(status));
136 if (WIFSIGNALED(status))
137 _exit(128 + WTERMSIG(status));
138 std::cerr << ": unknown status returned\n";
139 goto fail_exit;
140 case SIGINT:
141 case SIGTERM:
142 // Pass SIGINT and SIGTERM, which are usually used to terminate
143 // a process, to the child.
144 if (::kill(pid, signo) == -1) {
145 std::cerr << ": kill failed: " << cpp_strerror(errno) << "\n";
146 goto fail_exit;
147 }
148 continue;
149 case SIGALRM:
150 std::cerr << ": timed out (" << timeout << " sec)\n";
151 if (::killpg(pid, SIGKILL) == -1) {
152 std::cerr << ": kill failed: " << cpp_strerror(errno) << "\n";
153 goto fail_exit;
154 }
155 _exit(-ETIMEDOUT);
156 default:
157 std::cerr << ": sigwait: invalid signal: " << signo << "\n";
158 goto fail_exit;
159 }
160 }
161 return 0;
162 fail_exit:
163 _exit(EXIT_FAILURE);
164 }
165