blob: 6f82a1ec5a14e2d9f2b3d6428a40f00ebabee708 [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.
// This launches the binary specified in the arguments and takes the block device that is passed via
// the usual startup handle and makes it appear to the child process as an object that will work
// with POSIX I/O. The object will exist in the child's namespace under /device/block. At the time
// of writing, this is used to run the fsck-msdosfs and mkfs-msdosfs tools which use POSIX I/O to
// interact with block devices.
use {
anyhow::Error,
async_trait::async_trait,
fidl::endpoints::{create_endpoints, ServerEnd},
fidl_fuchsia_hardware_block as fhardware_block, fidl_fuchsia_io as fio,
fuchsia_async as fasync, fuchsia_zircon as zx,
remote_block_device::{BlockClient as _, BufferSlice, MutableBufferSlice, RemoteBlockClient},
std::sync::Arc,
vfs::{
attributes,
common::rights_to_posix_mode_bits,
directory::{
entry::{DirectoryEntry, EntryInfo, OpenRequest},
entry_container::Directory,
},
execution_scope::ExecutionScope,
file::{FidlIoConnection, File, FileIo, FileLike, FileOptions, SyncMode},
node::Node,
path::Path,
pseudo_directory, ObjectRequestRef,
},
};
fn map_to_status(err: Error) -> zx::Status {
if let Some(&status) = err.root_cause().downcast_ref::<zx::Status>() {
status
} else {
// Print the internal error if we re-map it because we will lose any context after this.
println!("Internal error: {:?}", err);
zx::Status::INTERNAL
}
}
struct BlockFile {
block_client: RemoteBlockClient,
}
impl DirectoryEntry for BlockFile {
fn entry_info(&self) -> EntryInfo {
EntryInfo::new(0, fio::DirentType::File)
}
fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), zx::Status> {
request.open_file(self)
}
}
#[async_trait]
impl Node for BlockFile {
async fn get_attrs(&self) -> Result<fio::NodeAttributes, zx::Status> {
let block_size = self.block_client.block_size();
let block_count = self.block_client.block_count();
let device_size = block_count.checked_mul(block_size.into()).unwrap();
Ok(fio::NodeAttributes {
mode: fio::MODE_TYPE_FILE
| rights_to_posix_mode_bits(/*r*/ true, /*w*/ true, /*x*/ false),
id: 0,
content_size: device_size,
storage_size: device_size,
link_count: 1,
creation_time: 0,
modification_time: 0,
})
}
async fn get_attributes(
&self,
requested_attributes: fio::NodeAttributesQuery,
) -> Result<fio::NodeAttributes2, zx::Status> {
let block_size = self.block_client.block_size();
let block_count = self.block_client.block_count();
let device_size = block_count.checked_mul(block_size.into()).unwrap();
Ok(attributes!(
requested_attributes,
Mutable { creation_time: 0, modification_time: 0, mode: 0, uid: 0, gid: 0, rdev: 0 },
Immutable {
protocols: fio::NodeProtocolKinds::FILE,
abilities: fio::Operations::GET_ATTRIBUTES
| fio::Operations::UPDATE_ATTRIBUTES
| fio::Operations::READ_BYTES
| fio::Operations::WRITE_BYTES,
content_size: device_size,
storage_size: device_size,
link_count: 1,
id: 0,
}
))
}
}
impl File for BlockFile {
fn writable(&self) -> bool {
true
}
async fn open_file(&self, _options: &FileOptions) -> Result<(), zx::Status> {
Ok(())
}
async fn truncate(&self, _length: u64) -> Result<(), zx::Status> {
Err(zx::Status::NOT_SUPPORTED)
}
async fn get_backing_memory(&self, _flags: fio::VmoFlags) -> Result<zx::Vmo, zx::Status> {
Err(zx::Status::NOT_SUPPORTED)
}
async fn get_size(&self) -> Result<u64, zx::Status> {
let block_size = self.block_client.block_size();
let block_count = self.block_client.block_count();
Ok(block_count.checked_mul(block_size.into()).unwrap())
}
async fn set_attrs(
&self,
_flags: fio::NodeAttributeFlags,
_attrs: fio::NodeAttributes,
) -> Result<(), zx::Status> {
Err(zx::Status::NOT_SUPPORTED)
}
async fn update_attributes(
&self,
_attributes: fio::MutableNodeAttributes,
) -> Result<(), zx::Status> {
Err(zx::Status::NOT_SUPPORTED)
}
async fn sync(&self, _mode: SyncMode) -> Result<(), zx::Status> {
self.block_client.flush().await.map_err(map_to_status)
}
}
impl FileIo for BlockFile {
async fn read_at(&self, offset: u64, buffer: &mut [u8]) -> Result<u64, zx::Status> {
let () = self
.block_client
.read_at(MutableBufferSlice::Memory(buffer), offset)
.await
.map_err(map_to_status)?;
Ok(buffer.len().try_into().unwrap())
}
async fn write_at(&self, offset: u64, content: &[u8]) -> Result<u64, zx::Status> {
let () = self
.block_client
.write_at(BufferSlice::Memory(content), offset)
.await
.map_err(map_to_status)?;
Ok(content.len().try_into().unwrap())
}
async fn append(&self, _content: &[u8]) -> Result<(u64, u64), zx::Status> {
Err(zx::Status::NOT_SUPPORTED)
}
}
impl FileLike for BlockFile {
fn open(
self: Arc<Self>,
scope: ExecutionScope,
options: FileOptions,
object_request: ObjectRequestRef<'_>,
) -> Result<(), zx::Status> {
FidlIoConnection::spawn(scope, self, options, object_request)
}
}
// This launches the binary specified in the arguments and takes a block device and makes it appear
// to the child process as an object that will work with POSIX I/O. The object will exist in the
// child's namespace under /device/block. At the time of writing, this is used to run the
// fsck-msdosfs and mkfs-msdosfs tools which use POSIX I/O to interact with block devices.
pub async fn run(
block_proxy: fhardware_block::BlockProxy,
binary: &str,
args: impl Iterator<Item = String>,
) -> Result<i64, Error> {
let (client, server) = create_endpoints::<fio::DirectoryMarker>();
let server_fut = {
let block_client = RemoteBlockClient::new(block_proxy).await?;
let dir = pseudo_directory! {
"block" => Arc::new(BlockFile {
block_client,
}),
};
let scope = ExecutionScope::new();
let () = dir.open(
scope.clone(),
fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE,
Path::dot(),
ServerEnd::new(server.into_channel()),
);
async move { scope.wait().await }
};
let client_fut = {
let mut builder = fdio::SpawnBuilder::new()
.options(fdio::SpawnOptions::CLONE_ALL)
.add_directory_to_namespace("/device", client)?
.arg(binary)?;
for arg in args {
builder = builder.arg(arg)?;
}
builder = builder.arg("/device/block")?;
let process = builder.spawn_from_path(binary, &fuchsia_runtime::job_default())?;
async move {
let _: zx::Signals =
fasync::OnSignals::new(&process, zx::Signals::PROCESS_TERMINATED).await?;
let info = process.info()?;
Ok::<_, Error>(info.return_code)
}
};
futures::future::join(server_fut, client_fut).await.1
}