forked from OSchip/llvm-project
292 lines
9.5 KiB
C++
292 lines
9.5 KiB
C++
//===-- PseudoTerminal.cpp ------------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "lldb/Host/PseudoTerminal.h"
|
|
#include "lldb/Host/Config.h"
|
|
|
|
#include "llvm/Support/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_private;
|
|
|
|
// Write string describing error number
|
|
static void ErrnoToStr(char *error_str, size_t error_len) {
|
|
std::string strerror = llvm::sys::StrError();
|
|
::snprintf(error_str, error_len, "%s", strerror.c_str());
|
|
}
|
|
|
|
// PseudoTerminal constructor
|
|
PseudoTerminal::PseudoTerminal()
|
|
: m_primary_fd(invalid_fd), m_secondary_fd(invalid_fd) {}
|
|
|
|
// Destructor
|
|
//
|
|
// The destructor will close the primary and secondary file descriptors if they
|
|
// are valid and ownership has not been released using the
|
|
// ReleasePrimaryFileDescriptor() or the ReleaseSaveFileDescriptor() member
|
|
// functions.
|
|
PseudoTerminal::~PseudoTerminal() {
|
|
ClosePrimaryFileDescriptor();
|
|
CloseSecondaryFileDescriptor();
|
|
}
|
|
|
|
// Close the primary file descriptor if it is valid.
|
|
void PseudoTerminal::ClosePrimaryFileDescriptor() {
|
|
if (m_primary_fd >= 0) {
|
|
::close(m_primary_fd);
|
|
m_primary_fd = invalid_fd;
|
|
}
|
|
}
|
|
|
|
// Close the secondary file descriptor if it is valid.
|
|
void PseudoTerminal::CloseSecondaryFileDescriptor() {
|
|
if (m_secondary_fd >= 0) {
|
|
::close(m_secondary_fd);
|
|
m_secondary_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
|
|
// PrimaryFileDescriptor() accessor. The ownership of the primary file
|
|
// descriptor can be released using the ReleasePrimaryFileDescriptor() accessor.
|
|
// If this object has a valid primary files descriptor when its destructor is
|
|
// called, it will close the primary file descriptor, therefore clients must
|
|
// call ReleasePrimaryFileDescriptor() if they wish to use the primary file
|
|
// descriptor after this object is out of scope or destroyed.
|
|
//
|
|
// RETURNS:
|
|
// True when successful, false indicating an error occurred.
|
|
bool PseudoTerminal::OpenFirstAvailablePrimary(int oflag, char *error_str,
|
|
size_t error_len) {
|
|
if (error_str)
|
|
error_str[0] = '\0';
|
|
|
|
#if LLDB_ENABLE_POSIX
|
|
// Open the primary side of a pseudo terminal
|
|
m_primary_fd = ::posix_openpt(oflag);
|
|
if (m_primary_fd < 0) {
|
|
if (error_str)
|
|
ErrnoToStr(error_str, error_len);
|
|
return false;
|
|
}
|
|
|
|
// Grant access to the secondary pseudo terminal
|
|
if (::grantpt(m_primary_fd) < 0) {
|
|
if (error_str)
|
|
ErrnoToStr(error_str, error_len);
|
|
ClosePrimaryFileDescriptor();
|
|
return false;
|
|
}
|
|
|
|
// Clear the lock flag on the secondary pseudo terminal
|
|
if (::unlockpt(m_primary_fd) < 0) {
|
|
if (error_str)
|
|
ErrnoToStr(error_str, error_len);
|
|
ClosePrimaryFileDescriptor();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
#else
|
|
if (error_str)
|
|
::snprintf(error_str, error_len, "%s", "pseudo terminal not supported");
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
// Open the secondary pseudo terminal for the current primary pseudo terminal. A
|
|
// primary pseudo terminal should already be valid prior to calling this
|
|
// function (see OpenFirstAvailablePrimary()). The file descriptor is stored
|
|
// this object's member variables and can be accessed via the
|
|
// GetSecondaryFileDescriptor(), or released using the
|
|
// ReleaseSecondaryFileDescriptor() member function.
|
|
//
|
|
// RETURNS:
|
|
// True when successful, false indicating an error occurred.
|
|
bool PseudoTerminal::OpenSecondary(int oflag, char *error_str,
|
|
size_t error_len) {
|
|
if (error_str)
|
|
error_str[0] = '\0';
|
|
|
|
CloseSecondaryFileDescriptor();
|
|
|
|
// Open the primary side of a pseudo terminal
|
|
const char *secondary_name = GetSecondaryName(error_str, error_len);
|
|
|
|
if (secondary_name == nullptr)
|
|
return false;
|
|
|
|
m_secondary_fd =
|
|
llvm::sys::RetryAfterSignal(-1, ::open, secondary_name, oflag);
|
|
|
|
if (m_secondary_fd < 0) {
|
|
if (error_str)
|
|
ErrnoToStr(error_str, error_len);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Get the name of the secondary pseudo terminal. A primary pseudo terminal
|
|
// should already be valid prior to calling this function (see
|
|
// OpenFirstAvailablePrimary()).
|
|
//
|
|
// RETURNS:
|
|
// NULL if no valid primary pseudo terminal or if ptsname() fails.
|
|
// The name of the secondary 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::GetSecondaryName(char *error_str,
|
|
size_t error_len) const {
|
|
if (error_str)
|
|
error_str[0] = '\0';
|
|
|
|
if (m_primary_fd < 0) {
|
|
if (error_str)
|
|
::snprintf(error_str, error_len, "%s",
|
|
"primary file descriptor is invalid");
|
|
return nullptr;
|
|
}
|
|
const char *secondary_name = ::ptsname(m_primary_fd);
|
|
|
|
if (error_str && secondary_name == nullptr)
|
|
ErrnoToStr(error_str, error_len);
|
|
|
|
return secondary_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 primary 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
|
|
// secondary pseudo terminal and the primary file descriptor will be closed as
|
|
// it is no longer needed by the child process.
|
|
//
|
|
// This class will close the file descriptors for the primary/secondary when the
|
|
// destructor is called, so be sure to call ReleasePrimaryFileDescriptor() or
|
|
// ReleaseSecondaryFileDescriptor() 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 LLDB_ENABLE_POSIX
|
|
int flags = O_RDWR;
|
|
flags |= O_CLOEXEC;
|
|
if (OpenFirstAvailablePrimary(flags, error_str, error_len)) {
|
|
// Successfully opened our primary pseudo terminal
|
|
|
|
pid = ::fork();
|
|
if (pid < 0) {
|
|
// Fork failed
|
|
if (error_str)
|
|
ErrnoToStr(error_str, error_len);
|
|
} else if (pid == 0) {
|
|
// Child Process
|
|
::setsid();
|
|
|
|
if (OpenSecondary(O_RDWR, error_str, error_len)) {
|
|
// Successfully opened secondary
|
|
|
|
// Primary FD should have O_CLOEXEC set, but let's close it just in
|
|
// case...
|
|
ClosePrimaryFileDescriptor();
|
|
|
|
#if defined(TIOCSCTTY)
|
|
// Acquire the controlling terminal
|
|
if (::ioctl(m_secondary_fd, TIOCSCTTY, (char *)0) < 0) {
|
|
if (error_str)
|
|
ErrnoToStr(error_str, error_len);
|
|
}
|
|
#endif
|
|
// Duplicate all stdio file descriptors to the secondary pseudo terminal
|
|
if (::dup2(m_secondary_fd, STDIN_FILENO) != STDIN_FILENO) {
|
|
if (error_str && !error_str[0])
|
|
ErrnoToStr(error_str, error_len);
|
|
}
|
|
|
|
if (::dup2(m_secondary_fd, STDOUT_FILENO) != STDOUT_FILENO) {
|
|
if (error_str && !error_str[0])
|
|
ErrnoToStr(error_str, error_len);
|
|
}
|
|
|
|
if (::dup2(m_secondary_fd, STDERR_FILENO) != STDERR_FILENO) {
|
|
if (error_str && !error_str[0])
|
|
ErrnoToStr(error_str, error_len);
|
|
}
|
|
}
|
|
} else {
|
|
// Parent Process
|
|
// Do nothing and let the pid get returned!
|
|
}
|
|
}
|
|
#endif
|
|
return pid;
|
|
}
|
|
|
|
// The primary file descriptor accessor. This object retains ownership of the
|
|
// primary file descriptor when this accessor is used. Use
|
|
// ReleasePrimaryFileDescriptor() if you wish this object to release ownership
|
|
// of the primary file descriptor.
|
|
//
|
|
// Returns the primary file descriptor, or -1 if the primary file descriptor is
|
|
// not currently valid.
|
|
int PseudoTerminal::GetPrimaryFileDescriptor() const { return m_primary_fd; }
|
|
|
|
// The secondary file descriptor accessor.
|
|
//
|
|
// Returns the secondary file descriptor, or -1 if the secondary file descriptor
|
|
// is not currently valid.
|
|
int PseudoTerminal::GetSecondaryFileDescriptor() const {
|
|
return m_secondary_fd;
|
|
}
|
|
|
|
// Release ownership of the primary pseudo terminal file descriptor without
|
|
// closing it. The destructor for this class will close the primary file
|
|
// descriptor if the ownership isn't released using this call and the primary
|
|
// file descriptor has been opened.
|
|
int PseudoTerminal::ReleasePrimaryFileDescriptor() {
|
|
// Release ownership of the primary pseudo terminal file descriptor without
|
|
// closing it. (the destructor for this class will close it otherwise!)
|
|
int fd = m_primary_fd;
|
|
m_primary_fd = invalid_fd;
|
|
return fd;
|
|
}
|
|
|
|
// Release ownership of the secondary pseudo terminal file descriptor without
|
|
// closing it. The destructor for this class will close the secondary file
|
|
// descriptor if the ownership isn't released using this call and the secondary
|
|
// file descriptor has been opened.
|
|
int PseudoTerminal::ReleaseSecondaryFileDescriptor() {
|
|
// Release ownership of the secondary pseudo terminal file descriptor without
|
|
// closing it (the destructor for this class will close it otherwise!)
|
|
int fd = m_secondary_fd;
|
|
m_secondary_fd = invalid_fd;
|
|
return fd;
|
|
}
|