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