blob: 32df4557442d472aa206250d9083ccde829292a6 [file] [log] [blame]
// Copyright 2022 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 crate::layout;
use crate::trampoline;
use fidl::{
endpoints::{ClientEnd, Proxy},
HandleBased,
};
use fidl_fuchsia_dash::LauncherError;
use fidl_fuchsia_hardware_pty as pty;
use fidl_fuchsia_io as fio;
use fidl_fuchsia_process as fproc;
use fuchsia_component::client::connect_to_protocol;
use fuchsia_runtime::{HandleInfo as HandleId, HandleType};
use fuchsia_zircon as zx;
pub mod component;
pub mod package;
// -s: force input from stdin
// -i: force interactive
const DASH_ARGS_FOR_INTERACTIVE: [&[u8]; 2] = ["-i".as_bytes(), "-s".as_bytes()];
// TODO(https://fxbug.dev/42055812): Verbose (-v) or write-commands-to-stderr (-x) is required if a command is
// given, else it errors with `Can't open <cmd>`. -c: execute command
const DASH_ARGS_FOR_COMMAND: [&[u8]; 2] = ["-v".as_bytes(), "-c".as_bytes()];
async fn explore_over_handles(
stdin: zx::Handle,
stdout: zx::Handle,
stderr: zx::Handle,
mut tool_urls: Vec<String>,
command: Option<String>,
mut name_infos: Vec<fproc::NameInfo>,
process_name: String,
package_resolver: &crate::package_resolver::PackageResolver,
) -> Result<zx::Process, LauncherError> {
// In addition to tools binaries requested by the user, add the built-in binaries of the
// debug-dash-launcher package, creating `#!resolve` trampolines for all.
tool_urls.push("fuchsia-pkg://fuchsia.com/debug-dash-launcher".into());
let (tools_pkg_dir, tools_path) =
trampoline::create_trampolines_from_packages(package_resolver, tool_urls).await?;
layout::add_tools_to_name_infos(tools_pkg_dir, &mut name_infos);
// The dash-launcher can be asked to launch multiple dash processes, each of which can make
// their own process hierarchies. This will look better topologically if we make a child job for
// each dash process.
let job =
fuchsia_runtime::job_default().create_child_job().map_err(|_| LauncherError::Internal)?;
// Add handles for the current job, stdio, library loader and UTC time.
let handle_infos = create_handle_infos(&job, stdin, stdout, stderr)?;
let launcher = connect_to_protocol::<fproc::LauncherMarker>()
.map_err(|_| LauncherError::ProcessLauncher)?;
let mut args = Vec::new();
if let Some(cmd) = command {
args.extend(DASH_ARGS_FOR_COMMAND.iter().map(|b| b.to_vec()));
args.push(cmd.into_bytes());
} else {
args.extend(DASH_ARGS_FOR_INTERACTIVE.iter().map(|b| b.to_vec()));
};
// Spawn the dash process.
let info = create_launch_info(process_name, &job).await?;
launcher.add_names(name_infos).map_err(|_| LauncherError::ProcessLauncher)?;
launcher.add_handles(handle_infos).map_err(|_| LauncherError::ProcessLauncher)?;
launcher.add_args(&args).map_err(|_| LauncherError::ProcessLauncher)?;
let path_envvar = trampoline::create_env_path(tools_path);
let env_vars = &[path_envvar.into_bytes()];
launcher.add_environs(env_vars).map_err(|_| LauncherError::ProcessLauncher)?;
let (status, process) =
launcher.launch(info).await.map_err(|_| LauncherError::ProcessLauncher)?;
zx::Status::ok(status).map_err(|_| LauncherError::ProcessLauncher)?;
let process = process.ok_or(LauncherError::ProcessLauncher)?;
// The job should be terminated when the dash process dies.
job.set_critical(zx::JobCriticalOptions::empty(), &process)
.map_err(|_| LauncherError::Internal)?;
Ok(process)
}
fn split_pty_into_handles(
pty: ClientEnd<pty::DeviceMarker>,
) -> Result<(zx::Handle, zx::Handle, zx::Handle), LauncherError> {
let pty = pty.into_proxy().unwrap();
// Split the PTY into 3 channels (stdin, stdout, stderr).
let (stdout, to_pty_stdout) = fidl::endpoints::create_endpoints::<pty::DeviceMarker>();
let (stderr, to_pty_stderr) = fidl::endpoints::create_endpoints::<pty::DeviceMarker>();
let to_pty_stdout = to_pty_stdout.into_channel().into();
let to_pty_stderr = to_pty_stderr.into_channel().into();
// Clone the PTY to also be used for stdout and stderr.
pty.clone2(to_pty_stdout).map_err(|_| LauncherError::Pty)?;
pty.clone2(to_pty_stderr).map_err(|_| LauncherError::Pty)?;
let stdin = pty.into_channel().unwrap().into_zx_channel().into_handle();
let stdout = stdout.into_handle();
let stderr = stderr.into_handle();
Ok((stdin, stdout, stderr))
}
fn create_handle_infos(
job: &zx::Job,
stdin: zx::Handle,
stdout: zx::Handle,
stderr: zx::Handle,
) -> Result<Vec<fproc::HandleInfo>, LauncherError> {
let stdin_handle = fproc::HandleInfo {
handle: stdin.into_handle(),
id: HandleId::new(HandleType::FileDescriptor, 0).as_raw(),
};
let stdout_handle = fproc::HandleInfo {
handle: stdout.into_handle(),
id: HandleId::new(HandleType::FileDescriptor, 1).as_raw(),
};
let stderr_handle = fproc::HandleInfo {
handle: stderr.into_handle(),
id: HandleId::new(HandleType::FileDescriptor, 2).as_raw(),
};
let job_dup =
job.duplicate_handle(zx::Rights::SAME_RIGHTS).map_err(|_| LauncherError::Internal)?;
let job_handle = fproc::HandleInfo {
handle: zx::Handle::from(job_dup),
id: HandleId::new(HandleType::DefaultJob, 0).as_raw(),
};
let ldsvc = fuchsia_runtime::loader_svc().map_err(|_| LauncherError::Internal)?;
let ldsvc_handle =
fproc::HandleInfo { handle: ldsvc, id: HandleId::new(HandleType::LdsvcLoader, 0).as_raw() };
let utc_clock = {
let utc_clock = fuchsia_runtime::duplicate_utc_clock_handle(zx::Rights::SAME_RIGHTS)
.map_err(|_| LauncherError::Internal)?;
utc_clock.into_handle()
};
let utc_clock_handle = fproc::HandleInfo {
handle: utc_clock,
id: HandleId::new(HandleType::ClockUtc, 0).as_raw(),
};
Ok(vec![stdin_handle, stdout_handle, stderr_handle, job_handle, ldsvc_handle, utc_clock_handle])
}
async fn create_launch_info(
process_name: String,
job: &zx::Job,
) -> Result<fproc::LaunchInfo, LauncherError> {
// Load `/pkg/bin/sh` as an executable VMO and pass it to the Launcher.
let dash_file = fuchsia_fs::file::open_in_namespace(
"/pkg/bin/sh",
fio::OpenFlags::RIGHT_EXECUTABLE | fio::OpenFlags::RIGHT_READABLE,
)
.map_err(|_| LauncherError::DashBinary)?;
let executable = dash_file
.get_backing_memory(
fio::VmoFlags::READ | fio::VmoFlags::EXECUTE | fio::VmoFlags::PRIVATE_CLONE,
)
.await
.map_err(|_| LauncherError::DashBinary)?
.map_err(|_| LauncherError::DashBinary)?;
let job_dup =
job.duplicate_handle(zx::Rights::SAME_RIGHTS).map_err(|_| LauncherError::Internal)?;
let name = truncate_str(&process_name, zx::sys::ZX_MAX_NAME_LEN).to_owned();
Ok(fproc::LaunchInfo { name, job: job_dup, executable })
}
/// Truncates `s` to be at most `max_len` bytes.
fn truncate_str(s: &str, max_len: usize) -> &str {
if s.len() <= max_len {
return s;
}
// TODO(https://github.com/rust-lang/rust/issues/93743): Use floor_char_boundary when stable.
let mut index = max_len;
while index > 0 && !s.is_char_boundary(index) {
index -= 1;
}
&s[..index]
}