summaryrefslogtreecommitdiff
path: root/source/Host/posix/PipePosix.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'source/Host/posix/PipePosix.cpp')
-rw-r--r--source/Host/posix/PipePosix.cpp378
1 files changed, 378 insertions, 0 deletions
diff --git a/source/Host/posix/PipePosix.cpp b/source/Host/posix/PipePosix.cpp
new file mode 100644
index 0000000000000..02838ec5124e7
--- /dev/null
+++ b/source/Host/posix/PipePosix.cpp
@@ -0,0 +1,378 @@
+//===-- PipePosix.cpp -------------------------------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Host/posix/PipePosix.h"
+#include "lldb/Host/FileSystem.h"
+
+#include <functional>
+#include <thread>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+using namespace lldb;
+using namespace lldb_private;
+
+int PipePosix::kInvalidDescriptor = -1;
+
+enum PIPES { READ, WRITE }; // Constants 0 and 1 for READ and WRITE
+
+// pipe2 is supported by Linux, FreeBSD v10 and higher.
+// TODO: Add more platforms that support pipe2.
+#if defined(__linux__) || (defined(__FreeBSD__) && __FreeBSD__ >= 10)
+#define PIPE2_SUPPORTED 1
+#else
+#define PIPE2_SUPPORTED 0
+#endif
+
+namespace
+{
+
+constexpr auto OPEN_WRITER_SLEEP_TIMEOUT_MSECS = 100;
+
+#if defined(FD_CLOEXEC) && !PIPE2_SUPPORTED
+bool SetCloexecFlag(int fd)
+{
+ int flags = ::fcntl(fd, F_GETFD);
+ if (flags == -1)
+ return false;
+ return (::fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == 0);
+}
+#endif
+
+std::chrono::time_point<std::chrono::steady_clock>
+Now()
+{
+ return std::chrono::steady_clock::now();
+}
+
+Error
+SelectIO(int handle, bool is_read, const std::function<Error(bool&)> &io_handler, const std::chrono::microseconds &timeout)
+{
+ Error error;
+ fd_set fds;
+ bool done = false;
+
+ using namespace std::chrono;
+
+ const auto finish_time = Now() + timeout;
+
+ while (!done)
+ {
+ struct timeval tv = {0, 0};
+ if (timeout != microseconds::zero())
+ {
+ const auto remaining_dur = duration_cast<microseconds>(finish_time - Now());
+ if (remaining_dur.count() <= 0)
+ {
+ error.SetErrorString("timeout exceeded");
+ break;
+ }
+ const auto dur_secs = duration_cast<seconds>(remaining_dur);
+ const auto dur_usecs = remaining_dur % seconds(1);
+
+ tv.tv_sec = dur_secs.count();
+ tv.tv_usec = dur_usecs.count();
+ }
+ else
+ tv.tv_sec = 1;
+
+ FD_ZERO(&fds);
+ FD_SET(handle, &fds);
+
+ const auto retval = ::select(handle + 1,
+ (is_read) ? &fds : nullptr,
+ (is_read) ? nullptr : &fds,
+ nullptr, &tv);
+ if (retval == -1)
+ {
+ if (errno == EINTR)
+ continue;
+ error.SetErrorToErrno();
+ break;
+ }
+ if (retval == 0)
+ {
+ error.SetErrorString("timeout exceeded");
+ break;
+ }
+ if (!FD_ISSET(handle, &fds))
+ {
+ error.SetErrorString("invalid state");
+ break;
+ }
+
+ error = io_handler(done);
+ if (error.Fail())
+ {
+ if (error.GetError() == EINTR)
+ continue;
+ break;
+ }
+ }
+ return error;
+}
+
+}
+
+PipePosix::PipePosix()
+{
+ m_fds[READ] = PipePosix::kInvalidDescriptor;
+ m_fds[WRITE] = PipePosix::kInvalidDescriptor;
+}
+
+PipePosix::~PipePosix()
+{
+ Close();
+}
+
+Error
+PipePosix::CreateNew(bool child_processes_inherit)
+{
+ if (CanRead() || CanWrite())
+ return Error(EINVAL, eErrorTypePOSIX);
+
+ Error error;
+#if PIPE2_SUPPORTED
+ if (::pipe2(m_fds, (child_processes_inherit) ? 0 : O_CLOEXEC) == 0)
+ return error;
+#else
+ if (::pipe(m_fds) == 0)
+ {
+#ifdef FD_CLOEXEC
+ if (!child_processes_inherit)
+ {
+ if (!SetCloexecFlag(m_fds[0]) || !SetCloexecFlag(m_fds[1]))
+ {
+ error.SetErrorToErrno();
+ Close();
+ return error;
+ }
+ }
+#endif
+ return error;
+ }
+#endif
+
+ error.SetErrorToErrno();
+ m_fds[READ] = PipePosix::kInvalidDescriptor;
+ m_fds[WRITE] = PipePosix::kInvalidDescriptor;
+ return error;
+}
+
+Error
+PipePosix::CreateNew(llvm::StringRef name, bool child_process_inherit)
+{
+ if (CanRead() || CanWrite())
+ return Error("Pipe is already opened");
+
+ Error error;
+ if (::mkfifo(name.data(), 0660) != 0)
+ error.SetErrorToErrno();
+
+ return error;
+}
+
+Error
+PipePosix::OpenAsReader(llvm::StringRef name, bool child_process_inherit)
+{
+ if (CanRead() || CanWrite())
+ return Error("Pipe is already opened");
+
+ int flags = O_RDONLY | O_NONBLOCK;
+ if (!child_process_inherit)
+ flags |= O_CLOEXEC;
+
+ Error error;
+ int fd = ::open(name.data(), flags);
+ if (fd != -1)
+ m_fds[READ] = fd;
+ else
+ error.SetErrorToErrno();
+
+ return error;
+}
+
+Error
+PipePosix::OpenAsWriterWithTimeout(llvm::StringRef name, bool child_process_inherit, const std::chrono::microseconds &timeout)
+{
+ if (CanRead() || CanWrite())
+ return Error("Pipe is already opened");
+
+ int flags = O_WRONLY | O_NONBLOCK;
+ if (!child_process_inherit)
+ flags |= O_CLOEXEC;
+
+ using namespace std::chrono;
+ const auto finish_time = Now() + timeout;
+
+ while (!CanWrite())
+ {
+ if (timeout != microseconds::zero())
+ {
+ const auto dur = duration_cast<microseconds>(finish_time - Now()).count();
+ if (dur <= 0)
+ return Error("timeout exceeded - reader hasn't opened so far");
+ }
+
+ errno = 0;
+ int fd = ::open(name.data(), flags);
+ if (fd == -1)
+ {
+ const auto errno_copy = errno;
+ // We may get ENXIO if a reader side of the pipe hasn't opened yet.
+ if (errno_copy != ENXIO)
+ return Error(errno_copy, eErrorTypePOSIX);
+
+ std::this_thread::sleep_for(milliseconds(OPEN_WRITER_SLEEP_TIMEOUT_MSECS));
+ }
+ else
+ {
+ m_fds[WRITE] = fd;
+ }
+ }
+
+ return Error();
+}
+
+int
+PipePosix::GetReadFileDescriptor() const
+{
+ return m_fds[READ];
+}
+
+int
+PipePosix::GetWriteFileDescriptor() const
+{
+ return m_fds[WRITE];
+}
+
+int
+PipePosix::ReleaseReadFileDescriptor()
+{
+ const int fd = m_fds[READ];
+ m_fds[READ] = PipePosix::kInvalidDescriptor;
+ return fd;
+}
+
+int
+PipePosix::ReleaseWriteFileDescriptor()
+{
+ const int fd = m_fds[WRITE];
+ m_fds[WRITE] = PipePosix::kInvalidDescriptor;
+ return fd;
+}
+
+void
+PipePosix::Close()
+{
+ CloseReadFileDescriptor();
+ CloseWriteFileDescriptor();
+}
+
+Error
+PipePosix::Delete(llvm::StringRef name)
+{
+ return FileSystem::Unlink(name.data());
+}
+
+bool
+PipePosix::CanRead() const
+{
+ return m_fds[READ] != PipePosix::kInvalidDescriptor;
+}
+
+bool
+PipePosix::CanWrite() const
+{
+ return m_fds[WRITE] != PipePosix::kInvalidDescriptor;
+}
+
+void
+PipePosix::CloseReadFileDescriptor()
+{
+ if (CanRead())
+ {
+ close(m_fds[READ]);
+ m_fds[READ] = PipePosix::kInvalidDescriptor;
+ }
+}
+
+void
+PipePosix::CloseWriteFileDescriptor()
+{
+ if (CanWrite())
+ {
+ close(m_fds[WRITE]);
+ m_fds[WRITE] = PipePosix::kInvalidDescriptor;
+ }
+}
+
+Error
+PipePosix::ReadWithTimeout(void *buf, size_t size, const std::chrono::microseconds &timeout, size_t &bytes_read)
+{
+ bytes_read = 0;
+ if (!CanRead())
+ return Error(EINVAL, eErrorTypePOSIX);
+
+ auto handle = GetReadFileDescriptor();
+ return SelectIO(handle,
+ true,
+ [=, &bytes_read](bool &done)
+ {
+ Error error;
+ auto result = ::read(handle,
+ reinterpret_cast<char*>(buf) + bytes_read,
+ size - bytes_read);
+ if (result != -1)
+ {
+ bytes_read += result;
+ if (bytes_read == size || result == 0)
+ done = true;
+ }
+ else
+ error.SetErrorToErrno();
+
+ return error;
+ },
+ timeout);
+}
+
+Error
+PipePosix::Write(const void *buf, size_t size, size_t &bytes_written)
+{
+ bytes_written = 0;
+ if (!CanWrite())
+ return Error(EINVAL, eErrorTypePOSIX);
+
+ auto handle = GetWriteFileDescriptor();
+ return SelectIO(handle,
+ false,
+ [=, &bytes_written](bool &done)
+ {
+ Error error;
+ auto result = ::write(handle,
+ reinterpret_cast<const char*>(buf) + bytes_written,
+ size - bytes_written);
+ if (result != -1)
+ {
+ bytes_written += result;
+ if (bytes_written == size)
+ done = true;
+ }
+ else
+ error.SetErrorToErrno();
+
+ return error;
+ },
+ std::chrono::microseconds::zero());
+}