diff options
Diffstat (limited to 'source/Host/posix/PipePosix.cpp')
-rw-r--r-- | source/Host/posix/PipePosix.cpp | 378 |
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()); +} |