blob: 536e50e027e4bfd27434d13e50fd1c0cd4d3fbf4 [file] [log] [blame]
// Copyright 2022 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.
//! Utilities for validating packages and components, separate from the `assembly_validate_product`
//! crate so they can be depended upon by libraries used to implement product validation.
use std::{
collections::BTreeMap,
error::Error,
fmt::{Debug, Display},
io::{Read, Seek},
path::{Path, PathBuf},
};
mod inner_impl {
use super::*;
pub trait PkgNamespaceInner {
/// Error type returned by namespace lookups/reads.
type Err: Debug + Display + Error + Send + Sync + 'static;
/// Read the contents of `path`.
fn read_impl(&mut self, path: &str) -> Result<Vec<u8>, Self::Err>;
}
}
use inner_impl::PkgNamespaceInner;
/// A type which can be used to describe the `/pkg` directory in a component's namespace.
pub trait PkgNamespace: PkgNamespaceInner {
/// Return a list of all paths in the package visible to the component.
fn paths(&self) -> Vec<String>;
fn read_file(&mut self, path: &str) -> Result<Vec<u8>, ReadError> {
match self.read_impl(path) {
Ok(b) => Ok(b),
Err(e) => {
let same_ext_alternatives = if let Some((_, extension)) = path.rsplit_once('.') {
self.paths().into_iter().filter(|p| p.ends_with(extension)).collect()
} else {
vec![]
};
Err(ReadError { inner: e.to_string(), same_ext_alternatives })
}
}
}
}
#[derive(Debug)]
pub struct ReadError {
inner: String,
same_ext_alternatives: Vec<String>,
}
impl Display for ReadError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.inner)?;
if !self.same_ext_alternatives.is_empty() {
f.write_str("\n\tPossible alternatives with the same extension: [")?;
for path in &self.same_ext_alternatives {
f.write_str("\n\t ")?;
f.write_str(path)?;
f.write_str(",")?;
}
f.write_str("\n\t]")?;
}
Ok(())
}
}
impl Error for ReadError {}
// NOTE: this implementation doesn't allow access to anything outside of `/pkg/meta`
impl<T> PkgNamespaceInner for fuchsia_archive::Utf8Reader<T>
where
T: Read + Seek,
{
type Err = fuchsia_archive::Error;
fn read_impl(&mut self, path: &str) -> Result<Vec<u8>, Self::Err> {
self.read_file(path)
}
}
impl<T> PkgNamespace for fuchsia_archive::Utf8Reader<T>
where
T: Read + Seek,
{
fn paths(&self) -> Vec<String> {
self.list().map(|e| e.path().to_owned()).collect()
}
}
/// An in-memory representation of the bootfs for a given system image.
pub struct BootfsContents {
files: BTreeMap<String, Vec<u8>>,
}
impl BootfsContents {
pub fn from_iter(
bootfs_files: impl Iterator<Item = (impl AsRef<str>, impl AsRef<Path>)>,
) -> Result<Self, BootfsContentsError> {
let mut files = BTreeMap::new();
for (dest, source) in bootfs_files {
let source = source.as_ref();
let contents = std::fs::read(source).map_err(|e| {
BootfsContentsError::ReadSourceFile { path: source.to_owned(), source: e }
})?;
files.insert(dest.as_ref().to_owned(), contents);
}
Ok(Self { files })
}
}
impl PkgNamespaceInner for BootfsContents {
type Err = BootfsContentsError;
fn read_impl(&mut self, path: &str) -> Result<Vec<u8>, Self::Err> {
self.files
.get(path)
.map(|b| b.to_owned())
.ok_or_else(|| BootfsContentsError::NoSuchFile(path.to_owned()))
}
}
impl PkgNamespace for BootfsContents {
fn paths(&self) -> Vec<String> {
self.files.keys().cloned().collect()
}
}
#[derive(Debug, thiserror::Error)]
pub enum BootfsContentsError {
#[error("Failed to read `{}`.", path.display())]
ReadSourceFile {
path: PathBuf,
#[source]
source: std::io::Error,
},
#[error("`{_0}` does not exist in bootfs.")]
NoSuchFile(String),
}