use super::*;
use fidl::endpoints::Proxy;
use futures_util::io::AsyncReadExt as _;
use {
fidl_fuchsia_io as fio, fidl_fuchsia_netemul_guest as fnetemul_guest,
fidl_fuchsia_virtualization_guest_interaction as fguest_interaction,
};
pub struct Controller {
guest: Option<fnetemul_guest::GuestProxy>,
name: String,
}
impl<'a> std::fmt::Debug for Controller {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
let Self { guest: _, name } = self;
f.debug_struct("Controller").field("name", name).finish_non_exhaustive()
}
}
impl Controller {
pub async fn new(
name: impl Into<String>,
network: &TestNetwork<'_>,
mac: Option<fnet::MacAddress>,
) -> Result<Controller> {
let name = name.into();
let controller_proxy =
fuchsia_component::client::connect_to_protocol::<fnetemul_guest::ControllerMarker>()
.with_context(|| {
format!("failed to connect to guest controller protocol for guest {}", name)
})?;
let network_client =
network.get_client_end_clone().await.context("failed to get network client end")?;
let guest = controller_proxy
.create_guest(&name, network_client, mac.as_ref())
.await
.with_context(|| format!("create_guest FIDL error for guest {}", name))?
.map_err(|err| {
anyhow::anyhow!(format!("create guest error for guest {}: {:?}", name, err))
})?;
Ok(Controller { guest: Some(guest.into_proxy()), name })
}
fn proxy(&self) -> &fnetemul_guest::GuestProxy {
self.guest.as_ref().expect("guest_proxy was empty")
}
pub async fn put_file(&self, local_path: &str, remote_path: &str) -> Result {
let (file_client_end, file_server_end) =
fidl::endpoints::create_endpoints::<fio::FileMarker>();
fdio::open(&local_path, fio::PERM_READABLE, file_server_end.into_channel())
.with_context(|| format!("failed to open file '{}'", local_path))?;
let status = self
.proxy()
.put_file(file_client_end, remote_path)
.await
.with_context(|| format!("put_file FIDL error for guest {}", self.name))?;
zx::Status::ok(status).with_context(|| {
format!(
"put_file for guest {} failed for file at local path {} and remote path {}",
self.name, local_path, remote_path
)
})
}
pub async fn get_file(&self, local_path: &str, remote_path: &str) -> Result {
let (file_client_end, file_server_end) =
fidl::endpoints::create_endpoints::<fio::FileMarker>();
fdio::open(
&local_path,
fio::PERM_WRITABLE | fio::Flags::FLAG_MAYBE_CREATE,
file_server_end.into_channel(),
)
.with_context(|| format!("failed to open file '{}'", local_path))?;
let status = self
.proxy()
.get_file(remote_path, file_client_end)
.await
.with_context(|| format!("get_file FIDL error for guest {}", self.name))?;
zx::Status::ok(status).with_context(|| {
format!(
"get_file for guest {} failed for file at local path {} and remote path {}",
self.name, local_path, remote_path
)
})
}
pub async fn exec_with_output_logged(
&self,
command: &str,
env: Vec<fguest_interaction::EnvironmentVariable>,
input: Option<&str>,
) -> Result<()> {
let (return_code, stdout, stderr) = self.exec(command, env, input).await?;
tracing::info!(
"command `{}` for guest {} output\nstdout: {}\nstderr: {}",
command,
self.name,
stdout,
stderr
);
if return_code != 0 {
return Err(anyhow!(
"command `{}` for guest {} failed with return code: {}",
command,
self.name,
return_code,
));
}
Ok(())
}
pub async fn exec(
&self,
command: &str,
env: Vec<fguest_interaction::EnvironmentVariable>,
input: Option<&str>,
) -> Result<(i32, String, String)> {
let (stdout_local, stdout_remote) = zx::Socket::create_stream();
let (stderr_local, stderr_remote) = zx::Socket::create_stream();
let (command_listener_client, command_listener_server) =
fidl::endpoints::create_proxy::<fguest_interaction::CommandListenerMarker>();
let (stdin_local, stdin_remote) = match input {
Some(input) => {
let (stdin_local, stdin_remote) = zx::Socket::create_stream();
(Some((stdin_local, input)), Some(stdin_remote))
}
None => (None, None),
};
let () = self
.proxy()
.execute_command(
command,
&env,
stdin_remote,
Some(stdout_remote),
Some(stderr_remote),
command_listener_server,
)
.with_context(|| format!("execute_command FIDL error for guest {}", self.name))?;
let mut async_stdout = fuchsia_async::Socket::from_socket(stdout_local);
let mut async_stderr = fuchsia_async::Socket::from_socket(stderr_local);
let mut stdout_buf = Vec::new();
let mut stderr_buf = Vec::new();
let stdout_fut = pin!(async_stdout
.read_to_end(&mut stdout_buf)
.map(|res| res.context("failed to read from stdout"))
.fuse());
let stderr_fut = pin!(async {
async_stderr.read_to_end(&mut stderr_buf).await.context("failed to read from socket")
}
.fuse());
let mut command_listener_stream = command_listener_client.take_event_stream();
let listener_fut = pin!(async {
loop {
let event = command_listener_stream
.try_next()
.await
.with_context(|| {
format!("failed to get next CommandListenerEvent for guest {}", self.name)
})?
.with_context(|| {
format!("empty CommandListenerEvent for guest {}", self.name)
})?;
match event {
fguest_interaction::CommandListenerEvent::OnStarted { status } => {
let () = zx::Status::ok(status).with_context(|| {
format!(
"error starting exec for guest {} and command {}",
self.name, command
)
})?;
if let Some((stdin_local, to_write)) = stdin_local.as_ref() {
assert_eq!(
stdin_local.write(to_write.as_bytes())?,
to_write.as_bytes().len()
);
}
}
fguest_interaction::CommandListenerEvent::OnTerminated {
status,
return_code,
} => {
let () = zx::Status::ok(status).with_context(|| {
format!(
"error returning from exec for guest {} and command {}",
self.name, command
)
})?;
return Ok(return_code);
}
}
}
}
.fuse());
let return_code = {
let (_, return_code, _): (usize, _, usize) =
futures::try_join!(stderr_fut, listener_fut, stdout_fut)?;
return_code
};
let stdout = String::from_utf8(stdout_buf).context("failed to convert stdout to string")?;
let stderr = String::from_utf8(stderr_buf).context("failed to convert stderr to string")?;
Ok((return_code, stdout, stderr))
}
}
impl Drop for Controller {
fn drop(&mut self) {
let guest = fnetemul_guest::GuestSynchronousProxy::new(
self.guest
.take()
.expect("guest proxy was empty")
.into_channel()
.expect("failed to convert to FIDL channel")
.into_zx_channel(),
);
let () = guest.shutdown(zx::MonotonicInstant::INFINITE).expect("shutdown FIDL error");
}
}