1 //===-- Terminal.cpp ------------------------------------------------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "lldb/Host/Terminal.h"
11 #include "lldb/Host/Config.h"
12 #include "lldb/Host/PosixApi.h"
13 #include "llvm/ADT/STLExtras.h"
19 #if LLDB_ENABLE_TERMIOS
23 using namespace lldb_private
;
25 struct Terminal::Data
{
26 #if LLDB_ENABLE_TERMIOS
27 struct termios m_termios
; ///< Cached terminal state information.
31 bool Terminal::IsATerminal() const { return m_fd
>= 0 && ::isatty(m_fd
); }
33 #if !LLDB_ENABLE_TERMIOS
34 static llvm::Error
termiosMissingError() {
35 return llvm::createStringError(llvm::inconvertibleErrorCode(),
36 "termios support missing in LLDB");
40 llvm::Expected
<Terminal::Data
> Terminal::GetData() {
41 #if LLDB_ENABLE_TERMIOS
42 if (!FileDescriptorIsValid())
43 return llvm::createStringError(llvm::inconvertibleErrorCode(),
47 return llvm::createStringError(llvm::inconvertibleErrorCode(),
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");
56 #else // !LLDB_ENABLE_TERMIOS
57 return termiosMissingError();
58 #endif // LLDB_ENABLE_TERMIOS
61 llvm::Error
Terminal::SetData(const Terminal::Data
&data
) {
62 #if LLDB_ENABLE_TERMIOS
63 assert(FileDescriptorIsValid());
64 assert(IsATerminal());
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_TERMIOS
72 return termiosMissingError();
73 #endif // LLDB_ENABLE_TERMIOS
76 llvm::Error
Terminal::SetEcho(bool enabled
) {
77 #if LLDB_ENABLE_TERMIOS
78 llvm::Expected
<Data
> data
= GetData();
80 return data
.takeError();
82 struct termios
&fd_termios
= data
->m_termios
;
83 fd_termios
.c_lflag
&= ~ECHO
;
85 fd_termios
.c_lflag
|= ECHO
;
86 return SetData(data
.get());
87 #else // !LLDB_ENABLE_TERMIOS
88 return termiosMissingError();
89 #endif // LLDB_ENABLE_TERMIOS
92 llvm::Error
Terminal::SetCanonical(bool enabled
) {
93 #if LLDB_ENABLE_TERMIOS
94 llvm::Expected
<Data
> data
= GetData();
96 return data
.takeError();
98 struct termios
&fd_termios
= data
->m_termios
;
99 fd_termios
.c_lflag
&= ~ICANON
;
101 fd_termios
.c_lflag
|= ICANON
;
102 return SetData(data
.get());
103 #else // !LLDB_ENABLE_TERMIOS
104 return termiosMissingError();
105 #endif // LLDB_ENABLE_TERMIOS
108 llvm::Error
Terminal::SetRaw() {
109 #if LLDB_ENABLE_TERMIOS
110 llvm::Expected
<Data
> data
= GetData();
112 return data
.takeError();
114 struct termios
&fd_termios
= data
->m_termios
;
115 ::cfmakeraw(&fd_termios
);
117 // Make sure only one character is needed to return from a read
118 // (cfmakeraw() doesn't do this on NetBSD)
119 fd_termios
.c_cc
[VMIN
] = 1;
120 fd_termios
.c_cc
[VTIME
] = 0;
122 return SetData(data
.get());
123 #else // !LLDB_ENABLE_TERMIOS
124 return termiosMissingError();
125 #endif // LLDB_ENABLE_TERMIOS
128 #if LLDB_ENABLE_TERMIOS
129 static std::optional
<speed_t
> baudRateToConst(unsigned int baud_rate
) {
219 #if defined(B1000000)
223 #if defined(B1152000)
227 #if defined(B1500000)
231 #if defined(B2000000)
251 #if defined(B2500000)
255 #if defined(B3000000)
259 #if defined(B3500000)
263 #if defined(B4000000)
273 llvm::Error
Terminal::SetBaudRate(unsigned int baud_rate
) {
274 #if LLDB_ENABLE_TERMIOS
275 llvm::Expected
<Data
> data
= GetData();
277 return data
.takeError();
279 struct termios
&fd_termios
= data
->m_termios
;
280 std::optional
<speed_t
> val
= baudRateToConst(baud_rate
);
281 if (!val
) // invalid value
282 return llvm::createStringError(llvm::inconvertibleErrorCode(),
283 "baud rate %d unsupported by the platform",
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_TERMIOS
295 return termiosMissingError();
296 #endif // LLDB_ENABLE_TERMIOS
299 llvm::Error
Terminal::SetStopBits(unsigned int stop_bits
) {
300 #if LLDB_ENABLE_TERMIOS
301 llvm::Expected
<Data
> data
= GetData();
303 return data
.takeError();
305 struct termios
&fd_termios
= data
->m_termios
;
308 fd_termios
.c_cflag
&= ~CSTOPB
;
311 fd_termios
.c_cflag
|= CSTOPB
;
314 return llvm::createStringError(
315 llvm::inconvertibleErrorCode(),
316 "invalid stop bit count: %d (must be 1 or 2)", stop_bits
);
318 return SetData(data
.get());
319 #else // !LLDB_ENABLE_TERMIOS
320 return termiosMissingError();
321 #endif // LLDB_ENABLE_TERMIOS
324 llvm::Error
Terminal::SetParity(Terminal::Parity parity
) {
325 #if LLDB_ENABLE_TERMIOS
326 llvm::Expected
<Data
> data
= GetData();
328 return data
.takeError();
330 struct termios
&fd_termios
= data
->m_termios
;
331 fd_termios
.c_cflag
&= ~(
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
) {
343 fd_termios
.c_cflag
|= CMSPAR
;
345 return llvm::createStringError(
346 llvm::inconvertibleErrorCode(),
347 "space/mark parity is not supported by the platform");
351 return SetData(data
.get());
352 #else // !LLDB_ENABLE_TERMIOS
353 return termiosMissingError();
354 #endif // LLDB_ENABLE_TERMIOS
357 llvm::Error
Terminal::SetParityCheck(Terminal::ParityCheck parity_check
) {
358 #if LLDB_ENABLE_TERMIOS
359 llvm::Expected
<Data
> data
= GetData();
361 return data
.takeError();
363 struct termios
&fd_termios
= data
->m_termios
;
364 fd_termios
.c_iflag
&= ~(IGNPAR
| PARMRK
| INPCK
);
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
;
373 return SetData(data
.get());
374 #else // !LLDB_ENABLE_TERMIOS
375 return termiosMissingError();
376 #endif // LLDB_ENABLE_TERMIOS
379 llvm::Error
Terminal::SetHardwareFlowControl(bool enabled
) {
380 #if LLDB_ENABLE_TERMIOS
381 llvm::Expected
<Data
> data
= GetData();
383 return data
.takeError();
386 struct termios
&fd_termios
= data
->m_termios
;
387 fd_termios
.c_cflag
&= ~CRTSCTS
;
389 fd_termios
.c_cflag
|= CRTSCTS
;
390 return SetData(data
.get());
391 #else // !defined(CRTSCTS)
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_TERMIOS
399 return termiosMissingError();
400 #endif // LLDB_ENABLE_TERMIOS
403 TerminalState::TerminalState(Terminal term
, bool save_process_group
)
405 Save(term
, save_process_group
);
408 TerminalState::~TerminalState() { Restore(); }
410 void TerminalState::Clear() {
414 m_process_group
= -1;
417 bool TerminalState::Save(Terminal term
, bool save_process_group
) {
420 if (m_tty
.IsATerminal()) {
421 #if LLDB_ENABLE_POSIX
422 int fd
= m_tty
.GetFileDescriptor();
423 m_tflags
= ::fcntl(fd
, F_GETFL
, 0);
424 #if LLDB_ENABLE_TERMIOS
425 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_TERMIOS
429 if (save_process_group
)
430 m_process_group
= ::tcgetpgrp(fd
);
431 #endif // LLDB_ENABLE_POSIX
436 bool TerminalState::Restore() const {
437 #if LLDB_ENABLE_POSIX
439 const int fd
= m_tty
.GetFileDescriptor();
441 fcntl(fd
, F_SETFL
, m_tflags
);
443 #if LLDB_ENABLE_TERMIOS
444 if (TTYStateIsValid())
445 tcsetattr(fd
, TCSANOW
, &m_data
->m_termios
);
446 #endif // LLDB_ENABLE_TERMIOS
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 group
453 tcsetpgrp(fd
, m_process_group
);
454 // Restore the original signal handler.
455 signal(SIGTTOU
, saved_sigttou_callback
);
459 #endif // LLDB_ENABLE_POSIX
463 bool TerminalState::IsValid() const {
464 return m_tty
.FileDescriptorIsValid() &&
465 (TFlagsIsValid() || TTYStateIsValid() || ProcessGroupIsValid());
468 bool TerminalState::TFlagsIsValid() const { return m_tflags
!= -1; }
470 bool TerminalState::TTYStateIsValid() const { return bool(m_data
); }
472 bool TerminalState::ProcessGroupIsValid() const {
473 return static_cast<int32_t>(m_process_group
) != -1;