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
IsATerminal() const24 bool Terminal::IsATerminal() const { return m_fd >= 0 && ::isatty(m_fd); }
25
SetEcho(bool enabled)26 bool Terminal::SetEcho(bool enabled) {
27 if (FileDescriptorIsValid()) {
28 #if LLDB_ENABLE_TERMIOS
29 if (IsATerminal()) {
30 struct termios fd_termios;
31 if (::tcgetattr(m_fd, &fd_termios) == 0) {
32 bool set_corectly = false;
33 if (enabled) {
34 if (fd_termios.c_lflag & ECHO)
35 set_corectly = true;
36 else
37 fd_termios.c_lflag |= ECHO;
38 } else {
39 if (fd_termios.c_lflag & ECHO)
40 fd_termios.c_lflag &= ~ECHO;
41 else
42 set_corectly = true;
43 }
44
45 if (set_corectly)
46 return true;
47 return ::tcsetattr(m_fd, TCSANOW, &fd_termios) == 0;
48 }
49 }
50 #endif // #if LLDB_ENABLE_TERMIOS
51 }
52 return false;
53 }
54
SetCanonical(bool enabled)55 bool Terminal::SetCanonical(bool enabled) {
56 if (FileDescriptorIsValid()) {
57 #if LLDB_ENABLE_TERMIOS
58 if (IsATerminal()) {
59 struct termios fd_termios;
60 if (::tcgetattr(m_fd, &fd_termios) == 0) {
61 bool set_corectly = false;
62 if (enabled) {
63 if (fd_termios.c_lflag & ICANON)
64 set_corectly = true;
65 else
66 fd_termios.c_lflag |= ICANON;
67 } else {
68 if (fd_termios.c_lflag & ICANON)
69 fd_termios.c_lflag &= ~ICANON;
70 else
71 set_corectly = true;
72 }
73
74 if (set_corectly)
75 return true;
76 return ::tcsetattr(m_fd, TCSANOW, &fd_termios) == 0;
77 }
78 }
79 #endif // #if LLDB_ENABLE_TERMIOS
80 }
81 return false;
82 }
83
84 // Default constructor
TerminalState()85 TerminalState::TerminalState()
86 : m_tty()
87 #if LLDB_ENABLE_TERMIOS
88 ,
89 m_termios_up()
90 #endif
91 {
92 }
93
94 // Destructor
95 TerminalState::~TerminalState() = default;
96
Clear()97 void TerminalState::Clear() {
98 m_tty.Clear();
99 m_tflags = -1;
100 #if LLDB_ENABLE_TERMIOS
101 m_termios_up.reset();
102 #endif
103 m_process_group = -1;
104 }
105
106 // Save the current state of the TTY for the file descriptor "fd" and if
107 // "save_process_group" is true, attempt to save the process group info for the
108 // TTY.
Save(int fd,bool save_process_group)109 bool TerminalState::Save(int fd, bool save_process_group) {
110 m_tty.SetFileDescriptor(fd);
111 if (m_tty.IsATerminal()) {
112 #if LLDB_ENABLE_POSIX
113 m_tflags = ::fcntl(fd, F_GETFL, 0);
114 #endif
115 #if LLDB_ENABLE_TERMIOS
116 if (m_termios_up == nullptr)
117 m_termios_up.reset(new struct termios);
118 int err = ::tcgetattr(fd, m_termios_up.get());
119 if (err != 0)
120 m_termios_up.reset();
121 #endif // #if LLDB_ENABLE_TERMIOS
122 #if LLDB_ENABLE_POSIX
123 if (save_process_group)
124 m_process_group = ::tcgetpgrp(0);
125 else
126 m_process_group = -1;
127 #endif
128 } else {
129 m_tty.Clear();
130 m_tflags = -1;
131 #if LLDB_ENABLE_TERMIOS
132 m_termios_up.reset();
133 #endif
134 m_process_group = -1;
135 }
136 return IsValid();
137 }
138
139 // Restore the state of the TTY using the cached values from a previous call to
140 // Save().
Restore() const141 bool TerminalState::Restore() const {
142 #if LLDB_ENABLE_POSIX
143 if (IsValid()) {
144 const int fd = m_tty.GetFileDescriptor();
145 if (TFlagsIsValid())
146 fcntl(fd, F_SETFL, m_tflags);
147
148 #if LLDB_ENABLE_TERMIOS
149 if (TTYStateIsValid())
150 tcsetattr(fd, TCSANOW, m_termios_up.get());
151 #endif // #if LLDB_ENABLE_TERMIOS
152
153 if (ProcessGroupIsValid()) {
154 // Save the original signal handler.
155 void (*saved_sigttou_callback)(int) = nullptr;
156 saved_sigttou_callback = (void (*)(int))signal(SIGTTOU, SIG_IGN);
157 // Set the process group
158 tcsetpgrp(fd, m_process_group);
159 // Restore the original signal handler.
160 signal(SIGTTOU, saved_sigttou_callback);
161 }
162 return true;
163 }
164 #endif
165 return false;
166 }
167
168 // Returns true if this object has valid saved TTY state settings that can be
169 // used to restore a previous state.
IsValid() const170 bool TerminalState::IsValid() const {
171 return m_tty.FileDescriptorIsValid() &&
172 (TFlagsIsValid() || TTYStateIsValid());
173 }
174
175 // Returns true if m_tflags is valid
TFlagsIsValid() const176 bool TerminalState::TFlagsIsValid() const { return m_tflags != -1; }
177
178 // Returns true if m_ttystate is valid
TTYStateIsValid() const179 bool TerminalState::TTYStateIsValid() const {
180 #if LLDB_ENABLE_TERMIOS
181 return m_termios_up != nullptr;
182 #else
183 return false;
184 #endif
185 }
186
187 // Returns true if m_process_group is valid
ProcessGroupIsValid() const188 bool TerminalState::ProcessGroupIsValid() const {
189 return static_cast<int32_t>(m_process_group) != -1;
190 }
191
192 // Constructor
193 TerminalStateSwitcher::TerminalStateSwitcher() = default;
194
195 // Destructor
196 TerminalStateSwitcher::~TerminalStateSwitcher() = default;
197
198 // Returns the number of states that this switcher contains
GetNumberOfStates() const199 uint32_t TerminalStateSwitcher::GetNumberOfStates() const {
200 return llvm::array_lengthof(m_ttystates);
201 }
202
203 // Restore the state at index "idx".
204 //
205 // Returns true if the restore was successful, false otherwise.
Restore(uint32_t idx) const206 bool TerminalStateSwitcher::Restore(uint32_t idx) const {
207 const uint32_t num_states = GetNumberOfStates();
208 if (idx >= num_states)
209 return false;
210
211 // See if we already are in this state?
212 if (m_currentState < num_states && (idx == m_currentState) &&
213 m_ttystates[idx].IsValid())
214 return true;
215
216 // Set the state to match the index passed in and only update the current
217 // state if there are no errors.
218 if (m_ttystates[idx].Restore()) {
219 m_currentState = idx;
220 return true;
221 }
222
223 // We failed to set the state. The tty state was invalid or not initialized.
224 return false;
225 }
226
227 // Save the state at index "idx" for file descriptor "fd" and save the process
228 // group if requested.
229 //
230 // Returns true if the restore was successful, false otherwise.
Save(uint32_t idx,int fd,bool save_process_group)231 bool TerminalStateSwitcher::Save(uint32_t idx, int fd,
232 bool save_process_group) {
233 const uint32_t num_states = GetNumberOfStates();
234 if (idx < num_states)
235 return m_ttystates[idx].Save(fd, save_process_group);
236 return false;
237 }
238