diff --git a/lldb/include/lldb/Host/Terminal.h b/lldb/include/lldb/Host/Terminal.h --- a/lldb/include/lldb/Host/Terminal.h +++ b/lldb/include/lldb/Host/Terminal.h @@ -20,6 +20,14 @@ class Terminal { public: + enum class Parity { + No, + Even, + Odd, + Space, + Mark, + }; + Terminal(int fd = -1) : m_fd(fd) {} ~Terminal() = default; @@ -38,6 +46,16 @@ llvm::Error SetCanonical(bool enabled); + llvm::Error SetRaw(); + + llvm::Error SetBaudRate(unsigned int baud_rate); + + llvm::Error SetStopBits(unsigned int stop_bits); + + llvm::Error SetParity(Parity parity); + + llvm::Error SetHardwareFlowControl(bool enabled); + protected: struct Data; diff --git a/lldb/source/Host/common/Terminal.cpp b/lldb/source/Host/common/Terminal.cpp --- a/lldb/source/Host/common/Terminal.cpp +++ b/lldb/source/Host/common/Terminal.cpp @@ -94,6 +94,267 @@ #endif // LLDB_ENABLE_TERMIOS } +llvm::Error Terminal::SetRaw() { + llvm::Expected data = GetData(); + if (!data) + return data.takeError(); + +#if LLDB_ENABLE_TERMIOS + struct termios &fd_termios = data->m_termios; + ::cfmakeraw(&fd_termios); + + // Make sure only one character is needed to return from a read + // (cfmakeraw() doesn't do this on NetBSD) + fd_termios.c_cc[VMIN] = 1; + fd_termios.c_cc[VTIME] = 0; + + return SetData(data.get()); +#endif // #if LLDB_ENABLE_TERMIOS +} + +static llvm::Optional baudRateToConst(unsigned int baud_rate) { + switch (baud_rate) { +#if defined(B50) + case 50: + return B50; +#endif +#if defined(B75) + case 75: + return B75; +#endif +#if defined(B110) + case 110: + return B110; +#endif +#if defined(B134) + case 134: + return B134; +#endif +#if defined(B150) + case 150: + return B150; +#endif +#if defined(B200) + case 200: + return B200; +#endif +#if defined(B300) + case 300: + return B300; +#endif +#if defined(B600) + case 600: + return B600; +#endif +#if defined(B1200) + case 1200: + return B1200; +#endif +#if defined(B1800) + case 1800: + return B1800; +#endif +#if defined(B2400) + case 2400: + return B2400; +#endif +#if defined(B4800) + case 4800: + return B4800; +#endif +#if defined(B9600) + case 9600: + return B9600; +#endif +#if defined(B19200) + case 19200: + return B19200; +#endif +#if defined(B38400) + case 38400: + return B38400; +#endif +#if defined(B57600) + case 57600: + return B57600; +#endif +#if defined(B115200) + case 115200: + return B115200; +#endif +#if defined(B230400) + case 230400: + return B230400; +#endif +#if defined(B460800) + case 460800: + return B460800; +#endif +#if defined(B500000) + case 500000: + return B500000; +#endif +#if defined(B576000) + case 576000: + return B576000; +#endif +#if defined(B921600) + case 921600: + return B921600; +#endif +#if defined(B1000000) + case 1000000: + return B1000000; +#endif +#if defined(B1152000) + case 1152000: + return B1152000; +#endif +#if defined(B1500000) + case 1500000: + return B1500000; +#endif +#if defined(B2000000) + case 2000000: + return B2000000; +#endif +#if defined(B76800) + case 76800: + return B76800; +#endif +#if defined(B153600) + case 153600: + return B153600; +#endif +#if defined(B307200) + case 307200: + return B307200; +#endif +#if defined(B614400) + case 614400: + return B614400; +#endif +#if defined(B2500000) + case 2500000: + return B2500000; +#endif +#if defined(B3000000) + case 3000000: + return B3000000; +#endif +#if defined(B3500000) + case 3500000: + return B3500000; +#endif +#if defined(B4000000) + case 4000000: + return B4000000; +#endif + default: + return llvm::None; + } +} + +llvm::Error Terminal::SetBaudRate(unsigned int baud_rate) { + llvm::Expected data = GetData(); + if (!data) + return data.takeError(); + +#if LLDB_ENABLE_TERMIOS + struct termios &fd_termios = data->m_termios; + llvm::Optional val = baudRateToConst(baud_rate); + if (!val) // invalid value + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "baud rate %d unsupported by the platform", + baud_rate); + if (::cfsetispeed(&fd_termios, val.getValue()) != 0) + return llvm::createStringError( + std::error_code(errno, std::generic_category()), + "setting input baud rate failed"); + if (::cfsetospeed(&fd_termios, val.getValue()) != 0) + return llvm::createStringError( + std::error_code(errno, std::generic_category()), + "setting output baud rate failed"); + return SetData(data.get()); +#endif // #if LLDB_ENABLE_TERMIOS +} + +llvm::Error Terminal::SetStopBits(unsigned int stop_bits) { + llvm::Expected data = GetData(); + if (!data) + return data.takeError(); + +#if LLDB_ENABLE_TERMIOS + struct termios &fd_termios = data->m_termios; + switch (stop_bits) { + case 1: + fd_termios.c_cflag &= ~CSTOPB; + break; + case 2: + fd_termios.c_cflag |= CSTOPB; + break; + default: + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "invalid stop bit count: %d (must be 1 or 2)", stop_bits); + } + return SetData(data.get()); +#endif // #if LLDB_ENABLE_TERMIOS +} + +llvm::Error Terminal::SetParity(Terminal::Parity parity) { + llvm::Expected data = GetData(); + if (!data) + return data.takeError(); + +#if LLDB_ENABLE_TERMIOS + struct termios &fd_termios = data->m_termios; + fd_termios.c_cflag &= ~( +#if defined(CMSPAR) + CMSPAR | +#endif + PARENB | PARODD); + + if (parity != Parity::No) { + fd_termios.c_cflag |= PARENB; + if (parity == Parity::Odd || parity == Parity::Mark) + fd_termios.c_cflag |= PARODD; + if (parity == Parity::Mark || parity == Parity::Space) { +#if defined(CMSPAR) + fd_termios.c_cflag |= CMSPAR; +#else + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "space/mark parity is not supported by the platform"); +#endif + } + } + return SetData(data.get()); +#endif // #if LLDB_ENABLE_TERMIOS +} + +llvm::Error Terminal::SetHardwareFlowControl(bool enabled) { + llvm::Expected data = GetData(); + if (!data) + return data.takeError(); + +#if LLDB_ENABLE_TERMIOS +#if defined(CRTSCTS) + struct termios &fd_termios = data->m_termios; + fd_termios.c_cflag &= ~CRTSCTS; + if (enabled) + fd_termios.c_cflag |= CRTSCTS; + return SetData(data.get()); +#else // !defined(CRTSCTS) + if (enabled) + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "hardware flow control is not supported by the platform"); + return llvm::Error::success(); +#endif // defined(CRTSCTS) +#endif // #if LLDB_ENABLE_TERMIOS +} + TerminalState::TerminalState(Terminal term, bool save_process_group) : m_tty(term) { Save(term, save_process_group); diff --git a/lldb/unittests/Host/posix/TerminalTest.cpp b/lldb/unittests/Host/posix/TerminalTest.cpp --- a/lldb/unittests/Host/posix/TerminalTest.cpp +++ b/lldb/unittests/Host/posix/TerminalTest.cpp @@ -72,6 +72,149 @@ EXPECT_EQ(terminfo.c_lflag & ICANON, 0U); } +TEST_F(TerminalTest, SetRaw) { + struct termios terminfo; + + ASSERT_THAT_ERROR(m_term.SetRaw(), llvm::Succeeded()); + ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0); + // NB: cfmakeraw() on glibc disables IGNBRK, on FreeBSD sets it + EXPECT_EQ(terminfo.c_iflag & + (BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON), + 0U); + EXPECT_EQ(terminfo.c_oflag & OPOST, 0U); + EXPECT_EQ(terminfo.c_lflag & (ICANON | ECHO | ISIG | IEXTEN), 0U); + EXPECT_EQ(terminfo.c_cflag & (CSIZE | PARENB), 0U | CS8); + EXPECT_EQ(terminfo.c_cc[VMIN], 1); + EXPECT_EQ(terminfo.c_cc[VTIME], 0); +} + +TEST_F(TerminalTest, SetBaudRate) { + struct termios terminfo; + + ASSERT_THAT_ERROR(m_term.SetBaudRate(38400), llvm::Succeeded()); + ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0); + EXPECT_EQ(cfgetispeed(&terminfo), static_cast(B38400)); + EXPECT_EQ(cfgetospeed(&terminfo), static_cast(B38400)); + + ASSERT_THAT_ERROR(m_term.SetBaudRate(115200), llvm::Succeeded()); + ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0); + EXPECT_EQ(cfgetispeed(&terminfo), static_cast(B115200)); + EXPECT_EQ(cfgetospeed(&terminfo), static_cast(B115200)); + + // uncommon value +#if defined(B153600) + ASSERT_THAT_ERROR(m_term.SetBaudRate(153600), llvm::Succeeded()); + ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0); + EXPECT_EQ(cfgetispeed(&terminfo), static_cast(B153600)); + EXPECT_EQ(cfgetospeed(&terminfo), static_cast(B153600)); +#else + ASSERT_THAT_ERROR(m_term.SetBaudRate(153600), + llvm::Failed(testing::Property( + &llvm::ErrorInfoBase::message, + "baud rate 153600 unsupported by the platform"))); +#endif +} + +TEST_F(TerminalTest, SetStopBits) { + struct termios terminfo; + + ASSERT_THAT_ERROR(m_term.SetStopBits(1), llvm::Succeeded()); + ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0); + EXPECT_EQ(terminfo.c_cflag & CSTOPB, 0U); + + ASSERT_THAT_ERROR(m_term.SetStopBits(2), llvm::Succeeded()); + ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0); + EXPECT_NE(terminfo.c_cflag & CSTOPB, 0U); + + ASSERT_THAT_ERROR(m_term.SetStopBits(0), + llvm::Failed(testing::Property( + &llvm::ErrorInfoBase::message, + "invalid stop bit count: 0 (must be 1 or 2)"))); + ASSERT_THAT_ERROR(m_term.SetStopBits(3), + llvm::Failed(testing::Property( + &llvm::ErrorInfoBase::message, + "invalid stop bit count: 3 (must be 1 or 2)"))); +} + +TEST_F(TerminalTest, SetParity) { + struct termios terminfo; + + ASSERT_THAT_ERROR(m_term.SetParity(Terminal::Parity::No), llvm::Succeeded()); + ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0); + EXPECT_EQ(terminfo.c_cflag & PARENB, 0U); + + ASSERT_THAT_ERROR(m_term.SetParity(Terminal::Parity::Even), + llvm::Succeeded()); + ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0); +#if !defined(__linux__) // Linux pty devices strip PARENB + EXPECT_NE(terminfo.c_cflag & PARENB, 0U); +#endif + EXPECT_EQ(terminfo.c_cflag & PARODD, 0U); +#if defined(CMSPAR) + EXPECT_EQ(terminfo.c_cflag & CMSPAR, 0U); +#endif + + ASSERT_THAT_ERROR(m_term.SetParity(Terminal::Parity::Odd), llvm::Succeeded()); + ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0); +#if !defined(__linux__) // Linux pty devices strip PARENB + EXPECT_NE(terminfo.c_cflag & PARENB, 0U); +#endif + EXPECT_NE(terminfo.c_cflag & PARODD, 0U); +#if defined(CMSPAR) + EXPECT_EQ(terminfo.c_cflag & CMSPAR, 0U); +#endif + +#if defined(CMSPAR) + ASSERT_THAT_ERROR(m_term.SetParity(Terminal::Parity::Space), + llvm::Succeeded()); + ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0); +#if !defined(__linux__) // Linux pty devices strip PARENB + EXPECT_NE(terminfo.c_cflag & PARENB, 0U); +#endif + EXPECT_EQ(terminfo.c_cflag & PARODD, 0U); + EXPECT_NE(terminfo.c_cflag & CMSPAR, 0U); + + ASSERT_THAT_ERROR(m_term.SetParity(Terminal::Parity::Mark), + llvm::Succeeded()); + ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0); +#if !defined(__linux__) // Linux pty devices strip PARENB + EXPECT_NE(terminfo.c_cflag & PARENB, 0U); +#endif + EXPECT_NE(terminfo.c_cflag & PARODD, 0U); + EXPECT_NE(terminfo.c_cflag & CMSPAR, 0U); +#else + ASSERT_THAT_ERROR(m_term.SetParity(Terminal::Parity::Space), + llvm::Failed(testing::Property( + &llvm::ErrorInfoBase::message, + "space/mark parity is not supported by the platform"))); + ASSERT_THAT_ERROR(m_term.SetParity(Terminal::Parity::Mark), + llvm::Failed(testing::Property( + &llvm::ErrorInfoBase::message, + "space/mark parity is not supported by the platform"))); +#endif +} + +TEST_F(TerminalTest, SetHardwareFlowControl) { +#if defined(CRTSCTS) + struct termios terminfo; + + ASSERT_THAT_ERROR(m_term.SetHardwareFlowControl(true), llvm::Succeeded()); + ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0); + EXPECT_NE(terminfo.c_cflag & CRTSCTS, 0U); + + ASSERT_THAT_ERROR(m_term.SetHardwareFlowControl(false), llvm::Succeeded()); + ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0); + EXPECT_EQ(terminfo.c_cflag & CRTSCTS, 0U); +#else + ASSERT_THAT_ERROR( + m_term.SetHardwareFlowControl(true), + llvm::Failed(testing::Property( + &llvm::ErrorInfoBase::message, + "hardware flow control is not supported by the platform"))); + ASSERT_THAT_ERROR(m_term.SetHardwareFlowControl(false), llvm::Succeeded()); +#endif +} + TEST_F(TerminalTest, SaveRestoreRAII) { struct termios orig_terminfo; struct termios terminfo;