blob: 883aa2cc53dc6e92c65e9d3412152c442edb2625 [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.
use {
fidl::AsHandleRef as _,
fidl_fuchsia_io as fio, fidl_fuchsia_kernel as fkernel, fuchsia_async as fasync,
fuchsia_bootfs::{BootfsParser, BootfsParserError},
fuchsia_component::client,
fuchsia_runtime::{take_startup_handle, HandleInfo, HandleType},
fuchsia_zircon::{self as zx, HandleBased, Resource},
std::sync::Arc,
thiserror::Error,
tracing::info,
vfs::{
directory::immutable::connection::ImmutableConnection,
execution_scope::ExecutionScope,
file::vmo,
tree_builder::{self, TreeBuilder},
ToObjectRequest,
},
};
// Used to create executable VMOs.
const BOOTFS_VMEX_NAME: &str = "bootfs_vmex";
// If passed as a kernel handle, this gets relocated into a '/boot/log' directory.
const KERNEL_CRASHLOG_NAME: &str = "crashlog";
const LAST_PANIC_FILEPATH: &str = "log/last-panic.txt";
// Kernel startup VMOs are published beneath '/boot/kernel'. The VFS is relative
// to '/boot', so we only need to prepend paths under that.
const KERNEL_VMO_SUBDIRECTORY: &str = "kernel/";
// Bootfs will sequentially number files and directories starting with this value.
// This is a self contained immutable filesystem, so we only need to ensure that
// there are no internal collisions.
const FIRST_INODE_VALUE: u64 = 1;
// Packages in bootfs can contain both executable and read-only files. For example,
// 'pkg/my_package/bin' should be executable but 'pkg/my_package/foo' should not.
const BOOTFS_PACKAGE_PREFIX: &str = "pkg";
const BOOTFS_EXECUTABLE_PACKAGE_DIRECTORIES: &[&str] = &["bin", "lib"];
// Top level directories in bootfs that are allowed to contain executable files.
// Every file in these directories will have ZX_RIGHT_EXECUTE.
const BOOTFS_EXECUTABLE_DIRECTORIES: &[&str] = &["bin", "driver", "lib", "test", "blob"];
#[derive(Debug, Error)]
pub enum BootfsError {
#[error("Invalid handle: {handle_type:?}")]
InvalidHandle { handle_type: HandleType },
#[error("Failed to duplicate handle: {0}")]
DuplicateHandle(zx::Status),
#[error("Failed to access vmex Resource: {0}")]
Vmex(zx::Status),
#[error("BootfsParser error: {0}")]
Parser(#[from] BootfsParserError),
#[error("Failed to add entry to Bootfs VFS: {0}")]
AddEntry(#[from] tree_builder::Error),
#[error("Failed to locate entry for {name}")]
MissingEntry { name: String },
#[error("Failed to create an executable VMO: {0}")]
ExecVmo(zx::Status),
#[error("VMO operation failed: {0}")]
Vmo(zx::Status),
#[error("Failed to get VMO name: {0}")]
VmoName(zx::Status),
#[error("Failed to create VMO child at offset {offset}: {err}")]
VmoCreateChild { offset: u64, err: zx::Status },
#[error("Failed to convert numerical value: {0}")]
ConvertNumber(#[from] std::num::TryFromIntError),
#[error("Failed to convert string value: {0}")]
ConvertString(#[from] std::ffi::IntoStringError),
#[error("Failed to bind Bootfs to Component Manager's namespace: {0}")]
Namespace(zx::Status),
}
pub struct BootfsSvc {
next_inode: u64,
parser: BootfsParser,
bootfs: zx::Vmo,
tree_builder: TreeBuilder,
}
impl BootfsSvc {
pub fn new() -> Result<Self, BootfsError> {
let bootfs_handle = take_startup_handle(HandleType::BootfsVmo.into())
.ok_or(BootfsError::InvalidHandle { handle_type: HandleType::BootfsVmo })?;
let bootfs = zx::Vmo::from(bootfs_handle);
Self::new_internal(bootfs)
}
fn new_internal(bootfs: zx::Vmo) -> Result<Self, BootfsError> {
let bootfs_dup = bootfs
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.map_err(BootfsError::DuplicateHandle)?
.into();
let parser = BootfsParser::create_from_vmo(bootfs_dup)?;
Ok(Self {
next_inode: FIRST_INODE_VALUE,
parser,
bootfs,
tree_builder: TreeBuilder::empty_dir(),
})
}
fn get_next_inode(inode: &mut u64) -> u64 {
let next_inode = *inode;
*inode += 1;
next_inode
}
fn file_in_executable_directory(path: &Vec<&str>) -> bool {
// If the first token is 'pkg', the second token can be anything, with the third
// token needing to be within the list of allowed executable package directories.
if path.len() > 2 && path[0] == BOOTFS_PACKAGE_PREFIX {
if BOOTFS_EXECUTABLE_PACKAGE_DIRECTORIES.iter().any(|dir| path[2] == *dir) {
return true;
}
}
// If the first token is an allowed executable directory, everything beneath it
// can be marked executable.
if path.len() > 1 {
if BOOTFS_EXECUTABLE_DIRECTORIES.iter().any(|dir| path[0] == *dir) {
return true;
}
}
false
}
fn create_dir_entry_with_child(
parent: &zx::Vmo,
offset: u64,
size: u64,
executable: bool,
inode: u64,
) -> Result<Arc<vmo::VmoFile>, BootfsError> {
// If this is a VMO with execution rights, passing zx::VmoChildOptions::NO_WRITE will
// allow the child to also inherit execution rights. Without that flag execution
// rights are stripped, even if the VMO already lacked write permission.
let child = parent
.create_child(
zx::VmoChildOptions::SNAPSHOT_AT_LEAST_ON_WRITE | zx::VmoChildOptions::NO_WRITE,
offset,
size,
)
.map_err(|err| BootfsError::VmoCreateChild { offset, err })?;
Ok(BootfsSvc::create_dir_entry(child, executable, inode))
}
fn create_dir_entry(vmo: zx::Vmo, executable: bool, inode: u64) -> Arc<vmo::VmoFile> {
vmo::VmoFile::new_with_inode(
vmo, /*readable*/ true, /*writable*/ false, executable, inode,
)
}
/// Read configs from the parsed bootfs image before the filesystem has been fully initialized.
/// This is required for configs needed to run the VFS, such as the component manager config
/// which specifies the number of threads for the executor which the VFS needs to run within.
/// Path should be relative to '/boot' without a leading forward slash.
pub fn read_config_from_uninitialized_vfs(
&self,
config_path: &str,
) -> Result<Vec<u8>, BootfsError> {
for entry in self.parser.zero_copy_iter() {
let entry = entry?;
assert!(entry.payload.is_none()); // Using the zero copy iterator.
if entry.name == config_path {
let mut buffer = vec![0; usize::try_from(entry.size)?];
self.bootfs.read(&mut buffer, entry.offset).map_err(BootfsError::Vmo)?;
return Ok(buffer);
}
}
Err(BootfsError::MissingEntry { name: config_path.to_string() })
}
pub fn ingest_bootfs_vmo_with_system_resource(
self,
system: &Option<Resource>,
) -> Result<Self, BootfsError> {
let system = system
.as_ref()
.ok_or(BootfsError::InvalidHandle { handle_type: HandleType::Resource })?;
let vmex = system
.create_child(
zx::ResourceKind::SYSTEM,
None,
zx::sys::ZX_RSRC_SYSTEM_VMEX_BASE,
1,
BOOTFS_VMEX_NAME.as_bytes(),
)
.map_err(BootfsError::Vmex)?;
self.ingest_bootfs_vmo(vmex)
}
pub async fn ingest_bootfs_vmo_with_namespace_vmex(self) -> Result<Self, BootfsError> {
let vmex_service = client::connect_to_protocol::<fkernel::VmexResourceMarker>()
.map_err(|_| BootfsError::Vmex(zx::Status::UNAVAILABLE))?;
let vmex =
vmex_service.get().await.map_err(|_| BootfsError::Vmex(zx::Status::UNAVAILABLE))?;
self.ingest_bootfs_vmo(vmex)
}
fn ingest_bootfs_vmo(self, vmex: Resource) -> Result<Self, BootfsError> {
// The bootfs VFS is comprised of multiple child VMOs which are just offsets into a
// single backing parent VMO.
//
// The parent VMO is duplicated here and marked as executable to reduce the total
// number of syscalls required. Files in directories that are read-only will just
// be children of the original read-only VMO, and files in directories that are
// read-execution will be children of the duplicated read-execution VMO.
let bootfs_exec: zx::Vmo = self
.bootfs
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.map_err(BootfsError::DuplicateHandle)?
.into();
let bootfs_exec = bootfs_exec.replace_as_executable(&vmex).map_err(BootfsError::ExecVmo)?;
self.ingest_bootfs_vmo_internal(bootfs_exec)
}
pub fn ingest_bootfs_vmo_internal(mut self, bootfs_exec: zx::Vmo) -> Result<Self, BootfsError> {
for entry in self.parser.zero_copy_iter() {
let entry = entry?;
assert!(entry.payload.is_none()); // Using the zero copy iterator.
let name = entry.name;
let path_parts: Vec<&str> = name.split("/").filter(|&x| !x.is_empty()).collect();
let is_exec = BootfsSvc::file_in_executable_directory(&path_parts);
let vmo = if is_exec { &bootfs_exec } else { &self.bootfs };
let dir_entry = BootfsSvc::create_dir_entry_with_child(
vmo,
entry.offset,
entry.size,
is_exec,
BootfsSvc::get_next_inode(&mut self.next_inode),
)?;
self.tree_builder.add_entry(&path_parts, dir_entry)?;
}
Ok(self)
}
// Publish a VMO beneath '/boot/kernel'. Used to publish VDSOs and kernel files.
pub fn publish_kernel_vmo(mut self, vmo: zx::Vmo) -> Result<Self, BootfsError> {
let name = vmo.get_name().map_err(BootfsError::VmoName)?.into_string()?;
if name.is_empty() {
// Skip VMOs without names.
return Ok(self);
}
let path = format!("{}{}", KERNEL_VMO_SUBDIRECTORY, name);
let mut path_parts: Vec<&str> = path.split("/").filter(|&x| !x.is_empty()).collect();
// There is special handling for the crashlog.
if path_parts.len() > 1 && path_parts[path_parts.len() - 1] == KERNEL_CRASHLOG_NAME {
path_parts = LAST_PANIC_FILEPATH.split("/").filter(|&x| !x.is_empty()).collect();
}
let vmo_size = vmo.get_size().map_err(BootfsError::Vmo)?;
if vmo_size == 0 {
// Skip empty VMOs.
return Ok(self);
}
// If content size is zero, set it to the size of the VMO.
if vmo.get_content_size().map_err(BootfsError::Vmo)? == 0 {
vmo.set_content_size(&vmo_size).map_err(BootfsError::Vmo)?;
}
let info = vmo.basic_info().map_err(BootfsError::Vmo)?;
let is_exec = info.rights.contains(zx::Rights::EXECUTE);
let dir_entry = BootfsSvc::create_dir_entry(
vmo,
is_exec,
BootfsSvc::get_next_inode(&mut self.next_inode),
);
self.tree_builder.add_entry(&path_parts, dir_entry)?;
Ok(self)
}
/// Publish all VMOs of a given type provided to this process through its processargs
/// bootstrap message. An initial index can be provided to skip handles that were already
/// taken.
pub fn publish_kernel_vmos(
mut self,
handle_type: HandleType,
first_index: u16,
) -> Result<Self, BootfsError> {
info!(
"[BootfsSvc] Adding kernel VMOs of type {:?} starting at index {}.",
handle_type, first_index
);
// The first handle may not be at index 0 if we have already taken it previously.
let mut index = first_index;
loop {
let vmo = take_startup_handle(HandleInfo::new(handle_type, index)).map(zx::Vmo::from);
match vmo {
Some(vmo) => {
index += 1;
self = self.publish_kernel_vmo(vmo)?;
}
None => break,
}
}
Ok(self)
}
pub fn create_and_bind_vfs(mut self) -> Result<(), BootfsError> {
info!("[BootfsSvc] Finalizing rust bootfs service.");
let (directory, directory_server_end) = fidl::endpoints::create_endpoints();
let mut get_inode = |_| -> u64 { BootfsSvc::get_next_inode(&mut self.next_inode) };
let vfs = self.tree_builder.build_with_inode_generator(&mut get_inode);
// Run the service with its own executor to avoid reentrancy issues.
std::thread::spawn(move || {
let flags = fio::OpenFlags::RIGHT_READABLE
| fio::OpenFlags::RIGHT_EXECUTABLE
| fio::OpenFlags::DIRECTORY;
fasync::LocalExecutor::new().run_singlethreaded(
flags
.to_object_request(directory_server_end)
.handle(|object_request| {
ImmutableConnection::create(
ExecutionScope::new(),
vfs,
flags,
object_request,
)
})
.unwrap(),
);
});
let ns = fdio::Namespace::installed().map_err(BootfsError::Namespace)?;
assert!(
ns.unbind("/boot").is_err(),
"No filesystem should already be bound to /boot when BootfsSvc is starting."
);
ns.bind("/boot", directory).map_err(BootfsError::Namespace)?;
info!("[BootfsSvc] Bootfs is ready and is now serving /boot.");
Ok(())
}
}