1use super::*;
6use fidl::endpoints::Proxy;
7use futures_util::io::AsyncReadExt as _;
8use {
9 fidl_fuchsia_io as fio, fidl_fuchsia_netemul_guest as fnetemul_guest,
10 fidl_fuchsia_virtualization_guest_interaction as fguest_interaction,
11};
12
13pub struct Controller {
20 guest: Option<fnetemul_guest::GuestProxy>,
23 name: String,
24}
25
26impl<'a> std::fmt::Debug for Controller {
27 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
28 let Self { guest: _, name } = self;
29 f.debug_struct("Controller").field("name", name).finish_non_exhaustive()
30 }
31}
32
33impl Controller {
34 pub async fn new(
38 name: impl Into<String>,
39 network: &TestNetwork<'_>,
40 mac: Option<fnet::MacAddress>,
41 ) -> Result<Controller> {
42 let name = name.into();
43 let controller_proxy =
44 fuchsia_component::client::connect_to_protocol::<fnetemul_guest::ControllerMarker>()
45 .with_context(|| {
46 format!("failed to connect to guest controller protocol for guest {}", name)
47 })?;
48
49 let network_client =
50 network.get_client_end_clone().await.context("failed to get network client end")?;
51 let guest = controller_proxy
52 .create_guest(&name, network_client, mac.as_ref())
53 .await
54 .with_context(|| format!("create_guest FIDL error for guest {}", name))?
55 .map_err(|err| {
56 anyhow::anyhow!(format!("create guest error for guest {}: {:?}", name, err))
57 })?;
58 Ok(Controller { guest: Some(guest.into_proxy()), name })
59 }
60
61 fn proxy(&self) -> &fnetemul_guest::GuestProxy {
62 self.guest.as_ref().expect("guest_proxy was empty")
63 }
64
65 pub async fn put_file(&self, local_path: &str, remote_path: &str) -> Result {
68 let (file_client_end, file_server_end) =
69 fidl::endpoints::create_endpoints::<fio::FileMarker>();
70 fdio::open(&local_path, fio::PERM_READABLE, file_server_end.into_channel())
71 .with_context(|| format!("failed to open file '{}'", local_path))?;
72 let status = self
73 .proxy()
74 .put_file(file_client_end, remote_path)
75 .await
76 .with_context(|| format!("put_file FIDL error for guest {}", self.name))?;
77 zx::Status::ok(status).with_context(|| {
78 format!(
79 "put_file for guest {} failed for file at local path {} and remote path {}",
80 self.name, local_path, remote_path
81 )
82 })
83 }
84
85 pub async fn get_file(&self, local_path: &str, remote_path: &str) -> Result {
88 let (file_client_end, file_server_end) =
89 fidl::endpoints::create_endpoints::<fio::FileMarker>();
90 fdio::open(
91 &local_path,
92 fio::PERM_WRITABLE | fio::Flags::FLAG_MAYBE_CREATE,
93 file_server_end.into_channel(),
94 )
95 .with_context(|| format!("failed to open file '{}'", local_path))?;
96 let status = self
97 .proxy()
98 .get_file(remote_path, file_client_end)
99 .await
100 .with_context(|| format!("get_file FIDL error for guest {}", self.name))?;
101 zx::Status::ok(status).with_context(|| {
102 format!(
103 "get_file for guest {} failed for file at local path {} and remote path {}",
104 self.name, local_path, remote_path
105 )
106 })
107 }
108
109 pub async fn exec_with_output_logged(
115 &self,
116 command: &str,
117 env: Vec<fguest_interaction::EnvironmentVariable>,
118 input: Option<&str>,
119 ) -> Result<()> {
120 let (return_code, stdout, stderr) = self.exec(command, env, input).await?;
121 log::info!(
122 "command `{}` for guest {} output\nstdout: {}\nstderr: {}",
123 command,
124 self.name,
125 stdout,
126 stderr
127 );
128 if return_code != 0 {
129 return Err(anyhow!(
130 "command `{}` for guest {} failed with return code: {}",
131 command,
132 self.name,
133 return_code,
134 ));
135 }
136 Ok(())
137 }
138
139 pub async fn exec(
143 &self,
144 command: &str,
145 env: Vec<fguest_interaction::EnvironmentVariable>,
146 input: Option<&str>,
147 ) -> Result<(i32, String, String)> {
148 let (stdout_local, stdout_remote) = zx::Socket::create_stream();
149 let (stderr_local, stderr_remote) = zx::Socket::create_stream();
150
151 let (command_listener_client, command_listener_server) =
152 fidl::endpoints::create_proxy::<fguest_interaction::CommandListenerMarker>();
153 let (stdin_local, stdin_remote) = match input {
154 Some(input) => {
155 let (stdin_local, stdin_remote) = zx::Socket::create_stream();
156 (Some((stdin_local, input)), Some(stdin_remote))
157 }
158 None => (None, None),
159 };
160 self.proxy()
161 .execute_command(
162 command,
163 &env,
164 stdin_remote,
165 Some(stdout_remote),
166 Some(stderr_remote),
167 command_listener_server,
168 )
169 .with_context(|| format!("execute_command FIDL error for guest {}", self.name))?;
170
171 let mut async_stdout = fuchsia_async::Socket::from_socket(stdout_local);
172 let mut async_stderr = fuchsia_async::Socket::from_socket(stderr_local);
173
174 let mut stdout_buf = Vec::new();
175 let mut stderr_buf = Vec::new();
176
177 let stdout_fut = pin!(
178 async_stdout
179 .read_to_end(&mut stdout_buf)
180 .map(|res| res.context("failed to read from stdout"))
181 .fuse()
182 );
183 let stderr_fut = pin!(
184 async {
185 async_stderr
186 .read_to_end(&mut stderr_buf)
187 .await
188 .context("failed to read from socket")
189 }
190 .fuse()
191 );
192
193 let mut command_listener_stream = command_listener_client.take_event_stream();
194 let listener_fut = pin!(
195 async {
196 loop {
197 let event = command_listener_stream
198 .try_next()
199 .await
200 .with_context(|| {
201 format!(
202 "failed to get next CommandListenerEvent for guest {}",
203 self.name
204 )
205 })?
206 .with_context(|| {
207 format!("empty CommandListenerEvent for guest {}", self.name)
208 })?;
209 match event {
210 fguest_interaction::CommandListenerEvent::OnStarted { status } => {
211 zx::Status::ok(status).with_context(|| {
212 format!(
213 "error starting exec for guest {} and command {}",
214 self.name, command
215 )
216 })?;
217
218 if let Some((stdin_local, to_write)) = stdin_local.as_ref() {
219 assert_eq!(
220 stdin_local.write(to_write.as_bytes())?,
221 to_write.as_bytes().len()
222 );
223 }
224 }
225 fguest_interaction::CommandListenerEvent::OnTerminated {
226 status,
227 return_code,
228 } => {
229 zx::Status::ok(status).with_context(|| {
230 format!(
231 "error returning from exec for guest {} and command {}",
232 self.name, command
233 )
234 })?;
235
236 return Ok(return_code);
237 }
238 }
239 }
240 }
241 .fuse()
242 );
243
244 let return_code = {
246 let (_, return_code, _): (usize, _, usize) =
250 futures::try_join!(stderr_fut, listener_fut, stdout_fut)?;
251 return_code
252 };
253
254 let stdout = String::from_utf8(stdout_buf).context("failed to convert stdout to string")?;
255 let stderr = String::from_utf8(stderr_buf).context("failed to convert stderr to string")?;
256
257 Ok((return_code, stdout, stderr))
258 }
259}
260
261impl Drop for Controller {
262 fn drop(&mut self) {
263 let guest = fnetemul_guest::GuestSynchronousProxy::new(
264 self.guest
265 .take()
266 .expect("guest proxy was empty")
267 .into_channel()
268 .expect("failed to convert to FIDL channel")
269 .into_zx_channel(),
270 );
271
272 guest.shutdown(zx::MonotonicInstant::INFINITE).expect("shutdown FIDL error");
273 }
274}