Terminal.cpp revision 360784
1//===-- Terminal.cpp --------------------------------------------*- C++ -*-===//
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 <fcntl.h>
16#include <signal.h>
17
18#if LLDB_ENABLE_TERMIOS
19#include <termios.h>
20#endif
21
22using namespace lldb_private;
23
24bool Terminal::IsATerminal() const { return m_fd >= 0 && ::isatty(m_fd); }
25
26bool 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
55bool 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
85TerminalState::TerminalState()
86    : m_tty(), m_tflags(-1),
87#if LLDB_ENABLE_TERMIOS
88      m_termios_up(),
89#endif
90      m_process_group(-1) {
91}
92
93// Destructor
94TerminalState::~TerminalState() {}
95
96void TerminalState::Clear() {
97  m_tty.Clear();
98  m_tflags = -1;
99#if LLDB_ENABLE_TERMIOS
100  m_termios_up.reset();
101#endif
102  m_process_group = -1;
103}
104
105// Save the current state of the TTY for the file descriptor "fd" and if
106// "save_process_group" is true, attempt to save the process group info for the
107// TTY.
108bool TerminalState::Save(int fd, bool save_process_group) {
109  m_tty.SetFileDescriptor(fd);
110  if (m_tty.IsATerminal()) {
111#if LLDB_ENABLE_POSIX
112    m_tflags = ::fcntl(fd, F_GETFL, 0);
113#endif
114#if LLDB_ENABLE_TERMIOS
115    if (m_termios_up == nullptr)
116      m_termios_up.reset(new struct termios);
117    int err = ::tcgetattr(fd, m_termios_up.get());
118    if (err != 0)
119      m_termios_up.reset();
120#endif // #if LLDB_ENABLE_TERMIOS
121#if LLDB_ENABLE_POSIX
122    if (save_process_group)
123      m_process_group = ::tcgetpgrp(0);
124    else
125      m_process_group = -1;
126#endif
127  } else {
128    m_tty.Clear();
129    m_tflags = -1;
130#if LLDB_ENABLE_TERMIOS
131    m_termios_up.reset();
132#endif
133    m_process_group = -1;
134  }
135  return IsValid();
136}
137
138// Restore the state of the TTY using the cached values from a previous call to
139// Save().
140bool TerminalState::Restore() const {
141#if LLDB_ENABLE_POSIX
142  if (IsValid()) {
143    const int fd = m_tty.GetFileDescriptor();
144    if (TFlagsIsValid())
145      fcntl(fd, F_SETFL, m_tflags);
146
147#if LLDB_ENABLE_TERMIOS
148    if (TTYStateIsValid())
149      tcsetattr(fd, TCSANOW, m_termios_up.get());
150#endif // #if LLDB_ENABLE_TERMIOS
151
152    if (ProcessGroupIsValid()) {
153      // Save the original signal handler.
154      void (*saved_sigttou_callback)(int) = nullptr;
155      saved_sigttou_callback = (void (*)(int))signal(SIGTTOU, SIG_IGN);
156      // Set the process group
157      tcsetpgrp(fd, m_process_group);
158      // Restore the original signal handler.
159      signal(SIGTTOU, saved_sigttou_callback);
160    }
161    return true;
162  }
163#endif
164  return false;
165}
166
167// Returns true if this object has valid saved TTY state settings that can be
168// used to restore a previous state.
169bool TerminalState::IsValid() const {
170  return m_tty.FileDescriptorIsValid() &&
171         (TFlagsIsValid() || TTYStateIsValid());
172}
173
174// Returns true if m_tflags is valid
175bool TerminalState::TFlagsIsValid() const { return m_tflags != -1; }
176
177// Returns true if m_ttystate is valid
178bool TerminalState::TTYStateIsValid() const {
179#if LLDB_ENABLE_TERMIOS
180  return m_termios_up != nullptr;
181#else
182  return false;
183#endif
184}
185
186// Returns true if m_process_group is valid
187bool TerminalState::ProcessGroupIsValid() const {
188  return static_cast<int32_t>(m_process_group) != -1;
189}
190
191// Constructor
192TerminalStateSwitcher::TerminalStateSwitcher() : m_currentState(UINT32_MAX) {}
193
194// Destructor
195TerminalStateSwitcher::~TerminalStateSwitcher() {}
196
197// Returns the number of states that this switcher contains
198uint32_t TerminalStateSwitcher::GetNumberOfStates() const {
199  return llvm::array_lengthof(m_ttystates);
200}
201
202// Restore the state at index "idx".
203//
204// Returns true if the restore was successful, false otherwise.
205bool TerminalStateSwitcher::Restore(uint32_t idx) const {
206  const uint32_t num_states = GetNumberOfStates();
207  if (idx >= num_states)
208    return false;
209
210  // See if we already are in this state?
211  if (m_currentState < num_states && (idx == m_currentState) &&
212      m_ttystates[idx].IsValid())
213    return true;
214
215  // Set the state to match the index passed in and only update the current
216  // state if there are no errors.
217  if (m_ttystates[idx].Restore()) {
218    m_currentState = idx;
219    return true;
220  }
221
222  // We failed to set the state. The tty state was invalid or not initialized.
223  return false;
224}
225
226// Save the state at index "idx" for file descriptor "fd" and save the process
227// group if requested.
228//
229// Returns true if the restore was successful, false otherwise.
230bool TerminalStateSwitcher::Save(uint32_t idx, int fd,
231                                 bool save_process_group) {
232  const uint32_t num_states = GetNumberOfStates();
233  if (idx < num_states)
234    return m_ttystates[idx].Save(fd, save_process_group);
235  return false;
236}
237