blob: 314988c09730bcd6b6e0199f1ee976615959b4f0 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
anyhow::{anyhow, Context, Result},
blocking::Unblock,
ffx_core::{ffx_error, ffx_plugin},
ffx_starnix_shell_args::ShellStarnixCommand,
fidl::endpoints::create_proxy,
fidl_fuchsia_starnix_developer::{
ManagerProxy, ShellControllerEvent, ShellControllerMarker, ShellParams,
},
futures::StreamExt,
signal_hook::{consts::signal::SIGINT, iterator::Signals},
};
#[ffx_plugin(
"starnix_enabled",
ManagerProxy = "core/starnix_manager:expose:fuchsia.starnix.developer.Manager"
)]
pub async fn shell_starnix(
manager_proxy: ManagerProxy,
_shell: ShellStarnixCommand,
) -> Result<i32> {
let (controller_proxy, controller_server_end) = create_proxy::<ShellControllerMarker>()?;
let (sin, cin) =
fidl::Socket::create(fidl::SocketOpts::STREAM).context("failed to create stdin socket")?;
let (sout, cout) =
fidl::Socket::create(fidl::SocketOpts::STREAM).context("failed to create stdout socket")?;
let (serr, cerr) =
fidl::Socket::create(fidl::SocketOpts::STREAM).context("failed to create stderr socket")?;
let mut stdin = fidl::AsyncSocket::from_socket(cin)?;
let mut stdout = Unblock::new(std::io::stdout());
let mut stderr = Unblock::new(std::io::stderr());
let copy_futures = futures::future::try_join3(
// We may need to swap out for a latency sensitive copy that calls "flush" regularly.
// If you see what feels like stalling behavior at odd buffer biases, it may down to this
// copy not pumping sufficient to flush.
futures::io::copy(Unblock::new(std::io::stdin()), &mut stdin),
// This approach does not support "ffx starnix shell cat largefile | head -n 1" because
// closing stdout is not propagated back to cout.
futures::io::copy(fidl::AsyncSocket::from_socket(cout)?, &mut stdout),
futures::io::copy(fidl::AsyncSocket::from_socket(cerr)?, &mut stderr),
);
let mut event_stream = controller_proxy.take_event_stream();
let term_event_future = async move {
while let Some(result) = event_stream.next().await {
match result? {
ShellControllerEvent::OnTerminated { return_code } => {
return Ok(return_code);
}
}
}
Err(anyhow!(ffx_error!("Shell terminated abnormally")))
};
// Force an exit on interrupt.
let mut signals = Signals::new(&[SIGINT]).unwrap();
let handle = signals.handle();
let thread = std::thread::spawn(move || {
for signal in signals.forever() {
match signal {
SIGINT => {
eprintln!("Caught interrupt. Forcing exit...");
std::process::exit(0);
}
_ => unreachable!(),
}
}
});
let params = ShellParams {
stdin: Some(sin.into()),
stdout: Some(sout.into()),
stderr: Some(serr.into()),
..ShellParams::EMPTY
};
manager_proxy
.start_shell(params, controller_server_end)
.map_err(|_| anyhow!("Error starting shell: {:?}"))?;
let (copy_result, return_code) = futures::join!(copy_futures, term_event_future);
copy_result?;
// Shut down the signal thread.
handle.close();
thread.join().expect("thread to shutdown without panic");
return_code
}