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
30using namespace lldb_private;
31
32// PseudoTerminal constructor
33PseudoTerminal::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.
41PseudoTerminal::~PseudoTerminal() {
42 ClosePrimaryFileDescriptor();
43 CloseSecondaryFileDescriptor();
44}
45
46// Close the primary file descriptor if it is valid.
47void 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.
55void PseudoTerminal::CloseSecondaryFileDescriptor() {
56 if (m_secondary_fd >= 0) {
57 ::close(fd: m_secondary_fd);
58 m_secondary_fd = invalid_fd;
59 }
60}
61
62llvm::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
91llvm::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__)
104static 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
113std::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
135llvm::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.
185int 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.
191int 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.
199int 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.
211int 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

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