/tmp/bitcoin/src/ipc/libmultiprocess/src/mp/util.cpp
Line | Count | Source |
1 | | // Copyright (c) The Bitcoin Core developers |
2 | | // Distributed under the MIT software license, see the accompanying |
3 | | // file COPYING or http://www.opensource.org/licenses/mit-license.php. |
4 | | |
5 | | #include <mp/config.h> |
6 | | #include <mp/util.h> |
7 | | |
8 | | #include <cerrno> |
9 | | #include <cstdio> |
10 | | #include <filesystem> |
11 | | #include <iostream> |
12 | | #include <kj/common.h> |
13 | | #include <kj/string-tree.h> |
14 | | #include <pthread.h> |
15 | | #include <sstream> |
16 | | #include <string> |
17 | | #include <sys/types.h> |
18 | | #include <sys/resource.h> |
19 | | #include <sys/socket.h> |
20 | | #include <sys/wait.h> |
21 | | #include <system_error> |
22 | | #include <thread> // NOLINT(misc-include-cleaner) // IWYU pragma: keep |
23 | | #include <unistd.h> |
24 | | #include <utility> |
25 | | #include <vector> |
26 | | |
27 | | #ifdef __linux__ |
28 | | #include <sys/syscall.h> |
29 | | #endif |
30 | | |
31 | | #ifdef HAVE_PTHREAD_GETTHREADID_NP |
32 | | #include <pthread_np.h> |
33 | | #endif // HAVE_PTHREAD_GETTHREADID_NP |
34 | | |
35 | | namespace fs = std::filesystem; |
36 | | |
37 | | namespace mp { |
38 | | namespace { |
39 | | |
40 | | std::vector<char*> MakeArgv(const std::vector<std::string>& args) |
41 | 0 | { |
42 | 0 | std::vector<char*> argv; |
43 | 0 | argv.reserve(args.size() + 1); |
44 | 0 | for (const auto& arg : args) { |
45 | 0 | argv.push_back(const_cast<char*>(arg.c_str())); |
46 | 0 | } |
47 | 0 | argv.push_back(nullptr); |
48 | 0 | return argv; |
49 | 0 | } |
50 | | |
51 | | //! Return highest possible file descriptor. |
52 | | size_t MaxFd() |
53 | 0 | { |
54 | 0 | struct rlimit nofile; |
55 | 0 | if (getrlimit(RLIMIT_NOFILE, &nofile) == 0) { |
56 | 0 | return nofile.rlim_cur - 1; |
57 | 0 | } else { |
58 | 0 | return 1023; |
59 | 0 | } |
60 | 0 | } |
61 | | |
62 | | } // namespace |
63 | | |
64 | | std::string ThreadName(const char* exe_name) |
65 | 242 | { |
66 | 242 | char thread_name[16] = {0}; |
67 | 242 | #ifdef HAVE_PTHREAD_GETNAME_NP |
68 | 242 | pthread_getname_np(pthread_self(), thread_name, sizeof(thread_name)); |
69 | 242 | #endif // HAVE_PTHREAD_GETNAME_NP |
70 | | |
71 | 242 | std::ostringstream buffer; |
72 | 242 | buffer << (exe_name ? exe_name : "") << "-" << getpid() << "/"; |
73 | | |
74 | 242 | if (thread_name[0] != '\0') { |
75 | 242 | buffer << thread_name << "-"; |
76 | 242 | } |
77 | | |
78 | | // Prefer platform specific thread ids over the standard C++11 ones because |
79 | | // the former are shorter and are the same as what gdb prints "LWP ...". |
80 | 242 | #ifdef __linux__ |
81 | 242 | buffer << syscall(SYS_gettid); |
82 | | #elif defined(HAVE_PTHREAD_THREADID_NP) |
83 | | uint64_t tid = 0; |
84 | | pthread_threadid_np(nullptr, &tid); |
85 | | buffer << tid; |
86 | | #elif defined(HAVE_PTHREAD_GETTHREADID_NP) |
87 | | buffer << pthread_getthreadid_np(); |
88 | | #else |
89 | | buffer << std::this_thread::get_id(); |
90 | | #endif |
91 | | |
92 | 242 | return std::move(buffer).str(); |
93 | 242 | } |
94 | | |
95 | | std::string LogEscape(const kj::StringTree& string, size_t max_size) |
96 | 144 | { |
97 | 144 | std::string result; |
98 | 1.01k | string.visit([&](const kj::ArrayPtr<const char>& piece) { |
99 | 1.01k | if (result.size() > max_size) return; |
100 | 8.09k | for (const char c : piece) { |
101 | 8.09k | if (c == '\\') { |
102 | 430 | result.append("\\\\"); |
103 | 7.66k | } else if (c < 0x20 || c > 0x7e) { |
104 | 0 | char escape[4]; |
105 | 0 | snprintf(escape, sizeof(escape), "\\%02x", static_cast<unsigned char>(c)); |
106 | 0 | result.append(escape); |
107 | 7.66k | } else { |
108 | 7.66k | result.push_back(c); |
109 | 7.66k | } |
110 | 8.09k | if (result.size() > max_size) { |
111 | 8 | result += "..."; |
112 | 8 | break; |
113 | 8 | } |
114 | 8.09k | } |
115 | 986 | }); |
116 | 144 | return result; |
117 | 144 | } |
118 | | |
119 | | int SpawnProcess(int& pid, FdToArgsFn&& fd_to_args) |
120 | 0 | { |
121 | 0 | int fds[2]; |
122 | 0 | if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) != 0) { |
123 | 0 | throw std::system_error(errno, std::system_category(), "socketpair"); |
124 | 0 | } |
125 | | |
126 | | // Evaluate the callback and build the argv array before forking. |
127 | | // |
128 | | // The parent process may be multi-threaded and holding internal library |
129 | | // locks at fork time. In that case, running code that allocates memory or |
130 | | // takes locks in the child between fork() and exec() can deadlock |
131 | | // indefinitely. Precomputing arguments in the parent avoids this. |
132 | 0 | const std::vector<std::string> args{fd_to_args(fds[0])}; |
133 | 0 | const std::vector<char*> argv{MakeArgv(args)}; |
134 | |
|
135 | 0 | pid = fork(); |
136 | 0 | if (pid == -1) { |
137 | 0 | throw std::system_error(errno, std::system_category(), "fork"); |
138 | 0 | } |
139 | | // Parent process closes the descriptor for socket 0, child closes the |
140 | | // descriptor for socket 1. On failure, the parent throws, but the child |
141 | | // must _exit(126) (post-fork child must not throw). |
142 | 0 | if (close(fds[pid ? 0 : 1]) != 0) { |
143 | 0 | if (pid) { |
144 | 0 | (void)close(fds[1]); |
145 | 0 | throw std::system_error(errno, std::system_category(), "close"); |
146 | 0 | } |
147 | 0 | static constexpr char msg[] = "SpawnProcess(child): close(fds[1]) failed\n"; |
148 | 0 | const ssize_t writeResult = ::write(STDERR_FILENO, msg, sizeof(msg) - 1); |
149 | 0 | (void)writeResult; |
150 | 0 | _exit(126); |
151 | 0 | } |
152 | | |
153 | 0 | if (!pid) { |
154 | | // Child process must close all potentially open descriptors, except |
155 | | // socket 0. Do not throw, allocate, or do non-fork-safe work here. |
156 | 0 | const int maxFd = MaxFd(); |
157 | 0 | for (int fd = 3; fd < maxFd; ++fd) { |
158 | 0 | if (fd != fds[0]) { |
159 | 0 | close(fd); |
160 | 0 | } |
161 | 0 | } |
162 | |
|
163 | 0 | execvp(argv[0], argv.data()); |
164 | | // NOTE: perror() is not async-signal-safe; calling it here in a |
165 | | // post-fork child may deadlock in multithreaded parents. |
166 | | // TODO: Report errors to the parent via a pipe (e.g. write errno) |
167 | | // so callers can get diagnostics without relying on perror(). |
168 | 0 | perror("execvp failed"); |
169 | 0 | _exit(127); |
170 | 0 | } |
171 | 0 | return fds[1]; |
172 | 0 | } |
173 | | |
174 | | void ExecProcess(const std::vector<std::string>& args) |
175 | 0 | { |
176 | 0 | const std::vector<char*> argv{MakeArgv(args)}; |
177 | 0 | if (execvp(argv[0], argv.data()) != 0) { |
178 | 0 | perror("execvp failed"); |
179 | 0 | if (errno == ENOENT && !args.empty()) { |
180 | 0 | std::cerr << "Missing executable: " << fs::weakly_canonical(args.front()) << '\n'; |
181 | 0 | } |
182 | 0 | _exit(1); |
183 | 0 | } |
184 | 0 | } |
185 | | |
186 | | int WaitProcess(int pid) |
187 | 0 | { |
188 | 0 | int status; |
189 | 0 | if (::waitpid(pid, &status, /*options=*/0) != pid) { |
190 | 0 | throw std::system_error(errno, std::system_category(), "waitpid"); |
191 | 0 | } |
192 | 0 | return status; |
193 | 0 | } |
194 | | |
195 | | } // namespace mp |