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/HostInfo.h"
11 #include "lldb/Utility/SelectHelper.h"
12 #include "llvm/ADT/SmallString.h"
13 #include "llvm/Support/Errno.h"
14 #include "llvm/Support/FileSystem.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__) && __FreeBSD__ >= 10) || \
35 defined(__NetBSD__) || 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
SetCloexecFlag(int fd)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
Now()52 static std::chrono::time_point<std::chrono::steady_clock> Now() {
53 return std::chrono::steady_clock::now();
54 }
55
PipePosix()56 PipePosix::PipePosix()
57 : m_fds{PipePosix::kInvalidDescriptor, PipePosix::kInvalidDescriptor} {}
58
PipePosix(lldb::pipe_t read,lldb::pipe_t write)59 PipePosix::PipePosix(lldb::pipe_t read, lldb::pipe_t write)
60 : m_fds{read, write} {}
61
PipePosix(PipePosix && pipe_posix)62 PipePosix::PipePosix(PipePosix &&pipe_posix)
63 : PipeBase{std::move(pipe_posix)},
64 m_fds{pipe_posix.ReleaseReadFileDescriptor(),
65 pipe_posix.ReleaseWriteFileDescriptor()} {}
66
operator =(PipePosix && pipe_posix)67 PipePosix &PipePosix::operator=(PipePosix &&pipe_posix) {
68 PipeBase::operator=(std::move(pipe_posix));
69 m_fds[READ] = pipe_posix.ReleaseReadFileDescriptor();
70 m_fds[WRITE] = pipe_posix.ReleaseWriteFileDescriptor();
71 return *this;
72 }
73
~PipePosix()74 PipePosix::~PipePosix() { Close(); }
75
CreateNew(bool child_processes_inherit)76 Status PipePosix::CreateNew(bool child_processes_inherit) {
77 if (CanRead() || CanWrite())
78 return Status(EINVAL, eErrorTypePOSIX);
79
80 Status error;
81 #if PIPE2_SUPPORTED
82 if (::pipe2(m_fds, (child_processes_inherit) ? 0 : O_CLOEXEC) == 0)
83 return error;
84 #else
85 if (::pipe(m_fds) == 0) {
86 #ifdef FD_CLOEXEC
87 if (!child_processes_inherit) {
88 if (!SetCloexecFlag(m_fds[0]) || !SetCloexecFlag(m_fds[1])) {
89 error.SetErrorToErrno();
90 Close();
91 return error;
92 }
93 }
94 #endif
95 return error;
96 }
97 #endif
98
99 error.SetErrorToErrno();
100 m_fds[READ] = PipePosix::kInvalidDescriptor;
101 m_fds[WRITE] = PipePosix::kInvalidDescriptor;
102 return error;
103 }
104
CreateNew(llvm::StringRef name,bool child_process_inherit)105 Status PipePosix::CreateNew(llvm::StringRef name, bool child_process_inherit) {
106 if (CanRead() || CanWrite())
107 return Status("Pipe is already opened");
108
109 Status error;
110 if (::mkfifo(name.str().c_str(), 0660) != 0)
111 error.SetErrorToErrno();
112
113 return error;
114 }
115
CreateWithUniqueName(llvm::StringRef prefix,bool child_process_inherit,llvm::SmallVectorImpl<char> & name)116 Status PipePosix::CreateWithUniqueName(llvm::StringRef prefix,
117 bool child_process_inherit,
118 llvm::SmallVectorImpl<char> &name) {
119 llvm::SmallString<128> named_pipe_path;
120 llvm::SmallString<128> pipe_spec((prefix + ".%%%%%%").str());
121 FileSpec tmpdir_file_spec = HostInfo::GetProcessTempDir();
122 if (!tmpdir_file_spec)
123 tmpdir_file_spec.AppendPathComponent("/tmp");
124 tmpdir_file_spec.AppendPathComponent(pipe_spec);
125
126 // It's possible that another process creates the target path after we've
127 // verified it's available but before we create it, in which case we should
128 // try again.
129 Status error;
130 do {
131 llvm::sys::fs::createUniquePath(tmpdir_file_spec.GetPath(), named_pipe_path,
132 /*MakeAbsolute=*/false);
133 error = CreateNew(named_pipe_path, child_process_inherit);
134 } while (error.GetError() == EEXIST);
135
136 if (error.Success())
137 name = named_pipe_path;
138 return error;
139 }
140
OpenAsReader(llvm::StringRef name,bool child_process_inherit)141 Status PipePosix::OpenAsReader(llvm::StringRef name,
142 bool child_process_inherit) {
143 if (CanRead() || CanWrite())
144 return Status("Pipe is already opened");
145
146 int flags = O_RDONLY | O_NONBLOCK;
147 if (!child_process_inherit)
148 flags |= O_CLOEXEC;
149
150 Status error;
151 int fd = llvm::sys::RetryAfterSignal(-1, ::open, name.str().c_str(), flags);
152 if (fd != -1)
153 m_fds[READ] = fd;
154 else
155 error.SetErrorToErrno();
156
157 return error;
158 }
159
160 Status
OpenAsWriterWithTimeout(llvm::StringRef name,bool child_process_inherit,const std::chrono::microseconds & timeout)161 PipePosix::OpenAsWriterWithTimeout(llvm::StringRef name,
162 bool child_process_inherit,
163 const std::chrono::microseconds &timeout) {
164 if (CanRead() || CanWrite())
165 return Status("Pipe is already opened");
166
167 int flags = O_WRONLY | O_NONBLOCK;
168 if (!child_process_inherit)
169 flags |= O_CLOEXEC;
170
171 using namespace std::chrono;
172 const auto finish_time = Now() + timeout;
173
174 while (!CanWrite()) {
175 if (timeout != microseconds::zero()) {
176 const auto dur = duration_cast<microseconds>(finish_time - Now()).count();
177 if (dur <= 0)
178 return Status("timeout exceeded - reader hasn't opened so far");
179 }
180
181 errno = 0;
182 int fd = ::open(name.str().c_str(), flags);
183 if (fd == -1) {
184 const auto errno_copy = errno;
185 // We may get ENXIO if a reader side of the pipe hasn't opened yet.
186 if (errno_copy != ENXIO && errno_copy != EINTR)
187 return Status(errno_copy, eErrorTypePOSIX);
188
189 std::this_thread::sleep_for(
190 milliseconds(OPEN_WRITER_SLEEP_TIMEOUT_MSECS));
191 } else {
192 m_fds[WRITE] = fd;
193 }
194 }
195
196 return Status();
197 }
198
GetReadFileDescriptor() const199 int PipePosix::GetReadFileDescriptor() const { return m_fds[READ]; }
200
GetWriteFileDescriptor() const201 int PipePosix::GetWriteFileDescriptor() const { return m_fds[WRITE]; }
202
ReleaseReadFileDescriptor()203 int PipePosix::ReleaseReadFileDescriptor() {
204 const int fd = m_fds[READ];
205 m_fds[READ] = PipePosix::kInvalidDescriptor;
206 return fd;
207 }
208
ReleaseWriteFileDescriptor()209 int PipePosix::ReleaseWriteFileDescriptor() {
210 const int fd = m_fds[WRITE];
211 m_fds[WRITE] = PipePosix::kInvalidDescriptor;
212 return fd;
213 }
214
Close()215 void PipePosix::Close() {
216 CloseReadFileDescriptor();
217 CloseWriteFileDescriptor();
218 }
219
Delete(llvm::StringRef name)220 Status PipePosix::Delete(llvm::StringRef name) {
221 return llvm::sys::fs::remove(name);
222 }
223
CanRead() const224 bool PipePosix::CanRead() const {
225 return m_fds[READ] != PipePosix::kInvalidDescriptor;
226 }
227
CanWrite() const228 bool PipePosix::CanWrite() const {
229 return m_fds[WRITE] != PipePosix::kInvalidDescriptor;
230 }
231
CloseReadFileDescriptor()232 void PipePosix::CloseReadFileDescriptor() {
233 if (CanRead()) {
234 close(m_fds[READ]);
235 m_fds[READ] = PipePosix::kInvalidDescriptor;
236 }
237 }
238
CloseWriteFileDescriptor()239 void PipePosix::CloseWriteFileDescriptor() {
240 if (CanWrite()) {
241 close(m_fds[WRITE]);
242 m_fds[WRITE] = PipePosix::kInvalidDescriptor;
243 }
244 }
245
ReadWithTimeout(void * buf,size_t size,const std::chrono::microseconds & timeout,size_t & bytes_read)246 Status PipePosix::ReadWithTimeout(void *buf, size_t size,
247 const std::chrono::microseconds &timeout,
248 size_t &bytes_read) {
249 bytes_read = 0;
250 if (!CanRead())
251 return Status(EINVAL, eErrorTypePOSIX);
252
253 const int fd = GetReadFileDescriptor();
254
255 SelectHelper select_helper;
256 select_helper.SetTimeout(timeout);
257 select_helper.FDSetRead(fd);
258
259 Status error;
260 while (error.Success()) {
261 error = select_helper.Select();
262 if (error.Success()) {
263 auto result =
264 ::read(fd, static_cast<char *>(buf) + bytes_read, size - bytes_read);
265 if (result != -1) {
266 bytes_read += result;
267 if (bytes_read == size || result == 0)
268 break;
269 } else if (errno == EINTR) {
270 continue;
271 } else {
272 error.SetErrorToErrno();
273 break;
274 }
275 }
276 }
277 return error;
278 }
279
Write(const void * buf,size_t size,size_t & bytes_written)280 Status PipePosix::Write(const void *buf, size_t size, size_t &bytes_written) {
281 bytes_written = 0;
282 if (!CanWrite())
283 return Status(EINVAL, eErrorTypePOSIX);
284
285 const int fd = GetWriteFileDescriptor();
286 SelectHelper select_helper;
287 select_helper.SetTimeout(std::chrono::seconds(0));
288 select_helper.FDSetWrite(fd);
289
290 Status error;
291 while (error.Success()) {
292 error = select_helper.Select();
293 if (error.Success()) {
294 auto result = ::write(fd, static_cast<const char *>(buf) + bytes_written,
295 size - bytes_written);
296 if (result != -1) {
297 bytes_written += result;
298 if (bytes_written == size)
299 break;
300 } else if (errno == EINTR) {
301 continue;
302 } else {
303 error.SetErrorToErrno();
304 }
305 }
306 }
307 return error;
308 }
309