| // 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::{ |
| errors::FxfsError, |
| object_store::{ |
| directory::Directory, |
| filesystem::FxFilesystem, |
| transaction::{LockKey, TransactionHandler}, |
| ObjectDescriptor, ObjectStore, |
| }, |
| }, |
| anyhow::{anyhow, bail, Context, Error}, |
| std::sync::Arc, |
| }; |
| |
| // Volumes are a grouping of an object store and a root directory within this object store. They |
| // model a hierarchical tree of objects within a single store. |
| // |
| // Typically there will be one root volume which is referenced directly by the superblock. This root |
| // volume stores references to all other volumes on the system (as volumes/foo, volumes/bar, ...). |
| // For now, this hierarchy is only one deep. |
| |
| const VOLUMES_DIRECTORY: &str = "volumes"; |
| |
| /// RootVolume is the top-level volume which stores references to all of the other Volumes. |
| pub struct RootVolume { |
| _root_directory: Directory<ObjectStore>, |
| volume_directory: Directory<ObjectStore>, |
| filesystem: Arc<FxFilesystem>, |
| } |
| |
| impl RootVolume { |
| pub fn volume_directory(&self) -> &Directory<ObjectStore> { |
| &self.volume_directory |
| } |
| |
| /// Creates a new volume. This is not thread-safe. |
| pub async fn new_volume(&self, volume_name: &str) -> Result<Arc<ObjectStore>, Error> { |
| let root_store = self.filesystem.root_store(); |
| let store; |
| let mut transaction = self.filesystem.clone().new_transaction(&[]).await?; |
| store = root_store.create_child_store(&mut transaction).await?; |
| |
| let root_directory = Directory::create(&mut transaction, &store).await?; |
| store.set_root_directory_object_id(&mut transaction, root_directory.object_id()); |
| |
| self.volume_directory.add_child_volume( |
| &mut transaction, |
| volume_name, |
| store.store_object_id(), |
| ); |
| transaction.commit().await; |
| |
| Ok(store) |
| } |
| |
| /// Returns the volume with the given name. This is not thread-safe. |
| pub async fn volume(&self, volume_name: &str) -> Result<Arc<ObjectStore>, Error> { |
| let object_id = |
| match self.volume_directory.lookup(volume_name).await?.ok_or(FxfsError::NotFound)? { |
| (object_id, ObjectDescriptor::Volume) => object_id, |
| _ => bail!(FxfsError::Inconsistent), |
| }; |
| Ok(if let Some(volume_store) = self.filesystem.store(object_id) { |
| volume_store |
| } else { |
| self.filesystem.root_store().open_store(object_id).await? |
| }) |
| } |
| |
| pub async fn open_or_create_volume( |
| &self, |
| volume_name: &str, |
| ) -> Result<Arc<ObjectStore>, Error> { |
| match self.volume(volume_name).await { |
| Ok(volume) => Ok(volume), |
| Err(e) => { |
| let cause = e.root_cause().downcast_ref::<FxfsError>().cloned(); |
| if let Some(FxfsError::NotFound) = cause { |
| self.new_volume(volume_name).await |
| } else { |
| Err(e) |
| } |
| } |
| } |
| } |
| } |
| |
| /// Returns the root volume for the filesystem or creates it if it does not exist. |
| pub async fn root_volume(fs: &Arc<FxFilesystem>) -> Result<RootVolume, Error> { |
| let root_store = fs.root_store(); |
| |
| let root_directory = Directory::open(&root_store, root_store.root_directory_object_id()) |
| .await |
| .context("Unable to open root volume directory")?; |
| |
| let mut transaction = None; |
| let volume_directory = loop { |
| match root_directory.lookup(VOLUMES_DIRECTORY).await? { |
| None => { |
| if let Some(mut transaction) = transaction { |
| let directory = root_directory |
| .create_child_dir(&mut transaction, VOLUMES_DIRECTORY) |
| .await?; |
| transaction.commit().await; |
| break directory; |
| } else { |
| transaction = Some( |
| fs.clone() |
| .new_transaction(&[LockKey::object( |
| root_store.store_object_id(), |
| root_directory.object_id(), |
| )]) |
| .await?, |
| ); |
| } |
| } |
| Some((object_id, ObjectDescriptor::Directory)) => { |
| break Directory::open(&root_store, object_id) |
| .await |
| .context("unable to open volumes directory")?; |
| } |
| _ => { |
| return Err(anyhow!(FxfsError::Inconsistent) |
| .context("Unexpected type for volumes directory")); |
| } |
| } |
| }; |
| |
| Ok(RootVolume { _root_directory: root_directory, volume_directory, filesystem: fs.clone() }) |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use { |
| super::root_volume, |
| crate::{ |
| device::DeviceHolder, |
| object_store::{ |
| directory::Directory, |
| filesystem::{FxFilesystem, SyncOptions}, |
| transaction::TransactionHandler, |
| }, |
| testing::fake_device::FakeDevice, |
| }, |
| anyhow::Error, |
| fuchsia_async as fasync, |
| }; |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_lookup_nonexistent_volume() -> Result<(), Error> { |
| let device = DeviceHolder::new(FakeDevice::new(2048, 512)); |
| let filesystem = FxFilesystem::new_empty(device).await?; |
| let root_volume = root_volume(&filesystem).await.expect("root_volume failed"); |
| root_volume.volume("vol").await.err().expect("Volume shouldn't exist"); |
| Ok(()) |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_add_volume() { |
| let device = DeviceHolder::new(FakeDevice::new(2048, 512)); |
| let filesystem = FxFilesystem::new_empty(device).await.expect("new_empty failed"); |
| { |
| let root_volume = root_volume(&filesystem).await.expect("root_volume failed"); |
| let store = root_volume.new_volume("vol").await.expect("new_volume failed"); |
| let mut transaction = |
| filesystem.clone().new_transaction(&[]).await.expect("new transaction failed"); |
| let root_directory = Directory::open(&store, store.root_directory_object_id()) |
| .await |
| .expect("open failed"); |
| root_directory |
| .create_child_file(&mut transaction, "foo") |
| .await |
| .expect("create_child_file failed"); |
| transaction.commit().await; |
| filesystem.sync(SyncOptions::default()).await.expect("sync failed"); |
| }; |
| { |
| let filesystem = |
| FxFilesystem::open(filesystem.take_device().await).await.expect("open failed"); |
| let root_volume = root_volume(&filesystem).await.expect("root_volume failed"); |
| let volume = root_volume.volume("vol").await.expect("volume failed"); |
| let root_directory = Directory::open(&volume, volume.root_directory_object_id()) |
| .await |
| .expect("open failed"); |
| root_directory.lookup("foo").await.expect("lookup failed").expect("not found"); |
| }; |
| } |
| } |