blob: a983bf7c6daca61f6948f40c73b4209a481fe1a2 [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,
lsm_tree::{
merge::{Merger, MergerIterator},
types::{ItemRef, LayerIterator},
},
object_handle::{ObjectHandle, ObjectProperties},
object_store::{
object_record::{
ObjectAttributes, ObjectItem, ObjectKey, ObjectKeyData, ObjectKind, ObjectValue,
Timestamp,
},
transaction::{Mutation, Transaction},
HandleOptions, HandleOwner, ObjectStore, ObjectStoreMutation, StoreObjectHandle,
},
trace_duration,
},
anyhow::{anyhow, bail, ensure, Error},
std::{
fmt,
ops::Bound,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
},
};
// ObjectDescriptor is exposed in Directory::lookup.
pub use crate::object_store::object_record::ObjectDescriptor;
/// A directory stores name to child object mappings.
pub struct Directory<S> {
owner: Arc<S>,
object_id: u64,
is_deleted: AtomicBool,
}
impl<S: HandleOwner> Directory<S> {
fn new(owner: Arc<S>, object_id: u64) -> Self {
Directory { owner, object_id, is_deleted: AtomicBool::new(false) }
}
pub fn object_id(&self) -> u64 {
return self.object_id;
}
pub fn owner(&self) -> &Arc<S> {
&self.owner
}
pub fn store(&self) -> &ObjectStore {
self.owner.as_ref().as_ref()
}
pub fn is_deleted(&self) -> bool {
self.is_deleted.load(Ordering::Relaxed)
}
pub fn set_deleted(&self) {
self.is_deleted.store(true, Ordering::Relaxed);
}
pub async fn create(
transaction: &mut Transaction<'_>,
owner: &Arc<S>,
) -> Result<Directory<S>, Error> {
let store = owner.as_ref().as_ref();
let object_id = store.get_next_object_id().await?;
let now = Timestamp::now();
transaction.add(
store.store_object_id,
Mutation::insert_object(
ObjectKey::object(object_id),
ObjectValue::Object {
kind: ObjectKind::Directory { sub_dirs: 0 },
attributes: ObjectAttributes {
creation_time: now.clone(),
modification_time: now,
},
},
),
);
Ok(Directory::new(owner.clone(), object_id))
}
pub async fn open(owner: &Arc<S>, object_id: u64) -> Result<Directory<S>, Error> {
trace_duration!("Directory::open");
let store = owner.as_ref().as_ref();
match store.tree.find(&ObjectKey::object(object_id)).await?.ok_or(FxfsError::NotFound)? {
ObjectItem {
value: ObjectValue::Object { kind: ObjectKind::Directory { .. }, .. },
..
} => Ok(Directory::new(owner.clone(), object_id)),
ObjectItem { value: ObjectValue::None, .. } => bail!(FxfsError::NotFound),
_ => bail!(FxfsError::NotDir),
}
}
async fn has_children(&self) -> Result<bool, Error> {
if self.is_deleted() {
return Ok(false);
}
let layer_set = self.store().tree().layer_set();
let mut merger = layer_set.merger();
Ok(self.iter(&mut merger).await?.get().is_some())
}
/// Returns the object ID and descriptor for the given child, or None if not found.
pub async fn lookup(&self, name: &str) -> Result<Option<(u64, ObjectDescriptor)>, Error> {
trace_duration!("Directory::lookup");
if self.is_deleted() {
return Ok(None);
}
match self.store().tree().find(&ObjectKey::child(self.object_id, name)).await? {
None | Some(ObjectItem { value: ObjectValue::None, .. }) => Ok(None),
Some(ObjectItem {
value: ObjectValue::Child { object_id, object_descriptor }, ..
}) => Ok(Some((object_id, object_descriptor))),
Some(item) => Err(anyhow!(FxfsError::Inconsistent)
.context(format!("Unexpected item in lookup: {:?}", item))),
}
}
pub async fn create_child_dir<'a>(
&self,
transaction: &mut Transaction<'a>,
name: &str,
) -> Result<Directory<S>, Error> {
ensure!(!self.is_deleted(), FxfsError::Deleted);
let handle = Directory::create(transaction, &self.owner).await?;
transaction.add(
self.store().store_object_id(),
Mutation::replace_or_insert_object(
ObjectKey::child(self.object_id, name),
ObjectValue::child(handle.object_id(), ObjectDescriptor::Directory),
),
);
self.update_attributes(transaction, None, Some(Timestamp::now()), |item| {
if let ObjectItem {
value: ObjectValue::Object { kind: ObjectKind::Directory { sub_dirs }, .. },
..
} = item
{
*sub_dirs = sub_dirs.saturating_add(1);
} else {
panic!("Expected directory");
}
})
.await?;
Ok(handle)
}
pub async fn create_child_file<'a>(
&self,
transaction: &mut Transaction<'a>,
name: &str,
) -> Result<StoreObjectHandle<S>, Error> {
ensure!(!self.is_deleted(), FxfsError::Deleted);
let handle =
ObjectStore::create_object(&self.owner, transaction, HandleOptions::default(), None)
.await?;
transaction.add(
self.store().store_object_id(),
Mutation::replace_or_insert_object(
ObjectKey::child(self.object_id, name),
ObjectValue::child(handle.object_id(), ObjectDescriptor::File),
),
);
self.update_attributes(transaction, None, Some(Timestamp::now()), |_| {}).await?;
Ok(handle)
}
pub async fn add_child_volume<'a>(
&self,
transaction: &mut Transaction<'a>,
volume_name: &str,
store_object_id: u64,
) -> Result<(), Error> {
ensure!(!self.is_deleted(), FxfsError::Deleted);
transaction.add(
self.store().store_object_id(),
Mutation::replace_or_insert_object(
ObjectKey::child(self.object_id, volume_name),
ObjectValue::child(store_object_id, ObjectDescriptor::Volume),
),
);
self.update_attributes(transaction, None, Some(Timestamp::now()), |_| {}).await
}
pub async fn delete_child_volume<'a>(
&self,
transaction: &mut Transaction<'a>,
volume_name: &str,
store_object_id: u64,
) -> Result<(), Error> {
ensure!(!self.is_deleted(), FxfsError::Deleted);
transaction.add(
self.store().store_object_id(),
Mutation::replace_or_insert_object(
ObjectKey::child(self.object_id, volume_name),
ObjectValue::None,
),
);
// We note in the journal that we've deleted the volume. ObjectManager applies this
// mutation by forgetting the store. We do it this way to ensure that the store is removed
// during replay where there may be mutations to the store prior to its deletion. Without
// this, we will try (and fail) to open the store after replay.
transaction.add(store_object_id, Mutation::DeleteVolume);
Ok(())
}
/// Inserts a child into the directory.
///
/// Requires transaction locks on |self|.
pub async fn insert_child<'a>(
&self,
transaction: &mut Transaction<'a>,
name: &str,
object_id: u64,
descriptor: ObjectDescriptor,
) -> Result<(), Error> {
ensure!(!self.is_deleted(), FxfsError::Deleted);
transaction.add(
self.store().store_object_id(),
Mutation::replace_or_insert_object(
ObjectKey::child(self.object_id, name),
ObjectValue::child(object_id, descriptor),
),
);
self.update_attributes(transaction, None, Some(Timestamp::now()), |_| {}).await
}
/// Updates attributes for the object. `updater` is a callback that allows modifications to
/// the object's attributes beyond just creation time and modification time.
pub async fn update_attributes<'a>(
&self,
transaction: &mut Transaction<'a>,
crtime: Option<Timestamp>,
mtime: Option<Timestamp>,
updater: impl FnOnce(&mut ObjectItem),
) -> Result<(), Error> {
ensure!(!self.is_deleted(), FxfsError::Deleted);
let mut item = if let Some(ObjectStoreMutation { item, .. }) = transaction
.get_object_mutation(self.store().store_object_id(), ObjectKey::object(self.object_id))
{
item.clone()
} else {
self.store()
.tree()
.find(&ObjectKey::object(self.object_id))
.await?
.ok_or(anyhow!(FxfsError::NotFound))?
};
if let ObjectValue::Object { ref mut attributes, .. } = item.value {
if let Some(time) = crtime {
attributes.creation_time = time;
}
if let Some(time) = mtime {
attributes.modification_time = time;
}
} else {
bail!(anyhow!(FxfsError::Inconsistent)
.context("update_attributes: Expected object value"));
};
updater(&mut item);
transaction.add(
self.store().store_object_id(),
Mutation::replace_or_insert_object(item.key, item.value),
);
Ok(())
}
pub async fn get_properties(&self) -> Result<ObjectProperties, Error> {
let item = self
.store()
.tree()
.find(&ObjectKey::object(self.object_id))
.await?
.ok_or(anyhow!(FxfsError::NotFound))?;
match item.value {
ObjectValue::Object {
kind: ObjectKind::Directory { sub_dirs },
attributes: ObjectAttributes { creation_time, modification_time },
} => Ok(ObjectProperties {
refs: 1,
allocated_size: 0,
data_attribute_size: 0,
creation_time,
modification_time,
sub_dirs,
}),
_ => {
bail!(anyhow!(FxfsError::Inconsistent)
.context("get_properties: Expected object value"))
}
}
}
/// Returns an iterator that will return directory entries skipping deleted ones. Example
/// usage:
///
/// let layer_set = dir.store().tree().layer_set();
/// let mut merger = layer_set.merger();
/// let mut iter = dir.iter(&mut merger).await?;
///
pub async fn iter<'a, 'b>(
&self,
merger: &'a mut Merger<'b, ObjectKey, ObjectValue>,
) -> Result<DirectoryIterator<'a, 'b>, Error> {
self.iter_from(merger, "").await
}
/// Like "iter", but seeks from a specific filename (inclusive). Example usage:
///
/// let layer_set = dir.store().tree().layer_set();
/// let mut merger = layer_set.merger();
/// let mut iter = dir.iter_from(&mut merger, "foo").await?;
///
pub async fn iter_from<'a, 'b>(
&self,
merger: &'a mut Merger<'b, ObjectKey, ObjectValue>,
from: &str,
) -> Result<DirectoryIterator<'a, 'b>, Error> {
ensure!(!self.is_deleted(), FxfsError::Deleted);
let mut iter =
merger.seek(Bound::Included(&ObjectKey::child(self.object_id, from))).await?;
// Skip deleted entries.
loop {
match iter.get() {
Some(ItemRef {
key: ObjectKey { object_id, .. },
value: ObjectValue::None,
..
}) if *object_id == self.object_id => {}
_ => break,
}
iter.advance().await?;
}
Ok(DirectoryIterator { object_id: self.object_id, iter })
}
}
impl<S: HandleOwner> fmt::Debug for Directory<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Directory")
.field("store_id", &self.store().store_object_id())
.field("object_id", &self.object_id)
.finish()
}
}
pub struct DirectoryIterator<'a, 'b> {
object_id: u64,
iter: MergerIterator<'a, 'b, ObjectKey, ObjectValue>,
}
impl DirectoryIterator<'_, '_> {
pub fn get(&self) -> Option<(&str, u64, &ObjectDescriptor)> {
match self.iter.get() {
Some(ItemRef {
key: ObjectKey { object_id: oid, data: ObjectKeyData::Child { name } },
value: ObjectValue::Child { object_id, object_descriptor },
..
}) if *oid == self.object_id => Some((name, *object_id, object_descriptor)),
_ => None,
}
}
pub async fn advance(&mut self) -> Result<(), Error> {
loop {
self.iter.advance().await?;
// Skip deleted entries.
match self.iter.get() {
Some(ItemRef {
key: ObjectKey { object_id, .. },
value: ObjectValue::None,
..
}) if *object_id == self.object_id => {}
_ => return Ok(()),
}
}
}
}
/// Return type for |replace_child| describing the object which was replaced. The u64 fields are all
/// object_ids.
#[derive(Debug)]
pub enum ReplacedChild {
None,
File(u64),
FileWithRemainingLinks(u64),
Directory(u64),
}
/// Moves src.0/src.1 to dst.0/dst.1.
///
/// If |dst.0| already has a child |dst.1|, it is removed from dst.0. For files, if this was their
/// last reference, the file is moved to the graveyard. For directories, the removed directory will
/// be deleted permanently (and must be empty).
///
/// If |src| is None, this is effectively the same as unlink(dst.0/dst.1).
pub async fn replace_child<'a, S: HandleOwner>(
transaction: &mut Transaction<'a>,
src: Option<(&'a Directory<S>, &str)>,
dst: (&'a Directory<S>, &str),
) -> Result<ReplacedChild, Error> {
let deleted_id_and_descriptor = dst.0.lookup(dst.1).await?;
let mut sub_dirs_delta: i64 = 0;
let result = match deleted_id_and_descriptor {
Some((old_id, ObjectDescriptor::File)) => {
let was_last_ref = dst.0.store().adjust_refs(transaction, old_id, -1).await?;
if was_last_ref {
ReplacedChild::File(old_id)
} else {
ReplacedChild::FileWithRemainingLinks(old_id)
}
}
Some((old_id, ObjectDescriptor::Directory)) => {
let dir = Directory::open(&dst.0.owner(), old_id).await?;
if dir.has_children().await? {
bail!(FxfsError::NotEmpty);
}
remove(transaction, &dst.0.store(), old_id);
sub_dirs_delta -= 1;
ReplacedChild::Directory(old_id)
}
Some((_, ObjectDescriptor::Volume)) => {
bail!(anyhow!(FxfsError::Inconsistent).context("Unexpected volume child"))
}
None => {
if src.is_none() {
// Neither src nor dst exist
bail!(FxfsError::NotFound);
}
ReplacedChild::None
}
};
let store_id = dst.0.store().store_object_id();
let now = Timestamp::now();
let new_value = if let Some((src_dir, src_name)) = src {
assert_eq!(store_id, src_dir.store().store_object_id());
transaction.add(
store_id,
Mutation::replace_or_insert_object(
ObjectKey::child(src_dir.object_id, src_name),
ObjectValue::None,
),
);
let (id, descriptor) = src_dir.lookup(src_name).await?.ok_or(FxfsError::NotFound)?;
if src_dir.object_id() != dst.0.object_id() {
src_dir
.update_attributes(transaction, None, Some(now.clone()), |item| {
if let ObjectDescriptor::Directory = descriptor {
if let ObjectItem {
value: ObjectValue::Object {
kind: ObjectKind::Directory { sub_dirs },
..
},
..
} = item
{
*sub_dirs = sub_dirs.saturating_sub(1);
sub_dirs_delta += 1;
} else {
panic!("Expected directory");
}
}
})
.await?;
}
ObjectValue::child(id, descriptor)
} else {
ObjectValue::None
};
transaction.add(
store_id,
Mutation::replace_or_insert_object(ObjectKey::child(dst.0.object_id, dst.1), new_value),
);
dst.0
.update_attributes(transaction, None, Some(now), |item| {
if sub_dirs_delta != 0 {
if let ObjectItem {
value: ObjectValue::Object { kind: ObjectKind::Directory { sub_dirs }, .. },
..
} = item
{
if sub_dirs_delta < 0 {
*sub_dirs = sub_dirs.saturating_sub((-sub_dirs_delta) as u64);
} else {
*sub_dirs = sub_dirs.saturating_add(sub_dirs_delta as u64);
}
} else {
panic!("Expected directory");
}
}
})
.await?;
Ok(result)
}
fn remove(transaction: &mut Transaction<'_>, store: &ObjectStore, object_id: u64) {
transaction.add(
store.store_object_id(),
Mutation::merge_object(ObjectKey::object(object_id), ObjectValue::None),
);
}
#[cfg(test)]
mod tests {
use {
super::remove,
crate::{
errors::FxfsError,
filesystem::{Filesystem, FxFilesystem, JournalingObject, SyncOptions},
object_handle::{ObjectHandle, ReadObjectHandle, WriteObjectHandle},
object_store::{
directory::{replace_child, Directory, ReplacedChild},
object_record::Timestamp,
transaction::{Options, TransactionHandler},
HandleOptions, ObjectDescriptor, ObjectStore,
},
},
assert_matches::assert_matches,
fuchsia_async as fasync,
storage_device::{fake_device::FakeDevice, DeviceHolder},
};
const TEST_DEVICE_BLOCK_SIZE: u32 = 512;
#[fasync::run_singlethreaded(test)]
async fn test_create_directory() {
let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
let object_id = {
let mut transaction = fs
.clone()
.new_transaction(&[], Options::default())
.await
.expect("new_transaction failed");
let dir =
Directory::create(&mut transaction, &fs.root_store()).await.expect("create failed");
let child_dir = dir
.create_child_dir(&mut transaction, "foo")
.await
.expect("create_child_dir failed");
let _child_dir_file = child_dir
.create_child_file(&mut transaction, "bar")
.await
.expect("create_child_file failed");
let _child_file = dir
.create_child_file(&mut transaction, "baz")
.await
.expect("create_child_file failed");
dir.add_child_volume(&mut transaction, "corge", 100)
.await
.expect("add_child_volume failed");
transaction.commit().await.expect("commit failed");
fs.sync(SyncOptions::default()).await.expect("sync failed");
dir.object_id()
};
fs.close().await.expect("Close failed");
let device = fs.take_device().await;
device.reopen();
let fs = FxFilesystem::open(device).await.expect("open failed");
{
let dir = Directory::open(&fs.root_store(), object_id).await.expect("open failed");
let (object_id, object_descriptor) =
dir.lookup("foo").await.expect("lookup failed").expect("not found");
assert_eq!(object_descriptor, ObjectDescriptor::Directory);
let child_dir =
Directory::open(&fs.root_store(), object_id).await.expect("open failed");
let (object_id, object_descriptor) =
child_dir.lookup("bar").await.expect("lookup failed").expect("not found");
assert_eq!(object_descriptor, ObjectDescriptor::File);
let _child_dir_file = ObjectStore::open_object(
&fs.root_store(),
object_id,
HandleOptions::default(),
None,
)
.await
.expect("open object failed");
let (object_id, object_descriptor) =
dir.lookup("baz").await.expect("lookup failed").expect("not found");
assert_eq!(object_descriptor, ObjectDescriptor::File);
let _child_file = ObjectStore::open_object(
&fs.root_store(),
object_id,
HandleOptions::default(),
None,
)
.await
.expect("open object failed");
let (object_id, object_descriptor) =
dir.lookup("corge").await.expect("lookup failed").expect("not found");
assert_eq!(object_id, 100);
if let ObjectDescriptor::Volume = object_descriptor {
} else {
panic!("wrong ObjectDescriptor");
}
assert_eq!(dir.lookup("qux").await.expect("lookup failed"), None);
}
fs.close().await.expect("Close failed");
}
#[fasync::run_singlethreaded(test)]
async fn test_delete_child() {
let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
let mut transaction = fs
.clone()
.new_transaction(&[], Options::default())
.await
.expect("new_transaction failed");
let dir =
Directory::create(&mut transaction, &fs.root_store()).await.expect("create failed");
dir.create_child_file(&mut transaction, "foo").await.expect("create_child_file failed");
transaction.commit().await.expect("commit failed");
let mut transaction = fs
.clone()
.new_transaction(&[], Options::default())
.await
.expect("new_transaction failed");
assert_matches!(
replace_child(&mut transaction, None, (&dir, "foo"))
.await
.expect("replace_child failed"),
ReplacedChild::File(..)
);
transaction.commit().await.expect("commit failed");
assert_eq!(dir.lookup("foo").await.expect("lookup failed"), None);
fs.close().await.expect("Close failed");
}
#[fasync::run_singlethreaded(test)]
async fn test_delete_child_with_children_fails() {
let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
let mut transaction = fs
.clone()
.new_transaction(&[], Options::default())
.await
.expect("new_transaction failed");
let dir =
Directory::create(&mut transaction, &fs.root_store()).await.expect("create failed");
let child =
dir.create_child_dir(&mut transaction, "foo").await.expect("create_child_dir failed");
child.create_child_file(&mut transaction, "bar").await.expect("create_child_file failed");
transaction.commit().await.expect("commit failed");
let mut transaction = fs
.clone()
.new_transaction(&[], Options::default())
.await
.expect("new_transaction failed");
assert_eq!(
replace_child(&mut transaction, None, (&dir, "foo"))
.await
.expect_err("replace_child succeeded")
.downcast::<FxfsError>()
.expect("wrong error"),
FxfsError::NotEmpty
);
let mut transaction = fs
.clone()
.new_transaction(&[], Options::default())
.await
.expect("new_transaction failed");
assert_matches!(
replace_child(&mut transaction, None, (&child, "bar"))
.await
.expect("replace_child failed"),
ReplacedChild::File(..)
);
transaction.commit().await.expect("commit failed");
let mut transaction = fs
.clone()
.new_transaction(&[], Options::default())
.await
.expect("new_transaction failed");
assert_matches!(
replace_child(&mut transaction, None, (&dir, "foo"))
.await
.expect("replace_child failed"),
ReplacedChild::Directory(..)
);
transaction.commit().await.expect("commit failed");
assert_eq!(dir.lookup("foo").await.expect("lookup failed"), None);
fs.close().await.expect("Close failed");
}
#[fasync::run_singlethreaded(test)]
async fn test_delete_and_reinsert_child() {
let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
let mut transaction = fs
.clone()
.new_transaction(&[], Options::default())
.await
.expect("new_transaction failed");
let dir =
Directory::create(&mut transaction, &fs.root_store()).await.expect("create failed");
dir.create_child_file(&mut transaction, "foo").await.expect("create_child_file failed");
transaction.commit().await.expect("commit failed");
let mut transaction = fs
.clone()
.new_transaction(&[], Options::default())
.await
.expect("new_transaction failed");
assert_matches!(
replace_child(&mut transaction, None, (&dir, "foo"))
.await
.expect("replace_child failed"),
ReplacedChild::File(..)
);
transaction.commit().await.expect("commit failed");
let mut transaction = fs
.clone()
.new_transaction(&[], Options::default())
.await
.expect("new_transaction failed");
dir.create_child_file(&mut transaction, "foo").await.expect("create_child_file failed");
transaction.commit().await.expect("commit failed");
dir.lookup("foo").await.expect("lookup failed");
fs.close().await.expect("Close failed");
}
#[fasync::run_singlethreaded(test)]
async fn test_delete_child_persists() {
let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
let object_id = {
let mut transaction = fs
.clone()
.new_transaction(&[], Options::default())
.await
.expect("new_transaction failed");
let dir =
Directory::create(&mut transaction, &fs.root_store()).await.expect("create failed");
dir.create_child_file(&mut transaction, "foo").await.expect("create_child_file failed");
transaction.commit().await.expect("commit failed");
dir.lookup("foo").await.expect("lookup failed");
let mut transaction = fs
.clone()
.new_transaction(&[], Options::default())
.await
.expect("new_transaction failed");
assert_matches!(
replace_child(&mut transaction, None, (&dir, "foo"))
.await
.expect("replace_child failed"),
ReplacedChild::File(..)
);
transaction.commit().await.expect("commit failed");
fs.sync(SyncOptions::default()).await.expect("sync failed");
dir.object_id()
};
fs.close().await.expect("Close failed");
let device = fs.take_device().await;
device.reopen();
let fs = FxFilesystem::open(device).await.expect("open failed");
let dir = Directory::open(&fs.root_store(), object_id).await.expect("open failed");
assert_eq!(dir.lookup("foo").await.expect("lookup failed"), None);
fs.close().await.expect("Close failed");
}
#[fasync::run_singlethreaded(test)]
async fn test_replace_child() {
let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
let mut transaction = fs
.clone()
.new_transaction(&[], Options::default())
.await
.expect("new_transaction failed");
let dir =
Directory::create(&mut transaction, &fs.root_store()).await.expect("create failed");
let child_dir1 =
dir.create_child_dir(&mut transaction, "dir1").await.expect("create_child_dir failed");
let child_dir2 =
dir.create_child_dir(&mut transaction, "dir2").await.expect("create_child_dir failed");
child_dir1
.create_child_file(&mut transaction, "foo")
.await
.expect("create_child_file failed");
transaction.commit().await.expect("commit failed");
let mut transaction = fs
.clone()
.new_transaction(&[], Options::default())
.await
.expect("new_transaction failed");
assert_matches!(
replace_child(&mut transaction, Some((&child_dir1, "foo")), (&child_dir2, "bar"))
.await
.expect("replace_child failed"),
ReplacedChild::None
);
transaction.commit().await.expect("commit failed");
assert_eq!(child_dir1.lookup("foo").await.expect("lookup failed"), None);
child_dir2.lookup("bar").await.expect("lookup failed");
fs.close().await.expect("Close failed");
}
#[fasync::run_singlethreaded(test)]
async fn test_replace_child_overwrites_dst() {
let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
let mut transaction = fs
.clone()
.new_transaction(&[], Options::default())
.await
.expect("new_transaction failed");
let dir =
Directory::create(&mut transaction, &fs.root_store()).await.expect("create failed");
let child_dir1 =
dir.create_child_dir(&mut transaction, "dir1").await.expect("create_child_dir failed");
let child_dir2 =
dir.create_child_dir(&mut transaction, "dir2").await.expect("create_child_dir failed");
let foo = child_dir1
.create_child_file(&mut transaction, "foo")
.await
.expect("create_child_file failed");
let bar = child_dir2
.create_child_file(&mut transaction, "bar")
.await
.expect("create_child_file failed");
transaction.commit().await.expect("commit failed");
{
let mut buf = foo.allocate_buffer(TEST_DEVICE_BLOCK_SIZE as usize);
buf.as_mut_slice().fill(0xaa);
foo.write_or_append(Some(0), buf.as_ref()).await.expect("write failed");
buf.as_mut_slice().fill(0xbb);
bar.write_or_append(Some(0), buf.as_ref()).await.expect("write failed");
}
std::mem::drop(bar);
std::mem::drop(foo);
let mut transaction = fs
.clone()
.new_transaction(&[], Options::default())
.await
.expect("new_transaction failed");
assert_matches!(
replace_child(&mut transaction, Some((&child_dir1, "foo")), (&child_dir2, "bar"))
.await
.expect("replace_child failed"),
ReplacedChild::File(..)
);
transaction.commit().await.expect("commit failed");
assert_eq!(child_dir1.lookup("foo").await.expect("lookup failed"), None);
// Check the contents to ensure that the file was replaced.
let (oid, object_descriptor) =
child_dir2.lookup("bar").await.expect("lookup failed").expect("not found");
assert_eq!(object_descriptor, ObjectDescriptor::File);
let bar = ObjectStore::open_object(&child_dir2.owner, oid, HandleOptions::default(), None)
.await
.expect("Open failed");
let mut buf = bar.allocate_buffer(TEST_DEVICE_BLOCK_SIZE as usize);
bar.read(0, buf.as_mut()).await.expect("read failed");
assert_eq!(buf.as_slice(), vec![0xaa; TEST_DEVICE_BLOCK_SIZE as usize]);
fs.close().await.expect("Close failed");
}
#[fasync::run_singlethreaded(test)]
async fn test_replace_child_fails_if_would_overwrite_nonempty_dir() {
let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
let mut transaction = fs
.clone()
.new_transaction(&[], Options::default())
.await
.expect("new_transaction failed");
let dir =
Directory::create(&mut transaction, &fs.root_store()).await.expect("create failed");
let child_dir1 =
dir.create_child_dir(&mut transaction, "dir1").await.expect("create_child_dir failed");
let child_dir2 =
dir.create_child_dir(&mut transaction, "dir2").await.expect("create_child_dir failed");
child_dir1
.create_child_file(&mut transaction, "foo")
.await
.expect("create_child_file failed");
let nested_child = child_dir2
.create_child_dir(&mut transaction, "bar")
.await
.expect("create_child_file failed");
nested_child
.create_child_file(&mut transaction, "baz")
.await
.expect("create_child_file failed");
transaction.commit().await.expect("commit failed");
let mut transaction = fs
.clone()
.new_transaction(&[], Options::default())
.await
.expect("new_transaction failed");
assert_eq!(
replace_child(&mut transaction, Some((&child_dir1, "foo")), (&child_dir2, "bar"))
.await
.expect_err("replace_child succeeded")
.downcast::<FxfsError>()
.expect("wrong error"),
FxfsError::NotEmpty
);
fs.close().await.expect("Close failed");
}
#[fasync::run_singlethreaded(test)]
async fn test_replace_child_within_dir() {
let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
let mut transaction = fs
.clone()
.new_transaction(&[], Options::default())
.await
.expect("new_transaction failed");
let dir =
Directory::create(&mut transaction, &fs.root_store()).await.expect("create failed");
dir.create_child_file(&mut transaction, "foo").await.expect("create_child_file failed");
transaction.commit().await.expect("commit failed");
let mut transaction = fs
.clone()
.new_transaction(&[], Options::default())
.await
.expect("new_transaction failed");
assert_matches!(
replace_child(&mut transaction, Some((&dir, "foo")), (&dir, "bar"))
.await
.expect("replace_child failed"),
ReplacedChild::None
);
transaction.commit().await.expect("commit failed");
assert_eq!(dir.lookup("foo").await.expect("lookup failed"), None);
dir.lookup("bar").await.expect("lookup new name failed");
fs.close().await.expect("Close failed");
}
#[fasync::run_singlethreaded(test)]
async fn test_iterate() {
let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
let mut transaction = fs
.clone()
.new_transaction(&[], Options::default())
.await
.expect("new_transaction failed");
let dir =
Directory::create(&mut transaction, &fs.root_store()).await.expect("create failed");
let _cat =
dir.create_child_file(&mut transaction, "cat").await.expect("create_child_file failed");
let _ball = dir
.create_child_file(&mut transaction, "ball")
.await
.expect("create_child_file failed");
let _apple = dir
.create_child_file(&mut transaction, "apple")
.await
.expect("create_child_file failed");
let _dog =
dir.create_child_file(&mut transaction, "dog").await.expect("create_child_file failed");
transaction.commit().await.expect("commit failed");
let mut transaction = fs
.clone()
.new_transaction(&[], Options::default())
.await
.expect("new_transaction failed");
replace_child(&mut transaction, None, (&dir, "apple"))
.await
.expect("rereplace_child failed");
transaction.commit().await.expect("commit failed");
let layer_set = dir.store().tree().layer_set();
let mut merger = layer_set.merger();
let mut iter = dir.iter(&mut merger).await.expect("iter failed");
let mut entries = Vec::new();
while let Some((name, _, _)) = iter.get() {
entries.push(name.to_string());
iter.advance().await.expect("advance failed");
}
assert_eq!(&entries, &["ball", "cat", "dog"]);
fs.close().await.expect("Close failed");
}
#[fasync::run_singlethreaded(test)]
async fn test_sub_dir_count() {
let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
let mut transaction = fs
.clone()
.new_transaction(&[], Options::default())
.await
.expect("new_transaction failed");
let dir =
Directory::create(&mut transaction, &fs.root_store()).await.expect("create failed");
let child_dir =
dir.create_child_dir(&mut transaction, "foo").await.expect("create_child_dir failed");
transaction.commit().await.expect("commit failed");
assert_eq!(dir.get_properties().await.expect("get_properties failed").sub_dirs, 1);
// Moving within the same directory should not change the sub_dir count.
let mut transaction = fs
.clone()
.new_transaction(&[], Options::default())
.await
.expect("new_transaction failed");
replace_child(&mut transaction, Some((&dir, "foo")), (&dir, "bar"))
.await
.expect("replace_child failed");
transaction.commit().await.expect("commit failed");
assert_eq!(dir.get_properties().await.expect("get_properties failed").sub_dirs, 1);
assert_eq!(child_dir.get_properties().await.expect("get_properties failed").sub_dirs, 0);
// Moving between two different directories should update source and destination.
let mut transaction = fs
.clone()
.new_transaction(&[], Options::default())
.await
.expect("new_transaction failed");
let _second_child = child_dir
.create_child_dir(&mut transaction, "baz")
.await
.expect("create_child_dir failed");
transaction.commit().await.expect("commit failed");
assert_eq!(child_dir.get_properties().await.expect("get_properties failed").sub_dirs, 1);
let mut transaction = fs
.clone()
.new_transaction(&[], Options::default())
.await
.expect("new_transaction failed");
replace_child(&mut transaction, Some((&child_dir, "baz")), (&dir, "foo"))
.await
.expect("replace_child failed");
transaction.commit().await.expect("commit failed");
assert_eq!(dir.get_properties().await.expect("get_properties failed").sub_dirs, 2);
assert_eq!(child_dir.get_properties().await.expect("get_properties failed").sub_dirs, 0);
// Moving over a directory.
let mut transaction = fs
.clone()
.new_transaction(&[], Options::default())
.await
.expect("new_transaction failed");
replace_child(&mut transaction, Some((&dir, "bar")), (&dir, "foo"))
.await
.expect("replace_child failed");
transaction.commit().await.expect("commit failed");
assert_eq!(dir.get_properties().await.expect("get_properties failed").sub_dirs, 1);
// Unlinking a directory.
let mut transaction = fs
.clone()
.new_transaction(&[], Options::default())
.await
.expect("new_transaction failed");
replace_child(&mut transaction, None, (&dir, "foo")).await.expect("replace_child failed");
transaction.commit().await.expect("commit failed");
assert_eq!(dir.get_properties().await.expect("get_properties failed").sub_dirs, 0);
fs.close().await.expect("Close failed");
}
#[fasync::run_singlethreaded(test)]
async fn test_deleted_dir() {
let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
let mut transaction = fs
.clone()
.new_transaction(&[], Options::default())
.await
.expect("new_transaction failed");
let dir =
Directory::create(&mut transaction, &fs.root_store()).await.expect("create failed");
dir.create_child_dir(&mut transaction, "foo").await.expect("create_child_dir failed");
dir.create_child_dir(&mut transaction, "bar").await.expect("create_child_dir failed");
transaction.commit().await.expect("commit failed");
// Flush the tree so that we end up with records in different layers.
dir.store().flush().await.expect("flush failed");
// Unlink the child directory.
let mut transaction = fs
.clone()
.new_transaction(&[], Options::default())
.await
.expect("new_transaction failed");
replace_child(&mut transaction, None, (&dir, "foo")).await.expect("replace_child failed");
transaction.commit().await.expect("commit failed");
// Finding the child should fail now.
assert_eq!(dir.lookup("foo").await.expect("lookup failed"), None);
// But finding "bar" should succeed.
assert!(dir.lookup("bar").await.expect("lookup failed").is_some());
// Remove dir now.
let mut transaction = fs
.clone()
.new_transaction(&[], Options::default())
.await
.expect("new_transaction failed");
remove(&mut transaction, dir.store(), dir.object_id());
transaction.commit().await.expect("commit failed");
// If we mark dir as deleted, any further operations should fail.
dir.set_deleted();
assert_eq!(dir.lookup("foo").await.expect("lookup failed"), None);
assert_eq!(dir.lookup("bar").await.expect("lookup failed"), None);
assert!(!dir.has_children().await.expect("has_children failed"));
let mut transaction = fs
.clone()
.new_transaction(&[], Options::default())
.await
.expect("new_transaction failed");
let assert_access_denied = |result| {
if let Err(e) = result {
assert!(FxfsError::Deleted.matches(&e));
} else {
panic!();
}
};
assert_access_denied(dir.create_child_dir(&mut transaction, "baz").await.map(|_| {}));
assert_access_denied(dir.create_child_file(&mut transaction, "baz").await.map(|_| {}));
assert_access_denied(dir.add_child_volume(&mut transaction, "baz", 1).await);
assert_access_denied(
dir.insert_child(&mut transaction, "baz", 1, ObjectDescriptor::File).await,
);
assert_access_denied(
dir.update_attributes(&mut transaction, Some(Timestamp::zero()), None, |_| {}).await,
);
let layer_set = dir.store().tree().layer_set();
let mut merger = layer_set.merger();
assert_access_denied(dir.iter(&mut merger).await.map(|_| {}));
}
}