| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/virtualization/bin/guest/serial.h" |
| |
| #include <fcntl.h> |
| #include <fuchsia/virtualization/cpp/fidl.h> |
| #include <lib/async/cpp/wait.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/fdio/fd.h> |
| #include <lib/fdio/fdio.h> |
| #include <lib/fit/function.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <poll.h> |
| |
| #include "src/lib/fsl/socket/socket_drainer.h" |
| #include "src/lib/fsl/tasks/fd_waiter.h" |
| |
| // Reads bytes from stdin and writes them to a socket provided by the guest. |
| // These bytes are generally delivered to emulated serial devices (ex: |
| // virtio-console). |
| class InputReader { |
| public: |
| void Start(zx::unowned_socket socket) { |
| socket_ = std::move(socket); |
| wait_.set_object(socket_->get()); |
| wait_.set_trigger(ZX_SOCKET_WRITABLE | ZX_SOCKET_WRITE_DISABLED | ZX_SOCKET_PEER_CLOSED); |
| WaitForKeystroke(); |
| } |
| |
| private: |
| void WaitForKeystroke() { |
| if (fcntl(STDIN_FILENO, F_GETFD) != -1) { |
| fd_waiter_.Wait(fit::bind_member(this, &InputReader::HandleKeystroke), STDIN_FILENO, POLLIN); |
| } |
| } |
| |
| void SendKeyToGuest() { |
| zx_status_t status = socket_->write(0, &pending_key_, 1, nullptr); |
| if (status == ZX_ERR_SHOULD_WAIT) { |
| wait_.Begin(async_get_default_dispatcher()); // ignore errors |
| return; |
| } else if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "Error " << status << " writing to socket"; |
| return; |
| } |
| WaitForKeystroke(); |
| } |
| |
| void HandleKeystroke(zx_status_t status, uint32_t events) { |
| if (status != ZX_OK) { |
| return; |
| } |
| ssize_t actual = read(STDIN_FILENO, &pending_key_, 1); |
| if (actual != 1) { |
| return; |
| } |
| switch (pending_key_) { |
| case '\b': |
| pending_key_ = 0x7f; |
| break; |
| case '\r': |
| pending_key_ = '\n'; |
| break; |
| } |
| SendKeyToGuest(); |
| } |
| |
| void OnSocketReady(async_dispatcher_t* dispatcher, async::WaitBase* wait, zx_status_t status, |
| const zx_packet_signal_t* signal) { |
| if (status != ZX_OK) { |
| return; |
| } |
| SendKeyToGuest(); |
| } |
| |
| zx::unowned_socket socket_; |
| fsl::FDWaiter fd_waiter_; |
| char pending_key_; |
| async::WaitMethod<InputReader, &InputReader::OnSocketReady> wait_{this}; |
| }; |
| |
| // Reads output from a socket provided by the guest and writes the data to |
| // stdout. This data generally comes from emulated serial devices (ex: |
| // virtio-console). |
| class OutputWriter : public fsl::SocketDrainer::Client { |
| public: |
| OutputWriter(async::Loop* loop) : loop_(loop) {} |
| |
| void Start(zx::socket socket) { socket_drainer_.Start(std::move(socket)); } |
| |
| // |fsl::SocketDrainer::Client| |
| void OnDataAvailable(const void* data, size_t num_bytes) override { |
| write(STDOUT_FILENO, data, num_bytes); |
| } |
| |
| void OnDataComplete() override { loop_->Shutdown(); } |
| |
| private: |
| async::Loop* loop_; |
| fsl::SocketDrainer socket_drainer_{this}; |
| }; |
| |
| SerialConsole::SerialConsole(async::Loop* loop) |
| : loop_(loop), |
| input_reader_(std::make_unique<InputReader>()), |
| output_writer_(std::make_unique<OutputWriter>(loop)) {} |
| |
| SerialConsole::SerialConsole(SerialConsole&& o) |
| : loop_(o.loop_), |
| input_reader_(std::move(o.input_reader_)), |
| output_writer_(std::move(o.output_writer_)) {} |
| |
| SerialConsole::~SerialConsole() = default; |
| |
| void SerialConsole::Start(zx::socket socket) { |
| input_reader_->Start(zx::unowned_socket(socket)); |
| output_writer_->Start(std::move(socket)); |
| } |
| |
| void handle_serial(uint32_t env_id, uint32_t cid, async::Loop* loop, |
| sys::ComponentContext* context) { |
| // Connect to environment. |
| fuchsia::virtualization::ManagerSyncPtr manager; |
| context->svc()->Connect(manager.NewRequest()); |
| fuchsia::virtualization::RealmSyncPtr env_ptr; |
| manager->Connect(env_id, env_ptr.NewRequest()); |
| |
| fuchsia::virtualization::GuestSyncPtr guest; |
| env_ptr->ConnectToInstance(cid, guest.NewRequest()); |
| |
| // Open the serial service of the guest and process IO. |
| zx::socket socket; |
| guest->GetSerial(&socket); |
| if (!socket) { |
| FX_LOGS(ERROR) << "Failed to open serial port"; |
| return; |
| } |
| SerialConsole console(loop); |
| console.Start(std::move(socket)); |
| loop->Run(); |
| } |