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