| // 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 { |
| crate::{ |
| boot_args::BootArgs, |
| copier::recursive_copy, |
| crypt::{ |
| fxfs::{self, CryptService}, |
| get_policy, |
| zxcrypt::{UnsealOutcome, ZxcryptDevice}, |
| Policy, |
| }, |
| device::{ |
| constants::{ |
| BLOBFS_PARTITION_LABEL, BLOBFS_TYPE_GUID, DATA_PARTITION_LABEL, DATA_TYPE_GUID, |
| DEFAULT_F2FS_MIN_BYTES, FVM_DRIVER_PATH, LEGACY_DATA_PARTITION_LABEL, |
| ZXCRYPT_DRIVER_PATH, |
| }, |
| BlockDevice, Device, |
| }, |
| inspect::register_migration_status, |
| }, |
| anyhow::{anyhow, bail, Context, Error}, |
| async_trait::async_trait, |
| device_watcher::{recursive_wait, recursive_wait_and_open}, |
| fidl::endpoints::{create_proxy, ServerEnd}, |
| fidl_fuchsia_fxfs::MountOptions, |
| fidl_fuchsia_hardware_block_partition::Guid, |
| fidl_fuchsia_hardware_block_volume::{VolumeManagerMarker, VolumeMarker}, |
| fidl_fuchsia_io as fio, |
| fs_management::{ |
| filesystem::{ServingMultiVolumeFilesystem, ServingSingleVolumeFilesystem, ServingVolume}, |
| format::DiskFormat, |
| partition::fvm_allocate_partition, |
| Blobfs, ComponentType, F2fs, FSConfig, Fxfs, Minfs, |
| }, |
| fuchsia_async as fasync, |
| fuchsia_component::client::{connect_to_protocol, connect_to_protocol_at_path}, |
| fuchsia_zircon as zx, |
| futures::lock::Mutex, |
| std::{collections::HashSet, sync::Arc}, |
| uuid::Uuid, |
| }; |
| |
| const INITIAL_SLICE_COUNT: u64 = 1; |
| |
| /// Returned from Environment::launch_data to signal when formatting is required. |
| pub enum ServeFilesystemStatus { |
| Serving(Filesystem), |
| FormatRequired, |
| } |
| |
| /// Environment is a trait that performs actions when a device is matched. |
| /// Nb: matcher.rs depend on this interface being used in order to mock tests. |
| #[async_trait] |
| pub trait Environment: Send + Sync { |
| /// Attaches the specified driver to the device. |
| async fn attach_driver(&self, device: &mut dyn Device, driver_path: &str) -> Result<(), Error>; |
| |
| /// Binds the fvm driver and returns a list of the names of the child partitions. |
| async fn bind_and_enumerate_fvm( |
| &mut self, |
| device: &mut dyn Device, |
| ) -> Result<Vec<String>, Error>; |
| |
| /// Creates a static instance of Fxfs on `device` and calls serve_multi_volume(). Only creates |
| /// the overall Fxfs instance. Mount_blob_volume and mount_data_volume still need to be called. |
| async fn mount_fxblob(&mut self, device: &mut dyn Device) -> Result<(), Error>; |
| |
| /// Mounts Fxblob's blob volume on the given device. |
| async fn mount_blob_volume(&mut self) -> Result<(), Error>; |
| |
| /// Mounts Fxblob's data volume on the given device. |
| async fn mount_data_volume(&mut self) -> Result<(), Error>; |
| |
| /// Called after the fvm driver is bound. Waits for the block driver to bind itself to the |
| /// blobfs partition before creating a blobfs BlockDevice, which it passes into mount_blobfs(). |
| async fn mount_blobfs_on(&mut self, blobfs_partition_name: &str) -> Result<(), Error>; |
| |
| /// Called after the fvm driver is bound. Waits for the block driver to bind itself to the |
| /// data partition before creating a data BlockDevice, which it then passes into launch_data(). |
| /// Calls bind_data() on the mounted filesystem. |
| async fn mount_data_on(&mut self, data_partition_name: &str) -> Result<(), Error>; |
| |
| /// Wipe and recreate data partition before reformatting with a filesystem. |
| async fn format_data(&mut self, fvm_topo_path: &str) -> Result<Filesystem, Error>; |
| |
| /// Attempt to migrate |filesystem|, if requested, to some other format. |
| /// |device| should refer to the FVM partition for |filesystem|. |
| /// |
| /// Returns either: |
| /// None if the original filesystem should be used. |
| /// Some(filesystem) a migrated to a different filesystem should be used. |
| async fn try_migrate_data( |
| &mut self, |
| _device: &mut dyn Device, |
| _filesystem: &mut Filesystem, |
| ) -> Result<Option<Filesystem>, Error> { |
| Ok(None) |
| } |
| |
| /// Binds |filesystem| to the `/data` path. Fails if already bound. |
| fn bind_data(&mut self, filesystem: Filesystem) -> Result<(), Error>; |
| |
| /// Shreds the data volume, triggering a reformat on reboot. |
| /// The data volume must be Fxfs-formatted and must be currently serving. |
| async fn shred_data(&mut self) -> Result<(), Error>; |
| |
| /// Synchronously shut down all associated filesystems. |
| async fn shutdown(&mut self) -> Result<(), Error>; |
| } |
| |
| // Before a filesystem is mounted, we queue requests. |
| pub enum Filesystem { |
| Queue(Vec<ServerEnd<fio::DirectoryMarker>>), |
| Serving(ServingSingleVolumeFilesystem), |
| ServingMultiVolume( |
| // We hold onto crypt service here to avoid it prematurely shutting down. |
| // Fxfs may expect to find it in via VFS at a later time and it stops running |
| // when all channels are closed. |
| #[allow(dead_code)] CryptService, |
| ServingMultiVolumeFilesystem, |
| String, |
| ), |
| ServingVolumeInFxblob( |
| // We hold onto crypt service here to avoid it prematurely shutting down. |
| #[allow(dead_code)] Option<CryptService>, |
| String, |
| ), |
| Shutdown, |
| } |
| |
| impl Filesystem { |
| fn is_serving(&self) -> bool { |
| if let Self::Queue(_) = self { |
| false |
| } else { |
| true |
| } |
| } |
| |
| pub fn exposed_dir( |
| &mut self, |
| serving_fs: Option<&mut ServingMultiVolumeFilesystem>, |
| ) -> Result<fio::DirectoryProxy, Error> { |
| let (proxy, server) = create_proxy::<fio::DirectoryMarker>()?; |
| match self { |
| Filesystem::Queue(queue) => queue.push(server), |
| Filesystem::Serving(fs) => fs |
| .exposed_dir() |
| .clone(fio::OpenFlags::CLONE_SAME_RIGHTS, server.into_channel().into())?, |
| Filesystem::ServingMultiVolume(_, fs, data_volume_name) => fs |
| .volume(&data_volume_name) |
| .ok_or(anyhow!("data volume {} not found", data_volume_name))? |
| .exposed_dir() |
| .clone(fio::OpenFlags::CLONE_SAME_RIGHTS, server.into_channel().into())?, |
| Filesystem::ServingVolumeInFxblob(_, data_volume_name) => serving_fs |
| .unwrap() |
| .volume(&data_volume_name) |
| .ok_or(anyhow!("data volume {} not found", data_volume_name))? |
| .exposed_dir() |
| .clone(fio::OpenFlags::CLONE_SAME_RIGHTS, server.into_channel().into())?, |
| Filesystem::Shutdown => bail!(anyhow!("filesystem is shutting down")), |
| } |
| Ok(proxy) |
| } |
| |
| pub fn root( |
| &mut self, |
| serving_fs: Option<&mut ServingMultiVolumeFilesystem>, |
| ) -> Result<fio::DirectoryProxy, Error> { |
| let root = fuchsia_fs::directory::open_directory_no_describe( |
| &self.exposed_dir(serving_fs).context("failed to get exposed dir")?, |
| "root", |
| fio::OpenFlags::RIGHT_READABLE |
| | fio::OpenFlags::POSIX_EXECUTABLE |
| | fio::OpenFlags::POSIX_WRITABLE, |
| ) |
| .context("failed to open the root directory")?; |
| Ok(root) |
| } |
| |
| pub fn volume(&mut self, volume_name: &str) -> Option<&mut ServingVolume> { |
| match self { |
| Filesystem::ServingMultiVolume(_, fs, _) => fs.volume_mut(&volume_name), |
| Filesystem::ServingVolumeInFxblob(..) => unreachable!(), |
| _ => None, |
| } |
| } |
| |
| fn queue(&mut self) -> Option<&mut Vec<ServerEnd<fio::DirectoryMarker>>> { |
| match self { |
| Filesystem::Queue(queue) => Some(queue), |
| _ => None, |
| } |
| } |
| |
| pub async fn shutdown( |
| &mut self, |
| serving_fs: Option<&mut ServingMultiVolumeFilesystem>, |
| ) -> Result<(), Error> { |
| let old = std::mem::replace(self, Filesystem::Shutdown); |
| match old { |
| Filesystem::Queue(_) => Ok(()), |
| Filesystem::Serving(fs) => fs.shutdown().await.context("shutdown failed"), |
| Filesystem::ServingMultiVolume(_, fs, _) => { |
| fs.shutdown().await.context("shutdown failed") |
| } |
| Filesystem::ServingVolumeInFxblob(_, volume_name) => { |
| serving_fs.unwrap().shutdown_volume(&volume_name).await.context("shutdown failed") |
| } |
| Filesystem::Shutdown => Err(anyhow!("double shutdown!")), |
| } |
| } |
| } |
| |
| /// Implements the Environment trait and keeps track of mounted filesystems. |
| pub struct FshostEnvironment { |
| config: Arc<fshost_config::Config>, |
| // `fxblob` is set inside mount_fxblob() and represents the overall Fxfs instance which |
| // contains both a data and blob volume. |
| fxblob: Option<ServingMultiVolumeFilesystem>, |
| blobfs: Filesystem, |
| data: Filesystem, |
| fvm: Option<(/*topological_path*/ String, /*device_directory*/ fio::DirectoryProxy)>, |
| launcher: Arc<FilesystemLauncher>, |
| /// This lock can be taken and device.path() added to the vector to have them |
| /// ignored the next time they appear to the Watcher/Matcher code. |
| matcher_lock: Arc<Mutex<HashSet<String>>>, |
| inspector: fuchsia_inspect::Inspector, |
| } |
| |
| impl FshostEnvironment { |
| pub fn new( |
| config: Arc<fshost_config::Config>, |
| boot_args: BootArgs, |
| ramdisk_prefix: Option<String>, |
| matcher_lock: Arc<Mutex<HashSet<String>>>, |
| inspector: fuchsia_inspect::Inspector, |
| ) -> Self { |
| Self { |
| config: config.clone(), |
| fxblob: None, |
| blobfs: Filesystem::Queue(Vec::new()), |
| data: Filesystem::Queue(Vec::new()), |
| fvm: None, |
| launcher: Arc::new(FilesystemLauncher { config, boot_args, ramdisk_prefix }), |
| matcher_lock, |
| inspector, |
| } |
| } |
| |
| fn get_fvm(&self) -> Result<(&String, &fio::DirectoryProxy), Error> { |
| debug_assert!( |
| self.fvm.is_some(), |
| "fvm was not initialized, ensure `bind_and_enumerate_fvm()` was called!" |
| ); |
| if let Some((ref fvm_topo_path, ref fvm_dir)) = self.fvm { |
| return Ok((fvm_topo_path, fvm_dir)); |
| } |
| bail!("fvm was not initialized"); |
| } |
| |
| /// Returns a proxy for the exposed dir of the Blobfs filesystem. This can be called before |
| /// Blobfs is mounted and it will get routed once Blobfs is mounted. |
| pub fn blobfs_exposed_dir(&mut self) -> Result<fio::DirectoryProxy, Error> { |
| self.blobfs.exposed_dir(self.fxblob.as_mut()) |
| } |
| |
| /// Returns a proxy for the exposed dir of the data filesystem. This can be called before |
| /// "/data" is mounted and it will get routed once the data partition is mounted. |
| pub fn data_exposed_dir(&mut self) -> Result<fio::DirectoryProxy, Error> { |
| self.data.exposed_dir(self.fxblob.as_mut()) |
| } |
| |
| /// Returns a proxy for the root of the data filesystem. This can be called before "/data" is |
| /// mounted and it will get routed once the data partition is mounted. |
| pub fn data_root(&mut self) -> Result<fio::DirectoryProxy, Error> { |
| self.data.root(self.fxblob.as_mut()) |
| } |
| |
| pub fn launcher(&self) -> Arc<FilesystemLauncher> { |
| self.launcher.clone() |
| } |
| |
| /// Set the max partition size for data |
| async fn apply_data_partition_limits(&self, device: &mut dyn Device) { |
| if !self.launcher.is_ramdisk_device(device) { |
| if let Err(error) = device.set_partition_max_bytes(self.config.data_max_bytes).await { |
| tracing::warn!(%error, "Failed to set max partition size for data"); |
| } |
| } |
| } |
| |
| /// Formats a device with the specified disk format. Normally we only format the configured |
| /// format but this level of abstraction lets us select the format for use in migration code |
| /// paths. |
| async fn format_data_with_disk_format( |
| &mut self, |
| format: DiskFormat, |
| device: &mut dyn Device, |
| ) -> Result<Filesystem, Error> { |
| // Potentially bind and format zxcrypt first. |
| let mut zxcrypt_device; |
| let device = if (self.config.use_disk_migration && format == DiskFormat::Zxcrypt) |
| || self.launcher.requires_zxcrypt(format, device) |
| { |
| tracing::info!( |
| path = device.path(), |
| "Formatting zxcrypt before formatting inner data partition.", |
| ); |
| let ignore_paths = &mut *self.matcher_lock.lock().await; |
| self.attach_driver(device, ZXCRYPT_DRIVER_PATH).await?; |
| zxcrypt_device = |
| Box::new(ZxcryptDevice::format(device).await.context("zxcrypt format failed")?); |
| ignore_paths.insert(zxcrypt_device.topological_path().to_string()); |
| zxcrypt_device.as_mut() |
| } else { |
| device |
| }; |
| |
| // Set the max partition size for data |
| self.apply_data_partition_limits(device).await; |
| |
| let filesystem = match format { |
| DiskFormat::Fxfs => { |
| let config = |
| Fxfs { component_type: ComponentType::StaticChild, ..Default::default() }; |
| self.launcher.format_data(device, config).await? |
| } |
| DiskFormat::F2fs => { |
| let config = |
| F2fs { component_type: ComponentType::StaticChild, ..Default::default() }; |
| self.launcher.format_data(device, config).await? |
| } |
| DiskFormat::Minfs => { |
| let config = |
| Minfs { component_type: ComponentType::StaticChild, ..Default::default() }; |
| self.launcher.format_data(device, config).await? |
| } |
| format => { |
| tracing::warn!("Unsupported format {:?}", format); |
| return Err(anyhow!("Cannot format filesystem")); |
| } |
| }; |
| |
| Ok(filesystem) |
| } |
| |
| async fn try_migrate_data_internal( |
| &mut self, |
| device: &mut dyn Device, |
| filesystem: &mut Filesystem, |
| ) -> Result<Option<Filesystem>, Error> { |
| // Take note of the original device GUID. We will mark it inactive on success. |
| let device_guid = device.partition_instance().await.map(|guid| *guid).ok(); |
| |
| // The filesystem may be zxcrypt wrapped so we can't use device.content_type() here. |
| // We query the FS directly. |
| let device_format = match filesystem { |
| Filesystem::Serving(filesystem) => { |
| if let Some(vfs_type) = |
| fidl_fuchsia_fs::VfsType::from_primitive(filesystem.query().await?.fs_type) |
| { |
| match vfs_type { |
| fidl_fuchsia_fs::VfsType::Minfs => DiskFormat::Minfs, |
| fidl_fuchsia_fs::VfsType::F2Fs => DiskFormat::F2fs, |
| fidl_fuchsia_fs::VfsType::Fxfs => DiskFormat::Fxfs, |
| _ => { |
| return Ok(None); |
| } |
| } |
| } else { |
| return Ok(None); |
| } |
| } |
| Filesystem::ServingMultiVolume(_, _, _) => DiskFormat::Fxfs, |
| _ => { |
| return Ok(None); |
| } |
| }; |
| |
| let root = filesystem.root(None)?; |
| |
| // Read desired format from fs_switch, use config as default. |
| let desired_format = match fuchsia_fs::directory::open_file( |
| &root, |
| "fs_switch", |
| fio::OpenFlags::RIGHT_READABLE, |
| ) |
| .await |
| { |
| Ok(file) => { |
| let mut desired_format = fuchsia_fs::file::read_to_string(&file).await?; |
| desired_format = desired_format.trim_end().to_string(); |
| // "toggle" is a special format request that flip-flops between fxfs and minfs. |
| if desired_format.as_str() == "toggle" { |
| desired_format = |
| if device_format == DiskFormat::Fxfs { "minfs" } else { "fxfs" } |
| .to_string(); |
| } |
| desired_format |
| } |
| Err(error) => { |
| tracing::info!(%error, default_format=self.config.data_filesystem_format.as_str(), |
| "fs_switch open failed."); |
| self.config.data_filesystem_format.to_string() |
| } |
| }; |
| let desired_format = match desired_format.as_str().into() { |
| DiskFormat::Fxfs => DiskFormat::Fxfs, |
| DiskFormat::F2fs => DiskFormat::F2fs, |
| _ => DiskFormat::Minfs, |
| }; |
| |
| if device_format != desired_format { |
| tracing::info!( |
| device_format = device_format.as_str(), |
| desired_format = desired_format.as_str(), |
| "Attempting migration" |
| ); |
| |
| let volume_manager = connect_to_protocol_at_path::<VolumeManagerMarker>( |
| &device.fvm_path().ok_or(anyhow!("Not an fvm device"))?, |
| ) |
| .context("Failed to connect to fvm volume manager")?; |
| let new_instance_guid = *uuid::Uuid::new_v4().as_bytes(); |
| |
| // Note that if we are using fxfs or f2fs we should always be setting data_max_bytes |
| // because these filesystems cannot automatically grow/shrink themselves. |
| let slices = self.config.data_max_bytes / self.config.fvm_slice_size; |
| if slices == 0 { |
| bail!("data_max_bytes not set. Cannot migrate."); |
| } |
| tracing::info!(slices, "Allocating new partition"); |
| let new_data_partition_controller = fvm_allocate_partition( |
| &volume_manager, |
| DATA_TYPE_GUID, |
| new_instance_guid, |
| DATA_PARTITION_LABEL, |
| fidl_fuchsia_hardware_block_volume::ALLOCATE_PARTITION_FLAG_INACTIVE, |
| slices, |
| ) |
| .await |
| .context("Allocate migration partition.")?; |
| |
| let device_path = new_data_partition_controller |
| .get_topological_path() |
| .await? |
| .map_err(zx::Status::from_raw)?; |
| |
| let mut new_device = |
| BlockDevice::from_proxy(new_data_partition_controller, device_path).await?; |
| |
| let mut new_filesystem = |
| self.format_data_with_disk_format(desired_format, &mut new_device).await?; |
| |
| recursive_copy(&filesystem.root(None)?, &new_filesystem.root(None)?) |
| .await |
| .context("copy data")?; |
| |
| // Ensure the watcher won't process the device we just added. |
| { |
| let mut ignore_paths = self.matcher_lock.lock().await; |
| ignore_paths.insert(new_device.topological_path().to_string()); |
| } |
| |
| // Mark the old partition inactive (deletion at next boot). |
| if let Some(old_instance_guid) = device_guid { |
| zx::Status::ok( |
| volume_manager |
| .activate( |
| &Guid { value: old_instance_guid }, |
| &Guid { value: new_instance_guid }, |
| ) |
| .await?, |
| )?; |
| } |
| Ok(Some(new_filesystem)) |
| } else { |
| Ok(None) |
| } |
| } |
| |
| /// Mounts Blobfs on the given device. |
| async fn mount_blobfs(&mut self, device: &mut dyn Device) -> Result<(), Error> { |
| let queue = self.blobfs.queue().ok_or(anyhow!("blobfs already mounted"))?; |
| |
| let mut fs = self.launcher.serve_blobfs(device).await?; |
| |
| let exposed_dir = fs.exposed_dir(None)?; |
| for server in queue.drain(..) { |
| exposed_dir.clone(fio::OpenFlags::CLONE_SAME_RIGHTS, server.into_channel().into())?; |
| } |
| self.blobfs = fs; |
| Ok(()) |
| } |
| |
| /// Launch data partition on the given device. |
| /// If formatting is required, returns ServeFilesystemStatus::FormatRequired. |
| async fn launch_data( |
| &mut self, |
| device: &mut dyn Device, |
| ) -> Result<ServeFilesystemStatus, Error> { |
| let _ = self.data.queue().ok_or_else(|| anyhow!("data partition already mounted"))?; |
| |
| let mut format: DiskFormat = if self.config.use_disk_migration { |
| let format = device.content_format().await?; |
| tracing::info!( |
| format = format.as_str(), |
| "Using detected disk format to potentially \ |
| migrate data" |
| ); |
| format |
| } else { |
| match self.config.data_filesystem_format.as_str().into() { |
| DiskFormat::Fxfs => DiskFormat::Fxfs, |
| DiskFormat::F2fs => DiskFormat::F2fs, |
| // Default to minfs if we don't match expected filesystems. |
| _ => DiskFormat::Minfs, |
| } |
| }; |
| |
| // Potentially bind and unseal zxcrypt before serving data. |
| let mut zxcrypt_device = None; |
| let device = if (self.config.use_disk_migration && format == DiskFormat::Zxcrypt) |
| || self.launcher.requires_zxcrypt(format, device) |
| { |
| tracing::info!(path = device.path(), "Attempting to unseal zxcrypt device",); |
| let ignore_paths = &mut *self.matcher_lock.lock().await; |
| self.attach_driver(device, ZXCRYPT_DRIVER_PATH).await?; |
| let mut new_device = match ZxcryptDevice::unseal(device).await? { |
| UnsealOutcome::Unsealed(device) => device, |
| UnsealOutcome::FormatRequired => { |
| tracing::warn!("failed to unseal zxcrypt, format required"); |
| return Ok(ServeFilesystemStatus::FormatRequired); |
| } |
| }; |
| // If we are using content sniffing to identify the filesystem, we have to do it again |
| // after unsealing zxcrypt. |
| if self.config.use_disk_migration { |
| format = new_device.content_format().await?; |
| tracing::info!(format = format.as_str(), "detected zxcrypt wrapped format"); |
| } |
| ignore_paths.insert(new_device.topological_path().to_string()); |
| zxcrypt_device = Some(Box::new(new_device)); |
| zxcrypt_device.as_mut().unwrap().as_mut() |
| } else { |
| device |
| }; |
| |
| // Set the max partition size for data |
| self.apply_data_partition_limits(device).await; |
| |
| let filesystem = match format { |
| DiskFormat::Fxfs => { |
| let config = |
| Fxfs { component_type: ComponentType::StaticChild, ..Default::default() }; |
| self.launcher.serve_data(device, config).await |
| } |
| DiskFormat::F2fs => { |
| let config = |
| F2fs { component_type: ComponentType::StaticChild, ..Default::default() }; |
| self.launcher.serve_data(device, config).await |
| } |
| DiskFormat::Minfs => { |
| let config = |
| Minfs { component_type: ComponentType::StaticChild, ..Default::default() }; |
| self.launcher.serve_data(device, config).await |
| } |
| format => { |
| tracing::warn!(format = format.as_str(), "Unsupported filesystem"); |
| Ok(ServeFilesystemStatus::FormatRequired) |
| } |
| }?; |
| |
| if let ServeFilesystemStatus::FormatRequired = filesystem { |
| if let Some(device) = zxcrypt_device { |
| tracing::info!(path = device.path(), "Resealing zxcrypt device due to error."); |
| device.seal().await?; |
| } |
| } |
| |
| Ok(filesystem) |
| } |
| } |
| |
| #[async_trait] |
| impl Environment for FshostEnvironment { |
| async fn attach_driver(&self, device: &mut dyn Device, driver_path: &str) -> Result<(), Error> { |
| self.launcher.attach_driver(device, driver_path).await |
| } |
| |
| async fn bind_and_enumerate_fvm( |
| &mut self, |
| device: &mut dyn Device, |
| ) -> Result<Vec<String>, Error> { |
| // Attach the FVM driver and connect to the VolumeManager. |
| self.attach_driver(device, FVM_DRIVER_PATH).await?; |
| let fvm_dir = fuchsia_fs::directory::open_in_namespace( |
| &device.topological_path(), |
| fuchsia_fs::OpenFlags::RIGHT_READABLE, |
| )?; |
| let fvm_volume_manager_proxy = |
| recursive_wait_and_open::<VolumeManagerMarker>(&fvm_dir, "/fvm") |
| .await |
| .context("failed to connect to the VolumeManager")?; |
| |
| // **NOTE**: We must call VolumeManager::GetInfo() to ensure all partitions are visible when |
| // we enumerate them below. See https://fxbug.dev/42077585 for more information. |
| zx::ok(fvm_volume_manager_proxy.get_info().await.context("transport error on get_info")?.0) |
| .context("get_info failed")?; |
| |
| let fvm_topo_path = format!("{}/fvm", device.topological_path()); |
| let fvm_dir = fuchsia_fs::directory::open_in_namespace( |
| &fvm_topo_path, |
| fuchsia_fs::OpenFlags::RIGHT_READABLE, |
| )?; |
| let dir_entries = fuchsia_fs::directory::readdir(&fvm_dir).await?; |
| |
| self.fvm = Some((fvm_topo_path, fvm_dir)); |
| Ok(dir_entries.into_iter().map(|entry| entry.name).collect()) |
| } |
| |
| async fn mount_fxblob(&mut self, device: &mut dyn Device) -> Result<(), Error> { |
| tracing::info!( |
| path = %device.path(), |
| expected_format = "fxfs", |
| "Mounting fxblob" |
| ); |
| let serving_fs = self.launcher.serve_fxblob(device).await?; |
| self.fxblob = Some(serving_fs); |
| Ok(()) |
| } |
| |
| async fn mount_blob_volume(&mut self) -> Result<(), Error> { |
| let _ = self.blobfs.queue().ok_or_else(|| anyhow!("blobfs partition already mounted"))?; |
| |
| let multi_vol_fs = |
| self.fxblob.as_mut().ok_or_else(|| anyhow!("ServingMultiVolumeFilesystem is None"))?; |
| let () = multi_vol_fs |
| .check_volume("blob", None) |
| .await |
| .context("Failed to verify the blob volume")?; |
| let blobfs = multi_vol_fs |
| .open_volume("blob", MountOptions { crypt: None, as_blob: true }) |
| .await |
| .context("Failed to open the blob volume")?; |
| let exposed_dir = blobfs.exposed_dir(); |
| let queue = self.blobfs.queue().ok_or(anyhow!("blobfs already mounted"))?; |
| for server in queue.drain(..) { |
| exposed_dir.clone(fio::OpenFlags::CLONE_SAME_RIGHTS, server.into_channel().into())?; |
| } |
| self.blobfs = Filesystem::ServingVolumeInFxblob(None, "blob".to_string()); |
| if let Err(e) = multi_vol_fs.set_byte_limit("blob", self.config.blobfs_max_bytes).await { |
| tracing::warn!("Failed to set byte limit for the blob volume: {:?}", e); |
| } |
| Ok(()) |
| } |
| |
| async fn mount_data_volume(&mut self) -> Result<(), Error> { |
| let _ = self.data.queue().ok_or_else(|| anyhow!("data partition already mounted"))?; |
| |
| let multi_vol_fs = |
| self.fxblob.as_mut().ok_or_else(|| anyhow!("ServingMultiVolumeFilesystem is None"))?; |
| let mut filesystem = self.launcher.serve_data_fxblob(multi_vol_fs).await?; |
| if let Err(e) = multi_vol_fs.set_byte_limit("data", self.config.data_max_bytes).await { |
| tracing::warn!("Failed to set byte limit for the data volume: {:?}", e); |
| } |
| let queue = self.data.queue().unwrap(); |
| let exposed_dir = filesystem.exposed_dir(self.fxblob.as_mut())?; |
| for server in queue.drain(..) { |
| exposed_dir.clone(fio::OpenFlags::CLONE_SAME_RIGHTS, server.into_channel().into())?; |
| } |
| self.data = filesystem; |
| Ok(()) |
| } |
| |
| async fn mount_blobfs_on(&mut self, blobfs_partition_name: &str) -> Result<(), Error> { |
| let (fvm_topo_path, fvm_dir) = self.get_fvm()?; |
| let blobfs_topo_path = format!("{}/{blobfs_partition_name}/block", fvm_topo_path); |
| recursive_wait(fvm_dir, &format!("{blobfs_partition_name}/block")) |
| .await |
| .context("failed to bind block driver to blobfs device")?; |
| let mut device = BlockDevice::new(blobfs_topo_path) |
| .await |
| .context("failed to create blobfs block device")?; |
| let (label, type_guid) = |
| (device.partition_label().await?.to_string(), *device.partition_type().await?); |
| if !(label == BLOBFS_PARTITION_LABEL && type_guid == BLOBFS_TYPE_GUID) { |
| tracing::error!( |
| "incorrect parameters for blobfs partition: label = {}, type = {:?}", |
| label, |
| type_guid |
| ); |
| bail!("blobfs partition has incorrect label/guid"); |
| } |
| self.mount_blobfs(&mut device).await |
| } |
| |
| async fn mount_data_on(&mut self, data_partition_name: &str) -> Result<(), Error> { |
| let (fvm_topo_path, fvm_dir) = self.get_fvm()?; |
| let data_topo_path = format!("{}/{data_partition_name}/block", fvm_topo_path); |
| recursive_wait(fvm_dir, &format!("{data_partition_name}/block")) |
| .await |
| .context("failed to bind block driver to the data device")?; |
| let mut device = BlockDevice::new(data_topo_path) |
| .await |
| .context("failed to create blobfs block device")?; |
| let (label, type_guid) = |
| (device.partition_label().await?.to_string(), *device.partition_type().await?); |
| if !((label == DATA_PARTITION_LABEL || label == LEGACY_DATA_PARTITION_LABEL) |
| && type_guid == DATA_TYPE_GUID) |
| { |
| tracing::error!( |
| "incorrect parameters for data partition: label = {}, type = {:?}", |
| label, |
| type_guid |
| ); |
| bail!("data partition has incorrect label/guid"); |
| } |
| |
| let fs = match self.launch_data(&mut device).await? { |
| ServeFilesystemStatus::Serving(mut filesystem) => { |
| // If this build supports migrating data partition, try now, failing back to |
| // just using `filesystem` in the case of any error. Non-migration builds |
| // should return Ok(None). |
| match self.try_migrate_data(&mut device, &mut filesystem).await { |
| Ok(Some(new_filesystem)) => { |
| // Migration successful. |
| filesystem.shutdown(None).await.unwrap_or_else(|error| { |
| tracing::error!( |
| ?error, |
| "Failed to shutdown original filesystem after migration" |
| ); |
| }); |
| new_filesystem |
| } |
| Ok(None) => filesystem, // Migration not requested. |
| Err(error) => { |
| // Migration failed. |
| tracing::error!(?error, "Failed to migrate filesystem"); |
| // TODO: Log migration failure metrics. |
| // Continue with the original (unmigrated) filesystem. |
| filesystem |
| } |
| } |
| } |
| ServeFilesystemStatus::FormatRequired => { |
| self.format_data(&device.fvm_path().ok_or(anyhow!("Not an fvm device"))?).await? |
| } |
| }; |
| self.bind_data(fs) |
| } |
| |
| async fn format_data(&mut self, fvm_topo_path: &str) -> Result<Filesystem, Error> { |
| // Reset FVM partition first, ensuring we blow away any existing zxcrypt volume. |
| let mut device = self |
| .launcher |
| .reset_fvm_partition(fvm_topo_path, &mut *self.matcher_lock.lock().await) |
| .await |
| .with_context(|| { |
| format!("Failed to reset non-blob FVM partitions on {}", fvm_topo_path) |
| })?; |
| let device = device.as_mut(); |
| |
| // Default to minfs if we don't match expected filesystems. |
| let format: DiskFormat = match self.config.data_filesystem_format.as_str().into() { |
| DiskFormat::Fxfs => DiskFormat::Fxfs, |
| DiskFormat::F2fs => DiskFormat::F2fs, |
| _ => DiskFormat::Minfs, |
| }; |
| |
| // Rotate hardware derived key before formatting if we follow a Tee policy |
| if get_policy().await? != Policy::Null { |
| tracing::info!("Rotate hardware derived key before formatting"); |
| // Hardware derived keys are not rotatable on certain devices. |
| // TODO(b/271166111): Assert hard fail when we know rotating the key should work. |
| match kms_stateless::rotate_hardware_derived_key(kms_stateless::KeyInfo::new("zxcrypt")) |
| .await |
| { |
| Ok(()) => {} |
| Err(kms_stateless::Error::TeeCommandNotSupported( |
| kms_stateless::TaKeysafeCommand::RotateHardwareDerivedKey, |
| )) => { |
| tracing::warn!("The device does not support rotatable hardware keys.") |
| } |
| Err(e) => { |
| tracing::warn!("Rotate hardware key failed with error {:?}.", e) |
| } |
| } |
| } |
| |
| self.format_data_with_disk_format(format, device).await |
| } |
| |
| async fn try_migrate_data( |
| &mut self, |
| device: &mut dyn Device, |
| filesystem: &mut Filesystem, |
| ) -> Result<Option<Filesystem>, Error> { |
| if !self.config.use_disk_migration { |
| return Ok(None); |
| } |
| |
| let res = self.try_migrate_data_internal(device, filesystem).await; |
| if let Err(error) = &res { |
| tracing::warn!(%error, "migration failed"); |
| if let Some(status) = error.downcast_ref::<zx::Status>().clone() { |
| register_migration_status(self.inspector.root(), *status).await; |
| } else { |
| register_migration_status(self.inspector.root(), zx::Status::INTERNAL).await; |
| } |
| } else { |
| register_migration_status(self.inspector.root(), zx::Status::OK).await; |
| } |
| res |
| } |
| |
| fn bind_data(&mut self, mut filesystem: Filesystem) -> Result<(), Error> { |
| let _ = self.data.queue().ok_or_else(|| anyhow!("data partition already mounted"))?; |
| |
| let queue = self.data.queue().unwrap(); |
| let exposed_dir = filesystem.exposed_dir(None)?; |
| for server in queue.drain(..) { |
| exposed_dir.clone(fio::OpenFlags::CLONE_SAME_RIGHTS, server.into_channel().into())?; |
| } |
| self.data = filesystem; |
| Ok(()) |
| } |
| |
| async fn shred_data(&mut self) -> Result<(), Error> { |
| if self.config.data_filesystem_format != "fxfs" { |
| return Err(anyhow!("Can't shred data; not fxfs")); |
| } |
| if !self.data.is_serving() { |
| return Err(anyhow!("Can't shred data; not already mounted")); |
| } |
| // Erase the keybag. |
| let unencrypted = if let Some(fxblob) = &mut self.fxblob { |
| fxblob.volume_mut("unencrypted") |
| } else { |
| self.data.volume("unencrypted") |
| } |
| .context("Failed to find unencrypted volume")?; |
| let dir = fuchsia_fs::directory::open_directory( |
| unencrypted.root(), |
| "keys", |
| fio::OpenFlags::RIGHT_WRITABLE, |
| ) |
| .await |
| .context("Failed to open keys dir")?; |
| dir.unlink("fxfs-data", &fio::UnlinkOptions::default()) |
| .await? |
| .map_err(|e| anyhow!(zx::Status::from_raw(e))) |
| .context("Failed to remove keybag")?; |
| Ok(()) |
| } |
| |
| async fn shutdown(&mut self) -> Result<(), Error> { |
| // If we encounter an error, log it, but continue trying to shut down the remaining |
| // filesystems. |
| self.blobfs.shutdown(self.fxblob.as_mut()).await.unwrap_or_else(|error| { |
| tracing::error!(?error, "failed to shut down blobfs"); |
| }); |
| self.data.shutdown(self.fxblob.as_mut()).await.unwrap_or_else(|error| { |
| tracing::error!(?error, "failed to shut down data"); |
| }); |
| if let Some(fxfs) = self.fxblob.take() { |
| fxfs.shutdown().await.unwrap_or_else(|error| { |
| tracing::error!(?error, "failed to shut down fxfs"); |
| }) |
| } |
| Ok(()) |
| } |
| } |
| |
| #[allow(dead_code)] // TODO(https://fxbug.dev/318827209) |
| #[derive(Debug)] |
| struct ReformatRequired(Error); |
| impl std::error::Error for ReformatRequired {} |
| impl std::fmt::Display for ReformatRequired { |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { |
| write!(f, "{:?}", self) |
| } |
| } |
| |
| pub struct FilesystemLauncher { |
| config: Arc<fshost_config::Config>, |
| boot_args: BootArgs, |
| ramdisk_prefix: Option<String>, |
| } |
| |
| impl FilesystemLauncher { |
| pub async fn attach_driver( |
| &self, |
| device: &mut dyn Device, |
| driver_path: &str, |
| ) -> Result<(), Error> { |
| tracing::info!(path = %device.path(), %driver_path, "Binding driver to device"); |
| match device.controller().bind(driver_path).await?.map_err(zx::Status::from_raw) { |
| Err(e) if e == zx::Status::ALREADY_BOUND => { |
| // It's fine if we get an ALREADY_BOUND error. |
| tracing::info!(path = %device.path(), %driver_path, |
| "Ignoring ALREADY_BOUND error."); |
| Ok(()) |
| } |
| Err(e) => Err(e.into()), |
| Ok(()) => Ok(()), |
| } |
| } |
| |
| /// This helper method returns true if the given device is a ramdisk. |
| /// We want to enforce partition limits and use zxcrypt only on non-ramdisk devices. |
| pub fn is_ramdisk_device(&self, device: &dyn Device) -> bool { |
| self.ramdisk_prefix |
| .as_ref() |
| .map_or(false, |prefix| device.topological_path().starts_with(prefix)) |
| } |
| |
| pub fn requires_zxcrypt(&self, format: DiskFormat, device: &dyn Device) -> bool { |
| match format { |
| // Fxfs never has zxcrypt underneath |
| DiskFormat::Fxfs => false, |
| _ if self.config.no_zxcrypt => false, |
| // No point using zxcrypt for ramdisk devices. |
| _ if self.is_ramdisk_device(device) => false, |
| _ => true, |
| } |
| } |
| |
| pub fn get_blobfs_config(&self) -> Blobfs { |
| Blobfs { |
| write_compression_algorithm: self.boot_args.blobfs_write_compression_algorithm(), |
| cache_eviction_policy_override: self.boot_args.blobfs_eviction_policy(), |
| ..Default::default() |
| } |
| } |
| |
| pub async fn serve_blobfs(&self, device: &mut dyn Device) -> Result<Filesystem, Error> { |
| tracing::info!(path = %device.path(), "Mounting /blob"); |
| |
| // Setting max partition size for blobfs |
| if !self.is_ramdisk_device(device) { |
| if let Err(e) = device.set_partition_max_bytes(self.config.blobfs_max_bytes).await { |
| tracing::warn!("Failed to set max partition size for blobfs: {:?}", e); |
| }; |
| } |
| |
| let config = Blobfs { |
| component_type: fs_management::ComponentType::StaticChild, |
| ..self.get_blobfs_config() |
| }; |
| let fs = fs_management::filesystem::Filesystem::new(device.reopen_controller()?, config) |
| .serve() |
| .await |
| .context("serving blobfs")?; |
| |
| Ok(Filesystem::Serving(fs)) |
| } |
| |
| pub async fn serve_data<FSC: FSConfig>( |
| &self, |
| device: &mut dyn Device, |
| config: FSC, |
| ) -> Result<ServeFilesystemStatus, Error> { |
| let fs = fs_management::filesystem::Filesystem::new(device.reopen_controller()?, config); |
| self.serve_data_from(device, fs).await |
| } |
| |
| // NB: keep these larger functions monomorphized, otherwise they cause significant code size |
| // increases. |
| async fn serve_data_from( |
| &self, |
| device: &mut dyn Device, |
| mut fs: fs_management::filesystem::Filesystem, |
| ) -> Result<ServeFilesystemStatus, Error> { |
| let format = fs.config().disk_format(); |
| tracing::info!( |
| path = %device.path(), |
| expected_format = ?format, |
| "Mounting /data" |
| ); |
| |
| let detected_format = device.content_format().await?; |
| if detected_format != format { |
| tracing::info!( |
| ?detected_format, |
| expected_format = ?format, |
| "Expected format not detected. Reformatting.", |
| ); |
| return Ok(ServeFilesystemStatus::FormatRequired); |
| } |
| |
| if self.config.check_filesystems { |
| tracing::info!(?format, "fsck started"); |
| if let Err(error) = fs.fsck().await { |
| self.report_corruption(format, &error); |
| if self.config.format_data_on_corruption { |
| tracing::info!("Reformatting filesystem, expect data loss..."); |
| return Ok(ServeFilesystemStatus::FormatRequired); |
| } else { |
| tracing::error!(?format, "format on corruption is disabled, not continuing"); |
| return Err(error); |
| } |
| } else { |
| tracing::info!(?format, "fsck completed OK"); |
| } |
| } |
| |
| // Wrap the serving in an async block so we can catch all errors. |
| let serve_fut = async { |
| match format { |
| DiskFormat::Fxfs => { |
| let mut serving_multi_vol_fs = fs.serve_multi_volume().await?; |
| match fxfs::unlock_data_volume(&mut serving_multi_vol_fs, &self.config).await? { |
| Some((crypt_service, volume_name, _)) => { |
| Ok(ServeFilesystemStatus::Serving(Filesystem::ServingMultiVolume( |
| crypt_service, |
| serving_multi_vol_fs, |
| volume_name, |
| ))) |
| } |
| // If unlocking returns none, the keybag got deleted by something. |
| None => { |
| tracing::warn!( |
| "keybag not found. Perhaps the keys were shredded? \ |
| Reformatting the data volume." |
| ); |
| Ok(ServeFilesystemStatus::FormatRequired) |
| } |
| } |
| } |
| _ => Ok(ServeFilesystemStatus::Serving(Filesystem::Serving(fs.serve().await?))), |
| } |
| }; |
| match serve_fut.await { |
| Ok(fs) => Ok(fs), |
| Err(error) => { |
| self.report_corruption(format, &error); |
| if self.config.format_data_on_corruption { |
| tracing::info!("Reformatting filesystem, expect data loss..."); |
| Ok(ServeFilesystemStatus::FormatRequired) |
| } else { |
| tracing::error!(?format, "format on corruption is disabled, not continuing"); |
| Err(error) |
| } |
| } |
| } |
| } |
| |
| // Destroy all non-blob fvm partitions and reallocate only the data partition. Called on the |
| // reformatting codepath. Takes the topological path of an fvm device with the fvm driver |
| // bound. |
| async fn reset_fvm_partition( |
| &self, |
| fvm_topo_path: &str, |
| ignore_paths: &mut HashSet<String>, |
| ) -> Result<Box<dyn Device>, Error> { |
| tracing::info!(path = fvm_topo_path, "Resetting fvm partitions"); |
| let fvm_directory_proxy = fuchsia_fs::directory::open_in_namespace( |
| &fvm_topo_path, |
| fio::OpenFlags::RIGHT_READABLE, |
| )?; |
| let fvm_volume_manager_proxy = |
| connect_to_protocol_at_path::<VolumeManagerMarker>(&fvm_topo_path) |
| .context("Failed to connect to the fvm VolumeManagerProxy")?; |
| |
| // **NOTE**: We must call VolumeManager::GetInfo() to ensure all partitions are visible when |
| // we enumerate them below. See https://fxbug.dev/42077585 for more information. |
| zx::ok(fvm_volume_manager_proxy.get_info().await.context("transport error on get_info")?.0) |
| .context("get_info failed")?; |
| |
| let dir_entries = fuchsia_fs::directory::readdir(&fvm_directory_proxy).await?; |
| for entry in dir_entries { |
| // Destroy all fvm partitions aside from blobfs |
| if !entry.name.contains("blobfs") && !entry.name.contains("device") { |
| let entry_volume_proxy = recursive_wait_and_open::<VolumeMarker>( |
| &fvm_directory_proxy, |
| &format!("{}/block", entry.name), |
| ) |
| .await |
| .with_context(|| format!("Failed to open partition {}", entry.name))?; |
| ignore_paths.insert(format!("{fvm_topo_path}/{}/block", entry.name)); |
| let status = entry_volume_proxy |
| .destroy() |
| .await |
| .with_context(|| format!("Failed to destroy partition {}", entry.name))?; |
| zx::Status::ok(status).context("destroy() returned an error")?; |
| tracing::info!(partition = %entry.name, "Destroyed partition"); |
| } |
| } |
| |
| // Recreate the data partition |
| let data_partition_controller = fvm_allocate_partition( |
| &fvm_volume_manager_proxy, |
| DATA_TYPE_GUID, |
| *Uuid::new_v4().as_bytes(), |
| DATA_PARTITION_LABEL, |
| 0, |
| INITIAL_SLICE_COUNT, |
| ) |
| .await |
| .context("Failed to allocate fvm data partition")?; |
| |
| let device_path = data_partition_controller |
| .get_topological_path() |
| .await? |
| .map_err(zx::Status::from_raw)?; |
| |
| ignore_paths.insert(device_path.to_string()); |
| Ok(Box::new(BlockDevice::from_proxy(data_partition_controller, device_path).await?)) |
| } |
| |
| /// Starts serving Fxblob without opening any volumes. |
| pub async fn serve_fxblob( |
| &self, |
| device: &mut dyn Device, |
| ) -> Result<ServingMultiVolumeFilesystem, Error> { |
| let mut fs = fs_management::filesystem::Filesystem::new( |
| device.reopen_controller()?, |
| Fxfs { |
| component_type: ComponentType::StaticChild, |
| startup_profiling_seconds: Some(60), |
| ..Default::default() |
| }, |
| ); |
| fs.serve_multi_volume().await |
| } |
| |
| /// Serves the data volume from Fxblob, formatting any non-blob volumes as needed. |
| pub async fn serve_data_fxblob( |
| &self, |
| serving_multi_vol_fs: &mut ServingMultiVolumeFilesystem, |
| ) -> Result<Filesystem, Error> { |
| let mut format_needed = false; |
| tracing::info!(expected_format = "fxfs", "Mounting /data"); |
| |
| // We expect the startup protocol to work with fxblob. There are no options for |
| // reformatting the entire fxfs partition now that blobfs is one of the volumes. |
| let volumes_dir = fuchsia_fs::directory::open_directory( |
| serving_multi_vol_fs.exposed_dir(), |
| "volumes", |
| fuchsia_fs::OpenFlags::empty(), |
| ) |
| .await |
| .context("opening volumes directory")?; |
| let volumes = fuchsia_fs::directory::readdir(&volumes_dir) |
| .await |
| .context("reading volumes directory")?; |
| let needed = |
| HashSet::from(["blob".to_string(), "data".to_string(), "unencrypted".to_string()]); |
| let mut found = HashSet::new(); |
| for volume in volumes { |
| found.insert(volume.name); |
| } |
| |
| if found != needed { |
| format_needed = true; |
| } |
| |
| if format_needed { |
| Ok(self.format_data_in_fxblob(serving_multi_vol_fs).await?) |
| } else { |
| match fxfs::unlock_data_volume(serving_multi_vol_fs, &self.config).await { |
| Ok(Some((crypt_service, volume_name, _))) => { |
| Ok(Filesystem::ServingVolumeInFxblob(Some(crypt_service), volume_name)) |
| } |
| Ok(None) => { |
| tracing::warn!( |
| "could not find keybag. Perhaps the keys were shredded? \ |
| Reformatting the data and unencrypted volumes." |
| ); |
| self.format_data_in_fxblob(serving_multi_vol_fs).await |
| } |
| Err(error) => { |
| self.report_corruption(DiskFormat::Fxfs, &error); |
| tracing::error!( |
| ?error, |
| "unlock_data_volume failed. Reformatting the data and unencrypted volumes." |
| ); |
| Ok(self.format_data_in_fxblob(serving_multi_vol_fs).await?) |
| } |
| } |
| } |
| } |
| |
| async fn format_data_in_fxblob( |
| &self, |
| serving_multi_vol_fs: &mut ServingMultiVolumeFilesystem, |
| ) -> Result<Filesystem, Error> { |
| // Reset fxfs volumes before reinitializing. |
| let volumes_dir = fuchsia_fs::directory::open_directory_no_describe( |
| serving_multi_vol_fs.exposed_dir(), |
| "volumes", |
| fuchsia_fs::OpenFlags::empty(), |
| )?; |
| let volumes = fuchsia_fs::directory::readdir(&volumes_dir).await?; |
| for volume in volumes { |
| if volume.name != "blob".to_string() { |
| // Unmount mounted non-blob volumes. |
| if serving_multi_vol_fs.volume(&volume.name).is_some() { |
| serving_multi_vol_fs.shutdown_volume(&volume.name).await?; |
| } |
| // Remove any non-blob volumes. |
| serving_multi_vol_fs |
| .remove_volume(&volume.name) |
| .await |
| .with_context(|| format!("failed to remove volume: {:?}", volume.name))?; |
| } |
| } |
| |
| let (crypt_service, volume_name, _) = |
| fxfs::init_data_volume(serving_multi_vol_fs, &self.config) |
| .await |
| .context("initializing data volume encryption")?; |
| let filesystem = Filesystem::ServingVolumeInFxblob(Some(crypt_service), volume_name); |
| |
| Ok(filesystem) |
| } |
| |
| pub async fn format_data<FSC: FSConfig>( |
| &self, |
| device: &mut dyn Device, |
| config: FSC, |
| ) -> Result<Filesystem, Error> { |
| let fs = fs_management::filesystem::Filesystem::new(device.reopen_controller()?, config); |
| self.format_data_from(device, fs).await |
| } |
| |
| async fn format_data_from( |
| &self, |
| device: &mut dyn Device, |
| mut fs: fs_management::filesystem::Filesystem, |
| ) -> Result<Filesystem, Error> { |
| let format = fs.config().disk_format(); |
| tracing::info!(path = device.path(), format = format.as_str(), "Formatting"); |
| match format { |
| DiskFormat::Fxfs => { |
| let target_bytes = self.config.data_max_bytes; |
| tracing::info!(target_bytes, "Resizing data volume"); |
| let allocated_bytes = |
| device.resize(target_bytes).await.context("format volume resize")?; |
| if allocated_bytes < target_bytes { |
| tracing::warn!( |
| target_bytes, |
| allocated_bytes, |
| "Allocated less space than desired" |
| ); |
| } |
| } |
| DiskFormat::F2fs => { |
| let target_bytes = |
| std::cmp::max(self.config.data_max_bytes, DEFAULT_F2FS_MIN_BYTES); |
| tracing::info!(target_bytes, "Resizing data volume"); |
| let allocated_bytes = |
| device.resize(target_bytes).await.context("format volume resize")?; |
| if allocated_bytes < DEFAULT_F2FS_MIN_BYTES { |
| tracing::error!( |
| minimum_bytes = DEFAULT_F2FS_MIN_BYTES, |
| allocated_bytes, |
| "Not enough space for f2fs" |
| ) |
| } |
| if allocated_bytes < target_bytes { |
| tracing::warn!( |
| target_bytes, |
| allocated_bytes, |
| "Allocated less space than desired" |
| ); |
| } |
| } |
| _ => (), |
| } |
| |
| fs.format().await.context("formatting data partition")?; |
| |
| tracing::info!(path = device.path(), format = format.as_str(), "Serving"); |
| let filesystem = if let DiskFormat::Fxfs = format { |
| let mut serving_multi_vol_fs = |
| fs.serve_multi_volume().await.context("serving multi volume data partition")?; |
| let (crypt_service, volume_name, _) = |
| fxfs::init_data_volume(&mut serving_multi_vol_fs, &self.config) |
| .await |
| .context("initializing data volume encryption")?; |
| Filesystem::ServingMultiVolume(crypt_service, serving_multi_vol_fs, volume_name) |
| } else { |
| Filesystem::Serving(fs.serve().await.context("serving single volume data partition")?) |
| }; |
| |
| Ok(filesystem) |
| } |
| |
| fn report_corruption(&self, format: DiskFormat, error: &Error) { |
| tracing::error!(?format, ?error, "FILESYSTEM CORRUPTION DETECTED!"); |
| tracing::error!( |
| "Please file a bug to the Storage component in http://fxbug.dev, including a \ |
| device snapshot collected with `ffx target snapshot` if possible.", |
| ); |
| |
| fasync::Task::spawn(async move { |
| let proxy = if let Ok(proxy) = |
| connect_to_protocol::<fidl_fuchsia_feedback::CrashReporterMarker>() |
| { |
| proxy |
| } else { |
| tracing::error!("Failed to connect to crash report service"); |
| return; |
| }; |
| let report = fidl_fuchsia_feedback::CrashReport { |
| program_name: Some(format.as_str().to_owned()), |
| crash_signature: Some(format!("fuchsia-{}-corruption", format.as_str())), |
| is_fatal: Some(false), |
| ..Default::default() |
| }; |
| if let Err(e) = proxy.file_report(report).await { |
| tracing::error!(?e, "Failed to file crash report"); |
| } |
| }) |
| .detach(); |
| } |
| } |