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 let () = self
161 .proxy()
162 .execute_command(
163 command,
164 &env,
165 stdin_remote,
166 Some(stdout_remote),
167 Some(stderr_remote),
168 command_listener_server,
169 )
170 .with_context(|| format!("execute_command FIDL error for guest {}", self.name))?;
171
172 let mut async_stdout = fuchsia_async::Socket::from_socket(stdout_local);
173 let mut async_stderr = fuchsia_async::Socket::from_socket(stderr_local);
174
175 let mut stdout_buf = Vec::new();
176 let mut stderr_buf = Vec::new();
177
178 let stdout_fut = pin!(async_stdout
179 .read_to_end(&mut stdout_buf)
180 .map(|res| res.context("failed to read from stdout"))
181 .fuse());
182 let stderr_fut = pin!(async {
183 async_stderr.read_to_end(&mut stderr_buf).await.context("failed to read from socket")
184 }
185 .fuse());
186
187 let mut command_listener_stream = command_listener_client.take_event_stream();
188 let listener_fut = pin!(async {
189 loop {
190 let event = command_listener_stream
191 .try_next()
192 .await
193 .with_context(|| {
194 format!("failed to get next CommandListenerEvent for guest {}", self.name)
195 })?
196 .with_context(|| {
197 format!("empty CommandListenerEvent for guest {}", self.name)
198 })?;
199 match event {
200 fguest_interaction::CommandListenerEvent::OnStarted { status } => {
201 let () = zx::Status::ok(status).with_context(|| {
202 format!(
203 "error starting exec for guest {} and command {}",
204 self.name, command
205 )
206 })?;
207
208 if let Some((stdin_local, to_write)) = stdin_local.as_ref() {
209 assert_eq!(
210 stdin_local.write(to_write.as_bytes())?,
211 to_write.as_bytes().len()
212 );
213 }
214 }
215 fguest_interaction::CommandListenerEvent::OnTerminated {
216 status,
217 return_code,
218 } => {
219 let () = zx::Status::ok(status).with_context(|| {
220 format!(
221 "error returning from exec for guest {} and command {}",
222 self.name, command
223 )
224 })?;
225
226 return Ok(return_code);
227 }
228 }
229 }
230 }
231 .fuse());
232
233 let return_code = {
235 let (_, return_code, _): (usize, _, usize) =
239 futures::try_join!(stderr_fut, listener_fut, stdout_fut)?;
240 return_code
241 };
242
243 let stdout = String::from_utf8(stdout_buf).context("failed to convert stdout to string")?;
244 let stderr = String::from_utf8(stderr_buf).context("failed to convert stderr to string")?;
245
246 Ok((return_code, stdout, stderr))
247 }
248}
249
250impl Drop for Controller {
251 fn drop(&mut self) {
252 let guest = fnetemul_guest::GuestSynchronousProxy::new(
253 self.guest
254 .take()
255 .expect("guest proxy was empty")
256 .into_channel()
257 .expect("failed to convert to FIDL channel")
258 .into_zx_channel(),
259 );
260
261 let () = guest.shutdown(zx::MonotonicInstant::INFINITE).expect("shutdown FIDL error");
262 }
263}