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