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
IsATerminal() const30 bool Terminal::IsATerminal() const { return m_fd >= 0 && ::isatty(m_fd); }
31
32 #if !LLDB_ENABLE_TERMIOS
termiosMissingError()33 static llvm::Error termiosMissingError() {
34 return llvm::createStringError(llvm::inconvertibleErrorCode(),
35 "termios support missing in LLDB");
36 }
37 #endif
38
GetData()39 llvm::Expected<Terminal::Data> Terminal::GetData() {
40 #if LLDB_ENABLE_TERMIOS
41 if (!FileDescriptorIsValid())
42 return llvm::createStringError(llvm::inconvertibleErrorCode(),
43 "invalid fd");
44
45 if (!IsATerminal())
46 return llvm::createStringError(llvm::inconvertibleErrorCode(),
47 "fd not a terminal");
48
49 Data data;
50 if (::tcgetattr(m_fd, &data.m_termios) != 0)
51 return llvm::createStringError(
52 std::error_code(errno, std::generic_category()),
53 "unable to get teletype attributes");
54 return data;
55 #else // !LLDB_ENABLE_TERMIOS
56 return termiosMissingError();
57 #endif // LLDB_ENABLE_TERMIOS
58 }
59
SetData(const Terminal::Data & data)60 llvm::Error Terminal::SetData(const Terminal::Data &data) {
61 #if LLDB_ENABLE_TERMIOS
62 assert(FileDescriptorIsValid());
63 assert(IsATerminal());
64
65 if (::tcsetattr(m_fd, TCSANOW, &data.m_termios) != 0)
66 return llvm::createStringError(
67 std::error_code(errno, std::generic_category()),
68 "unable to set teletype attributes");
69 return llvm::Error::success();
70 #else // !LLDB_ENABLE_TERMIOS
71 return termiosMissingError();
72 #endif // LLDB_ENABLE_TERMIOS
73 }
74
SetEcho(bool enabled)75 llvm::Error Terminal::SetEcho(bool enabled) {
76 #if LLDB_ENABLE_TERMIOS
77 llvm::Expected<Data> data = GetData();
78 if (!data)
79 return data.takeError();
80
81 struct termios &fd_termios = data->m_termios;
82 fd_termios.c_lflag &= ~ECHO;
83 if (enabled)
84 fd_termios.c_lflag |= ECHO;
85 return SetData(data.get());
86 #else // !LLDB_ENABLE_TERMIOS
87 return termiosMissingError();
88 #endif // LLDB_ENABLE_TERMIOS
89 }
90
SetCanonical(bool enabled)91 llvm::Error Terminal::SetCanonical(bool enabled) {
92 #if LLDB_ENABLE_TERMIOS
93 llvm::Expected<Data> data = GetData();
94 if (!data)
95 return data.takeError();
96
97 struct termios &fd_termios = data->m_termios;
98 fd_termios.c_lflag &= ~ICANON;
99 if (enabled)
100 fd_termios.c_lflag |= ICANON;
101 return SetData(data.get());
102 #else // !LLDB_ENABLE_TERMIOS
103 return termiosMissingError();
104 #endif // LLDB_ENABLE_TERMIOS
105 }
106
SetRaw()107 llvm::Error Terminal::SetRaw() {
108 #if LLDB_ENABLE_TERMIOS
109 llvm::Expected<Data> data = GetData();
110 if (!data)
111 return data.takeError();
112
113 struct termios &fd_termios = data->m_termios;
114 ::cfmakeraw(&fd_termios);
115
116 // Make sure only one character is needed to return from a read
117 // (cfmakeraw() doesn't do this on NetBSD)
118 fd_termios.c_cc[VMIN] = 1;
119 fd_termios.c_cc[VTIME] = 0;
120
121 return SetData(data.get());
122 #else // !LLDB_ENABLE_TERMIOS
123 return termiosMissingError();
124 #endif // LLDB_ENABLE_TERMIOS
125 }
126
127 #if LLDB_ENABLE_TERMIOS
baudRateToConst(unsigned int baud_rate)128 static llvm::Optional<speed_t> baudRateToConst(unsigned int baud_rate) {
129 switch (baud_rate) {
130 #if defined(B50)
131 case 50:
132 return B50;
133 #endif
134 #if defined(B75)
135 case 75:
136 return B75;
137 #endif
138 #if defined(B110)
139 case 110:
140 return B110;
141 #endif
142 #if defined(B134)
143 case 134:
144 return B134;
145 #endif
146 #if defined(B150)
147 case 150:
148 return B150;
149 #endif
150 #if defined(B200)
151 case 200:
152 return B200;
153 #endif
154 #if defined(B300)
155 case 300:
156 return B300;
157 #endif
158 #if defined(B600)
159 case 600:
160 return B600;
161 #endif
162 #if defined(B1200)
163 case 1200:
164 return B1200;
165 #endif
166 #if defined(B1800)
167 case 1800:
168 return B1800;
169 #endif
170 #if defined(B2400)
171 case 2400:
172 return B2400;
173 #endif
174 #if defined(B4800)
175 case 4800:
176 return B4800;
177 #endif
178 #if defined(B9600)
179 case 9600:
180 return B9600;
181 #endif
182 #if defined(B19200)
183 case 19200:
184 return B19200;
185 #endif
186 #if defined(B38400)
187 case 38400:
188 return B38400;
189 #endif
190 #if defined(B57600)
191 case 57600:
192 return B57600;
193 #endif
194 #if defined(B115200)
195 case 115200:
196 return B115200;
197 #endif
198 #if defined(B230400)
199 case 230400:
200 return B230400;
201 #endif
202 #if defined(B460800)
203 case 460800:
204 return B460800;
205 #endif
206 #if defined(B500000)
207 case 500000:
208 return B500000;
209 #endif
210 #if defined(B576000)
211 case 576000:
212 return B576000;
213 #endif
214 #if defined(B921600)
215 case 921600:
216 return B921600;
217 #endif
218 #if defined(B1000000)
219 case 1000000:
220 return B1000000;
221 #endif
222 #if defined(B1152000)
223 case 1152000:
224 return B1152000;
225 #endif
226 #if defined(B1500000)
227 case 1500000:
228 return B1500000;
229 #endif
230 #if defined(B2000000)
231 case 2000000:
232 return B2000000;
233 #endif
234 #if defined(B76800)
235 case 76800:
236 return B76800;
237 #endif
238 #if defined(B153600)
239 case 153600:
240 return B153600;
241 #endif
242 #if defined(B307200)
243 case 307200:
244 return B307200;
245 #endif
246 #if defined(B614400)
247 case 614400:
248 return B614400;
249 #endif
250 #if defined(B2500000)
251 case 2500000:
252 return B2500000;
253 #endif
254 #if defined(B3000000)
255 case 3000000:
256 return B3000000;
257 #endif
258 #if defined(B3500000)
259 case 3500000:
260 return B3500000;
261 #endif
262 #if defined(B4000000)
263 case 4000000:
264 return B4000000;
265 #endif
266 default:
267 return llvm::None;
268 }
269 }
270 #endif
271
SetBaudRate(unsigned int baud_rate)272 llvm::Error Terminal::SetBaudRate(unsigned int baud_rate) {
273 #if LLDB_ENABLE_TERMIOS
274 llvm::Expected<Data> data = GetData();
275 if (!data)
276 return data.takeError();
277
278 struct termios &fd_termios = data->m_termios;
279 llvm::Optional<speed_t> val = baudRateToConst(baud_rate);
280 if (!val) // invalid value
281 return llvm::createStringError(llvm::inconvertibleErrorCode(),
282 "baud rate %d unsupported by the platform",
283 baud_rate);
284 if (::cfsetispeed(&fd_termios, val.value()) != 0)
285 return llvm::createStringError(
286 std::error_code(errno, std::generic_category()),
287 "setting input baud rate failed");
288 if (::cfsetospeed(&fd_termios, val.value()) != 0)
289 return llvm::createStringError(
290 std::error_code(errno, std::generic_category()),
291 "setting output baud rate failed");
292 return SetData(data.get());
293 #else // !LLDB_ENABLE_TERMIOS
294 return termiosMissingError();
295 #endif // LLDB_ENABLE_TERMIOS
296 }
297
SetStopBits(unsigned int stop_bits)298 llvm::Error Terminal::SetStopBits(unsigned int stop_bits) {
299 #if LLDB_ENABLE_TERMIOS
300 llvm::Expected<Data> data = GetData();
301 if (!data)
302 return data.takeError();
303
304 struct termios &fd_termios = data->m_termios;
305 switch (stop_bits) {
306 case 1:
307 fd_termios.c_cflag &= ~CSTOPB;
308 break;
309 case 2:
310 fd_termios.c_cflag |= CSTOPB;
311 break;
312 default:
313 return llvm::createStringError(
314 llvm::inconvertibleErrorCode(),
315 "invalid stop bit count: %d (must be 1 or 2)", stop_bits);
316 }
317 return SetData(data.get());
318 #else // !LLDB_ENABLE_TERMIOS
319 return termiosMissingError();
320 #endif // LLDB_ENABLE_TERMIOS
321 }
322
SetParity(Terminal::Parity parity)323 llvm::Error Terminal::SetParity(Terminal::Parity parity) {
324 #if LLDB_ENABLE_TERMIOS
325 llvm::Expected<Data> data = GetData();
326 if (!data)
327 return data.takeError();
328
329 struct termios &fd_termios = data->m_termios;
330 fd_termios.c_cflag &= ~(
331 #if defined(CMSPAR)
332 CMSPAR |
333 #endif
334 PARENB | PARODD);
335
336 if (parity != Parity::No) {
337 fd_termios.c_cflag |= PARENB;
338 if (parity == Parity::Odd || parity == Parity::Mark)
339 fd_termios.c_cflag |= PARODD;
340 if (parity == Parity::Mark || parity == Parity::Space) {
341 #if defined(CMSPAR)
342 fd_termios.c_cflag |= CMSPAR;
343 #else
344 return llvm::createStringError(
345 llvm::inconvertibleErrorCode(),
346 "space/mark parity is not supported by the platform");
347 #endif
348 }
349 }
350 return SetData(data.get());
351 #else // !LLDB_ENABLE_TERMIOS
352 return termiosMissingError();
353 #endif // LLDB_ENABLE_TERMIOS
354 }
355
SetParityCheck(Terminal::ParityCheck parity_check)356 llvm::Error Terminal::SetParityCheck(Terminal::ParityCheck parity_check) {
357 #if LLDB_ENABLE_TERMIOS
358 llvm::Expected<Data> data = GetData();
359 if (!data)
360 return data.takeError();
361
362 struct termios &fd_termios = data->m_termios;
363 fd_termios.c_iflag &= ~(IGNPAR | PARMRK | INPCK);
364
365 if (parity_check != ParityCheck::No) {
366 fd_termios.c_iflag |= INPCK;
367 if (parity_check == ParityCheck::Ignore)
368 fd_termios.c_iflag |= IGNPAR;
369 else if (parity_check == ParityCheck::Mark)
370 fd_termios.c_iflag |= PARMRK;
371 }
372 return SetData(data.get());
373 #else // !LLDB_ENABLE_TERMIOS
374 return termiosMissingError();
375 #endif // LLDB_ENABLE_TERMIOS
376 }
377
SetHardwareFlowControl(bool enabled)378 llvm::Error Terminal::SetHardwareFlowControl(bool enabled) {
379 #if LLDB_ENABLE_TERMIOS
380 llvm::Expected<Data> data = GetData();
381 if (!data)
382 return data.takeError();
383
384 #if defined(CRTSCTS)
385 struct termios &fd_termios = data->m_termios;
386 fd_termios.c_cflag &= ~CRTSCTS;
387 if (enabled)
388 fd_termios.c_cflag |= CRTSCTS;
389 return SetData(data.get());
390 #else // !defined(CRTSCTS)
391 if (enabled)
392 return llvm::createStringError(
393 llvm::inconvertibleErrorCode(),
394 "hardware flow control is not supported by the platform");
395 return llvm::Error::success();
396 #endif // defined(CRTSCTS)
397 #else // !LLDB_ENABLE_TERMIOS
398 return termiosMissingError();
399 #endif // LLDB_ENABLE_TERMIOS
400 }
401
TerminalState(Terminal term,bool save_process_group)402 TerminalState::TerminalState(Terminal term, bool save_process_group)
403 : m_tty(term) {
404 Save(term, save_process_group);
405 }
406
~TerminalState()407 TerminalState::~TerminalState() { Restore(); }
408
Clear()409 void TerminalState::Clear() {
410 m_tty.Clear();
411 m_tflags = -1;
412 m_data.reset();
413 m_process_group = -1;
414 }
415
Save(Terminal term,bool save_process_group)416 bool TerminalState::Save(Terminal term, bool save_process_group) {
417 Clear();
418 m_tty = term;
419 if (m_tty.IsATerminal()) {
420 #if LLDB_ENABLE_POSIX
421 int fd = m_tty.GetFileDescriptor();
422 m_tflags = ::fcntl(fd, F_GETFL, 0);
423 #if LLDB_ENABLE_TERMIOS
424 std::unique_ptr<Terminal::Data> new_data{new Terminal::Data()};
425 if (::tcgetattr(fd, &new_data->m_termios) == 0)
426 m_data = std::move(new_data);
427 #endif // LLDB_ENABLE_TERMIOS
428 if (save_process_group)
429 m_process_group = ::tcgetpgrp(fd);
430 #endif // LLDB_ENABLE_POSIX
431 }
432 return IsValid();
433 }
434
Restore() const435 bool TerminalState::Restore() const {
436 #if LLDB_ENABLE_POSIX
437 if (IsValid()) {
438 const int fd = m_tty.GetFileDescriptor();
439 if (TFlagsIsValid())
440 fcntl(fd, F_SETFL, m_tflags);
441
442 #if LLDB_ENABLE_TERMIOS
443 if (TTYStateIsValid())
444 tcsetattr(fd, TCSANOW, &m_data->m_termios);
445 #endif // LLDB_ENABLE_TERMIOS
446
447 if (ProcessGroupIsValid()) {
448 // Save the original signal handler.
449 void (*saved_sigttou_callback)(int) = nullptr;
450 saved_sigttou_callback = (void (*)(int))signal(SIGTTOU, SIG_IGN);
451 // Set the process group
452 tcsetpgrp(fd, m_process_group);
453 // Restore the original signal handler.
454 signal(SIGTTOU, saved_sigttou_callback);
455 }
456 return true;
457 }
458 #endif // LLDB_ENABLE_POSIX
459 return false;
460 }
461
IsValid() const462 bool TerminalState::IsValid() const {
463 return m_tty.FileDescriptorIsValid() &&
464 (TFlagsIsValid() || TTYStateIsValid() || ProcessGroupIsValid());
465 }
466
TFlagsIsValid() const467 bool TerminalState::TFlagsIsValid() const { return m_tflags != -1; }
468
TTYStateIsValid() const469 bool TerminalState::TTYStateIsValid() const { return bool(m_data); }
470
ProcessGroupIsValid() const471 bool TerminalState::ProcessGroupIsValid() const {
472 return static_cast<int32_t>(m_process_group) != -1;
473 }
474