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-exception 6 // 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 18 #if LLDB_ENABLE_TERMIOS 19 #include <termios.h> 20 #endif 21 22 using namespace lldb_private; 23 24 struct Terminal::Data { 25 #if LLDB_ENABLE_TERMIOS 26 struct termios m_termios; ///< Cached terminal state information. 27 #endif 28 }; 29 30 bool Terminal::IsATerminal() const { return m_fd >= 0 && ::isatty(m_fd); } 31 32 llvm::Expected<Terminal::Data> Terminal::GetData() { 33 if (!FileDescriptorIsValid()) 34 return llvm::createStringError(llvm::inconvertibleErrorCode(), 35 "invalid fd"); 36 37 #if LLDB_ENABLE_TERMIOS 38 if (!IsATerminal()) 39 return llvm::createStringError(llvm::inconvertibleErrorCode(), 40 "fd not a terminal"); 41 42 Data data; 43 if (::tcgetattr(m_fd, &data.m_termios) != 0) 44 return llvm::createStringError( 45 std::error_code(errno, std::generic_category()), 46 "unable to get teletype attributes"); 47 return data; 48 #else // !LLDB_ENABLE_TERMIOS 49 return llvm::createStringError(llvm::inconvertibleErrorCode(), 50 "termios support missing in LLDB"); 51 #endif // LLDB_ENABLE_TERMIOS 52 } 53 54 llvm::Error Terminal::SetData(const Terminal::Data &data) { 55 #if LLDB_ENABLE_TERMIOS 56 assert(FileDescriptorIsValid()); 57 assert(IsATerminal()); 58 59 if (::tcsetattr(m_fd, TCSANOW, &data.m_termios) != 0) 60 return llvm::createStringError( 61 std::error_code(errno, std::generic_category()), 62 "unable to set teletype attributes"); 63 return llvm::Error::success(); 64 #else // !LLDB_ENABLE_TERMIOS 65 llvm_unreachable("SetData() should not be called if !LLDB_ENABLE_TERMIOS"); 66 #endif // LLDB_ENABLE_TERMIOS 67 } 68 69 llvm::Error Terminal::SetEcho(bool enabled) { 70 llvm::Expected<Data> data = GetData(); 71 if (!data) 72 return data.takeError(); 73 74 #if LLDB_ENABLE_TERMIOS 75 struct termios &fd_termios = data->m_termios; 76 fd_termios.c_lflag &= ~ECHO; 77 if (enabled) 78 fd_termios.c_lflag |= ECHO; 79 return SetData(data.get()); 80 #endif // LLDB_ENABLE_TERMIOS 81 } 82 83 llvm::Error Terminal::SetCanonical(bool enabled) { 84 llvm::Expected<Data> data = GetData(); 85 if (!data) 86 return data.takeError(); 87 88 #if LLDB_ENABLE_TERMIOS 89 struct termios &fd_termios = data->m_termios; 90 fd_termios.c_lflag &= ~ICANON; 91 if (enabled) 92 fd_termios.c_lflag |= ICANON; 93 return SetData(data.get()); 94 #endif // LLDB_ENABLE_TERMIOS 95 } 96 97 TerminalState::TerminalState(Terminal term, bool save_process_group) 98 : m_tty(term) { 99 Save(term, save_process_group); 100 } 101 102 TerminalState::~TerminalState() { Restore(); } 103 104 void TerminalState::Clear() { 105 m_tty.Clear(); 106 m_tflags = -1; 107 m_data.reset(); 108 m_process_group = -1; 109 } 110 111 bool TerminalState::Save(Terminal term, bool save_process_group) { 112 Clear(); 113 m_tty = term; 114 if (m_tty.IsATerminal()) { 115 int fd = m_tty.GetFileDescriptor(); 116 #if LLDB_ENABLE_POSIX 117 m_tflags = ::fcntl(fd, F_GETFL, 0); 118 #if LLDB_ENABLE_TERMIOS 119 std::unique_ptr<Terminal::Data> new_data{new Terminal::Data()}; 120 if (::tcgetattr(fd, &new_data->m_termios) == 0) 121 m_data = std::move(new_data); 122 #endif // LLDB_ENABLE_TERMIOS 123 if (save_process_group) 124 m_process_group = ::tcgetpgrp(fd); 125 #endif // LLDB_ENABLE_POSIX 126 } 127 return IsValid(); 128 } 129 130 bool TerminalState::Restore() const { 131 #if LLDB_ENABLE_POSIX 132 if (IsValid()) { 133 const int fd = m_tty.GetFileDescriptor(); 134 if (TFlagsIsValid()) 135 fcntl(fd, F_SETFL, m_tflags); 136 137 #if LLDB_ENABLE_TERMIOS 138 if (TTYStateIsValid()) 139 tcsetattr(fd, TCSANOW, &m_data->m_termios); 140 #endif // LLDB_ENABLE_TERMIOS 141 142 if (ProcessGroupIsValid()) { 143 // Save the original signal handler. 144 void (*saved_sigttou_callback)(int) = nullptr; 145 saved_sigttou_callback = (void (*)(int))signal(SIGTTOU, SIG_IGN); 146 // Set the process group 147 tcsetpgrp(fd, m_process_group); 148 // Restore the original signal handler. 149 signal(SIGTTOU, saved_sigttou_callback); 150 } 151 return true; 152 } 153 #endif // LLDB_ENABLE_POSIX 154 return false; 155 } 156 157 bool TerminalState::IsValid() const { 158 return m_tty.FileDescriptorIsValid() && 159 (TFlagsIsValid() || TTYStateIsValid() || ProcessGroupIsValid()); 160 } 161 162 bool TerminalState::TFlagsIsValid() const { return m_tflags != -1; } 163 164 bool TerminalState::TTYStateIsValid() const { return bool(m_data); } 165 166 bool TerminalState::ProcessGroupIsValid() const { 167 return static_cast<int32_t>(m_process_group) != -1; 168 } 169