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(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(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);
70  if (m_primary_fd < 0) {
71    return llvm::errorCodeToError(
72        std::error_code(errno, std::generic_category()));
73  }
74
75  // Grant access to the secondary pseudo terminal
76  if (::grantpt(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(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(name.c_str(), oflag);
100  if (m_secondary_fd >= 0)
101    return llvm::Error::success();
102
103  return llvm::errorCodeToError(
104      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(m_primary_fd, buf, 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        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(m_secondary_fd, fd) != fd) {
174      return llvm::errorCodeToError(
175          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