//===-- PipePosix.cpp -----------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "lldb/Host/posix/PipePosix.h" #include "lldb/Host/HostInfo.h" #include "lldb/Utility/SelectHelper.h" #include "llvm/ADT/SmallString.h" #include "llvm/Support/Errno.h" #include "llvm/Support/FileSystem.h" #if defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 8)) #ifndef _GLIBCXX_USE_NANOSLEEP #define _GLIBCXX_USE_NANOSLEEP #endif #endif #include #include #include #include #include #include #include #include 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 a limited set of platforms // TODO: Add more platforms that support pipe2. #if defined(__linux__) || (defined(__FreeBSD__) && __FreeBSD__ >= 10) || \ defined(__NetBSD__) #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 Now() { return std::chrono::steady_clock::now(); } } // namespace PipePosix::PipePosix() : m_fds{PipePosix::kInvalidDescriptor, PipePosix::kInvalidDescriptor} {} PipePosix::PipePosix(lldb::pipe_t read, lldb::pipe_t write) : m_fds{read, write} {} PipePosix::PipePosix(PipePosix &&pipe_posix) : PipeBase{std::move(pipe_posix)}, m_fds{pipe_posix.ReleaseReadFileDescriptor(), pipe_posix.ReleaseWriteFileDescriptor()} {} PipePosix &PipePosix::operator=(PipePosix &&pipe_posix) { PipeBase::operator=(std::move(pipe_posix)); m_fds[READ] = pipe_posix.ReleaseReadFileDescriptor(); m_fds[WRITE] = pipe_posix.ReleaseWriteFileDescriptor(); return *this; } PipePosix::~PipePosix() { Close(); } Status PipePosix::CreateNew(bool child_processes_inherit) { if (CanRead() || CanWrite()) return Status(EINVAL, eErrorTypePOSIX); Status 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; } Status PipePosix::CreateNew(llvm::StringRef name, bool child_process_inherit) { if (CanRead() || CanWrite()) return Status("Pipe is already opened"); Status error; if (::mkfifo(name.data(), 0660) != 0) error.SetErrorToErrno(); return error; } Status PipePosix::CreateWithUniqueName(llvm::StringRef prefix, bool child_process_inherit, llvm::SmallVectorImpl &name) { llvm::SmallString<128> named_pipe_path; llvm::SmallString<128> pipe_spec((prefix + ".%%%%%%").str()); FileSpec tmpdir_file_spec = HostInfo::GetProcessTempDir(); if (!tmpdir_file_spec) tmpdir_file_spec.AppendPathComponent("/tmp"); tmpdir_file_spec.AppendPathComponent(pipe_spec); // It's possible that another process creates the target path after we've // verified it's available but before we create it, in which case we should // try again. Status error; do { llvm::sys::fs::createUniqueFile(tmpdir_file_spec.GetPath(), named_pipe_path); error = CreateNew(named_pipe_path, child_process_inherit); } while (error.GetError() == EEXIST); if (error.Success()) name = named_pipe_path; return error; } Status PipePosix::OpenAsReader(llvm::StringRef name, bool child_process_inherit) { if (CanRead() || CanWrite()) return Status("Pipe is already opened"); int flags = O_RDONLY | O_NONBLOCK; if (!child_process_inherit) flags |= O_CLOEXEC; Status error; int fd = llvm::sys::RetryAfterSignal(-1, ::open, name.data(), flags); if (fd != -1) m_fds[READ] = fd; else error.SetErrorToErrno(); return error; } Status PipePosix::OpenAsWriterWithTimeout(llvm::StringRef name, bool child_process_inherit, const std::chrono::microseconds &timeout) { if (CanRead() || CanWrite()) return Status("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(finish_time - Now()).count(); if (dur <= 0) return Status("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 && errno_copy != EINTR) return Status(errno_copy, eErrorTypePOSIX); std::this_thread::sleep_for( milliseconds(OPEN_WRITER_SLEEP_TIMEOUT_MSECS)); } else { m_fds[WRITE] = fd; } } return Status(); } 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(); } Status PipePosix::Delete(llvm::StringRef name) { return llvm::sys::fs::remove(name); } 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; } } Status PipePosix::ReadWithTimeout(void *buf, size_t size, const std::chrono::microseconds &timeout, size_t &bytes_read) { bytes_read = 0; if (!CanRead()) return Status(EINVAL, eErrorTypePOSIX); const int fd = GetReadFileDescriptor(); SelectHelper select_helper; select_helper.SetTimeout(timeout); select_helper.FDSetRead(fd); Status error; while (error.Success()) { error = select_helper.Select(); if (error.Success()) { auto result = ::read(fd, static_cast(buf) + bytes_read, size - bytes_read); if (result != -1) { bytes_read += result; if (bytes_read == size || result == 0) break; } else if (errno == EINTR) { continue; } else { error.SetErrorToErrno(); break; } } } return error; } Status PipePosix::Write(const void *buf, size_t size, size_t &bytes_written) { bytes_written = 0; if (!CanWrite()) return Status(EINVAL, eErrorTypePOSIX); const int fd = GetWriteFileDescriptor(); SelectHelper select_helper; select_helper.SetTimeout(std::chrono::seconds(0)); select_helper.FDSetWrite(fd); Status error; while (error.Success()) { error = select_helper.Select(); if (error.Success()) { auto result = ::write(fd, static_cast(buf) + bytes_written, size - bytes_written); if (result != -1) { bytes_written += result; if (bytes_written == size) break; } else if (errno == EINTR) { continue; } else { error.SetErrorToErrno(); } } } return error; }