blob: 8034df89fb5de9fa64e25c918341b651f01f1547 [file] [log] [blame]
// Copyright 2020 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::fdr::execute_reset;
use anyhow::{Context, Error};
use async_trait::async_trait;
use fidl::endpoints::ClientEnd;
use fidl_fuchsia_io as fio;
use fs_management as fs;
use recovery_util::block::get_block_devices;
pub const BLOBFS_MOUNT_POINT: &str = "/b";
/// Required functionality from an fs::Filesystem.
/// See fs_management for the documentation.
trait Filesystem {
fn format(&mut self) -> Result<(), Error>;
fn mount(&mut self, mount_point: &str) -> Result<(), Error>;
}
/// Forwards calls to the fs_management implementation.
impl Filesystem for fs::Filesystem<fs::Blobfs> {
fn format(&mut self) -> Result<(), Error> {
self.format()
}
fn mount(&mut self, mount_point: &str) -> Result<(), Error> {
self.mount(mount_point)
}
}
#[async_trait(?Send)]
pub trait Initializer {
async fn initialize(&self) -> Result<Storage, Error>;
}
/// Creates blobfs and minfs fs::Filesystem objects by scanning topological paths.
pub struct TopoPathInitializer {}
#[async_trait(?Send)]
impl Initializer for TopoPathInitializer {
async fn initialize(&self) -> Result<Storage, Error> {
// TODO(b/235401377): Add tests when component is moved to CFv2.
let block_devices = get_block_devices().await.context("Failed to get block devices")?;
let blobfs_config = fs::Blobfs { readonly: false, ..fs::Blobfs::default() };
let mut blobfs_path = "".to_string();
for block_device in block_devices {
let topo_path = block_device.topo_path;
// Skip ramdisk entries.
if topo_path.contains("/ramdisk-") {
continue;
}
// Store the path as /dev/class/block/<number>.
if topo_path.contains("/fvm/blobfs-") {
blobfs_path = block_device.class_path.clone();
}
}
let blobfs = fs::Filesystem::from_path(&blobfs_path, blobfs_config)
.context(format!("Failed to open blobfs: {:?}", blobfs_path))?;
Ok(Storage { blobfs: Box::new(blobfs) })
}
}
/// The object that controls the lifetime of the newly formatted blobfs.
/// The filesystem is accessible through the "/b" path on the current namespace,
/// as long as this object is alive.
pub struct Storage {
blobfs: Box<dyn Filesystem>,
}
impl Storage {
// TODO(fxbug.dev/100049): Switch to storage API which can handle reprovisioning FVM and minfs.
/// Reformat filesystems, then mount available filesystems.
pub async fn wipe_storage(&mut self) -> Result<(), Error> {
self.blobfs.format().context("Failed to format blobfs")?;
self.blobfs.mount(BLOBFS_MOUNT_POINT).context("Failed to mount blobfs")?;
Ok(())
}
// Instead of formatting the data partition directly, reset it via the factory reset service.
// The data partition is reformatted on first boot under normal circumstances and will do so
// after a reboot following being reset.
// This immediately reboots the device and needs to run separately from wipe_storage for now.
pub async fn wipe_data(&self) -> Result<(), Error> {
execute_reset().await.context("Failed to factory reset data")
}
/// Get the minfs mount point if it has been properly wiped and reinitialized.
pub fn get_minfs_mount_point(&self) -> Result<Option<String>, Error> {
println!("Formatting new minfs is not currently supported in recovery.");
Ok(None)
}
pub fn get_blobfs(&mut self) -> Result<ClientEnd<fio::DirectoryMarker>, Error> {
let (blobfs_root, remote) = fidl::endpoints::create_endpoints::<fio::DirectoryMarker>()?;
fdio::open(
BLOBFS_MOUNT_POINT,
fio::OpenFlags::RIGHT_READABLE
| fio::OpenFlags::RIGHT_WRITABLE
| fio::OpenFlags::RIGHT_EXECUTABLE,
remote.into_channel(),
)?;
Ok(blobfs_root)
}
}
#[cfg(test)]
mod tests {
use super::{Filesystem, Initializer, Storage, BLOBFS_MOUNT_POINT};
use anyhow::Error;
use async_trait::async_trait;
use fuchsia_async as fasync;
use std::{cell::RefCell, rc::Rc};
// Mock for a Filesystem.
struct FilesystemMock {
format_called: Rc<RefCell<bool>>,
mount_called: Rc<RefCell<bool>>,
}
impl FilesystemMock {
fn new(
format_call_tracker: Rc<RefCell<bool>>,
mount_call_tracker: Rc<RefCell<bool>>,
) -> FilesystemMock {
FilesystemMock {
format_called: format_call_tracker.clone(),
mount_called: mount_call_tracker.clone(),
}
}
}
impl Filesystem for FilesystemMock {
fn format(&mut self) -> Result<(), Error> {
self.format_called.replace(true);
Ok(())
}
fn mount(&mut self, mount_point: &str) -> Result<(), Error> {
assert_eq!(mount_point, BLOBFS_MOUNT_POINT);
self.mount_called.replace(true);
Ok(())
}
}
struct InitializerMock {
format_called: Rc<RefCell<bool>>,
mount_called: Rc<RefCell<bool>>,
}
impl InitializerMock {
async fn new(
format_call_tracker: Rc<RefCell<bool>>,
mount_call_tracker: Rc<RefCell<bool>>,
) -> Result<InitializerMock, Error> {
Ok(InitializerMock {
format_called: format_call_tracker.clone(),
mount_called: mount_call_tracker.clone(),
})
}
}
#[async_trait(?Send)]
impl Initializer for InitializerMock {
async fn initialize(&self) -> Result<Storage, Error> {
Ok(Storage {
blobfs: Box::new(FilesystemMock::new(
self.format_called.clone(),
self.mount_called.clone(),
)),
})
}
}
#[fasync::run_singlethreaded(test)]
async fn test_wipe_storage() -> Result<(), Error> {
let format_call_tracker = Rc::new(RefCell::new(false));
let mount_call_tracker = Rc::new(RefCell::new(false));
let initializer =
InitializerMock::new(format_call_tracker.clone(), mount_call_tracker.clone()).await?;
let mut storage = initializer.initialize().await?;
storage.wipe_storage().await?;
// Check that it formatted and mounted blobfs.
assert!(*format_call_tracker.borrow());
assert!(*mount_call_tracker.borrow());
Ok(())
}
}