blob: faf1a3f0ca07ad0b15e2a605c6db939fbcc88952 [file] [log] [blame]
// Copyright 2023 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::composite_node_spec_manager::CompositeNodeSpecManager;
use crate::driver_loading_fuzzer::Session;
use crate::match_common::{node_to_device_property, node_to_device_property_no_autobind};
use crate::resolved_driver::{DriverPackageType, ResolvedDriver};
use bind::interpreter::decode_bind_rules::DecodedRules;
use fidl_fuchsia_pkg_ext::BlobId;
use fuchsia_zircon::Status;
use futures::StreamExt;
use std::cell::RefCell;
use std::collections::HashMap;
use std::ops::{Deref, DerefMut};
use std::rc::Rc;
use std::str::FromStr;
use {
fidl_fuchsia_component_resolution as fresolution, fidl_fuchsia_driver_framework as fdf,
fidl_fuchsia_driver_index as fdi, fuchsia_async as fasync,
};
fn ignore_peer_closed(err: fidl::Error) -> Result<(), fidl::Error> {
if err.is_closed() {
Ok(())
} else {
Err(err)
}
}
pub enum BaseRepo {
// We know that Base won't update so we can store these as resolved.
Resolved(Vec<ResolvedDriver>),
// If it's not resolved we store the clients waiting for it.
NotResolved,
}
pub struct Indexer {
pub boot_repo: RefCell<Vec<ResolvedDriver>>,
// |base_repo| needs to be in a RefCell because it starts out NotResolved,
// but will eventually resolve when base packages are available.
pub base_repo: RefCell<BaseRepo>,
// Manages the specs. This is wrapped in a RefCell since the
// specs are added after the driver index server has started.
pub composite_node_spec_manager: RefCell<CompositeNodeSpecManager>,
// Clients waiting for a response when a new boot/base driver is loaded.
driver_load_watchers: RefCell<Vec<fdi::DriverIndexWatchForDriverLoadResponder>>,
// Used to determine if the indexer should return fallback drivers that match or
// wait until based packaged drivers are indexed.
delay_fallback_until_base_drivers_indexed: bool,
// Contains the ephemeral drivers. This is wrapped in a RefCell since the
// ephemeral drivers are added after the driver index server has started
// through the FIDL API, fuchsia.driver.registrar.Register.
ephemeral_drivers: RefCell<HashMap<url::Url, ResolvedDriver>>,
}
impl Indexer {
pub fn new(
boot_repo: Vec<ResolvedDriver>,
base_repo: BaseRepo,
delay_fallback_until_base_drivers_indexed: bool,
) -> Indexer {
Indexer {
boot_repo: RefCell::new(boot_repo),
base_repo: RefCell::new(base_repo),
composite_node_spec_manager: RefCell::new(CompositeNodeSpecManager::new()),
driver_load_watchers: RefCell::new(vec![]),
delay_fallback_until_base_drivers_indexed,
ephemeral_drivers: RefCell::new(HashMap::new()),
}
}
pub fn start_driver_load(
self: Rc<Self>,
receiver: futures::channel::mpsc::UnboundedReceiver<Vec<ResolvedDriver>>,
session: Session,
) {
fasync::Task::local(async move {
self.handle_add_boot_driver(receiver).await;
})
.detach();
fasync::Task::local(session.run()).detach();
}
fn include_fallback_drivers(&self) -> bool {
!self.delay_fallback_until_base_drivers_indexed
|| match *self.base_repo.borrow() {
BaseRepo::Resolved(_) => true,
_ => false,
}
}
fn change_disabled_state_to(
&self,
driver_url: &str,
package_hash: Option<String>,
disabled_value: bool,
) -> u32 {
let op = match disabled_value {
true => "Disable".to_string(),
false => "Enable".to_string(),
};
let mut count = 0;
let requested_hash = package_hash.and_then(|hash| {
let parsed_hash = fuchsia_hash::Hash::from_str(hash.as_str());
match parsed_hash {
Ok(h) => Some(BlobId::from(h)),
Err(e) => {
tracing::warn!("Failed to parse package_hash in disable_driver: {:?}.", e);
tracing::warn!("Skipping hash comparisons.");
None
}
}
});
for driver in self.boot_repo.borrow_mut().iter_mut() {
if driver.component_url.as_str() == driver_url {
if requested_hash.is_some() && requested_hash != driver.package_hash {
tracing::warn!(
"{} found matching driver url, but skipping due to hash mismatch.",
op
);
continue;
}
if driver.disabled != disabled_value {
driver.disabled = disabled_value;
count += 1;
}
break;
}
}
match self.base_repo.borrow_mut().deref_mut() {
BaseRepo::Resolved(base_repo) => {
for driver in base_repo.iter_mut() {
if driver.component_url.as_str() == driver_url {
if requested_hash.is_some() && requested_hash != driver.package_hash {
tracing::warn!(
"{} found matching driver url, but skipping due to hash mismatch.",
op
);
continue;
}
if driver.disabled != disabled_value {
driver.disabled = disabled_value;
count += 1;
}
break;
}
}
}
BaseRepo::NotResolved => {}
}
for (_url, driver) in self.ephemeral_drivers.borrow_mut().iter_mut() {
if driver.component_url.as_str() == driver_url {
if requested_hash.is_some() && requested_hash != driver.package_hash {
tracing::warn!(
"{} found matching driver url, but skipping due to hash mismatch.",
op
);
continue;
}
if driver.disabled != disabled_value {
driver.disabled = disabled_value;
count += 1;
}
break;
}
}
count
}
pub fn watch_for_driver_load(&self, responder: fdi::DriverIndexWatchForDriverLoadResponder) {
self.driver_load_watchers.borrow_mut().push(responder);
}
pub async fn handle_add_boot_driver(
self: Rc<Self>,
mut receiver: futures::channel::mpsc::UnboundedReceiver<Vec<ResolvedDriver>>,
) {
while let Some(mut drivers) = receiver.next().await {
tracing::info!("Loaded drivers into the driver index:");
for driver in &drivers {
tracing::info!(" {}", driver.component_url);
let mut composite_node_spec_manager = self.composite_node_spec_manager.borrow_mut();
composite_node_spec_manager.new_driver_available(driver);
}
self.boot_repo.borrow_mut().append(&mut drivers);
self.report_driver_load();
}
}
fn report_driver_load(&self) {
let mut driver_load_watchers = self.driver_load_watchers.borrow_mut();
while let Some(watcher) = driver_load_watchers.pop() {
match watcher.send().or_else(ignore_peer_closed) {
Err(e) => tracing::error!("Error sending to base_waiter: {:?}", e),
Ok(_) => continue,
}
}
}
// Create a list of all drivers (except for disabled drivers) in the following priority:
// 1. Non-fallback boot drivers
// 2. Non-fallback base drivers
// 3. Fallback boot drivers
// 4. Fallback base drivers
fn list_drivers(&self) -> Vec<ResolvedDriver> {
let base_repo = self.base_repo.borrow();
let base_repo_iter = match base_repo.deref() {
BaseRepo::Resolved(drivers) => drivers.iter(),
BaseRepo::NotResolved => [].iter(),
};
let boot_repo = self.boot_repo.borrow();
let (boot_drivers, base_drivers) = if self.include_fallback_drivers() {
(boot_repo.iter(), base_repo_iter.clone())
} else {
([].iter(), [].iter())
};
let fallback_boot_drivers = boot_drivers.filter(|&driver| driver.fallback);
let fallback_base_drivers = base_drivers.filter(|&driver| driver.fallback);
let ephemeral = self.ephemeral_drivers.borrow();
boot_repo
.iter()
.filter(|&driver| !driver.fallback)
.chain(base_repo_iter.filter(|&driver| !driver.fallback))
.chain(ephemeral.values())
.chain(fallback_boot_drivers)
.chain(fallback_base_drivers)
.filter(|&driver| !driver.disabled)
.map(|driver| driver.clone())
.collect::<Vec<_>>()
}
pub fn load_base_repo(&self, base_drivers: Vec<ResolvedDriver>) {
if let BaseRepo::NotResolved = self.base_repo.borrow_mut().deref_mut() {
self.report_driver_load();
}
self.base_repo.replace(BaseRepo::Resolved(base_drivers));
}
pub fn match_driver(&self, args: fdi::MatchDriverArgs) -> fdi::DriverIndexMatchDriverResult {
if args.properties.is_none() {
tracing::error!("Failed to match driver: empty properties");
return Err(Status::INVALID_ARGS.into_raw());
}
let properties = args.properties.unwrap();
let properties = match args.driver_url_suffix {
Some(_) => node_to_device_property_no_autobind(&properties)?,
None => node_to_device_property(&properties)?,
};
// Prioritize specs to avoid match conflicts with composite drivers.
let spec_match = self.composite_node_spec_manager.borrow().match_parent_specs(&properties);
if let Some(spec) = spec_match {
return Ok(spec);
}
let driver_list = self.list_drivers();
let (mut fallback, mut non_fallback): (
Vec<(bool, fdi::MatchDriverResult)>,
Vec<(bool, fdi::MatchDriverResult)>,
) = driver_list
.iter()
.filter_map(|driver| {
if let Ok(Some(matched)) = driver.matches(&properties) {
if let Some(url_suffix) = &args.driver_url_suffix {
if !driver.component_url.as_str().ends_with(url_suffix.as_str()) {
return None;
}
}
Some((driver.fallback, matched))
} else {
None
}
})
.partition(|(fallback, _)| *fallback);
match (non_fallback.len(), fallback.len()) {
(1, _) => Ok(non_fallback.pop().unwrap().1),
(0, 1) => Ok(fallback.pop().unwrap().1),
(0, 0) => Err(Status::NOT_FOUND.into_raw()),
(0, _) => {
tracing::error!("Failed to match driver: Encountered unsupported behavior: Zero non-fallback drivers and more than one fallback drivers were matched");
tracing::error!("Fallback drivers {:#?}", fallback);
Err(Status::NOT_SUPPORTED.into_raw())
}
_ => {
tracing::error!("Failed to match driver: Encountered unsupported behavior: Multiple non-fallback drivers were matched");
tracing::error!("Drivers {:#?}", non_fallback);
Err(Status::NOT_SUPPORTED.into_raw())
}
}
}
pub fn add_composite_node_spec(&self, spec: fdf::CompositeNodeSpec) -> Result<(), i32> {
let driver_list = self.list_drivers();
let composite_drivers = driver_list
.iter()
.filter(|&driver| matches!(driver.bind_rules, DecodedRules::Composite(_)))
.collect::<Vec<_>>();
let mut composite_node_spec_manager = self.composite_node_spec_manager.borrow_mut();
composite_node_spec_manager.add_composite_node_spec(spec, composite_drivers)
}
pub fn rebind_composite(
&self,
spec_name: String,
driver_url_suffix: Option<String>,
) -> Result<(), i32> {
let driver_list = self.list_drivers();
let composite_drivers = driver_list
.iter()
.filter(|&driver| {
if let Some(url_suffix) = &driver_url_suffix {
if !driver.component_url.as_str().ends_with(url_suffix.as_str()) {
return false;
}
}
matches!(driver.bind_rules, DecodedRules::Composite(_))
})
.collect::<Vec<_>>();
self.composite_node_spec_manager.borrow_mut().rebind(spec_name, composite_drivers)
}
pub fn rebind_composites_with_driver(&self, driver: String) -> Result<(), i32> {
let driver_list = self.list_drivers();
let composite_drivers = driver_list
.iter()
.filter(|&driver| matches!(driver.bind_rules, DecodedRules::Composite(_)))
.collect::<Vec<_>>();
self.composite_node_spec_manager
.borrow_mut()
.rebind_composites_with_driver(driver, composite_drivers)
}
pub fn get_driver_info(&self, driver_filter: Vec<String>) -> Vec<fdf::DriverInfo> {
let mut driver_info = Vec::new();
for driver in self.boot_repo.borrow().iter() {
if driver_filter.len() == 0
|| driver_filter.iter().any(|f| f == driver.component_url.as_str())
{
driver_info.push(driver.create_driver_info(true));
}
}
let base_repo = self.base_repo.borrow();
if let BaseRepo::Resolved(drivers) = &base_repo.deref() {
for driver in drivers {
if driver_filter.len() == 0
|| driver_filter.iter().any(|f| f == driver.component_url.as_str())
{
driver_info.push(driver.create_driver_info(true));
}
}
}
let ephemeral = self.ephemeral_drivers.borrow();
for driver in ephemeral.values() {
if driver_filter.len() == 0
|| driver_filter.iter().any(|f| f == driver.component_url.as_str())
{
driver_info.push(driver.create_driver_info(true));
}
}
driver_info
}
pub async fn register_driver(
&self,
driver_url: String,
resolver: &fresolution::ResolverProxy,
) -> Result<(), i32> {
let component_url = url::Url::parse(&driver_url).map_err(|e| {
tracing::error!("Couldn't parse driver url: {}: error: {}", &driver_url, e);
Status::ADDRESS_UNREACHABLE.into_raw()
})?;
for boot_driver in self.boot_repo.borrow().iter() {
if boot_driver.component_url == component_url {
tracing::warn!("Driver being registered already exists in boot list.");
return Err(Status::ALREADY_EXISTS.into_raw());
}
}
match self.base_repo.borrow().deref() {
BaseRepo::Resolved(resolved_base_drivers) => {
for base_driver in resolved_base_drivers {
if base_driver.component_url == component_url {
tracing::warn!("Driver being registered already exists in base list.");
return Err(Status::ALREADY_EXISTS.into_raw());
}
}
}
_ => (),
};
let resolve =
ResolvedDriver::resolve(component_url.clone(), &resolver, DriverPackageType::Universe)
.await;
if resolve.is_err() {
return Err(resolve.err().unwrap().into_raw());
}
let resolved_driver = resolve.unwrap();
let mut composite_node_spec_manager = self.composite_node_spec_manager.borrow_mut();
composite_node_spec_manager.new_driver_available(&resolved_driver);
let mut ephemeral_drivers = self.ephemeral_drivers.borrow_mut();
let existing = ephemeral_drivers.insert(component_url.clone(), resolved_driver);
if let Some(existing_driver) = existing {
tracing::info!("Updating existing ephemeral driver {}.", existing_driver);
} else {
tracing::info!("Registered driver successfully: {}.", component_url);
}
Ok(())
}
pub fn disable_driver(
&self,
driver_url: String,
package_hash: Option<String>,
) -> Result<(), i32> {
let count = self.change_disabled_state_to(driver_url.as_str(), package_hash, true);
if count == 0 {
return Err(Status::NOT_FOUND.into_raw());
}
tracing::info!("Disabled {} driver(s).", count);
let rebind_result = self.rebind_composites_with_driver(driver_url);
if let Err(e) = rebind_result {
tracing::error!(
"Failed to rebind composites with the driver being disabled: {}.",
Status::from_raw(e)
);
}
Ok(())
}
pub fn enable_driver(
&self,
driver_url: String,
package_hash: Option<String>,
) -> Result<(), i32> {
let count = self.change_disabled_state_to(driver_url.as_str(), package_hash, false);
if count == 0 {
return Err(Status::NOT_FOUND.into_raw());
}
tracing::info!("Enabled {} driver(s).", count);
let rebind_result = self.rebind_composites_with_driver(driver_url);
if let Err(e) = rebind_result {
tracing::error!(
"Failed to rebind composites with the driver being enabled: {}.",
Status::from_raw(e)
);
}
Ok(())
}
}