blob: 5db473c068811d5d6148d9f01583fd881ff2725b [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 {
crate::{
capability::{CapabilityProvider, CapabilitySource},
model::{
component::{ComponentInstance, ExtendedInstance, StartReason, WeakComponentInstance},
routing::{
providers::{
DefaultComponentCapabilityProvider, DirectoryEntryCapabilityProvider,
NamespaceCapabilityProvider,
},
service::{
AnonymizedAggregateServiceDir, AnonymizedServiceRoute,
FilteredAggregateServiceProvider,
},
RouteSource,
},
start::Start,
storage::{self, BackingDirectoryInfo},
},
},
::routing::component_instance::ComponentInstanceInterface,
cm_moniker::InstancedExtendedMoniker,
errors::{CapabilityProviderError, ModelError, OpenError},
fidl_fuchsia_io as fio,
moniker::MonikerBase,
std::sync::Arc,
vfs::{directory::entry::OpenRequest, remote::remote_dir},
};
/// A request to open a capability at its source.
pub enum CapabilityOpenRequest<'a> {
// Open a capability backed by a component's outgoing directory.
OutgoingDirectory {
open_request: OpenRequest<'a>,
source: CapabilitySource,
target: &'a Arc<ComponentInstance>,
},
// Open a storage capability.
Storage {
open_request: OpenRequest<'a>,
source: storage::BackingDirectoryInfo,
target: &'a Arc<ComponentInstance>,
},
}
impl<'a> CapabilityOpenRequest<'a> {
/// Creates a request to open a capability with source `route_source` for `target`.
pub fn new_from_route_source(
route_source: RouteSource,
target: &'a Arc<ComponentInstance>,
mut open_request: OpenRequest<'a>,
) -> Result<Self, ModelError> {
let RouteSource { source, relative_path } = route_source;
if !relative_path.is_dot() {
open_request.prepend_path(
&relative_path.to_string().try_into().map_err(|_| ModelError::BadPath)?,
);
}
Ok(Self::OutgoingDirectory { open_request, source, target })
}
/// Creates a request to open a storage capability with source `storage_source` for `target`.
pub fn new_from_storage_source(
source: BackingDirectoryInfo,
target: &'a Arc<ComponentInstance>,
open_request: OpenRequest<'a>,
) -> Self {
Self::Storage { open_request, source, target }
}
/// Opens the capability in `self`, triggering a `CapabilityRouted` event and binding
/// to the source component instance if necessary.
pub async fn open(self) -> Result<(), OpenError> {
match self {
Self::OutgoingDirectory { open_request, source, target } => {
Self::open_outgoing_directory(open_request, source, target).await
}
Self::Storage { open_request, source, target } => {
Self::open_storage(open_request, &source, target)
.await
.map_err(|e| OpenError::OpenStorageError { err: Box::new(e) })
}
}
}
async fn open_outgoing_directory(
mut open_request: OpenRequest<'a>,
source: CapabilitySource,
target: &Arc<ComponentInstance>,
) -> Result<(), OpenError> {
let capability_provider = if let Some(provider) =
Self::get_default_provider(target.as_weak(), &source)
.await
.map_err(|e| OpenError::GetDefaultProviderError { err: Box::new(e) })?
{
provider
} else {
target
.context
.find_internal_provider(&source, target.as_weak())
.await
.ok_or(OpenError::CapabilityProviderNotFound)?
};
let source_instance =
source.source_instance().upgrade().map_err(CapabilityProviderError::from)?;
let task_group = match source_instance {
ExtendedInstance::AboveRoot(top) => top.task_group(),
ExtendedInstance::Component(component) => {
open_request.set_scope(component.execution_scope.clone());
component.nonblocking_task_group()
}
};
capability_provider.open(task_group, open_request).await?;
Ok(())
}
async fn open_storage(
open_request: OpenRequest<'a>,
source: &storage::BackingDirectoryInfo,
target: &Arc<ComponentInstance>,
) -> Result<(), ModelError> {
// As of today, the storage component instance must contain the target. This is because it
// is impossible to expose storage declarations up.
let moniker =
target.instanced_moniker().strip_prefix(&source.storage_source_moniker).unwrap();
let dir_source = source.storage_provider.clone();
let storage_dir_proxy = storage::open_isolated_storage(
&source,
target.persistent_storage,
moniker.clone(),
target.instance_id(),
)
.await
.map_err(|e| ModelError::from(e))?;
open_request.open_remote(remote_dir(storage_dir_proxy)).map_err(|err| {
let source_moniker = match &dir_source {
Some(r) => {
InstancedExtendedMoniker::ComponentInstance(r.instanced_moniker().clone())
}
None => InstancedExtendedMoniker::ComponentManager,
};
ModelError::OpenStorageFailed { source_moniker, moniker, path: String::new(), err }
})?;
Ok(())
}
/// Returns an instance of the default capability provider for the capability at `source`, if
/// supported.
async fn get_default_provider(
target: WeakComponentInstance,
source: &CapabilitySource,
) -> Result<Option<Box<dyn CapabilityProvider>>, ModelError> {
match source {
CapabilitySource::Component { capability, component } => {
// Route normally for a component capability with a source path
Ok(match capability.source_path() {
Some(_) => Some(Box::new(DefaultComponentCapabilityProvider::new(
target,
component.clone(),
capability
.source_name()
.expect("capability with source path should have a name")
.clone(),
))),
_ => None,
})
}
CapabilitySource::Namespace { capability, .. } => match capability.source_path() {
Some(path) => Ok(Some(Box::new(NamespaceCapabilityProvider {
path: path.clone(),
is_directory_like: fio::DirentType::from(capability.type_name())
== fio::DirentType::Directory,
}))),
_ => Ok(None),
},
CapabilitySource::FilteredAggregate { capability_provider, component, .. } => {
// TODO(https://fxbug.dev/42124541): This should cache the directory
Ok(Some(Box::new(
FilteredAggregateServiceProvider::new(
component.clone(),
target,
capability_provider.clone(),
)
.await?,
)))
}
CapabilitySource::AnonymizedAggregate {
capability,
component,
aggregate_capability_provider,
members,
} => {
let source_component_instance = component.upgrade()?;
let route = AnonymizedServiceRoute {
source_moniker: source_component_instance.moniker.clone(),
members: members.clone(),
service_name: capability.source_name().clone(),
};
source_component_instance
.ensure_started(&StartReason::AccessCapability {
target: target.moniker.clone(),
name: capability.source_name().clone(),
})
.await?;
// If there is an existing collection service directory, provide it.
{
let state = source_component_instance.lock_resolved_state().await?;
if let Some(service_dir) = state.anonymized_services.get(&route) {
let provider = DirectoryEntryCapabilityProvider {
entry: service_dir.dir_entry().await,
};
return Ok(Some(Box::new(provider)));
}
}
// Otherwise, create one. This must be done while the component ResolvedInstanceState
// is unlocked because the AggregateCapabilityProvider uses locked state.
let service_dir = Arc::new(AnonymizedAggregateServiceDir::new(
component.clone(),
route.clone(),
aggregate_capability_provider.clone_boxed(),
));
source_component_instance.hooks.install(service_dir.hooks()).await;
let provider = {
let mut state = source_component_instance.lock_resolved_state().await?;
let entry = service_dir.dir_entry().await;
state.anonymized_services.insert(route, service_dir.clone());
DirectoryEntryCapabilityProvider { entry }
};
// Populate the service dir with service entries from children that may have been started before the service
// capability had been routed from the collection.
service_dir.add_entries_from_children().await?;
Ok(Some(Box::new(provider)))
}
// These capabilities do not have a default provider.
CapabilitySource::Framework { .. }
| CapabilitySource::Void { .. }
| CapabilitySource::Capability { .. }
| CapabilitySource::Builtin { .. }
| CapabilitySource::Environment { .. } => Ok(None),
}
}
}