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