[cm][routing] Separate route step from open step

Defines an API for capability routing. This is essentially
one function, route_capability(), which takes an enum-valued
argument specifying the capability type (directory,
protocol, etc) and the relationship to the target (via
a use, expose, etc). Policy verification now occurs
in the routing step for all capability types.

The routing API also includes a helper function which
routes both a storage capability and its backing
directory.

In a follow-up, this will become the public API of the
routing lib.

Bug: 61861
Change-Id: I1b41c3808fb00c13d7cd448cd1ba71c79db07a2b
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/514300
Reviewed-by: Adam Lesinski <adamlesinski@google.com>
Commit-Queue: Laura Peskin <pesk@google.com>
diff --git a/src/sys/component_manager/BUILD.gn b/src/sys/component_manager/BUILD.gn
index 0b71664..7d2e491 100644
--- a/src/sys/component_manager/BUILD.gn
+++ b/src/sys/component_manager/BUILD.gn
@@ -179,6 +179,7 @@
     "src/model/rights.rs",
     "src/model/routing/error.rs",
     "src/model/routing/mod.rs",
+    "src/model/routing/open.rs",
     "src/model/routing/service.rs",
     "src/model/routing_fns.rs",
     "src/model/runner.rs",
diff --git a/src/sys/component_manager/src/model/component.rs b/src/sys/component_manager/src/model/component.rs
index 68a97a1..c3e63d4 100644
--- a/src/sys/component_manager/src/model/component.rs
+++ b/src/sys/component_manager/src/model/component.rs
@@ -23,7 +23,9 @@
             namespace::IncomingNamespace,
             policy::GlobalPolicyChecker,
             resolver::ResolvedComponent,
-            routing::{self, OpenResourceError, RoutingError},
+            routing::{
+                self, OpenOptions, OpenResourceError, OpenRunnerOptions, RouteRequest, RoutingError,
+            },
             runner::{NullRunner, RemoteRunner, Runner},
         },
     },
@@ -370,14 +372,15 @@
                     create_endpoints::<fcrunner::ComponentRunnerMarker>()
                         .map_err(|_| ModelError::InsufficientResources)?;
                 let mut server_channel = server_channel.into_channel();
-                let capability_source = routing::route_runner(runner, self).await?;
-                routing::open_capability_at_source(
-                    OPEN_RIGHT_READABLE,
-                    MODE_TYPE_SERVICE,
-                    PathBuf::new(),
-                    capability_source,
+                let options = OpenRunnerOptions {
+                    flags: OPEN_RIGHT_READABLE,
+                    open_mode: MODE_TYPE_SERVICE,
+                    server_chan: &mut server_channel,
+                };
+                routing::route_and_open_capability(
+                    RouteRequest::Runner(runner.clone()),
                     self,
-                    &mut server_channel,
+                    OpenOptions::Runner(options),
                 )
                 .await?;
 
diff --git a/src/sys/component_manager/src/model/events/registry.rs b/src/sys/component_manager/src/model/events/registry.rs
index 1ffbaa0..5659489 100644
--- a/src/sys/component_manager/src/model/events/registry.rs
+++ b/src/sys/component_manager/src/model/events/registry.rs
@@ -21,7 +21,7 @@
                 HooksRegistration,
             },
             model::Model,
-            routing,
+            routing::{route_capability, RouteRequest, RouteSource},
         },
     },
     async_trait::async_trait,
@@ -389,15 +389,16 @@
         event_decl: UseEventDecl,
         component: &Arc<ComponentInstance>,
     ) -> Result<(CapabilityName, ExtendedMoniker), ModelError> {
-        match routing::route_event(event_decl, component).await? {
-            CapabilitySource::Framework {
+        let route_source = route_capability(RouteRequest::UseEvent(event_decl), component).await?;
+        match route_source {
+            RouteSource::Event(CapabilitySource::Framework {
                 capability: InternalCapability::Event(source_name),
                 component,
-            } => Ok((source_name, component.moniker.into())),
-            CapabilitySource::Builtin {
+            }) => Ok((source_name, component.moniker.into())),
+            RouteSource::Event(CapabilitySource::Builtin {
                 capability: InternalCapability::Event(source_name),
                 ..
-            } if source_name == "capability_ready".into() => {
+            }) if source_name == "capability_ready".into() => {
                 Ok((source_name, ExtendedMoniker::ComponentManager))
             }
             _ => unreachable!(),
diff --git a/src/sys/component_manager/src/model/namespace.rs b/src/sys/component_manager/src/model/namespace.rs
index bbb2345..6f082116 100644
--- a/src/sys/component_manager/src/model/namespace.rs
+++ b/src/sys/component_manager/src/model/namespace.rs
@@ -11,7 +11,10 @@
             error::ModelError,
             logging::{FmtArgsLogger, LOGGER as MODEL_LOGGER},
             rights::Rights,
-            routing,
+            routing::{
+                self, route_and_open_capability, OpenDirectoryOptions, OpenOptions,
+                OpenProtocolOptions, OpenServiceOptions, OpenStorageOptions, RouteRequest,
+            },
         },
     },
     anyhow::{Context, Error},
@@ -351,40 +354,29 @@
                 }
             };
             let mut server_end = server_end.into_zx_channel();
-            let res = match &use_ {
-                UseDecl::Directory(use_directory_decl) => {
-                    async {
-                        let (source, cap_state) =
-                            routing::route_directory(use_directory_decl.clone(), &target).await?;
-                        let relative_path = cap_state.make_relative_path(String::new());
-                        routing::open_capability_at_source(
-                            flags,
-                            fio::MODE_TYPE_DIRECTORY,
-                            relative_path,
-                            source,
-                            &target,
-                            &mut server_end,
-                        )
-                        .await
-                    }
-                    .await
-                }
-                UseDecl::Storage(use_storage_decl) => {
+            let (route_request, open_options) = match &use_ {
+                UseDecl::Directory(use_dir_decl) => (
+                    RouteRequest::UseDirectory(use_dir_decl.clone()),
+                    OpenOptions::Directory(OpenDirectoryOptions {
+                        flags,
+                        open_mode: fio::MODE_TYPE_DIRECTORY,
+                        relative_path: String::new(),
+                        server_chan: &mut server_end,
+                    }),
+                ),
+                UseDecl::Storage(use_storage_decl) => (
+                    RouteRequest::UseStorage(use_storage_decl.clone()),
                     // TODO(fxbug.dev/50716): This BindReason is wrong. We need to refactor the Storage
                     // capability to plumb through the correct BindReason.
-                    routing::route_and_open_storage_capability(
-                        use_storage_decl.clone(),
-                        fio::MODE_TYPE_DIRECTORY,
-                        &target,
-                        &mut server_end,
-                        &BindReason::Eager,
-                    )
-                    .await
-                }
+                    OpenOptions::Storage(OpenStorageOptions {
+                        open_mode: fio::MODE_TYPE_DIRECTORY,
+                        server_chan: &mut server_end,
+                        bind_reason: BindReason::Eager,
+                    }),
+                ),
                 _ => panic!("not a directory or storage capability"),
             };
-
-            if let Err(e) = res {
+            if let Err(e) = route_and_open_capability(route_request, &target, open_options).await {
                 routing::report_routing_failure(
                     &target,
                     &ComponentCapability::Use(use_),
@@ -455,32 +447,43 @@
                         }
                     };
                     let mut server_end = server_end.into_channel();
-                    let res: Result<_, ModelError> = match &use_ {
-                        UseDecl::Service(service) => routing::route_service(service.clone(), &target).await.map_err(|err| err.into()),
-                        UseDecl::Protocol(protocol) => routing::route_protocol(protocol.clone(), &target).await,
-                        _ => panic!("add_service_or_protocol_use called with non-service or protocol capability"),
-                    };
-                    let res = match res {
-                        Ok(source) => {
-                            routing::open_capability_at_source(
-                                flags,
-                                mode,
-                                PathBuf::from(relative_path),
-                                source,
-                                &target,
-                                &mut server_end,
-                            )
-                            .await
+                    let (route_request, open_options) = {
+                        match &use_ {
+                            UseDecl::Service(use_service_decl) => {
+                                (RouteRequest::UseService(use_service_decl.clone()),
+                                 OpenOptions::Service(
+                                     OpenServiceOptions{
+                                         flags,
+                                         open_mode: mode,
+                                         relative_path,
+                                         server_chan: &mut server_end
+                                     }
+                                 ))
+                            },
+                            UseDecl::Protocol(use_protocol_decl) => {
+                                (RouteRequest::UseProtocol(use_protocol_decl.clone()),
+                                 OpenOptions::Protocol(
+                                     OpenProtocolOptions{
+                                         flags,
+                                         open_mode:mode,
+                                         relative_path,
+                                         server_chan: &mut server_end
+                                     }
+                                 ))
+                            },
+                            _ => panic!("add_service_or_protocol_use called with non-service or protocol capability"),
                         }
-                        Err(err) => Err(err),
                     };
+
+                    let res = routing::route_and_open_capability(route_request, &target, open_options).await;
                     if let Err(e) = res {
                         routing::report_routing_failure(
                             &target,
                             &ComponentCapability::Use(use_),
                             &e,
                             server_end,
-                        ).await;
+                        )
+                        .await;
                     }
                 })
                 .detach();
diff --git a/src/sys/component_manager/src/model/resolver.rs b/src/sys/component_manager/src/model/resolver.rs
index c723c1d..5837c60 100644
--- a/src/sys/component_manager/src/model/resolver.rs
+++ b/src/sys/component_manager/src/model/resolver.rs
@@ -6,7 +6,7 @@
     crate::model::{
         component::{ComponentInstance, WeakComponentInstance},
         error::ModelError,
-        routing,
+        routing::{route_and_open_capability, OpenOptions, OpenResolverOptions, RouteRequest},
     },
     ::routing::component_instance::ComponentInstanceInterface,
     anyhow::Error,
@@ -15,7 +15,7 @@
     cm_rust::ResolverRegistration,
     fidl_fuchsia_io as fio, fidl_fuchsia_mem as fmem, fidl_fuchsia_sys2 as fsys,
     fuchsia_zircon::Status,
-    std::{collections::HashMap, path::PathBuf, sync::Arc},
+    std::{collections::HashMap, sync::Arc},
     thiserror::Error,
     url::Url,
 };
@@ -112,16 +112,15 @@
         let (proxy, server_end) = fidl::endpoints::create_proxy::<fsys::ComponentResolverMarker>()
             .map_err(ResolverError::internal)?;
         let component = self.component.upgrade().map_err(ResolverError::routing_error)?;
-        let capability_source = routing::route_resolver(self.registration.clone(), &component)
-            .await
-            .map_err(ResolverError::routing_error)?;
-        routing::open_capability_at_source(
-            fio::OPEN_RIGHT_READABLE | fio::OPEN_RIGHT_WRITABLE,
-            fio::MODE_TYPE_SERVICE,
-            PathBuf::new(),
-            capability_source,
+        let open_options = OpenResolverOptions {
+            flags: fio::OPEN_RIGHT_READABLE | fio::OPEN_RIGHT_WRITABLE,
+            open_mode: fio::MODE_TYPE_SERVICE,
+            server_chan: &mut server_end.into_channel(),
+        };
+        route_and_open_capability(
+            RouteRequest::Resolver(self.registration.clone()),
             &component,
-            &mut server_end.into_channel(),
+            OpenOptions::Resolver(open_options),
         )
         .await
         .map_err(ResolverError::routing_error)?;
diff --git a/src/sys/component_manager/src/model/routing/mod.rs b/src/sys/component_manager/src/model/routing/mod.rs
index d827a490..2e20d27 100644
--- a/src/sys/component_manager/src/model/routing/mod.rs
+++ b/src/sys/component_manager/src/model/routing/mod.rs
@@ -3,8 +3,10 @@
 // found in the LICENSE file.
 
 pub mod error;
+pub mod open;
 pub use error::OpenResourceError;
 pub use error::RoutingError;
+pub use open::*;
 
 mod service;
 
@@ -60,6 +62,30 @@
 const SERVICE_OPEN_FLAGS: u32 =
     fio::OPEN_FLAG_DESCRIBE | fio::OPEN_RIGHT_READABLE | fio::OPEN_RIGHT_WRITABLE;
 
+/// Routes a capability from `target` to its source. Opens the capability if routing succeeds.
+///
+/// If the capability is not allowed to be routed to the `target`, per the
+/// [`crate::model::policy::GlobalPolicyChecker`], the capability is not opened and an error
+/// is returned.
+pub(super) async fn route_and_open_capability(
+    route_request: RouteRequest,
+    target: &Arc<ComponentInstance>,
+    open_options: OpenOptions<'_>,
+) -> Result<(), ModelError> {
+    match route_request {
+        RouteRequest::UseStorage(use_storage_decl) => {
+            let (storage_source_info, relative_moniker) =
+                route_storage_and_backing_directory(use_storage_decl, target).await?;
+            open_storage_capability(storage_source_info, relative_moniker, target, open_options)
+                .await
+        }
+        _ => {
+            let route_source = route_capability(route_request, target).await?;
+            open_capability_at_source(OpenRequest::new(route_source, target, open_options)).await
+        }
+    }
+}
+
 /// Routes a capability from `target` to its source, starting from a `use_decl`.
 ///
 /// If the capability is allowed to be routed to the `target`, per the
@@ -79,55 +105,15 @@
     target: &Arc<ComponentInstance>,
     server_chan: &mut zx::Channel,
 ) -> Result<(), ModelError> {
-    match use_decl {
-        UseDecl::Service(use_service_decl) => {
-            let source = route_service(use_service_decl, target).await?;
-            open_capability_at_source(
-                flags,
-                open_mode,
-                PathBuf::from(relative_path),
-                source,
-                target,
-                server_chan,
-            )
-            .await
-        }
-        UseDecl::Protocol(use_protocol_decl) => {
-            let source = route_protocol(use_protocol_decl, target).await?;
-            open_capability_at_source(flags, open_mode, PathBuf::new(), source, target, server_chan)
-                .await
-        }
-        UseDecl::Directory(use_directory_decl) => {
-            let (source, directory_state) = route_directory(use_directory_decl, target).await?;
-            open_capability_at_source(
-                flags,
-                open_mode,
-                directory_state.make_relative_path(relative_path),
-                source,
-                target,
-                server_chan,
-            )
-            .await
-        }
-        UseDecl::Storage(use_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(
-                use_storage_decl,
-                open_mode,
-                target,
-                server_chan,
-                &BindReason::Eager,
-            )
-            .await
-        }
-        UseDecl::Event(_) | UseDecl::EventStream(_) => {
-            // These capabilities are not representable on a VFS.
-            Err(ModelError::unsupported(
-                "opening a capability that cannot be installed in a namespace",
-            ))
-        }
-    }
+    let route_request = request_for_namespace_capability_use(use_decl)?;
+    let open_options = OpenOptions::for_namespace_capability(
+        &route_request,
+        flags,
+        open_mode,
+        relative_path,
+        server_chan,
+    )?;
+    route_and_open_capability(route_request, target, open_options).await
 }
 
 /// Routes a capability from `target` to its source, starting from an `expose_decl`.
@@ -149,34 +135,40 @@
     target: &Arc<ComponentInstance>,
     server_chan: &mut zx::Channel,
 ) -> Result<(), ModelError> {
-    let (capability_source, relative_path) = match expose_decl {
-        ExposeDecl::Service(expose_service_decl) => (
-            route_service_from_expose(expose_service_decl, target).await?,
-            PathBuf::from(relative_path),
-        ),
-        ExposeDecl::Protocol(expose_protocol_decl) => {
-            (route_protocol_from_expose(expose_protocol_decl, target).await?, PathBuf::new())
-        }
-        ExposeDecl::Directory(expose_directory_decl) => {
-            let (capability_source, directory_state) =
-                route_directory_from_expose(expose_directory_decl, target).await?;
-            (capability_source, directory_state.make_relative_path(relative_path))
-        }
-        ExposeDecl::Runner(_) | ExposeDecl::Resolver(_) => {
-            return Err(ModelError::unsupported(
-                "opening a capability that cannot be installed in a namespace",
-            ))
-        }
-    };
-    open_capability_at_source(
+    let route_request = request_for_namespace_capability_expose(expose_decl)?;
+    let open_options = OpenOptions::for_namespace_capability(
+        &route_request,
         flags,
         open_mode,
         relative_path,
-        capability_source,
-        target,
         server_chan,
-    )
-    .await
+    )?;
+    route_and_open_capability(route_request, target, open_options).await
+}
+
+/// Create a new `RouteRequest` from a `UseDecl`, checking that the capability type can
+/// be installed in a namespace.
+fn request_for_namespace_capability_use(use_decl: UseDecl) -> Result<RouteRequest, ModelError> {
+    match use_decl {
+        UseDecl::Directory(decl) => Ok(RouteRequest::UseDirectory(decl)),
+        UseDecl::Protocol(decl) => Ok(RouteRequest::UseProtocol(decl)),
+        UseDecl::Service(decl) => Ok(RouteRequest::UseService(decl)),
+        UseDecl::Storage(decl) => Ok(RouteRequest::UseStorage(decl)),
+        _ => Err(ModelError::unsupported("capability cannot be installed in a namespace")),
+    }
+}
+
+/// Create a new `RouteRequest` from an `ExposeDecl`, checking that the capability type can
+/// be installed in a namespace.
+fn request_for_namespace_capability_expose(
+    expose_decl: ExposeDecl,
+) -> Result<RouteRequest, ModelError> {
+    match expose_decl {
+        ExposeDecl::Directory(decl) => Ok(RouteRequest::ExposeDirectory(decl)),
+        ExposeDecl::Protocol(decl) => Ok(RouteRequest::ExposeProtocol(decl)),
+        ExposeDecl::Service(decl) => Ok(RouteRequest::ExposeService(decl)),
+        _ => Err(ModelError::unsupported("capability cannot be installed in a namespace")),
+    }
 }
 
 /// The default provider for a ComponentCapability.
@@ -273,22 +265,10 @@
 /// Opens the capability at `source`, triggering a `CapabilityRouted` event and binding
 /// to the source component instance if necessary.
 ///
-/// If the capability is not allowed to be routed to the `target`, per the
-/// [`crate::model::policy::GlobalPolicyChecker`], the capability is not opened and an error
-/// is returned.
-///
 /// See [`fidl_fuchsia_io::Directory::Open`] for how the `flags`, `open_mode`, `relative_path`,
 /// and `server_chan` parameters are used in the open call.
-pub async fn open_capability_at_source(
-    flags: u32,
-    open_mode: u32,
-    relative_path: PathBuf,
-    source: CapabilitySource,
-    target: &Arc<ComponentInstance>,
-    server_chan: &mut zx::Channel,
-) -> Result<(), ModelError> {
-    target.try_get_policy_checker()?.can_route_capability(&source, &target.abs_moniker)?;
-
+async fn open_capability_at_source(open_request: OpenRequest<'_>) -> Result<(), ModelError> {
+    let OpenRequest { flags, open_mode, relative_path, source, target, server_chan } = open_request;
     // When serving a collection, routing hasn't reached the source. The CapabilityRouted event
     // should not fire, nor should hooks be able to modify the provider (which is hosted by
     // component_manager). Once a component is routed to from the collection, CapabilityRouted will
@@ -404,6 +384,22 @@
     }
 }
 
+/// Routes a storage capability from `target` to its source and deletes its isolated storage.
+pub(super) async fn route_and_delete_storage(
+    use_storage_decl: UseStorageDecl,
+    target: &Arc<ComponentInstance>,
+) -> Result<(), ModelError> {
+    let (storage_source_info, relative_moniker) =
+        route_storage_and_backing_directory(use_storage_decl, target).await?;
+
+    storage::delete_isolated_storage(
+        storage_source_info,
+        relative_moniker,
+        target.instance_id().as_ref(),
+    )
+    .await
+}
+
 /// 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.
@@ -432,6 +428,178 @@
         .await
 }
 
+/// Routes a storage capability from `target` to its source and opens its backing directory
+/// capability, binding to the component instance if necessary.
+///
+/// See [`fidl_fuchsia_io::Directory::Open`] for how the `flags`, `open_mode`, `relative_path`,
+/// and `server_chan` parameters are used in the open call.
+async fn open_storage_capability(
+    source: storage::StorageCapabilitySource,
+    relative_moniker: RelativeMoniker,
+    target: &Arc<ComponentInstance>,
+    options: OpenOptions<'_>,
+) -> Result<(), ModelError> {
+    let dir_source = source.storage_provider.clone();
+    let relative_moniker_2 = relative_moniker.clone();
+    match options {
+        OpenOptions::Storage(OpenStorageOptions { open_mode, server_chan, bind_reason }) => {
+            let storage_dir_proxy = storage::open_isolated_storage(
+                source,
+                relative_moniker,
+                target.instance_id().as_ref(),
+                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 {
+                        Some(r) => ExtendedMoniker::ComponentInstance(r.abs_moniker.clone()),
+                        None => ExtendedMoniker::ComponentManager,
+                    };
+                    ModelError::from(OpenResourceError::open_storage_failed(
+                        &moniker,
+                        &relative_moniker_2,
+                        "",
+                        e,
+                    ))
+                })?;
+            return Ok(());
+        }
+        _ => unreachable!("expected OpenStorageOptions"),
+    }
+}
+
+// Functions which route capabilities without opening.
+// TODO(https://fxbug.dev/61861): Move everything below this comment into src/sys/lib/routing.
+
+/// A request to route a capability, together with the data needed to do so.
+pub enum RouteRequest {
+    // Route a capability from an ExposeDecl.
+    ExposeDirectory(ExposeDirectoryDecl),
+    ExposeProtocol(ExposeProtocolDecl),
+    ExposeService(ExposeServiceDecl),
+
+    // Route a capability from a realm's environment.
+    Resolver(ResolverRegistration),
+    Runner(CapabilityName),
+
+    // Route the directory capability that backs a storage capability.
+    StorageBackingDirectory(StorageDecl),
+
+    // Route a capability from a UseDecl.
+    UseDirectory(UseDirectoryDecl),
+    UseEvent(UseEventDecl),
+    UseProtocol(UseProtocolDecl),
+    UseService(UseServiceDecl),
+    UseStorage(UseStorageDecl),
+}
+
+/// The data returned after successfully routing a capability to its source.
+#[derive(Debug)]
+pub enum RouteSource {
+    Directory(CapabilitySource, DirectoryState),
+    Event(CapabilitySource),
+    Protocol(CapabilitySource),
+    Resolver(CapabilitySource),
+    Runner(CapabilitySource),
+    Service(CapabilitySource),
+    Storage(CapabilitySource),
+    StorageBackingDirectory(storage::StorageCapabilitySource),
+}
+
+/// Routes a capability to its source.
+///
+/// If the capability is not allowed to be routed to the `target`, per the
+/// [`crate::model::policy::GlobalPolicyChecker`], then an error is returned.
+pub(super) async fn route_capability(
+    request: RouteRequest,
+    target: &Arc<ComponentInstance>,
+) -> Result<RouteSource, RoutingError> {
+    match request {
+        // Route from an ExposeDecl
+        RouteRequest::ExposeDirectory(expose_directory_decl) => {
+            route_directory_from_expose(expose_directory_decl, target).await
+        }
+        RouteRequest::ExposeProtocol(expose_protocol_decl) => {
+            route_protocol_from_expose(expose_protocol_decl, target).await
+        }
+        RouteRequest::ExposeService(expose_service_decl) => {
+            route_service_from_expose(expose_service_decl, target).await
+        }
+
+        // Route a resolver or runner from an environment
+        RouteRequest::Resolver(resolver_registration) => {
+            route_resolver(resolver_registration, target).await
+        }
+        RouteRequest::Runner(runner_name) => route_runner(&runner_name, target).await,
+
+        // Route the backing directory for a storage capability
+        RouteRequest::StorageBackingDirectory(storage_decl) => {
+            route_storage_backing_directory(storage_decl, target).await
+        }
+
+        // Route from a UseDecl
+        RouteRequest::UseDirectory(use_directory_decl) => {
+            route_directory(use_directory_decl, target).await
+        }
+        RouteRequest::UseEvent(use_event_decl) => route_event(use_event_decl, target).await,
+        RouteRequest::UseProtocol(use_protocol_decl) => {
+            route_protocol(use_protocol_decl, target).await
+        }
+        RouteRequest::UseService(use_service_decl) => route_service(use_service_decl, target).await,
+        RouteRequest::UseStorage(use_storage_decl) => route_storage(use_storage_decl, target).await,
+    }
+}
+
+/// Routes a storage capability and its backing directory capability to their sources,
+/// returning the data needed to open the storage capability.
+///
+/// If either capability is not allowed to be routed to the `target`, per the
+/// [`crate::model::policy::GlobalPolicyChecker`], then an error is returned.
+pub(super) async fn route_storage_and_backing_directory(
+    use_decl: UseStorageDecl,
+    target: &Arc<ComponentInstance>,
+) -> Result<(storage::StorageCapabilitySource, RelativeMoniker), RoutingError> {
+    // First route the storage capability to its source.
+    let storage_source = {
+        match route_capability(RouteRequest::UseStorage(use_decl), target).await? {
+            RouteSource::Storage(source) => source,
+            _ => unreachable!("expected RouteSource::Storage"),
+        }
+    };
+
+    let (storage_decl, storage_component_instance) = match storage_source {
+        CapabilitySource::Component {
+            capability: ComponentCapability::Storage(storage_decl),
+            component,
+        } => (storage_decl, component.upgrade()?),
+        _ => unreachable!("unexpected storage source"),
+    };
+    let relative_moniker = RelativeMoniker::from_absolute(
+        &storage_component_instance.abs_moniker,
+        &target.abs_moniker,
+    );
+
+    // Now route the backing directory capability.
+    match route_capability(
+        RouteRequest::StorageBackingDirectory(storage_decl),
+        &storage_component_instance,
+    )
+    .await?
+    {
+        RouteSource::StorageBackingDirectory(storage_source_info) => {
+            Ok((storage_source_info, relative_moniker))
+        }
+        _ => unreachable!("expected RouteSource::StorageBackingDirectory"),
+    }
+}
+
 make_noop_visitor!(ProtocolVisitor, {
     OfferDecl => OfferProtocolDecl,
     ExposeDecl => ExposeProtocolDecl,
@@ -439,10 +607,10 @@
 });
 
 /// Routes a Protocol capability from `target` to its source, starting from `use_decl`.
-pub async fn route_protocol(
+async fn route_protocol(
     use_decl: UseProtocolDecl,
     target: &Arc<ComponentInstance>,
-) -> Result<CapabilitySource, ModelError> {
+) -> Result<RouteSource, RoutingError> {
     let allowed_sources = AllowedSourcesBuilder::new()
         .framework(InternalCapability::Protocol)
         .builtin(InternalCapability::Protocol)
@@ -499,34 +667,40 @@
             &env_name,
             &target.abs_moniker,
         )?;
-        return Ok(source);
+        return Ok(RouteSource::Protocol(source));
     } else {
-        Ok(RoutingStrategy::new()
+        let source = RoutingStrategy::new()
             .use_::<UseProtocolDecl>()
             .offer::<OfferProtocolDecl>()
             .expose::<ExposeProtocolDecl>()
             .route(use_decl, target.clone(), allowed_sources, &mut ProtocolVisitor)
-            .await?)
+            .await?;
+
+        target.try_get_policy_checker()?.can_route_capability(&source, target.abs_moniker())?;
+        Ok(RouteSource::Protocol(source))
     }
 }
 
 /// Routes a Protocol capability from `target` to its source, starting from `expose_decl`.
-pub async fn route_protocol_from_expose(
+async fn route_protocol_from_expose(
     expose_decl: ExposeProtocolDecl,
     target: &Arc<ComponentInstance>,
-) -> Result<CapabilitySource, RoutingError> {
+) -> Result<RouteSource, RoutingError> {
     let allowed_sources = AllowedSourcesBuilder::new()
         .framework(InternalCapability::Protocol)
         .builtin(InternalCapability::Protocol)
         .namespace()
         .component()
         .capability();
-    RoutingStrategy::new()
+    let source = RoutingStrategy::new()
         .use_::<UseProtocolDecl>()
         .offer::<OfferProtocolDecl>()
         .expose::<ExposeProtocolDecl>()
         .route_from_expose(expose_decl, target.clone(), allowed_sources, &mut ProtocolVisitor)
-        .await
+        .await?;
+
+    target.try_get_policy_checker()?.can_route_capability(&source, target.abs_moniker())?;
+    Ok(RouteSource::Protocol(source))
 }
 
 make_noop_visitor!(ServiceVisitor, {
@@ -535,34 +709,40 @@
     CapabilityDecl => ServiceDecl,
 });
 
-pub async fn route_service(
+async fn route_service(
     use_decl: UseServiceDecl,
     target: &Arc<ComponentInstance>,
-) -> Result<CapabilitySource, RoutingError> {
+) -> Result<RouteSource, RoutingError> {
     let allowed_sources = AllowedSourcesBuilder::new().component().collection();
-    RoutingStrategy::new()
+    let source = RoutingStrategy::new()
         .use_::<UseServiceDecl>()
         .offer::<OfferServiceDecl>()
         .expose::<ExposeServiceDecl>()
         .route(use_decl, target.clone(), allowed_sources, &mut ServiceVisitor)
-        .await
+        .await?;
+
+    target.try_get_policy_checker()?.can_route_capability(&source, target.abs_moniker())?;
+    Ok(RouteSource::Service(source))
 }
 
 async fn route_service_from_expose(
     expose_decl: ExposeServiceDecl,
     target: &Arc<ComponentInstance>,
-) -> Result<CapabilitySource, RoutingError> {
+) -> Result<RouteSource, RoutingError> {
     let allowed_sources = AllowedSourcesBuilder::new().component().collection();
-    RoutingStrategy::new()
+    let source = RoutingStrategy::new()
         .use_::<UseServiceDecl>()
         .offer::<OfferServiceDecl>()
         .expose::<ExposeServiceDecl>()
         .route_from_expose(expose_decl, target.clone(), allowed_sources, &mut ServiceVisitor)
-        .await
+        .await?;
+
+    target.try_get_policy_checker()?.can_route_capability(&source, target.abs_moniker())?;
+    Ok(RouteSource::Service(source))
 }
 
 /// The accumulated state of routing a Directory capability.
-#[derive(Clone)]
+#[derive(Clone, Debug)]
 pub struct DirectoryState {
     rights: WalkState<Rights>,
     subdir: PathBuf,
@@ -638,10 +818,10 @@
 /// Routes a Directory capability from `target` to its source, starting from `use_decl`.
 /// Returns the capability source, along with a `DirectoryState` accumulated from traversing
 /// the route.
-pub async fn route_directory(
+async fn route_directory(
     use_decl: UseDirectoryDecl,
     target: &Arc<ComponentInstance>,
-) -> Result<(CapabilitySource, DirectoryState), RoutingError> {
+) -> Result<RouteSource, RoutingError> {
     let mut state = DirectoryState::new(use_decl.rights.clone(), use_decl.subdir.clone());
     if let UseSource::Framework = &use_decl.source {
         state.finalize(*READ_RIGHTS, None)?;
@@ -657,16 +837,18 @@
         .expose::<ExposeDirectoryDecl>()
         .route(use_decl, target.clone(), allowed_sources, &mut state)
         .await?;
-    Ok((source, state))
+
+    target.try_get_policy_checker()?.can_route_capability(&source, target.abs_moniker())?;
+    Ok(RouteSource::Directory(source, state))
 }
 
 /// Routes a Directory capability from `target` to its source, starting from `expose_decl`.
 /// Returns the capability source, along with a `DirectoryState` accumulated from traversing
 /// the route.
-pub async fn route_directory_from_expose(
+async fn route_directory_from_expose(
     expose_decl: ExposeDirectoryDecl,
     target: &Arc<ComponentInstance>,
-) -> Result<(CapabilitySource, DirectoryState), RoutingError> {
+) -> Result<RouteSource, RoutingError> {
     let mut state = DirectoryState { rights: WalkState::new(), subdir: PathBuf::new() };
     let allowed_sources = AllowedSourcesBuilder::new()
         .framework(InternalCapability::Directory)
@@ -679,69 +861,9 @@
         .expose::<ExposeDirectoryDecl>()
         .route_from_expose(expose_decl, target.clone(), allowed_sources, &mut state)
         .await?;
-    Ok((source, state))
-}
 
-/// Routes a storage capability from `target` to its source and opens its backing directory
-/// capability, binding to the component instance if necessary.
-///
-/// If the capability is not allowed to be routed to the `target`, per the
-/// [`crate::model::policy::GlobalPolicyChecker`], the capability is not opened and an error
-/// is returned.
-///
-/// See [`fidl_fuchsia_io::Directory::Open`] for how the `flags`, `open_mode`, `relative_path`,
-/// and `server_chan` parameters are used in the open call.
-pub async fn route_and_open_storage_capability(
-    use_decl: UseStorageDecl,
-    open_mode: u32,
-    target: &Arc<ComponentInstance>,
-    server_chan: &mut zx::Channel,
-    bind_reason: &BindReason,
-) -> Result<(), ModelError> {
-    let (storage_source_info, relative_moniker) = route_storage(use_decl, target).await?;
-    let dir_source = 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,
-        target.instance_id().as_ref(),
-        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 {
-                Some(r) => ExtendedMoniker::ComponentInstance(r.abs_moniker.clone()),
-                None => ExtendedMoniker::ComponentManager,
-            };
-            ModelError::from(OpenResourceError::open_storage_failed(
-                &moniker,
-                &relative_moniker_2,
-                "",
-                e,
-            ))
-        },
-    )?;
-    Ok(())
-}
-
-/// Routes a storage capability from `target` to its source and deletes its isolated storage.
-pub(super) async fn route_and_delete_storage(
-    use_decl: UseStorageDecl,
-    target: &Arc<ComponentInstance>,
-) -> Result<(), ModelError> {
-    let (storage_source_info, relative_moniker) = route_storage(use_decl, target).await?;
-    storage::delete_isolated_storage(
-        storage_source_info,
-        relative_moniker,
-        target.instance_id().as_ref(),
-    )
-    .await
+    target.try_get_policy_checker()?.can_route_capability(&source, target.abs_moniker())?;
+    Ok(RouteSource::Directory(source, state))
 }
 
 make_noop_visitor!(StorageVisitor, {
@@ -754,39 +876,24 @@
 async fn route_storage(
     use_decl: UseStorageDecl,
     target: &Arc<ComponentInstance>,
-) -> Result<(storage::StorageCapabilitySource, RelativeMoniker), ModelError> {
+) -> Result<RouteSource, RoutingError> {
     let allowed_sources = AllowedSourcesBuilder::new().component();
-    let storage_source = RoutingStrategy::new()
+    let source = RoutingStrategy::new()
         .use_::<UseStorageDecl>()
         .offer::<OfferStorageDecl>()
         .route(use_decl, target.clone(), allowed_sources, &mut StorageVisitor)
         .await?;
-    target.try_get_policy_checker()?.can_route_capability(&storage_source, &target.abs_moniker)?;
-    let (storage_decl, storage_component_instance) = match storage_source {
-        CapabilitySource::Component {
-            capability: ComponentCapability::Storage(storage_decl),
-            component,
-        } => (storage_decl, component.upgrade()?),
-        _ => unreachable!("unexpected storage source"),
-    };
-    let relative_moniker = RelativeMoniker::from_absolute(
-        &storage_component_instance.abs_moniker,
-        &target.abs_moniker,
-    );
 
-    // The storage capability was routed to its source. Now route the backing directory capability.
-    Ok((
-        route_storage_backing_directory(storage_decl, storage_component_instance).await?,
-        relative_moniker,
-    ))
+    target.try_get_policy_checker()?.can_route_capability(&source, target.abs_moniker())?;
+    Ok(RouteSource::Storage(source))
 }
 
 /// Routes the backing Directory capability of a Storage capability from `target` to its source,
 /// starting from `storage_decl`.
-pub async fn route_storage_backing_directory(
+async fn route_storage_backing_directory(
     storage_decl: StorageDecl,
-    target: Arc<ComponentInstance>,
-) -> Result<storage::StorageCapabilitySource, RoutingError> {
+    target: &Arc<ComponentInstance>,
+) -> Result<RouteSource, RoutingError> {
     // Storage rights are always READ+WRITE.
     let mut state = DirectoryState::new(*READ_RIGHTS | *WRITE_RIGHTS, None);
     let allowed_sources = AllowedSourcesBuilder::new().component().namespace();
@@ -794,9 +901,11 @@
         .registration::<StorageDeclAsRegistration>()
         .offer::<OfferDirectoryDecl>()
         .expose::<ExposeDirectoryDecl>()
-        .route(storage_decl.clone().into(), target, allowed_sources, &mut state)
+        .route(storage_decl.clone().into(), target.clone(), allowed_sources, &mut state)
         .await?;
 
+    target.try_get_policy_checker()?.can_route_capability(&source, target.abs_moniker())?;
+
     let (dir_source_path, dir_source_instance) = match source {
         CapabilitySource::Component { capability, component } => (
             capability.source_path().expect("directory has no source path?").clone(),
@@ -810,12 +919,12 @@
 
     let dir_subdir = if state.subdir == Path::new("") { None } else { Some(state.subdir) };
 
-    Ok(storage::StorageCapabilitySource {
+    Ok(RouteSource::StorageBackingDirectory(storage::StorageCapabilitySource {
         storage_provider: dir_source_instance,
         backing_directory_path: dir_source_path,
         backing_directory_subdir: dir_subdir,
         storage_subdir: storage_decl.subdir.clone(),
-    })
+    }))
 }
 
 make_noop_visitor!(RunnerVisitor, {
@@ -826,10 +935,10 @@
 
 /// Finds a Runner capability that matches `runner` in the `target`'s environment, and then
 /// routes the Runner capability from the environment's component instance to its source.
-pub async fn route_runner(
+async fn route_runner(
     runner: &CapabilityName,
     target: &Arc<ComponentInstance>,
-) -> Result<CapabilitySource, RoutingError> {
+) -> Result<RouteSource, RoutingError> {
     // Find the component instance in which the runner was registered with the environment.
     let (env_component_instance, registration_decl) =
         match target.environment().get_registered_runner(&runner)? {
@@ -838,10 +947,10 @@
             }
             Some((ExtendedInstance::AboveRoot(top_instance), reg)) => {
                 // Root environment.
-                return Ok(CapabilitySource::Builtin {
+                return Ok(RouteSource::Runner(CapabilitySource::Builtin {
                     capability: InternalCapability::Runner(reg.source_name.clone()),
                     top_instance: Arc::downgrade(&top_instance),
-                });
+                }));
             }
             None => {
                 return Err(RoutingError::UseFromEnvironmentNotFound {
@@ -855,12 +964,15 @@
 
     let allowed_sources =
         AllowedSourcesBuilder::new().builtin(InternalCapability::Runner).component();
-    RoutingStrategy::new()
+    let source = RoutingStrategy::new()
         .registration::<RunnerRegistration>()
         .offer::<OfferRunnerDecl>()
         .expose::<ExposeRunnerDecl>()
         .route(registration_decl, env_component_instance, allowed_sources, &mut RunnerVisitor)
-        .await
+        .await?;
+
+    target.try_get_policy_checker()?.can_route_capability(&source, target.abs_moniker())?;
+    Ok(RouteSource::Runner(source))
 }
 
 make_noop_visitor!(ResolverVisitor, {
@@ -870,18 +982,22 @@
 });
 
 /// Routes a Resolver capability from `target` to its source, starting from `registration_decl`.
-pub async fn route_resolver(
+async fn route_resolver(
     registration: ResolverRegistration,
     target: &Arc<ComponentInstance>,
-) -> Result<CapabilitySource, RoutingError> {
+) -> Result<RouteSource, RoutingError> {
     let allowed_sources =
         AllowedSourcesBuilder::new().builtin(InternalCapability::Resolver).component();
-    RoutingStrategy::new()
+
+    let source = RoutingStrategy::new()
         .registration::<ResolverRegistration>()
         .offer::<OfferResolverDecl>()
         .expose::<ExposeResolverDecl>()
         .route(registration, target.clone(), allowed_sources, &mut ResolverVisitor)
-        .await
+        .await?;
+
+    target.try_get_policy_checker()?.can_route_capability(&source, target.abs_moniker())?;
+    Ok(RouteSource::Resolver(source))
 }
 
 /// State accumulated from routing an Event capability to its source.
@@ -916,10 +1032,10 @@
 }
 
 /// Routes an Event capability from `target` to its source, starting from `use_decl`.
-pub async fn route_event(
+async fn route_event(
     use_decl: UseEventDecl,
     target: &Arc<ComponentInstance>,
-) -> Result<CapabilitySource, ModelError> {
+) -> Result<RouteSource, RoutingError> {
     let mut state = EventState {
         filter_state: WalkState::at(EventFilter::new(use_decl.filter.clone())),
         modes_state: WalkState::at(EventModeSet::new(use_decl.mode.clone())),
@@ -933,6 +1049,7 @@
         .offer::<OfferEventDecl>()
         .route(use_decl, target.clone(), allowed_sources, &mut state)
         .await?;
-    target.try_get_policy_checker()?.can_route_capability(&source, &target.abs_moniker)?;
-    Ok(source)
+
+    target.try_get_policy_checker()?.can_route_capability(&source, target.abs_moniker())?;
+    Ok(RouteSource::Event(source))
 }
diff --git a/src/sys/component_manager/src/model/routing/open.rs b/src/sys/component_manager/src/model/routing/open.rs
new file mode 100644
index 0000000..024174e
--- /dev/null
+++ b/src/sys/component_manager/src/model/routing/open.rs
@@ -0,0 +1,198 @@
+// 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::CapabilitySource,
+        model::{
+            component::{BindReason, ComponentInstance},
+            error::ModelError,
+            routing::{RouteRequest, RouteSource},
+        },
+    },
+    fuchsia_zircon as zx,
+    std::{path::PathBuf, sync::Arc},
+};
+
+/// A container for the data needed to open a capability.
+pub enum OpenOptions<'a> {
+    Directory(OpenDirectoryOptions<'a>),
+    Protocol(OpenProtocolOptions<'a>),
+    Resolver(OpenResolverOptions<'a>),
+    Runner(OpenRunnerOptions<'a>),
+    Service(OpenServiceOptions<'a>),
+    Storage(OpenStorageOptions<'a>),
+}
+
+impl<'a> OpenOptions<'a> {
+    /// Creates an `OpenOptions` for a capability that can be installed in a namespace,
+    /// or an error if `route_request` specifies a capability that cannot be installed
+    /// in a namespace.
+    pub fn for_namespace_capability(
+        route_request: &RouteRequest,
+        flags: u32,
+        open_mode: u32,
+        relative_path: String,
+        server_chan: &'a mut zx::Channel,
+    ) -> Result<Self, ModelError> {
+        match route_request {
+            RouteRequest::UseDirectory(_) | RouteRequest::ExposeDirectory(_) => {
+                Ok(Self::Directory(OpenDirectoryOptions {
+                    flags,
+                    open_mode,
+                    relative_path,
+                    server_chan,
+                }))
+            }
+            RouteRequest::UseProtocol(_) | RouteRequest::ExposeProtocol(_) => {
+                Ok(Self::Protocol(OpenProtocolOptions {
+                    flags,
+                    open_mode,
+                    relative_path,
+                    server_chan,
+                }))
+            }
+            RouteRequest::UseService(_) | RouteRequest::ExposeService(_) => {
+                Ok(Self::Service(OpenServiceOptions {
+                    flags,
+                    open_mode,
+                    relative_path,
+                    server_chan,
+                }))
+            }
+            // TODO(fxbug.dev/50716): This BindReason is wrong. We need to refactor the Storage
+            // capability to plumb through the correct BindReason.
+            RouteRequest::UseStorage(_) => Ok(Self::Storage(OpenStorageOptions {
+                open_mode,
+                server_chan,
+                bind_reason: BindReason::Eager,
+            })),
+            _ => Err(ModelError::unsupported("capability cannot be installed in a namespace")),
+        }
+    }
+}
+
+pub struct OpenDirectoryOptions<'a> {
+    pub flags: u32,
+    pub open_mode: u32,
+    pub relative_path: String,
+    pub server_chan: &'a mut zx::Channel,
+}
+
+pub struct OpenProtocolOptions<'a> {
+    pub flags: u32,
+    pub open_mode: u32,
+    pub relative_path: String,
+    pub server_chan: &'a mut zx::Channel,
+}
+
+pub struct OpenResolverOptions<'a> {
+    pub flags: u32,
+    pub open_mode: u32,
+    pub server_chan: &'a mut zx::Channel,
+}
+
+pub struct OpenRunnerOptions<'a> {
+    pub flags: u32,
+    pub open_mode: u32,
+    pub server_chan: &'a mut zx::Channel,
+}
+
+pub struct OpenServiceOptions<'a> {
+    pub flags: u32,
+    pub open_mode: u32,
+    pub relative_path: String,
+    pub server_chan: &'a mut zx::Channel,
+}
+
+pub struct OpenStorageOptions<'a> {
+    pub open_mode: u32,
+    pub server_chan: &'a mut zx::Channel,
+    pub bind_reason: BindReason,
+}
+
+/// A request to open a capability at its source.
+pub struct OpenRequest<'a> {
+    pub flags: u32,
+    pub open_mode: u32,
+    pub relative_path: PathBuf,
+    pub source: CapabilitySource,
+    pub target: &'a Arc<ComponentInstance>,
+    pub server_chan: &'a mut zx::Channel,
+}
+
+impl<'a> OpenRequest<'a> {
+    /// Creates a request to open a capability with source `route_source` for `target`.
+    pub fn new(
+        route_source: RouteSource,
+        target: &'a Arc<ComponentInstance>,
+        options: OpenOptions<'a>,
+    ) -> Self {
+        match route_source {
+            RouteSource::Directory(source, directory_state) => {
+                if let OpenOptions::Directory(open_dir_options) = options {
+                    return Self {
+                        flags: open_dir_options.flags,
+                        open_mode: open_dir_options.open_mode,
+                        relative_path: directory_state
+                            .make_relative_path(open_dir_options.relative_path),
+                        source,
+                        target,
+                        server_chan: open_dir_options.server_chan,
+                    };
+                }
+            }
+            RouteSource::Protocol(source) => {
+                if let OpenOptions::Protocol(open_protocol_options) = options {
+                    return Self {
+                        flags: open_protocol_options.flags,
+                        open_mode: open_protocol_options.open_mode,
+                        relative_path: PathBuf::from(open_protocol_options.relative_path),
+                        source,
+                        target,
+                        server_chan: open_protocol_options.server_chan,
+                    };
+                }
+            }
+            RouteSource::Service(source) => {
+                if let OpenOptions::Service(open_service_options) = options {
+                    return Self {
+                        flags: open_service_options.flags,
+                        open_mode: open_service_options.open_mode,
+                        relative_path: PathBuf::from(open_service_options.relative_path),
+                        source,
+                        target,
+                        server_chan: open_service_options.server_chan,
+                    };
+                }
+            }
+            RouteSource::Resolver(source) => {
+                if let OpenOptions::Resolver(open_resolver_options) = options {
+                    return Self {
+                        flags: open_resolver_options.flags,
+                        open_mode: open_resolver_options.open_mode,
+                        relative_path: PathBuf::new(),
+                        source,
+                        target,
+                        server_chan: open_resolver_options.server_chan,
+                    };
+                }
+            }
+            RouteSource::Runner(source) => {
+                if let OpenOptions::Runner(open_runner_options) = options {
+                    return Self {
+                        flags: open_runner_options.flags,
+                        open_mode: open_runner_options.open_mode,
+                        relative_path: PathBuf::new(),
+                        source,
+                        target,
+                        server_chan: open_runner_options.server_chan,
+                    };
+                }
+            }
+            _ => panic!("unsupported route source"),
+        }
+        panic!("route source type did not match option type")
+    }
+}
diff --git a/src/sys/component_manager/src/model/routing/service.rs b/src/sys/component_manager/src/model/routing/service.rs
index f848027..4d855d5e 100644
--- a/src/sys/component_manager/src/model/routing/service.rs
+++ b/src/sys/component_manager/src/model/routing/service.rs
@@ -8,7 +8,7 @@
         model::{
             component::{ComponentInstance, WeakComponentInstance},
             error::ModelError,
-            routing::open_capability_at_source,
+            routing::{open_capability_at_source, OpenRequest},
         },
     },
     ::routing::capability_source::CollectionCapabilityProvider,
@@ -123,14 +123,14 @@
             } else {
                 Path::new(DEFAULT_INSTANCE).join(path.into_string())
             };
-            if let Err(err) = open_capability_at_source(
+            if let Err(err) = open_capability_at_source(OpenRequest {
                 flags,
-                mode,
+                open_mode: mode,
                 relative_path,
                 source,
-                &target,
-                &mut server_end,
-            )
+                target: &target,
+                server_chan: &mut server_end,
+            })
             .await
             {
                 let _ = server_end.close_with_epitaph(err.as_zx_status());
diff --git a/src/sys/component_manager/src/model/storage/admin_protocol.rs b/src/sys/component_manager/src/model/storage/admin_protocol.rs
index 2fa0120..8902fb9 100644
--- a/src/sys/component_manager/src/model/storage/admin_protocol.rs
+++ b/src/sys/component_manager/src/model/storage/admin_protocol.rs
@@ -18,7 +18,8 @@
             component::{BindReason, WeakComponentInstance},
             error::ModelError,
             hooks::{Event, EventPayload, EventType, Hook, HooksRegistration},
-            routing, storage,
+            routing::{route_capability, RouteRequest, RouteSource},
+            storage,
         },
     },
     anyhow::{format_err, Error},
@@ -175,8 +176,14 @@
         })?;
         let storage_moniker = component.abs_moniker.clone();
 
-        let storage_capability_source_info =
-            routing::route_storage_backing_directory(storage_decl, component.clone()).await?;
+        let storage_capability_source_info = {
+            match route_capability(RouteRequest::StorageBackingDirectory(storage_decl), &component)
+                .await?
+            {
+                RouteSource::StorageBackingDirectory(storage_source) => storage_source,
+                _ => unreachable!("expected RouteSource::StorageBackingDirectory"),
+            }
+        };
 
         let mut stream = ServerEnd::<fsys::StorageAdminMarker>::new(server_end)
             .into_stream()
diff --git a/src/sys/component_manager/src/model/storage/mod.rs b/src/sys/component_manager/src/model/storage/mod.rs
index d1f7d52..b9a7855 100644
--- a/src/sys/component_manager/src/model/storage/mod.rs
+++ b/src/sys/component_manager/src/model/storage/mod.rs
@@ -116,7 +116,7 @@
 
 /// Information returned by the route_storage_capability function on the source of a storage
 /// capability.
-#[derive(Clone)]
+#[derive(Clone, Debug)]
 pub struct StorageCapabilitySource {
     /// The component that's providing the backing directory capability for this storage
     /// capability. If None, then the backing directory comes from component_manager's namespace.
diff --git a/src/sys/component_manager/src/model/tests/routing.rs b/src/sys/component_manager/src/model/tests/routing.rs
index 2cba38c..dff1ed5 100644
--- a/src/sys/component_manager/src/model/tests/routing.rs
+++ b/src/sys/component_manager/src/model/tests/routing.rs
@@ -17,7 +17,7 @@
             events::registry::EventSubscription,
             hooks::{Event, EventPayload, EventType, Hook, HooksRegistration},
             rights,
-            routing::{self, RoutingError},
+            routing::{self, RouteRequest, RouteSource, RoutingError},
             testing::{routing_test_helpers::*, test_helpers::*},
         },
     },
@@ -2619,16 +2619,14 @@
 
     // Now attempt to route the service from "c". Should fail because "b" does not exist so we
     // cannot follow it.
-    let err = routing::route_protocol(use_protocol_decl, &realm_c)
+    let err = routing::route_capability(RouteRequest::UseProtocol(use_protocol_decl), &realm_c)
         .await
         .expect_err("routing unexpectedly succeeded");
     assert_matches!(
         err,
-        ModelError::RoutingError {
-            err: RoutingError::ComponentInstanceError(
-                ComponentInstanceError::InstanceNotFound { moniker }
-            )
-        } if moniker == vec!["coll:b:1"].into()
+        RoutingError::ComponentInstanceError(
+            ComponentInstanceError::InstanceNotFound { moniker }
+        ) if moniker == vec!["coll:b:1"].into()
     );
 }
 
@@ -4407,12 +4405,12 @@
     let root_instance = test.model.look_up(&AbsoluteMoniker::root()).await.expect("root instance");
     let expected_source_moniker = AbsoluteMoniker::parse_string_without_instances("/b").unwrap();
     assert_matches!(
-        routing::route_protocol_from_expose(expose_decl, &root_instance).await,
-        Ok(
+    routing::route_capability(RouteRequest::ExposeProtocol(expose_decl), &root_instance).await,
+        Ok(RouteSource::Protocol(
             CapabilitySource::Component {
                 capability: ComponentCapability::Protocol(protocol_decl),
                 component,
-            }
+            })
         ) if protocol_decl == expected_protocol_decl && component.moniker == expected_source_moniker
     );
 }
@@ -4906,13 +4904,14 @@
     let test = RoutingTestBuilder::new("a", components).build().await;
     let b_component = test.model.look_up(&vec!["b:0"].into()).await.expect("b instance");
     let a_component = test.model.look_up(&AbsoluteMoniker::root()).await.expect("root instance");
-    let source =
-        routing::route_service(use_decl, &b_component).await.expect("failed to route service");
+    let source = routing::route_capability(RouteRequest::UseService(use_decl), &b_component)
+        .await
+        .expect("failed to route service");
     match source {
-        CapabilitySource::Component {
+        RouteSource::Service(CapabilitySource::Component {
             capability: ComponentCapability::Service(ServiceDecl { name, source_path }),
             component,
-        } => {
+        }) => {
             assert_eq!(name, CapabilityName("foo".into()));
             assert_eq!(source_path, "/svc/foo".parse::<CapabilityPath>().unwrap());
             assert!(Arc::ptr_eq(&component.upgrade().unwrap(), &a_component));
@@ -4973,15 +4972,16 @@
     let test = RoutingTestBuilder::new("a", components).build().await;
     let b_component = test.model.look_up(&vec!["b:0"].into()).await.expect("b instance");
     let c_component = test.model.look_up(&vec!["c:0"].into()).await.expect("c instance");
-    let source =
-        routing::route_service(use_decl, &b_component).await.expect("failed to route service");
+    let source = routing::route_capability(RouteRequest::UseService(use_decl), &b_component)
+        .await
+        .expect("failed to route service");
 
     // Verify this source comes from `c`.
     match source {
-        CapabilitySource::Component {
+        RouteSource::Service(CapabilitySource::Component {
             capability: ComponentCapability::Service(ServiceDecl { name, source_path }),
             component,
-        } => {
+        }) => {
             assert_eq!(name, CapabilityName("foo".into()));
             assert_eq!(source_path, "/svc/foo".parse::<CapabilityPath>().unwrap());
             assert!(Arc::ptr_eq(&component.upgrade().unwrap(), &c_component));
@@ -5018,10 +5018,16 @@
     let test = RoutingTestBuilder::new("a", components).build().await;
     let b_component = test.model.look_up(&vec!["b:0"].into()).await.expect("b instance");
     let a_component = test.model.look_up(&AbsoluteMoniker::root()).await.expect("root instance");
-    let source =
-        routing::route_service(use_decl, &b_component).await.expect("failed to route service");
+    let source = routing::route_capability(RouteRequest::UseService(use_decl), &b_component)
+        .await
+        .expect("failed to route service");
     match source {
-        CapabilitySource::Collection { collection_name, source_name, component, .. } => {
+        RouteSource::Service(CapabilitySource::Collection {
+            collection_name,
+            source_name,
+            component,
+            ..
+        }) => {
             assert_eq!(collection_name, "coll");
             assert_eq!(source_name, CapabilityName("foo".into()));
             assert!(Arc::ptr_eq(&component.upgrade().unwrap(), &a_component));
@@ -5119,10 +5125,13 @@
 
     let client_component =
         test.model.look_up(&vec!["client:0"].into()).await.expect("client instance");
-    let source =
-        routing::route_service(use_decl, &client_component).await.expect("failed to route service");
+    let source = routing::route_capability(RouteRequest::UseService(use_decl), &client_component)
+        .await
+        .expect("failed to route service");
     let capability_provider = match source {
-        CapabilitySource::Collection { capability_provider, .. } => capability_provider,
+        RouteSource::Service(CapabilitySource::Collection { capability_provider, .. }) => {
+            capability_provider
+        }
         _ => panic!("bad capability source"),
     };
 
diff --git a/src/sys/lib/routing/src/error.rs b/src/sys/lib/routing/src/error.rs
index af76834..541a361 100644
--- a/src/sys/lib/routing/src/error.rs
+++ b/src/sys/lib/routing/src/error.rs
@@ -349,7 +349,10 @@
 
     /// Convert this error into its approximate `zx::Status` equivalent.
     pub fn as_zx_status(&self) -> zx::Status {
-        zx::Status::UNAVAILABLE
+        match self {
+            RoutingError::PolicyError(_) => zx::Status::ACCESS_DENIED,
+            _ => zx::Status::UNAVAILABLE,
+        }
     }
 
     pub fn source_instance_stopped(moniker: &AbsoluteMoniker) -> Self {