diff options
Diffstat (limited to 'source/Host/common/PseudoTerminal.cpp')
-rw-r--r-- | source/Host/common/PseudoTerminal.cpp | 310 |
1 files changed, 310 insertions, 0 deletions
diff --git a/source/Host/common/PseudoTerminal.cpp b/source/Host/common/PseudoTerminal.cpp new file mode 100644 index 000000000000..58c32e4a1c4b --- /dev/null +++ b/source/Host/common/PseudoTerminal.cpp @@ -0,0 +1,310 @@ +//===-- PseudoTerminal.cpp --------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/PseudoTerminal.h" +#include "lldb/Host/Config.h" + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#if defined(TIOCSCTTY) +#include <sys/ioctl.h> +#endif + +#include "lldb/Host/PosixApi.h" + +#if defined(__ANDROID__) +int posix_openpt(int flags); +#endif + +using namespace lldb_utility; + +//---------------------------------------------------------------------- +// PseudoTerminal constructor +//---------------------------------------------------------------------- +PseudoTerminal::PseudoTerminal() + : m_master_fd(invalid_fd), m_slave_fd(invalid_fd) {} + +//---------------------------------------------------------------------- +// Destructor +// +// The destructor will close the master and slave file descriptors +// if they are valid and ownership has not been released using the +// ReleaseMasterFileDescriptor() or the ReleaseSaveFileDescriptor() +// member functions. +//---------------------------------------------------------------------- +PseudoTerminal::~PseudoTerminal() { + CloseMasterFileDescriptor(); + CloseSlaveFileDescriptor(); +} + +//---------------------------------------------------------------------- +// Close the master file descriptor if it is valid. +//---------------------------------------------------------------------- +void PseudoTerminal::CloseMasterFileDescriptor() { + if (m_master_fd >= 0) { + ::close(m_master_fd); + m_master_fd = invalid_fd; + } +} + +//---------------------------------------------------------------------- +// Close the slave file descriptor if it is valid. +//---------------------------------------------------------------------- +void PseudoTerminal::CloseSlaveFileDescriptor() { + if (m_slave_fd >= 0) { + ::close(m_slave_fd); + m_slave_fd = invalid_fd; + } +} + +//---------------------------------------------------------------------- +// Open the first available pseudo terminal with OFLAG as the +// permissions. The file descriptor is stored in this object and can +// be accessed with the MasterFileDescriptor() accessor. The +// ownership of the master file descriptor can be released using +// the ReleaseMasterFileDescriptor() accessor. If this object has +// a valid master files descriptor when its destructor is called, it +// will close the master file descriptor, therefore clients must +// call ReleaseMasterFileDescriptor() if they wish to use the master +// file descriptor after this object is out of scope or destroyed. +// +// RETURNS: +// True when successful, false indicating an error occurred. +//---------------------------------------------------------------------- +bool PseudoTerminal::OpenFirstAvailableMaster(int oflag, char *error_str, + size_t error_len) { + if (error_str) + error_str[0] = '\0'; + +#if !defined(LLDB_DISABLE_POSIX) + // Open the master side of a pseudo terminal + m_master_fd = ::posix_openpt(oflag); + if (m_master_fd < 0) { + if (error_str) + ::strerror_r(errno, error_str, error_len); + return false; + } + + // Grant access to the slave pseudo terminal + if (::grantpt(m_master_fd) < 0) { + if (error_str) + ::strerror_r(errno, error_str, error_len); + CloseMasterFileDescriptor(); + return false; + } + + // Clear the lock flag on the slave pseudo terminal + if (::unlockpt(m_master_fd) < 0) { + if (error_str) + ::strerror_r(errno, error_str, error_len); + CloseMasterFileDescriptor(); + return false; + } + + return true; +#else + if (error_str) + ::snprintf(error_str, error_len, "%s", "pseudo terminal not supported"); + return false; +#endif +} + +//---------------------------------------------------------------------- +// Open the slave pseudo terminal for the current master pseudo +// terminal. A master pseudo terminal should already be valid prior to +// calling this function (see OpenFirstAvailableMaster()). +// The file descriptor is stored this object's member variables and can +// be accessed via the GetSlaveFileDescriptor(), or released using the +// ReleaseSlaveFileDescriptor() member function. +// +// RETURNS: +// True when successful, false indicating an error occurred. +//---------------------------------------------------------------------- +bool PseudoTerminal::OpenSlave(int oflag, char *error_str, size_t error_len) { + if (error_str) + error_str[0] = '\0'; + + CloseSlaveFileDescriptor(); + + // Open the master side of a pseudo terminal + const char *slave_name = GetSlaveName(error_str, error_len); + + if (slave_name == nullptr) + return false; + + m_slave_fd = ::open(slave_name, oflag); + + if (m_slave_fd < 0) { + if (error_str) + ::strerror_r(errno, error_str, error_len); + return false; + } + + return true; +} + +//---------------------------------------------------------------------- +// Get the name of the slave pseudo terminal. A master pseudo terminal +// should already be valid prior to calling this function (see +// OpenFirstAvailableMaster()). +// +// RETURNS: +// NULL if no valid master pseudo terminal or if ptsname() fails. +// The name of the slave pseudo terminal as a NULL terminated C string +// that comes from static memory, so a copy of the string should be +// made as subsequent calls can change this value. +//---------------------------------------------------------------------- +const char *PseudoTerminal::GetSlaveName(char *error_str, + size_t error_len) const { + if (error_str) + error_str[0] = '\0'; + + if (m_master_fd < 0) { + if (error_str) + ::snprintf(error_str, error_len, "%s", + "master file descriptor is invalid"); + return nullptr; + } + const char *slave_name = ::ptsname(m_master_fd); + + if (error_str && slave_name == nullptr) + ::strerror_r(errno, error_str, error_len); + + return slave_name; +} + +//---------------------------------------------------------------------- +// Fork a child process and have its stdio routed to a pseudo terminal. +// +// In the parent process when a valid pid is returned, the master file +// descriptor can be used as a read/write access to stdio of the +// child process. +// +// In the child process the stdin/stdout/stderr will already be routed +// to the slave pseudo terminal and the master file descriptor will be +// closed as it is no longer needed by the child process. +// +// This class will close the file descriptors for the master/slave +// when the destructor is called, so be sure to call +// ReleaseMasterFileDescriptor() or ReleaseSlaveFileDescriptor() if any +// file descriptors are going to be used past the lifespan of this +// object. +// +// RETURNS: +// in the parent process: the pid of the child, or -1 if fork fails +// in the child process: zero +//---------------------------------------------------------------------- +lldb::pid_t PseudoTerminal::Fork(char *error_str, size_t error_len) { + if (error_str) + error_str[0] = '\0'; + pid_t pid = LLDB_INVALID_PROCESS_ID; +#if !defined(LLDB_DISABLE_POSIX) + int flags = O_RDWR; + flags |= O_CLOEXEC; + if (OpenFirstAvailableMaster(flags, error_str, error_len)) { + // Successfully opened our master pseudo terminal + + pid = ::fork(); + if (pid < 0) { + // Fork failed + if (error_str) + ::strerror_r(errno, error_str, error_len); + } else if (pid == 0) { + // Child Process + ::setsid(); + + if (OpenSlave(O_RDWR, error_str, error_len)) { + // Successfully opened slave + + // Master FD should have O_CLOEXEC set, but let's close it just in + // case... + CloseMasterFileDescriptor(); + +#if defined(TIOCSCTTY) + // Acquire the controlling terminal + if (::ioctl(m_slave_fd, TIOCSCTTY, (char *)0) < 0) { + if (error_str) + ::strerror_r(errno, error_str, error_len); + } +#endif + // Duplicate all stdio file descriptors to the slave pseudo terminal + if (::dup2(m_slave_fd, STDIN_FILENO) != STDIN_FILENO) { + if (error_str && !error_str[0]) + ::strerror_r(errno, error_str, error_len); + } + + if (::dup2(m_slave_fd, STDOUT_FILENO) != STDOUT_FILENO) { + if (error_str && !error_str[0]) + ::strerror_r(errno, error_str, error_len); + } + + if (::dup2(m_slave_fd, STDERR_FILENO) != STDERR_FILENO) { + if (error_str && !error_str[0]) + ::strerror_r(errno, error_str, error_len); + } + } + } else { + // Parent Process + // Do nothing and let the pid get returned! + } + } +#endif + return pid; +} + +//---------------------------------------------------------------------- +// The master file descriptor accessor. This object retains ownership +// of the master file descriptor when this accessor is used. Use +// ReleaseMasterFileDescriptor() if you wish this object to release +// ownership of the master file descriptor. +// +// Returns the master file descriptor, or -1 if the master file +// descriptor is not currently valid. +//---------------------------------------------------------------------- +int PseudoTerminal::GetMasterFileDescriptor() const { return m_master_fd; } + +//---------------------------------------------------------------------- +// The slave file descriptor accessor. +// +// Returns the slave file descriptor, or -1 if the slave file +// descriptor is not currently valid. +//---------------------------------------------------------------------- +int PseudoTerminal::GetSlaveFileDescriptor() const { return m_slave_fd; } + +//---------------------------------------------------------------------- +// Release ownership of the master pseudo terminal file descriptor +// without closing it. The destructor for this class will close the +// master file descriptor if the ownership isn't released using this +// call and the master file descriptor has been opened. +//---------------------------------------------------------------------- +int PseudoTerminal::ReleaseMasterFileDescriptor() { + // Release ownership of the master pseudo terminal file + // descriptor without closing it. (the destructor for this + // class will close it otherwise!) + int fd = m_master_fd; + m_master_fd = invalid_fd; + return fd; +} + +//---------------------------------------------------------------------- +// Release ownership of the slave pseudo terminal file descriptor +// without closing it. The destructor for this class will close the +// slave file descriptor if the ownership isn't released using this +// call and the slave file descriptor has been opened. +//---------------------------------------------------------------------- +int PseudoTerminal::ReleaseSlaveFileDescriptor() { + // Release ownership of the slave pseudo terminal file + // descriptor without closing it (the destructor for this + // class will close it otherwise!) + int fd = m_slave_fd; + m_slave_fd = invalid_fd; + return fd; +} |