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 #if !LLDB_ENABLE_TERMIOS
33 static llvm::Error termiosMissingError() {
34   return llvm::createStringError(llvm::inconvertibleErrorCode(),
35                                  "termios support missing in LLDB");
36 }
37 #endif
38 
39 llvm::Expected<Terminal::Data> Terminal::GetData() {
40 #if LLDB_ENABLE_TERMIOS
41   if (!FileDescriptorIsValid())
42     return llvm::createStringError(llvm::inconvertibleErrorCode(),
43                                    "invalid fd");
44 
45   if (!IsATerminal())
46     return llvm::createStringError(llvm::inconvertibleErrorCode(),
47                                    "fd not a terminal");
48 
49   Data data;
50   if (::tcgetattr(m_fd, &data.m_termios) != 0)
51     return llvm::createStringError(
52         std::error_code(errno, std::generic_category()),
53         "unable to get teletype attributes");
54   return data;
55 #else // !LLDB_ENABLE_TERMIOS
56   return termiosMissingError();
57 #endif // LLDB_ENABLE_TERMIOS
58 }
59 
60 llvm::Error Terminal::SetData(const Terminal::Data &data) {
61 #if LLDB_ENABLE_TERMIOS
62   assert(FileDescriptorIsValid());
63   assert(IsATerminal());
64 
65   if (::tcsetattr(m_fd, TCSANOW, &data.m_termios) != 0)
66     return llvm::createStringError(
67         std::error_code(errno, std::generic_category()),
68         "unable to set teletype attributes");
69   return llvm::Error::success();
70 #else // !LLDB_ENABLE_TERMIOS
71   return termiosMissingError();
72 #endif // LLDB_ENABLE_TERMIOS
73 }
74 
75 llvm::Error Terminal::SetEcho(bool enabled) {
76 #if LLDB_ENABLE_TERMIOS
77   llvm::Expected<Data> data = GetData();
78   if (!data)
79     return data.takeError();
80 
81   struct termios &fd_termios = data->m_termios;
82   fd_termios.c_lflag &= ~ECHO;
83   if (enabled)
84     fd_termios.c_lflag |= ECHO;
85   return SetData(data.get());
86 #else // !LLDB_ENABLE_TERMIOS
87   return termiosMissingError();
88 #endif // LLDB_ENABLE_TERMIOS
89 }
90 
91 llvm::Error Terminal::SetCanonical(bool enabled) {
92 #if LLDB_ENABLE_TERMIOS
93   llvm::Expected<Data> data = GetData();
94   if (!data)
95     return data.takeError();
96 
97   struct termios &fd_termios = data->m_termios;
98   fd_termios.c_lflag &= ~ICANON;
99   if (enabled)
100     fd_termios.c_lflag |= ICANON;
101   return SetData(data.get());
102 #else // !LLDB_ENABLE_TERMIOS
103   return termiosMissingError();
104 #endif // LLDB_ENABLE_TERMIOS
105 }
106 
107 llvm::Error Terminal::SetRaw() {
108 #if LLDB_ENABLE_TERMIOS
109   llvm::Expected<Data> data = GetData();
110   if (!data)
111     return data.takeError();
112 
113   struct termios &fd_termios = data->m_termios;
114   ::cfmakeraw(&fd_termios);
115 
116   // Make sure only one character is needed to return from a read
117   // (cfmakeraw() doesn't do this on NetBSD)
118   fd_termios.c_cc[VMIN] = 1;
119   fd_termios.c_cc[VTIME] = 0;
120 
121   return SetData(data.get());
122 #else // !LLDB_ENABLE_TERMIOS
123   return termiosMissingError();
124 #endif // LLDB_ENABLE_TERMIOS
125 }
126 
127 #if LLDB_ENABLE_TERMIOS
128 static llvm::Optional<speed_t> baudRateToConst(unsigned int baud_rate) {
129   switch (baud_rate) {
130 #if defined(B50)
131   case 50:
132     return B50;
133 #endif
134 #if defined(B75)
135   case 75:
136     return B75;
137 #endif
138 #if defined(B110)
139   case 110:
140     return B110;
141 #endif
142 #if defined(B134)
143   case 134:
144     return B134;
145 #endif
146 #if defined(B150)
147   case 150:
148     return B150;
149 #endif
150 #if defined(B200)
151   case 200:
152     return B200;
153 #endif
154 #if defined(B300)
155   case 300:
156     return B300;
157 #endif
158 #if defined(B600)
159   case 600:
160     return B600;
161 #endif
162 #if defined(B1200)
163   case 1200:
164     return B1200;
165 #endif
166 #if defined(B1800)
167   case 1800:
168     return B1800;
169 #endif
170 #if defined(B2400)
171   case 2400:
172     return B2400;
173 #endif
174 #if defined(B4800)
175   case 4800:
176     return B4800;
177 #endif
178 #if defined(B9600)
179   case 9600:
180     return B9600;
181 #endif
182 #if defined(B19200)
183   case 19200:
184     return B19200;
185 #endif
186 #if defined(B38400)
187   case 38400:
188     return B38400;
189 #endif
190 #if defined(B57600)
191   case 57600:
192     return B57600;
193 #endif
194 #if defined(B115200)
195   case 115200:
196     return B115200;
197 #endif
198 #if defined(B230400)
199   case 230400:
200     return B230400;
201 #endif
202 #if defined(B460800)
203   case 460800:
204     return B460800;
205 #endif
206 #if defined(B500000)
207   case 500000:
208     return B500000;
209 #endif
210 #if defined(B576000)
211   case 576000:
212     return B576000;
213 #endif
214 #if defined(B921600)
215   case 921600:
216     return B921600;
217 #endif
218 #if defined(B1000000)
219   case 1000000:
220     return B1000000;
221 #endif
222 #if defined(B1152000)
223   case 1152000:
224     return B1152000;
225 #endif
226 #if defined(B1500000)
227   case 1500000:
228     return B1500000;
229 #endif
230 #if defined(B2000000)
231   case 2000000:
232     return B2000000;
233 #endif
234 #if defined(B76800)
235   case 76800:
236     return B76800;
237 #endif
238 #if defined(B153600)
239   case 153600:
240     return B153600;
241 #endif
242 #if defined(B307200)
243   case 307200:
244     return B307200;
245 #endif
246 #if defined(B614400)
247   case 614400:
248     return B614400;
249 #endif
250 #if defined(B2500000)
251   case 2500000:
252     return B2500000;
253 #endif
254 #if defined(B3000000)
255   case 3000000:
256     return B3000000;
257 #endif
258 #if defined(B3500000)
259   case 3500000:
260     return B3500000;
261 #endif
262 #if defined(B4000000)
263   case 4000000:
264     return B4000000;
265 #endif
266   default:
267     return llvm::None;
268   }
269 }
270 #endif
271 
272 llvm::Error Terminal::SetBaudRate(unsigned int baud_rate) {
273 #if LLDB_ENABLE_TERMIOS
274   llvm::Expected<Data> data = GetData();
275   if (!data)
276     return data.takeError();
277 
278   struct termios &fd_termios = data->m_termios;
279   llvm::Optional<speed_t> val = baudRateToConst(baud_rate);
280   if (!val) // invalid value
281     return llvm::createStringError(llvm::inconvertibleErrorCode(),
282                                    "baud rate %d unsupported by the platform",
283                                    baud_rate);
284   if (::cfsetispeed(&fd_termios, val.value()) != 0)
285     return llvm::createStringError(
286         std::error_code(errno, std::generic_category()),
287         "setting input baud rate failed");
288   if (::cfsetospeed(&fd_termios, val.value()) != 0)
289     return llvm::createStringError(
290         std::error_code(errno, std::generic_category()),
291         "setting output baud rate failed");
292   return SetData(data.get());
293 #else // !LLDB_ENABLE_TERMIOS
294   return termiosMissingError();
295 #endif // LLDB_ENABLE_TERMIOS
296 }
297 
298 llvm::Error Terminal::SetStopBits(unsigned int stop_bits) {
299 #if LLDB_ENABLE_TERMIOS
300   llvm::Expected<Data> data = GetData();
301   if (!data)
302     return data.takeError();
303 
304   struct termios &fd_termios = data->m_termios;
305   switch (stop_bits) {
306   case 1:
307     fd_termios.c_cflag &= ~CSTOPB;
308     break;
309   case 2:
310     fd_termios.c_cflag |= CSTOPB;
311     break;
312   default:
313     return llvm::createStringError(
314         llvm::inconvertibleErrorCode(),
315         "invalid stop bit count: %d (must be 1 or 2)", stop_bits);
316   }
317   return SetData(data.get());
318 #else // !LLDB_ENABLE_TERMIOS
319   return termiosMissingError();
320 #endif // LLDB_ENABLE_TERMIOS
321 }
322 
323 llvm::Error Terminal::SetParity(Terminal::Parity parity) {
324 #if LLDB_ENABLE_TERMIOS
325   llvm::Expected<Data> data = GetData();
326   if (!data)
327     return data.takeError();
328 
329   struct termios &fd_termios = data->m_termios;
330   fd_termios.c_cflag &= ~(
331 #if defined(CMSPAR)
332       CMSPAR |
333 #endif
334       PARENB | PARODD);
335 
336   if (parity != Parity::No) {
337     fd_termios.c_cflag |= PARENB;
338     if (parity == Parity::Odd || parity == Parity::Mark)
339       fd_termios.c_cflag |= PARODD;
340     if (parity == Parity::Mark || parity == Parity::Space) {
341 #if defined(CMSPAR)
342       fd_termios.c_cflag |= CMSPAR;
343 #else
344       return llvm::createStringError(
345           llvm::inconvertibleErrorCode(),
346           "space/mark parity is not supported by the platform");
347 #endif
348     }
349   }
350   return SetData(data.get());
351 #else // !LLDB_ENABLE_TERMIOS
352   return termiosMissingError();
353 #endif // LLDB_ENABLE_TERMIOS
354 }
355 
356 llvm::Error Terminal::SetParityCheck(Terminal::ParityCheck parity_check) {
357 #if LLDB_ENABLE_TERMIOS
358   llvm::Expected<Data> data = GetData();
359   if (!data)
360     return data.takeError();
361 
362   struct termios &fd_termios = data->m_termios;
363   fd_termios.c_iflag &= ~(IGNPAR | PARMRK | INPCK);
364 
365   if (parity_check != ParityCheck::No) {
366     fd_termios.c_iflag |= INPCK;
367     if (parity_check == ParityCheck::Ignore)
368       fd_termios.c_iflag |= IGNPAR;
369     else if (parity_check == ParityCheck::Mark)
370       fd_termios.c_iflag |= PARMRK;
371   }
372   return SetData(data.get());
373 #else // !LLDB_ENABLE_TERMIOS
374   return termiosMissingError();
375 #endif // LLDB_ENABLE_TERMIOS
376 }
377 
378 llvm::Error Terminal::SetHardwareFlowControl(bool enabled) {
379 #if LLDB_ENABLE_TERMIOS
380   llvm::Expected<Data> data = GetData();
381   if (!data)
382     return data.takeError();
383 
384 #if defined(CRTSCTS)
385   struct termios &fd_termios = data->m_termios;
386   fd_termios.c_cflag &= ~CRTSCTS;
387   if (enabled)
388     fd_termios.c_cflag |= CRTSCTS;
389   return SetData(data.get());
390 #else  // !defined(CRTSCTS)
391   if (enabled)
392     return llvm::createStringError(
393         llvm::inconvertibleErrorCode(),
394         "hardware flow control is not supported by the platform");
395   return llvm::Error::success();
396 #endif // defined(CRTSCTS)
397 #else // !LLDB_ENABLE_TERMIOS
398   return termiosMissingError();
399 #endif // LLDB_ENABLE_TERMIOS
400 }
401 
402 TerminalState::TerminalState(Terminal term, bool save_process_group)
403     : m_tty(term) {
404   Save(term, save_process_group);
405 }
406 
407 TerminalState::~TerminalState() { Restore(); }
408 
409 void TerminalState::Clear() {
410   m_tty.Clear();
411   m_tflags = -1;
412   m_data.reset();
413   m_process_group = -1;
414 }
415 
416 bool TerminalState::Save(Terminal term, bool save_process_group) {
417   Clear();
418   m_tty = term;
419   if (m_tty.IsATerminal()) {
420 #if LLDB_ENABLE_POSIX
421     int fd = m_tty.GetFileDescriptor();
422     m_tflags = ::fcntl(fd, F_GETFL, 0);
423 #if LLDB_ENABLE_TERMIOS
424     std::unique_ptr<Terminal::Data> new_data{new Terminal::Data()};
425     if (::tcgetattr(fd, &new_data->m_termios) == 0)
426       m_data = std::move(new_data);
427 #endif // LLDB_ENABLE_TERMIOS
428     if (save_process_group)
429       m_process_group = ::tcgetpgrp(fd);
430 #endif // LLDB_ENABLE_POSIX
431   }
432   return IsValid();
433 }
434 
435 bool TerminalState::Restore() const {
436 #if LLDB_ENABLE_POSIX
437   if (IsValid()) {
438     const int fd = m_tty.GetFileDescriptor();
439     if (TFlagsIsValid())
440       fcntl(fd, F_SETFL, m_tflags);
441 
442 #if LLDB_ENABLE_TERMIOS
443     if (TTYStateIsValid())
444       tcsetattr(fd, TCSANOW, &m_data->m_termios);
445 #endif // LLDB_ENABLE_TERMIOS
446 
447     if (ProcessGroupIsValid()) {
448       // Save the original signal handler.
449       void (*saved_sigttou_callback)(int) = nullptr;
450       saved_sigttou_callback = (void (*)(int))signal(SIGTTOU, SIG_IGN);
451       // Set the process group
452       tcsetpgrp(fd, m_process_group);
453       // Restore the original signal handler.
454       signal(SIGTTOU, saved_sigttou_callback);
455     }
456     return true;
457   }
458 #endif // LLDB_ENABLE_POSIX
459   return false;
460 }
461 
462 bool TerminalState::IsValid() const {
463   return m_tty.FileDescriptorIsValid() &&
464          (TFlagsIsValid() || TTYStateIsValid() || ProcessGroupIsValid());
465 }
466 
467 bool TerminalState::TFlagsIsValid() const { return m_tflags != -1; }
468 
469 bool TerminalState::TTYStateIsValid() const { return bool(m_data); }
470 
471 bool TerminalState::ProcessGroupIsValid() const {
472   return static_cast<int32_t>(m_process_group) != -1;
473 }
474