• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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     grantpt(pty);
80     unlockpt(pty);
81 
82     // Disable all echo modes on the PTY
83     struct termios termios;
84     CHECK(tcgetattr(pty, &termios) >= 0)
85         << "Failed to get terminal control: " << strerror(errno);
86 
87     termios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
88     termios.c_oflag &= ~(ONLCR);
89     CHECK(tcsetattr(pty, TCSANOW, &termios) >= 0)
90         << "Failed to set terminal control: " << strerror(errno);
91 
92     auto pty_dev_name = ptsname(pty);
93     CHECK(pty_dev_name != nullptr)
94         << "Failed to obtain PTY device name: " << strerror(errno);
95 
96     CHECK(symlink(pty_dev_name, console_path_.c_str()) >= 0)
97         << "Failed to create symlink to " << pty_dev_name << " at "
98         << console_path_ << ": " << strerror(errno);
99 
100     auto pty_shared_fd = SharedFD::Dup(pty);
101     close(pty);
102     CHECK(pty_shared_fd->IsOpen())
103         << "Error dupping fd " << pty << ": " << pty_shared_fd->StrError();
104 
105     return pty_shared_fd;
106   }
107 
EnqueueWrite(std::shared_ptr<std::vector<char>> buf_ptr,SharedFD fd)108   void EnqueueWrite(std::shared_ptr<std::vector<char>> buf_ptr, SharedFD fd) {
109     std::lock_guard<std::mutex> lock(write_queue_mutex_);
110     write_queue_.emplace_back(fd, buf_ptr);
111     condvar_.notify_one();
112   }
113 
WriteLoop()114   [[noreturn]] void WriteLoop() {
115     while (true) {
116       while (!write_queue_.empty()) {
117         std::shared_ptr<std::vector<char>> buf_ptr;
118         SharedFD fd;
119         {
120           std::lock_guard<std::mutex> lock(write_queue_mutex_);
121           auto& front = write_queue_.front();
122           buf_ptr = front.second;
123           fd = front.first;
124           write_queue_.pop_front();
125         }
126         // Write all bytes to the file descriptor. Writes may block, so the
127         // mutex lock should NOT be held while writing to avoid blocking the
128         // other thread.
129         ssize_t bytes_written = 0;
130         ssize_t bytes_to_write = buf_ptr->size();
131         while (bytes_to_write > 0) {
132           bytes_written =
133               fd->Write(buf_ptr->data() + bytes_written, bytes_to_write);
134           if (bytes_written < 0) {
135             // It is expected for writes to the PTY to fail if nothing is connected
136             if(fd->GetErrno() != EAGAIN) {
137               LOG(ERROR) << "Error writing to fd: " << fd->StrError();
138             }
139 
140             // Don't try to write from this buffer anymore, error handling will
141             // be done on the reading thread (failed client will be
142             // disconnected, on serial console failure this process will abort).
143             break;
144           }
145           bytes_to_write -= bytes_written;
146         }
147       }
148       {
149         std::unique_lock<std::mutex> lock(write_queue_mutex_);
150         // Check again before sleeping, state may have changed
151         if (write_queue_.empty()) {
152           condvar_.wait(lock);
153         }
154       }
155     }
156   }
157 
ReadLoop()158   [[noreturn]] void ReadLoop() {
159     SharedFD client_fd;
160     while (true) {
161       if (!client_fd->IsOpen()) {
162         client_fd = OpenPTY();
163       }
164 
165       SharedFDSet read_set;
166       read_set.Set(console_out_);
167       read_set.Set(client_fd);
168 
169       Select(&read_set, nullptr, nullptr, nullptr);
170       if (read_set.IsSet(console_out_)) {
171         std::shared_ptr<std::vector<char>> buf_ptr = std::make_shared<std::vector<char>>(4096);
172         auto bytes_read = console_out_->Read(buf_ptr->data(), buf_ptr->size());
173         // This is likely unrecoverable, so exit here
174         CHECK(bytes_read > 0) << "Error reading from console output: "
175                               << console_out_->StrError();
176         buf_ptr->resize(bytes_read);
177         EnqueueWrite(buf_ptr, console_log_);
178         if (client_fd->IsOpen()) {
179           EnqueueWrite(buf_ptr, client_fd);
180         }
181         EnqueueWrite(buf_ptr, kernel_log_);
182       }
183       if (read_set.IsSet(client_fd)) {
184         std::shared_ptr<std::vector<char>> buf_ptr = std::make_shared<std::vector<char>>(4096);
185         auto bytes_read = client_fd->Read(buf_ptr->data(), buf_ptr->size());
186         if (bytes_read <= 0) {
187           // If this happens, it's usually because the PTY controller went away
188           // e.g. the user closed minicom, or killed screen, or closed kgdb. In
189           // such a case, we will just re-create the PTY
190           LOG(ERROR) << "Error reading from client fd: "
191                      << client_fd->StrError();
192           client_fd->Close();
193         } else {
194           buf_ptr->resize(bytes_read);
195           EnqueueWrite(buf_ptr, console_in_);
196         }
197       }
198     }
199   }
200 
201   std::string console_path_;
202   SharedFD console_in_;
203   SharedFD console_out_;
204   SharedFD console_log_;
205   SharedFD kernel_log_;
206   std::thread writer_thread_;
207   std::mutex write_queue_mutex_;
208   std::condition_variable condvar_;
209   std::deque<std::pair<SharedFD, std::shared_ptr<std::vector<char>>>>
210       write_queue_;
211 };
212 
ConsoleForwarderMain(int argc,char ** argv)213 int ConsoleForwarderMain(int argc, char** argv) {
214   DefaultSubprocessLogging(argv);
215   ::gflags::ParseCommandLineFlags(&argc, &argv, true);
216 
217   CHECK(!(FLAGS_console_in_fd < 0 || FLAGS_console_out_fd < 0))
218       << "Invalid file descriptors: " << FLAGS_console_in_fd << ", "
219       << FLAGS_console_out_fd;
220 
221   auto console_in = SharedFD::Dup(FLAGS_console_in_fd);
222   CHECK(console_in->IsOpen()) << "Error dupping fd " << FLAGS_console_in_fd
223                               << ": " << console_in->StrError();
224   close(FLAGS_console_in_fd);
225 
226   auto console_out = SharedFD::Dup(FLAGS_console_out_fd);
227   CHECK(console_out->IsOpen()) << "Error dupping fd " << FLAGS_console_out_fd
228                                << ": " << console_out->StrError();
229   close(FLAGS_console_out_fd);
230 
231   auto config = CuttlefishConfig::Get();
232   CHECK(config) << "Unable to get config object";
233 
234   auto instance = config->ForDefaultInstance();
235   auto console_path = instance.console_path();
236   auto console_log = instance.PerInstancePath("console_log");
237   auto console_log_fd =
238       SharedFD::Open(console_log.c_str(), O_CREAT | O_APPEND | O_WRONLY, 0666);
239   auto kernel_log_fd = SharedFD::Open(instance.kernel_log_pipe_name(),
240                                       O_APPEND | O_WRONLY, 0666);
241   ConsoleForwarder console_forwarder(console_path, console_in, console_out,
242                                      console_log_fd, kernel_log_fd);
243 
244   // Don't get a SIGPIPE from the clients
245   CHECK(sigaction(SIGPIPE, nullptr, nullptr) == 0)
246       << "Failed to set SIGPIPE to be ignored: " << strerror(errno);
247 
248   console_forwarder.StartServer();
249 }
250 
251 }  // namespace cuttlefish
252 
main(int argc,char ** argv)253 int main(int argc, char** argv) {
254   return cuttlefish::ConsoleForwarderMain(argc, argv);
255 }
256