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(fd: 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(fd: 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: 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(fd: 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(fd: 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(file: secondary_name, oflag: 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(fd: 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(fd: 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(fd: m_secondary_fd, STDIN_FILENO) != STDIN_FILENO) |
167 | error = error ? error : err_dup2_failed_on_stdin; |
168 | if (::dup2(fd: m_secondary_fd, STDOUT_FILENO) != STDOUT_FILENO) |
169 | error = error ? error : err_dup2_failed_on_stdout; |
170 | if (::dup2(fd: 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 | |