blob: 1d12b22c9d0aa5dae387b924818e935eb5e8539a [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <signal.h>
#include <deque>
#include <thread>
#include <vector>
#include <gflags/gflags.h>
#include <glog/logging.h>
#include <common/libs/fs/shared_fd.h>
#include <common/libs/fs/shared_select.h>
#include <host/libs/config/cuttlefish_config.h>
DEFINE_int32(console_in_fd,
-1,
"File descriptor for the console's input channel");
DEFINE_int32(console_out_fd,
-1,
"File descriptor for the console's output channel");
// Handles forwarding the serial console to a socket.
// It receives the socket fd along with a couple of fds for the console (could
// be the same fd twice if, for example a socket_pair were used).
// Data available in the console's output needs to be read immediately to avoid
// the having the VMM blocked on writes to the pipe. To achieve this one thread
// takes care of (and only of) all read calls (from console output and from the
// socket client), using select(2) to ensure it never blocks. Writes are handled
// in a different thread, the two threads communicate through a buffer queue
// protected by a mutex.
class ConsoleForwarder {
public:
ConsoleForwarder(cvd::SharedFD socket,
cvd::SharedFD console_in,
cvd::SharedFD console_out) : socket_(socket),
console_in_(console_in),
console_out_(console_out) {}
[[noreturn]] void StartServer() {
// Create a new thread to handle writes to the console and to the any client
// connected to the socket.
writer_thread_ = std::thread([this]() { WriteLoop(); });
// Use the calling thread (likely the process' main thread) to handle
// reading the console's output and input from the client.
ReadLoop();
}
private:
void EnqueueWrite(std::vector<char> buffer, cvd::SharedFD fd) {
std::lock_guard<std::mutex> lock(write_queue_mutex_);
write_queue_.emplace_back(fd, std::move(buffer));
condvar_.notify_one();
}
[[noreturn]] void WriteLoop() {
while (true) {
while (!write_queue_.empty()) {
std::vector<char> buffer;
cvd::SharedFD fd;
{
std::lock_guard<std::mutex> lock(write_queue_mutex_);
auto& front = write_queue_.front();
buffer = std::move(front.second);
fd = front.first;
write_queue_.pop_front();
}
// Write all bytes to the file descriptor. Writes may block, so the
// mutex lock should NOT be held while writing to avoid blocking the
// other thread.
ssize_t bytes_written = 0;
ssize_t bytes_to_write = buffer.size();
while (bytes_to_write > 0) {
bytes_written =
fd->Write(buffer.data() + bytes_written, bytes_to_write);
if (bytes_written < 0) {
LOG(ERROR) << "Error writing to fd: " << fd->StrError();
// Don't try to write from this buffer anymore, error handling will
// be done on the reading thread (failed client will be
// disconnected, on serial console failure this process will abort).
break;
}
bytes_to_write -= bytes_written;
}
}
{
std::unique_lock<std::mutex> lock(write_queue_mutex_);
// Check again before sleeping, state may have changed
if (write_queue_.empty()) {
condvar_.wait(lock);
}
}
}
}
[[noreturn]] void ReadLoop() {
cvd::SharedFD client_fd;
while (true) {
cvd::SharedFDSet read_set;
if (client_fd->IsOpen()) {
read_set.Set(client_fd);
} else {
read_set.Set(socket_);
}
read_set.Set(console_out_);
cvd::Select(&read_set, nullptr, nullptr, nullptr);
if (read_set.IsSet(console_out_)) {
std::vector<char> buffer(4096);
auto bytes_read = console_out_->Read(buffer.data(), buffer.size());
if (bytes_read <= 0) {
LOG(ERROR) << "Error reading from console output: "
<< console_out_->StrError();
// This is likely unrecoverable, so exit here
std::exit(-4);
}
buffer.resize(bytes_read);
if (client_fd->IsOpen()) {
EnqueueWrite(std::move(buffer), client_fd);
}
}
if (read_set.IsSet(socket_)) {
// socket_ will only be included in the select call (and therefore only
// present in the read set) if there is no client connected, so this
// assignment is safe.
client_fd = cvd::SharedFD::Accept(*socket_);
if (!client_fd->IsOpen()) {
LOG(ERROR) << "Error accepting connection on socket: "
<< client_fd->StrError();
}
}
if (read_set.IsSet(client_fd)) {
std::vector<char> buffer(4096);
auto bytes_read = client_fd->Read(buffer.data(), buffer.size());
if (bytes_read <= 0) {
LOG(ERROR) << "Error reading from client fd: "
<< client_fd->StrError();
client_fd->Close(); // ignore errors here
} else {
buffer.resize(bytes_read);
EnqueueWrite(std::move(buffer), console_in_);
}
}
}
}
cvd::SharedFD socket_;
cvd::SharedFD console_in_;
cvd::SharedFD console_out_;
std::thread writer_thread_;
std::mutex write_queue_mutex_;
std::condition_variable condvar_;
std::deque<std::pair<cvd::SharedFD, std::vector<char>>> write_queue_;
};
int main(int argc, char** argv) {
::android::base::InitLogging(argv, android::base::StderrLogger);
::gflags::ParseCommandLineFlags(&argc, &argv, true);
if (FLAGS_console_in_fd < 0 || FLAGS_console_out_fd < 0) {
LOG(ERROR) << "Invalid file descriptors: " << FLAGS_console_in_fd << ", "
<< FLAGS_console_out_fd;
return -1;
}
auto console_in = cvd::SharedFD::Dup(FLAGS_console_in_fd);
close(FLAGS_console_in_fd);
if (!console_in->IsOpen()) {
LOG(ERROR) << "Error dupping fd " << FLAGS_console_in_fd << ": "
<< console_in->StrError();
return -2;
}
close(FLAGS_console_in_fd);
auto console_out = cvd::SharedFD::Dup(FLAGS_console_out_fd);
close(FLAGS_console_out_fd);
if (!console_out->IsOpen()) {
LOG(ERROR) << "Error dupping fd " << FLAGS_console_out_fd << ": "
<< console_out->StrError();
return -2;
}
auto config = vsoc::CuttlefishConfig::Get();
if (!config) {
LOG(ERROR) << "Unable to get config object";
return -3;
}
auto instance = config->ForDefaultInstance();
auto console_socket_name = instance.console_path();
auto socket = cvd::SharedFD::SocketLocalServer(console_socket_name.c_str(),
false,
SOCK_STREAM,
0600);
if (!socket->IsOpen()) {
LOG(ERROR) << "Failed to create console socket at " << console_socket_name
<< ": " << socket->StrError();
return -5;
}
ConsoleForwarder console_forwarder(socket, console_in, console_out);
// Don't get a SIGPIPE from the clients
if (sigaction(SIGPIPE, nullptr, nullptr) != 0) {
LOG(FATAL) << "Failed to set SIGPIPE to be ignored: " << strerror(errno);
return -6;
}
console_forwarder.StartServer();
}