218 lines · cpp
1//===-- PseudoTerminal.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-exception6//7//===----------------------------------------------------------------------===//8 9#include "lldb/Host/PseudoTerminal.h"10#include "lldb/Host/Config.h"11#include "lldb/Host/FileSystem.h"12#include "llvm/Support/Errc.h"13#include "llvm/Support/Errno.h"14#include <cassert>15#include <climits>16#include <cstdio>17#include <cstdlib>18#include <cstring>19#include <mutex>20#if defined(TIOCSCTTY)21#include <sys/ioctl.h>22#endif23 24#include "lldb/Host/PosixApi.h"25 26#if defined(__APPLE__)27#include <Availability.h>28#endif29 30using namespace lldb_private;31 32// PseudoTerminal constructor33PseudoTerminal::PseudoTerminal() = default;34 35// Destructor36//37// The destructor will close the primary and secondary file descriptors if they38// are valid and ownership has not been released using the39// ReleasePrimaryFileDescriptor() or the ReleaseSaveFileDescriptor() member40// functions.41PseudoTerminal::~PseudoTerminal() {42 ClosePrimaryFileDescriptor();43 CloseSecondaryFileDescriptor();44}45 46// Close the primary file descriptor if it is valid.47void PseudoTerminal::ClosePrimaryFileDescriptor() {48 if (m_primary_fd >= 0) {49 ::close(m_primary_fd);50 m_primary_fd = invalid_fd;51 }52}53 54// Close the secondary file descriptor if it is valid.55void PseudoTerminal::CloseSecondaryFileDescriptor() {56 if (m_secondary_fd >= 0) {57 ::close(m_secondary_fd);58 m_secondary_fd = invalid_fd;59 }60}61 62llvm::Error PseudoTerminal::OpenFirstAvailablePrimary(int oflag) {63#if LLDB_ENABLE_POSIX64 // Open the primary side of a pseudo terminal65 m_primary_fd = ::posix_openpt(oflag);66 if (m_primary_fd < 0) {67 return llvm::errorCodeToError(68 std::error_code(errno, std::generic_category()));69 }70 71 // Grant access to the secondary pseudo terminal72 if (::grantpt(m_primary_fd) < 0) {73 std::error_code EC(errno, std::generic_category());74 ClosePrimaryFileDescriptor();75 return llvm::errorCodeToError(EC);76 }77 78 // Clear the lock flag on the secondary pseudo terminal79 if (::unlockpt(m_primary_fd) < 0) {80 std::error_code EC(errno, std::generic_category());81 ClosePrimaryFileDescriptor();82 return llvm::errorCodeToError(EC);83 }84 85 return llvm::Error::success();86#else87 return llvm::errorCodeToError(llvm::errc::not_supported);88#endif89}90 91llvm::Error PseudoTerminal::OpenSecondary(int oflag) {92 CloseSecondaryFileDescriptor();93 94 std::string name = GetSecondaryName();95 m_secondary_fd = FileSystem::Instance().Open(name.c_str(), oflag);96 if (m_secondary_fd >= 0)97 return llvm::Error::success();98 99 return llvm::errorCodeToError(100 std::error_code(errno, std::generic_category()));101}102 103#if !HAVE_PTSNAME_R || defined(__APPLE__)104static std::string use_ptsname(int fd) {105 static std::mutex mutex;106 std::lock_guard<std::mutex> guard(mutex);107 const char *r = ptsname(fd);108 assert(r != nullptr);109 return r;110}111#endif112 113std::string PseudoTerminal::GetSecondaryName() const {114 assert(m_primary_fd >= 0);115#if HAVE_PTSNAME_R116#if defined(__APPLE__)117 if (__builtin_available(macos 10.13.4, iOS 11.3, tvOS 11.3, watchOS 4.4, *)) {118#endif119 char buf[PATH_MAX];120 buf[0] = '\0';121 int r = ptsname_r(m_primary_fd, buf, sizeof(buf));122 UNUSED_IF_ASSERT_DISABLED(r);123 assert(r == 0);124 return buf;125#if defined(__APPLE__)126 } else {127 return use_ptsname(m_primary_fd);128 }129#endif130#else131 return use_ptsname(m_primary_fd);132#endif133}134 135llvm::Expected<lldb::pid_t> PseudoTerminal::Fork() {136#if LLDB_ENABLE_POSIX137 if (llvm::Error Err = OpenFirstAvailablePrimary(O_RDWR | O_CLOEXEC))138 return std::move(Err);139 140 pid_t pid = ::fork();141 if (pid < 0) {142 return llvm::errorCodeToError(143 std::error_code(errno, std::generic_category()));144 }145 if (pid > 0) {146 // Parent process.147 return pid;148 }149 150 // Child Process151 ::setsid();152 153 if (llvm::Error Err = OpenSecondary(O_RDWR))154 return std::move(Err);155 156 // Primary FD should have O_CLOEXEC set, but let's close it just in157 // case...158 ClosePrimaryFileDescriptor();159 160#if defined(TIOCSCTTY)161 // Acquire the controlling terminal162 if (::ioctl(m_secondary_fd, TIOCSCTTY, (char *)0) < 0) {163 return llvm::errorCodeToError(164 std::error_code(errno, std::generic_category()));165 }166#endif167 // Duplicate all stdio file descriptors to the secondary pseudo terminal168 for (int fd : {STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO}) {169 if (::dup2(m_secondary_fd, fd) != fd) {170 return llvm::errorCodeToError(171 std::error_code(errno, std::generic_category()));172 }173 }174#endif175 return 0;176}177 178// The primary file descriptor accessor. This object retains ownership of the179// primary file descriptor when this accessor is used. Use180// ReleasePrimaryFileDescriptor() if you wish this object to release ownership181// of the primary file descriptor.182//183// Returns the primary file descriptor, or -1 if the primary file descriptor is184// not currently valid.185int PseudoTerminal::GetPrimaryFileDescriptor() const { return m_primary_fd; }186 187// The secondary file descriptor accessor.188//189// Returns the secondary file descriptor, or -1 if the secondary file descriptor190// is not currently valid.191int PseudoTerminal::GetSecondaryFileDescriptor() const {192 return m_secondary_fd;193}194 195// Release ownership of the primary pseudo terminal file descriptor without196// closing it. The destructor for this class will close the primary file197// descriptor if the ownership isn't released using this call and the primary198// file descriptor has been opened.199int PseudoTerminal::ReleasePrimaryFileDescriptor() {200 // Release ownership of the primary pseudo terminal file descriptor without201 // closing it. (the destructor for this class will close it otherwise!)202 int fd = m_primary_fd;203 m_primary_fd = invalid_fd;204 return fd;205}206 207// Release ownership of the secondary pseudo terminal file descriptor without208// closing it. The destructor for this class will close the secondary file209// descriptor if the ownership isn't released using this call and the secondary210// file descriptor has been opened.211int PseudoTerminal::ReleaseSecondaryFileDescriptor() {212 // Release ownership of the secondary pseudo terminal file descriptor without213 // closing it (the destructor for this class will close it otherwise!)214 int fd = m_secondary_fd;215 m_secondary_fd = invalid_fd;216 return fd;217}218