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