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#if defined(__ANDROID__)
31int posix_openpt(int flags);
32#endif
33
34using namespace lldb_private;
35
36// PseudoTerminal constructor
37PseudoTerminal::PseudoTerminal() = default;
38
39// Destructor
40//
41// The destructor will close the primary and secondary file descriptors if they
42// are valid and ownership has not been released using the
43// ReleasePrimaryFileDescriptor() or the ReleaseSaveFileDescriptor() member
44// functions.
45PseudoTerminal::~PseudoTerminal() {
46 ClosePrimaryFileDescriptor();
47 CloseSecondaryFileDescriptor();
48}
49
50// Close the primary file descriptor if it is valid.
51void PseudoTerminal::ClosePrimaryFileDescriptor() {
52 if (m_primary_fd >= 0) {
53 ::close(fd: m_primary_fd);
54 m_primary_fd = invalid_fd;
55 }
56}
57
58// Close the secondary file descriptor if it is valid.
59void PseudoTerminal::CloseSecondaryFileDescriptor() {
60 if (m_secondary_fd >= 0) {
61 ::close(fd: m_secondary_fd);
62 m_secondary_fd = invalid_fd;
63 }
64}
65
66llvm::Error PseudoTerminal::OpenFirstAvailablePrimary(int oflag) {
67#if LLDB_ENABLE_POSIX
68 // Open the primary side of a pseudo terminal
69 m_primary_fd = ::posix_openpt(oflag: oflag);
70 if (m_primary_fd < 0) {
71 return llvm::errorCodeToError(
72 EC: std::error_code(errno, std::generic_category()));
73 }
74
75 // Grant access to the secondary pseudo terminal
76 if (::grantpt(fd: m_primary_fd) < 0) {
77 std::error_code EC(errno, std::generic_category());
78 ClosePrimaryFileDescriptor();
79 return llvm::errorCodeToError(EC);
80 }
81
82 // Clear the lock flag on the secondary pseudo terminal
83 if (::unlockpt(fd: m_primary_fd) < 0) {
84 std::error_code EC(errno, std::generic_category());
85 ClosePrimaryFileDescriptor();
86 return llvm::errorCodeToError(EC);
87 }
88
89 return llvm::Error::success();
90#else
91 return llvm::errorCodeToError(llvm::errc::not_supported);
92#endif
93}
94
95llvm::Error PseudoTerminal::OpenSecondary(int oflag) {
96 CloseSecondaryFileDescriptor();
97
98 std::string name = GetSecondaryName();
99 m_secondary_fd = FileSystem::Instance().Open(path: name.c_str(), flags: oflag);
100 if (m_secondary_fd >= 0)
101 return llvm::Error::success();
102
103 return llvm::errorCodeToError(
104 EC: std::error_code(errno, std::generic_category()));
105}
106
107#if !HAVE_PTSNAME_R || defined(__APPLE__)
108static std::string use_ptsname(int fd) {
109 static std::mutex mutex;
110 std::lock_guard<std::mutex> guard(mutex);
111 const char *r = ptsname(fd);
112 assert(r != nullptr);
113 return r;
114}
115#endif
116
117std::string PseudoTerminal::GetSecondaryName() const {
118 assert(m_primary_fd >= 0);
119#if HAVE_PTSNAME_R
120#if defined(__APPLE__)
121 if (__builtin_available(macos 10.13.4, iOS 11.3, tvOS 11.3, watchOS 4.4, *)) {
122#endif
123 char buf[PATH_MAX];
124 buf[0] = '\0';
125 int r = ptsname_r(fd: m_primary_fd, buf: buf, buflen: sizeof(buf));
126 UNUSED_IF_ASSERT_DISABLED(r);
127 assert(r == 0);
128 return buf;
129#if defined(__APPLE__)
130 } else {
131 return use_ptsname(m_primary_fd);
132 }
133#endif
134#else
135 return use_ptsname(m_primary_fd);
136#endif
137}
138
139llvm::Expected<lldb::pid_t> PseudoTerminal::Fork() {
140#if LLDB_ENABLE_POSIX
141 if (llvm::Error Err = OpenFirstAvailablePrimary(O_RDWR | O_CLOEXEC))
142 return std::move(Err);
143
144 pid_t pid = ::fork();
145 if (pid < 0) {
146 return llvm::errorCodeToError(
147 EC: std::error_code(errno, std::generic_category()));
148 }
149 if (pid > 0) {
150 // Parent process.
151 return pid;
152 }
153
154 // Child Process
155 ::setsid();
156
157 if (llvm::Error Err = OpenSecondary(O_RDWR))
158 return std::move(Err);
159
160 // Primary FD should have O_CLOEXEC set, but let's close it just in
161 // case...
162 ClosePrimaryFileDescriptor();
163
164#if defined(TIOCSCTTY)
165 // Acquire the controlling terminal
166 if (::ioctl(m_secondary_fd, TIOCSCTTY, (char *)0) < 0) {
167 return llvm::errorCodeToError(
168 std::error_code(errno, std::generic_category()));
169 }
170#endif
171 // Duplicate all stdio file descriptors to the secondary pseudo terminal
172 for (int fd : {STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO}) {
173 if (::dup2(fd: m_secondary_fd, fd2: fd) != fd) {
174 return llvm::errorCodeToError(
175 EC: std::error_code(errno, std::generic_category()));
176 }
177 }
178#endif
179 return 0;
180}
181
182// The primary file descriptor accessor. This object retains ownership of the
183// primary file descriptor when this accessor is used. Use
184// ReleasePrimaryFileDescriptor() if you wish this object to release ownership
185// of the primary file descriptor.
186//
187// Returns the primary file descriptor, or -1 if the primary file descriptor is
188// not currently valid.
189int PseudoTerminal::GetPrimaryFileDescriptor() const { return m_primary_fd; }
190
191// The secondary file descriptor accessor.
192//
193// Returns the secondary file descriptor, or -1 if the secondary file descriptor
194// is not currently valid.
195int PseudoTerminal::GetSecondaryFileDescriptor() const {
196 return m_secondary_fd;
197}
198
199// Release ownership of the primary pseudo terminal file descriptor without
200// closing it. The destructor for this class will close the primary file
201// descriptor if the ownership isn't released using this call and the primary
202// file descriptor has been opened.
203int PseudoTerminal::ReleasePrimaryFileDescriptor() {
204 // Release ownership of the primary pseudo terminal file descriptor without
205 // closing it. (the destructor for this class will close it otherwise!)
206 int fd = m_primary_fd;
207 m_primary_fd = invalid_fd;
208 return fd;
209}
210
211// Release ownership of the secondary pseudo terminal file descriptor without
212// closing it. The destructor for this class will close the secondary file
213// descriptor if the ownership isn't released using this call and the secondary
214// file descriptor has been opened.
215int PseudoTerminal::ReleaseSecondaryFileDescriptor() {
216 // Release ownership of the secondary pseudo terminal file descriptor without
217 // closing it (the destructor for this class will close it otherwise!)
218 int fd = m_secondary_fd;
219 m_secondary_fd = invalid_fd;
220 return fd;
221}
222

source code of lldb/source/Host/common/PseudoTerminal.cpp