guest_cli/platform/
mod.rs
1use 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
24pub 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 fn new(stdio: Stdio) -> Self {
50 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 _ = 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 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 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 let test_string = "Test Command";
173 guest_console_socket.write(format!("{test_string}").as_bytes()).unwrap();
174
175 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}