blob: b5075b45584d5b43764bcbd85f2347d8f467d9ae [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, INVALID_OBJECT_ID},
object_store::{
object_record::{
ChildValue, ObjectAttributes, ObjectDescriptor, ObjectItem, ObjectKey,
ObjectKeyData, ObjectKind, ObjectValue, PosixAttributes, Timestamp,
},
transaction::{LockKey, LockKeys, Mutation, Options, Transaction},
DataObjectHandle, HandleOptions, HandleOwner, ObjectStore, SetExtendedAttributeMode,
StoreObjectHandle,
},
},
anyhow::{anyhow, bail, ensure, Error},
fidl_fuchsia_io as fio,
std::{
fmt,
ops::Bound,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
},
};
/// This contains the transaction with the appropriate locks to replace src with dst, and also the
/// ID and type of the src and dst.
pub struct ReplaceContext<'a> {
pub transaction: Transaction<'a>,
pub src_id_and_descriptor: Option<(u64, ObjectDescriptor)>,
pub dst_id_and_descriptor: Option<(u64, ObjectDescriptor)>,
}
/// A directory stores name to child object mappings.
pub struct Directory<S: HandleOwner> {
handle: StoreObjectHandle<S>,
is_deleted: AtomicBool,
}
#[fxfs_trace::trace]
impl<S: HandleOwner> Directory<S> {
fn new(owner: Arc<S>, object_id: u64) -> Self {
Directory {
handle: StoreObjectHandle::new(
owner,
object_id,
/* permanent_keys: */ false,
HandleOptions::default(),
/* trace: */ false,
),
is_deleted: AtomicBool::new(false),
}
}
pub fn object_id(&self) -> u64 {
self.handle.object_id()
}
pub fn owner(&self) -> &Arc<S> {
self.handle.owner()
}
pub fn store(&self) -> &ObjectStore {
self.handle.store()
}
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>,
create_attributes: Option<&fio::MutableNodeAttributes>,
) -> Result<Directory<S>, Error> {
let store = owner.as_ref().as_ref();
let object_id = store.get_next_object_id(transaction).await?;
let now = Timestamp::now();
let creation_time = create_attributes
.and_then(|a| a.creation_time)
.map(Timestamp::from_nanos)
.unwrap_or_else(|| now.clone());
let modification_time = create_attributes
.and_then(|a| a.modification_time)
.map(Timestamp::from_nanos)
.unwrap_or_else(|| now.clone());
let access_time = create_attributes
.and_then(|a| a.access_time)
.map(Timestamp::from_nanos)
.unwrap_or_else(|| now.clone());
let change_time = now;
let posix_attributes = create_attributes.and_then(|a| {
(a.mode.is_some() || a.uid.is_some() || a.gid.is_some() || a.rdev.is_some()).then_some(
PosixAttributes {
mode: a.mode.unwrap_or_default(),
uid: a.uid.unwrap_or_default(),
gid: a.gid.unwrap_or_default(),
rdev: a.rdev.unwrap_or_default(),
},
)
});
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,
modification_time,
project_id: 0,
posix_attributes,
allocated_size: 0,
access_time,
change_time,
},
},
),
);
Ok(Directory::new(owner.clone(), object_id))
}
#[trace]
pub async fn open(owner: &Arc<S>, object_id: u64) -> Result<Directory<S>, Error> {
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),
}
}
/// Opens a directory. The caller is responsible for ensuring that the object exists and is a
/// directory.
pub fn open_unchecked(owner: Arc<S>, object_id: u64) -> Self {
Self::new(owner, object_id)
}
/// Acquires the transaction with the appropriate locks to replace src.0/src.1 with |dst|.
/// src can be None in the case of unlinking |dst| from |self|.
/// Returns the transaction, as well as the ID and type of the child and the src. If the child
/// doesn't exist, then a transaction is returned with a lock only on the parent and None for
/// the target info so that the transaction can be executed with the confidence that the target
/// doesn't exist. If the src doesn't exist (in the case of unlinking), None is return for the
/// source info.
///
/// We need to lock |self|, but also the child if it exists. When it is a directory the lock
/// prevents entries being added at the same time. When it is a file needs to be able to
/// decrement the reference count.
/// If src exists, we also need to lock |src.0| and |src.1|. This is to update their timestamps.
pub async fn acquire_context_for_replace(
&self,
src: Option<(&Directory<S>, &str)>,
dst: &str,
borrow_metadata_space: bool,
) -> Result<ReplaceContext<'_>, Error> {
// Since we don't know the child object ID until we've looked up the child, we need to loop
// until we have acquired a lock on a child whose ID is the same as it was in the last
// iteration. This also applies for src object ID if |src| is passed in.
//
// Note that the returned transaction may lock more objects than is necessary (for example,
// if the child "foo" was first a directory, then was renamed to "bar" and a file "foo" was
// created, we might acquire a lock on both the parent and "bar").
//
// We can look into not having this loop by adding support to try to add locks in the
// transaction. If it fails, we can drop all the locks and start a new transaction.
let store = self.store();
let mut child_object_id = INVALID_OBJECT_ID;
let mut src_object_id = src.map(|_| INVALID_OBJECT_ID);
let mut lock_keys = LockKeys::with_capacity(4);
lock_keys.push(LockKey::object(store.store_object_id(), self.object_id()));
loop {
lock_keys.truncate(1);
if let Some(src) = src {
lock_keys.push(LockKey::object(store.store_object_id(), src.0.object_id()));
if let Some(src_object_id) = src_object_id {
if src_object_id != INVALID_OBJECT_ID {
lock_keys.push(LockKey::object(store.store_object_id(), src_object_id));
}
}
}
if child_object_id != INVALID_OBJECT_ID {
lock_keys.push(LockKey::object(store.store_object_id(), child_object_id));
};
let fs = store.filesystem().clone();
let transaction = fs
.new_transaction(
lock_keys.clone(),
Options { borrow_metadata_space, ..Default::default() },
)
.await?;
let mut have_required_locks = true;
let mut src_id_and_descriptor = None;
if let Some((src_dir, src_name)) = src {
match src_dir.lookup(src_name).await? {
Some((object_id, object_descriptor)) => match object_descriptor {
ObjectDescriptor::File
| ObjectDescriptor::Directory
| ObjectDescriptor::Symlink => {
if src_object_id != Some(object_id) {
have_required_locks = false;
src_object_id = Some(object_id);
}
src_id_and_descriptor = Some((object_id, object_descriptor));
}
_ => bail!(FxfsError::Inconsistent),
},
None => {
// Can't find src.0/src.1
bail!(FxfsError::NotFound)
}
}
};
let dst_id_and_descriptor = match self.lookup(dst).await? {
Some((object_id, object_descriptor)) => match object_descriptor {
ObjectDescriptor::File
| ObjectDescriptor::Directory
| ObjectDescriptor::Symlink => {
if child_object_id != object_id {
have_required_locks = false;
child_object_id = object_id
}
Some((object_id, object_descriptor))
}
_ => bail!(FxfsError::Inconsistent),
},
None => {
if child_object_id != INVALID_OBJECT_ID {
have_required_locks = false;
child_object_id = INVALID_OBJECT_ID;
}
None
}
};
if have_required_locks {
return Ok(ReplaceContext {
transaction,
src_id_and_descriptor,
dst_id_and_descriptor,
});
}
}
}
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.
#[trace]
pub async fn lookup(&self, name: &str) -> Result<Option<(u64, ObjectDescriptor)>, Error> {
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(ChildValue { 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(
&self,
transaction: &mut Transaction<'_>,
name: &str,
create_attributes: Option<&fio::MutableNodeAttributes>,
) -> Result<Directory<S>, Error> {
ensure!(!self.is_deleted(), FxfsError::Deleted);
let handle = Directory::create(transaction, self.owner(), create_attributes).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),
),
);
let now = Timestamp::now();
self.update_attributes(
transaction,
Some(&fio::MutableNodeAttributes {
modification_time: Some(now.as_nanos()),
..Default::default()
}),
1,
Some(now),
)
.await?;
self.copy_project_id_to_object_in_txn(transaction, handle.object_id())?;
Ok(handle)
}
pub async fn add_child_file<'a>(
&self,
transaction: &mut Transaction<'a>,
name: &str,
handle: &DataObjectHandle<S>,
) -> 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(handle.object_id(), ObjectDescriptor::File),
),
);
let now = Timestamp::now();
self.update_attributes(
transaction,
Some(&fio::MutableNodeAttributes {
modification_time: Some(now.as_nanos()),
..Default::default()
}),
0,
Some(now),
)
.await
}
// This applies the project id of this directory (if nonzero) to an object. The method assumes
// both this and child objects are already present in the mutations of the provided
// transactions and that the child is of of zero size. This is meant for use inside
// `create_child_file()` and `create_child_dir()` only, where such assumptions are safe.
fn copy_project_id_to_object_in_txn<'a>(
&self,
transaction: &mut Transaction<'a>,
object_id: u64,
) -> Result<(), Error> {
let store_id = self.store().store_object_id();
// This mutation must already be in here as we've just modified the mtime.
let ObjectValue::Object { attributes: ObjectAttributes { project_id, .. }, .. } =
transaction
.get_object_mutation(store_id, ObjectKey::object(self.object_id()))
.unwrap()
.item
.value
else {
return Err(anyhow!(FxfsError::Inconsistent));
};
if project_id > 0 {
// This mutation must be present as well since we've just created the object. So this
// replaces it.
let mut mutation = transaction
.get_object_mutation(store_id, ObjectKey::object(object_id))
.unwrap()
.clone();
if let ObjectValue::Object {
attributes: ObjectAttributes { project_id: child_project_id, .. },
..
} = &mut mutation.item.value
{
*child_project_id = project_id;
} else {
return Err(anyhow!(FxfsError::Inconsistent));
}
transaction.add(store_id, Mutation::ObjectStore(mutation));
transaction.add(
store_id,
Mutation::merge_object(
ObjectKey::project_usage(self.store().root_directory_object_id(), project_id),
ObjectValue::BytesAndNodes { bytes: 0, nodes: 1 },
),
);
}
Ok(())
}
pub async fn create_child_file<'a>(
&self,
transaction: &mut Transaction<'a>,
name: &str,
create_attributes: Option<&fio::MutableNodeAttributes>,
) -> Result<DataObjectHandle<S>, Error> {
self.create_child_file_with_options(
transaction,
name,
create_attributes,
HandleOptions::default(),
)
.await
}
pub async fn create_child_file_with_options<'a>(
&self,
transaction: &mut Transaction<'a>,
name: &str,
create_attributes: Option<&fio::MutableNodeAttributes>,
options: HandleOptions,
) -> Result<DataObjectHandle<S>, Error> {
ensure!(!self.is_deleted(), FxfsError::Deleted);
let handle =
ObjectStore::create_object(self.owner(), transaction, options, None, create_attributes)
.await?;
self.add_child_file(transaction, name, &handle).await?;
self.copy_project_id_to_object_in_txn(transaction, handle.object_id())?;
Ok(handle)
}
pub async fn create_symlink(
&self,
transaction: &mut Transaction<'_>,
link: &[u8],
name: &str,
) -> Result<u64, Error> {
ensure!(!self.is_deleted(), FxfsError::Deleted);
// Limit the length of link that might be too big to put in the tree.
// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/limits.h.html.
// See _POSIX_SYMLINK_MAX.
ensure!(link.len() <= 256, FxfsError::BadPath);
let symlink_id = self.store().get_next_object_id(transaction).await?;
transaction.add(
self.store().store_object_id(),
Mutation::insert_object(
ObjectKey::object(symlink_id),
ObjectValue::symlink(link, Timestamp::now(), Timestamp::now(), 0),
),
);
transaction.add(
self.store().store_object_id(),
Mutation::replace_or_insert_object(
ObjectKey::child(self.object_id(), name),
ObjectValue::child(symlink_id, ObjectDescriptor::Symlink),
),
);
self.update_attributes(
transaction,
Some(&fio::MutableNodeAttributes {
modification_time: Some(Timestamp::now().as_nanos()),
..Default::default()
}),
0,
None,
)
.await?;
Ok(symlink_id)
}
pub async fn add_child_volume(
&self,
transaction: &mut Transaction<'_>,
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),
),
);
let now = Timestamp::now();
self.update_attributes(
transaction,
Some(&fio::MutableNodeAttributes {
modification_time: Some(now.as_nanos()),
..Default::default()
}),
0,
Some(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);
let sub_dirs_delta = if descriptor == ObjectDescriptor::Directory { 1 } else { 0 };
transaction.add(
self.store().store_object_id(),
Mutation::replace_or_insert_object(
ObjectKey::child(self.object_id(), name),
ObjectValue::child(object_id, descriptor),
),
);
let now = Timestamp::now();
self.update_attributes(
transaction,
Some(&fio::MutableNodeAttributes {
modification_time: Some(now.as_nanos()),
..Default::default()
}),
sub_dirs_delta,
Some(now),
)
.await
}
/// Updates attributes for the directory.
pub async fn update_attributes<'a>(
&self,
transaction: &mut Transaction<'a>,
node_attributes: Option<&fio::MutableNodeAttributes>,
sub_dirs_delta: i64,
change_time: Option<Timestamp>,
) -> Result<(), Error> {
ensure!(!self.is_deleted(), FxfsError::Deleted);
if sub_dirs_delta != 0 {
let mut mutation =
self.store().txn_get_object_mutation(&transaction, self.object_id()).await?;
if let ObjectValue::Object { kind: ObjectKind::Directory { sub_dirs }, .. } =
&mut mutation.item.value
{
*sub_dirs = sub_dirs.saturating_add_signed(sub_dirs_delta);
} else {
bail!(anyhow!(FxfsError::Inconsistent)
.context("Directory.update_attributes: expected directory object"));
};
transaction.add(self.store().store_object_id(), Mutation::ObjectStore(mutation));
}
// Delegate to the StoreObjectHandle update_attributes for the rest of the updates.
if node_attributes.is_some() || change_time.is_some() {
self.handle.update_attributes(transaction, node_attributes, change_time).await?;
}
Ok(())
}
pub async fn get_properties(&self) -> Result<ObjectProperties, Error> {
if self.is_deleted() {
return Ok(ObjectProperties {
refs: 0,
allocated_size: 0,
data_attribute_size: 0,
creation_time: Timestamp::zero(),
modification_time: Timestamp::zero(),
access_time: Timestamp::zero(),
change_time: Timestamp::zero(),
sub_dirs: 0,
posix_attributes: None,
});
}
let item = self
.store()
.tree()
.find(&ObjectKey::object(self.object_id()))
.await?
.ok_or(FxfsError::NotFound)?;
match item.value {
ObjectValue::Object {
kind: ObjectKind::Directory { sub_dirs },
attributes:
ObjectAttributes {
creation_time,
modification_time,
posix_attributes,
access_time,
change_time,
..
},
} => Ok(ObjectProperties {
refs: 1,
allocated_size: 0,
data_attribute_size: 0,
creation_time,
modification_time,
access_time,
change_time,
sub_dirs,
posix_attributes,
}),
_ => {
bail!(anyhow!(FxfsError::Inconsistent)
.context("get_properties: Expected object value"))
}
}
}
pub async fn list_extended_attributes(&self) -> Result<Vec<Vec<u8>>, Error> {
ensure!(!self.is_deleted(), FxfsError::Deleted);
self.handle.list_extended_attributes().await
}
pub async fn get_extended_attribute(&self, name: Vec<u8>) -> Result<Vec<u8>, Error> {
ensure!(!self.is_deleted(), FxfsError::Deleted);
self.handle.get_extended_attribute(name).await
}
pub async fn set_extended_attribute(
&self,
name: Vec<u8>,
value: Vec<u8>,
mode: SetExtendedAttributeMode,
) -> Result<(), Error> {
ensure!(!self.is_deleted(), FxfsError::Deleted);
self.handle.set_extended_attribute(name, value, mode).await
}
pub async fn remove_extended_attribute(&self, name: Vec<u8>) -> Result<(), Error> {
ensure!(!self.is_deleted(), FxfsError::Deleted);
self.handle.remove_extended_attribute(name).await
}
/// 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(ChildValue { 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,
// "Object" can be a file or symbolic link, but not a directory.
Object(u64),
ObjectWithRemainingLinks(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 mut sub_dirs_delta: i64 = 0;
let now = Timestamp::now();
let src = if let Some((src_dir, src_name)) = src {
let store_id = dst.0.store().store_object_id();
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)?;
src_dir.store().update_attributes(transaction, id, None, Some(now)).await?;
if src_dir.object_id() != dst.0.object_id() {
sub_dirs_delta = if descriptor == ObjectDescriptor::Directory { 1 } else { 0 };
src_dir
.update_attributes(
transaction,
Some(&fio::MutableNodeAttributes {
modification_time: Some(now.as_nanos()),
..Default::default()
}),
-sub_dirs_delta,
Some(now),
)
.await?;
}
Some((id, descriptor))
} else {
None
};
replace_child_with_object(transaction, src, dst, sub_dirs_delta, now).await
}
/// Replaces dst.0/dst.1 with the given object, or unlinks if `src` is None.
///
/// 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).
///
/// `sub_dirs_delta` can be used if `src` is a directory and happened to already be a child of
/// `dst`.
pub async fn replace_child_with_object<'a, S: HandleOwner>(
transaction: &mut Transaction<'a>,
src: Option<(u64, ObjectDescriptor)>,
dst: (&'a Directory<S>, &str),
mut sub_dirs_delta: i64,
timestamp: Timestamp,
) -> Result<ReplacedChild, Error> {
let deleted_id_and_descriptor = dst.0.lookup(dst.1).await?;
let store_id = dst.0.store().store_object_id();
// There might be optimizations here that allow us to skip the graveyard where we can delete an
// object in a single transaction (which should be the common case).
let result = match deleted_id_and_descriptor {
Some((old_id, ObjectDescriptor::File | ObjectDescriptor::Symlink)) => {
let was_last_ref = dst.0.store().adjust_refs(transaction, old_id, -1).await?;
dst.0.store().update_attributes(transaction, old_id, None, Some(timestamp)).await?;
if was_last_ref {
ReplacedChild::Object(old_id)
} else {
ReplacedChild::ObjectWithRemainingLinks(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);
}
// Directories might have extended attributes which might require multiple transactions
// to delete, so we delete directories via the graveyard.
dst.0.store().add_to_graveyard(transaction, old_id);
dst.0.store().filesystem().graveyard().queue_tombstone_object(store_id, 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 new_value = match src {
Some((id, descriptor)) => ObjectValue::child(id, descriptor),
None => 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,
Some(&fio::MutableNodeAttributes {
modification_time: Some(timestamp.as_nanos()),
..Default::default()
}),
sub_dirs_delta,
Some(timestamp),
)
.await?;
Ok(result)
}
#[cfg(test)]
mod tests {
use crate::object_store::StoreObjectHandle;
use {
crate::{
errors::FxfsError,
filesystem::{FxFilesystem, JournalingObject, SyncOptions},
object_handle::{ObjectHandle, ReadObjectHandle, WriteObjectHandle},
object_store::{
directory::{replace_child, Directory, ReplacedChild},
object_record::Timestamp,
transaction::{lock_keys, Options},
volume::root_volume,
HandleOptions, LockKey, ObjectDescriptor, ObjectStore, SetExtendedAttributeMode,
},
},
assert_matches::assert_matches,
fidl_fuchsia_io as fio,
fxfs_insecure_crypto::InsecureCrypt,
std::sync::Arc,
storage_device::{fake_device::FakeDevice, DeviceHolder},
};
const TEST_DEVICE_BLOCK_SIZE: u32 = 512;
#[fuchsia::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(lock_keys![], Options::default())
.await
.expect("new_transaction failed");
let dir = Directory::create(&mut transaction, &fs.root_store(), None)
.await
.expect("create failed");
let child_dir = dir
.create_child_dir(&mut transaction, "foo", None)
.await
.expect("create_child_dir failed");
let _child_dir_file = child_dir
.create_child_file(&mut transaction, "bar", None)
.await
.expect("create_child_file failed");
let _child_file = dir
.create_child_file(&mut transaction, "baz", None)
.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(false);
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");
}
#[fuchsia::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 dir;
let child;
let mut transaction = fs
.clone()
.new_transaction(lock_keys![], Options::default())
.await
.expect("new_transaction failed");
dir = Directory::create(&mut transaction, &fs.root_store(), None)
.await
.expect("create failed");
child = dir
.create_child_file(&mut transaction, "foo", None)
.await
.expect("create_child_file failed");
transaction.commit().await.expect("commit failed");
transaction = fs
.clone()
.new_transaction(
lock_keys![
LockKey::object(fs.root_store().store_object_id(), dir.object_id()),
LockKey::object(fs.root_store().store_object_id(), child.object_id()),
],
Options::default(),
)
.await
.expect("new_transaction failed");
assert_matches!(
replace_child(&mut transaction, None, (&dir, "foo"))
.await
.expect("replace_child failed"),
ReplacedChild::Object(..)
);
transaction.commit().await.expect("commit failed");
assert_eq!(dir.lookup("foo").await.expect("lookup failed"), None);
fs.close().await.expect("Close failed");
}
#[fuchsia::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 dir;
let child;
let bar;
let mut transaction = fs
.clone()
.new_transaction(lock_keys![], Options::default())
.await
.expect("new_transaction failed");
dir = Directory::create(&mut transaction, &fs.root_store(), None)
.await
.expect("create failed");
child = dir
.create_child_dir(&mut transaction, "foo", None)
.await
.expect("create_child_dir failed");
bar = child
.create_child_file(&mut transaction, "bar", None)
.await
.expect("create_child_file failed");
transaction.commit().await.expect("commit failed");
transaction = fs
.clone()
.new_transaction(
lock_keys![
LockKey::object(fs.root_store().store_object_id(), dir.object_id()),
LockKey::object(fs.root_store().store_object_id(), child.object_id()),
],
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
);
transaction.commit().await.expect("commit failed");
transaction = fs
.clone()
.new_transaction(
lock_keys![
LockKey::object(fs.root_store().store_object_id(), child.object_id()),
LockKey::object(fs.root_store().store_object_id(), bar.object_id()),
],
Options::default(),
)
.await
.expect("new_transaction failed");
assert_matches!(
replace_child(&mut transaction, None, (&child, "bar"))
.await
.expect("replace_child failed"),
ReplacedChild::Object(..)
);
transaction.commit().await.expect("commit failed");
transaction = fs
.clone()
.new_transaction(
lock_keys![
LockKey::object(fs.root_store().store_object_id(), dir.object_id()),
LockKey::object(fs.root_store().store_object_id(), child.object_id()),
],
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");
}
#[fuchsia::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 dir;
let child;
let mut transaction = fs
.clone()
.new_transaction(lock_keys![], Options::default())
.await
.expect("new_transaction failed");
dir = Directory::create(&mut transaction, &fs.root_store(), None)
.await
.expect("create failed");
child = dir
.create_child_file(&mut transaction, "foo", None)
.await
.expect("create_child_file failed");
transaction.commit().await.expect("commit failed");
transaction = fs
.clone()
.new_transaction(
lock_keys![
LockKey::object(fs.root_store().store_object_id(), dir.object_id()),
LockKey::object(fs.root_store().store_object_id(), child.object_id()),
],
Options::default(),
)
.await
.expect("new_transaction failed");
assert_matches!(
replace_child(&mut transaction, None, (&dir, "foo"))
.await
.expect("replace_child failed"),
ReplacedChild::Object(..)
);
transaction.commit().await.expect("commit failed");
transaction = fs
.clone()
.new_transaction(
lock_keys![LockKey::object(fs.root_store().store_object_id(), dir.object_id())],
Options::default(),
)
.await
.expect("new_transaction failed");
dir.create_child_file(&mut transaction, "foo", None)
.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");
}
#[fuchsia::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 dir;
let child;
let mut transaction = fs
.clone()
.new_transaction(lock_keys![], Options::default())
.await
.expect("new_transaction failed");
dir = Directory::create(&mut transaction, &fs.root_store(), None)
.await
.expect("create failed");
child = dir
.create_child_file(&mut transaction, "foo", None)
.await
.expect("create_child_file failed");
transaction.commit().await.expect("commit failed");
dir.lookup("foo").await.expect("lookup failed");
transaction = fs
.clone()
.new_transaction(
lock_keys![
LockKey::object(fs.root_store().store_object_id(), dir.object_id()),
LockKey::object(fs.root_store().store_object_id(), child.object_id()),
],
Options::default(),
)
.await
.expect("new_transaction failed");
assert_matches!(
replace_child(&mut transaction, None, (&dir, "foo"))
.await
.expect("replace_child failed"),
ReplacedChild::Object(..)
);
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(false);
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");
}
#[fuchsia::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 dir;
let child_dir1;
let child_dir2;
let mut transaction = fs
.clone()
.new_transaction(lock_keys![], Options::default())
.await
.expect("new_transaction failed");
dir = Directory::create(&mut transaction, &fs.root_store(), None)
.await
.expect("create failed");
child_dir1 = dir
.create_child_dir(&mut transaction, "dir1", None)
.await
.expect("create_child_dir failed");
child_dir2 = dir
.create_child_dir(&mut transaction, "dir2", None)
.await
.expect("create_child_dir failed");
let file = child_dir1
.create_child_file(&mut transaction, "foo", None)
.await
.expect("create_child_file failed");
transaction.commit().await.expect("commit failed");
transaction = fs
.clone()
.new_transaction(
lock_keys![
LockKey::object(fs.root_store().store_object_id(), child_dir1.object_id()),
LockKey::object(fs.root_store().store_object_id(), child_dir2.object_id()),
LockKey::object(fs.root_store().store_object_id(), file.object_id()),
],
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");
}
#[fuchsia::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 dir;
let child_dir1;
let child_dir2;
let mut transaction = fs
.clone()
.new_transaction(lock_keys![], Options::default())
.await
.expect("new_transaction failed");
dir = Directory::create(&mut transaction, &fs.root_store(), None)
.await
.expect("create failed");
child_dir1 = dir
.create_child_dir(&mut transaction, "dir1", None)
.await
.expect("create_child_dir failed");
child_dir2 = dir
.create_child_dir(&mut transaction, "dir2", None)
.await
.expect("create_child_dir failed");
let foo = child_dir1
.create_child_file(&mut transaction, "foo", None)
.await
.expect("create_child_file failed");
let bar = child_dir2
.create_child_file(&mut transaction, "bar", None)
.await
.expect("create_child_file failed");
let foo_oid = foo.object_id();
let bar_oid = bar.object_id();
transaction.commit().await.expect("commit failed");
{
let mut buf = foo.allocate_buffer(TEST_DEVICE_BLOCK_SIZE as usize).await;
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);
transaction = fs
.clone()
.new_transaction(
lock_keys![
LockKey::object(fs.root_store().store_object_id(), child_dir1.object_id()),
LockKey::object(fs.root_store().store_object_id(), child_dir2.object_id()),
LockKey::object(fs.root_store().store_object_id(), foo_oid),
LockKey::object(fs.root_store().store_object_id(), bar_oid),
],
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::Object(..)
);
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).await;
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");
}
#[fuchsia::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 dir;
let child_dir1;
let child_dir2;
let mut transaction = fs
.clone()
.new_transaction(lock_keys![], Options::default())
.await
.expect("new_transaction failed");
dir = Directory::create(&mut transaction, &fs.root_store(), None)
.await
.expect("create failed");
child_dir1 = dir
.create_child_dir(&mut transaction, "dir1", None)
.await
.expect("create_child_dir failed");
child_dir2 = dir
.create_child_dir(&mut transaction, "dir2", None)
.await
.expect("create_child_dir failed");
let foo = child_dir1
.create_child_file(&mut transaction, "foo", None)
.await
.expect("create_child_file failed");
let nested_child = child_dir2
.create_child_dir(&mut transaction, "bar", None)
.await
.expect("create_child_file failed");
nested_child
.create_child_file(&mut transaction, "baz", None)
.await
.expect("create_child_file failed");
transaction.commit().await.expect("commit failed");
transaction = fs
.clone()
.new_transaction(
lock_keys![
LockKey::object(fs.root_store().store_object_id(), child_dir1.object_id()),
LockKey::object(fs.root_store().store_object_id(), child_dir2.object_id()),
LockKey::object(fs.root_store().store_object_id(), foo.object_id()),
LockKey::object(fs.root_store().store_object_id(), nested_child.object_id()),
],
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");
}
#[fuchsia::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 dir;
let mut transaction = fs
.clone()
.new_transaction(lock_keys![], Options::default())
.await
.expect("new_transaction failed");
dir = Directory::create(&mut transaction, &fs.root_store(), None)
.await
.expect("create failed");
let foo = dir
.create_child_file(&mut transaction, "foo", None)
.await
.expect("create_child_file failed");
transaction.commit().await.expect("commit failed");
transaction = fs
.clone()
.new_transaction(
lock_keys![
LockKey::object(fs.root_store().store_object_id(), dir.object_id()),
LockKey::object(fs.root_store().store_object_id(), foo.object_id()),
],
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");
}
#[fuchsia::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 dir;
let mut transaction = fs
.clone()
.new_transaction(lock_keys![], Options::default())
.await
.expect("new_transaction failed");
dir = Directory::create(&mut transaction, &fs.root_store(), None)
.await
.expect("create failed");
let _cat = dir
.create_child_file(&mut transaction, "cat", None)
.await
.expect("create_child_file failed");
let _ball = dir
.create_child_file(&mut transaction, "ball", None)
.await
.expect("create_child_file failed");
let apple = dir
.create_child_file(&mut transaction, "apple", None)
.await
.expect("create_child_file failed");
let _dog = dir
.create_child_file(&mut transaction, "dog", None)
.await
.expect("create_child_file failed");
transaction.commit().await.expect("commit failed");
transaction = fs
.clone()
.new_transaction(
lock_keys![
LockKey::object(fs.root_store().store_object_id(), dir.object_id()),
LockKey::object(fs.root_store().store_object_id(), apple.object_id()),
],
Options::default(),
)
.await
.expect("new_transaction failed");
replace_child(&mut transaction, None, (&dir, "apple")).await.expect("replace_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");
}
#[fuchsia::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 dir;
let child_dir;
let mut transaction = fs
.clone()
.new_transaction(lock_keys![], Options::default())
.await
.expect("new_transaction failed");
dir = Directory::create(&mut transaction, &fs.root_store(), None)
.await
.expect("create failed");
child_dir = dir
.create_child_dir(&mut transaction, "foo", None)
.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.
transaction = fs
.clone()
.new_transaction(
lock_keys![
LockKey::object(fs.root_store().store_object_id(), dir.object_id()),
LockKey::object(fs.root_store().store_object_id(), child_dir.object_id()),
],
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.
transaction = fs
.clone()
.new_transaction(
lock_keys![LockKey::object(
fs.root_store().store_object_id(),
child_dir.object_id()
)],
Options::default(),
)
.await
.expect("new_transaction failed");
let second_child = child_dir
.create_child_dir(&mut transaction, "baz", None)
.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);
transaction = fs
.clone()
.new_transaction(
lock_keys![
LockKey::object(fs.root_store().store_object_id(), child_dir.object_id()),
LockKey::object(fs.root_store().store_object_id(), dir.object_id()),
LockKey::object(fs.root_store().store_object_id(), second_child.object_id()),
],
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.
transaction = fs
.clone()
.new_transaction(
lock_keys![
LockKey::object(fs.root_store().store_object_id(), dir.object_id()),
LockKey::object(fs.root_store().store_object_id(), second_child.object_id()),
LockKey::object(fs.root_store().store_object_id(), child_dir.object_id()),
],
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.
transaction = fs
.clone()
.new_transaction(
lock_keys![
LockKey::object(fs.root_store().store_object_id(), dir.object_id()),
LockKey::object(fs.root_store().store_object_id(), child_dir.object_id()),
],
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");
}
#[fuchsia::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 dir;
let mut transaction = fs
.clone()
.new_transaction(lock_keys![], Options::default())
.await
.expect("new_transaction failed");
dir = Directory::create(&mut transaction, &fs.root_store(), None)
.await
.expect("create failed");
let child = dir
.create_child_dir(&mut transaction, "foo", None)
.await
.expect("create_child_dir failed");
dir.create_child_dir(&mut transaction, "bar", None).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.
transaction = fs
.clone()
.new_transaction(
lock_keys![
LockKey::object(fs.root_store().store_object_id(), dir.object_id()),
LockKey::object(fs.root_store().store_object_id(), child.object_id()),
],
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());
// 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"));
transaction = fs
.clone()
.new_transaction(lock_keys![], 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", None).await.map(|_| {}));
assert_access_denied(
dir.create_child_file(&mut transaction, "baz", None).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(&fio::MutableNodeAttributes {
creation_time: Some(Timestamp::zero().as_nanos()),
..Default::default()
}),
0,
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(|_| {}));
}
#[fuchsia::test]
async fn test_create_symlink() {
let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
let (dir_id, symlink_id) = {
let mut transaction = fs
.clone()
.new_transaction(lock_keys![], Options::default())
.await
.expect("new_transaction failed");
let dir = Directory::create(&mut transaction, &fs.root_store(), None)
.await
.expect("create failed");
let symlink_id = dir
.create_symlink(&mut transaction, b"link", "foo")
.await
.expect("create_symlink failed");
transaction.commit().await.expect("commit failed");
fs.sync(SyncOptions::default()).await.expect("sync failed");
(dir.object_id(), symlink_id)
};
fs.close().await.expect("Close failed");
let device = fs.take_device().await;
device.reopen(false);
let fs = FxFilesystem::open(device).await.expect("open failed");
{
let dir = Directory::open(&fs.root_store(), dir_id).await.expect("open failed");
assert_eq!(
dir.lookup("foo").await.expect("lookup failed").expect("not found"),
(symlink_id, ObjectDescriptor::Symlink)
);
}
fs.close().await.expect("Close failed");
}
#[fuchsia::test]
async fn test_read_symlink() {
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(lock_keys![], Options::default())
.await
.expect("new_transaction failed");
let store = fs.root_store();
let dir = Directory::create(&mut transaction, &store, None).await.expect("create failed");
let symlink_id = dir
.create_symlink(&mut transaction, b"link", "foo")
.await
.expect("create_symlink failed");
transaction.commit().await.expect("commit failed");
let link = store.read_symlink(symlink_id).await.expect("read_symlink failed");
assert_eq!(&link, b"link");
fs.close().await.expect("Close failed");
}
#[fuchsia::test]
async fn test_unlink_symlink() {
let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
let dir;
let mut transaction = fs
.clone()
.new_transaction(lock_keys![], Options::default())
.await
.expect("new_transaction failed");
let store = fs.root_store();
dir = Directory::create(&mut transaction, &store, None).await.expect("create failed");
let symlink_id = dir
.create_symlink(&mut transaction, b"link", "foo")
.await
.expect("create_symlink failed");
transaction.commit().await.expect("commit failed");
transaction = fs
.clone()
.new_transaction(
lock_keys![
LockKey::object(store.store_object_id(), dir.object_id()),
LockKey::object(store.store_object_id(), symlink_id),
],
Options::default(),
)
.await
.expect("new_transaction failed");
assert_matches!(
replace_child(&mut transaction, None, (&dir, "foo"))
.await
.expect("replace_child failed"),
ReplacedChild::Object(_)
);
transaction.commit().await.expect("commit failed");
assert_eq!(dir.lookup("foo").await.expect("lookup failed"), None);
fs.close().await.expect("Close failed");
}
#[fuchsia::test]
async fn test_get_properties() {
let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
let dir;
let mut transaction = fs
.clone()
.new_transaction(lock_keys![], Options::default())
.await
.expect("new_transaction failed");
let create_attributes = fio::MutableNodeAttributes::default();
dir = Directory::create(&mut transaction, &fs.root_store(), Some(&create_attributes))
.await
.expect("create failed");
transaction.commit().await.expect("commit failed");
// Check attributes of `dir`
let mut properties = dir.get_properties().await.expect("get_properties failed");
let dir_creation_time = properties.creation_time;
assert_eq!(dir_creation_time, properties.modification_time);
assert_eq!(properties.sub_dirs, 0);
assert!(properties.posix_attributes.is_none());
// Create child directory
transaction = fs
.clone()
.new_transaction(
lock_keys![LockKey::object(fs.root_store().store_object_id(), dir.object_id())],
Options::default(),
)
.await
.expect("new_transaction failed");
let child_dir = dir
.create_child_dir(&mut transaction, "foo", None)
.await
.expect("create_child_dir failed");
transaction.commit().await.expect("commit failed");
// Check attributes of `dir` after adding child directory
properties = dir.get_properties().await.expect("get_properties failed");
// The modification time property should have updated
assert_eq!(dir_creation_time, properties.creation_time);
assert!(dir_creation_time < properties.modification_time);
assert_eq!(properties.sub_dirs, 1);
assert!(properties.posix_attributes.is_none());
// Check attributes of `child_dir`
properties = child_dir.get_properties().await.expect("get_properties failed");
assert_eq!(properties.creation_time, properties.modification_time);
assert_eq!(properties.sub_dirs, 0);
assert!(properties.posix_attributes.is_none());
// Create child file with MutableAttributes
transaction = fs
.clone()
.new_transaction(
lock_keys![LockKey::object(
fs.root_store().store_object_id(),
child_dir.object_id()
)],
Options::default(),
)
.await
.expect("new_transaction failed");
let child_dir_file = child_dir
.create_child_file(
&mut transaction,
"bar",
Some(&fio::MutableNodeAttributes { gid: Some(1), ..Default::default() }),
)
.await
.expect("create_child_file failed");
transaction.commit().await.expect("commit failed");
// The modification time property of `child_dir` should have updated
properties = child_dir.get_properties().await.expect("get_properties failed");
assert!(properties.creation_time < properties.modification_time);
assert!(properties.posix_attributes.is_none());
// Check attributes of `child_dir_file`
properties = child_dir_file.get_properties().await.expect("get_properties failed");
assert_eq!(properties.creation_time, properties.modification_time);
assert_eq!(properties.sub_dirs, 0);
assert!(properties.posix_attributes.is_some());
assert_eq!(properties.posix_attributes.unwrap().gid, 1);
// The other POSIX attributes should be set to default values
assert_eq!(properties.posix_attributes.unwrap().uid, 0);
assert_eq!(properties.posix_attributes.unwrap().mode, 0);
assert_eq!(properties.posix_attributes.unwrap().rdev, 0);
}
#[fuchsia::test]
async fn test_update_create_attributes() {
let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
let dir;
let mut transaction = fs
.clone()
.new_transaction(lock_keys![], Options::default())
.await
.expect("new_transaction failed");
let create_attributes = fio::MutableNodeAttributes::default();
dir = Directory::create(&mut transaction, &fs.root_store(), Some(&create_attributes))
.await
.expect("create failed");
transaction.commit().await.expect("commit failed");
let mut properties = dir.get_properties().await.expect("get_properties failed");
assert_eq!(properties.sub_dirs, 0);
assert!(properties.posix_attributes.is_none());
let creation_time = properties.creation_time;
let modification_time = properties.modification_time;
assert_eq!(creation_time, modification_time);
// First update: test that
// 1. updating attributes with a POSIX attribute will assign some PosixAttributes to the
// Object associated with `dir`,
// 2. creation/modification time are only updated if specified in the update,
// 3. any changes will not overwrite other attributes.
transaction = fs
.clone()
.new_transaction(
lock_keys![LockKey::object(fs.root_store().store_object_id(), dir.object_id())],
Options::default(),
)
.await
.expect("new_transaction failed");
let now = Timestamp::now();
dir.update_attributes(
&mut transaction,
Some(&fio::MutableNodeAttributes {
modification_time: Some(now.as_nanos()),
uid: Some(1),
gid: Some(2),
..Default::default()
}),
0,
None,
)
.await
.expect("update_attributes failed");
transaction.commit().await.expect("commit failed");
properties = dir.get_properties().await.expect("get_properties failed");
// Check that the properties reflect the updates
assert_eq!(properties.modification_time, now);
assert!(properties.posix_attributes.is_some());
assert_eq!(properties.posix_attributes.unwrap().uid, 1);
assert_eq!(properties.posix_attributes.unwrap().gid, 2);
// The other POSIX attributes should be set to default values
assert_eq!(properties.posix_attributes.unwrap().mode, 0);
assert_eq!(properties.posix_attributes.unwrap().rdev, 0);
// The remaining properties should not have changed
assert_eq!(properties.sub_dirs, 0);
assert_eq!(properties.creation_time, creation_time);
// Second update: test that we can update attributes and that any changes will not overwrite
// other attributes
let mut transaction = fs
.clone()
.new_transaction(
lock_keys![LockKey::object(fs.root_store().store_object_id(), dir.object_id())],
Options::default(),
)
.await
.expect("new_transaction failed");
dir.update_attributes(
&mut transaction,
Some(&fio::MutableNodeAttributes {
creation_time: Some(now.as_nanos()),
uid: Some(3),
rdev: Some(10),
..Default::default()
}),
0,
None,
)
.await
.expect("update_attributes failed");
transaction.commit().await.expect("commit failed");
properties = dir.get_properties().await.expect("get_properties failed");
assert_eq!(properties.creation_time, now);
assert!(properties.posix_attributes.is_some());
assert_eq!(properties.posix_attributes.unwrap().uid, 3);
assert_eq!(properties.posix_attributes.unwrap().rdev, 10);
// The other properties should not have changed
assert_eq!(properties.sub_dirs, 0);
assert_eq!(properties.modification_time, now);
assert_eq!(properties.posix_attributes.unwrap().gid, 2);
assert_eq!(properties.posix_attributes.unwrap().mode, 0);
}
#[fuchsia::test]
async fn write_to_directory_attribute_creates_keys() {
let device = DeviceHolder::new(FakeDevice::new(16384, 512));
let filesystem = FxFilesystem::new_empty(device).await.expect("new_empty failed");
let crypt = Arc::new(InsecureCrypt::new());
{
let root_volume = root_volume(filesystem.clone()).await.expect("root_volume failed");
let store = root_volume
.new_volume("vol", Some(crypt.clone()))
.await
.expect("new_volume failed");
let mut transaction = filesystem
.clone()
.new_transaction(
lock_keys![LockKey::object(
store.store_object_id(),
store.root_directory_object_id()
)],
Options::default(),
)
.await
.expect("new transaction failed");
let root_directory = Directory::open(&store, store.root_directory_object_id())
.await
.expect("open failed");
let directory = root_directory
.create_child_dir(&mut transaction, "foo", None)
.await
.expect("create_child_dir failed");
transaction.commit().await.expect("commit failed");
let mut transaction = filesystem
.clone()
.new_transaction(
lock_keys![LockKey::object(store.store_object_id(), directory.object_id())],
Options::default(),
)
.await
.expect("new transaction failed");
let _ = directory
.handle
.write_attr(&mut transaction, 1, b"bar")
.await
.expect("write_attr failed");
transaction.commit().await.expect("commit failed");
}
filesystem.close().await.expect("Close failed");
let device = filesystem.take_device().await;
device.reopen(false);
let filesystem = FxFilesystem::open(device).await.expect("open failed");
{
let root_volume = root_volume(filesystem.clone()).await.expect("root_volume failed");
let volume = root_volume.volume("vol", Some(crypt)).await.expect("volume failed");
let root_directory = Directory::open(&volume, volume.root_directory_object_id())
.await
.expect("open failed");
let directory = Directory::open(
&volume,
root_directory.lookup("foo").await.expect("lookup failed").expect("not found").0,
)
.await
.expect("open failed");
let mut buffer = directory.handle.allocate_buffer(10).await;
assert_eq!(directory.handle.read(1, 0, buffer.as_mut()).await.expect("read failed"), 3);
assert_eq!(&buffer.as_slice()[..3], b"bar");
}
filesystem.close().await.expect("Close failed");
}
#[fuchsia::test]
async fn directory_with_extended_attributes() {
let device = DeviceHolder::new(FakeDevice::new(16384, 512));
let filesystem = FxFilesystem::new_empty(device).await.expect("new_empty failed");
let crypt = Arc::new(InsecureCrypt::new());
let root_volume = root_volume(filesystem.clone()).await.expect("root_volume failed");
let store =
root_volume.new_volume("vol", Some(crypt.clone())).await.expect("new_volume failed");
let directory =
Directory::open(&store, store.root_directory_object_id()).await.expect("open failed");
let test_small_name = b"security.selinux".to_vec();
let test_small_value = b"foo".to_vec();
let test_large_name = b"large.attribute".to_vec();
let test_large_value = vec![1u8; 500];
directory
.set_extended_attribute(
test_small_name.clone(),
test_small_value.clone(),
SetExtendedAttributeMode::Set,
)
.await
.unwrap();
assert_eq!(
directory.get_extended_attribute(test_small_name.clone()).await.unwrap(),
test_small_value
);
directory
.set_extended_attribute(
test_large_name.clone(),
test_large_value.clone(),
SetExtendedAttributeMode::Set,
)
.await
.unwrap();
assert_eq!(
directory.get_extended_attribute(test_large_name.clone()).await.unwrap(),
test_large_value
);
crate::fsck::fsck(filesystem.clone()).await.unwrap();
crate::fsck::fsck_volume(filesystem.as_ref(), store.store_object_id(), Some(crypt.clone()))
.await
.unwrap();
directory.remove_extended_attribute(test_small_name.clone()).await.unwrap();
directory.remove_extended_attribute(test_large_name.clone()).await.unwrap();
filesystem.close().await.expect("close failed");
}
#[fuchsia::test]
async fn remove_directory_with_extended_attributes() {
let device = DeviceHolder::new(FakeDevice::new(16384, 512));
let filesystem = FxFilesystem::new_empty(device).await.expect("new_empty failed");
let crypt = Arc::new(InsecureCrypt::new());
let root_volume = root_volume(filesystem.clone()).await.expect("root_volume failed");
let store =
root_volume.new_volume("vol", Some(crypt.clone())).await.expect("new_volume failed");
let mut transaction = filesystem
.clone()
.new_transaction(
lock_keys![LockKey::object(
store.store_object_id(),
store.root_directory_object_id()
)],
Options::default(),
)
.await
.expect("new transaction failed");
let root_directory =
Directory::open(&store, store.root_directory_object_id()).await.expect("open failed");
let directory = root_directory
.create_child_dir(&mut transaction, "foo", None)
.await
.expect("create_child_dir failed");
transaction.commit().await.expect("commit failed");
crate::fsck::fsck(filesystem.clone()).await.unwrap();
crate::fsck::fsck_volume(filesystem.as_ref(), store.store_object_id(), Some(crypt.clone()))
.await
.unwrap();
let test_small_name = b"security.selinux".to_vec();
let test_small_value = b"foo".to_vec();
let test_large_name = b"large.attribute".to_vec();
let test_large_value = vec![1u8; 500];
directory
.set_extended_attribute(
test_small_name.clone(),
test_small_value.clone(),
SetExtendedAttributeMode::Set,
)
.await
.unwrap();
directory
.set_extended_attribute(
test_large_name.clone(),
test_large_value.clone(),
SetExtendedAttributeMode::Set,
)
.await
.unwrap();
crate::fsck::fsck(filesystem.clone()).await.unwrap();
crate::fsck::fsck_volume(filesystem.as_ref(), store.store_object_id(), Some(crypt.clone()))
.await
.unwrap();
let mut transaction = filesystem
.clone()
.new_transaction(
lock_keys![
LockKey::object(store.store_object_id(), root_directory.object_id()),
LockKey::object(store.store_object_id(), directory.object_id()),
],
Options::default(),
)
.await
.expect("new_transaction failed");
replace_child(&mut transaction, None, (&root_directory, "foo"))
.await
.expect("replace_child failed");
transaction.commit().await.unwrap();
crate::fsck::fsck(filesystem.clone()).await.unwrap();
crate::fsck::fsck_volume(filesystem.as_ref(), store.store_object_id(), Some(crypt.clone()))
.await
.unwrap();
filesystem.close().await.expect("close failed");
}
#[fuchsia::test]
async fn remove_symlink_with_extended_attributes() {
let device = DeviceHolder::new(FakeDevice::new(16384, 512));
let filesystem = FxFilesystem::new_empty(device).await.expect("new_empty failed");
let crypt = Arc::new(InsecureCrypt::new());
let root_volume = root_volume(filesystem.clone()).await.expect("root_volume failed");
let store =
root_volume.new_volume("vol", Some(crypt.clone())).await.expect("new_volume failed");
let mut transaction = filesystem
.clone()
.new_transaction(
lock_keys![LockKey::object(
store.store_object_id(),
store.root_directory_object_id()
)],
Options::default(),
)
.await
.expect("new transaction failed");
let root_directory =
Directory::open(&store, store.root_directory_object_id()).await.expect("open failed");
let symlink_id = root_directory
.create_symlink(&mut transaction, b"somewhere/else", "foo")
.await
.expect("create_symlink failed");
transaction.commit().await.expect("commit failed");
let symlink = StoreObjectHandle::new(
store.clone(),
symlink_id,
false,
HandleOptions::default(),
false,
);
crate::fsck::fsck(filesystem.clone()).await.unwrap();
crate::fsck::fsck_volume(filesystem.as_ref(), store.store_object_id(), Some(crypt.clone()))
.await
.unwrap();
let test_small_name = b"security.selinux".to_vec();
let test_small_value = b"foo".to_vec();
let test_large_name = b"large.attribute".to_vec();
let test_large_value = vec![1u8; 500];
symlink
.set_extended_attribute(
test_small_name.clone(),
test_small_value.clone(),
SetExtendedAttributeMode::Set,
)
.await
.unwrap();
symlink
.set_extended_attribute(
test_large_name.clone(),
test_large_value.clone(),
SetExtendedAttributeMode::Set,
)
.await
.unwrap();
crate::fsck::fsck(filesystem.clone()).await.unwrap();
crate::fsck::fsck_volume(filesystem.as_ref(), store.store_object_id(), Some(crypt.clone()))
.await
.unwrap();
let mut transaction = filesystem
.clone()
.new_transaction(
lock_keys![
LockKey::object(store.store_object_id(), root_directory.object_id()),
LockKey::object(store.store_object_id(), symlink.object_id()),
],
Options::default(),
)
.await
.expect("new_transaction failed");
replace_child(&mut transaction, None, (&root_directory, "foo"))
.await
.expect("replace_child failed");
transaction.commit().await.unwrap();
crate::fsck::fsck(filesystem.clone()).await.unwrap();
crate::fsck::fsck_volume(filesystem.as_ref(), store.store_object_id(), Some(crypt.clone()))
.await
.unwrap();
filesystem.close().await.expect("close failed");
}
#[fuchsia::test]
async fn test_update_timestamps() {
let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
let dir;
let mut transaction = fs
.clone()
.new_transaction(lock_keys![], Options::default())
.await
.expect("new_transaction failed");
let create_attributes = fio::MutableNodeAttributes::default();
// Expect that atime, ctime, mtime (and creation time) to be the same when we create a
// directory
dir = Directory::create(&mut transaction, &fs.root_store(), Some(&create_attributes))
.await
.expect("create failed");
transaction.commit().await.expect("commit failed");
let mut properties = dir.get_properties().await.expect("get_properties failed");
let starting_time = properties.creation_time;
assert_eq!(properties.creation_time, starting_time);
assert_eq!(properties.modification_time, starting_time);
assert_eq!(properties.change_time, starting_time);
assert_eq!(properties.access_time, starting_time);
// Test that we can update the timestamps
transaction = fs
.clone()
.new_transaction(
lock_keys![LockKey::object(fs.root_store().store_object_id(), dir.object_id())],
Options::default(),
)
.await
.expect("new_transaction failed");
let update1_time = Timestamp::now();
dir.update_attributes(
&mut transaction,
Some(&fio::MutableNodeAttributes {
modification_time: Some(update1_time.as_nanos()),
..Default::default()
}),
0,
Some(update1_time),
)
.await
.expect("update_attributes failed");
transaction.commit().await.expect("commit failed");
properties = dir.get_properties().await.expect("get_properties failed");
assert_eq!(properties.modification_time, update1_time);
assert_eq!(properties.access_time, starting_time);
assert_eq!(properties.creation_time, starting_time);
assert_eq!(properties.change_time, update1_time);
}
#[fuchsia::test]
async fn test_move_dir_timestamps() {
let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
let dir;
let child1;
let child2;
let mut transaction = fs
.clone()
.new_transaction(lock_keys![], Options::default())
.await
.expect("new_transaction failed");
let create_attributes = fio::MutableNodeAttributes::default();
dir = Directory::create(&mut transaction, &fs.root_store(), Some(&create_attributes))
.await
.expect("create failed");
child1 = dir
.create_child_dir(&mut transaction, "child1", None)
.await
.expect("create_child_dir failed");
child2 = dir
.create_child_dir(&mut transaction, "child2", None)
.await
.expect("create_child_dir failed");
transaction.commit().await.expect("commit failed");
let dir_properties = dir.get_properties().await.expect("get_properties failed");
let child2_properties = child2.get_properties().await.expect("get_properties failed");
// Move dir/child2 to dir/child1/child2
transaction = fs
.clone()
.new_transaction(
lock_keys![
LockKey::object(fs.root_store().store_object_id(), dir.object_id()),
LockKey::object(fs.root_store().store_object_id(), child1.object_id()),
LockKey::object(fs.root_store().store_object_id(), child2.object_id()),
],
Options::default(),
)
.await
.expect("new_transaction failed");
assert_matches!(
replace_child(&mut transaction, Some((&dir, "child2")), (&child1, "child2"))
.await
.expect("replace_child failed"),
ReplacedChild::None
);
transaction.commit().await.expect("commit failed");
// Both mtime and ctime for dir should be updated
let new_dir_properties = dir.get_properties().await.expect("get_properties failed");
let time_of_replacement = new_dir_properties.change_time;
assert!(new_dir_properties.change_time > dir_properties.change_time);
assert_eq!(new_dir_properties.modification_time, time_of_replacement);
// Both mtime and ctime for child1 should be updated
let new_child1_properties = child1.get_properties().await.expect("get_properties failed");
assert_eq!(new_child1_properties.modification_time, time_of_replacement);
assert_eq!(new_child1_properties.change_time, time_of_replacement);
// Only ctime for child2 should be updated
let moved_child2_properties = child2.get_properties().await.expect("get_properties failed");
assert_eq!(moved_child2_properties.change_time, time_of_replacement);
assert_eq!(moved_child2_properties.creation_time, child2_properties.creation_time);
assert_eq!(moved_child2_properties.access_time, child2_properties.access_time);
assert_eq!(moved_child2_properties.modification_time, child2_properties.modification_time);
fs.close().await.expect("Close failed");
}
#[fuchsia::test]
async fn test_unlink_timestamps() {
let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
let dir;
let foo;
let mut transaction = fs
.clone()
.new_transaction(lock_keys![], Options::default())
.await
.expect("new_transaction failed");
let create_attributes = fio::MutableNodeAttributes::default();
dir = Directory::create(&mut transaction, &fs.root_store(), Some(&create_attributes))
.await
.expect("create failed");
foo = dir
.create_child_file(&mut transaction, "foo", None)
.await
.expect("create_child_dir failed");
transaction.commit().await.expect("commit failed");
let dir_properties = dir.get_properties().await.expect("get_properties failed");
let foo_properties = foo.get_properties().await.expect("get_properties failed");
transaction = fs
.clone()
.new_transaction(
lock_keys![
LockKey::object(fs.root_store().store_object_id(), dir.object_id()),
LockKey::object(fs.root_store().store_object_id(), foo.object_id()),
],
Options::default(),
)
.await
.expect("new_transaction failed");
assert_matches!(
replace_child(&mut transaction, None, (&dir, "foo"))
.await
.expect("replace_child failed"),
ReplacedChild::Object(_)
);
transaction.commit().await.expect("commit failed");
// Both mtime and ctime for dir should be updated
let new_dir_properties = dir.get_properties().await.expect("get_properties failed");
let time_of_replacement = new_dir_properties.change_time;
assert!(new_dir_properties.change_time > dir_properties.change_time);
assert_eq!(new_dir_properties.modification_time, time_of_replacement);
// Only ctime for foo should be updated
let moved_foo_properties = foo.get_properties().await.expect("get_properties failed");
assert_eq!(moved_foo_properties.change_time, time_of_replacement);
assert_eq!(moved_foo_properties.creation_time, foo_properties.creation_time);
assert_eq!(moved_foo_properties.access_time, foo_properties.access_time);
assert_eq!(moved_foo_properties.modification_time, foo_properties.modification_time);
fs.close().await.expect("Close failed");
}
#[fuchsia::test]
async fn test_replace_dir_timestamps() {
let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
let dir;
let child_dir1;
let child_dir2;
let foo;
let mut transaction = fs
.clone()
.new_transaction(lock_keys![], Options::default())
.await
.expect("new_transaction failed");
let create_attributes = fio::MutableNodeAttributes::default();
dir = Directory::create(&mut transaction, &fs.root_store(), Some(&create_attributes))
.await
.expect("create failed");
child_dir1 = dir
.create_child_dir(&mut transaction, "dir1", None)
.await
.expect("create_child_dir failed");
child_dir2 = dir
.create_child_dir(&mut transaction, "dir2", None)
.await
.expect("create_child_dir failed");
foo = child_dir1
.create_child_dir(&mut transaction, "foo", None)
.await
.expect("create_child_dir failed");
transaction.commit().await.expect("commit failed");
let dir_props = dir.get_properties().await.expect("get_properties failed");
let foo_props = foo.get_properties().await.expect("get_properties failed");
transaction = fs
.clone()
.new_transaction(
lock_keys![
LockKey::object(fs.root_store().store_object_id(), dir.object_id()),
LockKey::object(fs.root_store().store_object_id(), child_dir1.object_id()),
LockKey::object(fs.root_store().store_object_id(), child_dir2.object_id()),
LockKey::object(fs.root_store().store_object_id(), foo.object_id()),
],
Options::default(),
)
.await
.expect("new_transaction failed");
assert_matches!(
replace_child(&mut transaction, Some((&child_dir1, "foo")), (&dir, "dir2"))
.await
.expect("replace_child failed"),
ReplacedChild::Directory(_)
);
transaction.commit().await.expect("commit failed");
// Both mtime and ctime for dir should be updated
let new_dir_props = dir.get_properties().await.expect("get_properties failed");
let time_of_replacement = new_dir_props.change_time;
assert!(new_dir_props.change_time > dir_props.change_time);
assert_eq!(new_dir_props.modification_time, time_of_replacement);
// Both mtime and ctime for dir1 should be updated
let new_dir1_props = child_dir1.get_properties().await.expect("get_properties failed");
let time_of_replacement = new_dir1_props.change_time;
assert_eq!(new_dir1_props.change_time, time_of_replacement);
assert_eq!(new_dir1_props.modification_time, time_of_replacement);
// Only ctime for foo should be updated
let moved_foo_props = foo.get_properties().await.expect("get_properties failed");
assert_eq!(moved_foo_props.change_time, time_of_replacement);
assert_eq!(moved_foo_props.creation_time, foo_props.creation_time);
assert_eq!(moved_foo_props.access_time, foo_props.access_time);
assert_eq!(moved_foo_props.modification_time, foo_props.modification_time);
fs.close().await.expect("Close failed");
}
}