blob: 57878d0d97e385fcd00c62fd1a4e3a25a83bb03b [file] [log] [blame]
// 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::ConfigOverridePolicy,
anyhow::{Context, Error},
cm_rust::NativeIntoFidl,
fidl::endpoints::{create_endpoints, ServerEnd},
fidl::Vmo,
fidl_fuchsia_component_decl as fcdecl, fidl_fuchsia_component_resolution as fresolution,
fidl_fuchsia_io as fio, fidl_fuchsia_mem as fmem, fuchsia_async as fasync,
futures::{
lock::{Mutex, MutexGuard},
TryStreamExt,
},
std::{collections::HashMap, sync::Arc},
tracing::*,
url::Url,
version_history::AbiRevision,
};
const RESOLVER_SCHEME: &'static str = "realm-builder";
#[derive(Clone)]
struct ResolveableComponent {
/// The component's declaration/manifest.
decl: fcdecl::Component,
/// The component's package directory.
package_dir: fio::DirectoryProxy,
/// Whether to rely on the `value_source` field in the component manifest or to only use
/// values provided as replacements.
config_override_policy: ConfigOverridePolicy,
/// Any configuration values provided by the caller. If `config_override_policy` is
/// `RequireAllValuesFromBuilder`, must provide a value for every field in the component's schema.
config_value_replacements: HashMap<usize, cm_rust::ConfigValueSpec>,
}
#[derive(Debug)]
pub struct Registry {
next_unique_component_id: Mutex<u64>,
component_decls: Mutex<HashMap<Url, ResolveableComponent>>,
}
impl Registry {
pub fn new() -> Arc<Self> {
Arc::new(Self {
next_unique_component_id: Mutex::new(0),
component_decls: Mutex::new(HashMap::new()),
})
}
// Only used for unit tests
#[cfg(test)]
pub async fn get_decl_for_url(self: &Arc<Self>, url: &str) -> Option<cm_rust::ComponentDecl> {
use cm_rust::FidlIntoNative;
let url = Url::parse(url).expect("Failed to parse component URL.");
let component = self.component_decls.lock().await.get(&url).cloned();
component.map(|c| c.decl.fidl_into_native())
}
// Only used for unit tests
#[cfg(test)]
pub async fn get_component_urls(self: &Arc<Self>) -> Vec<Url> {
self.component_decls.lock().await.keys().cloned().collect()
}
// Validates the given decl, and returns a URL at which it can be resolved
pub async fn validate_and_register(
self: &Arc<Self>,
decl: &fcdecl::Component,
name: String,
package_dir: fio::DirectoryProxy,
config_value_replacements: HashMap<usize, cm_rust::ConfigValueSpec>,
config_override_policy: ConfigOverridePolicy,
) -> Result<String, cm_fidl_validator::error::ErrorList> {
cm_fidl_validator::validate(decl)?;
let mut next_unique_component_id_guard = self.next_unique_component_id.lock().await;
let mut component_decls_guard = self.component_decls.lock().await;
let url = format!("{}://{}/{}", RESOLVER_SCHEME, *next_unique_component_id_guard, name);
*next_unique_component_id_guard += 1;
component_decls_guard.insert(
Url::parse(&url).expect("Generated invalid component URL."),
ResolveableComponent {
decl: decl.clone(),
package_dir,
config_value_replacements,
config_override_policy,
},
);
Ok(url)
}
pub async fn delete_manifest(self: &Arc<Self>, url: &String) {
self.component_decls
.lock()
.await
.remove(&Url::parse(&url).expect("failed to parse component URL"));
}
pub fn run_resolver_service(self: &Arc<Self>, stream: fresolution::ResolverRequestStream) {
let self_ref = self.clone();
fasync::Task::local(async move {
if let Err(err) = self_ref.handle_resolver_request_stream(stream).await {
warn!(%err, "`Resolver` server unexpectedly failed.", );
}
})
.detach();
}
async fn resolve_structured_config(
decl: &fcdecl::Component,
package_dir: Option<&fio::DirectoryProxy>,
config_value_replacements: &HashMap<usize, cm_rust::ConfigValueSpec>,
) -> Result<Option<fmem::Data>, Error> {
let Some(schema) = &decl.config else {
return Ok(None);
};
let path = match &schema.value_source {
Some(fcdecl::ConfigValueSource::PackagePath(path)) => Some(path),
// If we are routing from capabilities we don't need to do anything else.
Some(fcdecl::ConfigValueSource::Capabilities(_)) => {
if !config_value_replacements.is_empty() {
return Err(anyhow::anyhow!(
"Mutability values are not supported for config capabilities"
));
}
return Ok(None);
}
// Fall back to using any overrides we have.
_ => None,
};
let existing_values = match (path, package_dir) {
(Some(path), Some(dir)) => Some(
mem_util::open_file_data(dir, &path)
.await
.context(format!("Failed to load structured config file: {}", path))?,
),
// Fall back to using any overrides we have.
_ => None,
};
Ok(Some(
Self::verify_and_replace_config_values(
schema,
existing_values,
config_value_replacements,
)
.await?,
))
}
async fn verify_and_replace_config_values(
schema: &fcdecl::ConfigSchema,
values_data: Option<fmem::Data>,
config_value_replacements: &HashMap<usize, cm_rust::ConfigValueSpec>,
) -> Result<fmem::Data, Error> {
let mut values_data: fcdecl::ConfigValuesData = if let Some(v) = values_data {
let bytes = mem_util::bytes_from_data(&v)?;
let values_data = fidl::unpersist(&bytes)?;
cm_fidl_validator::validate_values_data(&values_data)?;
values_data
} else {
// make a boilerplate ValuesData that our replacements can fill
let num_expected_values =
schema.fields.as_ref().context("schema must have fields")?.len();
let blank_values = (0..num_expected_values)
.map(|_| fcdecl::ConfigValueSpec { value: None, ..Default::default() })
.collect::<Vec<_>>();
fcdecl::ConfigValuesData {
checksum: schema.checksum.clone(),
values: Some(blank_values),
..Default::default()
}
};
for (index, replacement) in config_value_replacements {
let value = values_data
.values
.as_mut()
.expect("either validated or constructed above")
.get_mut(*index)
.expect("Config Value File and Schema should have the same number of fields!");
*value = replacement.clone().native_into_fidl();
}
cm_fidl_validator::validate_values_data(&values_data)?;
let data = fidl::persist(&values_data)?;
let data = fmem::Data::Bytes(data);
return Ok(data);
}
async fn handle_resolver_request_stream(
self: &Arc<Self>,
mut stream: fresolution::ResolverRequestStream,
) -> Result<(), Error> {
while let Some(req) = stream.try_next().await? {
match req {
fresolution::ResolverRequest::Resolve { component_url, responder } => {
let resolve_result = self.resolve(&component_url).await;
responder.send(resolve_result).map_err(|err| {
warn!("FIDL error {err:?} responding to resolve request for component URL '{component_url}'");
err
})?;
}
fresolution::ResolverRequest::ResolveWithContext {
component_url,
context,
responder,
} => {
warn!("The RealmBuilder resolver does not resolve relative path component URLs with a context. Cannot resolve {} with context {:?}.", component_url, context);
responder.send(Err(fresolution::ResolverError::InvalidArgs))?;
}
}
}
Ok(())
}
pub async fn resolve(
self: &Arc<Self>,
component_url: &str,
) -> Result<fresolution::Component, fresolution::ResolverError> {
let parsed_url = Url::parse(&component_url).map_err(|e| {
// Don't swallow the root cause of the error without a trace. It may
// be impossible to correlate resulting error to its root cause
// otherwise.
error!("URL parse error: url={}: {}", &component_url, e);
fresolution::ResolverError::Internal
})?;
let component_decls_guard = self.component_decls.lock().await;
if let Some(resolvable_component) = component_decls_guard.get(&parsed_url).cloned() {
Self::load_absolute_url(component_url, resolvable_component).await.map_err(|e| {
// See similar above.
error!("URL resolution error for: url={}: {}", &component_url, e);
fresolution::ResolverError::Internal
})
} else {
Self::load_fragment_only_url(parsed_url, component_decls_guard).await
}
}
async fn load_absolute_url(
component_url: &str,
resolveable_component: ResolveableComponent,
) -> Result<fresolution::Component, Error> {
let ResolveableComponent {
decl,
package_dir,
config_value_replacements,
config_override_policy,
} = resolveable_component;
let abi_revision = fidl_fuchsia_component_abi_ext::read_abi_revision_optional(
&package_dir,
AbiRevision::PATH,
)
.await?;
let (client_end, server_end) = create_endpoints::<fio::DirectoryMarker>();
package_dir
.clone(fio::OpenFlags::CLONE_SAME_RIGHTS, ServerEnd::new(server_end.into_channel()))?;
let package = Some(fresolution::Package {
url: Some(component_url.to_string()),
directory: Some(client_end),
..Default::default()
});
let package_dir_for_config = match config_override_policy {
ConfigOverridePolicy::DisallowValuesFromBuilder => {
assert!(
config_value_replacements.is_empty(),
"cannot have received overrides if disallowed"
);
Some(&package_dir)
}
ConfigOverridePolicy::LoadPackagedValuesFirst => Some(&package_dir),
ConfigOverridePolicy::RequireAllValuesFromBuilder => None,
};
let config_values = Self::resolve_structured_config(
&decl,
package_dir_for_config,
&config_value_replacements,
)
.await?;
Ok(fresolution::Component {
url: Some(component_url.to_string()),
decl: Some(encode(decl)?),
package,
config_values,
resolution_context: None,
abi_revision: abi_revision.map(Into::into),
..Default::default()
})
}
// Realm builder never generates URLs with fragments. If we're asked to resolve a URL with
// one, then this must be from component manager attempting to make sense of a fragment-only URL
// where the parent of the component is an absolute realm builder URL. We rewrite relative
// URLs to be absolute realm builder URLs at build time, but if a component is added at
// runtime (as in, to a collection) then realm builder doesn't have a chance to do this
// rewrite.
//
// To handle this: let's use the fragment to look for a component in the test package.
async fn load_fragment_only_url<'a>(
mut parsed_url: Url,
component_decls_guard: MutexGuard<'a, HashMap<Url, ResolveableComponent>>,
) -> Result<fresolution::Component, fresolution::ResolverError> {
let component_url = parsed_url.as_str().to_string();
let fragment = match parsed_url.fragment() {
Some(fragment) => fragment.to_string(),
None => return Err(fresolution::ResolverError::ManifestNotFound),
};
parsed_url.set_fragment(None);
let component = component_decls_guard
.get(&parsed_url)
.ok_or(fresolution::ResolverError::ManifestNotFound)?;
let manifest_file = fuchsia_fs::directory::open_file_no_describe(
&component.package_dir,
&fragment,
fio::OpenFlags::RIGHT_READABLE,
)
.map_err(|_| fresolution::ResolverError::ManifestNotFound)?;
let component_decl: fcdecl::Component =
fuchsia_fs::file::read_fidl(&manifest_file)
.await
.map_err(|_| fresolution::ResolverError::ManifestNotFound)?;
cm_fidl_validator::validate(&component_decl)
.map_err(|_| fresolution::ResolverError::ManifestNotFound)?;
let (client_end, server_end) = create_endpoints::<fio::DirectoryMarker>();
component
.package_dir
.clone(fio::OpenFlags::CLONE_SAME_RIGHTS, ServerEnd::new(server_end.into_channel()))
.map_err(|_| fresolution::ResolverError::Io)?;
let package_dir_for_config = match component.config_override_policy {
ConfigOverridePolicy::DisallowValuesFromBuilder => {
assert!(
component.config_value_replacements.is_empty(),
"cannot have received config overrides if disallowed"
);
Some(&component.package_dir)
}
ConfigOverridePolicy::LoadPackagedValuesFirst => Some(&component.package_dir),
ConfigOverridePolicy::RequireAllValuesFromBuilder => None,
};
let config_values = Self::resolve_structured_config(
&component_decl,
package_dir_for_config,
// Since realm builder never generates fragment-only URLs, the component we are
// resolving here will never be one with config value replacements set, so don't provide
// any.
&HashMap::new(),
)
.await
.map_err(|_| fresolution::ResolverError::ConfigValuesNotFound)?;
let abi_revision = fidl_fuchsia_component_abi_ext::read_abi_revision_optional(
&component.package_dir,
AbiRevision::PATH,
)
.await?;
Ok(fresolution::Component {
url: Some(component_url.clone()),
resolution_context: None,
decl: Some(encode(component_decl).map_err(|e| {
error!("while encoding component decl: {}", e);
fresolution::ResolverError::Internal
})?),
package: Some(fresolution::Package {
url: Some(component_url),
directory: Some(client_end),
..Default::default()
}),
config_values,
abi_revision: abi_revision.map(Into::into),
..Default::default()
})
}
}
fn encode(component_decl: fcdecl::Component) -> Result<fmem::Data, Error> {
let encoded = fidl::persist(&component_decl).context("failed to encode ComponentDecl")?;
let encoded_size = encoded.len() as u64;
let vmo = Vmo::create(encoded_size)?;
vmo.write(&encoded, 0)?;
Ok(fmem::Data::Buffer(fmem::Buffer { vmo, size: encoded_size }))
}