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