]> git.proxmox.com Git - ceph.git/blob - ceph/src/common/fork_function.h
update sources to v12.1.1
[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 <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 }