346 lines · cpp
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-exception6//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 "llvm/Support/Error.h"16#include <functional>17#include <system_error>18#include <thread>19 20#include <cerrno>21#include <climits>22#include <fcntl.h>23#include <sys/stat.h>24#include <sys/types.h>25#include <unistd.h>26 27using namespace lldb;28using namespace lldb_private;29 30int PipePosix::kInvalidDescriptor = -1;31 32enum PIPES { READ, WRITE }; // Constants 0 and 1 for READ and WRITE33 34// pipe2 is supported by a limited set of platforms35// TODO: Add more platforms that support pipe2.36#if defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) || \37 defined(__OpenBSD__)38#define PIPE2_SUPPORTED 139#else40#define PIPE2_SUPPORTED 041#endif42 43static constexpr auto OPEN_WRITER_SLEEP_TIMEOUT_MSECS = 100;44 45#if defined(FD_CLOEXEC) && !PIPE2_SUPPORTED46static 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#endif53 54static std::chrono::time_point<std::chrono::steady_clock> Now() {55 return std::chrono::steady_clock::now();56}57 58PipePosix::PipePosix()59 : m_fds{PipePosix::kInvalidDescriptor, PipePosix::kInvalidDescriptor} {}60 61PipePosix::PipePosix(lldb::pipe_t read, lldb::pipe_t write)62 : m_fds{read, write} {}63 64PipePosix::PipePosix(PipePosix &&pipe_posix)65 : PipeBase{std::move(pipe_posix)},66 m_fds{pipe_posix.ReleaseReadFileDescriptor(),67 pipe_posix.ReleaseWriteFileDescriptor()} {}68 69PipePosix &PipePosix::operator=(PipePosix &&pipe_posix) {70 std::scoped_lock<std::mutex, std::mutex, std::mutex, std::mutex> guard(71 m_read_mutex, m_write_mutex, pipe_posix.m_read_mutex,72 pipe_posix.m_write_mutex);73 74 PipeBase::operator=(std::move(pipe_posix));75 m_fds[READ] = pipe_posix.ReleaseReadFileDescriptorUnlocked();76 m_fds[WRITE] = pipe_posix.ReleaseWriteFileDescriptorUnlocked();77 return *this;78}79 80PipePosix::~PipePosix() { Close(); }81 82Status PipePosix::CreateNew() {83 std::scoped_lock<std::mutex, std::mutex> guard(m_read_mutex, m_write_mutex);84 if (CanReadUnlocked() || CanWriteUnlocked())85 return Status(EINVAL, eErrorTypePOSIX);86 87 Status error;88#if PIPE2_SUPPORTED89 if (::pipe2(m_fds, O_CLOEXEC) == 0)90 return error;91#else92 if (::pipe(m_fds) == 0) {93#ifdef FD_CLOEXEC94 if (!SetCloexecFlag(m_fds[0]) || !SetCloexecFlag(m_fds[1])) {95 error = Status::FromErrno();96 CloseUnlocked();97 return error;98 }99#endif100 return error;101 }102#endif103 104 error = Status::FromErrno();105 m_fds[READ] = PipePosix::kInvalidDescriptor;106 m_fds[WRITE] = PipePosix::kInvalidDescriptor;107 return error;108}109 110Status PipePosix::CreateNew(llvm::StringRef name) {111 std::scoped_lock<std::mutex, std::mutex> guard(m_read_mutex, m_write_mutex);112 if (CanReadUnlocked() || CanWriteUnlocked())113 return Status::FromErrorString("Pipe is already opened");114 115 Status error;116 if (::mkfifo(name.str().c_str(), 0660) != 0)117 error = Status::FromErrno();118 return error;119}120 121Status PipePosix::CreateWithUniqueName(llvm::StringRef prefix,122 llvm::SmallVectorImpl<char> &name) {123 llvm::SmallString<128> named_pipe_path;124 llvm::SmallString<128> pipe_spec((prefix + ".%%%%%%").str());125 FileSpec tmpdir_file_spec = HostInfo::GetProcessTempDir();126 if (!tmpdir_file_spec)127 tmpdir_file_spec.AppendPathComponent("/tmp");128 tmpdir_file_spec.AppendPathComponent(pipe_spec);129 130 // It's possible that another process creates the target path after we've131 // verified it's available but before we create it, in which case we should132 // try again.133 Status error;134 do {135 llvm::sys::fs::createUniquePath(tmpdir_file_spec.GetPath(), named_pipe_path,136 /*MakeAbsolute=*/false);137 error = CreateNew(named_pipe_path);138 } while (error.GetError() == EEXIST);139 140 if (error.Success())141 name = named_pipe_path;142 return error;143}144 145Status PipePosix::OpenAsReader(llvm::StringRef name) {146 std::scoped_lock<std::mutex, std::mutex> guard(m_read_mutex, m_write_mutex);147 148 if (CanReadUnlocked() || CanWriteUnlocked())149 return Status::FromErrorString("Pipe is already opened");150 151 int flags = O_RDONLY | O_NONBLOCK | O_CLOEXEC;152 153 Status error;154 int fd = FileSystem::Instance().Open(name.str().c_str(), flags);155 if (fd != -1)156 m_fds[READ] = fd;157 else158 error = Status::FromErrno();159 160 return error;161}162 163llvm::Error PipePosix::OpenAsWriter(llvm::StringRef name,164 const Timeout<std::micro> &timeout) {165 std::lock_guard<std::mutex> guard(m_write_mutex);166 if (CanReadUnlocked() || CanWriteUnlocked())167 return llvm::createStringError("Pipe is already opened");168 169 int flags = O_WRONLY | O_NONBLOCK | O_CLOEXEC;170 171 using namespace std::chrono;172 std::optional<time_point<steady_clock>> finish_time;173 if (timeout)174 finish_time = Now() + *timeout;175 176 while (!CanWriteUnlocked()) {177 if (timeout) {178 if (Now() > finish_time)179 return llvm::createStringError(180 std::make_error_code(std::errc::timed_out),181 "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 llvm::errorCodeToError(191 std::error_code(errno_copy, std::generic_category()));192 193 std::this_thread::sleep_for(194 milliseconds(OPEN_WRITER_SLEEP_TIMEOUT_MSECS));195 } else {196 m_fds[WRITE] = fd;197 }198 }199 200 return llvm::Error::success();201}202 203int PipePosix::GetReadFileDescriptor() const {204 std::lock_guard<std::mutex> guard(m_read_mutex);205 return GetReadFileDescriptorUnlocked();206}207 208int PipePosix::GetReadFileDescriptorUnlocked() const {209 return m_fds[READ];210}211 212int PipePosix::GetWriteFileDescriptor() const {213 std::lock_guard<std::mutex> guard(m_write_mutex);214 return GetWriteFileDescriptorUnlocked();215}216 217int PipePosix::GetWriteFileDescriptorUnlocked() const {218 return m_fds[WRITE];219}220 221int PipePosix::ReleaseReadFileDescriptor() {222 std::lock_guard<std::mutex> guard(m_read_mutex);223 return ReleaseReadFileDescriptorUnlocked();224}225 226int PipePosix::ReleaseReadFileDescriptorUnlocked() {227 const int fd = m_fds[READ];228 m_fds[READ] = PipePosix::kInvalidDescriptor;229 return fd;230}231 232int PipePosix::ReleaseWriteFileDescriptor() {233 std::lock_guard<std::mutex> guard(m_write_mutex);234 return ReleaseWriteFileDescriptorUnlocked();235}236 237int PipePosix::ReleaseWriteFileDescriptorUnlocked() {238 const int fd = m_fds[WRITE];239 m_fds[WRITE] = PipePosix::kInvalidDescriptor;240 return fd;241}242 243void PipePosix::Close() {244 std::scoped_lock<std::mutex, std::mutex> guard(m_read_mutex, m_write_mutex);245 CloseUnlocked();246}247 248void PipePosix::CloseUnlocked() {249 CloseReadFileDescriptorUnlocked();250 CloseWriteFileDescriptorUnlocked();251}252 253Status PipePosix::Delete(llvm::StringRef name) {254 return llvm::sys::fs::remove(name);255}256 257bool PipePosix::CanRead() const {258 std::lock_guard<std::mutex> guard(m_read_mutex);259 return CanReadUnlocked();260}261 262bool PipePosix::CanReadUnlocked() const {263 return m_fds[READ] != PipePosix::kInvalidDescriptor;264}265 266bool PipePosix::CanWrite() const {267 std::lock_guard<std::mutex> guard(m_write_mutex);268 return CanWriteUnlocked();269}270 271bool PipePosix::CanWriteUnlocked() const {272 return m_fds[WRITE] != PipePosix::kInvalidDescriptor;273}274 275void PipePosix::CloseReadFileDescriptor() {276 std::lock_guard<std::mutex> guard(m_read_mutex);277 CloseReadFileDescriptorUnlocked();278}279void PipePosix::CloseReadFileDescriptorUnlocked() {280 if (CanReadUnlocked()) {281 close(m_fds[READ]);282 m_fds[READ] = PipePosix::kInvalidDescriptor;283 }284}285 286void PipePosix::CloseWriteFileDescriptor() {287 std::lock_guard<std::mutex> guard(m_write_mutex);288 CloseWriteFileDescriptorUnlocked();289}290 291void PipePosix::CloseWriteFileDescriptorUnlocked() {292 if (CanWriteUnlocked()) {293 close(m_fds[WRITE]);294 m_fds[WRITE] = PipePosix::kInvalidDescriptor;295 }296}297 298llvm::Expected<size_t> PipePosix::Read(void *buf, size_t size,299 const Timeout<std::micro> &timeout) {300 std::lock_guard<std::mutex> guard(m_read_mutex);301 if (!CanReadUnlocked())302 return llvm::errorCodeToError(303 std::make_error_code(std::errc::invalid_argument));304 305 const int fd = GetReadFileDescriptorUnlocked();306 307 SelectHelper select_helper;308 if (timeout)309 select_helper.SetTimeout(*timeout);310 select_helper.FDSetRead(fd);311 312 if (llvm::Error error = select_helper.Select().takeError())313 return error;314 315 ssize_t result = ::read(fd, buf, size);316 if (result == -1)317 return llvm::errorCodeToError(318 std::error_code(errno, std::generic_category()));319 320 return result;321}322 323llvm::Expected<size_t> PipePosix::Write(const void *buf, size_t size,324 const Timeout<std::micro> &timeout) {325 std::lock_guard<std::mutex> guard(m_write_mutex);326 if (!CanWriteUnlocked())327 return llvm::errorCodeToError(328 std::make_error_code(std::errc::invalid_argument));329 330 const int fd = GetWriteFileDescriptorUnlocked();331 SelectHelper select_helper;332 if (timeout)333 select_helper.SetTimeout(*timeout);334 select_helper.FDSetWrite(fd);335 336 if (llvm::Error error = select_helper.Select().takeError())337 return error;338 339 ssize_t result = ::write(fd, buf, size);340 if (result == -1)341 return llvm::errorCodeToError(342 std::error_code(errno, std::generic_category()));343 344 return result;345}346