| // 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. |
| |
| pub mod error; |
| pub use error::RoutingError; |
| |
| use { |
| crate::{ |
| capability::{ |
| CapabilityProvider, CapabilitySource, ComponentCapability, EnvironmentCapability, |
| InternalCapability, |
| }, |
| channel, |
| model::{ |
| error::ModelError, |
| events::{filter::EventFilter, mode_set::EventModeSet}, |
| hooks::{Event, EventPayload}, |
| logging::{FmtArgsLogger, LOGGER as MODEL_LOGGER}, |
| realm::{BindReason, ComponentManagerRealm, ExtendedRealm, Realm, WeakRealm}, |
| rights::{Rights, READ_RIGHTS, WRITE_RIGHTS}, |
| storage, |
| walk_state::WalkState, |
| }, |
| path::PathBufExt, |
| }, |
| async_trait::async_trait, |
| cm_rust::{ |
| self, CapabilityDecl, CapabilityName, CapabilityPath, ComponentDecl, ExposeDecl, |
| ExposeDirectoryDecl, ExposeSource, ExposeTarget, OfferDecl, OfferDirectoryDecl, |
| OfferDirectorySource, OfferEventDecl, OfferEventSource, OfferResolverSource, |
| OfferRunnerSource, OfferServiceSource, OfferStorageSource, StorageDecl, |
| StorageDirectorySource, UseDecl, UseDirectoryDecl, UseEventDecl, UseProtocolDecl, |
| UseSource, UseStorageDecl, |
| }, |
| fidl::{endpoints::ServerEnd, epitaph::ChannelEpitaphExt}, |
| fidl_fuchsia_io as fio, fuchsia_zircon as zx, |
| futures::lock::Mutex, |
| log::*, |
| moniker::{AbsoluteMoniker, ChildMoniker, ExtendedMoniker, PartialMoniker, RelativeMoniker}, |
| std::{path::PathBuf, sync::Arc}, |
| }; |
| |
| const SERVICE_OPEN_FLAGS: u32 = |
| fio::OPEN_FLAG_DESCRIBE | fio::OPEN_RIGHT_READABLE | fio::OPEN_RIGHT_WRITABLE; |
| |
| /// Describes the source of a capability, for any type of capability. |
| #[derive(Debug)] |
| enum OfferSource<'a> { |
| // TODO(fxbug.dev/4776): Enable this once unified services are implemented. |
| #[allow(dead_code)] |
| Service(&'a OfferServiceSource), |
| Protocol(&'a OfferServiceSource), |
| Directory(&'a OfferDirectorySource), |
| Storage(&'a OfferStorageSource), |
| Runner(&'a OfferRunnerSource), |
| Event(&'a OfferEventSource), |
| Resolver(&'a OfferResolverSource), |
| } |
| |
| /// Describes the source of a capability, for any type of capability. |
| #[derive(Debug)] |
| enum CapabilityExposeSource<'a> { |
| Protocol(&'a ExposeSource), |
| Directory(&'a ExposeSource), |
| Runner(&'a ExposeSource), |
| Resolver(&'a ExposeSource), |
| } |
| |
| /// Finds the source of the `capability` used by `absolute_moniker`, and pass along the |
| /// `server_chan` to the hosting component's out directory (or componentmgr's namespace, if |
| /// applicable) using an open request with `open_mode`. |
| pub(super) async fn route_use_capability<'a>( |
| flags: u32, |
| open_mode: u32, |
| relative_path: String, |
| use_decl: &'a UseDecl, |
| target_realm: &'a Arc<Realm>, |
| server_chan: &mut zx::Channel, |
| ) -> Result<(), ModelError> { |
| match use_decl { |
| UseDecl::Service(_) | UseDecl::Protocol(_) | UseDecl::Directory(_) | UseDecl::Runner(_) => { |
| let (source, cap_state) = find_used_capability_source(use_decl, target_realm).await?; |
| let relative_path = cap_state.make_relative_path(relative_path); |
| open_capability_at_source( |
| flags, |
| open_mode, |
| relative_path, |
| source, |
| target_realm, |
| server_chan, |
| ) |
| .await |
| } |
| UseDecl::Storage(storage_decl) => { |
| // TODO(fxbug.dev/50716): This BindReason is wrong. We need to refactor the Storage |
| // capability to plumb through the correct BindReason. |
| route_and_open_storage_capability( |
| storage_decl, |
| open_mode, |
| target_realm, |
| server_chan, |
| &BindReason::Eager, |
| ) |
| .await |
| } |
| UseDecl::Event(_) | UseDecl::EventStream(_) => { |
| // Events are logged separately through route_use_event_capability. |
| Ok(()) |
| } |
| } |
| } |
| |
| pub(super) async fn route_use_event_capability<'a>( |
| use_decl: &'a UseDecl, |
| target_realm: &'a Arc<Realm>, |
| ) -> Result<CapabilitySource, ModelError> { |
| let (source, _cap_state) = find_used_capability_source(use_decl, target_realm).await?; |
| target_realm |
| .try_get_context()? |
| .policy() |
| .can_route_capability(&source, &target_realm.abs_moniker)?; |
| Ok(source) |
| } |
| |
| /// Finds the source of a capability that is registered with an environment, and |
| /// opens the capability with the given server-side channel. |
| /// TODO(61304): Make runner capability routing use this method. |
| pub(super) async fn route_capability_from_environment<'a>( |
| flags: u32, |
| open_mode: u32, |
| relative_path: String, |
| capability: EnvironmentCapability, |
| target_realm: &'a Arc<Realm>, |
| server_chan: &mut zx::Channel, |
| ) -> Result<(), ModelError> { |
| let source = capability.registration_source().clone(); |
| let capability = ComponentCapability::Environment(capability); |
| let cap_state = CapabilityState::Other; |
| let (cap_source, cap_state) = |
| find_environment_component_capability_source(&target_realm, capability, cap_state, &source) |
| .await?; |
| |
| let relative_path = cap_state.make_relative_path(relative_path); |
| open_capability_at_source( |
| flags, |
| open_mode, |
| relative_path, |
| cap_source, |
| target_realm, |
| server_chan, |
| ) |
| .await |
| } |
| |
| /// Finds the source of the expose capability used at `source_path` by `target_realm`, and pass |
| /// along the `server_chan` to the hosting component's out directory (or componentmgr's namespace, |
| /// if applicable) |
| pub(super) async fn route_expose_capability<'a>( |
| flags: u32, |
| open_mode: u32, |
| relative_path: String, |
| expose_decl: &'a ExposeDecl, |
| target_realm: &'a Arc<Realm>, |
| server_chan: &mut zx::Channel, |
| ) -> Result<(), ModelError> { |
| let capability = ComponentCapability::UsedExpose(expose_decl.clone()); |
| let cap_state = CapabilityState::new(&capability); |
| let mut pos = WalkPosition { |
| capability, |
| cap_state, |
| last_child_moniker: None, |
| realm: ExtendedRealm::Component(target_realm.clone()), |
| }; |
| let source = walk_expose_chain(&mut pos).await?; |
| let relative_path = pos.cap_state.make_relative_path(relative_path); |
| open_capability_at_source(flags, open_mode, relative_path, source, target_realm, server_chan) |
| .await |
| } |
| |
| /// The default provider for a ComponentCapability. |
| /// This provider will bind to the source moniker's realm and then open the service |
| /// from the realm's outgoing directory. |
| struct DefaultComponentCapabilityProvider { |
| target_realm: WeakRealm, |
| source_realm: WeakRealm, |
| name: CapabilityName, |
| path: CapabilityPath, |
| } |
| |
| #[async_trait] |
| impl CapabilityProvider for DefaultComponentCapabilityProvider { |
| async fn open( |
| self: Box<Self>, |
| flags: u32, |
| open_mode: u32, |
| relative_path: PathBuf, |
| server_end: &mut zx::Channel, |
| ) -> Result<(), ModelError> { |
| let capability = Arc::new(Mutex::new(Some(channel::take_channel(server_end)))); |
| // Start the source component, if necessary |
| let path = self.path.to_path_buf().attach(relative_path); |
| let source_realm = self |
| .source_realm |
| .upgrade()? |
| .bind(&BindReason::AccessCapability { |
| target: ExtendedMoniker::ComponentInstance(self.target_realm.moniker.clone()), |
| path: self.path.clone(), |
| }) |
| .await?; |
| |
| let event = Event::new( |
| &self.target_realm.upgrade()?, |
| Ok(EventPayload::CapabilityRequested { |
| source_moniker: source_realm.abs_moniker.clone(), |
| name: self.name.to_string(), |
| capability: capability.clone(), |
| }), |
| ); |
| source_realm.hooks.dispatch(&event).await?; |
| |
| // If the capability transported through the event above wasn't transferred |
| // out, then we can open the capability through the realm's outgoing directory. |
| // If some hook consumes the capability, then we don't bother looking in the outgoing |
| // directory. |
| let capability = capability.lock().await.take(); |
| if let Some(mut server_end_for_event) = capability { |
| if let Err(e) = |
| source_realm.open_outgoing(flags, open_mode, path, &mut server_end_for_event).await |
| { |
| // Pass back the channel to propagate the epitaph. |
| *server_end = channel::take_channel(&mut server_end_for_event); |
| return Err(e); |
| } |
| } |
| Ok(()) |
| } |
| } |
| |
| /// This method gets an optional default capability provider based on the |
| /// capability source. |
| fn get_default_provider( |
| target_realm: WeakRealm, |
| source: &CapabilitySource, |
| ) -> Option<Box<dyn CapabilityProvider>> { |
| match source { |
| CapabilitySource::Component { capability, realm } => { |
| // Route normally for a component capability with a source path |
| match capability.source_path() { |
| Some(path) => Some(Box::new(DefaultComponentCapabilityProvider { |
| target_realm, |
| source_realm: realm.clone(), |
| name: capability |
| .source_name() |
| .expect("capability with source path should have a name") |
| .clone(), |
| path: path.clone(), |
| })), |
| _ => None, |
| } |
| } |
| CapabilitySource::Framework { .. } |
| | CapabilitySource::Capability { .. } |
| | CapabilitySource::Builtin { .. } |
| | CapabilitySource::Namespace { .. } => { |
| // There is no default provider for a framework or builtin capability |
| None |
| } |
| } |
| } |
| |
| /// Open the capability at the given source, binding to its component instance if necessary. |
| pub async fn open_capability_at_source( |
| flags: u32, |
| open_mode: u32, |
| relative_path: PathBuf, |
| source: CapabilitySource, |
| target_realm: &Arc<Realm>, |
| server_chan: &mut zx::Channel, |
| ) -> Result<(), ModelError> { |
| target_realm |
| .try_get_context()? |
| .policy() |
| .can_route_capability(&source, &target_realm.abs_moniker)?; |
| let capability_provider = |
| Arc::new(Mutex::new(get_default_provider(target_realm.as_weak(), &source))); |
| |
| let event = Event::new( |
| &target_realm, |
| Ok(EventPayload::CapabilityRouted { |
| source: source.clone(), |
| capability_provider: capability_provider.clone(), |
| }), |
| ); |
| // Get a capability provider from the tree |
| target_realm.hooks.dispatch(&event).await?; |
| |
| // This hack changes the flags for a scoped framework service |
| let mut flags = flags; |
| if let CapabilitySource::Framework { .. } = source { |
| flags = SERVICE_OPEN_FLAGS; |
| } |
| |
| let capability_provider = capability_provider.lock().await.take(); |
| |
| // If a hook in the component tree gave a capability provider, then use it. |
| if let Some(capability_provider) = capability_provider { |
| capability_provider.open(flags, open_mode, relative_path, server_chan).await?; |
| Ok(()) |
| } else { |
| // TODO(fsamuel): This is a temporary hack. If a global path-based framework capability |
| // is not provided by a hook in the component tree, then attempt to connect to the service |
| // in component manager's namespace. We could have modeled this as a default provider, |
| // but several hooks (such as WorkScheduler) require that a provider is not set. |
| let namespace_path = match &source { |
| CapabilitySource::Component { .. } => { |
| unreachable!( |
| "Capability source is a component, which should have been caught by \ |
| default_capability_provider: {:?}", |
| source |
| ); |
| } |
| CapabilitySource::Framework { capability, scope_moniker: m } => { |
| return Err(RoutingError::capability_from_framework_not_found( |
| &m, |
| capability.source_name().to_string(), |
| ) |
| .into()); |
| } |
| CapabilitySource::Capability { source_capability, realm } => { |
| return Err(RoutingError::capability_from_capability_not_found( |
| &realm.moniker, |
| source_capability.to_string(), |
| ) |
| .into()); |
| } |
| CapabilitySource::Builtin { capability } => { |
| return Err(ModelError::from( |
| RoutingError::capability_from_component_manager_not_found( |
| capability.source_name().to_string(), |
| ), |
| )); |
| } |
| CapabilitySource::Namespace { capability } => match capability.source_path() { |
| Some(p) => p.clone(), |
| _ => { |
| return Err(ModelError::from( |
| RoutingError::capability_from_component_manager_not_found( |
| capability.source_id(), |
| ), |
| )); |
| } |
| }, |
| }; |
| let namespace_path = namespace_path.to_path_buf().attach(relative_path); |
| let namespace_path = namespace_path |
| .to_str() |
| .ok_or_else(|| ModelError::path_is_not_utf8(namespace_path.clone()))?; |
| let server_chan = channel::take_channel(server_chan); |
| io_util::connect_in_namespace(namespace_path, server_chan, flags).map_err(|e| { |
| RoutingError::open_component_manager_namespace_failed(namespace_path, e).into() |
| }) |
| } |
| } |
| |
| /// Routes a `UseDecl::Storage` to the component instance providing the backing directory and |
| /// opens its isolated storage with `server_chan`. |
| pub async fn route_and_open_storage_capability<'a>( |
| use_decl: &'a UseStorageDecl, |
| open_mode: u32, |
| target_realm: &'a Arc<Realm>, |
| server_chan: &mut zx::Channel, |
| bind_reason: &BindReason, |
| ) -> Result<(), ModelError> { |
| let (storage_source_info, relative_moniker) = |
| route_storage_capability(use_decl, target_realm).await?; |
| let dir_source_realm = storage_source_info.storage_provider.clone(); |
| let relative_moniker_2 = relative_moniker.clone(); |
| let storage_dir_proxy = storage::open_isolated_storage( |
| storage_source_info, |
| relative_moniker, |
| open_mode, |
| bind_reason, |
| ) |
| .await |
| .map_err(|e| ModelError::from(e))?; |
| |
| // clone the final connection to connect the channel we're routing to its destination |
| let server_chan = channel::take_channel(server_chan); |
| storage_dir_proxy.clone(fio::CLONE_FLAG_SAME_RIGHTS, ServerEnd::new(server_chan)).map_err( |
| |e| { |
| let moniker = match &dir_source_realm { |
| Some(r) => ExtendedMoniker::ComponentInstance(r.abs_moniker.clone()), |
| None => ExtendedMoniker::ComponentManager, |
| }; |
| ModelError::from(RoutingError::open_storage_failed( |
| &moniker, |
| &relative_moniker_2, |
| "", |
| e, |
| )) |
| }, |
| )?; |
| Ok(()) |
| } |
| |
| /// Routes a `UseDecl::Storage` to the component instance providing the backing directory and |
| /// deletes its isolated storage. |
| pub(super) async fn route_and_delete_storage<'a>( |
| use_decl: &'a UseStorageDecl, |
| target_realm: &'a Arc<Realm>, |
| ) -> Result<(), ModelError> { |
| let (storage_source_info, relative_moniker) = |
| route_storage_capability(use_decl, target_realm).await?; |
| storage::delete_isolated_storage(storage_source_info, relative_moniker) |
| .await |
| .map_err(|e| ModelError::from(e))?; |
| Ok(()) |
| } |
| |
| /// Follows the capability routing for the given use declaration from the target realm to the |
| /// storage capability declaration, and then on to the backing directory provider, and returns a |
| /// `StorageCapabilitySource` containing information discovered durng the routing. |
| async fn route_storage_capability<'a>( |
| use_decl: &'a UseStorageDecl, |
| target_realm: &'a Arc<Realm>, |
| ) -> Result<(storage::StorageCapabilitySource, RelativeMoniker), ModelError> { |
| // Walk the offer chain to find the storage decl |
| let parent_realm = match target_realm.try_get_parent()? { |
| ExtendedRealm::Component(p) => p, |
| ExtendedRealm::AboveRoot(_) => { |
| return Err(ModelError::from(RoutingError::storage_source_is_not_component( |
| "component manager's namespace", |
| ))); |
| } |
| }; |
| |
| let capability = ComponentCapability::Use(UseDecl::Storage(use_decl.clone())); |
| let cap_state = CapabilityState::new(&capability); |
| let mut pos = WalkPosition { |
| capability, |
| cap_state, |
| last_child_moniker: target_realm.abs_moniker.path().last().map(|c| c.clone()), |
| realm: ExtendedRealm::Component(parent_realm), |
| }; |
| |
| let source = walk_offer_chain(&mut pos).await?; |
| |
| let (storage_decl, source_realm) = match source { |
| Some(capability_source) => { |
| target_realm |
| .try_get_context()? |
| .policy() |
| .can_route_capability(&capability_source, &target_realm.abs_moniker)?; |
| match capability_source { |
| CapabilitySource::Component { |
| capability: ComponentCapability::Storage(decl), |
| realm, |
| } => (decl, realm.upgrade()?), |
| _ => { |
| unreachable!("Storage capability must come from a storage declaration."); |
| } |
| } |
| } |
| _ => { |
| unreachable!("Storage capability must come from a storage declaration."); |
| } |
| }; |
| |
| let relative_moniker = |
| RelativeMoniker::from_absolute(&source_realm.abs_moniker, &target_realm.abs_moniker); |
| |
| Ok(( |
| route_storage_backing_directory(storage_decl, source_realm, pos.cap_state).await?, |
| relative_moniker, |
| )) |
| } |
| |
| pub async fn route_storage_backing_directory( |
| storage_decl: StorageDecl, |
| source_realm: Arc<Realm>, |
| mut cap_state: CapabilityState, |
| ) -> Result<storage::StorageCapabilitySource, ModelError> { |
| // Find the path and source of the directory consumed by the storage capability. |
| let storage_subdir = storage_decl.subdir.clone(); |
| let (dir_source_path, mut dir_subdir, dir_source_realm) = match storage_decl.source { |
| StorageDirectorySource::Self_ => { |
| let realm_state = source_realm.lock_resolved_state().await?; |
| let decl = realm_state.decl(); |
| let capability = ComponentCapability::Storage(storage_decl.clone()); |
| let capability = |
| cap_state.finalize_directory_from_component(&capability, decl, None, None)?; |
| let source_path = |
| capability.source_path().expect("directory has no source path?").clone(); |
| drop(realm_state); // We can't be holding a reference into source_realm when we move it |
| (source_path, None, Some(source_realm)) |
| } |
| StorageDirectorySource::Parent => { |
| let capability = ComponentCapability::Storage(storage_decl); |
| let (source, cap_state) = find_capability_source(capability, &source_realm).await?; |
| match source { |
| CapabilitySource::Component { capability, realm } => { |
| let source_path = |
| capability.source_path().expect("directory has no source path?").clone(); |
| let dir_subdir = cap_state.get_subdir().map(Clone::clone); |
| |
| (source_path, dir_subdir, Some(realm.upgrade()?)) |
| } |
| CapabilitySource::Framework { .. } => { |
| return Err(RoutingError::storage_directory_source_invalid( |
| "framework", |
| &source_realm.abs_moniker, |
| ) |
| .into()); |
| } |
| CapabilitySource::Builtin { .. } => { |
| return Err(RoutingError::storage_directory_source_invalid( |
| "component manager builtin", |
| &source_realm.abs_moniker, |
| ) |
| .into()); |
| } |
| CapabilitySource::Capability { .. } => { |
| return Err(RoutingError::storage_directory_source_invalid( |
| "capability", |
| &source_realm.abs_moniker, |
| ) |
| .into()); |
| } |
| CapabilitySource::Namespace { capability } => { |
| let source_path = |
| capability.source_path().expect("directory has no source path?").clone(); |
| let dir_subdir = cap_state.get_subdir().map(Clone::clone); |
| |
| (source_path, dir_subdir, None) |
| } |
| } |
| } |
| StorageDirectorySource::Child(ref name) => { |
| let mut pos = { |
| let partial = PartialMoniker::new(name.to_string(), None); |
| let realm_state = source_realm.lock_resolved_state().await?; |
| let child_realm = realm_state.get_live_child_realm(&partial).ok_or_else(|| { |
| ModelError::from(RoutingError::storage_directory_source_child_not_found( |
| &source_realm.abs_moniker, |
| &partial, |
| )) |
| })?; |
| let capability = ComponentCapability::Storage(storage_decl); |
| WalkPosition { |
| capability, |
| cap_state, |
| last_child_moniker: None, |
| realm: ExtendedRealm::Component(child_realm), |
| } |
| }; |
| let source = walk_expose_chain(&mut pos).await?; |
| match source { |
| CapabilitySource::Component { capability, realm } => { |
| let source_path = |
| capability.source_path().expect("directory has no source path?").clone(); |
| let dir_subdir = pos.cap_state.get_subdir().map(Clone::clone); |
| (source_path, dir_subdir, Some(realm.upgrade()?)) |
| } |
| CapabilitySource::Framework { .. } => { |
| return Err(RoutingError::storage_directory_source_invalid( |
| "framework", |
| &source_realm.abs_moniker, |
| ) |
| .into()); |
| } |
| CapabilitySource::Capability { .. } => { |
| return Err(RoutingError::storage_directory_source_invalid( |
| "capability", |
| &source_realm.abs_moniker, |
| ) |
| .into()); |
| } |
| CapabilitySource::Builtin { .. } | CapabilitySource::Namespace { .. } => { |
| unreachable!( |
| "Invalid capability source for storage with backing dir from child" |
| ); |
| } |
| } |
| } |
| }; |
| if dir_subdir == Some(PathBuf::from("")) { |
| dir_subdir = None; |
| } |
| Ok(storage::StorageCapabilitySource { |
| storage_provider: dir_source_realm, |
| backing_directory_path: dir_source_path, |
| backing_directory_subdir: dir_subdir, |
| storage_subdir, |
| }) |
| } |
| |
| /// Check if a used capability is from the framework, and if so return a framework |
| /// `CapabilitySource`. |
| async fn find_scoped_framework_capability_source<'a>( |
| use_decl: &'a UseDecl, |
| target_realm: &'a Arc<Realm>, |
| ) -> Result<Option<CapabilitySource>, ModelError> { |
| if let Ok(capability) = InternalCapability::framework_from_use_decl(use_decl) { |
| return Ok(Some(CapabilitySource::Framework { |
| capability, |
| scope_moniker: target_realm.abs_moniker.clone(), |
| })); |
| } |
| return Ok(None); |
| } |
| |
| async fn find_use_from_capability_source<'a>( |
| use_decl: &'a UseDecl, |
| target_realm: &'a Arc<Realm>, |
| ) -> Result<Option<CapabilitySource>, ModelError> { |
| if let UseDecl::Protocol(UseProtocolDecl { source: UseSource::Capability(_), .. }) = use_decl { |
| return Ok(Some(CapabilitySource::Capability { |
| source_capability: ComponentCapability::Use(use_decl.clone()), |
| realm: target_realm.as_weak(), |
| })); |
| } |
| Ok(None) |
| } |
| |
| /// Holds state about the current position when walking the tree. |
| #[derive(Debug)] |
| struct WalkPosition { |
| /// The capability declaration as it's represented in the current component. |
| capability: ComponentCapability, |
| /// Holds any capability-specific state. |
| cap_state: CapabilityState, |
| /// The moniker of the child we came from. |
| last_child_moniker: Option<ChildMoniker>, |
| /// The realm of the component we are currently looking at. `None` for component manager's |
| /// realm. |
| realm: ExtendedRealm, |
| } |
| |
| impl WalkPosition { |
| fn realm(&self) -> &Arc<Realm> { |
| match &self.realm { |
| ExtendedRealm::Component(r) => &r, |
| ExtendedRealm::AboveRoot(_) => { |
| panic!("no Realm in WalkPosition"); |
| } |
| } |
| } |
| |
| fn moniker(&self) -> &AbsoluteMoniker { |
| match &self.realm { |
| ExtendedRealm::Component(r) => &r.abs_moniker, |
| ExtendedRealm::AboveRoot(_) => { |
| panic!("no moniker in WalkPosition"); |
| } |
| } |
| } |
| |
| fn abs_last_child_moniker(&self) -> AbsoluteMoniker { |
| self.moniker().child(self.last_child_moniker.as_ref().expect("no child moniker").clone()) |
| } |
| |
| fn component_manager_realm(&self) -> Option<&Arc<ComponentManagerRealm>> { |
| match &self.realm { |
| ExtendedRealm::Component(_) => None, |
| ExtendedRealm::AboveRoot(r) => Some(r), |
| } |
| } |
| } |
| |
| /// Holds state related to a capability when walking the tree |
| #[derive(Debug, Clone)] |
| pub enum CapabilityState { |
| Directory { |
| /// Holds the state of the rights. This is used to enforce directory rights. |
| rights_state: WalkState<Rights>, |
| /// Holds the subdirectory path to open. |
| subdir: PathBuf, |
| }, |
| Event { |
| filter_state: WalkState<EventFilter>, |
| modes_state: WalkState<EventModeSet>, |
| }, |
| Other, |
| } |
| |
| impl CapabilityState { |
| pub fn new(cap: &ComponentCapability) -> Self { |
| match cap { |
| ComponentCapability::Use(UseDecl::Directory(UseDirectoryDecl { |
| subdir, |
| rights, |
| .. |
| })) => CapabilityState::Directory { |
| rights_state: WalkState::at(Rights::from(*rights)), |
| subdir: subdir.as_ref().map_or(PathBuf::new(), |s| PathBuf::from(s)), |
| }, |
| ComponentCapability::Expose(ExposeDecl::Directory(ExposeDirectoryDecl { |
| subdir, |
| rights, |
| .. |
| })) |
| | ComponentCapability::Offer(OfferDecl::Directory(OfferDirectoryDecl { |
| subdir, |
| rights, |
| .. |
| })) => { |
| let rights_state = match rights { |
| Some(rights) => WalkState::at(Rights::from(*rights)), |
| None => WalkState::new(), |
| }; |
| CapabilityState::Directory { |
| rights_state, |
| subdir: subdir.as_ref().map_or(PathBuf::new(), |s| PathBuf::from(s)), |
| } |
| } |
| ComponentCapability::Use(UseDecl::Event(UseEventDecl { mode, filter, .. })) |
| | ComponentCapability::Offer(OfferDecl::Event(OfferEventDecl { |
| mode, filter, .. |
| })) => CapabilityState::Event { |
| filter_state: WalkState::at(EventFilter::new(filter.clone())), |
| modes_state: WalkState::at(EventModeSet::new(mode.clone())), |
| }, |
| ComponentCapability::UsedExpose(ExposeDecl::Directory(ExposeDirectoryDecl { |
| .. |
| })) => CapabilityState::Directory { |
| rights_state: WalkState::new(), |
| subdir: PathBuf::new(), |
| }, |
| // Directories backing storage must provide read and write rights. |
| ComponentCapability::Use(UseDecl::Storage { .. }) => CapabilityState::Directory { |
| rights_state: WalkState::at(Rights::from(*READ_RIGHTS | *WRITE_RIGHTS)), |
| subdir: PathBuf::new(), |
| }, |
| ComponentCapability::Storage(_) => CapabilityState::Directory { |
| rights_state: WalkState::at(Rights::from(*READ_RIGHTS | *WRITE_RIGHTS)), |
| subdir: PathBuf::new(), |
| }, |
| _ => CapabilityState::Other, |
| } |
| } |
| |
| /// Finalize the directory state according to `capability`. Returns a `ComponentCapability` for |
| /// the end of routing, or an error if rights did not match. |
| /// |
| /// REQUIRES: `capability` is a directory expose or offer from `self`. |
| fn finalize_directory_from_component( |
| &mut self, |
| capability: &ComponentCapability, |
| decl: &ComponentDecl, |
| dir_rights: Option<Rights>, |
| subdir_decl: Option<PathBuf>, |
| ) -> Result<ComponentCapability, ModelError> { |
| if let CapabilityState::Directory { rights_state, subdir } = self { |
| *rights_state = rights_state.advance(dir_rights)?; |
| CapabilityState::update_subdir(subdir, subdir_decl); |
| } |
| |
| let directory_decl = capability |
| .find_directory_source(decl) |
| .expect("directory offer references nonexistent section") |
| .clone(); |
| let dir_rights = Some(Rights::from(directory_decl.rights)); |
| let capability = ComponentCapability::Directory(directory_decl); |
| if let CapabilityState::Directory { rights_state, subdir: _subdir } = self { |
| *rights_state = rights_state.finalize(dir_rights)?; |
| } |
| Ok(capability) |
| } |
| |
| /// Finalize the directory state for a directory that component from the framework. |
| fn finalize_directory_from_framework( |
| &mut self, |
| subdir_decl: Option<PathBuf>, |
| ) -> Result<(), ModelError> { |
| if let CapabilityState::Directory { rights_state, subdir } = self { |
| *rights_state = rights_state.finalize(Some(Rights::from(*READ_RIGHTS)))?; |
| CapabilityState::update_subdir(subdir, subdir_decl); |
| } |
| Ok(()) |
| } |
| |
| fn make_relative_path(&self, in_relative_path: String) -> PathBuf { |
| match self { |
| Self::Directory { subdir, .. } => subdir.clone().attach(in_relative_path), |
| _ => PathBuf::from(in_relative_path), |
| } |
| } |
| |
| fn update_subdir(subdir: &mut PathBuf, subdir_decl: Option<PathBuf>) { |
| let subdir_decl = subdir_decl.unwrap_or(PathBuf::new()); |
| let old_subdir = subdir.clone(); |
| *subdir = subdir_decl.attach(old_subdir); |
| } |
| |
| /// Returns the subdir for the given capability, or None if the subdir is empty. Panics if this |
| /// is not a Directory. |
| fn get_subdir(&self) -> Option<&PathBuf> { |
| match &self { |
| CapabilityState::Directory { subdir, .. } if subdir != &PathBuf::new() => Some(subdir), |
| CapabilityState::Directory { .. } => None, |
| _ => panic!( |
| "get_subdir called on a ComponentCapability that is not a Directory: {:?}", |
| self |
| ), |
| } |
| } |
| } |
| |
| async fn find_used_capability_source<'a>( |
| use_decl: &'a UseDecl, |
| target_realm: &'a Arc<Realm>, |
| ) -> Result<(CapabilitySource, CapabilityState), ModelError> { |
| let capability = ComponentCapability::Use(use_decl.clone()); |
| if let Some(framework_capability) = |
| find_scoped_framework_capability_source(use_decl, target_realm).await? |
| { |
| let mut cap_state = CapabilityState::new(&capability); |
| match use_decl { |
| UseDecl::Service(_) | UseDecl::Protocol(_) | UseDecl::Event(_) => {} |
| UseDecl::Directory(_) => { |
| cap_state.finalize_directory_from_framework(None)?; |
| } |
| _ => { |
| unreachable!("Invalid framework capability: {:?}", use_decl); |
| } |
| } |
| return Ok((framework_capability, cap_state)); |
| } |
| if let Some((cap_source, cap_state)) = |
| find_environment_capability_source(use_decl, target_realm).await? |
| { |
| return Ok((cap_source, cap_state)); |
| } |
| if let Some(cap_source) = find_use_from_capability_source(use_decl, target_realm).await? { |
| let cap_state = CapabilityState::new(&capability); |
| return Ok((cap_source, cap_state)); |
| } |
| find_capability_source(capability, target_realm).await |
| } |
| |
| /// Attempts to perform capability routing starting from a `use` of a capability that could be |
| /// provided by an environment. Returns `None` if `use_decl` is not the type of capability |
| /// provided by an environment. |
| async fn find_environment_capability_source<'a>( |
| use_decl: &'a UseDecl, |
| target_realm: &'a Arc<Realm>, |
| ) -> Result<Option<(CapabilitySource, CapabilityState)>, ModelError> { |
| let cap_state = CapabilityState::new(&ComponentCapability::Use(use_decl.clone())); |
| match use_decl { |
| UseDecl::Runner(cm_rust::UseRunnerDecl { source_name }) => { |
| match target_realm.environment.get_registered_runner(source_name)? { |
| Some((Some(env_realm), reg)) => { |
| let capability = |
| ComponentCapability::Environment(EnvironmentCapability::Runner { |
| source_name: reg.source_name, |
| source: reg.source.clone(), |
| }); |
| Ok(Some( |
| find_environment_component_capability_source( |
| &env_realm, |
| capability, |
| cap_state, |
| ®.source, |
| ) |
| .await?, |
| )) |
| } |
| Some((None, reg)) => { |
| // Root environment. |
| let cap_source = CapabilitySource::Builtin { |
| capability: InternalCapability::Runner(reg.source_name.clone()), |
| }; |
| Ok(Some((cap_source, cap_state))) |
| } |
| None => Err(ModelError::from(RoutingError::use_from_environment_not_found( |
| &target_realm.abs_moniker, |
| "runner", |
| source_name.to_string(), |
| ))), |
| } |
| } |
| _ => Ok(None), |
| } |
| } |
| |
| async fn find_environment_component_capability_source<'a>( |
| env_realm: &Arc<Realm>, |
| capability: ComponentCapability, |
| cap_state: CapabilityState, |
| source: &cm_rust::RegistrationSource, |
| ) -> Result<(CapabilitySource, CapabilityState), ModelError> { |
| let env_realm_state = env_realm.lock_resolved_state().await?; |
| let decl = env_realm_state.decl(); |
| match &source { |
| cm_rust::RegistrationSource::Self_ => { |
| let cap_source = find_capability_source_from_self(&env_realm, &capability, decl); |
| Ok((cap_source, cap_state)) |
| } |
| cm_rust::RegistrationSource::Parent => { |
| let starting_realm = env_realm.try_get_parent()?; |
| let mut pos = WalkPosition { |
| capability, |
| cap_state, |
| last_child_moniker: env_realm.abs_moniker.leaf().cloned(), |
| realm: starting_realm, |
| }; |
| if let Some(cap_source) = walk_offer_chain(&mut pos).await? { |
| return Ok((cap_source, pos.cap_state)); |
| } |
| let cap_source = walk_expose_chain(&mut pos).await?; |
| Ok((cap_source, pos.cap_state)) |
| } |
| cm_rust::RegistrationSource::Child(child_name) => { |
| let partial = PartialMoniker::new(child_name.into(), None); |
| let realm = ExtendedRealm::Component( |
| env_realm_state.get_live_child_realm(&partial).ok_or_else(|| { |
| ModelError::from(RoutingError::environment_from_child_expose_not_found( |
| &partial, |
| &env_realm.abs_moniker, |
| capability.type_name().to_string(), |
| capability.source_id(), |
| )) |
| })?, |
| ); |
| let mut pos = WalkPosition { capability, cap_state, last_child_moniker: None, realm }; |
| let cap_source = walk_expose_chain(&mut pos).await?; |
| Ok((cap_source, pos.cap_state)) |
| } |
| } |
| } |
| |
| fn find_capability_source_from_self( |
| env_realm: &Arc<Realm>, |
| capability: &ComponentCapability, |
| decl: &cm_rust::ComponentDecl, |
| ) -> CapabilitySource { |
| match capability { |
| ComponentCapability::Environment(env_cap) => match env_cap { |
| EnvironmentCapability::Runner { .. } => { |
| let runner_decl = |
| capability.find_runner_source(decl).expect("missing runner").clone(); |
| CapabilitySource::Component { |
| capability: ComponentCapability::Runner(runner_decl), |
| realm: env_realm.as_weak(), |
| } |
| } |
| EnvironmentCapability::Resolver { .. } => { |
| let resolver_decl = |
| capability.find_resolver_source(decl).expect("missing resolver").clone(); |
| CapabilitySource::Component { |
| capability: ComponentCapability::Resolver(resolver_decl), |
| realm: env_realm.as_weak(), |
| } |
| } |
| }, |
| _ => { |
| panic!("Capability has invalid type: {:?}", capability); |
| } |
| } |
| } |
| |
| /// Walks the component tree to return the originating source of a capability, starting on the given |
| /// abs_moniker, as well as the final capability state. |
| async fn find_capability_source<'a>( |
| capability: ComponentCapability, |
| target_realm: &'a Arc<Realm>, |
| ) -> Result<(CapabilitySource, CapabilityState), ModelError> { |
| let starting_realm = target_realm.try_get_parent()?; |
| let cap_state = CapabilityState::new(&capability); |
| let mut pos = WalkPosition { |
| capability, |
| cap_state, |
| last_child_moniker: target_realm.abs_moniker.leaf().cloned(), |
| realm: starting_realm, |
| }; |
| if let Some(source) = walk_offer_chain(&mut pos).await? { |
| return Ok((source, pos.cap_state)); |
| } |
| let source = walk_expose_chain(&mut pos).await?; |
| Ok((source, pos.cap_state)) |
| } |
| |
| /// Follows `offer` declarations up the component tree, starting at `pos`. The algorithm looks |
| /// for a matching `offer` in the parent, as long as the `offer` is from `realm`. |
| /// |
| /// Returns the source of the capability if found, or `None` if `expose` declarations must be |
| /// walked. |
| async fn walk_offer_chain<'a>( |
| pos: &'a mut WalkPosition, |
| ) -> Result<Option<CapabilitySource>, ModelError> { |
| 'offerloop: loop { |
| if let Some(cm_realm) = pos.component_manager_realm() { |
| // This is a built-in or namespace capability because the routing path was traced to |
| // the component manager's realm. |
| match pos.capability.find_namespace_source(&cm_realm.namespace_capabilities) { |
| Some(CapabilityDecl::Protocol(p)) => { |
| let capability = ComponentCapability::Protocol(p.clone()); |
| return Ok(Some(CapabilitySource::Namespace { capability })); |
| } |
| Some(CapabilityDecl::Directory(d)) => { |
| let dir_rights = Some(Rights::from(d.rights)); |
| let capability = ComponentCapability::Directory(d.clone()); |
| if let CapabilityState::Directory { rights_state, subdir: _subdir } = |
| &mut pos.cap_state |
| { |
| *rights_state = rights_state.finalize(dir_rights)?; |
| } |
| return Ok(Some(CapabilitySource::Namespace { capability })); |
| } |
| Some(_) => { |
| return Err(ModelError::unsupported(format!( |
| "Namespace capability not supported: {:?}", |
| pos.capability, |
| ))); |
| } |
| None => (), |
| } |
| |
| // Not a namespace capability, fallback to builtin. |
| let capability = match &pos.capability { |
| ComponentCapability::Use(use_decl) => { |
| InternalCapability::builtin_from_use_decl(use_decl).map_err(|_| { |
| ModelError::from(RoutingError::use_from_component_manager_not_found( |
| pos.capability.source_id(), |
| )) |
| }) |
| } |
| ComponentCapability::Offer(offer_decl) => { |
| InternalCapability::builtin_from_offer_decl(offer_decl).map_err(|_| { |
| ModelError::from(RoutingError::offer_from_component_manager_not_found( |
| pos.capability.source_id(), |
| )) |
| }) |
| } |
| ComponentCapability::Storage(storage_decl) => { |
| InternalCapability::builtin_from_storage_decl(storage_decl).map_err(|_| { |
| ModelError::from(RoutingError::storage_from_component_manager_not_found( |
| pos.capability.source_id(), |
| )) |
| }) |
| } |
| _ => Err(ModelError::unsupported(format!( |
| "Built-in capability not supported: {:?}", |
| pos.capability, |
| ))), |
| }?; |
| return Ok(Some(CapabilitySource::Builtin { capability })); |
| } |
| |
| let cur_realm = pos.realm().clone(); |
| let cur_realm_state = cur_realm.lock_resolved_state().await?; |
| let decl = cur_realm_state.decl(); |
| let last_child_moniker = pos.last_child_moniker.as_ref().expect("no child moniker"); |
| let offer = |
| pos.capability.find_offer_source(decl, last_child_moniker).ok_or_else(|| match pos |
| .capability |
| { |
| ComponentCapability::Use(_) => { |
| ModelError::from(RoutingError::use_from_parent_not_found( |
| &pos.abs_last_child_moniker(), |
| pos.capability.source_id(), |
| )) |
| } |
| ComponentCapability::Environment(_) => { |
| ModelError::from(RoutingError::environment_from_parent_not_found( |
| &pos.abs_last_child_moniker(), |
| pos.capability.type_name().to_string(), |
| pos.capability.source_id(), |
| )) |
| } |
| ComponentCapability::Offer(_) => { |
| ModelError::from(RoutingError::offer_from_parent_not_found( |
| &pos.abs_last_child_moniker(), |
| pos.capability.source_id(), |
| )) |
| } |
| ComponentCapability::Storage(_) => { |
| ModelError::from(RoutingError::storage_from_parent_not_found( |
| &pos.abs_last_child_moniker(), |
| pos.capability.source_id(), |
| )) |
| } |
| _ => { |
| panic!("Invalid offer target: {:?}", pos.capability); |
| } |
| })?; |
| let source = match offer { |
| OfferDecl::Service(_) => return Err(ModelError::unsupported("Service capability")), |
| OfferDecl::Protocol(s) => OfferSource::Protocol(&s.source), |
| OfferDecl::Directory(d) => OfferSource::Directory(&d.source), |
| OfferDecl::Storage(s) => OfferSource::Storage(&s.source), |
| OfferDecl::Runner(r) => OfferSource::Runner(&r.source), |
| OfferDecl::Resolver(r) => OfferSource::Resolver(&r.source), |
| OfferDecl::Event(e) => OfferSource::Event(&e.source), |
| }; |
| let (dir_rights, subdir_decl) = match offer { |
| OfferDecl::Directory(OfferDirectoryDecl { rights, subdir, .. }) => { |
| (rights.map(Rights::from), subdir.clone()) |
| } |
| _ => (None, None), |
| }; |
| let event_filter = Some(EventFilter::new(match offer { |
| OfferDecl::Event(OfferEventDecl { filter, .. }) => filter.clone(), |
| _ => None, |
| })); |
| let event_mode = Some(EventModeSet::new(match offer { |
| OfferDecl::Event(OfferEventDecl { mode, .. }) => mode.clone(), |
| _ => cm_rust::EventMode::Async, |
| })); |
| match source { |
| OfferSource::Service(_) => { |
| return Err(ModelError::unsupported("Service capability")); |
| } |
| OfferSource::Directory(OfferDirectorySource::Framework) => { |
| // Directories coming from the framework are limited to |
| // read-only rights. |
| pos.cap_state.finalize_directory_from_framework(subdir_decl)?; |
| let capability = InternalCapability::framework_from_offer_decl(offer) |
| .expect("not a framework offer declaration"); |
| return Ok(Some(CapabilitySource::Framework { |
| capability, |
| scope_moniker: pos.moniker().clone(), |
| })); |
| } |
| OfferSource::Event(OfferEventSource::Framework) => { |
| // An event offered from framework is scoped to the current realm. |
| if let CapabilityState::Event { modes_state, filter_state } = &mut pos.cap_state { |
| *modes_state = modes_state.finalize(event_mode)?; |
| *filter_state = filter_state.finalize(event_filter)?; |
| } |
| let capability = InternalCapability::framework_from_offer_decl(offer) |
| .expect("not a framework offer declaration"); |
| return Ok(Some(CapabilitySource::Framework { |
| capability, |
| scope_moniker: pos.moniker().clone(), |
| })); |
| } |
| OfferSource::Protocol(OfferServiceSource::Parent) |
| | OfferSource::Storage(OfferStorageSource::Parent) |
| | OfferSource::Runner(OfferRunnerSource::Parent) |
| | OfferSource::Resolver(OfferResolverSource::Parent) => { |
| // The offered capability comes from the parent, so follow the |
| // parent |
| pos.capability = ComponentCapability::Offer(offer.clone()); |
| pos.last_child_moniker = pos.moniker().path().last().map(|c| c.clone()); |
| pos.realm = cur_realm.try_get_parent()?; |
| continue 'offerloop; |
| } |
| OfferSource::Event(OfferEventSource::Parent) => { |
| // The offered capability comes from the parent, so follow the |
| // parent |
| if let CapabilityState::Event { modes_state, filter_state } = &mut pos.cap_state { |
| *modes_state = modes_state.advance(event_mode)?; |
| *filter_state = filter_state.advance(event_filter)?; |
| } |
| pos.capability = ComponentCapability::Offer(offer.clone()); |
| pos.last_child_moniker = pos.moniker().path().last().map(|c| c.clone()); |
| pos.realm = cur_realm.try_get_parent()?; |
| continue 'offerloop; |
| } |
| OfferSource::Directory(OfferDirectorySource::Parent) => { |
| if let CapabilityState::Directory { rights_state, subdir } = &mut pos.cap_state { |
| *rights_state = rights_state.advance(dir_rights)?; |
| CapabilityState::update_subdir(subdir, subdir_decl); |
| } |
| pos.capability = ComponentCapability::Offer(offer.clone()); |
| pos.last_child_moniker = pos.moniker().path().last().map(|c| c.clone()); |
| pos.realm = cur_realm.try_get_parent()?; |
| continue 'offerloop; |
| } |
| OfferSource::Protocol(OfferServiceSource::Self_) => { |
| match pos.capability.source_path() { |
| None => { |
| // The offered capability comes from the current component. |
| // Find the current component's Protocol declaration. |
| let cap = ComponentCapability::Offer(offer.clone()); |
| return Ok(Some(CapabilitySource::Component { |
| capability: ComponentCapability::Protocol( |
| cap.find_protocol_source(decl) |
| .expect("protocol offer references nonexistent section") |
| .clone(), |
| ), |
| realm: cur_realm.as_weak(), |
| })); |
| } |
| Some(_) => { |
| // Legacy path: protocol is offered by path, get its info from the |
| // OfferDecl. |
| return Ok(Some(CapabilitySource::Component { |
| capability: ComponentCapability::Offer(offer.clone()), |
| realm: cur_realm.as_weak(), |
| })); |
| } |
| } |
| } |
| OfferSource::Protocol(OfferServiceSource::Capability(_)) => { |
| return Ok(Some(CapabilitySource::Capability { |
| source_capability: ComponentCapability::Offer(offer.clone()), |
| realm: cur_realm.as_weak(), |
| })); |
| } |
| OfferSource::Directory(OfferDirectorySource::Self_) => { |
| match pos.capability.source_path() { |
| None => { |
| // The offered capability comes from the current component. Update state |
| // and return a capability corresponding to the current component's |
| // Directory declaration. |
| let capability = ComponentCapability::Offer(offer.clone()); |
| let capability = pos.cap_state.finalize_directory_from_component( |
| &capability, |
| decl, |
| dir_rights, |
| subdir_decl, |
| )?; |
| return Ok(Some(CapabilitySource::Component { |
| capability, |
| realm: cur_realm.as_weak(), |
| })); |
| } |
| Some(_) => { |
| // Legacy path: directory is offered by path, get its info from the |
| // OfferDecl. |
| if let CapabilityState::Directory { rights_state, subdir } = |
| &mut pos.cap_state |
| { |
| *rights_state = rights_state.finalize(dir_rights)?; |
| CapabilityState::update_subdir(subdir, subdir_decl); |
| } |
| return Ok(Some(CapabilitySource::Component { |
| capability: ComponentCapability::Offer(offer.clone()), |
| realm: cur_realm.as_weak(), |
| })); |
| } |
| } |
| } |
| OfferSource::Runner(OfferRunnerSource::Self_) => { |
| // The offered capability comes from the current component. |
| // Find the current component's Runner declaration. |
| let cap = ComponentCapability::Offer(offer.clone()); |
| return Ok(Some(CapabilitySource::Component { |
| capability: ComponentCapability::Runner( |
| cap.find_runner_source(decl) |
| .expect("runner offer references nonexistent section") |
| .clone(), |
| ), |
| realm: cur_realm.as_weak(), |
| })); |
| } |
| OfferSource::Resolver(OfferResolverSource::Self_) => { |
| // The offered capability comes from the current component. |
| // Find the current component's Resolver declaration. |
| let cap = ComponentCapability::Offer(offer.clone()); |
| return Ok(Some(CapabilitySource::Component { |
| capability: ComponentCapability::Resolver( |
| cap.find_resolver_source(decl) |
| .expect("resolver offer references nonexistent section") |
| .clone(), |
| ), |
| realm: cur_realm.as_weak(), |
| })); |
| } |
| OfferSource::Protocol(OfferServiceSource::Child(child_name)) |
| | OfferSource::Runner(OfferRunnerSource::Child(child_name)) |
| | OfferSource::Resolver(OfferResolverSource::Child(child_name)) => { |
| // The offered capability comes from a child, break the loop |
| // and begin walking the expose chain. |
| pos.capability = ComponentCapability::Offer(offer.clone()); |
| let partial = PartialMoniker::new(child_name.to_string(), None); |
| pos.realm = ExtendedRealm::Component( |
| cur_realm_state.get_live_child_realm(&partial).ok_or_else(|| { |
| ModelError::from(RoutingError::offer_from_child_expose_not_found( |
| &partial, |
| pos.moniker(), |
| pos.capability.source_id(), |
| )) |
| })?, |
| ); |
| return Ok(None); |
| } |
| OfferSource::Directory(OfferDirectorySource::Child(child_name)) => { |
| if let CapabilityState::Directory { rights_state, subdir } = &mut pos.cap_state { |
| *rights_state = rights_state.advance(dir_rights)?; |
| CapabilityState::update_subdir(subdir, subdir_decl); |
| } |
| pos.capability = ComponentCapability::Offer(offer.clone()); |
| let partial = PartialMoniker::new(child_name.to_string(), None); |
| pos.realm = ExtendedRealm::Component( |
| cur_realm_state.get_live_child_realm(&partial).ok_or(ModelError::from( |
| RoutingError::offer_from_child_instance_not_found( |
| &partial, |
| pos.moniker(), |
| pos.capability.source_id(), |
| ), |
| ))?, |
| ); |
| return Ok(None); |
| } |
| OfferSource::Storage(OfferStorageSource::Self_) => { |
| let storage_name = match &offer { |
| OfferDecl::Storage(s) => &s.source_name, |
| _ => panic!( |
| "OfferSource::Storage on a non-storage OfferDecl should be impossible" |
| ), |
| }; |
| let storage = decl |
| .find_storage_source(storage_name) |
| .expect("storage offer references nonexistent section"); |
| let capability = ComponentCapability::Storage(storage.clone()); |
| pos.cap_state = CapabilityState::new(&capability); |
| return Ok(Some(CapabilitySource::Component { |
| capability, |
| realm: cur_realm.as_weak(), |
| })); |
| } |
| } |
| } |
| } |
| |
| /// Follows `expose` declarations down the component tree, starting at `pos`. The algorithm looks |
| /// for a matching `expose` in the child, as long as the `expose` is not from `self`. |
| /// |
| /// Returns the source of the capability. |
| async fn walk_expose_chain<'a>(pos: &'a mut WalkPosition) -> Result<CapabilitySource, ModelError> { |
| loop { |
| // TODO(xbhatnag): See if the locking needs to be over the entire loop |
| // Consider -> let current_decl = { .. }; |
| let cur_realm = pos.realm().clone(); |
| let cur_realm_state = cur_realm.lock_resolved_state().await?; |
| let decl = cur_realm_state.decl(); |
| let expose = |
| pos.capability.find_expose_source(decl).ok_or_else(|| match pos.capability { |
| ComponentCapability::UsedExpose(_) => ModelError::from( |
| RoutingError::used_expose_not_found(pos.moniker(), pos.capability.source_id()), |
| ), |
| ComponentCapability::Environment(_) => { |
| let partial = |
| pos.moniker().leaf().expect("impossible source above root").to_partial(); |
| ModelError::from(RoutingError::environment_from_child_expose_not_found( |
| &partial, |
| pos.moniker().parent().as_ref().expect("impossible source above root"), |
| pos.capability.type_name().to_string(), |
| pos.capability.source_id(), |
| )) |
| } |
| ComponentCapability::Expose(_) => { |
| let partial = |
| pos.moniker().leaf().expect("impossible source above root").to_partial(); |
| ModelError::from(RoutingError::expose_from_child_expose_not_found( |
| &partial, |
| pos.moniker().parent().as_ref().expect("impossible source above root"), |
| pos.capability.source_id(), |
| )) |
| } |
| ComponentCapability::Offer(_) => { |
| let partial = |
| pos.moniker().leaf().expect("impossible source above root").to_partial(); |
| ModelError::from(RoutingError::offer_from_child_expose_not_found( |
| &partial, |
| pos.moniker().parent().as_ref().expect("impossible source above root"), |
| pos.capability.source_id(), |
| )) |
| } |
| ComponentCapability::Storage(_) => { |
| let partial = |
| pos.moniker().leaf().expect("impossible source above root").to_partial(); |
| ModelError::from(RoutingError::storage_from_child_expose_not_found( |
| &partial, |
| pos.moniker().parent().as_ref().expect("impossible source above root"), |
| pos.capability.source_id(), |
| )) |
| } |
| _ => { |
| unreachable!( |
| "Searched for an expose declaration at `{}` for `{}`, but the \ |
| source doesn't seem like it should map to an expose declaration", |
| pos.moniker().parent().expect("impossible source above root"), |
| pos.capability.source_id() |
| ); |
| } |
| })?; |
| let (source, target) = match expose { |
| ExposeDecl::Service(_) => return Err(ModelError::unsupported("Service capability")), |
| ExposeDecl::Protocol(ls) => (CapabilityExposeSource::Protocol(&ls.source), &ls.target), |
| ExposeDecl::Directory(d) => (CapabilityExposeSource::Directory(&d.source), &d.target), |
| ExposeDecl::Runner(r) => (CapabilityExposeSource::Runner(&r.source), &r.target), |
| ExposeDecl::Resolver(r) => (CapabilityExposeSource::Resolver(&r.source), &r.target), |
| }; |
| if target != &ExposeTarget::Parent { |
| let partial = pos.moniker().leaf().expect("impossible source above root").to_partial(); |
| return Err(RoutingError::expose_from_child_expose_not_found( |
| &partial, |
| pos.moniker().parent().as_ref().expect("impossible source above root"), |
| pos.capability.source_id(), |
| ) |
| .into()); |
| } |
| let (dir_rights, subdir_decl) = match expose { |
| ExposeDecl::Directory(ExposeDirectoryDecl { rights, subdir, .. }) => { |
| (rights.map(Rights::from), subdir.clone()) |
| } |
| _ => (None, None), |
| }; |
| match source { |
| CapabilityExposeSource::Protocol(ExposeSource::Self_) => { |
| match pos.capability.source_path() { |
| None => { |
| // The exposed capability comes from the current component. |
| // Find the current component's Protocol declaration. |
| let cap = ComponentCapability::Expose(expose.clone()); |
| return Ok(CapabilitySource::Component { |
| capability: ComponentCapability::Protocol( |
| cap.find_protocol_source(decl) |
| .expect("protocol offer references nonexistent section") |
| .clone(), |
| ), |
| realm: cur_realm.as_weak(), |
| }); |
| } |
| Some(_) => { |
| // Legacy path: protocol is exposed by path, get its info from the |
| // ExposeDecl. |
| return Ok(CapabilitySource::Component { |
| capability: ComponentCapability::Expose(expose.clone()), |
| realm: cur_realm.as_weak(), |
| }); |
| } |
| } |
| } |
| CapabilityExposeSource::Protocol(ExposeSource::Capability(_)) => { |
| return Ok(CapabilitySource::Capability { |
| source_capability: ComponentCapability::Expose(expose.clone()), |
| realm: cur_realm.as_weak(), |
| }); |
| } |
| CapabilityExposeSource::Directory(ExposeSource::Self_) => { |
| match pos.capability.source_path() { |
| None => { |
| // The offered capability comes from the current component. Update state |
| // and return a capability corresponding to the current component's |
| // Directory declaration. |
| let capability = ComponentCapability::Expose(expose.clone()); |
| let capability = pos.cap_state.finalize_directory_from_component( |
| &capability, |
| decl, |
| dir_rights, |
| subdir_decl, |
| )?; |
| return Ok(CapabilitySource::Component { |
| capability, |
| realm: cur_realm.as_weak(), |
| }); |
| } |
| Some(_) => { |
| // Legacy path: directory is exposed by path, get its info from the |
| // ExposeDecl. |
| if let CapabilityState::Directory { rights_state, subdir } = |
| &mut pos.cap_state |
| { |
| *rights_state = rights_state.finalize(dir_rights)?; |
| CapabilityState::update_subdir(subdir, subdir_decl); |
| } |
| return Ok(CapabilitySource::Component { |
| capability: ComponentCapability::Expose(expose.clone()), |
| realm: cur_realm.as_weak(), |
| }); |
| } |
| } |
| } |
| CapabilityExposeSource::Runner(ExposeSource::Self_) => { |
| // The exposed capability comes from the current component. |
| // Find the current component's Runner declaration. |
| let cap = ComponentCapability::Expose(expose.clone()); |
| return Ok(CapabilitySource::Component { |
| capability: ComponentCapability::Runner( |
| cap.find_runner_source(decl) |
| .expect(&format!( |
| "An `expose from runner` declaration was found at `{}` for `{}` |
| with no corresponding runner declaration. This ComponentDecl should |
| not have passed validation.", |
| pos.moniker(), |
| cap.source_id() |
| )) |
| .clone(), |
| ), |
| realm: cur_realm.as_weak(), |
| }); |
| } |
| CapabilityExposeSource::Resolver(ExposeSource::Self_) => { |
| // The exposed capability comes from the current component. |
| // Find the current component's Resolver declaration. |
| let cap = ComponentCapability::Expose(expose.clone()); |
| return Ok(CapabilitySource::Component { |
| capability: ComponentCapability::Resolver( |
| cap.find_resolver_source(decl) |
| .expect(&format!( |
| "An `expose from resolver` declaration was found at `{}` for `{}` |
| with no corresponding resolver declaration. This ComponentDecl should |
| not have passed validation.", |
| pos.moniker(), |
| cap.source_id() |
| )) |
| .clone(), |
| ), |
| realm: cur_realm.as_weak(), |
| }); |
| } |
| CapabilityExposeSource::Protocol(ExposeSource::Child(child_name)) |
| | CapabilityExposeSource::Runner(ExposeSource::Child(child_name)) |
| | CapabilityExposeSource::Resolver(ExposeSource::Child(child_name)) => { |
| // The offered capability comes from a child, so follow the child. |
| pos.capability = ComponentCapability::Expose(expose.clone()); |
| let partial = PartialMoniker::new(child_name.to_string(), None); |
| pos.realm = ExtendedRealm::Component( |
| cur_realm_state.get_live_child_realm(&partial).ok_or(ModelError::from( |
| RoutingError::expose_from_child_instance_not_found( |
| &partial, |
| pos.moniker(), |
| pos.capability.source_id(), |
| ), |
| ))?, |
| ); |
| continue; |
| } |
| CapabilityExposeSource::Directory(ExposeSource::Child(child_name)) => { |
| if let CapabilityState::Directory { rights_state, subdir } = &mut pos.cap_state { |
| *rights_state = rights_state.advance(dir_rights)?; |
| CapabilityState::update_subdir(subdir, subdir_decl); |
| } |
| // The offered capability comes from a child, so follow the child. |
| pos.capability = ComponentCapability::Expose(expose.clone()); |
| let partial = PartialMoniker::new(child_name.to_string(), None); |
| pos.realm = ExtendedRealm::Component( |
| cur_realm_state.get_live_child_realm(&partial).ok_or(ModelError::from( |
| RoutingError::expose_from_child_instance_not_found( |
| &partial, |
| pos.moniker(), |
| pos.capability.source_id(), |
| ), |
| ))?, |
| ); |
| continue; |
| } |
| CapabilityExposeSource::Protocol(ExposeSource::Framework) => { |
| let capability = |
| InternalCapability::framework_from_expose_decl(expose).map_err(|_| { |
| ModelError::from(RoutingError::expose_from_framework_not_found( |
| pos.moniker(), |
| pos.capability.source_id(), |
| )) |
| })?; |
| return Ok(CapabilitySource::Framework { |
| capability, |
| scope_moniker: pos.moniker().clone(), |
| }); |
| } |
| CapabilityExposeSource::Directory(ExposeSource::Framework) => { |
| // Directories offered or exposed directly from the framework are limited to |
| // read-only rights. |
| if let CapabilityState::Directory { rights_state, subdir } = &mut pos.cap_state { |
| *rights_state = rights_state.finalize(Some(Rights::from(*READ_RIGHTS)))?; |
| CapabilityState::update_subdir(subdir, subdir_decl); |
| } |
| let capability = |
| InternalCapability::framework_from_expose_decl(expose).map_err(|_| { |
| ModelError::from(RoutingError::expose_from_framework_not_found( |
| pos.moniker(), |
| pos.capability.source_id(), |
| )) |
| })?; |
| return Ok(CapabilitySource::Framework { |
| capability, |
| scope_moniker: pos.moniker().clone(), |
| }); |
| } |
| CapabilityExposeSource::Runner(ExposeSource::Capability(_)) |
| | CapabilityExposeSource::Resolver(ExposeSource::Capability(_)) |
| | CapabilityExposeSource::Directory(ExposeSource::Capability(_)) => { |
| // Currently we don't expose any of these capabilities based on a different |
| // capability. This is disallowed in validation, so we should never reach here. |
| panic!("non-protocol used a capability as a source"); |
| } |
| CapabilityExposeSource::Runner(ExposeSource::Framework) |
| | CapabilityExposeSource::Resolver(ExposeSource::Framework) => { |
| // Currently we don't expose any runners/resolvers from `framework`. |
| // TODO: This error should be caught by validation. We shouldn't have to handle |
| // this case here. |
| return Err(RoutingError::expose_from_framework_not_found( |
| pos.moniker(), |
| pos.capability.source_id(), |
| ) |
| .into()); |
| } |
| } |
| } |
| } |
| |
| /// Sets an epitaph on `server_end` for a capability routing failure, and logs the error. Logs a |
| /// failure to route a capability. Formats `err` as a `String`, but elides the type if the error is |
| /// a `RoutingError`, the common case. |
| pub(super) fn report_routing_failure( |
| target_moniker: &AbsoluteMoniker, |
| cap: &ComponentCapability, |
| err: &ModelError, |
| server_end: zx::Channel, |
| logger: Option<&dyn FmtArgsLogger>, |
| ) { |
| let _ = server_end.close_with_epitaph(routing_epitaph(err)); |
| let err_str = match err { |
| ModelError::RoutingError { err } => format!("{}", err), |
| _ => format!("{}", err), |
| }; |
| let log_msg = format!( |
| "Failed to route {} `{}` with target component `{}`: {}", |
| cap.type_name(), |
| cap.source_id(), |
| target_moniker, |
| err_str |
| ); |
| if let Some(l) = logger { |
| l.log(Level::Error, format_args!("{}", log_msg)); |
| } else { |
| MODEL_LOGGER.log(Level::Error, format_args!("{}", log_msg)) |
| } |
| } |
| |
| /// Converts `err` to a `zx::Status` to use as an epitaph on a routed channel. |
| fn routing_epitaph(err: &ModelError) -> zx::Status { |
| match err { |
| ModelError::RoutingError { err } => err.as_zx_status(), |
| ModelError::RightsError { err } => err.as_zx_status(), |
| ModelError::PolicyError { err } => err.as_zx_status(), |
| ModelError::Unsupported { .. } => zx::Status::NOT_SUPPORTED, |
| // Any other type of error is not expected. |
| _ => zx::Status::INTERNAL, |
| } |
| } |