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
27using namespace lldb;
28using namespace lldb_private;
29
30int PipePosix::kInvalidDescriptor = -1;
31
32enum 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
43static constexpr auto OPEN_WRITER_SLEEP_TIMEOUT_MSECS = 100;
44
45#if defined(FD_CLOEXEC) && !PIPE2_SUPPORTED
46static 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
54static std::chrono::time_point<std::chrono::steady_clock> Now() {
55 return std::chrono::steady_clock::now();
56}
57
58PipePosix::PipePosix()
59 : m_fds{PipePosix::kInvalidDescriptor, PipePosix::kInvalidDescriptor} {}
60
61PipePosix::PipePosix(lldb::pipe_t read, lldb::pipe_t write)
62 : m_fds{read, write} {}
63
64PipePosix::PipePosix(PipePosix &&pipe_posix)
65 : PipeBase{std::move(pipe_posix)},
66 m_fds{pipe_posix.ReleaseReadFileDescriptor(),
67 pipe_posix.ReleaseWriteFileDescriptor()} {}
68
69PipePosix &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
80PipePosix::~PipePosix() { Close(); }
81
82Status 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
112Status 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
123Status 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
148Status 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
169llvm::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
212int PipePosix::GetReadFileDescriptor() const {
213 std::lock_guard<std::mutex> guard(m_read_mutex);
214 return GetReadFileDescriptorUnlocked();
215}
216
217int PipePosix::GetReadFileDescriptorUnlocked() const {
218 return m_fds[READ];
219}
220
221int PipePosix::GetWriteFileDescriptor() const {
222 std::lock_guard<std::mutex> guard(m_write_mutex);
223 return GetWriteFileDescriptorUnlocked();
224}
225
226int PipePosix::GetWriteFileDescriptorUnlocked() const {
227 return m_fds[WRITE];
228}
229
230int PipePosix::ReleaseReadFileDescriptor() {
231 std::lock_guard<std::mutex> guard(m_read_mutex);
232 return ReleaseReadFileDescriptorUnlocked();
233}
234
235int PipePosix::ReleaseReadFileDescriptorUnlocked() {
236 const int fd = m_fds[READ];
237 m_fds[READ] = PipePosix::kInvalidDescriptor;
238 return fd;
239}
240
241int PipePosix::ReleaseWriteFileDescriptor() {
242 std::lock_guard<std::mutex> guard(m_write_mutex);
243 return ReleaseWriteFileDescriptorUnlocked();
244}
245
246int PipePosix::ReleaseWriteFileDescriptorUnlocked() {
247 const int fd = m_fds[WRITE];
248 m_fds[WRITE] = PipePosix::kInvalidDescriptor;
249 return fd;
250}
251
252void PipePosix::Close() {
253 std::scoped_lock<std::mutex, std::mutex> guard(m_read_mutex, m_write_mutex);
254 CloseUnlocked();
255}
256
257void PipePosix::CloseUnlocked() {
258 CloseReadFileDescriptorUnlocked();
259 CloseWriteFileDescriptorUnlocked();
260}
261
262Status PipePosix::Delete(llvm::StringRef name) {
263 return llvm::sys::fs::remove(path: name);
264}
265
266bool PipePosix::CanRead() const {
267 std::lock_guard<std::mutex> guard(m_read_mutex);
268 return CanReadUnlocked();
269}
270
271bool PipePosix::CanReadUnlocked() const {
272 return m_fds[READ] != PipePosix::kInvalidDescriptor;
273}
274
275bool PipePosix::CanWrite() const {
276 std::lock_guard<std::mutex> guard(m_write_mutex);
277 return CanWriteUnlocked();
278}
279
280bool PipePosix::CanWriteUnlocked() const {
281 return m_fds[WRITE] != PipePosix::kInvalidDescriptor;
282}
283
284void PipePosix::CloseReadFileDescriptor() {
285 std::lock_guard<std::mutex> guard(m_read_mutex);
286 CloseReadFileDescriptorUnlocked();
287}
288void PipePosix::CloseReadFileDescriptorUnlocked() {
289 if (CanReadUnlocked()) {
290 close(fd: m_fds[READ]);
291 m_fds[READ] = PipePosix::kInvalidDescriptor;
292 }
293}
294
295void PipePosix::CloseWriteFileDescriptor() {
296 std::lock_guard<std::mutex> guard(m_write_mutex);
297 CloseWriteFileDescriptorUnlocked();
298}
299
300void PipePosix::CloseWriteFileDescriptorUnlocked() {
301 if (CanWriteUnlocked()) {
302 close(fd: m_fds[WRITE]);
303 m_fds[WRITE] = PipePosix::kInvalidDescriptor;
304 }
305}
306
307llvm::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
332llvm::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

Provided by KDAB

Privacy Policy
Update your C++ knowledge – Modern C++11/14/17 Training
Find out more

source code of lldb/source/Host/posix/PipePosix.cpp