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 // Handles forwarding the serial console to a pseudo-terminal (PTY)
42 // It receives a couple of fds for the console (could be the same fd twice if,
43 // for example a socket_pair were used).
44 // Data available in the console's output needs to be read immediately to avoid
45 // the having the VMM blocked on writes to the pipe. To achieve this one thread
46 // takes care of (and only of) all read calls (from console output and from the
47 // socket client), using select(2) to ensure it never blocks. Writes are handled
48 // in a different thread, the two threads communicate through a buffer queue
49 // protected by a mutex.
50 class ConsoleForwarder {
51  public:
ConsoleForwarder(std::string console_path,cuttlefish::SharedFD console_in,cuttlefish::SharedFD console_out,cuttlefish::SharedFD console_log)52   ConsoleForwarder(std::string console_path,
53                    cuttlefish::SharedFD console_in,
54                    cuttlefish::SharedFD console_out,
55                    cuttlefish::SharedFD console_log) :
56                                                 console_path_(console_path),
57                                                 console_in_(console_in),
58                                                 console_out_(console_out),
59                                                 console_log_(console_log) {}
StartServer()60   [[noreturn]] void StartServer() {
61     // Create a new thread to handle writes to the console
62     writer_thread_ = std::thread([this]() { WriteLoop(); });
63     // Use the calling thread (likely the process' main thread) to handle
64     // reading the console's output and input from the client.
65     ReadLoop();
66   }
67  private:
68 
OpenPTY()69   cuttlefish::SharedFD OpenPTY() {
70     // Remove any stale symlink to a pts device
71     auto ret = unlink(console_path_.c_str());
72     if (ret < 0 && errno != ENOENT) {
73       LOG(ERROR) << "Failed to unlink " << console_path_.c_str()
74                  << ": " << strerror(errno);
75       std::exit(-5);
76     }
77 
78     auto pty = posix_openpt(O_RDWR | O_NOCTTY);
79     if (pty < 0) {
80       LOG(ERROR) << "Failed to open a PTY: " << strerror(errno);
81       std::exit(-6);
82     }
83     grantpt(pty);
84     unlockpt(pty);
85 
86     // Disable all echo modes on the PTY
87     struct termios termios;
88     if (tcgetattr(pty, &termios) < 0) {
89       LOG(ERROR) << "Failed to get terminal control: " << strerror(errno);
90       std::exit(-7);
91     }
92     termios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
93     termios.c_oflag &= ~(ONLCR);
94     if (tcsetattr(pty, TCSANOW, &termios) < 0) {
95       LOG(ERROR) << "Failed to set terminal control: " << strerror(errno);
96       std::exit(-8);
97     }
98 
99     auto pty_dev_name = ptsname(pty);
100     if (pty_dev_name == nullptr) {
101       LOG(ERROR) << "Failed to obtain PTY device name: " << strerror(errno);
102       std::exit(-9);
103     }
104 
105     if (symlink(pty_dev_name, console_path_.c_str()) < 0) {
106       LOG(ERROR) << "Failed to create symlink to " << pty_dev_name << " at "
107                  << console_path_.c_str() << ": " << strerror(errno);
108       std::exit(-10);
109     }
110 
111     auto pty_shared_fd = cuttlefish::SharedFD::Dup(pty);
112     close(pty);
113     if (!pty_shared_fd->IsOpen()) {
114       LOG(ERROR) << "Error dupping fd " << pty << ": "
115                  << pty_shared_fd->StrError();
116       std::exit(-11);
117     }
118 
119     return pty_shared_fd;
120   }
121 
EnqueueWrite(std::shared_ptr<std::vector<char>> buf_ptr,cuttlefish::SharedFD fd)122   void EnqueueWrite(std::shared_ptr<std::vector<char>> buf_ptr, cuttlefish::SharedFD fd) {
123     std::lock_guard<std::mutex> lock(write_queue_mutex_);
124     write_queue_.emplace_back(fd, buf_ptr);
125     condvar_.notify_one();
126   }
127 
WriteLoop()128   [[noreturn]] void WriteLoop() {
129     while (true) {
130       while (!write_queue_.empty()) {
131         std::shared_ptr<std::vector<char>> buf_ptr;
132         cuttlefish::SharedFD fd;
133         {
134           std::lock_guard<std::mutex> lock(write_queue_mutex_);
135           auto& front = write_queue_.front();
136           buf_ptr = front.second;
137           fd = front.first;
138           write_queue_.pop_front();
139         }
140         // Write all bytes to the file descriptor. Writes may block, so the
141         // mutex lock should NOT be held while writing to avoid blocking the
142         // other thread.
143         ssize_t bytes_written = 0;
144         ssize_t bytes_to_write = buf_ptr->size();
145         while (bytes_to_write > 0) {
146           bytes_written =
147               fd->Write(buf_ptr->data() + bytes_written, bytes_to_write);
148           if (bytes_written < 0) {
149             LOG(ERROR) << "Error writing to fd: " << fd->StrError();
150             // Don't try to write from this buffer anymore, error handling will
151             // be done on the reading thread (failed client will be
152             // disconnected, on serial console failure this process will abort).
153             break;
154           }
155           bytes_to_write -= bytes_written;
156         }
157       }
158       {
159         std::unique_lock<std::mutex> lock(write_queue_mutex_);
160         // Check again before sleeping, state may have changed
161         if (write_queue_.empty()) {
162           condvar_.wait(lock);
163         }
164       }
165     }
166   }
167 
ReadLoop()168   [[noreturn]] void ReadLoop() {
169     cuttlefish::SharedFD client_fd;
170     while (true) {
171       if (!client_fd->IsOpen()) {
172         client_fd = OpenPTY();
173       }
174 
175       cuttlefish::SharedFDSet read_set;
176       read_set.Set(console_out_);
177       read_set.Set(client_fd);
178 
179       cuttlefish::Select(&read_set, nullptr, nullptr, nullptr);
180       if (read_set.IsSet(console_out_)) {
181         std::shared_ptr<std::vector<char>> buf_ptr = std::make_shared<std::vector<char>>(4096);
182         auto bytes_read = console_out_->Read(buf_ptr->data(), buf_ptr->size());
183         if (bytes_read <= 0) {
184           LOG(ERROR) << "Error reading from console output: "
185                      << console_out_->StrError();
186           // This is likely unrecoverable, so exit here
187           std::exit(-12);
188         }
189         buf_ptr->resize(bytes_read);
190         EnqueueWrite(buf_ptr, console_log_);
191         if (client_fd->IsOpen()) {
192           EnqueueWrite(buf_ptr, client_fd);
193         }
194       }
195       if (read_set.IsSet(client_fd)) {
196         std::shared_ptr<std::vector<char>> buf_ptr = std::make_shared<std::vector<char>>(4096);
197         auto bytes_read = client_fd->Read(buf_ptr->data(), buf_ptr->size());
198         if (bytes_read <= 0) {
199           // If this happens, it's usually because the PTY controller went away
200           // e.g. the user closed minicom, or killed screen, or closed kgdb. In
201           // such a case, we will just re-create the PTY
202           LOG(ERROR) << "Error reading from client fd: "
203                      << client_fd->StrError();
204           client_fd->Close();
205         } else {
206           buf_ptr->resize(bytes_read);
207           EnqueueWrite(buf_ptr, console_in_);
208         }
209       }
210     }
211   }
212 
213   std::string console_path_;
214   cuttlefish::SharedFD console_in_;
215   cuttlefish::SharedFD console_out_;
216   cuttlefish::SharedFD console_log_;
217   std::thread writer_thread_;
218   std::mutex write_queue_mutex_;
219   std::condition_variable condvar_;
220   std::deque<std::pair<cuttlefish::SharedFD, std::shared_ptr<std::vector<char>>>> write_queue_;
221 };
222 
main(int argc,char ** argv)223 int main(int argc, char** argv) {
224   cuttlefish::DefaultSubprocessLogging(argv);
225   ::gflags::ParseCommandLineFlags(&argc, &argv, true);
226 
227   if (FLAGS_console_in_fd < 0 || FLAGS_console_out_fd < 0) {
228     LOG(ERROR) << "Invalid file descriptors: " << FLAGS_console_in_fd << ", "
229                << FLAGS_console_out_fd;
230     return -1;
231   }
232 
233   auto console_in = cuttlefish::SharedFD::Dup(FLAGS_console_in_fd);
234   close(FLAGS_console_in_fd);
235   if (!console_in->IsOpen()) {
236     LOG(ERROR) << "Error dupping fd " << FLAGS_console_in_fd << ": "
237                << console_in->StrError();
238     return -2;
239   }
240   close(FLAGS_console_in_fd);
241 
242   auto console_out = cuttlefish::SharedFD::Dup(FLAGS_console_out_fd);
243   close(FLAGS_console_out_fd);
244   if (!console_out->IsOpen()) {
245     LOG(ERROR) << "Error dupping fd " << FLAGS_console_out_fd << ": "
246                << console_out->StrError();
247     return -3;
248   }
249 
250   auto config = cuttlefish::CuttlefishConfig::Get();
251   if (!config) {
252     LOG(ERROR) << "Unable to get config object";
253     return -4;
254   }
255 
256   auto instance = config->ForDefaultInstance();
257   auto console_path = instance.console_path();
258   auto console_log = instance.PerInstancePath("console_log");
259   auto console_log_fd = cuttlefish::SharedFD::Open(console_log.c_str(), O_CREAT | O_APPEND | O_WRONLY, 0666);
260   ConsoleForwarder console_forwarder(console_path, console_in, console_out, console_log_fd);
261 
262   // Don't get a SIGPIPE from the clients
263   if (sigaction(SIGPIPE, nullptr, nullptr) != 0) {
264     LOG(FATAL) << "Failed to set SIGPIPE to be ignored: " << strerror(errno);
265     return -13;
266   }
267 
268   console_forwarder.StartServer();
269 }
270