blob: ec057e0f65d056af69714e26283ecb7ebc70f203 [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::device::run_component_features;
use ::runner::{get_program_string, get_program_strvec};
use anyhow::{anyhow, format_err, Context, Error};
use fidl::endpoints::ServerEnd;
use fidl_fuchsia_component as fcomponent;
use fidl_fuchsia_component_decl as fdecl;
use fidl_fuchsia_component_runner::{ComponentControllerMarker, ComponentStartInfo};
use fidl_fuchsia_io as fio;
use fuchsia_component::client as fclient;
use fuchsia_zircon as zx;
use rand::Rng;
use std::ffi::CString;
use tracing::info;
use crate::auth::Credentials;
use crate::execution::{
create_filesystem_from_spec, create_galaxy, execute_task, galaxy::Galaxy,
parse_numbered_handles,
};
use crate::task::*;
use crate::types::*;
/// Starts a component in an isolated environment, called a "galaxy".
///
/// The galaxy will be configured according to a configuration file in the Starnix runner's package.
/// The configuration file specifies, for example, which binary to run as "init", whether or not the
/// system should wait for the existence of a given file path to run the component, etc.
///
/// The Starnix runner's package also contains the system image to mount.
///
/// The component's `binary` can either:
/// - an absolute path, in which case the path is treated as a path into the root filesystem that
/// is mounted by the galaxy's configuration
/// - relative path, in which case the binary is read from the component's package (which is
/// mounted at /data/pkg.)
pub async fn start_component(
mut start_info: ComponentStartInfo,
controller: ServerEnd<ComponentControllerMarker>,
) -> Result<(), Error> {
let galaxy = create_galaxy().await?;
let url = start_info.resolved_url.clone().unwrap_or("<unknown>".to_string());
info!(
"start_component: {}\narguments: {:?}\nmanifest: {:?}",
url.clone(),
start_info.numbered_handles,
start_info.program,
);
let args = get_program_strvec(&start_info, "args")
.map(|args| {
args.iter().map(|arg| CString::new(arg.clone())).collect::<Result<Vec<CString>, _>>()
})
.unwrap_or(Ok(vec![]))?;
let environ = get_program_strvec(&start_info, "environ")
.map(|args| {
args.iter().map(|arg| CString::new(arg.clone())).collect::<Result<Vec<CString>, _>>()
})
.unwrap_or(Ok(vec![]))?;
info!("start_component environment: {:?}", environ);
const COMPONENT_PKG_DIRECTORY: &str = "/data/pkg/";
let binary_path = get_program_string(&start_info, "binary")
.ok_or_else(|| anyhow!("Missing \"binary\" in manifest"))?;
let binary_in_package = &binary_path[..1] != "/";
let binary_path = CString::new(if binary_in_package {
// If the binary path is relative, treat it as a path into the component's package
// directory.
COMPONENT_PKG_DIRECTORY.to_owned() + binary_path
} else {
// If the binary path is absolute, treat it as a path to an existing binary.
binary_path.to_owned()
})?;
let mut current_task = Task::create_process_without_parent(
&galaxy.kernel,
binary_path.clone(),
galaxy.root_fs.clone(),
)?;
let user_passwd = get_program_string(&start_info, "user").unwrap_or("fuchsia:x:42:42");
let credentials = Credentials::from_passwd(user_passwd)?;
current_task.write().creds = credentials;
let ns = start_info.ns.take().ok_or_else(|| anyhow!("Missing namespace"))?;
let pkg = fio::DirectorySynchronousProxy::new(
ns.into_iter()
.find(|entry| entry.path == Some("/pkg".to_string()))
.ok_or_else(|| anyhow!("Missing /pkg entry in namespace"))?
.directory
.ok_or_else(|| anyhow!("Missing directory handlee in pkg namespace entry"))?
.into_channel(),
);
if binary_in_package {
// If the component's binary path point inside the package, mount the package directory.
mount_component_pkg_data(&current_task, &galaxy, &COMPONENT_PKG_DIRECTORY, &pkg)?;
}
let startup_handles =
parse_numbered_handles(start_info.numbered_handles, &current_task.files, &galaxy.kernel)?;
let shell_controller = startup_handles.shell_controller;
let mut argv = vec![binary_path];
argv.extend(args.into_iter());
current_task.exec(argv[0].clone(), argv.clone(), environ.clone())?;
// run per-component features
// TODO(fxb/100316) - we should examine start_info to determine which features are needed for this component.
run_component_features(&galaxy.kernel.features, &current_task, &mut start_info.outgoing_dir)
.unwrap_or_else(|e| {
tracing::error!("failed to set component features for {} - {:?}", url, e);
});
execute_task(current_task, |result| {
// TODO(fxb/74803): Using the component controller's epitaph may not be the best way to
// communicate the exit status. The component manager could interpret certain epitaphs as starnix
// being unstable, and chose to terminate starnix as a result.
// Errors when closing the controller with an epitaph are disregarded, since there are
// legitimate reasons for this to fail (like the client having closed the channel).
if let Some(shell_controller) = shell_controller {
let _ = shell_controller.close_with_epitaph(zx::Status::OK);
}
let _ = match result {
Ok(ExitStatus::Exit(0)) => controller.close_with_epitaph(zx::Status::OK),
_ => controller.close_with_epitaph(zx::Status::from_raw(
fcomponent::Error::InstanceDied.into_primitive() as i32,
)),
};
});
Ok(())
}
/// Attempts to mount the component's package directory at `package_directory_path` in the task's
/// filesystem. This allows components to bundle their own binary in their package, instead of
/// relying on it existing in the system image of the galaxy.
fn mount_component_pkg_data(
current_task: &CurrentTask,
galaxy: &Galaxy,
package_directory_path: &str,
package_proxy: &fio::DirectorySynchronousProxy,
) -> Result<(), Error> {
let remotefs_mount_point = package_directory_path.to_owned() + ":remotefs";
let (mount_point, child_fs) = create_filesystem_from_spec(
&galaxy.kernel,
Some(&current_task),
&package_proxy,
&remotefs_mount_point,
)?;
let mount_point = current_task.lookup_path_from_root(mount_point)?;
mount_point.mount(child_fs, MountFlags::empty())?;
Ok(())
}
/// Creates a new child component in the `playground` collection.
///
/// # Parameters
/// - `url`: The URL of the component to create.
/// - `args`: The `CreateChildArgs` that are passed to the component manager.
pub async fn create_child_component(
url: String,
args: fcomponent::CreateChildArgs,
) -> Result<(), Error> {
// TODO(fxbug.dev/74511): The amount of setup required here is a bit lengthy. Ideally,
// fuchsia-component would provide language-specific bindings for the Realm API that could
// reduce this logic to a few lines.
const COLLECTION: &str = "playground";
let realm = fclient::realm().context("failed to connect to Realm service")?;
let mut collection_ref = fdecl::CollectionRef { name: COLLECTION.into() };
let id: u64 = rand::thread_rng().gen();
let child_name = format!("starnix-{}", id);
let child_decl = fdecl::Child {
name: Some(child_name.clone()),
url: Some(url),
startup: Some(fdecl::StartupMode::Lazy),
environment: None,
..fdecl::Child::EMPTY
};
let () = realm
.create_child(&mut collection_ref, child_decl, args)
.await?
.map_err(|e| format_err!("failed to create child: {:?}", e))?;
// The component is run in a `SingleRun` collection instance, and will be automatically
// deleted when it exits.
Ok(())
}