//===-- Terminal.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/Terminal.h" #include "lldb/Host/Config.h" #include "lldb/Host/PosixApi.h" #include "llvm/ADT/STLExtras.h" #include #include #include #if LLDB_ENABLE_TERMIOS #include #endif using namespace lldb_private; struct Terminal::Data { #if LLDB_ENABLE_TERMIOS struct termios m_termios; ///< Cached terminal state information. #endif }; bool Terminal::IsATerminal() const { return m_fd >= 0 && ::isatty(m_fd); } #if !LLDB_ENABLE_TERMIOS static llvm::Error termiosMissingError() { return llvm::createStringError(llvm::inconvertibleErrorCode(), "termios support missing in LLDB"); } #endif llvm::Expected Terminal::GetData() { #if LLDB_ENABLE_TERMIOS if (!FileDescriptorIsValid()) return llvm::createStringError(llvm::inconvertibleErrorCode(), "invalid fd"); if (!IsATerminal()) return llvm::createStringError(llvm::inconvertibleErrorCode(), "fd not a terminal"); Data data; if (::tcgetattr(m_fd, &data.m_termios) != 0) return llvm::createStringError( std::error_code(errno, std::generic_category()), "unable to get teletype attributes"); return data; #else // !LLDB_ENABLE_TERMIOS return termiosMissingError(); #endif // LLDB_ENABLE_TERMIOS } llvm::Error Terminal::SetData(const Terminal::Data &data) { #if LLDB_ENABLE_TERMIOS assert(FileDescriptorIsValid()); assert(IsATerminal()); if (::tcsetattr(m_fd, TCSANOW, &data.m_termios) != 0) return llvm::createStringError( std::error_code(errno, std::generic_category()), "unable to set teletype attributes"); return llvm::Error::success(); #else // !LLDB_ENABLE_TERMIOS return termiosMissingError(); #endif // LLDB_ENABLE_TERMIOS } llvm::Error Terminal::SetEcho(bool enabled) { #if LLDB_ENABLE_TERMIOS llvm::Expected data = GetData(); if (!data) return data.takeError(); struct termios &fd_termios = data->m_termios; fd_termios.c_lflag &= ~ECHO; if (enabled) fd_termios.c_lflag |= ECHO; return SetData(data.get()); #else // !LLDB_ENABLE_TERMIOS return termiosMissingError(); #endif // LLDB_ENABLE_TERMIOS } llvm::Error Terminal::SetCanonical(bool enabled) { #if LLDB_ENABLE_TERMIOS llvm::Expected data = GetData(); if (!data) return data.takeError(); struct termios &fd_termios = data->m_termios; fd_termios.c_lflag &= ~ICANON; if (enabled) fd_termios.c_lflag |= ICANON; return SetData(data.get()); #else // !LLDB_ENABLE_TERMIOS return termiosMissingError(); #endif // LLDB_ENABLE_TERMIOS } llvm::Error Terminal::SetRaw() { #if LLDB_ENABLE_TERMIOS llvm::Expected data = GetData(); if (!data) return data.takeError(); struct termios &fd_termios = data->m_termios; ::cfmakeraw(&fd_termios); // Make sure only one character is needed to return from a read // (cfmakeraw() doesn't do this on NetBSD) fd_termios.c_cc[VMIN] = 1; fd_termios.c_cc[VTIME] = 0; return SetData(data.get()); #else // !LLDB_ENABLE_TERMIOS return termiosMissingError(); #endif // LLDB_ENABLE_TERMIOS } #if LLDB_ENABLE_TERMIOS static std::optional baudRateToConst(unsigned int baud_rate) { switch (baud_rate) { #if defined(B50) case 50: return B50; #endif #if defined(B75) case 75: return B75; #endif #if defined(B110) case 110: return B110; #endif #if defined(B134) case 134: return B134; #endif #if defined(B150) case 150: return B150; #endif #if defined(B200) case 200: return B200; #endif #if defined(B300) case 300: return B300; #endif #if defined(B600) case 600: return B600; #endif #if defined(B1200) case 1200: return B1200; #endif #if defined(B1800) case 1800: return B1800; #endif #if defined(B2400) case 2400: return B2400; #endif #if defined(B4800) case 4800: return B4800; #endif #if defined(B9600) case 9600: return B9600; #endif #if defined(B19200) case 19200: return B19200; #endif #if defined(B38400) case 38400: return B38400; #endif #if defined(B57600) case 57600: return B57600; #endif #if defined(B115200) case 115200: return B115200; #endif #if defined(B230400) case 230400: return B230400; #endif #if defined(B460800) case 460800: return B460800; #endif #if defined(B500000) case 500000: return B500000; #endif #if defined(B576000) case 576000: return B576000; #endif #if defined(B921600) case 921600: return B921600; #endif #if defined(B1000000) case 1000000: return B1000000; #endif #if defined(B1152000) case 1152000: return B1152000; #endif #if defined(B1500000) case 1500000: return B1500000; #endif #if defined(B2000000) case 2000000: return B2000000; #endif #if defined(B76800) case 76800: return B76800; #endif #if defined(B153600) case 153600: return B153600; #endif #if defined(B307200) case 307200: return B307200; #endif #if defined(B614400) case 614400: return B614400; #endif #if defined(B2500000) case 2500000: return B2500000; #endif #if defined(B3000000) case 3000000: return B3000000; #endif #if defined(B3500000) case 3500000: return B3500000; #endif #if defined(B4000000) case 4000000: return B4000000; #endif default: return std::nullopt; } } #endif llvm::Error Terminal::SetBaudRate(unsigned int baud_rate) { #if LLDB_ENABLE_TERMIOS llvm::Expected data = GetData(); if (!data) return data.takeError(); struct termios &fd_termios = data->m_termios; std::optional val = baudRateToConst(baud_rate); if (!val) // invalid value return llvm::createStringError(llvm::inconvertibleErrorCode(), "baud rate %d unsupported by the platform", baud_rate); if (::cfsetispeed(&fd_termios, *val) != 0) return llvm::createStringError( std::error_code(errno, std::generic_category()), "setting input baud rate failed"); if (::cfsetospeed(&fd_termios, *val) != 0) return llvm::createStringError( std::error_code(errno, std::generic_category()), "setting output baud rate failed"); return SetData(data.get()); #else // !LLDB_ENABLE_TERMIOS return termiosMissingError(); #endif // LLDB_ENABLE_TERMIOS } llvm::Error Terminal::SetStopBits(unsigned int stop_bits) { #if LLDB_ENABLE_TERMIOS llvm::Expected data = GetData(); if (!data) return data.takeError(); struct termios &fd_termios = data->m_termios; switch (stop_bits) { case 1: fd_termios.c_cflag &= ~CSTOPB; break; case 2: fd_termios.c_cflag |= CSTOPB; break; default: return llvm::createStringError( llvm::inconvertibleErrorCode(), "invalid stop bit count: %d (must be 1 or 2)", stop_bits); } return SetData(data.get()); #else // !LLDB_ENABLE_TERMIOS return termiosMissingError(); #endif // LLDB_ENABLE_TERMIOS } llvm::Error Terminal::SetParity(Terminal::Parity parity) { #if LLDB_ENABLE_TERMIOS llvm::Expected data = GetData(); if (!data) return data.takeError(); struct termios &fd_termios = data->m_termios; fd_termios.c_cflag &= ~( #if defined(CMSPAR) CMSPAR | #endif PARENB | PARODD); if (parity != Parity::No) { fd_termios.c_cflag |= PARENB; if (parity == Parity::Odd || parity == Parity::Mark) fd_termios.c_cflag |= PARODD; if (parity == Parity::Mark || parity == Parity::Space) { #if defined(CMSPAR) fd_termios.c_cflag |= CMSPAR; #else return llvm::createStringError( llvm::inconvertibleErrorCode(), "space/mark parity is not supported by the platform"); #endif } } return SetData(data.get()); #else // !LLDB_ENABLE_TERMIOS return termiosMissingError(); #endif // LLDB_ENABLE_TERMIOS } llvm::Error Terminal::SetParityCheck(Terminal::ParityCheck parity_check) { #if LLDB_ENABLE_TERMIOS llvm::Expected data = GetData(); if (!data) return data.takeError(); struct termios &fd_termios = data->m_termios; fd_termios.c_iflag &= ~(IGNPAR | PARMRK | INPCK); if (parity_check != ParityCheck::No) { fd_termios.c_iflag |= INPCK; if (parity_check == ParityCheck::Ignore) fd_termios.c_iflag |= IGNPAR; else if (parity_check == ParityCheck::Mark) fd_termios.c_iflag |= PARMRK; } return SetData(data.get()); #else // !LLDB_ENABLE_TERMIOS return termiosMissingError(); #endif // LLDB_ENABLE_TERMIOS } llvm::Error Terminal::SetHardwareFlowControl(bool enabled) { #if LLDB_ENABLE_TERMIOS llvm::Expected data = GetData(); if (!data) return data.takeError(); #if defined(CRTSCTS) struct termios &fd_termios = data->m_termios; fd_termios.c_cflag &= ~CRTSCTS; if (enabled) fd_termios.c_cflag |= CRTSCTS; return SetData(data.get()); #else // !defined(CRTSCTS) if (enabled) return llvm::createStringError( llvm::inconvertibleErrorCode(), "hardware flow control is not supported by the platform"); return llvm::Error::success(); #endif // defined(CRTSCTS) #else // !LLDB_ENABLE_TERMIOS return termiosMissingError(); #endif // LLDB_ENABLE_TERMIOS } TerminalState::TerminalState(Terminal term, bool save_process_group) : m_tty(term) { Save(term, save_process_group); } TerminalState::~TerminalState() { Restore(); } void TerminalState::Clear() { m_tty.Clear(); m_tflags = -1; m_data.reset(); m_process_group = -1; } bool TerminalState::Save(Terminal term, bool save_process_group) { Clear(); m_tty = term; if (m_tty.IsATerminal()) { #if LLDB_ENABLE_POSIX int fd = m_tty.GetFileDescriptor(); m_tflags = ::fcntl(fd, F_GETFL, 0); #if LLDB_ENABLE_TERMIOS std::unique_ptr new_data{new Terminal::Data()}; if (::tcgetattr(fd, &new_data->m_termios) == 0) m_data = std::move(new_data); #endif // LLDB_ENABLE_TERMIOS if (save_process_group) m_process_group = ::tcgetpgrp(fd); #endif // LLDB_ENABLE_POSIX } return IsValid(); } bool TerminalState::Restore() const { #if LLDB_ENABLE_POSIX if (IsValid()) { const int fd = m_tty.GetFileDescriptor(); if (TFlagsIsValid()) fcntl(fd, F_SETFL, m_tflags); #if LLDB_ENABLE_TERMIOS if (TTYStateIsValid()) tcsetattr(fd, TCSANOW, &m_data->m_termios); #endif // LLDB_ENABLE_TERMIOS if (ProcessGroupIsValid()) { // Save the original signal handler. void (*saved_sigttou_callback)(int) = nullptr; saved_sigttou_callback = (void (*)(int))signal(SIGTTOU, SIG_IGN); // Set the process group tcsetpgrp(fd, m_process_group); // Restore the original signal handler. signal(SIGTTOU, saved_sigttou_callback); } return true; } #endif // LLDB_ENABLE_POSIX return false; } bool TerminalState::IsValid() const { return m_tty.FileDescriptorIsValid() && (TFlagsIsValid() || TTYStateIsValid() || ProcessGroupIsValid()); } bool TerminalState::TFlagsIsValid() const { return m_tflags != -1; } bool TerminalState::TTYStateIsValid() const { return bool(m_data); } bool TerminalState::ProcessGroupIsValid() const { return static_cast(m_process_group) != -1; }