1 | //===-- PipePosix.cpp -----------------------------------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "lldb/Host/posix/PipePosix.h" |
10 | #include "lldb/Host/FileSystem.h" |
11 | #include "lldb/Host/HostInfo.h" |
12 | #include "lldb/Utility/SelectHelper.h" |
13 | #include "llvm/ADT/SmallString.h" |
14 | #include "llvm/Support/Errno.h" |
15 | #include <functional> |
16 | #include <thread> |
17 | |
18 | #include <cerrno> |
19 | #include <climits> |
20 | #include <fcntl.h> |
21 | #include <sys/stat.h> |
22 | #include <sys/types.h> |
23 | #include <unistd.h> |
24 | |
25 | using namespace lldb; |
26 | using namespace lldb_private; |
27 | |
28 | int PipePosix::kInvalidDescriptor = -1; |
29 | |
30 | enum PIPES { READ, WRITE }; // Constants 0 and 1 for READ and WRITE |
31 | |
32 | // pipe2 is supported by a limited set of platforms |
33 | // TODO: Add more platforms that support pipe2. |
34 | #if defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) || \ |
35 | defined(__OpenBSD__) |
36 | #define PIPE2_SUPPORTED 1 |
37 | #else |
38 | #define PIPE2_SUPPORTED 0 |
39 | #endif |
40 | |
41 | static constexpr auto OPEN_WRITER_SLEEP_TIMEOUT_MSECS = 100; |
42 | |
43 | #if defined(FD_CLOEXEC) && !PIPE2_SUPPORTED |
44 | static bool SetCloexecFlag(int fd) { |
45 | int flags = ::fcntl(fd, F_GETFD); |
46 | if (flags == -1) |
47 | return false; |
48 | return (::fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == 0); |
49 | } |
50 | #endif |
51 | |
52 | static std::chrono::time_point<std::chrono::steady_clock> Now() { |
53 | return std::chrono::steady_clock::now(); |
54 | } |
55 | |
56 | PipePosix::PipePosix() |
57 | : m_fds{PipePosix::kInvalidDescriptor, PipePosix::kInvalidDescriptor} {} |
58 | |
59 | PipePosix::PipePosix(lldb::pipe_t read, lldb::pipe_t write) |
60 | : m_fds{read, write} {} |
61 | |
62 | PipePosix::PipePosix(PipePosix &&pipe_posix) |
63 | : PipeBase{std::move(pipe_posix)}, |
64 | m_fds{pipe_posix.ReleaseReadFileDescriptor(), |
65 | pipe_posix.ReleaseWriteFileDescriptor()} {} |
66 | |
67 | PipePosix &PipePosix::operator=(PipePosix &&pipe_posix) { |
68 | std::scoped_lock<std::mutex, std::mutex, std::mutex, std::mutex> guard( |
69 | m_read_mutex, m_write_mutex, pipe_posix.m_read_mutex, |
70 | pipe_posix.m_write_mutex); |
71 | |
72 | PipeBase::operator=(std::move(pipe_posix)); |
73 | m_fds[READ] = pipe_posix.ReleaseReadFileDescriptorUnlocked(); |
74 | m_fds[WRITE] = pipe_posix.ReleaseWriteFileDescriptorUnlocked(); |
75 | return *this; |
76 | } |
77 | |
78 | PipePosix::~PipePosix() { Close(); } |
79 | |
80 | Status PipePosix::CreateNew(bool child_processes_inherit) { |
81 | std::scoped_lock<std::mutex, std::mutex> guard(m_read_mutex, m_write_mutex); |
82 | if (CanReadUnlocked() || CanWriteUnlocked()) |
83 | return Status(EINVAL, eErrorTypePOSIX); |
84 | |
85 | Status error; |
86 | #if PIPE2_SUPPORTED |
87 | if (::pipe2(pipedes: m_fds, flags: (child_processes_inherit) ? 0 : O_CLOEXEC) == 0) |
88 | return error; |
89 | #else |
90 | if (::pipe(m_fds) == 0) { |
91 | #ifdef FD_CLOEXEC |
92 | if (!child_processes_inherit) { |
93 | if (!SetCloexecFlag(m_fds[0]) || !SetCloexecFlag(m_fds[1])) { |
94 | error.SetErrorToErrno(); |
95 | CloseUnlocked(); |
96 | return error; |
97 | } |
98 | } |
99 | #endif |
100 | return error; |
101 | } |
102 | #endif |
103 | |
104 | error.SetErrorToErrno(); |
105 | m_fds[READ] = PipePosix::kInvalidDescriptor; |
106 | m_fds[WRITE] = PipePosix::kInvalidDescriptor; |
107 | return error; |
108 | } |
109 | |
110 | Status PipePosix::CreateNew(llvm::StringRef name, bool child_process_inherit) { |
111 | std::scoped_lock<std::mutex, std::mutex> (m_read_mutex, m_write_mutex); |
112 | if (CanReadUnlocked() || CanWriteUnlocked()) |
113 | return Status("Pipe is already opened" ); |
114 | |
115 | Status error; |
116 | if (::mkfifo(path: name.str().c_str(), mode: 0660) != 0) |
117 | error.SetErrorToErrno(); |
118 | |
119 | return error; |
120 | } |
121 | |
122 | Status PipePosix::CreateWithUniqueName(llvm::StringRef prefix, |
123 | bool child_process_inherit, |
124 | llvm::SmallVectorImpl<char> &name) { |
125 | llvm::SmallString<128> named_pipe_path; |
126 | llvm::SmallString<128> pipe_spec((prefix + ".%%%%%%" ).str()); |
127 | FileSpec tmpdir_file_spec = HostInfo::GetProcessTempDir(); |
128 | if (!tmpdir_file_spec) |
129 | tmpdir_file_spec.AppendPathComponent(component: "/tmp" ); |
130 | tmpdir_file_spec.AppendPathComponent(component: pipe_spec); |
131 | |
132 | // It's possible that another process creates the target path after we've |
133 | // verified it's available but before we create it, in which case we should |
134 | // try again. |
135 | Status error; |
136 | do { |
137 | llvm::sys::fs::createUniquePath(Model: tmpdir_file_spec.GetPath(), ResultPath&: named_pipe_path, |
138 | /*MakeAbsolute=*/false); |
139 | error = CreateNew(name: named_pipe_path, child_process_inherit); |
140 | } while (error.GetError() == EEXIST); |
141 | |
142 | if (error.Success()) |
143 | name = named_pipe_path; |
144 | return error; |
145 | } |
146 | |
147 | Status PipePosix::OpenAsReader(llvm::StringRef name, |
148 | bool child_process_inherit) { |
149 | std::scoped_lock<std::mutex, std::mutex> (m_read_mutex, m_write_mutex); |
150 | |
151 | if (CanReadUnlocked() || CanWriteUnlocked()) |
152 | return Status("Pipe is already opened" ); |
153 | |
154 | int flags = O_RDONLY | O_NONBLOCK; |
155 | if (!child_process_inherit) |
156 | flags |= O_CLOEXEC; |
157 | |
158 | Status error; |
159 | int fd = FileSystem::Instance().Open(path: name.str().c_str(), flags); |
160 | if (fd != -1) |
161 | m_fds[READ] = fd; |
162 | else |
163 | error.SetErrorToErrno(); |
164 | |
165 | return error; |
166 | } |
167 | |
168 | Status |
169 | PipePosix::OpenAsWriterWithTimeout(llvm::StringRef name, |
170 | bool child_process_inherit, |
171 | const std::chrono::microseconds &timeout) { |
172 | std::lock_guard<std::mutex> guard(m_write_mutex); |
173 | if (CanReadUnlocked() || CanWriteUnlocked()) |
174 | return Status("Pipe is already opened" ); |
175 | |
176 | int flags = O_WRONLY | O_NONBLOCK; |
177 | if (!child_process_inherit) |
178 | flags |= O_CLOEXEC; |
179 | |
180 | using namespace std::chrono; |
181 | const auto finish_time = Now() + timeout; |
182 | |
183 | while (!CanWriteUnlocked()) { |
184 | if (timeout != microseconds::zero()) { |
185 | const auto dur = duration_cast<microseconds>(d: finish_time - Now()).count(); |
186 | if (dur <= 0) |
187 | return Status("timeout exceeded - reader hasn't opened so far" ); |
188 | } |
189 | |
190 | errno = 0; |
191 | int fd = ::open(file: name.str().c_str(), oflag: flags); |
192 | if (fd == -1) { |
193 | const auto errno_copy = errno; |
194 | // We may get ENXIO if a reader side of the pipe hasn't opened yet. |
195 | if (errno_copy != ENXIO && errno_copy != EINTR) |
196 | return Status(errno_copy, eErrorTypePOSIX); |
197 | |
198 | std::this_thread::sleep_for( |
199 | rtime: milliseconds(OPEN_WRITER_SLEEP_TIMEOUT_MSECS)); |
200 | } else { |
201 | m_fds[WRITE] = fd; |
202 | } |
203 | } |
204 | |
205 | return Status(); |
206 | } |
207 | |
208 | int PipePosix::GetReadFileDescriptor() const { |
209 | std::lock_guard<std::mutex> guard(m_read_mutex); |
210 | return GetReadFileDescriptorUnlocked(); |
211 | } |
212 | |
213 | int PipePosix::GetReadFileDescriptorUnlocked() const { |
214 | return m_fds[READ]; |
215 | } |
216 | |
217 | int PipePosix::GetWriteFileDescriptor() const { |
218 | std::lock_guard<std::mutex> guard(m_write_mutex); |
219 | return GetWriteFileDescriptorUnlocked(); |
220 | } |
221 | |
222 | int PipePosix::GetWriteFileDescriptorUnlocked() const { |
223 | return m_fds[WRITE]; |
224 | } |
225 | |
226 | int PipePosix::ReleaseReadFileDescriptor() { |
227 | std::lock_guard<std::mutex> guard(m_read_mutex); |
228 | return ReleaseReadFileDescriptorUnlocked(); |
229 | } |
230 | |
231 | int PipePosix::ReleaseReadFileDescriptorUnlocked() { |
232 | const int fd = m_fds[READ]; |
233 | m_fds[READ] = PipePosix::kInvalidDescriptor; |
234 | return fd; |
235 | } |
236 | |
237 | int PipePosix::ReleaseWriteFileDescriptor() { |
238 | std::lock_guard<std::mutex> guard(m_write_mutex); |
239 | return ReleaseWriteFileDescriptorUnlocked(); |
240 | } |
241 | |
242 | int PipePosix::ReleaseWriteFileDescriptorUnlocked() { |
243 | const int fd = m_fds[WRITE]; |
244 | m_fds[WRITE] = PipePosix::kInvalidDescriptor; |
245 | return fd; |
246 | } |
247 | |
248 | void PipePosix::Close() { |
249 | std::scoped_lock<std::mutex, std::mutex> guard(m_read_mutex, m_write_mutex); |
250 | CloseUnlocked(); |
251 | } |
252 | |
253 | void PipePosix::CloseUnlocked() { |
254 | CloseReadFileDescriptorUnlocked(); |
255 | CloseWriteFileDescriptorUnlocked(); |
256 | } |
257 | |
258 | Status PipePosix::Delete(llvm::StringRef name) { |
259 | return llvm::sys::fs::remove(path: name); |
260 | } |
261 | |
262 | bool PipePosix::CanRead() const { |
263 | std::lock_guard<std::mutex> guard(m_read_mutex); |
264 | return CanReadUnlocked(); |
265 | } |
266 | |
267 | bool PipePosix::CanReadUnlocked() const { |
268 | return m_fds[READ] != PipePosix::kInvalidDescriptor; |
269 | } |
270 | |
271 | bool PipePosix::CanWrite() const { |
272 | std::lock_guard<std::mutex> guard(m_write_mutex); |
273 | return CanWriteUnlocked(); |
274 | } |
275 | |
276 | bool PipePosix::CanWriteUnlocked() const { |
277 | return m_fds[WRITE] != PipePosix::kInvalidDescriptor; |
278 | } |
279 | |
280 | void PipePosix::CloseReadFileDescriptor() { |
281 | std::lock_guard<std::mutex> guard(m_read_mutex); |
282 | CloseReadFileDescriptorUnlocked(); |
283 | } |
284 | void PipePosix::CloseReadFileDescriptorUnlocked() { |
285 | if (CanReadUnlocked()) { |
286 | close(fd: m_fds[READ]); |
287 | m_fds[READ] = PipePosix::kInvalidDescriptor; |
288 | } |
289 | } |
290 | |
291 | void PipePosix::CloseWriteFileDescriptor() { |
292 | std::lock_guard<std::mutex> guard(m_write_mutex); |
293 | CloseWriteFileDescriptorUnlocked(); |
294 | } |
295 | |
296 | void PipePosix::CloseWriteFileDescriptorUnlocked() { |
297 | if (CanWriteUnlocked()) { |
298 | close(fd: m_fds[WRITE]); |
299 | m_fds[WRITE] = PipePosix::kInvalidDescriptor; |
300 | } |
301 | } |
302 | |
303 | Status PipePosix::ReadWithTimeout(void *buf, size_t size, |
304 | const std::chrono::microseconds &timeout, |
305 | size_t &bytes_read) { |
306 | std::lock_guard<std::mutex> guard(m_read_mutex); |
307 | bytes_read = 0; |
308 | if (!CanReadUnlocked()) |
309 | return Status(EINVAL, eErrorTypePOSIX); |
310 | |
311 | const int fd = GetReadFileDescriptorUnlocked(); |
312 | |
313 | SelectHelper select_helper; |
314 | select_helper.SetTimeout(timeout); |
315 | select_helper.FDSetRead(fd); |
316 | |
317 | Status error; |
318 | while (error.Success()) { |
319 | error = select_helper.Select(); |
320 | if (error.Success()) { |
321 | auto result = |
322 | ::read(fd: fd, buf: static_cast<char *>(buf) + bytes_read, nbytes: size - bytes_read); |
323 | if (result != -1) { |
324 | bytes_read += result; |
325 | if (bytes_read == size || result == 0) |
326 | break; |
327 | } else if (errno == EINTR) { |
328 | continue; |
329 | } else { |
330 | error.SetErrorToErrno(); |
331 | break; |
332 | } |
333 | } |
334 | } |
335 | return error; |
336 | } |
337 | |
338 | Status PipePosix::Write(const void *buf, size_t size, size_t &bytes_written) { |
339 | std::lock_guard<std::mutex> guard(m_write_mutex); |
340 | bytes_written = 0; |
341 | if (!CanWriteUnlocked()) |
342 | return Status(EINVAL, eErrorTypePOSIX); |
343 | |
344 | const int fd = GetWriteFileDescriptorUnlocked(); |
345 | SelectHelper select_helper; |
346 | select_helper.SetTimeout(std::chrono::seconds(0)); |
347 | select_helper.FDSetWrite(fd); |
348 | |
349 | Status error; |
350 | while (error.Success()) { |
351 | error = select_helper.Select(); |
352 | if (error.Success()) { |
353 | auto result = ::write(fd: fd, buf: static_cast<const char *>(buf) + bytes_written, |
354 | n: size - bytes_written); |
355 | if (result != -1) { |
356 | bytes_written += result; |
357 | if (bytes_written == size) |
358 | break; |
359 | } else if (errno == EINTR) { |
360 | continue; |
361 | } else { |
362 | error.SetErrorToErrno(); |
363 | } |
364 | } |
365 | } |
366 | return error; |
367 | } |
368 | |