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-exception |
6 | // |
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 | #endif |
23 | |
24 | #include "lldb/Host/PosixApi.h" |
25 | |
26 | #if defined(__APPLE__) |
27 | #include <Availability.h> |
28 | #endif |
29 | |
30 | using namespace lldb_private; |
31 | |
32 | // PseudoTerminal constructor |
33 | PseudoTerminal::PseudoTerminal() = default; |
34 | |
35 | // Destructor |
36 | // |
37 | // The destructor will close the primary and secondary file descriptors if they |
38 | // are valid and ownership has not been released using the |
39 | // ReleasePrimaryFileDescriptor() or the ReleaseSaveFileDescriptor() member |
40 | // functions. |
41 | PseudoTerminal::~PseudoTerminal() { |
42 | ClosePrimaryFileDescriptor(); |
43 | CloseSecondaryFileDescriptor(); |
44 | } |
45 | |
46 | // Close the primary file descriptor if it is valid. |
47 | void PseudoTerminal::ClosePrimaryFileDescriptor() { |
48 | if (m_primary_fd >= 0) { |
49 | ::close(fd: m_primary_fd); |
50 | m_primary_fd = invalid_fd; |
51 | } |
52 | } |
53 | |
54 | // Close the secondary file descriptor if it is valid. |
55 | void PseudoTerminal::CloseSecondaryFileDescriptor() { |
56 | if (m_secondary_fd >= 0) { |
57 | ::close(fd: m_secondary_fd); |
58 | m_secondary_fd = invalid_fd; |
59 | } |
60 | } |
61 | |
62 | llvm::Error PseudoTerminal::OpenFirstAvailablePrimary(int oflag) { |
63 | #if LLDB_ENABLE_POSIX |
64 | // Open the primary side of a pseudo terminal |
65 | m_primary_fd = ::posix_openpt(oflag: oflag); |
66 | if (m_primary_fd < 0) { |
67 | return llvm::errorCodeToError( |
68 | EC: std::error_code(errno, std::generic_category())); |
69 | } |
70 | |
71 | // Grant access to the secondary pseudo terminal |
72 | if (::grantpt(fd: 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 terminal |
79 | if (::unlockpt(fd: 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 | #else |
87 | return llvm::errorCodeToError(llvm::errc::not_supported); |
88 | #endif |
89 | } |
90 | |
91 | llvm::Error PseudoTerminal::OpenSecondary(int oflag) { |
92 | CloseSecondaryFileDescriptor(); |
93 | |
94 | std::string name = GetSecondaryName(); |
95 | m_secondary_fd = FileSystem::Instance().Open(path: name.c_str(), flags: oflag); |
96 | if (m_secondary_fd >= 0) |
97 | return llvm::Error::success(); |
98 | |
99 | return llvm::errorCodeToError( |
100 | EC: std::error_code(errno, std::generic_category())); |
101 | } |
102 | |
103 | #if !HAVE_PTSNAME_R || defined(__APPLE__) |
104 | static 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 | #endif |
112 | |
113 | std::string PseudoTerminal::GetSecondaryName() const { |
114 | assert(m_primary_fd >= 0); |
115 | #if HAVE_PTSNAME_R |
116 | #if defined(__APPLE__) |
117 | if (__builtin_available(macos 10.13.4, iOS 11.3, tvOS 11.3, watchOS 4.4, *)) { |
118 | #endif |
119 | char buf[PATH_MAX]; |
120 | buf[0] = '\0'; |
121 | int r = ptsname_r(fd: m_primary_fd, buf: buf, buflen: 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 | #endif |
130 | #else |
131 | return use_ptsname(m_primary_fd); |
132 | #endif |
133 | } |
134 | |
135 | llvm::Expected<lldb::pid_t> PseudoTerminal::Fork() { |
136 | #if LLDB_ENABLE_POSIX |
137 | 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 | EC: std::error_code(errno, std::generic_category())); |
144 | } |
145 | if (pid > 0) { |
146 | // Parent process. |
147 | return pid; |
148 | } |
149 | |
150 | // Child Process |
151 | ::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 in |
157 | // case... |
158 | ClosePrimaryFileDescriptor(); |
159 | |
160 | #if defined(TIOCSCTTY) |
161 | // Acquire the controlling terminal |
162 | if (::ioctl(m_secondary_fd, TIOCSCTTY, (char *)0) < 0) { |
163 | return llvm::errorCodeToError( |
164 | std::error_code(errno, std::generic_category())); |
165 | } |
166 | #endif |
167 | // Duplicate all stdio file descriptors to the secondary pseudo terminal |
168 | for (int fd : {STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO}) { |
169 | if (::dup2(fd: m_secondary_fd, fd2: fd) != fd) { |
170 | return llvm::errorCodeToError( |
171 | EC: std::error_code(errno, std::generic_category())); |
172 | } |
173 | } |
174 | #endif |
175 | return 0; |
176 | } |
177 | |
178 | // The primary file descriptor accessor. This object retains ownership of the |
179 | // primary file descriptor when this accessor is used. Use |
180 | // ReleasePrimaryFileDescriptor() if you wish this object to release ownership |
181 | // of the primary file descriptor. |
182 | // |
183 | // Returns the primary file descriptor, or -1 if the primary file descriptor is |
184 | // not currently valid. |
185 | int 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 descriptor |
190 | // is not currently valid. |
191 | int PseudoTerminal::GetSecondaryFileDescriptor() const { |
192 | return m_secondary_fd; |
193 | } |
194 | |
195 | // Release ownership of the primary pseudo terminal file descriptor without |
196 | // closing it. The destructor for this class will close the primary file |
197 | // descriptor if the ownership isn't released using this call and the primary |
198 | // file descriptor has been opened. |
199 | int PseudoTerminal::ReleasePrimaryFileDescriptor() { |
200 | // Release ownership of the primary pseudo terminal file descriptor without |
201 | // 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 without |
208 | // closing it. The destructor for this class will close the secondary file |
209 | // descriptor if the ownership isn't released using this call and the secondary |
210 | // file descriptor has been opened. |
211 | int PseudoTerminal::ReleaseSecondaryFileDescriptor() { |
212 | // Release ownership of the secondary pseudo terminal file descriptor without |
213 | // 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 | |