| // Copyright 2020 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::model::{ |
| error::ModelError, |
| exposed_dir::ExposedDir, |
| hooks::{Event, EventError, EventErrorPayload, EventPayload, RuntimeInfo}, |
| namespace::IncomingNamespace, |
| realm::{BindReason, ExecutionState, Package, Realm, Runtime, WeakRealm}, |
| runner::Runner, |
| }, |
| cm_rust::data, |
| fidl::endpoints::{self, Proxy, ServerEnd}, |
| fidl_fuchsia_component_runner as fcrunner, |
| fidl_fuchsia_io::DirectoryProxy, |
| fuchsia_async as fasync, fuchsia_zircon as zx, |
| log::*, |
| moniker::AbsoluteMoniker, |
| std::sync::Arc, |
| vfs::execution_scope::ExecutionScope, |
| }; |
| |
| pub(super) async fn do_start( |
| realm: &Arc<Realm>, |
| bind_reason: &BindReason, |
| ) -> Result<(), ModelError> { |
| // Pre-flight check: if the component is already started, return now. Note that `bind_at` also |
| // performs this check before scheduling the action; here, we do it again while the action is |
| // registered so we avoid the risk of invoking the BeforeStart hook twice. |
| { |
| let execution = realm.lock_execution().await; |
| if let Some(res) = should_return_early(&execution, &realm.abs_moniker) { |
| return res; |
| } |
| } |
| |
| struct StartContext { |
| component_decl: cm_rust::ComponentDecl, |
| resolved_url: String, |
| runner: Arc<dyn Runner>, |
| pending_runtime: Runtime, |
| start_info: fcrunner::ComponentStartInfo, |
| controller_server_end: ServerEnd<fcrunner::ComponentControllerMarker>, |
| } |
| |
| let result = async move { |
| // Resolve the component. |
| let component = realm.resolve().await?; |
| |
| // Find the runner to use. |
| let runner = realm.resolve_runner().await.map_err(|e| { |
| error!("Failed to resolve runner for `{}`: {}", realm.abs_moniker, e); |
| e |
| })?; |
| |
| // Generate the Runtime which will be set in the Execution. |
| let (pending_runtime, start_info, controller_server_end) = make_execution_runtime( |
| realm.as_weak(), |
| component.resolved_url.clone(), |
| component.package, |
| &component.decl, |
| ) |
| .await?; |
| |
| Ok(StartContext { |
| component_decl: component.decl, |
| resolved_url: component.resolved_url.clone(), |
| runner, |
| pending_runtime, |
| start_info, |
| controller_server_end, |
| }) |
| } |
| .await; |
| |
| let mut start_context = match result { |
| Ok(start_context) => { |
| let event = Event::new_with_timestamp( |
| realm, |
| Ok(EventPayload::Started { |
| realm: realm.into(), |
| runtime: RuntimeInfo::from_runtime( |
| &start_context.pending_runtime, |
| start_context.resolved_url.clone(), |
| ), |
| component_decl: start_context.component_decl.clone(), |
| bind_reason: bind_reason.clone(), |
| }), |
| start_context.pending_runtime.timestamp, |
| ); |
| |
| realm.hooks.dispatch(&event).await?; |
| start_context |
| } |
| Err(e) => { |
| let event = Event::new(realm, Err(EventError::new(&e, EventErrorPayload::Started))); |
| realm.hooks.dispatch(&event).await?; |
| return Err(e); |
| } |
| }; |
| |
| // Set the Runtime in the Execution. From component manager's perspective, this indicates |
| // that the component has started. This may return early if the component is shut down. |
| { |
| let mut execution = realm.lock_execution().await; |
| if let Some(res) = should_return_early(&execution, &realm.abs_moniker) { |
| return res; |
| } |
| start_context.pending_runtime.watch_for_exit(realm.as_weak()); |
| execution.runtime = Some(start_context.pending_runtime); |
| } |
| |
| // It's possible that the component is stopped before getting here. If so, that's fine: the |
| // runner will start the component, but its stop or kill signal will be immediately set on the |
| // component controller. |
| start_context.runner.start(start_context.start_info, start_context.controller_server_end).await; |
| |
| Ok(()) |
| } |
| |
| /// Returns `Some(Result)` if `bind` should return early based on either of the following: |
| /// - The component instance is shut down. |
| /// - The component instance is already started. |
| pub fn should_return_early( |
| execution: &ExecutionState, |
| abs_moniker: &AbsoluteMoniker, |
| ) -> Option<Result<(), ModelError>> { |
| if execution.is_shut_down() { |
| Some(Err(ModelError::instance_shut_down(abs_moniker.clone()))) |
| } else if execution.runtime.is_some() { |
| Some(Ok(())) |
| } else { |
| None |
| } |
| } |
| |
| /// Returns a configured Runtime for a component and the start info (without actually starting |
| /// the component). |
| async fn make_execution_runtime( |
| realm: WeakRealm, |
| url: String, |
| package: Option<Package>, |
| decl: &cm_rust::ComponentDecl, |
| ) -> Result< |
| (Runtime, fcrunner::ComponentStartInfo, ServerEnd<fcrunner::ComponentControllerMarker>), |
| ModelError, |
| > { |
| // Create incoming/outgoing directories, and populate them. |
| let exposed_dir = ExposedDir::new(ExecutionScope::new(), realm.clone(), decl.clone())?; |
| let (outgoing_dir_client, outgoing_dir_server) = |
| zx::Channel::create().map_err(|e| ModelError::namespace_creation_failed(e))?; |
| let (runtime_dir_client, runtime_dir_server) = |
| zx::Channel::create().map_err(|e| ModelError::namespace_creation_failed(e))?; |
| let mut namespace = IncomingNamespace::new(package)?; |
| let ns = namespace.populate(realm, decl).await?; |
| |
| let (controller_client, controller_server) = |
| endpoints::create_endpoints::<fcrunner::ComponentControllerMarker>() |
| .expect("could not create component controller endpoints"); |
| let controller = |
| controller_client.into_proxy().expect("failed to create ComponentControllerProxy"); |
| // Set up channels into/out of the new component. These are absent from non-executable |
| // components. |
| let outgoing_dir_client = decl.get_used_runner().map(|_| { |
| DirectoryProxy::from_channel(fasync::Channel::from_channel(outgoing_dir_client).unwrap()) |
| }); |
| let runtime_dir_client = decl.get_used_runner().map(|_| { |
| DirectoryProxy::from_channel(fasync::Channel::from_channel(runtime_dir_client).unwrap()) |
| }); |
| let runtime = Runtime::start_from( |
| Some(namespace), |
| outgoing_dir_client, |
| runtime_dir_client, |
| exposed_dir, |
| Some(controller), |
| )?; |
| let start_info = fcrunner::ComponentStartInfo { |
| resolved_url: Some(url), |
| program: data::clone_option_dictionary(&decl.program), |
| ns: Some(ns), |
| outgoing_dir: Some(ServerEnd::new(outgoing_dir_server)), |
| runtime_dir: Some(ServerEnd::new(runtime_dir_server)), |
| ..fcrunner::ComponentStartInfo::EMPTY |
| }; |
| |
| Ok((runtime, start_info, controller_server)) |
| } |