blob: d5370dca6a15bc69842c9c35ded0f9136f4b9618 [file] [log] [blame]
// Copyright 2021 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 {
crate::gpt,
anyhow::{Context, Error},
fidl_fuchsia_device::ControllerProxy,
fidl_fuchsia_fshost::{BlockWatcherMarker, BlockWatcherProxy},
fidl_fuchsia_hardware_block_partition::PartitionMarker,
fuchsia_async as fasync,
fuchsia_syslog::{fx_log_err, fx_log_info},
fuchsia_zircon as zx,
futures::prelude::*,
payload_streamer::PayloadStreamer,
ramdevice_client::{RamdiskClient, RamdiskClientBuilder},
std::fs::File,
};
/// Block size of the ramdisk.
const BLOCK_SIZE: u64 = 512;
/// Returns the size in bytes of the partition at `path`.
pub async fn get_partition_size(path: &str) -> Result<usize, Error> {
let (status, size) =
fuchsia_component::client::connect_to_service_at_path::<PartitionMarker>(path)
.context("connecting to partition")?
.get_info()
.await
.context("sending get info request")?;
zx::Status::ok(status).context("getting partition info")?;
let size = size.unwrap();
Ok((size.block_size as usize) * (size.block_count as usize))
}
struct BlockWatcherPauser {
connection: Option<BlockWatcherProxy>,
}
impl BlockWatcherPauser {
async fn new() -> Result<Self, Error> {
let connection = fuchsia_component::client::connect_to_service::<BlockWatcherMarker>()
.context("connecting to block watcher")?;
connection.pause().await.context("pausing the block watcher")?;
Ok(Self { connection: Some(connection) })
}
async fn resume(&mut self) -> Result<(), Error> {
if let Some(conn) = self.connection.take() {
conn.resume().await.context("resuming the block watcher")?;
}
Ok(())
}
}
impl Drop for BlockWatcherPauser {
fn drop(&mut self) {
if self.connection.is_some() {
let mut executor = fuchsia_async::Executor::new().expect("failed to create executor");
executor
.run_singlethreaded(self.resume())
.map_err(|e| fx_log_err!("failed to resume block watcher: {:?}", e))
.ok();
}
}
}
pub struct FvmRamdisk {
ramdisk: RamdiskClient,
sparse_fvm_path: String,
pauser: BlockWatcherPauser,
}
impl FvmRamdisk {
pub async fn new(ramdisk_size: u64, sparse_fvm_path: String) -> Result<Self, Error> {
Self::new_with_physmemfn(ramdisk_size, sparse_fvm_path, zx::system_get_physmem).await
}
async fn new_with_physmemfn<PhysmemFn>(
ramdisk_size: u64,
sparse_fvm_path: String,
get_physmem_size: PhysmemFn,
) -> Result<Self, Error>
where
PhysmemFn: Fn() -> u64,
{
let physmem_size = get_physmem_size();
if ramdisk_size >= physmem_size {
return Err(anyhow::anyhow!(
"Not enough available physical memory to create the ramdisk!"
));
}
let pauser = BlockWatcherPauser::new().await.context("pausing block watcher")?;
let ramdisk = Self::create_ramdisk_with_fvm(ramdisk_size)
.await
.context("Creating ramdisk with partition table")?;
let ret = FvmRamdisk { ramdisk, sparse_fvm_path, pauser };
// Manually bind the GPT driver now that the disk contains a valid GPT, so that
// the paver can pave to the disk.
ret.rebind_gpt_driver().await.context("Rebinding GPT driver")?;
Ok(ret)
}
/// Pave the FVM into this ramdisk, consuming this object
/// and leaving the ramdisk to run once the application has quit.
pub async fn pave_fvm(mut self) -> Result<(), Error> {
fx_log_info!("connecting to the paver");
let channel = self.ramdisk.open().context("Opening ramdisk")?;
let paver =
fuchsia_component::client::connect_to_service::<fidl_fuchsia_paver::PaverMarker>()?;
let (data_sink, remote) =
fidl::endpoints::create_proxy::<fidl_fuchsia_paver::DynamicDataSinkMarker>()?;
paver.use_block_device(fidl::endpoints::ClientEnd::new(channel), remote)?;
let size =
get_partition_size(&self.sparse_fvm_path).await.context("getting partition size")?;
let file = File::open(&self.sparse_fvm_path)?;
let streamer = PayloadStreamer::new(Box::new(file), size);
let (client, mut server) =
fidl::endpoints::create_request_stream::<fidl_fuchsia_paver::PayloadStreamMarker>()?;
fasync::Task::spawn(async move {
while let Some(req) = server.try_next().await.expect("Failed to get request") {
streamer.handle_request(req).await.expect("Failed to handle request!");
}
})
.detach();
data_sink.write_volumes(client).await.context("writing volumes")?;
self.pauser.resume().await.context("resuming block watcher")?;
// Now that the ramdisk has successfully been paved, and the block watcher resumed,
// we need to force a rebind of everything so that the block watcher sees the final
// state of the FVM partition. Without this, the block watcher ignores the FVM because
// it was paused when it appeared.
self.rebind_gpt_driver().await.context("rebinding GPT driver")?;
// Forget the ramdisk, so that we don't run its destructor (which closes it).
// This means that we don't have to keep running to keep the ramdisk around.
std::mem::forget(self.ramdisk);
Ok(())
}
/// Creates a ramdisk and writes a suitable partition table to it,
/// and automatically rebinds the appropriate drivers so the GPT is parsed.
async fn create_ramdisk_with_fvm(size: u64) -> Result<RamdiskClient, Error> {
let blocks = size / BLOCK_SIZE;
let ramdisk =
RamdiskClientBuilder::new(BLOCK_SIZE, blocks).build().context("Building ramdisk")?;
let channel = ramdisk.open().context("Opening ramdisk")?;
let file: File = fdio::create_fd(channel.into()).context("Creating FD")?;
gpt::write_ramdisk(Box::new(file)).context("writing GPT")?;
Ok(ramdisk)
}
/// Explicitly binds the GPT driver to the given ramdisk.
async fn rebind_gpt_driver(&self) -> Result<(), Error> {
let channel = self.ramdisk.open()?;
let controller = ControllerProxy::new(fidl::AsyncChannel::from_channel(channel)?);
controller.rebind("gpt.so").await?.map_err(zx::Status::from_raw)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[fasync::run_singlethreaded(test)]
async fn test_not_enough_physmem() {
assert!(FvmRamdisk::new_with_physmemfn(512, "".to_owned(), || 512).await.is_err());
assert!(FvmRamdisk::new_with_physmemfn(512, "".to_owned(), || 6).await.is_err());
}
}