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() {
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, O_CLOEXEC) == 0)
90 return error;
91#else
92 if (::pipe(m_fds) == 0) {
93#ifdef FD_CLOEXEC
94 if (!SetCloexecFlag(m_fds[0]) || !SetCloexecFlag(m_fds[1])) {
95 error = Status::FromErrno();
96 CloseUnlocked();
97 return error;
98 }
99#endif
100 return error;
101 }
102#endif
103
104 error = Status::FromErrno();
105 m_fds[READ] = PipePosix::kInvalidDescriptor;
106 m_fds[WRITE] = PipePosix::kInvalidDescriptor;
107 return error;
108}
109
110Status PipePosix::CreateNew(llvm::StringRef name) {
111 std::scoped_lock<std::mutex, std::mutex> guard(m_read_mutex, m_write_mutex);
112 if (CanReadUnlocked() || CanWriteUnlocked())
113 return Status::FromErrorString(str: "Pipe is already opened");
114
115 Status error;
116 if (::mkfifo(path: name.str().c_str(), mode: 0660) != 0)
117 error = Status::FromErrno();
118 return error;
119}
120
121Status PipePosix::CreateWithUniqueName(llvm::StringRef prefix,
122 llvm::SmallVectorImpl<char> &name) {
123 llvm::SmallString<128> named_pipe_path;
124 llvm::SmallString<128> pipe_spec((prefix + ".%%%%%%").str());
125 FileSpec tmpdir_file_spec = HostInfo::GetProcessTempDir();
126 if (!tmpdir_file_spec)
127 tmpdir_file_spec.AppendPathComponent(component: "/tmp");
128 tmpdir_file_spec.AppendPathComponent(component: pipe_spec);
129
130 // It's possible that another process creates the target path after we've
131 // verified it's available but before we create it, in which case we should
132 // try again.
133 Status error;
134 do {
135 llvm::sys::fs::createUniquePath(Model: tmpdir_file_spec.GetPath(), ResultPath&: named_pipe_path,
136 /*MakeAbsolute=*/false);
137 error = CreateNew(name: named_pipe_path);
138 } while (error.GetError() == EEXIST);
139
140 if (error.Success())
141 name = named_pipe_path;
142 return error;
143}
144
145Status PipePosix::OpenAsReader(llvm::StringRef name) {
146 std::scoped_lock<std::mutex, std::mutex> guard(m_read_mutex, m_write_mutex);
147
148 if (CanReadUnlocked() || CanWriteUnlocked())
149 return Status::FromErrorString(str: "Pipe is already opened");
150
151 int flags = O_RDONLY | O_NONBLOCK | O_CLOEXEC;
152
153 Status error;
154 int fd = FileSystem::Instance().Open(path: name.str().c_str(), flags);
155 if (fd != -1)
156 m_fds[READ] = fd;
157 else
158 error = Status::FromErrno();
159
160 return error;
161}
162
163llvm::Error PipePosix::OpenAsWriter(llvm::StringRef name,
164 const Timeout<std::micro> &timeout) {
165 std::lock_guard<std::mutex> guard(m_write_mutex);
166 if (CanReadUnlocked() || CanWriteUnlocked())
167 return llvm::createStringError(Fmt: "Pipe is already opened");
168
169 int flags = O_WRONLY | O_NONBLOCK | O_CLOEXEC;
170
171 using namespace std::chrono;
172 std::optional<time_point<steady_clock>> finish_time;
173 if (timeout)
174 finish_time = Now() + *timeout;
175
176 while (!CanWriteUnlocked()) {
177 if (timeout) {
178 if (Now() > finish_time)
179 return llvm::createStringError(
180 EC: std::make_error_code(e: std::errc::timed_out),
181 S: "timeout exceeded - reader hasn't opened so far");
182 }
183
184 errno = 0;
185 int fd = ::open(file: name.str().c_str(), oflag: flags);
186 if (fd == -1) {
187 const auto errno_copy = errno;
188 // We may get ENXIO if a reader side of the pipe hasn't opened yet.
189 if (errno_copy != ENXIO && errno_copy != EINTR)
190 return llvm::errorCodeToError(
191 EC: std::error_code(errno_copy, std::generic_category()));
192
193 std::this_thread::sleep_for(
194 rtime: milliseconds(OPEN_WRITER_SLEEP_TIMEOUT_MSECS));
195 } else {
196 m_fds[WRITE] = fd;
197 }
198 }
199
200 return llvm::Error::success();
201}
202
203int PipePosix::GetReadFileDescriptor() const {
204 std::lock_guard<std::mutex> guard(m_read_mutex);
205 return GetReadFileDescriptorUnlocked();
206}
207
208int PipePosix::GetReadFileDescriptorUnlocked() const {
209 return m_fds[READ];
210}
211
212int PipePosix::GetWriteFileDescriptor() const {
213 std::lock_guard<std::mutex> guard(m_write_mutex);
214 return GetWriteFileDescriptorUnlocked();
215}
216
217int PipePosix::GetWriteFileDescriptorUnlocked() const {
218 return m_fds[WRITE];
219}
220
221int PipePosix::ReleaseReadFileDescriptor() {
222 std::lock_guard<std::mutex> guard(m_read_mutex);
223 return ReleaseReadFileDescriptorUnlocked();
224}
225
226int PipePosix::ReleaseReadFileDescriptorUnlocked() {
227 const int fd = m_fds[READ];
228 m_fds[READ] = PipePosix::kInvalidDescriptor;
229 return fd;
230}
231
232int PipePosix::ReleaseWriteFileDescriptor() {
233 std::lock_guard<std::mutex> guard(m_write_mutex);
234 return ReleaseWriteFileDescriptorUnlocked();
235}
236
237int PipePosix::ReleaseWriteFileDescriptorUnlocked() {
238 const int fd = m_fds[WRITE];
239 m_fds[WRITE] = PipePosix::kInvalidDescriptor;
240 return fd;
241}
242
243void PipePosix::Close() {
244 std::scoped_lock<std::mutex, std::mutex> guard(m_read_mutex, m_write_mutex);
245 CloseUnlocked();
246}
247
248void PipePosix::CloseUnlocked() {
249 CloseReadFileDescriptorUnlocked();
250 CloseWriteFileDescriptorUnlocked();
251}
252
253Status PipePosix::Delete(llvm::StringRef name) {
254 return llvm::sys::fs::remove(path: name);
255}
256
257bool PipePosix::CanRead() const {
258 std::lock_guard<std::mutex> guard(m_read_mutex);
259 return CanReadUnlocked();
260}
261
262bool PipePosix::CanReadUnlocked() const {
263 return m_fds[READ] != PipePosix::kInvalidDescriptor;
264}
265
266bool PipePosix::CanWrite() const {
267 std::lock_guard<std::mutex> guard(m_write_mutex);
268 return CanWriteUnlocked();
269}
270
271bool PipePosix::CanWriteUnlocked() const {
272 return m_fds[WRITE] != PipePosix::kInvalidDescriptor;
273}
274
275void PipePosix::CloseReadFileDescriptor() {
276 std::lock_guard<std::mutex> guard(m_read_mutex);
277 CloseReadFileDescriptorUnlocked();
278}
279void PipePosix::CloseReadFileDescriptorUnlocked() {
280 if (CanReadUnlocked()) {
281 close(fd: m_fds[READ]);
282 m_fds[READ] = PipePosix::kInvalidDescriptor;
283 }
284}
285
286void PipePosix::CloseWriteFileDescriptor() {
287 std::lock_guard<std::mutex> guard(m_write_mutex);
288 CloseWriteFileDescriptorUnlocked();
289}
290
291void PipePosix::CloseWriteFileDescriptorUnlocked() {
292 if (CanWriteUnlocked()) {
293 close(fd: m_fds[WRITE]);
294 m_fds[WRITE] = PipePosix::kInvalidDescriptor;
295 }
296}
297
298llvm::Expected<size_t> PipePosix::Read(void *buf, size_t size,
299 const Timeout<std::micro> &timeout) {
300 std::lock_guard<std::mutex> guard(m_read_mutex);
301 if (!CanReadUnlocked())
302 return llvm::errorCodeToError(
303 EC: std::make_error_code(e: std::errc::invalid_argument));
304
305 const int fd = GetReadFileDescriptorUnlocked();
306
307 SelectHelper select_helper;
308 if (timeout)
309 select_helper.SetTimeout(*timeout);
310 select_helper.FDSetRead(fd);
311
312 if (llvm::Error error = select_helper.Select().takeError())
313 return error;
314
315 ssize_t result = ::read(fd: fd, buf: buf, nbytes: size);
316 if (result == -1)
317 return llvm::errorCodeToError(
318 EC: std::error_code(errno, std::generic_category()));
319
320 return result;
321}
322
323llvm::Expected<size_t> PipePosix::Write(const void *buf, size_t size,
324 const Timeout<std::micro> &timeout) {
325 std::lock_guard<std::mutex> guard(m_write_mutex);
326 if (!CanWriteUnlocked())
327 return llvm::errorCodeToError(
328 EC: std::make_error_code(e: std::errc::invalid_argument));
329
330 const int fd = GetWriteFileDescriptorUnlocked();
331 SelectHelper select_helper;
332 if (timeout)
333 select_helper.SetTimeout(*timeout);
334 select_helper.FDSetWrite(fd);
335
336 if (llvm::Error error = select_helper.Select().takeError())
337 return error;
338
339 ssize_t result = ::write(fd: fd, buf: buf, n: size);
340 if (result == -1)
341 return llvm::errorCodeToError(
342 EC: std::error_code(errno, std::generic_category()));
343
344 return result;
345}
346

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