1 //===-- PseudoTerminal.cpp --------------------------------------*- C++ -*-===// 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 // Created by Greg Clayton on 1/8/08. 10 // 11 //===----------------------------------------------------------------------===// 12 13 #include "PseudoTerminal.h" 14 #include <cstdlib> 15 #include <sys/ioctl.h> 16 #include <unistd.h> 17 18 // PseudoTerminal constructor 19 PseudoTerminal::PseudoTerminal() 20 : m_primary_fd(invalid_fd), m_secondary_fd(invalid_fd) {} 21 22 // Destructor 23 // The primary and secondary file descriptors will get closed if they are 24 // valid. Call the ReleasePrimaryFD()/ReleaseSecondaryFD() member functions 25 // to release any file descriptors that are needed beyond the lifespan 26 // of this object. 27 PseudoTerminal::~PseudoTerminal() { 28 ClosePrimary(); 29 CloseSecondary(); 30 } 31 32 // Close the primary file descriptor if it is valid. 33 void PseudoTerminal::ClosePrimary() { 34 if (m_primary_fd > 0) { 35 ::close(m_primary_fd); 36 m_primary_fd = invalid_fd; 37 } 38 } 39 40 // Close the secondary file descriptor if it is valid. 41 void PseudoTerminal::CloseSecondary() { 42 if (m_secondary_fd > 0) { 43 ::close(m_secondary_fd); 44 m_secondary_fd = invalid_fd; 45 } 46 } 47 48 // Open the first available pseudo terminal with OFLAG as the 49 // permissions. The file descriptor is store in the m_primary_fd member 50 // variable and can be accessed via the PrimaryFD() or ReleasePrimaryFD() 51 // accessors. 52 // 53 // Suggested value for oflag is O_RDWR|O_NOCTTY 54 // 55 // RETURNS: 56 // Zero when successful, non-zero indicating an error occurred. 57 PseudoTerminal::Status PseudoTerminal::OpenFirstAvailablePrimary(int oflag) { 58 // Open the primary side of a pseudo terminal 59 m_primary_fd = ::posix_openpt(oflag); 60 if (m_primary_fd < 0) { 61 return err_posix_openpt_failed; 62 } 63 64 // Grant access to the secondary pseudo terminal 65 if (::grantpt(m_primary_fd) < 0) { 66 ClosePrimary(); 67 return err_grantpt_failed; 68 } 69 70 // Clear the lock flag on the secondary pseudo terminal 71 if (::unlockpt(m_primary_fd) < 0) { 72 ClosePrimary(); 73 return err_unlockpt_failed; 74 } 75 76 return success; 77 } 78 79 // Open the secondary pseudo terminal for the current primary pseudo 80 // terminal. A primary pseudo terminal should already be valid prior to 81 // calling this function (see PseudoTerminal::OpenFirstAvailablePrimary()). 82 // The file descriptor is stored in the m_secondary_fd member variable and 83 // can be accessed via the SecondaryFD() or ReleaseSecondaryFD() accessors. 84 // 85 // RETURNS: 86 // Zero when successful, non-zero indicating an error occurred. 87 PseudoTerminal::Status PseudoTerminal::OpenSecondary(int oflag) { 88 CloseSecondary(); 89 90 // Open the primary side of a pseudo terminal 91 const char *secondary_name = SecondaryName(); 92 93 if (secondary_name == NULL) 94 return err_ptsname_failed; 95 96 m_secondary_fd = ::open(secondary_name, oflag); 97 98 if (m_secondary_fd < 0) 99 return err_open_secondary_failed; 100 101 return success; 102 } 103 104 // Get the name of the secondary pseudo terminal. A primary pseudo terminal 105 // should already be valid prior to calling this function (see 106 // PseudoTerminal::OpenFirstAvailablePrimary()). 107 // 108 // RETURNS: 109 // NULL if no valid primary pseudo terminal or if ptsname() fails. 110 // The name of the secondary pseudo terminal as a NULL terminated C string 111 // that comes from static memory, so a copy of the string should be 112 // made as subsequent calls can change this value. 113 const char *PseudoTerminal::SecondaryName() const { 114 if (m_primary_fd < 0) 115 return NULL; 116 return ::ptsname(m_primary_fd); 117 } 118 119 // Fork a child process that and have its stdio routed to a pseudo 120 // terminal. 121 // 122 // In the parent process when a valid pid is returned, the primary file 123 // descriptor can be used as a read/write access to stdio of the 124 // child process. 125 // 126 // In the child process the stdin/stdout/stderr will already be routed 127 // to the secondary pseudo terminal and the primary file descriptor will be 128 // closed as it is no longer needed by the child process. 129 // 130 // This class will close the file descriptors for the primary/secondary 131 // when the destructor is called, so be sure to call ReleasePrimaryFD() 132 // or ReleaseSecondaryFD() if any file descriptors are going to be used 133 // past the lifespan of this object. 134 // 135 // RETURNS: 136 // in the parent process: the pid of the child, or -1 if fork fails 137 // in the child process: zero 138 139 pid_t PseudoTerminal::Fork(PseudoTerminal::Status &error) { 140 pid_t pid = invalid_pid; 141 error = OpenFirstAvailablePrimary(O_RDWR | O_NOCTTY); 142 143 if (error == 0) { 144 // Successfully opened our primary pseudo terminal 145 146 pid = ::fork(); 147 if (pid < 0) { 148 // Fork failed 149 error = err_fork_failed; 150 } else if (pid == 0) { 151 // Child Process 152 ::setsid(); 153 154 error = OpenSecondary(O_RDWR); 155 if (error == 0) { 156 // Successfully opened secondary 157 // We are done with the primary in the child process so lets close it 158 ClosePrimary(); 159 160 #if defined(TIOCSCTTY) 161 // Acquire the controlling terminal 162 if (::ioctl(m_secondary_fd, TIOCSCTTY, (char *)0) < 0) 163 error = err_failed_to_acquire_controlling_terminal; 164 #endif 165 // Duplicate all stdio file descriptors to the secondary pseudo terminal 166 if (::dup2(m_secondary_fd, STDIN_FILENO) != STDIN_FILENO) 167 error = error ? error : err_dup2_failed_on_stdin; 168 if (::dup2(m_secondary_fd, STDOUT_FILENO) != STDOUT_FILENO) 169 error = error ? error : err_dup2_failed_on_stdout; 170 if (::dup2(m_secondary_fd, STDERR_FILENO) != STDERR_FILENO) 171 error = error ? error : err_dup2_failed_on_stderr; 172 } 173 } else { 174 // Parent Process 175 // Do nothing and let the pid get returned! 176 } 177 } 178 return pid; 179 } 180