blob: 75c131d12393f5c189b455c0edc06bb7c593d0e7 [file] [log] [blame]
// Copyright 2019 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.
#![feature(async_await)]
mod config;
mod validators;
use {
config::{Config, ConfigContext},
failure::{format_err, Error},
fidl::endpoints::{create_proxy, Request, RequestStream, ServerEnd, ServiceMarker},
fidl_fuchsia_boot::FactoryItemsMarker,
fidl_fuchsia_factory::{
CastCredentialsFactoryStoreProviderMarker, CastCredentialsFactoryStoreProviderRequest,
CastCredentialsFactoryStoreProviderRequestStream, MiscFactoryStoreProviderMarker,
MiscFactoryStoreProviderRequest, MiscFactoryStoreProviderRequestStream,
PlayReadyFactoryStoreProviderMarker, PlayReadyFactoryStoreProviderRequest,
PlayReadyFactoryStoreProviderRequestStream, WidevineFactoryStoreProviderMarker,
WidevineFactoryStoreProviderRequest, WidevineFactoryStoreProviderRequestStream,
},
fidl_fuchsia_io::{
DirectoryMarker, DirectoryProxy, NodeMarker, MODE_TYPE_DIRECTORY, OPEN_RIGHT_READABLE,
},
fuchsia_async::{self as fasync},
fuchsia_bootfs::BootfsParser,
fuchsia_component::server::ServiceFs,
fuchsia_syslog as syslog,
fuchsia_vfs_pseudo_fs::{
directory::{self, entry::DirectoryEntry},
file::simple::read_only,
},
fuchsia_zircon as zx,
futures::{lock::Mutex, prelude::*, TryStreamExt},
std::{collections::HashMap, iter, path::PathBuf, sync::Arc},
};
const CONCURRENT_LIMIT: usize = 10_000;
const DEFAULT_BOOTFS_FACTORY_ITEM_EXTRA: u32 = 0;
enum IncomingServices {
CastCredentialsFactoryStoreProvider(CastCredentialsFactoryStoreProviderRequestStream),
MiscFactoryStoreProvider(MiscFactoryStoreProviderRequestStream),
PlayReadyFactoryStoreProvider(PlayReadyFactoryStoreProviderRequestStream),
WidevineFactoryStoreProvider(WidevineFactoryStoreProviderRequestStream),
}
// TODO(mbrunson): Use implementation from fuchsia_vfs_pseudo_fs when it becomes available:
// https://fuchsia-review.googlesource.com/c/fuchsia/+/305595
/// A "node" within a potential directory tree.
///
/// Unlike the pseudo directory types in the fuchsia-vfs library which only allow adding of direct
/// child directory entries, `DirectoryTreeBuilder::Directory` allows adding files using the full
/// file path, creating extra `DirectoryTreeBuilder` instances as necessary to allow successful
/// conversion of the entire directory tree to a `DirectoryEntry` implementation.
///
/// The `DirectoryTreeBuilder::File` type represents a pseudo file. It can only be a leaf in the
/// directory tree and store file contents unlike `DirectoryTreeBuilder::Directory`.
enum DirectoryTreeBuilder {
Directory(HashMap<String, DirectoryTreeBuilder>),
File(Vec<u8>),
}
impl DirectoryTreeBuilder {
pub fn empty_dir() -> Self {
DirectoryTreeBuilder::Directory(HashMap::new())
}
/// Adds a file to the directory tree.
///
/// An error is returned if either of the following occur:
/// * This function is called on a `DirectoryTreeBuilder::File` enum.
/// * A file already exists at the given `path`.
pub fn add_file(&mut self, path: &[&str], content: Vec<u8>) -> Result<(), Error> {
self.add_file_impl(path, content, &mut PathBuf::from(""))
}
fn add_file_impl(
&mut self,
path: &[&str],
content: Vec<u8>,
mut full_path: &mut PathBuf,
) -> Result<(), Error> {
match self {
DirectoryTreeBuilder::File(_) => Err(format_err!(
"Cannot add a file within a File: path={}, name={}, content={:X?}",
full_path.to_string_lossy(),
path[0],
content
)),
DirectoryTreeBuilder::Directory(children) => {
let name = path[0].to_string();
let nested = &path[1..];
full_path.push(&name);
if nested.is_empty() {
if children.insert(name, DirectoryTreeBuilder::File(content)).is_some() {
Err(format_err!("Duplicate entry at {}", full_path.to_string_lossy()))
} else {
Ok(())
}
} else {
match children.get_mut(&name) {
Some(entry) => entry.add_file_impl(nested, content, &mut full_path),
None => {
let mut entry = DirectoryTreeBuilder::empty_dir();
entry.add_file_impl(nested, content, &mut full_path)?;
children.insert(name, entry);
Ok(())
}
}
}
}
}
}
/// Converts this `DirectoryTreeBuilder` into a `DirectoryEntry`.
///
/// On successful creation of the `DirectoryEntry`, any payloads owned by this node or its
/// children are moved into closures called by an associated pseudo file implementation.
///
/// Errors are propogated from `Controllable::add_boxed_entry()` but are converted to
/// `zx::Status` before being wrapped in an `Error`.
pub fn build<'a>(self) -> Result<Box<dyn DirectoryEntry>, Error> {
self.build_impl(&mut PathBuf::from(""))
}
fn build_impl<'a>(self, mut full_path: &mut PathBuf) -> Result<Box<dyn DirectoryEntry>, Error> {
match self {
DirectoryTreeBuilder::File(content) => {
syslog::fx_log_info!("Adding content at {}", full_path.to_string_lossy());
Ok(Box::new(read_only(move || Ok(content.to_vec()))))
}
DirectoryTreeBuilder::Directory(children) => {
let mut dir = directory::simple::empty();
for (name, child) in children.into_iter() {
full_path.push(&name);
let entry = child.build_impl(&mut full_path)?;
full_path.pop();
dir.add_boxed_entry(&name, entry).map_err(|err| Error::from(err.0))?;
}
Ok(Box::new(dir))
}
}
}
}
fn parse_bootfs<'a>(vmo: zx::Vmo) -> HashMap<String, Vec<u8>> {
let mut items = HashMap::new();
match BootfsParser::create_from_vmo(vmo) {
Ok(parser) => parser.iter().for_each(|result| match result {
Ok(entry) => {
syslog::fx_log_info!("Found {} in factory bootfs", &entry.name);
items.insert(entry.name, entry.payload);
}
Err(err) => syslog::fx_log_err!(tag: "BootfsParser", "{}", err),
}),
Err(err) => syslog::fx_log_err!(tag: "BootfsParser", "{}", err),
};
items
}
async fn fetch_new_factory_item() -> Result<zx::Vmo, Error> {
let factory_items = fuchsia_component::client::connect_to_service::<FactoryItemsMarker>()?;
let (vmo_opt, _) = factory_items.get(DEFAULT_BOOTFS_FACTORY_ITEM_EXTRA).await?;
vmo_opt.ok_or(format_err!("Failed to get a valid VMO from service"))
}
async fn create_dir_from_context<'a>(
context: &'a ConfigContext,
items: &'a HashMap<String, Vec<u8>>,
) -> Result<Box<dyn DirectoryEntry>, Error> {
let mut dir_builder = DirectoryTreeBuilder::empty_dir();
for (path, dest) in &context.file_path_map {
let contents = match items.get(path) {
Some(contents) => contents,
None => {
syslog::fx_log_err!("Failed to find {}, skipping", &path);
continue;
}
};
let mut failed_validation = false;
let mut validated = false;
for validator_context in &context.validator_contexts {
if validator_context.paths_to_validate.contains(path) {
syslog::fx_log_info!(
"Validating {} with {} validator",
&path,
&validator_context.name
);
if let Err(err) = validator_context.validator.validate(&path, &contents) {
syslog::fx_log_err!("{}", err);
failed_validation = true;
break;
}
validated = true;
}
}
// Do not allow files that failed validation or have not been validated at all.
if !failed_validation && validated {
let path_parts: Vec<&str> = dest.split("/").collect();
dir_builder.add_file(&path_parts, contents.to_vec()).unwrap_or_else(|err| {
syslog::fx_log_err!("Failed to add file {} to directory: {}", dest, err);
});
} else if !validated {
syslog::fx_log_err!("{} was never validated, ignored", &path);
}
}
dir_builder.build()
}
fn apply_config(config: Config, items: Arc<Mutex<HashMap<String, Vec<u8>>>>) -> DirectoryProxy {
let (directory_proxy, directory_server_end) = create_proxy::<DirectoryMarker>().unwrap();
fasync::spawn(async move {
let items_mtx = items.clone();
// We only want to hold this lock to create `dir` so limit the scope of `items_ref`.
let mut dir = {
let items_ref = items_mtx.lock().await;
let context = config.into_context().expect("Failed to convert config into context");
create_dir_from_context(&context, &*items_ref).await.unwrap_or_else(|err| {
syslog::fx_log_err!(
"Failed to create directory from config: {}, {:?}",
err,
context
);
Box::new(directory::simple::empty())
})
};
dir.open(
OPEN_RIGHT_READABLE,
MODE_TYPE_DIRECTORY,
&mut iter::empty(),
ServerEnd::<NodeMarker>::new(directory_server_end.into_channel()),
);
dir.await;
});
directory_proxy
}
async fn handle_request_stream<RS, G>(
mut stream: RS,
directory_mutex: Arc<Mutex<DirectoryProxy>>,
mut get_directory_request_fn: G,
) -> Result<(), Error>
where
RS: RequestStream,
G: FnMut(
Request<RS::Service>,
) -> Option<fidl::endpoints::ServerEnd<fidl_fuchsia_io::DirectoryMarker>>,
{
while let Some(request) = stream.try_next().await? {
if let Some(directory_request) = get_directory_request_fn(request) {
if let Err(err) = directory_mutex.lock().await.clone(
OPEN_RIGHT_READABLE,
ServerEnd::<NodeMarker>::new(directory_request.into_channel()),
) {
syslog::fx_log_err!(
"Failed to clone directory connection for {}: {:?}",
RS::Service::DEBUG_NAME,
err
);
}
}
}
Ok(())
}
#[fasync::run_singlethreaded]
async fn main() -> Result<(), Error> {
syslog::init_with_tags(&["factory_store_providers"]).expect("Can't init logger");
syslog::fx_log_info!("{}", "Starting factory_store_providers");
let directory_items =
fetch_new_factory_item().await.map(|vmo| parse_bootfs(vmo)).unwrap_or_else(|err| {
syslog::fx_log_err!("Failed to get factory item, returning empty item list: {}", err);
HashMap::new()
});
let mut fs = ServiceFs::new();
fs.dir("svc")
.add_fidl_service(IncomingServices::CastCredentialsFactoryStoreProvider)
.add_fidl_service(IncomingServices::MiscFactoryStoreProvider)
.add_fidl_service(IncomingServices::PlayReadyFactoryStoreProvider)
.add_fidl_service(IncomingServices::WidevineFactoryStoreProvider);
fs.take_and_serve_directory_handle().expect("Failed to serve factory providers");
let items_mtx = Arc::new(Mutex::new(directory_items));
let cast_credentials_config = Config::load::<CastCredentialsFactoryStoreProviderMarker>()?;
let cast_directory =
Arc::new(Mutex::new(apply_config(cast_credentials_config, items_mtx.clone())));
let misc_config = Config::load::<MiscFactoryStoreProviderMarker>()?;
let misc_directory = Arc::new(Mutex::new(apply_config(misc_config, items_mtx.clone())));
let playready_config = Config::load::<PlayReadyFactoryStoreProviderMarker>()?;
let playready_directory =
Arc::new(Mutex::new(apply_config(playready_config, items_mtx.clone())));
let widevine_config = Config::load::<WidevineFactoryStoreProviderMarker>()?;
let widevine_directory = Arc::new(Mutex::new(apply_config(widevine_config, items_mtx.clone())));
fs.for_each_concurrent(CONCURRENT_LIMIT, move |incoming_service| {
let cast_directory_clone = cast_directory.clone();
let misc_directory_clone = misc_directory.clone();
let playready_directory_clone = playready_directory.clone();
let widevine_directory_clone = widevine_directory.clone();
async move {
match incoming_service {
IncomingServices::CastCredentialsFactoryStoreProvider(stream) => {
let cast_directory_clone = cast_directory_clone.clone();
handle_request_stream(
stream,
cast_directory_clone,
|req: CastCredentialsFactoryStoreProviderRequest| {
req.into_get_factory_store().map(|item| item.0)
},
)
.await
}
IncomingServices::MiscFactoryStoreProvider(stream) => {
let misc_directory_clone = misc_directory_clone.clone();
handle_request_stream(
stream,
misc_directory_clone,
|req: MiscFactoryStoreProviderRequest| {
req.into_get_factory_store().map(|item| item.0)
},
)
.await
}
IncomingServices::PlayReadyFactoryStoreProvider(stream) => {
let playready_directory_clone = playready_directory_clone.clone();
handle_request_stream(
stream,
playready_directory_clone,
|req: PlayReadyFactoryStoreProviderRequest| {
req.into_get_factory_store().map(|item| item.0)
},
)
.await
}
IncomingServices::WidevineFactoryStoreProvider(stream) => {
let widevine_directory_clone = widevine_directory_clone.clone();
handle_request_stream(
stream,
widevine_directory_clone,
|req: WidevineFactoryStoreProviderRequest| {
req.into_get_factory_store().map(|item| item.0)
},
)
.await
}
}
}
.unwrap_or_else(|err| syslog::fx_log_err!("Failed to handle incoming service: {}", err))
})
.await;
Ok(())
}