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