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