//===-- OutputRedirector.cpp -----------------------------------*- C++ -*-===// // // 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 "OutputRedirector.h" #include "DAP.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Error.h" #include #include #if defined(_WIN32) #include #include #else #include #endif using namespace llvm; static constexpr auto kCloseSentinel = StringLiteral::withInnerNUL("\0"); namespace lldb_dap { int OutputRedirector::kInvalidDescriptor = -1; OutputRedirector::OutputRedirector() : m_fd(kInvalidDescriptor), m_original_fd(kInvalidDescriptor), m_restore_fd(kInvalidDescriptor) {} Expected OutputRedirector::GetWriteFileDescriptor() { if (m_fd == kInvalidDescriptor) return createStringError(std::errc::bad_file_descriptor, "write handle is not open for writing"); return m_fd; } Error OutputRedirector::RedirectTo(std::FILE *file_override, std::function callback) { assert(m_fd == kInvalidDescriptor && "Output readirector already started."); int new_fd[2]; #if defined(_WIN32) if (::_pipe(new_fd, OutputBufferSize, O_TEXT) == -1) { #else if (::pipe(new_fd) == -1) { #endif int error = errno; return createStringError(inconvertibleErrorCode(), "Couldn't create new pipe %s", strerror(error)); } int read_fd = new_fd[0]; m_fd = new_fd[1]; if (file_override) { int override_fd = fileno(file_override); // Backup the FD to restore once redirection is complete. m_original_fd = override_fd; m_restore_fd = dup(override_fd); // Override the existing fd the new write end of the pipe. if (::dup2(m_fd, override_fd) == -1) return llvm::errorCodeToError(llvm::errnoAsErrorCode()); } m_forwarder = std::thread([this, callback, read_fd]() { char buffer[OutputBufferSize]; while (!m_stopped) { ssize_t bytes_count = ::read(read_fd, &buffer, sizeof(buffer)); if (bytes_count == -1) { // Skip non-fatal errors. if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK) continue; break; } // Skip the null byte used to trigger a Stop. if (bytes_count == 1 && buffer[0] == '\0') continue; StringRef data(buffer, bytes_count); if (m_stopped) data.consume_back(kCloseSentinel); if (data.empty()) break; callback(data); } ::close(read_fd); }); return Error::success(); } void OutputRedirector::Stop() { m_stopped = true; if (m_fd != kInvalidDescriptor) { int fd = m_fd; m_fd = kInvalidDescriptor; // Closing the pipe may not be sufficient to wake up the thread in case the // write descriptor is duplicated (to stdout/err or to another process). // Write a null byte to ensure the read call returns. (void)::write(fd, kCloseSentinel.data(), kCloseSentinel.size()); ::close(fd); m_forwarder.join(); // Restore the fd back to its original state since we stopped the // redirection. if (m_restore_fd != kInvalidDescriptor && m_original_fd != kInvalidDescriptor) { int restore_fd = m_restore_fd; m_restore_fd = kInvalidDescriptor; int original_fd = m_original_fd; m_original_fd = kInvalidDescriptor; ::dup2(restore_fd, original_fd); } } } } // namespace lldb_dap