475 lines · cpp
1//===-- Terminal.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/Terminal.h"10 11#include "lldb/Host/Config.h"12#include "lldb/Host/PosixApi.h"13#include "llvm/ADT/STLExtras.h"14 15#include <csignal>16#include <fcntl.h>17#include <optional>18 19#if LLDB_ENABLE_TERMIOS20#include <termios.h>21#endif22 23using namespace lldb_private;24 25struct Terminal::Data {26#if LLDB_ENABLE_TERMIOS27 struct termios m_termios; ///< Cached terminal state information.28#endif29};30 31bool Terminal::IsATerminal() const { return m_fd >= 0 && ::isatty(m_fd); }32 33#if !LLDB_ENABLE_TERMIOS34static llvm::Error termiosMissingError() {35 return llvm::createStringError(llvm::inconvertibleErrorCode(),36 "termios support missing in LLDB");37}38#endif39 40llvm::Expected<Terminal::Data> Terminal::GetData() {41#if LLDB_ENABLE_TERMIOS42 if (!FileDescriptorIsValid())43 return llvm::createStringError(llvm::inconvertibleErrorCode(),44 "invalid fd");45 46 if (!IsATerminal())47 return llvm::createStringError(llvm::inconvertibleErrorCode(),48 "fd not a terminal");49 50 Data data;51 if (::tcgetattr(m_fd, &data.m_termios) != 0)52 return llvm::createStringError(53 std::error_code(errno, std::generic_category()),54 "unable to get teletype attributes");55 return data;56#else // !LLDB_ENABLE_TERMIOS57 return termiosMissingError();58#endif // LLDB_ENABLE_TERMIOS59}60 61llvm::Error Terminal::SetData(const Terminal::Data &data) {62#if LLDB_ENABLE_TERMIOS63 assert(FileDescriptorIsValid());64 assert(IsATerminal());65 66 if (::tcsetattr(m_fd, TCSANOW, &data.m_termios) != 0)67 return llvm::createStringError(68 std::error_code(errno, std::generic_category()),69 "unable to set teletype attributes");70 return llvm::Error::success();71#else // !LLDB_ENABLE_TERMIOS72 return termiosMissingError();73#endif // LLDB_ENABLE_TERMIOS74}75 76llvm::Error Terminal::SetEcho(bool enabled) {77#if LLDB_ENABLE_TERMIOS78 llvm::Expected<Data> data = GetData();79 if (!data)80 return data.takeError();81 82 struct termios &fd_termios = data->m_termios;83 fd_termios.c_lflag &= ~ECHO;84 if (enabled)85 fd_termios.c_lflag |= ECHO;86 return SetData(data.get());87#else // !LLDB_ENABLE_TERMIOS88 return termiosMissingError();89#endif // LLDB_ENABLE_TERMIOS90}91 92llvm::Error Terminal::SetCanonical(bool enabled) {93#if LLDB_ENABLE_TERMIOS94 llvm::Expected<Data> data = GetData();95 if (!data)96 return data.takeError();97 98 struct termios &fd_termios = data->m_termios;99 fd_termios.c_lflag &= ~ICANON;100 if (enabled)101 fd_termios.c_lflag |= ICANON;102 return SetData(data.get());103#else // !LLDB_ENABLE_TERMIOS104 return termiosMissingError();105#endif // LLDB_ENABLE_TERMIOS106}107 108llvm::Error Terminal::SetRaw() {109#if LLDB_ENABLE_TERMIOS110 llvm::Expected<Data> data = GetData();111 if (!data)112 return data.takeError();113 114 struct termios &fd_termios = data->m_termios;115 ::cfmakeraw(&fd_termios);116 117 // Make sure only one character is needed to return from a read118 // (cfmakeraw() doesn't do this on NetBSD)119 fd_termios.c_cc[VMIN] = 1;120 fd_termios.c_cc[VTIME] = 0;121 122 return SetData(data.get());123#else // !LLDB_ENABLE_TERMIOS124 return termiosMissingError();125#endif // LLDB_ENABLE_TERMIOS126}127 128#if LLDB_ENABLE_TERMIOS129static std::optional<speed_t> baudRateToConst(unsigned int baud_rate) {130 switch (baud_rate) {131#if defined(B50)132 case 50:133 return B50;134#endif135#if defined(B75)136 case 75:137 return B75;138#endif139#if defined(B110)140 case 110:141 return B110;142#endif143#if defined(B134)144 case 134:145 return B134;146#endif147#if defined(B150)148 case 150:149 return B150;150#endif151#if defined(B200)152 case 200:153 return B200;154#endif155#if defined(B300)156 case 300:157 return B300;158#endif159#if defined(B600)160 case 600:161 return B600;162#endif163#if defined(B1200)164 case 1200:165 return B1200;166#endif167#if defined(B1800)168 case 1800:169 return B1800;170#endif171#if defined(B2400)172 case 2400:173 return B2400;174#endif175#if defined(B4800)176 case 4800:177 return B4800;178#endif179#if defined(B9600)180 case 9600:181 return B9600;182#endif183#if defined(B19200)184 case 19200:185 return B19200;186#endif187#if defined(B38400)188 case 38400:189 return B38400;190#endif191#if defined(B57600)192 case 57600:193 return B57600;194#endif195#if defined(B115200)196 case 115200:197 return B115200;198#endif199#if defined(B230400)200 case 230400:201 return B230400;202#endif203#if defined(B460800)204 case 460800:205 return B460800;206#endif207#if defined(B500000)208 case 500000:209 return B500000;210#endif211#if defined(B576000)212 case 576000:213 return B576000;214#endif215#if defined(B921600)216 case 921600:217 return B921600;218#endif219#if defined(B1000000)220 case 1000000:221 return B1000000;222#endif223#if defined(B1152000)224 case 1152000:225 return B1152000;226#endif227#if defined(B1500000)228 case 1500000:229 return B1500000;230#endif231#if defined(B2000000)232 case 2000000:233 return B2000000;234#endif235#if defined(B76800)236 case 76800:237 return B76800;238#endif239#if defined(B153600)240 case 153600:241 return B153600;242#endif243#if defined(B307200)244 case 307200:245 return B307200;246#endif247#if defined(B614400)248 case 614400:249 return B614400;250#endif251#if defined(B2500000)252 case 2500000:253 return B2500000;254#endif255#if defined(B3000000)256 case 3000000:257 return B3000000;258#endif259#if defined(B3500000)260 case 3500000:261 return B3500000;262#endif263#if defined(B4000000)264 case 4000000:265 return B4000000;266#endif267 default:268 return std::nullopt;269 }270}271#endif272 273llvm::Error Terminal::SetBaudRate(unsigned int baud_rate) {274#if LLDB_ENABLE_TERMIOS275 llvm::Expected<Data> data = GetData();276 if (!data)277 return data.takeError();278 279 struct termios &fd_termios = data->m_termios;280 std::optional<speed_t> val = baudRateToConst(baud_rate);281 if (!val) // invalid value282 return llvm::createStringError(llvm::inconvertibleErrorCode(),283 "baud rate %d unsupported by the platform",284 baud_rate);285 if (::cfsetispeed(&fd_termios, *val) != 0)286 return llvm::createStringError(287 std::error_code(errno, std::generic_category()),288 "setting input baud rate failed");289 if (::cfsetospeed(&fd_termios, *val) != 0)290 return llvm::createStringError(291 std::error_code(errno, std::generic_category()),292 "setting output baud rate failed");293 return SetData(data.get());294#else // !LLDB_ENABLE_TERMIOS295 return termiosMissingError();296#endif // LLDB_ENABLE_TERMIOS297}298 299llvm::Error Terminal::SetStopBits(unsigned int stop_bits) {300#if LLDB_ENABLE_TERMIOS301 llvm::Expected<Data> data = GetData();302 if (!data)303 return data.takeError();304 305 struct termios &fd_termios = data->m_termios;306 switch (stop_bits) {307 case 1:308 fd_termios.c_cflag &= ~CSTOPB;309 break;310 case 2:311 fd_termios.c_cflag |= CSTOPB;312 break;313 default:314 return llvm::createStringError(315 llvm::inconvertibleErrorCode(),316 "invalid stop bit count: %d (must be 1 or 2)", stop_bits);317 }318 return SetData(data.get());319#else // !LLDB_ENABLE_TERMIOS320 return termiosMissingError();321#endif // LLDB_ENABLE_TERMIOS322}323 324llvm::Error Terminal::SetParity(Terminal::Parity parity) {325#if LLDB_ENABLE_TERMIOS326 llvm::Expected<Data> data = GetData();327 if (!data)328 return data.takeError();329 330 struct termios &fd_termios = data->m_termios;331 fd_termios.c_cflag &= ~(332#if defined(CMSPAR)333 CMSPAR |334#endif335 PARENB | PARODD);336 337 if (parity != Parity::No) {338 fd_termios.c_cflag |= PARENB;339 if (parity == Parity::Odd || parity == Parity::Mark)340 fd_termios.c_cflag |= PARODD;341 if (parity == Parity::Mark || parity == Parity::Space) {342#if defined(CMSPAR)343 fd_termios.c_cflag |= CMSPAR;344#else345 return llvm::createStringError(346 llvm::inconvertibleErrorCode(),347 "space/mark parity is not supported by the platform");348#endif349 }350 }351 return SetData(data.get());352#else // !LLDB_ENABLE_TERMIOS353 return termiosMissingError();354#endif // LLDB_ENABLE_TERMIOS355}356 357llvm::Error Terminal::SetParityCheck(Terminal::ParityCheck parity_check) {358#if LLDB_ENABLE_TERMIOS359 llvm::Expected<Data> data = GetData();360 if (!data)361 return data.takeError();362 363 struct termios &fd_termios = data->m_termios;364 fd_termios.c_iflag &= ~(IGNPAR | PARMRK | INPCK);365 366 if (parity_check != ParityCheck::No) {367 fd_termios.c_iflag |= INPCK;368 if (parity_check == ParityCheck::Ignore)369 fd_termios.c_iflag |= IGNPAR;370 else if (parity_check == ParityCheck::Mark)371 fd_termios.c_iflag |= PARMRK;372 }373 return SetData(data.get());374#else // !LLDB_ENABLE_TERMIOS375 return termiosMissingError();376#endif // LLDB_ENABLE_TERMIOS377}378 379llvm::Error Terminal::SetHardwareFlowControl(bool enabled) {380#if LLDB_ENABLE_TERMIOS381 llvm::Expected<Data> data = GetData();382 if (!data)383 return data.takeError();384 385#if defined(CRTSCTS)386 struct termios &fd_termios = data->m_termios;387 fd_termios.c_cflag &= ~CRTSCTS;388 if (enabled)389 fd_termios.c_cflag |= CRTSCTS;390 return SetData(data.get());391#else // !defined(CRTSCTS)392 if (enabled)393 return llvm::createStringError(394 llvm::inconvertibleErrorCode(),395 "hardware flow control is not supported by the platform");396 return llvm::Error::success();397#endif // defined(CRTSCTS)398#else // !LLDB_ENABLE_TERMIOS399 return termiosMissingError();400#endif // LLDB_ENABLE_TERMIOS401}402 403TerminalState::TerminalState(Terminal term, bool save_process_group)404 : m_tty(term) {405 Save(term, save_process_group);406}407 408TerminalState::~TerminalState() { Restore(); }409 410void TerminalState::Clear() {411 m_tty.Clear();412 m_tflags = -1;413 m_data.reset();414 m_process_group = -1;415}416 417bool TerminalState::Save(Terminal term, bool save_process_group) {418 Clear();419 m_tty = term;420 if (m_tty.IsATerminal()) {421#if LLDB_ENABLE_POSIX422 int fd = m_tty.GetFileDescriptor();423 m_tflags = ::fcntl(fd, F_GETFL, 0);424#if LLDB_ENABLE_TERMIOS425 std::unique_ptr<Terminal::Data> new_data{new Terminal::Data()};426 if (::tcgetattr(fd, &new_data->m_termios) == 0)427 m_data = std::move(new_data);428#endif // LLDB_ENABLE_TERMIOS429 if (save_process_group)430 m_process_group = ::tcgetpgrp(fd);431#endif // LLDB_ENABLE_POSIX432 }433 return IsValid();434}435 436bool TerminalState::Restore() const {437#if LLDB_ENABLE_POSIX438 if (IsValid()) {439 const int fd = m_tty.GetFileDescriptor();440 if (TFlagsIsValid())441 fcntl(fd, F_SETFL, m_tflags);442 443#if LLDB_ENABLE_TERMIOS444 if (TTYStateIsValid())445 tcsetattr(fd, TCSANOW, &m_data->m_termios);446#endif // LLDB_ENABLE_TERMIOS447 448 if (ProcessGroupIsValid()) {449 // Save the original signal handler.450 void (*saved_sigttou_callback)(int) = nullptr;451 saved_sigttou_callback = (void (*)(int))signal(SIGTTOU, SIG_IGN);452 // Set the process group453 tcsetpgrp(fd, m_process_group);454 // Restore the original signal handler.455 signal(SIGTTOU, saved_sigttou_callback);456 }457 return true;458 }459#endif // LLDB_ENABLE_POSIX460 return false;461}462 463bool TerminalState::IsValid() const {464 return m_tty.FileDescriptorIsValid() &&465 (TFlagsIsValid() || TTYStateIsValid() || ProcessGroupIsValid());466}467 468bool TerminalState::TFlagsIsValid() const { return m_tflags != -1; }469 470bool TerminalState::TTYStateIsValid() const { return bool(m_data); }471 472bool TerminalState::ProcessGroupIsValid() const {473 return static_cast<int32_t>(m_process_group) != -1;474}475