blob: 3bcc6c261e1e7f83f006e62ed324be4d26d84bc2 [file] [log] [blame]
// Copyright 2019 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 {
failure::{Error, Fail},
fidl::encoding::OutOfLine,
fidl_fuchsia_process as fproc,
fuchsia_runtime::{HandleInfo, HandleInfoError},
fuchsia_zircon::{self as zx, AsHandleRef},
futures::prelude::*,
log::warn,
process_builder::{
BuiltProcess, NamespaceEntry, ProcessBuilder, ProcessBuilderError, StartupHandle,
},
std::convert::TryFrom,
std::ffi::CString,
};
/// Internal error type for ProcessLauncherService which conveniently wraps errors that might
/// result during process launching and allows for mapping them to an equivalent zx::Status, which
/// is what actually gets returned through the protocol.
#[derive(Fail, Debug)]
enum LauncherError {
#[fail(display = "{}", _0)]
InvalidArg(&'static str),
#[fail(display = "{}", _0)]
BuilderError(#[cause] ProcessBuilderError),
#[fail(display = "{}", _0)]
HandleInfoError(#[cause] HandleInfoError),
}
impl LauncherError {
pub fn as_zx_status(&self) -> zx::Status {
match self {
LauncherError::InvalidArg(_) => zx::Status::INVALID_ARGS,
LauncherError::BuilderError(e) => e.as_zx_status(),
LauncherError::HandleInfoError(_) => zx::Status::INVALID_ARGS,
}
}
}
impl From<ProcessBuilderError> for LauncherError {
fn from(err: ProcessBuilderError) -> Self {
LauncherError::BuilderError(err)
}
}
impl From<HandleInfoError> for LauncherError {
fn from(err: HandleInfoError) -> Self {
LauncherError::HandleInfoError(err)
}
}
/// An implementation of the `fuchsia.process.Launcher protocol using the process_builder crate.
pub struct ProcessLauncherService;
#[derive(Default, Debug)]
struct ProcessLauncherState {
args: Vec<Vec<u8>>,
environ: Vec<Vec<u8>>,
name_info: Vec<fproc::NameInfo>,
handles: Vec<fproc::HandleInfo>,
}
impl ProcessLauncherService {
/// Serves an instance of the `fuchsia.process.Launcher` protocol given an appropriate
/// RequestStream. Returns when the channel backing the RequestStream is closed or an
/// unrecoverable error, like a failure to read from the stream, occurs.
pub async fn serve(mut stream: fproc::LauncherRequestStream) -> Result<(), Error> {
// `fuchsia.process.Launcher is stateful. The Add methods accumulate state that is
// consumed/reset by either Launch or CreateWithoutStarting.
let mut state = ProcessLauncherState::default();
while let Some(req) = await!(stream.try_next())? {
match req {
fproc::LauncherRequest::Launch { info, responder } => {
let job_koid = info.job.get_koid();
let name = info.name.clone();
match await!(Self::launch_process(info, state)) {
Ok(process) => {
responder.send(zx::Status::OK.into_raw(), Some(process))?;
}
Err(err) => {
warn!(
"Failed to launch process '{}' in job {}: {}",
name,
koid_to_string(job_koid),
err
);
responder.send(err.as_zx_status().into_raw(), None)?;
}
}
// Reset state to defaults.
state = ProcessLauncherState::default();
}
fproc::LauncherRequest::CreateWithoutStarting { info, responder } => {
let job_koid = info.job.get_koid();
let name = info.name.clone();
match await!(Self::create_process(info, state)) {
Ok(built) => {
let mut process_data = fproc::ProcessStartData {
process: built.process,
root_vmar: built.root_vmar,
thread: built.thread,
entry: built.entry as u64,
stack: built.stack as u64,
bootstrap: built.bootstrap,
vdso_base: built.vdso_base as u64,
base: built.elf_base as u64,
};
responder.send(
zx::Status::OK.into_raw(),
Some(OutOfLine(&mut process_data)),
)?;
}
Err(err) => {
warn!(
"Failed to create process '{}' in job {}: {}",
name,
koid_to_string(job_koid),
err
);
responder.send(err.as_zx_status().into_raw(), None)?;
}
}
// Reset state to defaults.
state = ProcessLauncherState::default();
}
fproc::LauncherRequest::AddArgs { mut args, control_handle: _ } => {
state.args.append(&mut args);
}
fproc::LauncherRequest::AddEnvirons { mut environ, control_handle: _ } => {
state.environ.append(&mut environ);
}
fproc::LauncherRequest::AddNames { mut names, control_handle: _ } => {
state.name_info.append(&mut names);
}
fproc::LauncherRequest::AddHandles { mut handles, control_handle: _ } => {
state.handles.append(&mut handles);
}
}
}
Ok(())
}
async fn launch_process(
info: fproc::LaunchInfo,
state: ProcessLauncherState,
) -> Result<zx::Process, LauncherError> {
Ok(await!(Self::create_process(info, state))?.start()?)
}
async fn create_process(
info: fproc::LaunchInfo,
state: ProcessLauncherState,
) -> Result<BuiltProcess, LauncherError> {
Ok(await!(Self::create_process_builder(info, state)?.build())?)
}
fn create_process_builder(
info: fproc::LaunchInfo,
state: ProcessLauncherState,
) -> Result<ProcessBuilder, LauncherError> {
let proc_name = CString::new(info.name)
.map_err(|_| LauncherError::InvalidArg("Process name contained null byte"))?;
let mut b = ProcessBuilder::new(&proc_name, &info.job, info.executable)?;
let arg_cstr = state
.args
.into_iter()
.map(|a| CString::new(a))
.collect::<Result<_, _>>()
.map_err(|_| LauncherError::InvalidArg("Argument contained null byte"))?;
b.add_arguments(arg_cstr);
let env_cstr = state
.environ
.into_iter()
.map(|e| CString::new(e))
.collect::<Result<_, _>>()
.map_err(|_| LauncherError::InvalidArg("Environment string contained null byte"))?;
b.add_environment_variables(env_cstr);
let entries = state
.name_info
.into_iter()
.map(|n| Self::new_namespace_entry(n))
.collect::<Result<_, _>>()?;
b.add_namespace_entries(entries)?;
// Note that clients of ``fuchsia.process.Launcher` provide the `fuchsia.ldsvc.Loader`
// through AddHandles, with a handle type of [HandleType::LdsvcLoader].
// [ProcessBuilder::add_handles] automatically handles that for convenience.
let handles = state
.handles
.into_iter()
.map(|h| Self::new_startup_handle(h))
.collect::<Result<_, _>>()?;
b.add_handles(handles)?;
Ok(b)
}
// Can't impl TryFrom for these because both types are from external crates. :(
// Could wrap in a newtype, but then have to unwrap, so this is simplest.
fn new_namespace_entry(info: fproc::NameInfo) -> Result<NamespaceEntry, LauncherError> {
let cstr = CString::new(info.path)
.map_err(|_| LauncherError::InvalidArg("Namespace path contained null byte"))?;
Ok(NamespaceEntry { path: cstr, directory: info.directory })
}
fn new_startup_handle(info: fproc::HandleInfo) -> Result<StartupHandle, LauncherError> {
Ok(StartupHandle { handle: info.handle, info: HandleInfo::try_from(info.id)? })
}
}
fn koid_to_string(koid: Result<zx::Koid, zx::Status>) -> String {
koid.map(|j| j.raw_koid().to_string()).unwrap_or("<unknown>".to_string())
}
// These tests are very similar to the tests in process_builder itself, and even reuse the test
// util from that, since the process_builder API is close to 1:1 with the process launcher service.
#[cfg(test)]
mod tests {
use {
super::*,
failure::ResultExt,
fidl::endpoints::{ClientEnd, Proxy, ServerEnd, ServiceMarker},
fidl_fuchsia_io as fio,
fidl_test_processbuilder::{UtilMarker, UtilProxy},
fuchsia_async as fasync,
fuchsia_runtime::{job_default, HandleType},
fuchsia_vfs_pseudo_fs::{
directory::entry::DirectoryEntry, file::simple::read_only, pseudo_directory,
},
fuchsia_zircon::HandleBased,
std::{fs::File, iter, mem, path::Path},
};
extern "C" {
fn dl_clone_loader_service(handle: *mut zx::sys::zx_handle_t) -> zx::sys::zx_status_t;
}
// Clone the current loader service to provide to the new test processes.
fn clone_loader_service() -> Result<zx::Handle, zx::Status> {
let mut raw = 0;
let status = unsafe { dl_clone_loader_service(&mut raw) };
zx::Status::ok(status)?;
let handle = unsafe { zx::Handle::from_raw(raw) };
Ok(handle)
}
// It is not possible to test this process launcher service, which uses the process_builder
// crate, except in an environment where zx_process_start is allowed (generally, when the test
// process runs in the root job). We return early from the tests otherwise.
fn expect_access_denied() -> bool {
// This is somewhat fragile but intentionally so, so that this will fail if the binary
// names change and get updated properly.
let bin = std::env::args().next();
match bin.as_ref().map(String::as_ref) {
Some("/pkg/test/component_manager_tests") => true,
Some("/pkg/test/component_manager_boot_env_tests") => false,
_ => panic!("Unexpected test binary name {:?}", bin),
}
}
fn serve_launcher() -> Result<fproc::LauncherProxy, Error> {
let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<fproc::LauncherMarker>()?;
fasync::spawn_local(
ProcessLauncherService::serve(stream)
.unwrap_or_else(|e| panic!("Error while serving process launcher: {}", e)),
);
Ok(proxy)
}
fn connect_util(client: &zx::Channel) -> Result<UtilProxy, Error> {
let (proxy, server) = zx::Channel::create()?;
fdio::service_connect_at(&client, UtilMarker::NAME, server)
.context("failed to connect to util service")?;
Ok(UtilProxy::from_channel(fasync::Channel::from_channel(proxy)?))
}
fn check_process_running(process: &zx::Process) -> Result<(), Error> {
let info = process.info()?;
assert_eq!(
info,
zx::ProcessInfo {
return_code: 0,
started: true,
exited: false,
debugger_attached: false
}
);
Ok(())
}
async fn check_process_exited_ok(process: &zx::Process) -> Result<(), Error> {
await!(fasync::OnSignals::new(process, zx::Signals::PROCESS_TERMINATED))?;
let info = process.info()?;
assert_eq!(
info,
zx::ProcessInfo {
return_code: 0,
started: true,
exited: true,
debugger_attached: false
}
);
Ok(())
}
// Common setup for all tests that start a test util process through the launcher service.
fn setup_test_util(
launcher: &fproc::LauncherProxy,
) -> Result<(fproc::LaunchInfo, UtilProxy), Error> {
const TEST_UTIL_BIN: &'static str = "/pkg/bin/process_builder_test_util";
let binpath = Path::new(TEST_UTIL_BIN);
let vmo = fdio::get_vmo_copy_from_file(&File::open(binpath)?)?.replace_as_executable()?;
let job = job_default();
let (dir_client, dir_server) = zx::Channel::create()?;
let mut handles = vec![
fproc::HandleInfo {
handle: dir_server.into_handle(),
id: HandleInfo::new(HandleType::DirectoryRequest, 0).as_raw(),
},
fproc::HandleInfo {
handle: clone_loader_service()?,
id: HandleInfo::new(HandleType::LdsvcLoader, 0).as_raw(),
},
];
launcher.add_handles(&mut handles.iter_mut())?;
let launch_info = fproc::LaunchInfo {
name: TEST_UTIL_BIN.to_owned(),
executable: vmo,
job: job.duplicate(zx::Rights::SAME_RIGHTS)?,
};
let util_proxy = connect_util(&dir_client)?;
Ok((launch_info, util_proxy))
}
#[fasync::run_singlethreaded(test)]
async fn start_util_with_args() -> Result<(), Error> {
let launcher = serve_launcher()?;
let (mut launch_info, proxy) = setup_test_util(&launcher)?;
let test_args = vec!["arg0", "arg1", "arg2"];
let mut test_args_iters: Vec<_> = test_args.iter().map(|s| s.bytes()).collect();
let mut test_args_iters =
test_args_iters.iter_mut().map(|iter| iter as &mut dyn ExactSizeIterator<Item = u8>);
launcher.add_args(&mut test_args_iters)?;
let (status, process) = await!(launcher.launch(&mut launch_info))?;
if expect_access_denied() {
assert_eq!(zx::Status::from_raw(status), zx::Status::ACCESS_DENIED);
return Ok(());
}
zx::Status::ok(status).context("Failed to launch test util process")?;
let process = process.expect("Status was OK but no process returned");
check_process_running(&process)?;
let proc_args = await!(proxy.get_arguments()).context("failed to get args from util")?;
assert_eq!(proc_args, test_args);
mem::drop(proxy);
await!(check_process_exited_ok(&process))?;
Ok(())
}
#[fasync::run_singlethreaded(test)]
async fn start_util_with_env() -> Result<(), Error> {
let launcher = serve_launcher()?;
let (mut launch_info, proxy) = setup_test_util(&launcher)?;
let test_env = vec![("VAR1", "value2"), ("VAR2", "value2")];
let test_env_strs: Vec<_> = test_env.iter().map(|v| format!("{}={}", v.0, v.1)).collect();
let mut test_env_iters: Vec<_> = test_env_strs.iter().map(|s| s.bytes()).collect();
let mut test_env_iters =
test_env_iters.iter_mut().map(|iter| iter as &mut dyn ExactSizeIterator<Item = u8>);
launcher.add_environs(&mut test_env_iters)?;
let (status, process) = await!(launcher.launch(&mut launch_info))?;
if expect_access_denied() {
assert_eq!(zx::Status::from_raw(status), zx::Status::ACCESS_DENIED);
return Ok(());
}
zx::Status::ok(status).context("Failed to launch test util process")?;
let process = process.expect("Status was OK but no process returned");
check_process_running(&process)?;
let proc_env = await!(proxy.get_environment()).context("failed to get env from util")?;
let proc_env_tuple: Vec<(&str, &str)> =
proc_env.iter().map(|v| (&*v.key, &*v.value)).collect();
assert_eq!(proc_env_tuple, test_env);
Ok(())
}
#[fasync::run_singlethreaded(test)]
async fn start_util_with_namespace_entries() -> Result<(), Error> {
let launcher = serve_launcher()?;
let (mut launch_info, proxy) = setup_test_util(&launcher)?;
let mut randbuf = [0; 8];
zx::cprng_draw(&mut randbuf)?;
let test_content = format!("test content {}", u64::from_le_bytes(randbuf));
let test_content_bytes = test_content.clone().into_bytes();
let (dir_server, dir_client) = zx::Channel::create()?;
fasync::spawn(async move {
let mut dir = pseudo_directory! {
"test_file" => read_only(|| Ok(test_content_bytes.clone())),
};
dir.open(
fio::OPEN_RIGHT_READABLE | fio::OPEN_RIGHT_WRITABLE,
fio::MODE_TYPE_DIRECTORY,
&mut iter::empty(),
ServerEnd::new(dir_server),
);
await!(dir);
panic!("Pseudo dir stopped serving!");
});
let mut name_infos = vec![fproc::NameInfo {
path: "/dir".to_string(),
directory: ClientEnd::new(dir_client),
}];
launcher.add_names(&mut name_infos.iter_mut())?;
let (status, process) = await!(launcher.launch(&mut launch_info))?;
if expect_access_denied() {
assert_eq!(zx::Status::from_raw(status), zx::Status::ACCESS_DENIED);
return Ok(());
}
zx::Status::ok(status).context("Failed to launch test util process")?;
let process = process.expect("Status was OK but no process returned");
check_process_running(&process)?;
let namespace_dump = await!(proxy.dump_namespace()).context("failed to dump namespace")?;
assert_eq!(namespace_dump, "/dir, /dir/test_file");
let dir_contents =
await!(proxy.read_file("/dir/test_file")).context("failed to read file via util")?;
assert_eq!(dir_contents, test_content);
Ok(())
}
#[fasync::run_singlethreaded(test)]
async fn create_without_starting() -> Result<(), Error> {
let launcher = serve_launcher()?;
let (mut launch_info, proxy) = setup_test_util(&launcher)?;
let test_args = vec!["arg0", "arg1", "arg2"];
let mut test_args_iters: Vec<_> = test_args.iter().map(|s| s.bytes()).collect();
let mut test_args_iters =
test_args_iters.iter_mut().map(|iter| iter as &mut dyn ExactSizeIterator<Item = u8>);
launcher.add_args(&mut test_args_iters)?;
let (status, start_data) = await!(launcher.create_without_starting(&mut launch_info))?;
if expect_access_denied() {
assert_eq!(zx::Status::from_raw(status), zx::Status::ACCESS_DENIED);
return Ok(());
}
zx::Status::ok(status).context("Failed to launch test util process")?;
let start_data = start_data.expect("Status was OK but no ProcessStartData returned");
// Process should exist & be valid but not yet be running.
let info = start_data.process.info()?;
assert_eq!(
info,
zx::ProcessInfo {
return_code: 0,
started: false,
exited: false,
debugger_attached: false
}
);
// Start the process manually using the info from ProcessStartData.
start_data
.process
.start(
&start_data.thread,
start_data.entry as usize,
start_data.stack as usize,
start_data.bootstrap.into_handle(),
start_data.vdso_base as usize,
)
.context("Failed to start process from ProcessStartData")?;
check_process_running(&start_data.process)?;
let proc_args = await!(proxy.get_arguments()).context("failed to get args from util")?;
assert_eq!(proc_args, test_args);
mem::drop(proxy);
await!(check_process_exited_ok(&start_data.process))?;
Ok(())
}
}