guest_cli/platform/
mod.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use anyhow::{anyhow, Result};
6use async_trait::async_trait;
7use blocking::Unblock;
8use fidl_fuchsia_virtualization::{GuestManagerProxy, GuestMarker, GuestProxy, LinuxManagerProxy};
9use fuchsia_async as fasync;
10use guest_cli_args::GuestType;
11use std::io::{Read, Write};
12use std::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd};
13
14#[cfg(target_os = "fuchsia")]
15mod fuchsia;
16#[cfg(target_os = "fuchsia")]
17pub use fuchsia::*;
18
19#[cfg(not(target_os = "fuchsia"))]
20mod host;
21#[cfg(not(target_os = "fuchsia"))]
22pub use host::*;
23
24// NOTE: This logic is partially duplicated in //src/virtualization/bin/guest/src/services.rs,
25// but that will be removed once this migration to a common codebase is finished.
26// TODO(https://fxbug.dev/42067874): Remove other implementation.
27pub enum Stdio {
28    Stdin,
29    Stdout,
30    Stderr,
31}
32
33impl AsRawFd for Stdio {
34    fn as_raw_fd(&self) -> RawFd {
35        match self {
36            Stdio::Stdin => std::io::stdin().as_raw_fd(),
37            Stdio::Stdout => std::io::stdout().as_raw_fd(),
38            Stdio::Stderr => std::io::stderr().as_raw_fd(),
39        }
40    }
41}
42
43pub struct UnbufferedStdio(Option<std::fs::File>);
44
45impl UnbufferedStdio {
46    // Unbuffer stdio by creating a File from the raw FD, but without taking ownership of the FD.
47    // This allows for using interactive commands, and the custom Drop implementation prevents
48    // the file from closing the stdio FD upon destruction.
49    fn new(stdio: Stdio) -> Self {
50        // A note about safety: Using from_raw_fd requires that the fd remains valid. As this is
51        // only using the process stdio handles, validity can be assumed for the life of th
52        // process.
53        unsafe { Self { 0: Some(std::fs::File::from_raw_fd(stdio.as_raw_fd())) } }
54    }
55}
56
57impl AsRawFd for UnbufferedStdio {
58    fn as_raw_fd(&self) -> RawFd {
59        self.0.as_ref().unwrap().as_raw_fd()
60    }
61}
62
63impl AsFd for UnbufferedStdio {
64    fn as_fd(&self) -> BorrowedFd<'_> {
65        self.0.as_ref().unwrap().as_fd()
66    }
67}
68
69impl Drop for UnbufferedStdio {
70    fn drop(&mut self) {
71        // Prevent the file from closing the fd (which it doesn't own).
72        _ = self.0.take().unwrap().into_raw_fd();
73    }
74}
75
76impl Write for UnbufferedStdio {
77    fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
78        self.0.as_mut().unwrap().write(buf)
79    }
80
81    fn flush(&mut self) -> Result<(), std::io::Error> {
82        self.0.as_mut().unwrap().flush()
83    }
84}
85
86impl Read for UnbufferedStdio {
87    fn read(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error> {
88        self.0.as_mut().unwrap().read(buf)
89    }
90}
91
92pub struct GuestConsole {
93    input: Option<fasync::Socket>,
94    output: Option<fasync::Socket>,
95}
96
97impl GuestConsole {
98    pub fn new(input: fidl::Socket, output: fidl::Socket) -> Result<Self> {
99        Ok(GuestConsole {
100            input: Some(fasync::Socket::from_socket(input)),
101            output: Some(fasync::Socket::from_socket(output)),
102        })
103    }
104
105    pub fn get_unblocked_stdio(stdio: Stdio) -> Unblock<UnbufferedStdio> {
106        Unblock::new(UnbufferedStdio::new(stdio))
107    }
108
109    pub async fn run<R: futures::io::AsyncRead + Unpin, W: futures::io::AsyncWrite + Unpin>(
110        mut self,
111        host_tx: R,
112        mut host_rx: W,
113    ) -> Result<()> {
114        let mut input = self.input.take().expect("run can only be called once");
115        let output = self.output.take().expect("run can only be called once");
116
117        let guest_input = futures::io::copy(host_tx, &mut input);
118        let guest_output = futures::io::copy(output, &mut host_rx);
119
120        futures::future::try_select(guest_input, guest_output)
121            .await
122            .map(|_| ())
123            .map_err(|e| e.factor_first().0.into())
124    }
125
126    pub async fn run_with_stdio(self) -> Result<()> {
127        self.run(
128            GuestConsole::get_unblocked_stdio(Stdio::Stdin),
129            GuestConsole::get_unblocked_stdio(Stdio::Stdout),
130        )
131        .await
132    }
133}
134
135#[async_trait(?Send)]
136pub trait PlatformServices {
137    async fn connect_to_linux_manager(&self) -> Result<LinuxManagerProxy>;
138
139    async fn connect_to_manager(&self, guest_type: GuestType) -> Result<GuestManagerProxy>;
140
141    async fn connect_to_guest(&self, guest_type: GuestType) -> Result<GuestProxy> {
142        let guest_manager = self.connect_to_manager(guest_type).await?;
143        let (guest, guest_server_end) = fidl::endpoints::create_proxy::<GuestMarker>();
144        guest_manager.connect(guest_server_end).await?.map_err(|err| anyhow!("{:?}", err))?;
145        Ok(guest)
146    }
147}
148
149#[cfg(test)]
150mod test {
151    use super::*;
152    use fidl::HandleBased;
153
154    #[fasync::run_singlethreaded(test)]
155    async fn guest_console_copies_async_stream() {
156        // Wire up a mock guest console, mocking out the virtio-console on a guest.
157        let (guest_console_socket, guest_console_tx) = fidl::Socket::create_stream();
158        let guest_console_rx =
159            guest_console_tx.duplicate_handle(fidl::Rights::SAME_RIGHTS).unwrap();
160        let guest_console = GuestConsole::new(guest_console_rx, guest_console_tx)
161            .expect("failed to make guest console");
162
163        // This represents a host's stdio. While stdout and stdin are unidirectional, sockets
164        // are bidirectional so we can duplicate one and split it into both in and out.
165        let (host_stdio, host_stdin_sock) = fidl::Socket::create_stream();
166        let host_stdout_sock = host_stdin_sock.duplicate_handle(fidl::Rights::SAME_RIGHTS).unwrap();
167        let host_stdout = fasync::Socket::from_socket(host_stdout_sock);
168        let host_stdin = fasync::Socket::from_socket(host_stdin_sock);
169
170        // Have the "guest" write this command to its console stdout, which should be pushed to our
171        // stdout.
172        let test_string = "Test Command";
173        guest_console_socket.write(format!("{test_string}").as_bytes()).unwrap();
174
175        // Drop the endpoint, which looks like the guest terminating (and sends an EOF).
176        drop(guest_console_socket);
177
178        guest_console.run(host_stdin, host_stdout).await.expect("failed to complete!");
179
180        let mut buffer = [0; 1024];
181        let n = host_stdio.read(&mut buffer[..]).expect("failed to read from socket");
182
183        assert_eq!(n, test_string.len());
184        assert_eq!(String::from_utf8(buffer[..n].to_vec()).unwrap(), test_string);
185    }
186}