1//===-- Terminal.cpp --------------------------------------------*- C++ -*-===//
2//
3//                     The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9
10#include "lldb/Host/Terminal.h"
11#include "lldb/Host/Config.h"
12
13#include <fcntl.h>
14#include <unistd.h>
15#include <signal.h>
16
17#ifdef LLDB_CONFIG_TERMIOS_SUPPORTED
18#include <termios.h>
19#endif
20
21
22using namespace lldb_private;
23
24bool
25Terminal::IsATerminal () const
26{
27    return m_fd >= 0 && ::isatty (m_fd);
28}
29
30
31bool
32Terminal::SetEcho (bool enabled)
33{
34    if (FileDescriptorIsValid())
35    {
36#ifdef LLDB_CONFIG_TERMIOS_SUPPORTED
37        if (IsATerminal ())
38        {
39            struct termios fd_termios;
40            if (::tcgetattr(m_fd, &fd_termios) == 0)
41            {
42                bool set_corectly = false;
43                if (enabled)
44                {
45                    if (fd_termios.c_lflag & ECHO)
46                        set_corectly = true;
47                    else
48                        fd_termios.c_lflag |= ECHO;
49                }
50                else
51                {
52                    if (fd_termios.c_lflag & ECHO)
53                        fd_termios.c_lflag &= ~ECHO;
54                    else
55                        set_corectly = true;
56                }
57
58                if (set_corectly)
59                    return true;
60                return ::tcsetattr (m_fd, TCSANOW, &fd_termios) == 0;
61            }
62        }
63#endif // #ifdef LLDB_CONFIG_TERMIOS_SUPPORTED
64    }
65    return false;
66}
67
68bool
69Terminal::SetCanonical (bool enabled)
70{
71    if (FileDescriptorIsValid())
72    {
73#ifdef LLDB_CONFIG_TERMIOS_SUPPORTED
74        if (IsATerminal ())
75        {
76            struct termios fd_termios;
77            if (::tcgetattr(m_fd, &fd_termios) == 0)
78            {
79                bool set_corectly = false;
80                if (enabled)
81                {
82                    if (fd_termios.c_lflag & ICANON)
83                        set_corectly = true;
84                    else
85                        fd_termios.c_lflag |= ICANON;
86                }
87                else
88                {
89                    if (fd_termios.c_lflag & ICANON)
90                        fd_termios.c_lflag &= ~ICANON;
91                    else
92                        set_corectly = true;
93                }
94
95                if (set_corectly)
96                    return true;
97                return ::tcsetattr (m_fd, TCSANOW, &fd_termios) == 0;
98            }
99        }
100#endif // #ifdef LLDB_CONFIG_TERMIOS_SUPPORTED
101    }
102    return false;
103}
104
105//----------------------------------------------------------------------
106// Default constructor
107//----------------------------------------------------------------------
108TerminalState::TerminalState() :
109    m_tty(),
110    m_tflags(-1),
111    m_termios_ap(),
112    m_process_group(-1)
113{
114}
115
116//----------------------------------------------------------------------
117// Destructor
118//----------------------------------------------------------------------
119TerminalState::~TerminalState()
120{
121}
122
123void
124TerminalState::Clear ()
125{
126    m_tty.Clear();
127    m_tflags = -1;
128    m_termios_ap.reset();
129    m_process_group = -1;
130}
131
132//----------------------------------------------------------------------
133// Save the current state of the TTY for the file descriptor "fd"
134// and if "save_process_group" is true, attempt to save the process
135// group info for the TTY.
136//----------------------------------------------------------------------
137bool
138TerminalState::Save (int fd, bool save_process_group)
139{
140    m_tty.SetFileDescriptor(fd);
141    if (m_tty.IsATerminal())
142    {
143        m_tflags = ::fcntl (fd, F_GETFL, 0);
144#ifdef LLDB_CONFIG_TERMIOS_SUPPORTED
145        if (m_termios_ap.get() == NULL)
146            m_termios_ap.reset (new struct termios);
147        int err = ::tcgetattr (fd, m_termios_ap.get());
148        if (err != 0)
149            m_termios_ap.reset();
150#endif // #ifdef LLDB_CONFIG_TERMIOS_SUPPORTED
151        if (save_process_group)
152            m_process_group = ::tcgetpgrp (0);
153        else
154            m_process_group = -1;
155    }
156    else
157    {
158        m_tty.Clear();
159        m_tflags = -1;
160        m_termios_ap.reset();
161        m_process_group = -1;
162    }
163    return IsValid();
164}
165
166//----------------------------------------------------------------------
167// Restore the state of the TTY using the cached values from a
168// previous call to Save().
169//----------------------------------------------------------------------
170bool
171TerminalState::Restore () const
172{
173    if (IsValid())
174    {
175        const int fd = m_tty.GetFileDescriptor();
176        if (TFlagsIsValid())
177            fcntl (fd, F_SETFL, m_tflags);
178
179#ifdef LLDB_CONFIG_TERMIOS_SUPPORTED
180        if (TTYStateIsValid())
181            tcsetattr (fd, TCSANOW, m_termios_ap.get());
182#endif // #ifdef LLDB_CONFIG_TERMIOS_SUPPORTED
183
184        if (ProcessGroupIsValid())
185        {
186            // Save the original signal handler.
187            void (*saved_sigttou_callback) (int) = NULL;
188            saved_sigttou_callback = (void (*)(int)) signal (SIGTTOU, SIG_IGN);
189            // Set the process group
190            tcsetpgrp (fd, m_process_group);
191            // Restore the original signal handler.
192            signal (SIGTTOU, saved_sigttou_callback);
193        }
194        return true;
195    }
196    return false;
197}
198
199
200
201
202//----------------------------------------------------------------------
203// Returns true if this object has valid saved TTY state settings
204// that can be used to restore a previous state.
205//----------------------------------------------------------------------
206bool
207TerminalState::IsValid() const
208{
209    return m_tty.FileDescriptorIsValid () && (TFlagsIsValid() || TTYStateIsValid());
210}
211
212//----------------------------------------------------------------------
213// Returns true if m_tflags is valid
214//----------------------------------------------------------------------
215bool
216TerminalState::TFlagsIsValid() const
217{
218    return m_tflags != -1;
219}
220
221//----------------------------------------------------------------------
222// Returns true if m_ttystate is valid
223//----------------------------------------------------------------------
224bool
225TerminalState::TTYStateIsValid() const
226{
227    return m_termios_ap.get() != 0;
228}
229
230//----------------------------------------------------------------------
231// Returns true if m_process_group is valid
232//----------------------------------------------------------------------
233bool
234TerminalState::ProcessGroupIsValid() const
235{
236    return m_process_group != -1;
237}
238
239//------------------------------------------------------------------
240// Constructor
241//------------------------------------------------------------------
242TerminalStateSwitcher::TerminalStateSwitcher () :
243    m_currentState(UINT32_MAX)
244{
245}
246
247//------------------------------------------------------------------
248// Destructor
249//------------------------------------------------------------------
250TerminalStateSwitcher::~TerminalStateSwitcher ()
251{
252}
253
254//------------------------------------------------------------------
255// Returns the number of states that this switcher contains
256//------------------------------------------------------------------
257uint32_t
258TerminalStateSwitcher::GetNumberOfStates() const
259{
260    return sizeof(m_ttystates)/sizeof(TerminalState);
261}
262
263//------------------------------------------------------------------
264// Restore the state at index "idx".
265//
266// Returns true if the restore was successful, false otherwise.
267//------------------------------------------------------------------
268bool
269TerminalStateSwitcher::Restore (uint32_t idx) const
270{
271    const uint32_t num_states = GetNumberOfStates();
272    if (idx >= num_states)
273        return false;
274
275    // See if we already are in this state?
276    if (m_currentState < num_states && (idx == m_currentState) && m_ttystates[idx].IsValid())
277        return true;
278
279    // Set the state to match the index passed in and only update the
280    // current state if there are no errors.
281    if (m_ttystates[idx].Restore())
282    {
283        m_currentState = idx;
284        return true;
285    }
286
287    // We failed to set the state. The tty state was invalid or not
288    // initialized.
289    return false;
290}
291
292//------------------------------------------------------------------
293// Save the state at index "idx" for file descriptor "fd" and
294// save the process group if requested.
295//
296// Returns true if the restore was successful, false otherwise.
297//------------------------------------------------------------------
298bool
299TerminalStateSwitcher::Save (uint32_t idx, int fd, bool save_process_group)
300{
301    const uint32_t num_states = GetNumberOfStates();
302    if (idx < num_states)
303        return m_ttystates[idx].Save(fd, save_process_group);
304    return false;
305}
306
307
308