1 /*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <termios.h>
18 #include <stdlib.h>
19 #include <signal.h>
20 #include <unistd.h>
21
22 #include <deque>
23 #include <thread>
24 #include <vector>
25
26 #include <gflags/gflags.h>
27 #include <android-base/logging.h>
28
29 #include <common/libs/fs/shared_fd.h>
30 #include <common/libs/fs/shared_select.h>
31 #include <host/libs/config/cuttlefish_config.h>
32 #include <host/libs/config/logging.h>
33
34 DEFINE_int32(console_in_fd,
35 -1,
36 "File descriptor for the console's input channel");
37 DEFINE_int32(console_out_fd,
38 -1,
39 "File descriptor for the console's output channel");
40
41 namespace cuttlefish {
42
43 // Handles forwarding the serial console to a pseudo-terminal (PTY)
44 // It receives a couple of fds for the console (could be the same fd twice if,
45 // for example a socket_pair were used).
46 // Data available in the console's output needs to be read immediately to avoid
47 // the having the VMM blocked on writes to the pipe. To achieve this one thread
48 // takes care of (and only of) all read calls (from console output and from the
49 // socket client), using select(2) to ensure it never blocks. Writes are handled
50 // in a different thread, the two threads communicate through a buffer queue
51 // protected by a mutex.
52 class ConsoleForwarder {
53 public:
ConsoleForwarder(std::string console_path,SharedFD console_in,SharedFD console_out,SharedFD console_log,SharedFD kernel_log)54 ConsoleForwarder(std::string console_path, SharedFD console_in,
55 SharedFD console_out, SharedFD console_log,
56 SharedFD kernel_log)
57 : console_path_(console_path),
58 console_in_(console_in),
59 console_out_(console_out),
60 console_log_(console_log),
61 kernel_log_(kernel_log) {}
StartServer()62 [[noreturn]] void StartServer() {
63 // Create a new thread to handle writes to the console
64 writer_thread_ = std::thread([this]() { WriteLoop(); });
65 // Use the calling thread (likely the process' main thread) to handle
66 // reading the console's output and input from the client.
67 ReadLoop();
68 }
69 private:
OpenPTY()70 SharedFD OpenPTY() {
71 // Remove any stale symlink to a pts device
72 auto ret = unlink(console_path_.c_str());
73 CHECK(!(ret < 0 && errno != ENOENT))
74 << "Failed to unlink " << console_path_ << ": " << strerror(errno);
75
76 auto pty = posix_openpt(O_RDWR | O_NOCTTY | O_NONBLOCK);
77 CHECK(pty >= 0) << "Failed to open a PTY: " << strerror(errno);
78
79 CHECK_EQ(grantpt(pty), 0) << strerror(errno);
80 CHECK_EQ(unlockpt(pty), 0) << strerror(errno);
81
82 int packet_mode_enabled = 1;
83 CHECK_EQ(ioctl(pty, TIOCPKT, &packet_mode_enabled), 0) << strerror(errno);
84
85 auto pty_dev_name = ptsname(pty);
86 CHECK(pty_dev_name != nullptr)
87 << "Failed to obtain PTY device name: " << strerror(errno);
88
89 CHECK(symlink(pty_dev_name, console_path_.c_str()) >= 0)
90 << "Failed to create symlink to " << pty_dev_name << " at "
91 << console_path_ << ": " << strerror(errno);
92
93 auto pty_shared_fd = SharedFD::Dup(pty);
94 close(pty);
95 CHECK(pty_shared_fd->IsOpen())
96 << "Error dupping fd " << pty << ": " << pty_shared_fd->StrError();
97
98 return pty_shared_fd;
99 }
100
EnqueueWrite(std::shared_ptr<std::vector<char>> buf_ptr,SharedFD fd)101 void EnqueueWrite(std::shared_ptr<std::vector<char>> buf_ptr, SharedFD fd) {
102 std::lock_guard<std::mutex> lock(write_queue_mutex_);
103 write_queue_.emplace_back(fd, buf_ptr);
104 condvar_.notify_one();
105 }
106
WriteLoop()107 [[noreturn]] void WriteLoop() {
108 while (true) {
109 while (!write_queue_.empty()) {
110 std::shared_ptr<std::vector<char>> buf_ptr;
111 SharedFD fd;
112 {
113 std::lock_guard<std::mutex> lock(write_queue_mutex_);
114 auto& front = write_queue_.front();
115 buf_ptr = front.second;
116 fd = front.first;
117 write_queue_.pop_front();
118 }
119 // Write all bytes to the file descriptor. Writes may block, so the
120 // mutex lock should NOT be held while writing to avoid blocking the
121 // other thread.
122 ssize_t bytes_written = 0;
123 ssize_t bytes_to_write = buf_ptr->size();
124 while (bytes_to_write > 0) {
125 bytes_written =
126 fd->Write(buf_ptr->data() + bytes_written, bytes_to_write);
127 if (bytes_written < 0) {
128 // It is expected for writes to the PTY to fail if nothing is connected
129 if(fd->GetErrno() != EAGAIN) {
130 LOG(ERROR) << "Error writing to fd: " << fd->StrError();
131 }
132
133 // Don't try to write from this buffer anymore, error handling will
134 // be done on the reading thread (failed client will be
135 // disconnected, on serial console failure this process will abort).
136 break;
137 }
138 bytes_to_write -= bytes_written;
139 }
140 }
141 {
142 std::unique_lock<std::mutex> lock(write_queue_mutex_);
143 // Check again before sleeping, state may have changed
144 if (write_queue_.empty()) {
145 condvar_.wait(lock);
146 }
147 }
148 }
149 }
150
ReadLoop()151 [[noreturn]] void ReadLoop() {
152 SharedFD client_fd;
153 while (true) {
154 if (!client_fd->IsOpen()) {
155 client_fd = OpenPTY();
156 }
157
158 SharedFDSet read_set;
159 read_set.Set(console_out_);
160 read_set.Set(client_fd);
161
162 SharedFDSet error_set;
163 error_set.Set(client_fd);
164
165 Select(&read_set, nullptr, &error_set, nullptr);
166 if (read_set.IsSet(console_out_)) {
167 std::shared_ptr<std::vector<char>> buf_ptr = std::make_shared<std::vector<char>>(4096);
168 auto bytes_read = console_out_->Read(buf_ptr->data(), buf_ptr->size());
169 // This is likely unrecoverable, so exit here
170 CHECK(bytes_read > 0) << "Error reading from console output: "
171 << console_out_->StrError();
172 buf_ptr->resize(bytes_read);
173 EnqueueWrite(buf_ptr, console_log_);
174 if (client_fd->IsOpen()) {
175 EnqueueWrite(buf_ptr, client_fd);
176 }
177 EnqueueWrite(buf_ptr, kernel_log_);
178 }
179 if (read_set.IsSet(client_fd) || error_set.IsSet(client_fd)) {
180 std::shared_ptr<std::vector<char>> buf_ptr = std::make_shared<std::vector<char>>(4096);
181 auto bytes_read = client_fd->Read(buf_ptr->data(), buf_ptr->size());
182 if (bytes_read <= 0) {
183 // If this happens, it's usually because the PTY controller went away
184 // e.g. the user closed minicom, or killed screen, or closed kgdb. In
185 // such a case, we will just re-create the PTY
186 LOG(ERROR) << "Error reading from client fd: "
187 << client_fd->StrError();
188 client_fd->Close();
189 } else if (bytes_read == 1) { // Control message
190 LOG(DEBUG) << "pty control message: " << (int)(*buf_ptr)[0];
191 } else {
192 buf_ptr->resize(bytes_read);
193 buf_ptr->erase(buf_ptr->begin());
194 EnqueueWrite(buf_ptr, console_in_);
195 }
196 }
197 }
198 }
199
200 std::string console_path_;
201 SharedFD console_in_;
202 SharedFD console_out_;
203 SharedFD console_log_;
204 SharedFD kernel_log_;
205 std::thread writer_thread_;
206 std::mutex write_queue_mutex_;
207 std::condition_variable condvar_;
208 std::deque<std::pair<SharedFD, std::shared_ptr<std::vector<char>>>>
209 write_queue_;
210 };
211
ConsoleForwarderMain(int argc,char ** argv)212 int ConsoleForwarderMain(int argc, char** argv) {
213 DefaultSubprocessLogging(argv);
214 ::gflags::ParseCommandLineFlags(&argc, &argv, true);
215
216 CHECK(!(FLAGS_console_in_fd < 0 || FLAGS_console_out_fd < 0))
217 << "Invalid file descriptors: " << FLAGS_console_in_fd << ", "
218 << FLAGS_console_out_fd;
219
220 auto console_in = SharedFD::Dup(FLAGS_console_in_fd);
221 CHECK(console_in->IsOpen()) << "Error dupping fd " << FLAGS_console_in_fd
222 << ": " << console_in->StrError();
223 close(FLAGS_console_in_fd);
224
225 auto console_out = SharedFD::Dup(FLAGS_console_out_fd);
226 CHECK(console_out->IsOpen()) << "Error dupping fd " << FLAGS_console_out_fd
227 << ": " << console_out->StrError();
228 close(FLAGS_console_out_fd);
229
230 auto config = CuttlefishConfig::Get();
231 CHECK(config) << "Unable to get config object";
232
233 auto instance = config->ForDefaultInstance();
234 auto console_path = instance.console_path();
235 auto console_log = instance.PerInstancePath("console_log");
236 auto console_log_fd =
237 SharedFD::Open(console_log.c_str(), O_CREAT | O_APPEND | O_WRONLY, 0666);
238 auto kernel_log_fd = SharedFD::Open(instance.kernel_log_pipe_name(),
239 O_APPEND | O_WRONLY, 0666);
240 ConsoleForwarder console_forwarder(console_path, console_in, console_out,
241 console_log_fd, kernel_log_fd);
242
243 // Don't get a SIGPIPE from the clients
244 CHECK(sigaction(SIGPIPE, nullptr, nullptr) == 0)
245 << "Failed to set SIGPIPE to be ignored: " << strerror(errno);
246
247 console_forwarder.StartServer();
248 }
249
250 } // namespace cuttlefish
251
main(int argc,char ** argv)252 int main(int argc, char** argv) {
253 return cuttlefish::ConsoleForwarderMain(argc, argv);
254 }
255