blob: 83b8bb5f296709996878dc4c3ff7a29f2b5fe9b4 [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::{
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");
};
}
}