15ffd83dbSDimitry Andric //===-- Terminal.cpp ------------------------------------------------------===//
20b57cec5SDimitry Andric //
30b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
40b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
50b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
60b57cec5SDimitry Andric //
70b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
80b57cec5SDimitry Andric 
90b57cec5SDimitry Andric #include "lldb/Host/Terminal.h"
100b57cec5SDimitry Andric 
110b57cec5SDimitry Andric #include "lldb/Host/Config.h"
120b57cec5SDimitry Andric #include "lldb/Host/PosixApi.h"
130b57cec5SDimitry Andric #include "llvm/ADT/STLExtras.h"
140b57cec5SDimitry Andric 
15*5f7ddb14SDimitry Andric #include <csignal>
160b57cec5SDimitry Andric #include <fcntl.h>
170b57cec5SDimitry Andric 
18480093f4SDimitry Andric #if LLDB_ENABLE_TERMIOS
190b57cec5SDimitry Andric #include <termios.h>
200b57cec5SDimitry Andric #endif
210b57cec5SDimitry Andric 
220b57cec5SDimitry Andric using namespace lldb_private;
230b57cec5SDimitry Andric 
IsATerminal() const240b57cec5SDimitry Andric bool Terminal::IsATerminal() const { return m_fd >= 0 && ::isatty(m_fd); }
250b57cec5SDimitry Andric 
SetEcho(bool enabled)260b57cec5SDimitry Andric bool Terminal::SetEcho(bool enabled) {
270b57cec5SDimitry Andric   if (FileDescriptorIsValid()) {
28480093f4SDimitry Andric #if LLDB_ENABLE_TERMIOS
290b57cec5SDimitry Andric     if (IsATerminal()) {
300b57cec5SDimitry Andric       struct termios fd_termios;
310b57cec5SDimitry Andric       if (::tcgetattr(m_fd, &fd_termios) == 0) {
320b57cec5SDimitry Andric         bool set_corectly = false;
330b57cec5SDimitry Andric         if (enabled) {
340b57cec5SDimitry Andric           if (fd_termios.c_lflag & ECHO)
350b57cec5SDimitry Andric             set_corectly = true;
360b57cec5SDimitry Andric           else
370b57cec5SDimitry Andric             fd_termios.c_lflag |= ECHO;
380b57cec5SDimitry Andric         } else {
390b57cec5SDimitry Andric           if (fd_termios.c_lflag & ECHO)
400b57cec5SDimitry Andric             fd_termios.c_lflag &= ~ECHO;
410b57cec5SDimitry Andric           else
420b57cec5SDimitry Andric             set_corectly = true;
430b57cec5SDimitry Andric         }
440b57cec5SDimitry Andric 
450b57cec5SDimitry Andric         if (set_corectly)
460b57cec5SDimitry Andric           return true;
470b57cec5SDimitry Andric         return ::tcsetattr(m_fd, TCSANOW, &fd_termios) == 0;
480b57cec5SDimitry Andric       }
490b57cec5SDimitry Andric     }
50480093f4SDimitry Andric #endif // #if LLDB_ENABLE_TERMIOS
510b57cec5SDimitry Andric   }
520b57cec5SDimitry Andric   return false;
530b57cec5SDimitry Andric }
540b57cec5SDimitry Andric 
SetCanonical(bool enabled)550b57cec5SDimitry Andric bool Terminal::SetCanonical(bool enabled) {
560b57cec5SDimitry Andric   if (FileDescriptorIsValid()) {
57480093f4SDimitry Andric #if LLDB_ENABLE_TERMIOS
580b57cec5SDimitry Andric     if (IsATerminal()) {
590b57cec5SDimitry Andric       struct termios fd_termios;
600b57cec5SDimitry Andric       if (::tcgetattr(m_fd, &fd_termios) == 0) {
610b57cec5SDimitry Andric         bool set_corectly = false;
620b57cec5SDimitry Andric         if (enabled) {
630b57cec5SDimitry Andric           if (fd_termios.c_lflag & ICANON)
640b57cec5SDimitry Andric             set_corectly = true;
650b57cec5SDimitry Andric           else
660b57cec5SDimitry Andric             fd_termios.c_lflag |= ICANON;
670b57cec5SDimitry Andric         } else {
680b57cec5SDimitry Andric           if (fd_termios.c_lflag & ICANON)
690b57cec5SDimitry Andric             fd_termios.c_lflag &= ~ICANON;
700b57cec5SDimitry Andric           else
710b57cec5SDimitry Andric             set_corectly = true;
720b57cec5SDimitry Andric         }
730b57cec5SDimitry Andric 
740b57cec5SDimitry Andric         if (set_corectly)
750b57cec5SDimitry Andric           return true;
760b57cec5SDimitry Andric         return ::tcsetattr(m_fd, TCSANOW, &fd_termios) == 0;
770b57cec5SDimitry Andric       }
780b57cec5SDimitry Andric     }
79480093f4SDimitry Andric #endif // #if LLDB_ENABLE_TERMIOS
800b57cec5SDimitry Andric   }
810b57cec5SDimitry Andric   return false;
820b57cec5SDimitry Andric }
830b57cec5SDimitry Andric 
840b57cec5SDimitry Andric // Default constructor
TerminalState()850b57cec5SDimitry Andric TerminalState::TerminalState()
86*5f7ddb14SDimitry Andric     : m_tty()
87480093f4SDimitry Andric #if LLDB_ENABLE_TERMIOS
88*5f7ddb14SDimitry Andric       ,
89*5f7ddb14SDimitry Andric       m_termios_up()
900b57cec5SDimitry Andric #endif
91*5f7ddb14SDimitry Andric {
920b57cec5SDimitry Andric }
930b57cec5SDimitry Andric 
940b57cec5SDimitry Andric // Destructor
95*5f7ddb14SDimitry Andric TerminalState::~TerminalState() = default;
960b57cec5SDimitry Andric 
Clear()970b57cec5SDimitry Andric void TerminalState::Clear() {
980b57cec5SDimitry Andric   m_tty.Clear();
990b57cec5SDimitry Andric   m_tflags = -1;
100480093f4SDimitry Andric #if LLDB_ENABLE_TERMIOS
1010b57cec5SDimitry Andric   m_termios_up.reset();
1020b57cec5SDimitry Andric #endif
1030b57cec5SDimitry Andric   m_process_group = -1;
1040b57cec5SDimitry Andric }
1050b57cec5SDimitry Andric 
1060b57cec5SDimitry Andric // Save the current state of the TTY for the file descriptor "fd" and if
1070b57cec5SDimitry Andric // "save_process_group" is true, attempt to save the process group info for the
1080b57cec5SDimitry Andric // TTY.
Save(int fd,bool save_process_group)1090b57cec5SDimitry Andric bool TerminalState::Save(int fd, bool save_process_group) {
1100b57cec5SDimitry Andric   m_tty.SetFileDescriptor(fd);
1110b57cec5SDimitry Andric   if (m_tty.IsATerminal()) {
112480093f4SDimitry Andric #if LLDB_ENABLE_POSIX
1130b57cec5SDimitry Andric     m_tflags = ::fcntl(fd, F_GETFL, 0);
1140b57cec5SDimitry Andric #endif
115480093f4SDimitry Andric #if LLDB_ENABLE_TERMIOS
1160b57cec5SDimitry Andric     if (m_termios_up == nullptr)
1170b57cec5SDimitry Andric       m_termios_up.reset(new struct termios);
1180b57cec5SDimitry Andric     int err = ::tcgetattr(fd, m_termios_up.get());
1190b57cec5SDimitry Andric     if (err != 0)
1200b57cec5SDimitry Andric       m_termios_up.reset();
121480093f4SDimitry Andric #endif // #if LLDB_ENABLE_TERMIOS
122480093f4SDimitry Andric #if LLDB_ENABLE_POSIX
1230b57cec5SDimitry Andric     if (save_process_group)
1240b57cec5SDimitry Andric       m_process_group = ::tcgetpgrp(0);
1250b57cec5SDimitry Andric     else
1260b57cec5SDimitry Andric       m_process_group = -1;
1270b57cec5SDimitry Andric #endif
1280b57cec5SDimitry Andric   } else {
1290b57cec5SDimitry Andric     m_tty.Clear();
1300b57cec5SDimitry Andric     m_tflags = -1;
131480093f4SDimitry Andric #if LLDB_ENABLE_TERMIOS
1320b57cec5SDimitry Andric     m_termios_up.reset();
1330b57cec5SDimitry Andric #endif
1340b57cec5SDimitry Andric     m_process_group = -1;
1350b57cec5SDimitry Andric   }
1360b57cec5SDimitry Andric   return IsValid();
1370b57cec5SDimitry Andric }
1380b57cec5SDimitry Andric 
1390b57cec5SDimitry Andric // Restore the state of the TTY using the cached values from a previous call to
1400b57cec5SDimitry Andric // Save().
Restore() const1410b57cec5SDimitry Andric bool TerminalState::Restore() const {
142480093f4SDimitry Andric #if LLDB_ENABLE_POSIX
1430b57cec5SDimitry Andric   if (IsValid()) {
1440b57cec5SDimitry Andric     const int fd = m_tty.GetFileDescriptor();
1450b57cec5SDimitry Andric     if (TFlagsIsValid())
1460b57cec5SDimitry Andric       fcntl(fd, F_SETFL, m_tflags);
1470b57cec5SDimitry Andric 
148480093f4SDimitry Andric #if LLDB_ENABLE_TERMIOS
1490b57cec5SDimitry Andric     if (TTYStateIsValid())
1500b57cec5SDimitry Andric       tcsetattr(fd, TCSANOW, m_termios_up.get());
151480093f4SDimitry Andric #endif // #if LLDB_ENABLE_TERMIOS
1520b57cec5SDimitry Andric 
1530b57cec5SDimitry Andric     if (ProcessGroupIsValid()) {
1540b57cec5SDimitry Andric       // Save the original signal handler.
1550b57cec5SDimitry Andric       void (*saved_sigttou_callback)(int) = nullptr;
1560b57cec5SDimitry Andric       saved_sigttou_callback = (void (*)(int))signal(SIGTTOU, SIG_IGN);
1570b57cec5SDimitry Andric       // Set the process group
1580b57cec5SDimitry Andric       tcsetpgrp(fd, m_process_group);
1590b57cec5SDimitry Andric       // Restore the original signal handler.
1600b57cec5SDimitry Andric       signal(SIGTTOU, saved_sigttou_callback);
1610b57cec5SDimitry Andric     }
1620b57cec5SDimitry Andric     return true;
1630b57cec5SDimitry Andric   }
1640b57cec5SDimitry Andric #endif
1650b57cec5SDimitry Andric   return false;
1660b57cec5SDimitry Andric }
1670b57cec5SDimitry Andric 
1680b57cec5SDimitry Andric // Returns true if this object has valid saved TTY state settings that can be
1690b57cec5SDimitry Andric // used to restore a previous state.
IsValid() const1700b57cec5SDimitry Andric bool TerminalState::IsValid() const {
1710b57cec5SDimitry Andric   return m_tty.FileDescriptorIsValid() &&
1720b57cec5SDimitry Andric          (TFlagsIsValid() || TTYStateIsValid());
1730b57cec5SDimitry Andric }
1740b57cec5SDimitry Andric 
1750b57cec5SDimitry Andric // Returns true if m_tflags is valid
TFlagsIsValid() const1760b57cec5SDimitry Andric bool TerminalState::TFlagsIsValid() const { return m_tflags != -1; }
1770b57cec5SDimitry Andric 
1780b57cec5SDimitry Andric // Returns true if m_ttystate is valid
TTYStateIsValid() const1790b57cec5SDimitry Andric bool TerminalState::TTYStateIsValid() const {
180480093f4SDimitry Andric #if LLDB_ENABLE_TERMIOS
1810b57cec5SDimitry Andric   return m_termios_up != nullptr;
1820b57cec5SDimitry Andric #else
1830b57cec5SDimitry Andric   return false;
1840b57cec5SDimitry Andric #endif
1850b57cec5SDimitry Andric }
1860b57cec5SDimitry Andric 
1870b57cec5SDimitry Andric // Returns true if m_process_group is valid
ProcessGroupIsValid() const1880b57cec5SDimitry Andric bool TerminalState::ProcessGroupIsValid() const {
1890b57cec5SDimitry Andric   return static_cast<int32_t>(m_process_group) != -1;
1900b57cec5SDimitry Andric }
1910b57cec5SDimitry Andric 
1920b57cec5SDimitry Andric // Constructor
193*5f7ddb14SDimitry Andric TerminalStateSwitcher::TerminalStateSwitcher() = default;
1940b57cec5SDimitry Andric 
1950b57cec5SDimitry Andric // Destructor
196*5f7ddb14SDimitry Andric TerminalStateSwitcher::~TerminalStateSwitcher() = default;
1970b57cec5SDimitry Andric 
1980b57cec5SDimitry Andric // Returns the number of states that this switcher contains
GetNumberOfStates() const1990b57cec5SDimitry Andric uint32_t TerminalStateSwitcher::GetNumberOfStates() const {
2000b57cec5SDimitry Andric   return llvm::array_lengthof(m_ttystates);
2010b57cec5SDimitry Andric }
2020b57cec5SDimitry Andric 
2030b57cec5SDimitry Andric // Restore the state at index "idx".
2040b57cec5SDimitry Andric //
2050b57cec5SDimitry Andric // Returns true if the restore was successful, false otherwise.
Restore(uint32_t idx) const2060b57cec5SDimitry Andric bool TerminalStateSwitcher::Restore(uint32_t idx) const {
2070b57cec5SDimitry Andric   const uint32_t num_states = GetNumberOfStates();
2080b57cec5SDimitry Andric   if (idx >= num_states)
2090b57cec5SDimitry Andric     return false;
2100b57cec5SDimitry Andric 
2110b57cec5SDimitry Andric   // See if we already are in this state?
2120b57cec5SDimitry Andric   if (m_currentState < num_states && (idx == m_currentState) &&
2130b57cec5SDimitry Andric       m_ttystates[idx].IsValid())
2140b57cec5SDimitry Andric     return true;
2150b57cec5SDimitry Andric 
2160b57cec5SDimitry Andric   // Set the state to match the index passed in and only update the current
2170b57cec5SDimitry Andric   // state if there are no errors.
2180b57cec5SDimitry Andric   if (m_ttystates[idx].Restore()) {
2190b57cec5SDimitry Andric     m_currentState = idx;
2200b57cec5SDimitry Andric     return true;
2210b57cec5SDimitry Andric   }
2220b57cec5SDimitry Andric 
2230b57cec5SDimitry Andric   // We failed to set the state. The tty state was invalid or not initialized.
2240b57cec5SDimitry Andric   return false;
2250b57cec5SDimitry Andric }
2260b57cec5SDimitry Andric 
2270b57cec5SDimitry Andric // Save the state at index "idx" for file descriptor "fd" and save the process
2280b57cec5SDimitry Andric // group if requested.
2290b57cec5SDimitry Andric //
2300b57cec5SDimitry Andric // Returns true if the restore was successful, false otherwise.
Save(uint32_t idx,int fd,bool save_process_group)2310b57cec5SDimitry Andric bool TerminalStateSwitcher::Save(uint32_t idx, int fd,
2320b57cec5SDimitry Andric                                  bool save_process_group) {
2330b57cec5SDimitry Andric   const uint32_t num_states = GetNumberOfStates();
2340b57cec5SDimitry Andric   if (idx < num_states)
2350b57cec5SDimitry Andric     return m_ttystates[idx].Save(fd, save_process_group);
2360b57cec5SDimitry Andric   return false;
2370b57cec5SDimitry Andric }
238