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