blob: ce8bac2cfca3bc9e7d767ee1906f0c974e6053ff [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 {
crate::{
constants::PKG_PATH,
model::{
component::{ComponentInstance, Package, WeakComponentInstance},
routing::router_ext::{RouterExt, WeakComponentTokenExt},
routing::{self, route_and_open_capability},
},
sandbox_util::DictExt,
},
::routing::{
component_instance::ComponentInstanceInterface, mapper::NoopRouteMapper, rights::Rights,
route_to_storage_decl, verify_instance_in_component_id_index, RouteRequest,
},
bedrock_error::{BedrockError, Explain},
cm_rust::{ComponentDecl, UseDecl, UseEventStreamDecl, UseStorageDecl},
errors::CreateNamespaceError,
fidl::{endpoints::ClientEnd, prelude::*},
fidl_fuchsia_io as fio, fuchsia_zircon as zx,
futures::{
channel::mpsc::{unbounded, UnboundedSender},
FutureExt, StreamExt,
},
sandbox::{Capability, Dict, Directory, Open, Request, WeakComponentToken},
serve_processargs::NamespaceBuilder,
std::{collections::HashSet, sync::Arc},
tracing::{error, warn},
vfs::{
directory::entry::{
serve_directory, DirectoryEntry, DirectoryEntryAsync, EntryInfo, OpenRequest,
},
execution_scope::ExecutionScope,
},
};
/// Creates a component's namespace.
///
/// TODO(b/298106231): eventually this should only build a delivery map as
/// the program dict will be fetched from the resolved component state.
pub async fn create_namespace(
package: Option<&Package>,
component: &Arc<ComponentInstance>,
decl: &ComponentDecl,
program_input_dict: &Dict,
scope: ExecutionScope,
) -> Result<NamespaceBuilder, CreateNamespaceError> {
let not_found_sender = not_found_logging(component);
let mut namespace = NamespaceBuilder::new(scope.clone(), not_found_sender);
if let Some(package) = package {
let pkg_dir = fuchsia_fs::directory::clone_no_describe(&package.package_dir, None)
.map_err(CreateNamespaceError::ClonePkgDirFailed)?;
add_pkg_directory(&mut namespace, pkg_dir)?;
}
let uses = deduplicate_event_stream(decl.uses.iter());
add_use_decls(&mut namespace, component, uses, program_input_dict).await?;
Ok(namespace)
}
/// This function transforms a sequence of [`UseDecl`] such that the duplicate event stream
/// uses by paths are removed.
///
/// Different from all other use declarations, multiple event stream capabilities may be used
/// at the same path, the semantics being a single FIDL protocol capability is made available
/// at that path, subscribing to all the specified events:
/// see [`crate::model::events::registry::EventRegistry`].
fn deduplicate_event_stream<'a>(
iter: std::slice::Iter<'a, UseDecl>,
) -> impl Iterator<Item = &'a UseDecl> {
let mut paths = HashSet::new();
iter.filter_map(move |use_decl| match use_decl {
UseDecl::EventStream(ref event_stream) => {
if !paths.insert(event_stream.target_path.clone()) {
None
} else {
Some(use_decl)
}
}
_ => Some(use_decl),
})
}
/// Adds the package directory to the namespace under the path "/pkg".
fn add_pkg_directory(
namespace: &mut NamespaceBuilder,
pkg_dir: fio::DirectoryProxy,
) -> Result<(), CreateNamespaceError> {
// TODO(https://fxbug.dev/42060182): Use Proxy::into_client_end when available.
let client_end = ClientEnd::new(pkg_dir.into_channel().unwrap().into_zx_channel());
let directory: sandbox::Directory = client_end.into();
let path = cm_types::NamespacePath::new(PKG_PATH.to_str().unwrap()).unwrap();
namespace.add_entry(Capability::Directory(directory), &path)?;
Ok(())
}
/// Adds namespace entries for a component's use declarations.
async fn add_use_decls(
namespace: &mut NamespaceBuilder,
component: &Arc<ComponentInstance>,
uses: impl Iterator<Item = &UseDecl>,
program_input_dict: &Dict,
) -> Result<(), CreateNamespaceError> {
for use_ in uses {
if let cm_rust::UseDecl::Runner(_) = use_ {
// The runner is not available in the namespace.
continue;
}
if let cm_rust::UseDecl::Config(_) = use_ {
// Configuration is not available in the namespace.
continue;
}
let target_path =
use_.path().ok_or_else(|| CreateNamespaceError::UseDeclWithoutPath(use_.clone()))?;
let capability: Capability = match use_ {
cm_rust::UseDecl::Directory(_) => directory_use(&use_, component).into(),
cm_rust::UseDecl::Storage(storage) => {
storage_use(storage, use_, component).await?.into()
}
cm_rust::UseDecl::Protocol(s) => {
service_or_protocol_use(UseDecl::Protocol(s.clone()), component, program_input_dict)
.into()
}
cm_rust::UseDecl::Service(s) => {
service_or_protocol_use(UseDecl::Service(s.clone()), component, program_input_dict)
.into()
}
cm_rust::UseDecl::EventStream(s) => service_or_protocol_use(
UseDecl::EventStream(s.clone()),
component,
program_input_dict,
)
.into(),
cm_rust::UseDecl::Runner(_) => {
std::process::abort();
}
cm_rust::UseDecl::Config(_) => {
std::process::abort();
}
};
match use_ {
cm_rust::UseDecl::Directory(_) | cm_rust::UseDecl::Storage(_) => {
namespace.add_entry(capability, &target_path.clone().into())
}
cm_rust::UseDecl::Protocol(_)
| cm_rust::UseDecl::Service(_)
| cm_rust::UseDecl::EventStream(_) => namespace.add_object(capability, target_path),
cm_rust::UseDecl::Runner(_) => {
std::process::abort();
}
cm_rust::UseDecl::Config(_) => {
std::process::abort();
}
}?
}
Ok(())
}
/// Makes a capability representing the storage described by `use_decl`. Once the channel
/// is readable, the future calls `route_storage` to forward the channel to the source
/// component's outgoing directory and terminates.
async fn storage_use(
use_storage_decl: &UseStorageDecl,
use_decl: &UseDecl,
component: &Arc<ComponentInstance>,
) -> Result<Directory, CreateNamespaceError> {
// Prevent component from using storage capability if it is restricted to the component ID
// index, and the component isn't in the index.
// To check that the storage capability is restricted to the storage decl, we have
// to resolve the storage source capability. Because storage capabilities are only
// ever `offer`d down the component tree, and we always resolve parents before
// children, this resolution will walk the cache-happy path.
// TODO(dgonyeo): Eventually combine this logic with the general-purpose startup
// capability check.
if let Ok(source) =
route_to_storage_decl(use_storage_decl.clone(), &component, &mut NoopRouteMapper).await
{
verify_instance_in_component_id_index(&source, &component)
.map_err(CreateNamespaceError::InstanceNotInInstanceIdIndex)?;
}
Ok(directory_use(use_decl, component))
}
/// Makes a capability representing the directory described by `use_`. Once the
/// channel is readable, the future calls `route_directory` to forward the channel to the
/// source component's outgoing directory and terminates.
///
/// `component` is a weak pointer, which is important because we don't want the task
/// waiting for channel readability to hold a strong pointer to this component lest it
/// create a reference cycle.
fn directory_use(use_: &UseDecl, component: &Arc<ComponentInstance>) -> Directory {
let flags = match use_ {
UseDecl::Directory(dir) => Rights::from(dir.rights).into_legacy(),
UseDecl::Storage(_) => fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE,
_ => panic!("not a directory or storage capability"),
};
// Specify that the capability must be opened as a directory. In particular, this affects
// how a devfs-based capability will handle the open call. If this flag is not specified,
// devfs attempts to open the directory as a service, which is not what is desired here.
let flags = flags | fio::OpenFlags::DIRECTORY;
struct RouteDirectory {
component: WeakComponentInstance,
use_decl: UseDecl,
}
impl DirectoryEntry for RouteDirectory {
fn entry_info(&self) -> EntryInfo {
EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)
}
fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), zx::Status> {
request.spawn(self);
Ok(())
}
}
impl DirectoryEntryAsync for RouteDirectory {
async fn open_entry_async(
self: Arc<Self>,
request: OpenRequest<'_>,
) -> Result<(), zx::Status> {
if request.path().is_empty() {
if !request.wait_till_ready().await {
return Ok(());
}
}
// Hold a guard to prevent this task from being dropped during component destruction.
let _guard = request.scope().active_guard();
let target = match self.component.upgrade() {
Ok(component) => component,
Err(e) => {
error!(
"failed to upgrade WeakComponentInstance routing use \
decl `{:?}`: {:?}",
&self.use_decl, e
);
return Err(e.as_zx_status());
}
};
route_directory(target, self.use_decl.clone(), request)
.await
.map_err(|e| e.as_zx_status())
}
}
// Serve this directory on the component's execution scope rather than the namespace execution
// scope so that requests don't block block namespace teardown, but they will block component
// destruction.
sandbox::Directory::new(
serve_directory(
Arc::new(RouteDirectory { component: component.as_weak(), use_decl: use_.clone() }),
&component.execution_scope.clone(),
flags,
)
.unwrap(),
)
}
async fn route_directory(
target: Arc<ComponentInstance>,
use_: UseDecl,
open_request: OpenRequest<'_>,
) -> Result<(), BedrockError> {
let (route_request, open_request) = match &use_ {
UseDecl::Directory(use_dir_decl) => {
(RouteRequest::UseDirectory(use_dir_decl.clone()), open_request)
}
UseDecl::Storage(use_storage_decl) => {
(RouteRequest::UseStorage(use_storage_decl.clone()), open_request)
}
_ => panic!("not a directory or storage capability"),
};
if let Err(e) = route_and_open_capability(&route_request, &target, open_request).await {
routing::report_routing_failure(&route_request, &target, &e).await;
Err(e)
} else {
Ok(())
}
}
/// Makes a capability for the service/protocol described by `use_`. The service will be
/// proxied to the outgoing directory of the source component.
///
/// `component` is a weak pointer, which is important because we don't want the VFS
/// closure to hold a strong pointer to this component lest it create a reference cycle.
fn service_or_protocol_use(
use_: UseDecl,
component: &Arc<ComponentInstance>,
program_input_dict: &Dict,
) -> Open {
match use_ {
// Bedrock routing.
UseDecl::Protocol(use_protocol_decl) => {
let request = Request {
availability: use_protocol_decl.availability.clone(),
target: WeakComponentToken::new(component.as_weak()),
};
let Some(capability) =
program_input_dict.get_capability(&use_protocol_decl.target_path)
else {
panic!(
"router for capability {:?} is missing from program input dictionary for \
component {}",
use_protocol_decl.target_path, component.moniker
);
};
let Capability::Router(router) = &capability else {
panic!(
"program input dictionary for component {} had an entry with an unexpected \
type: {:?}",
component.moniker, capability
);
};
let router = router.clone();
let legacy_request = RouteRequest::UseProtocol(use_protocol_decl.clone());
// When there are router errors, they are sent to the error handler, which reports
// errors.
let weak_component = component.as_weak();
Open::new(router.into_directory_entry(
request,
fio::DirentType::Service,
component.execution_scope.clone(),
move |error: &BedrockError| {
let Ok(target) = weak_component.upgrade() else {
return None;
};
let legacy_request = legacy_request.clone();
Some(
async move {
routing::report_routing_failure(&legacy_request, &target, error).await
}
.boxed(),
)
},
))
}
// Legacy routing.
UseDecl::Service(use_service_decl) => {
struct Service {
component: WeakComponentInstance,
scope: ExecutionScope,
use_service_decl: cm_rust::UseServiceDecl,
}
impl DirectoryEntry for Service {
fn entry_info(&self) -> EntryInfo {
EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)
}
fn open_entry(
self: Arc<Self>,
mut request: OpenRequest<'_>,
) -> Result<(), zx::Status> {
// Move this request from the namespace scope to the component's scope so that
// we don't block namespace teardown.
request.set_scope(self.scope.clone());
request.spawn(self);
Ok(())
}
}
impl DirectoryEntryAsync for Service {
async fn open_entry_async(
self: Arc<Self>,
request: OpenRequest<'_>,
) -> Result<(), zx::Status> {
if request.path().is_empty() {
if !request.wait_till_ready().await {
return Ok(());
}
}
let component = match self.component.upgrade() {
Ok(component) => component,
Err(e) => {
error!(
"failed to upgrade WeakComponentInstance routing use \
decl `{:?}`: {:?}",
self.use_service_decl, e
);
return Err(e.as_zx_status());
}
};
// Hold a guard to prevent this task from being dropped during component
// destruction.
let _guard = request.scope().active_guard();
let route_request = RouteRequest::UseService(self.use_service_decl.clone());
if let Err(e) =
routing::route_and_open_capability(&route_request, &component, request)
.await
{
routing::report_routing_failure(&route_request, &component, &e).await;
Err(e.as_zx_status())
} else {
Ok(())
}
}
}
Open::new(Arc::new(Service {
component: component.as_weak(),
scope: component.execution_scope.clone(),
use_service_decl,
}))
}
UseDecl::EventStream(stream) => {
struct UseEventStream {
component: WeakComponentInstance,
stream: UseEventStreamDecl,
}
impl DirectoryEntry for UseEventStream {
fn entry_info(&self) -> EntryInfo {
EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Service)
}
fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), zx::Status> {
if !request.path().is_empty() {
return Err(zx::Status::NOT_DIR);
}
request.spawn(self);
Ok(())
}
}
impl DirectoryEntryAsync for UseEventStream {
async fn open_entry_async(
self: Arc<Self>,
mut request: OpenRequest<'_>,
) -> Result<(), zx::Status> {
let component = match self.component.upgrade() {
Ok(component) => component,
Err(e) => {
error!(
"failed to upgrade WeakComponentInstance routing use \
decl `{:?}`: {:?}",
self.stream, e
);
return Err(e.as_zx_status());
}
};
request.prepend_path(&self.stream.target_path.to_string().try_into()?);
let route_request = RouteRequest::UseEventStream(self.stream.clone());
if let Err(e) =
routing::route_and_open_capability(&route_request, &component, request)
.await
{
routing::report_routing_failure(&route_request, &component, &e).await;
Err(e.as_zx_status())
} else {
Ok(())
}
}
}
Open::new(Arc::new(UseEventStream { component: component.as_weak(), stream }))
}
_ => panic!("add_service_or_protocol_use called with non-service or protocol capability"),
}
}
fn not_found_logging(component: &Arc<ComponentInstance>) -> UnboundedSender<String> {
let (sender, mut receiver) = unbounded();
let component_for_logger: WeakComponentInstance = component.as_weak();
component.nonblocking_task_group().spawn(async move {
while let Some(path) = receiver.next().await {
match component_for_logger.upgrade() {
Ok(target) => {
target
.with_logger_as_default(|| {
warn!(
"No capability available at path {} for component {}, \
verify the component has the proper `use` declaration.",
path, target.moniker
);
})
.await;
}
Err(_) => {}
}
}
});
sender
}