use crate::cli::format::{
format_create_error, format_destroy_error, format_resolve_error, format_start_error,
};
use crate::lifecycle::{
create_instance_in_collection, destroy_instance_in_collection, resolve_instance,
start_instance, start_instance_with_args, ActionError, CreateError, DestroyError, StartError,
};
use anyhow::{bail, format_err, Result};
use fidl::HandleBased;
use fuchsia_url::AbsoluteComponentUrl;
use futures::future::BoxFuture;
use futures::AsyncReadExt;
use moniker::Moniker;
use std::io::Read;
use {
fidl_fuchsia_component as fcomponent, fidl_fuchsia_component_decl as fdecl,
fidl_fuchsia_process as fprocess, fidl_fuchsia_sys2 as fsys,
};
const TRANSFER_CHUNK_SIZE: usize = 8192;
async fn copy<W: std::io::Write>(source: fidl::Socket, mut sink: W) -> Result<()> {
let mut source = fuchsia_async::Socket::from_socket(source);
let mut buf = [0u8; TRANSFER_CHUNK_SIZE];
loop {
let bytes_read = source.read(&mut buf).await?;
if bytes_read == 0 {
return Ok(());
}
sink.write_all(&buf[..bytes_read])?;
sink.flush()?;
}
}
fn handle_id_for_fd(fd: u32) -> u32 {
const PA_FD: u32 = 0x30;
PA_FD | fd << 16
}
struct Stdio {
local_in: fidl::Socket,
local_out: fidl::Socket,
local_err: fidl::Socket,
}
impl Stdio {
fn new() -> (Self, Vec<fprocess::HandleInfo>) {
let (local_in, remote_in) = fidl::Socket::create_stream();
let (local_out, remote_out) = fidl::Socket::create_stream();
let (local_err, remote_err) = fidl::Socket::create_stream();
(
Self { local_in, local_out, local_err },
vec![
fprocess::HandleInfo { handle: remote_in.into_handle(), id: handle_id_for_fd(0) },
fprocess::HandleInfo { handle: remote_out.into_handle(), id: handle_id_for_fd(1) },
fprocess::HandleInfo { handle: remote_err.into_handle(), id: handle_id_for_fd(2) },
],
)
}
async fn forward(self) {
let local_in = self.local_in;
let local_out = self.local_out;
let local_err = self.local_err;
std::thread::spawn(move || {
let mut term_in = std::io::stdin().lock();
let mut buf = [0u8; TRANSFER_CHUNK_SIZE];
loop {
let bytes_read = term_in.read(&mut buf)?;
if bytes_read == 0 {
return Ok::<(), anyhow::Error>(());
}
local_in.write(&buf[..bytes_read])?;
}
});
std::thread::spawn(move || {
let mut executor = fuchsia_async::LocalExecutor::new();
let _result: Result<()> = executor
.run_singlethreaded(async move { copy(local_err, std::io::stderr()).await });
});
std::thread::spawn(move || {
let mut executor = fuchsia_async::LocalExecutor::new();
let _result: Result<()> = executor
.run_singlethreaded(async move { copy(local_out, std::io::stdout().lock()).await });
std::process::exit(0);
});
let () = futures::future::pending().await;
}
}
pub async fn run_cmd<W: std::io::Write>(
moniker: Moniker,
url: AbsoluteComponentUrl,
recreate: bool,
connect_stdio: bool,
config_overrides: Vec<fdecl::ConfigOverride>,
lifecycle_controller_factory: impl Fn()
-> BoxFuture<'static, Result<fsys::LifecycleControllerProxy>>,
mut writer: W,
) -> Result<()> {
let lifecycle_controller = lifecycle_controller_factory().await?;
let parent = moniker
.parent()
.ok_or_else(|| format_err!("Error: {} does not reference a dynamic instance", moniker))?;
let leaf = moniker
.leaf()
.ok_or_else(|| format_err!("Error: {} does not reference a dynamic instance", moniker))?;
let child_name = leaf.name();
let collection = leaf
.collection()
.ok_or_else(|| format_err!("Error: {} does not reference a dynamic instance", moniker))?;
if recreate {
match destroy_instance_in_collection(&lifecycle_controller, &parent, collection, child_name)
.await
{
Ok(()) => {
writeln!(writer, "Destroyed existing component instance at {}...", moniker)?;
}
Err(DestroyError::ActionError(ActionError::InstanceNotFound))
| Err(DestroyError::ActionError(ActionError::InstanceNotResolved)) => {
}
Err(e) => return Err(format_destroy_error(&moniker, e)),
}
}
writeln!(writer, "URL: {}", url)?;
writeln!(writer, "Moniker: {}", moniker)?;
writeln!(writer, "Creating component instance...")?;
let (mut maybe_stdio, numbered_handles) = if connect_stdio {
let (stdio, numbered_handles) = Stdio::new();
(Some(stdio), Some(numbered_handles))
} else {
(None, Some(vec![]))
};
let create_result = create_instance_in_collection(
&lifecycle_controller,
&parent,
collection,
child_name,
&url,
config_overrides.clone(),
None,
)
.await;
match create_result {
Err(CreateError::InstanceAlreadyExists) => {
bail!("\nError: {} already exists.\nUse --recreate to destroy and create a new instance, or provide a different moniker.\n", moniker)
}
Err(e) => {
return Err(format_create_error(&moniker, &parent, collection, e));
}
Ok(()) => {}
}
writeln!(writer, "Resolving component instance...")?;
resolve_instance(&lifecycle_controller, &moniker)
.await
.map_err(|e| format_resolve_error(&moniker, e))?;
writeln!(writer, "Starting component instance...")?;
let start_args = fcomponent::StartChildArgs { numbered_handles, ..Default::default() };
let res = start_instance_with_args(&lifecycle_controller, &moniker, start_args).await;
if let Err(StartError::ActionError(ActionError::Fidl(_e))) = &res {
let lifecycle_controller = lifecycle_controller_factory().await?;
if connect_stdio {
let (stdio, numbered_handles) = Stdio::new();
maybe_stdio = Some(stdio);
let create_args = fcomponent::CreateChildArgs {
numbered_handles: Some(numbered_handles),
..Default::default()
};
destroy_instance_in_collection(&lifecycle_controller, &parent, collection, child_name)
.await?;
create_instance_in_collection(
&lifecycle_controller,
&parent,
collection,
child_name,
&url,
config_overrides,
Some(create_args),
)
.await?;
resolve_instance(&lifecycle_controller, &moniker)
.await
.map_err(|e| format_resolve_error(&moniker, e))?;
}
let _stop_future = start_instance(&lifecycle_controller, &moniker)
.await
.map_err(|e| format_start_error(&moniker, e))?;
} else {
let _stop_future = res.map_err(|e| format_start_error(&moniker, e))?;
}
if let Some(stdio) = maybe_stdio {
stdio.forward().await;
}
writeln!(writer, "Component instance is running!")?;
Ok(())
}
#[cfg(test)]
mod test {
use super::*;
use fidl::endpoints::create_proxy_and_stream;
use fidl_fuchsia_sys2 as fsys;
use futures::{FutureExt, TryStreamExt};
fn setup_fake_lifecycle_controller_ok(
expected_parent_moniker: &'static str,
expected_collection: &'static str,
expected_name: &'static str,
expected_url: &'static str,
expected_moniker: &'static str,
expect_destroy: bool,
) -> fsys::LifecycleControllerProxy {
let (lifecycle_controller, mut stream) =
create_proxy_and_stream::<fsys::LifecycleControllerMarker>();
fuchsia_async::Task::local(async move {
if expect_destroy {
let req = stream.try_next().await.unwrap().unwrap();
match req {
fsys::LifecycleControllerRequest::DestroyInstance {
parent_moniker,
child,
responder,
} => {
assert_eq!(
Moniker::parse_str(expected_parent_moniker),
Moniker::parse_str(&parent_moniker)
);
assert_eq!(expected_name, child.name);
assert_eq!(expected_collection, child.collection.unwrap());
responder.send(Ok(())).unwrap();
}
_ => panic!("Unexpected Lifecycle Controller request: {:?}", req),
}
}
let req = stream.try_next().await.unwrap().unwrap();
match req {
fsys::LifecycleControllerRequest::CreateInstance {
parent_moniker,
collection,
decl,
responder,
args: _,
} => {
assert_eq!(
Moniker::parse_str(expected_parent_moniker),
Moniker::parse_str(&parent_moniker)
);
assert_eq!(expected_collection, collection.name);
assert_eq!(expected_name, decl.name.unwrap());
assert_eq!(expected_url, decl.url.unwrap());
responder.send(Ok(())).unwrap();
}
_ => panic!("Unexpected Lifecycle Controller request: {:?}", req),
}
let req = stream.try_next().await.unwrap().unwrap();
match req {
fsys::LifecycleControllerRequest::ResolveInstance { moniker, responder } => {
assert_eq!(Moniker::parse_str(expected_moniker), Moniker::parse_str(&moniker));
responder.send(Ok(())).unwrap();
}
_ => panic!("Unexpected Lifecycle Controller request: {:?}", req),
}
let req = stream.try_next().await.unwrap().unwrap();
match req {
fsys::LifecycleControllerRequest::StartInstanceWithArgs {
moniker,
binder: _,
args: _,
responder,
} => {
assert_eq!(Moniker::parse_str(expected_moniker), Moniker::parse_str(&moniker));
responder.send(Ok(())).unwrap();
}
_ => panic!("Unexpected Lifecycle Controller request: {:?}", req),
}
})
.detach();
lifecycle_controller
}
fn setup_fake_lifecycle_controller_fail(
expected_parent_moniker: &'static str,
expected_collection: &'static str,
expected_name: &'static str,
expected_url: &'static str,
) -> fsys::LifecycleControllerProxy {
let (lifecycle_controller, mut stream) =
create_proxy_and_stream::<fsys::LifecycleControllerMarker>();
fuchsia_async::Task::local(async move {
let req = stream.try_next().await.unwrap().unwrap();
match req {
fsys::LifecycleControllerRequest::DestroyInstance {
parent_moniker,
child,
responder,
} => {
assert_eq!(
Moniker::parse_str(expected_parent_moniker),
Moniker::parse_str(&parent_moniker)
);
assert_eq!(expected_name, child.name);
assert_eq!(expected_collection, child.collection.unwrap());
responder.send(Ok(())).unwrap();
}
_ => panic!("Unexpected Lifecycle Controller request: {:?}", req),
}
let req = stream.try_next().await.unwrap().unwrap();
match req {
fsys::LifecycleControllerRequest::CreateInstance {
parent_moniker,
collection,
decl,
responder,
args: _,
} => {
assert_eq!(
Moniker::parse_str(expected_parent_moniker),
Moniker::parse_str(&parent_moniker)
);
assert_eq!(expected_collection, collection.name);
assert_eq!(expected_name, decl.name.unwrap());
assert_eq!(expected_url, decl.url.unwrap());
responder.send(Err(fsys::CreateError::InstanceAlreadyExists)).unwrap();
}
_ => panic!("Unexpected Lifecycle Controller request: {:?}", req),
}
})
.detach();
lifecycle_controller
}
fn setup_fake_lifecycle_controller_recreate(
expected_parent_moniker: &'static str,
expected_collection: &'static str,
expected_name: &'static str,
expected_url: &'static str,
expected_moniker: &'static str,
) -> fsys::LifecycleControllerProxy {
let (lifecycle_controller, mut stream) =
create_proxy_and_stream::<fsys::LifecycleControllerMarker>();
fuchsia_async::Task::local(async move {
let req = stream.try_next().await.unwrap().unwrap();
match req {
fsys::LifecycleControllerRequest::DestroyInstance {
parent_moniker,
child,
responder,
} => {
assert_eq!(
Moniker::parse_str(expected_parent_moniker),
Moniker::parse_str(&parent_moniker)
);
assert_eq!(expected_name, child.name);
assert_eq!(expected_collection, child.collection.unwrap());
responder.send(Ok(())).unwrap();
}
_ => panic!("Unexpected Lifecycle Controller request: {:?}", req),
}
let req = stream.try_next().await.unwrap().unwrap();
match req {
fsys::LifecycleControllerRequest::CreateInstance {
parent_moniker,
collection,
decl,
responder,
args: _,
} => {
assert_eq!(
Moniker::parse_str(expected_parent_moniker),
Moniker::parse_str(&parent_moniker)
);
assert_eq!(expected_collection, collection.name);
assert_eq!(expected_name, decl.name.unwrap());
assert_eq!(expected_url, decl.url.unwrap());
responder.send(Ok(())).unwrap();
}
_ => panic!("Unexpected Lifecycle Controller request: {:?}", req),
}
let req = stream.try_next().await.unwrap().unwrap();
match req {
fsys::LifecycleControllerRequest::ResolveInstance { moniker, responder } => {
assert_eq!(Moniker::parse_str(expected_moniker), Moniker::parse_str(&moniker));
responder.send(Ok(())).unwrap();
}
_ => panic!("Unexpected Lifecycle Controller request: {:?}", req),
}
let req = stream.try_next().await.unwrap().unwrap();
match req {
fsys::LifecycleControllerRequest::StartInstanceWithArgs {
moniker,
binder: _,
args: _,
responder,
} => {
assert_eq!(Moniker::parse_str(expected_moniker), Moniker::parse_str(&moniker));
responder.send(Ok(())).unwrap();
}
_ => panic!("Unexpected Lifecycle Controller request: {:?}", req),
}
})
.detach();
lifecycle_controller
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_ok() -> Result<()> {
let mut output = Vec::new();
let lifecycle_controller = setup_fake_lifecycle_controller_ok(
"/some",
"collection",
"name",
"fuchsia-pkg://fuchsia.com/test#meta/test.cm",
"/some/collection:name",
true,
);
let response = run_cmd(
"/some/collection:name".try_into().unwrap(),
"fuchsia-pkg://fuchsia.com/test#meta/test.cm".try_into().unwrap(),
true,
false,
vec![],
move || {
let lifecycle_controller = lifecycle_controller.clone();
async move { Ok(lifecycle_controller) }.boxed()
},
&mut output,
)
.await;
response.unwrap();
Ok(())
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_name() -> Result<()> {
let mut output = Vec::new();
let lifecycle_controller = setup_fake_lifecycle_controller_ok(
"/core",
"ffx-laboratory",
"foobar",
"fuchsia-pkg://fuchsia.com/test#meta/test.cm",
"/core/ffx-laboratory:foobar",
false,
);
let response = run_cmd(
"/core/ffx-laboratory:foobar".try_into().unwrap(),
"fuchsia-pkg://fuchsia.com/test#meta/test.cm".try_into().unwrap(),
false,
false,
vec![],
move || {
let lifecycle_controller = lifecycle_controller.clone();
async move { Ok(lifecycle_controller) }.boxed()
},
&mut output,
)
.await;
response.unwrap();
Ok(())
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_fail() -> Result<()> {
let mut output = Vec::new();
let lifecycle_controller = setup_fake_lifecycle_controller_fail(
"/core",
"ffx-laboratory",
"test",
"fuchsia-pkg://fuchsia.com/test#meta/test.cm",
);
let response = run_cmd(
"/core/ffx-laboratory:test".try_into().unwrap(),
"fuchsia-pkg://fuchsia.com/test#meta/test.cm".try_into().unwrap(),
true,
false,
vec![],
move || {
let lifecycle_controller = lifecycle_controller.clone();
async move { Ok(lifecycle_controller) }.boxed()
},
&mut output,
)
.await;
response.unwrap_err();
Ok(())
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_recreate() -> Result<()> {
let mut output = Vec::new();
let lifecycle_controller = setup_fake_lifecycle_controller_recreate(
"/core",
"ffx-laboratory",
"test",
"fuchsia-pkg://fuchsia.com/test#meta/test.cm",
"/core/ffx-laboratory:test",
);
let response = run_cmd(
"/core/ffx-laboratory:test".try_into().unwrap(),
"fuchsia-pkg://fuchsia.com/test#meta/test.cm".try_into().unwrap(),
true,
false,
vec![],
move || {
let lifecycle_controller = lifecycle_controller.clone();
async move { Ok(lifecycle_controller) }.boxed()
},
&mut output,
)
.await;
response.unwrap();
Ok(())
}
}