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 <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 static constexpr auto OPEN_WRITER_SLEEP_TIMEOUT_MSECS = 100;
42 
43 #if defined(FD_CLOEXEC) && !PIPE2_SUPPORTED
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 
52 static std::chrono::time_point<std::chrono::steady_clock> Now() {
53   return std::chrono::steady_clock::now();
54 }
55 
56 PipePosix::PipePosix()
57     : m_fds{PipePosix::kInvalidDescriptor, PipePosix::kInvalidDescriptor} {}
58 
59 PipePosix::PipePosix(lldb::pipe_t read, lldb::pipe_t write)
60     : m_fds{read, write} {}
61 
62 PipePosix::PipePosix(PipePosix &&pipe_posix)
63     : PipeBase{std::move(pipe_posix)},
64       m_fds{pipe_posix.ReleaseReadFileDescriptor(),
65             pipe_posix.ReleaseWriteFileDescriptor()} {}
66 
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 
74 PipePosix::~PipePosix() { Close(); }
75 
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 
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 
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 
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 = FileSystem::Instance().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
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 
199 int PipePosix::GetReadFileDescriptor() const { return m_fds[READ]; }
200 
201 int PipePosix::GetWriteFileDescriptor() const { return m_fds[WRITE]; }
202 
203 int PipePosix::ReleaseReadFileDescriptor() {
204   const int fd = m_fds[READ];
205   m_fds[READ] = PipePosix::kInvalidDescriptor;
206   return fd;
207 }
208 
209 int PipePosix::ReleaseWriteFileDescriptor() {
210   const int fd = m_fds[WRITE];
211   m_fds[WRITE] = PipePosix::kInvalidDescriptor;
212   return fd;
213 }
214 
215 void PipePosix::Close() {
216   CloseReadFileDescriptor();
217   CloseWriteFileDescriptor();
218 }
219 
220 Status PipePosix::Delete(llvm::StringRef name) {
221   return llvm::sys::fs::remove(name);
222 }
223 
224 bool PipePosix::CanRead() const {
225   return m_fds[READ] != PipePosix::kInvalidDescriptor;
226 }
227 
228 bool PipePosix::CanWrite() const {
229   return m_fds[WRITE] != PipePosix::kInvalidDescriptor;
230 }
231 
232 void PipePosix::CloseReadFileDescriptor() {
233   if (CanRead()) {
234     close(m_fds[READ]);
235     m_fds[READ] = PipePosix::kInvalidDescriptor;
236   }
237 }
238 
239 void PipePosix::CloseWriteFileDescriptor() {
240   if (CanWrite()) {
241     close(m_fds[WRITE]);
242     m_fds[WRITE] = PipePosix::kInvalidDescriptor;
243   }
244 }
245 
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 
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