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