blob: c61d5471077c56b05cbef365b4adac3809b038df [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.
//! ext4_readonly reads and exposes read-only ext4 file systems to clients.
//!
//! This binary supports two modes of operation:
//! - If a handle to a block device is given to the process, the file system contained within is
//! served over the directory request channel. The block device handle is expected to be passed as
//! `PA_USER0` arg 1 for compatibility with the existing `fs_management` library. The process
//! continues to run until the directory request channel is closed.
//! - Otherwise, the binary implements the `fuchsia.storage.ext4.Server` protocol and runs
//! indefinitely to process FIDL messages.
use {
anyhow::{format_err, Context as _, Error},
ext4_parser::{construct_fs, ConstructFsError, FsSourceType},
ext4_read_only::structs::{InvalidAddressErrorType, ParsingError},
fidl::endpoints::ServerEnd,
fidl_fuchsia_io as fio,
fidl_fuchsia_storage_ext4::{
BadDirectory, BadEntryType, BadFile, BannedFeatureIncompat, BlockNumberOutOfBounds,
BlockSizeInvalid, DirEntry2NonUtf8, ExtentUnexpectedLength, Incompatible, InvalidAddress,
InvalidBlockGroupDesc, InvalidDirEntry2, InvalidExtent, InvalidExtentHeader,
InvalidExtentHeaderMagic, InvalidINode, InvalidInputPath, InvalidSuperBlock,
InvalidSuperBlockMagic, MountVmoResult, OutOfBoundsDirection, ParseError, PathNotFound,
ReaderReadError, RequiredFeatureIncompat, Server_Request, Server_RequestStream,
ServiceRequest, Success,
},
fuchsia_component::server::ServiceFs,
fuchsia_runtime::{take_startup_handle, HandleInfo, HandleType},
fuchsia_zircon::{Channel, Vmo},
futures::{
future::TryFutureExt,
stream::{StreamExt, TryStreamExt},
},
tracing::info,
vfs::{directory::entry_container::Directory, execution_scope::ExecutionScope, path::Path},
};
async fn run_ext4_server(mut stream: Server_RequestStream) -> Result<(), Error> {
while let Some(req) = stream.try_next().await.context("Error while reading request")? {
match req {
Server_Request::MountVmo { source, flags, root, responder } => {
// Each mount get's its own scope. We may provide additional control over this
// scope in the future. For example, one thing we may want to do is also return an
// "administrative chanel" that would allow calling "shutdown" on a mount.
let scope = ExecutionScope::new();
let res = serve_vmo(scope, source, flags, root);
// If the connection was already closed when we tried to send the result, there is
// nothing we can do.
let _ = responder.send(&res);
}
}
}
Ok(())
}
fn construct_fs_error_to_mount_vmo_result(source: ConstructFsError) -> MountVmoResult {
match source {
ConstructFsError::VmoReadError(status) => MountVmoResult::VmoReadFailure(status.into_raw()),
ConstructFsError::ParsingError(error) => {
let result = match error {
ParsingError::InvalidSuperBlock(pos) => {
ParseError::InvalidSuperBlock(InvalidSuperBlock { position: pos as u64 })
}
ParsingError::InvalidSuperBlockMagic(val) => {
ParseError::InvalidSuperBlockMagic(InvalidSuperBlockMagic { value: val })
}
ParsingError::BlockNumberOutOfBounds(num) => {
ParseError::BlockNumberOutOfBounds(BlockNumberOutOfBounds { block_number: num })
}
ParsingError::BlockSizeInvalid(bs) => {
ParseError::BlockSizeInvalid(BlockSizeInvalid { block_size: bs })
}
ParsingError::InvalidBlockGroupDesc(pos) => {
ParseError::InvalidBlockGroupDesc(InvalidBlockGroupDesc {
position: pos as u64,
})
}
ParsingError::InvalidInode(num) => {
ParseError::InvalidInode(InvalidINode { inode_number: num })
}
ParsingError::InvalidExtentHeader => {
ParseError::InvalidExtentHeader(InvalidExtentHeader {})
}
ParsingError::InvalidExtentHeaderMagic(val) => {
ParseError::InvalidExtentHeaderMagic(InvalidExtentHeaderMagic { value: val })
}
ParsingError::InvalidExtent(pos) => {
ParseError::InvalidExtent(InvalidExtent { position: pos as u64 })
}
ParsingError::ExtentUnexpectedLength(size, exp) => {
ParseError::ExtentUnexpectedLength(ExtentUnexpectedLength {
size: size as u64,
expected: exp as u64,
})
}
ParsingError::InvalidDirEntry2(pos) => {
ParseError::InvalidDirEntry2(InvalidDirEntry2 { position: pos as u64 })
}
ParsingError::DirEntry2NonUtf8(val) => {
ParseError::DirEntry2NonUtf8(DirEntry2NonUtf8 { data: val })
}
ParsingError::InvalidInputPath => {
// TODO(https://fxbug.dev/42073143): Get the actual path.
ParseError::InvalidInputPath(InvalidInputPath { path: "".to_string() })
}
ParsingError::PathNotFound(path) => {
ParseError::PathNotFound(PathNotFound { path: path })
}
ParsingError::BadEntryType(val) => {
ParseError::BadEntryType(BadEntryType { value: val })
}
ParsingError::BannedFeatureIncompat(val) => {
ParseError::BannedFeatureIncompat(BannedFeatureIncompat { value: val })
}
ParsingError::RequiredFeatureIncompat(val) => {
ParseError::RequiredFeatureIncompat(RequiredFeatureIncompat { value: val })
}
ParsingError::Incompatible(msg) => {
ParseError::Incompatible(Incompatible { msg: msg })
}
ParsingError::BadFile(path) => ParseError::BadFile(BadFile { path: path }),
ParsingError::BadDirectory(path) => {
ParseError::BadDirectory(BadDirectory { path: path })
}
ParsingError::SourceReadError(pos) => {
ParseError::ReaderReadError(ReaderReadError { position: pos as u64 })
}
ParsingError::InvalidAddress(direction, pos, bound) => {
let mut dir = OutOfBoundsDirection::Below;
if direction == InvalidAddressErrorType::Upper {
dir = OutOfBoundsDirection::Above
}
ParseError::InvalidAddress(InvalidAddress {
position: pos as u64,
direction: dir,
bound: bound as u64,
})
}
ParsingError::InvalidInodeSize(size) => ParseError::Incompatible(Incompatible {
msg: format!("Invalid inode size: {}", size),
}),
ParsingError::NotFile => unreachable!(),
};
MountVmoResult::ParseError(result)
}
}
}
fn serve_vmo(
scope: ExecutionScope,
source: Vmo,
flags: fio::OpenFlags,
root: ServerEnd<fio::DirectoryMarker>,
) -> MountVmoResult {
let tree = match construct_fs(FsSourceType::Vmo(source)) {
Ok(tree) => tree,
Err(err) => return construct_fs_error_to_mount_vmo_result(err),
};
tree.open(scope, flags, Path::dot(), root.into_channel().into());
MountVmoResult::Success(Success {})
}
enum IncomingService {
Server(Server_RequestStream),
Svc(ServiceRequest),
}
// `run` argument is the number of thread to use for the server.
#[fuchsia::main(threads = 10)]
async fn main() -> Result<(), Error> {
info!("Starting ext4_readonly");
let block_device_handle_info = HandleInfo::new(HandleType::User0, 1);
let directory_handle_info = HandleType::DirectoryRequest.into();
if let Some(block_device_handle) = take_startup_handle(block_device_handle_info) {
info!("Opening block device");
let directory_handle = take_startup_handle(directory_handle_info).unwrap();
let tree = match construct_fs(FsSourceType::BlockDevice(
Channel::from(block_device_handle).into(),
)) {
Ok(tree) => tree,
Err(err) => return Err(format_err!("Failed to construct file system: {:?}", err)),
};
let scope = ExecutionScope::new();
tree.open(
scope.clone(),
fio::OpenFlags::RIGHT_READABLE,
Path::dot(),
Channel::from(directory_handle).into(),
);
// Wait until the directory connection is closed by the client before exiting.
scope.wait().await;
info!("ext4 directory connection dropped, exiting");
return Ok(());
}
info!("Starting ext4 server");
let mut fs = ServiceFs::new();
fs.dir("svc")
.add_fidl_service(IncomingService::Server)
.add_unified_service(IncomingService::Svc);
fs.take_and_serve_directory_handle()?;
const MAX_CONCURRENT: usize = 10_000;
let fut = fs.for_each_concurrent(MAX_CONCURRENT, move |request| {
match request {
IncomingService::Server(stream) => run_ext4_server(stream),
IncomingService::Svc(ServiceRequest::Server(stream)) => run_ext4_server(stream),
}
.unwrap_or_else(|e| println!("{:?}", e))
});
fut.await;
Ok(())
}
#[cfg(test)]
mod tests {}