| // 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::{ |
| core::{ |
| collection::{Components, CoreDataDeps, ManifestData, Manifests, Zbi}, |
| package::collector::ROOT_RESOURCE, |
| }, |
| verify::collection::V2ComponentModel, |
| }, |
| anyhow::{anyhow, Context, Result}, |
| cm_fidl_analyzer::{component_model::ModelBuilderForAnalyzer, node_path::NodePath}, |
| cm_rust::{ComponentDecl, FidlIntoNative, RegistrationSource, RunnerRegistration}, |
| fidl::encoding::decode_persistent, |
| fidl_fuchsia_component_decl as fdecl, fidl_fuchsia_component_internal as component_internal, |
| fuchsia_url::{boot_url::BootUrl, AbsoluteComponentUrl}, |
| log::{error, info, warn}, |
| once_cell::sync::Lazy, |
| routing::{ |
| component_id_index::ComponentIdIndex, config::RuntimeConfig, environment::RunnerRegistry, |
| }, |
| scrutiny::model::{collector::DataCollector, model::DataModel}, |
| serde::{Deserialize, Serialize}, |
| serde_json5::from_reader, |
| std::{collections::HashMap, convert::TryFrom, fs::File, path::PathBuf, sync::Arc}, |
| url::Url, |
| }; |
| |
| // The default root component URL used to identify the root instance of the component model |
| // unless the RuntimeConfig specifies a different root URL. |
| pub static DEFAULT_ROOT_URL: Lazy<Url> = Lazy::new(|| { |
| Url::parse( |
| &BootUrl::new_resource("/".to_string(), ROOT_RESOURCE.to_string()).unwrap().to_string(), |
| ) |
| .unwrap() |
| }); |
| |
| // The path to the runtime config in bootfs. |
| pub const DEFAULT_CONFIG_PATH: &str = "config/component_manager"; |
| |
| // The name of the ELF runner. |
| pub const ELF_RUNNER_NAME: &str = "elf"; |
| // The name of the RealmBuilder runner. |
| pub const REALM_BUILDER_RUNNER_NAME: &str = "realm_builder"; |
| |
| #[derive(Deserialize, Serialize)] |
| pub struct DynamicComponent { |
| pub url: AbsoluteComponentUrl, |
| pub environment: Option<String>, |
| } |
| |
| #[derive(Deserialize, Serialize)] |
| pub struct ComponentTreeConfig { |
| pub dynamic_components: HashMap<NodePath, DynamicComponent>, |
| } |
| |
| pub struct V2ComponentModelDataCollector {} |
| |
| impl V2ComponentModelDataCollector { |
| pub fn new() -> Self { |
| Self {} |
| } |
| |
| fn get_decls(&self, model: &Arc<DataModel>) -> Result<HashMap<Url, ComponentDecl>> { |
| let mut decls = HashMap::new(); |
| let mut urls = HashMap::new(); |
| |
| let components = |
| model.get::<Components>().context("Unable to retrieve components from the model")?; |
| for component in components.entries.iter().filter(|x| x.version == 2) { |
| urls.insert(component.id, component.url.clone()); |
| } |
| |
| for manifest in model |
| .get::<Manifests>() |
| .context("Unable to retrieve manifests from the model")? |
| .entries |
| .iter() |
| { |
| if let ManifestData::Version2(decl_base64) = &manifest.manifest { |
| match urls.remove(&manifest.component_id) { |
| Some(url) => { |
| let result: Result<fdecl::Component, fidl::Error> = decode_persistent( |
| &base64::decode(&decl_base64) |
| .context("Unable to decode base64 v2 manifest")?, |
| ); |
| match result { |
| Ok(decl) => { |
| decls.insert(url, decl.fidl_into_native()); |
| } |
| Err(err) => { |
| error!( |
| "Manifest for component: {} is corrupted. Error: {}", |
| url, err |
| ); |
| } |
| } |
| } |
| None => { |
| return Err(anyhow!( |
| "No component URL found for v2 component with id {}", |
| manifest.component_id |
| )); |
| } |
| } |
| } |
| } |
| Ok(decls) |
| } |
| |
| fn get_runtime_config(&self, config_path: &str, zbi: &Zbi) -> Result<RuntimeConfig> { |
| match zbi.bootfs.get(config_path) { |
| Some(config_data) => Ok(RuntimeConfig::try_from( |
| decode_persistent::<component_internal::Config>(&config_data) |
| .context("Unable to decode runtime config")?, |
| ) |
| .context("Unable to parse runtime config")?), |
| None => Err(anyhow!("file {} not found in bootfs", config_path.to_string())), |
| } |
| } |
| |
| fn get_component_id_index( |
| &self, |
| index_path: Option<&str>, |
| zbi: &Zbi, |
| ) -> Result<ComponentIdIndex> { |
| match index_path { |
| Some(path) => { |
| let split: Vec<&str> = path.split_inclusive("/").collect(); |
| if split.as_slice()[..2] == ["/", "boot/"] { |
| let remainder = split[2..].join(""); |
| match zbi.bootfs.get(&remainder) { |
| Some(index_data) => { |
| let fidl_index = decode_persistent::< |
| component_internal::ComponentIdIndex, |
| >(index_data) |
| .context("Unable to decode component ID index from persistent FIDL")?; |
| let index = component_id_index::Index::from_fidl(fidl_index).context( |
| "Unable to create internal index for component ID index from FIDL", |
| )?; |
| Ok(ComponentIdIndex::new_from_index(index).context( |
| "Unable to create component ID index from internal index", |
| )?) |
| } |
| None => Err(anyhow!("file {} not found in bootfs", remainder)), |
| } |
| } else { |
| Err(anyhow!("Unable to parse component ID index file path {}", path)) |
| } |
| } |
| None => Ok(ComponentIdIndex::default()), |
| } |
| } |
| |
| fn make_builtin_runner_registry(&self, runtime_config: &RuntimeConfig) -> RunnerRegistry { |
| let mut runners = Vec::new(); |
| // Always register the ELF runner. |
| runners.push(RunnerRegistration { |
| source_name: ELF_RUNNER_NAME.into(), |
| target_name: ELF_RUNNER_NAME.into(), |
| source: RegistrationSource::Self_, |
| }); |
| // Register the RealmBuilder runner if needed. |
| if runtime_config.realm_builder_resolver_and_runner |
| == component_internal::RealmBuilderResolverAndRunner::Namespace |
| { |
| runners.push(RunnerRegistration { |
| source_name: REALM_BUILDER_RUNNER_NAME.into(), |
| target_name: REALM_BUILDER_RUNNER_NAME.into(), |
| source: RegistrationSource::Self_, |
| }); |
| } |
| RunnerRegistry::from_decl(&runners) |
| } |
| |
| fn load_dynamic_components( |
| component_tree_config_path: &Option<PathBuf>, |
| ) -> Result<HashMap<NodePath, (AbsoluteComponentUrl, Option<String>)>> { |
| if component_tree_config_path.is_none() { |
| return Ok(HashMap::new()); |
| } |
| let component_tree_config_path = component_tree_config_path.as_ref().unwrap(); |
| |
| let mut component_tree_config_file = File::open(component_tree_config_path) |
| .context("Failed to open component tree configuration file")?; |
| let component_tree_config: ComponentTreeConfig = |
| from_reader(&mut component_tree_config_file) |
| .context("Failed to parse component tree configuration file")?; |
| |
| let mut dynamic_components = HashMap::new(); |
| for (node_path, dynamic_component) in component_tree_config.dynamic_components.into_iter() { |
| dynamic_components |
| .insert(node_path, (dynamic_component.url, dynamic_component.environment)); |
| } |
| Ok(dynamic_components) |
| } |
| } |
| |
| impl DataCollector for V2ComponentModelDataCollector { |
| fn collect(&self, model: Arc<DataModel>) -> Result<()> { |
| let builder = ModelBuilderForAnalyzer::new(DEFAULT_ROOT_URL.clone()); |
| |
| let decls_by_url = self.get_decls(&model)?; |
| |
| let zbi = &model.get::<Zbi>().context("Unable to find the zbi model.")?; |
| let runtime_config = self.get_runtime_config(DEFAULT_CONFIG_PATH, &zbi).context( |
| format!("Unable to get the runtime config at path {:?}", DEFAULT_CONFIG_PATH), |
| )?; |
| let component_id_index = |
| self.get_component_id_index(runtime_config.component_id_index_path.as_deref(), &zbi)?; |
| |
| info!( |
| "V2ComponentModelDataCollector: Found {} v2 component declarations", |
| decls_by_url.len(), |
| ); |
| |
| let dynamic_components = |
| Self::load_dynamic_components(&model.config().component_tree_config_path)?; |
| let runner_registry = self.make_builtin_runner_registry(&runtime_config); |
| let build_result = builder.build_with_dynamic_components( |
| dynamic_components, |
| decls_by_url, |
| Arc::new(runtime_config), |
| Arc::new(component_id_index), |
| runner_registry, |
| ); |
| |
| for error in build_result.errors.iter() { |
| warn!("V2ComponentModelDataCollector: {}", error); |
| } |
| |
| match build_result.model { |
| Some(component_model) => { |
| info!( |
| "V2ComponentModelDataCollector: Built v2 component model with {} instances", |
| component_model.len() |
| ); |
| let core_deps_collection: Arc<CoreDataDeps> = model.get().map_err(|err| { |
| anyhow!( |
| "Failed to read core data deps for v2 component model data: {}", |
| err.to_string() |
| ) |
| })?; |
| let deps = core_deps_collection.deps.clone(); |
| model |
| .set(V2ComponentModel::new(deps, component_model, build_result.errors)) |
| .map_err(|err| { |
| anyhow!( |
| "Failed to store v2 component model in data model: {}", |
| err.to_string() |
| ) |
| })?; |
| Ok(()) |
| } |
| None => Err(anyhow!("Failed to build v2 component model")), |
| } |
| } |
| } |