#![deny(missing_docs)]
use fdio::{SpawnAction, SpawnOptions};
use fuchsia_async::{self as fasync};
use fuchsia_runtime::{job_default, HandleInfo, HandleType};
use futures::prelude::*;
use libc::{STDERR_FILENO, STDOUT_FILENO};
use std::ffi::{CStr, CString};
use zx::{self as zx, HandleBased, ProcessInfo};
pub struct ProcessOutput {
pub return_code: i64,
pub stderr: Vec<u8>,
pub stdout: Vec<u8>,
}
impl ProcessOutput {
pub fn is_ok(&self) -> bool {
self.return_code == 0
}
pub fn return_code(&self) -> i64 {
self.return_code
}
pub fn stdout_str(&self) -> &str {
std::str::from_utf8(&self.stdout).unwrap()
}
pub fn stderr_str(&self) -> &str {
std::str::from_utf8(&self.stderr).unwrap()
}
}
pub async fn run_process_async<'a>(
binary_path: &'a str,
args: impl IntoIterator<Item = &'a str>,
proxies: impl IntoIterator<Item = (&'a str, &fidl_fuchsia_io::DirectoryProxy)>,
) -> (fasync::Task<i64>, fasync::Socket, fasync::Socket) {
let (stdout_reader, stdout_writer) = zx::Socket::create_stream();
let (stderr_reader, stderr_writer) = zx::Socket::create_stream();
let () = stdout_writer.half_close().expect("stdout_reader.half_close");
let () = stderr_writer.half_close().expect("stderr_reader.half_close");
let args: Vec<CString> = std::iter::once(binary_path)
.chain(args)
.map(|a| CString::new(a).unwrap_or_else(|e| panic!("failed to parse {a} to CString: {e}")))
.collect();
let args: Vec<&CStr> = args.iter().map(|s| s.as_c_str()).collect();
let mut spawn_actions = vec![];
let proxy_by_path_cstring: Vec<(CString, &fidl_fuchsia_io::DirectoryProxy)> =
proxies.into_iter().map(|(path, dir)| (CString::new(path).unwrap(), dir)).collect();
let proxy_by_path_cstr: Vec<(&CStr, &fidl_fuchsia_io::DirectoryProxy)> =
proxy_by_path_cstring.iter().map(|(path, dir)| (path.as_c_str(), *dir)).collect();
for (path, proxy) in proxy_by_path_cstr {
let (proxy_client_end, proxy_server_end) = fidl::endpoints::create_endpoints();
fuchsia_fs::directory::clone_onto(proxy, proxy_server_end).unwrap();
let proxy_client_channel = proxy_client_end.into_channel();
spawn_actions
.push(SpawnAction::add_namespace_entry(path, proxy_client_channel.into_handle()));
}
spawn_actions.push(SpawnAction::add_handle(
HandleInfo::new(
HandleType::FileDescriptor,
STDOUT_FILENO.try_into().expect("STDOUT_FILENO.try_into"),
),
stdout_writer.into(),
));
spawn_actions.push(SpawnAction::add_handle(
HandleInfo::new(
HandleType::FileDescriptor,
STDERR_FILENO.try_into().expect("STDERR_FILENO.try_into"),
),
stderr_writer.into(),
));
let process = fdio::spawn_etc(
&job_default(),
SpawnOptions::DEFAULT_LOADER,
CString::new(binary_path).expect("cstring path").as_c_str(),
&args[..],
None,
&mut spawn_actions,
)
.expect("spawn process");
(
fasync::Task::spawn(async move {
assert_eq!(
fasync::OnSignals::new(&process, zx::Signals::PROCESS_TERMINATED)
.await
.expect("wait for process termination"),
zx::Signals::PROCESS_TERMINATED
);
let ProcessInfo { return_code, start_time: _, flags: _ } =
process.info().expect("process info");
return_code
}),
fasync::Socket::from_socket(stdout_reader),
fasync::Socket::from_socket(stderr_reader),
)
}
pub async fn run_process<'a>(
binary_path: &'a str,
args: impl IntoIterator<Item = &'a str>,
proxies: impl IntoIterator<Item = (&'a str, &fidl_fuchsia_io::DirectoryProxy)>,
) -> ProcessOutput {
let (update, stdout_reader, stderr_reader) =
run_process_async(binary_path, args, proxies).await;
let drain = |pipe: fasync::Socket| pipe.into_datagram_stream().try_concat().err_into();
future::try_join3(
update.map(Result::<_, anyhow::Error>::Ok),
drain(stdout_reader),
drain(stderr_reader),
)
.map_ok(|(return_code, stdout, stderr)| ProcessOutput { return_code, stdout, stderr })
.await
.unwrap()
}