| // 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::fuchsia::{ |
| device::BlockServer, |
| errors::map_to_status, |
| file::FxFile, |
| node::{FxNode, GetResult, OpenedNode}, |
| symlink::FxSymlink, |
| volume::{info_to_filesystem_info, FxVolume, RootDir}, |
| }, |
| anyhow::{bail, Error}, |
| async_trait::async_trait, |
| either::{Left, Right}, |
| fidl::endpoints::ServerEnd, |
| fidl_fuchsia_io as fio, fuchsia_zircon as zx, |
| futures::FutureExt, |
| fxfs::{ |
| errors::FxfsError, |
| filesystem::SyncOptions, |
| log::*, |
| object_store::{ |
| self, |
| directory::{self, ReplacedChild}, |
| transaction::{lock_keys, LockKey, Options, Transaction}, |
| Directory, ObjectDescriptor, ObjectStore, Timestamp, |
| }, |
| }, |
| std::{ |
| any::Any, |
| sync::{Arc, Mutex}, |
| }, |
| vfs::{ |
| attributes, |
| common::rights_to_posix_mode_bits, |
| directory::{ |
| dirents_sink::{self, AppendResult, Sink}, |
| entry::{DirectoryEntry, EntryInfo, OpenRequest}, |
| entry_container::{Directory as VfsDirectory, DirectoryWatcher, MutableDirectory}, |
| mutable::connection::MutableConnection, |
| traversal_position::TraversalPosition, |
| watchers::{event_producers::SingleNameEventProducer, Watchers}, |
| }, |
| execution_scope::ExecutionScope, |
| path::Path, |
| symlink, ObjectRequestRef, ProtocolsExt, ToObjectRequest, |
| }, |
| }; |
| |
| pub struct FxDirectory { |
| // The root directory is the only directory which has no parent, and its parent can never |
| // change, hence the Option can go on the outside. |
| parent: Option<Mutex<Arc<FxDirectory>>>, |
| directory: object_store::Directory<FxVolume>, |
| watchers: Mutex<Watchers>, |
| } |
| |
| impl RootDir for FxDirectory { |
| fn as_directory_entry(self: Arc<Self>) -> Arc<dyn DirectoryEntry> { |
| self |
| } |
| |
| fn as_directory(self: Arc<Self>) -> Arc<dyn VfsDirectory> { |
| self |
| } |
| |
| fn as_node(self: Arc<Self>) -> Arc<dyn FxNode> { |
| self as Arc<dyn FxNode> |
| } |
| } |
| |
| impl FxDirectory { |
| pub(super) fn new( |
| parent: Option<Arc<FxDirectory>>, |
| directory: object_store::Directory<FxVolume>, |
| ) -> Self { |
| Self { |
| parent: parent.map(|p| Mutex::new(p)), |
| directory, |
| watchers: Mutex::new(Watchers::new()), |
| } |
| } |
| |
| pub fn directory(&self) -> &object_store::Directory<FxVolume> { |
| &self.directory |
| } |
| |
| pub fn volume(&self) -> &Arc<FxVolume> { |
| self.directory.owner() |
| } |
| |
| pub fn store(&self) -> &ObjectStore { |
| self.directory.store() |
| } |
| |
| pub fn is_deleted(&self) -> bool { |
| self.directory.is_deleted() |
| } |
| |
| pub fn set_deleted(&self) { |
| self.directory.set_deleted(); |
| self.watchers.lock().unwrap().send_event(&mut SingleNameEventProducer::deleted()); |
| } |
| |
| async fn lookup( |
| self: &Arc<Self>, |
| protocols: &dyn ProtocolsExt, |
| mut path: Path, |
| ) -> Result<OpenedNode<dyn FxNode>, Error> { |
| if path.is_empty() { |
| return Ok(OpenedNode::new(self.clone())); |
| } |
| let store = self.store(); |
| let fs = store.filesystem(); |
| let mut current_node = self.clone() as Arc<dyn FxNode>; |
| loop { |
| let last_segment = path.is_single_component(); |
| let current_dir = |
| current_node.into_any().downcast::<FxDirectory>().map_err(|_| FxfsError::NotDir)?; |
| let name = path.next().unwrap(); |
| |
| // Create the transaction here if we might need to create the object so that we have a |
| // lock in place. |
| let keys = lock_keys![LockKey::object( |
| store.store_object_id(), |
| current_dir.directory.object_id() |
| )]; |
| let transaction_or_guard = |
| if last_segment && protocols.creation_mode() != vfs::CreationMode::Never { |
| Left(fs.clone().new_transaction(keys, Options::default()).await?) |
| } else { |
| // When child objects are created, the object is created along with the |
| // directory entry in the same transaction, and so we need to hold a read lock |
| // over the lookup and open calls. |
| Right(fs.lock_manager().read_lock(keys).await) |
| }; |
| |
| let child_descriptor = match self |
| .directory |
| .owner() |
| .dirent_cache() |
| .lookup(&(current_dir.object_id(), name)) |
| { |
| Some(node) => { |
| let desc = node.object_descriptor(); |
| Some((node, desc)) |
| } |
| None => { |
| if let Some((object_id, object_descriptor)) = |
| current_dir.directory.lookup(name).await? |
| { |
| let child_node = self |
| .volume() |
| .get_or_load_node( |
| object_id, |
| object_descriptor.clone(), |
| Some(current_dir.clone()), |
| ) |
| .await?; |
| |
| self.directory.owner().dirent_cache().insert( |
| current_dir.object_id(), |
| name.to_owned(), |
| child_node.clone(), |
| ); |
| Some((child_node, object_descriptor)) |
| } else { |
| None |
| } |
| } |
| }; |
| |
| match child_descriptor { |
| Some((child_node, object_descriptor)) => { |
| if transaction_or_guard.is_left() |
| && protocols.creation_mode() == vfs::CreationMode::Always |
| { |
| bail!(FxfsError::AlreadyExists); |
| } |
| if last_segment { |
| match object_descriptor { |
| ObjectDescriptor::Directory => { |
| if !protocols.is_node() && !protocols.is_dir_allowed() { |
| if protocols.is_file_allowed() { |
| bail!(FxfsError::NotFile) |
| } else { |
| bail!(FxfsError::WrongType) |
| } |
| } |
| } |
| ObjectDescriptor::File => { |
| if !protocols.is_node() && !protocols.is_file_allowed() { |
| if protocols.is_dir_allowed() { |
| bail!(FxfsError::NotDir) |
| } else { |
| bail!(FxfsError::WrongType) |
| } |
| } |
| } |
| ObjectDescriptor::Symlink => { |
| if !protocols.is_node() && !protocols.is_symlink_allowed() { |
| bail!(FxfsError::WrongType) |
| } |
| } |
| ObjectDescriptor::Volume => bail!(FxfsError::Inconsistent), |
| } |
| } |
| current_node = child_node; |
| if last_segment { |
| // We must make sure to take an open-count whilst we are holding a read |
| // lock. |
| return Ok(OpenedNode::new(current_node)); |
| } |
| } |
| None => { |
| if let Left(mut transaction) = transaction_or_guard { |
| let new_node = current_dir |
| .create_child( |
| &mut transaction, |
| name, |
| protocols.create_directory(), |
| protocols.create_attributes(), |
| ) |
| .await?; |
| let node = OpenedNode::new(new_node.clone()); |
| if let GetResult::Placeholder(p) = |
| self.volume().cache().get_or_reserve(node.object_id()).await |
| { |
| transaction |
| .commit_with_callback(|_| { |
| p.commit(&node); |
| current_dir.did_add(name, Some(new_node)); |
| }) |
| .await?; |
| return Ok(node); |
| } else { |
| // We created a node, but the object ID was already used in the cache, |
| // which suggests a object ID was reused (which would either be a bug or |
| // corruption). |
| bail!(FxfsError::Inconsistent); |
| } |
| } else { |
| bail!(FxfsError::NotFound); |
| } |
| } |
| }; |
| } |
| } |
| |
| async fn create_child( |
| self: &Arc<Self>, |
| transaction: &mut Transaction<'_>, |
| name: &str, |
| create_dir: bool, // If false, creates a file. |
| create_attributes: Option<&fio::MutableNodeAttributes>, |
| ) -> Result<Arc<dyn FxNode>, Error> { |
| if create_dir { |
| Ok(Arc::new(FxDirectory::new( |
| Some(self.clone()), |
| self.directory.create_child_dir(transaction, name, create_attributes).await?, |
| )) as Arc<dyn FxNode>) |
| } else { |
| Ok(FxFile::new( |
| self.directory.create_child_file(transaction, name, create_attributes).await?, |
| ) as Arc<dyn FxNode>) |
| } |
| } |
| |
| /// Called to indicate a file or directory was removed from this directory. |
| pub(crate) fn did_remove(&self, name: &str) { |
| self.directory.owner().dirent_cache().remove(&(self.directory.object_id(), name)); |
| self.watchers.lock().unwrap().send_event(&mut SingleNameEventProducer::removed(name)); |
| } |
| |
| /// Called to indicate a file or directory was added to this directory. |
| pub(crate) fn did_add(&self, name: &str, node: Option<Arc<dyn FxNode>>) { |
| if let Some(node) = node { |
| self.directory.owner().dirent_cache().insert( |
| self.directory.object_id(), |
| name.to_owned(), |
| node, |
| ); |
| } |
| self.watchers.lock().unwrap().send_event(&mut SingleNameEventProducer::added(name)); |
| } |
| |
| pub(crate) async fn link_object( |
| &self, |
| mut transaction: Transaction<'_>, |
| name: &str, |
| source_id: u64, |
| kind: ObjectDescriptor, |
| ) -> Result<(), zx::Status> { |
| let store = self.store(); |
| if self.is_deleted() { |
| return Err(zx::Status::ACCESS_DENIED); |
| } |
| if self.directory.lookup(&name).await.map_err(map_to_status)?.is_some() { |
| return Err(zx::Status::ALREADY_EXISTS); |
| } |
| self.directory |
| .insert_child(&mut transaction, &name, source_id, kind.clone()) |
| .await |
| .map_err(map_to_status)?; |
| store.adjust_refs(&mut transaction, source_id, 1).await.map_err(map_to_status)?; |
| transaction |
| .commit_with_callback(|_| self.did_add(&name, None)) |
| .await |
| .map_err(map_to_status)?; |
| Ok(()) |
| } |
| } |
| |
| impl Drop for FxDirectory { |
| fn drop(&mut self) { |
| self.volume().cache().remove(self); |
| } |
| } |
| |
| impl FxNode for FxDirectory { |
| fn object_id(&self) -> u64 { |
| self.directory.object_id() |
| } |
| |
| fn parent(&self) -> Option<Arc<FxDirectory>> { |
| self.parent.as_ref().map(|p| p.lock().unwrap().clone()) |
| } |
| |
| fn set_parent(&self, parent: Arc<FxDirectory>) { |
| match &self.parent { |
| Some(p) => *p.lock().unwrap() = parent, |
| None => panic!("Called set_parent on root node"), |
| } |
| } |
| |
| // If these ever do anything, BlobDirectory might need to be fixed. |
| fn open_count_add_one(&self) {} |
| fn open_count_sub_one(self: Arc<Self>) {} |
| |
| fn object_descriptor(&self) -> ObjectDescriptor { |
| ObjectDescriptor::Directory |
| } |
| } |
| |
| #[async_trait] |
| impl MutableDirectory for FxDirectory { |
| async fn link( |
| self: Arc<Self>, |
| name: String, |
| source_dir: Arc<dyn Any + Send + Sync>, |
| source_name: &str, |
| ) -> Result<(), zx::Status> { |
| let source_dir = source_dir.downcast::<Self>().unwrap(); |
| let store = self.store(); |
| let fs = store.filesystem().clone(); |
| let source_id = |
| match source_dir.directory.lookup(source_name).await.map_err(map_to_status)? { |
| Some((object_id, ObjectDescriptor::File)) => object_id, |
| None => return Err(zx::Status::NOT_FOUND), |
| _ => return Err(zx::Status::NOT_SUPPORTED), |
| }; |
| // We don't need a lock on the source directory, as it will be unchanged (unless it is the |
| // same as the destination directory). We just need a lock on the source object to ensure |
| // that it hasn't been simultaneously unlinked. We need that lock anyway to update the ref |
| // count. |
| let transaction = fs |
| .new_transaction( |
| lock_keys![ |
| LockKey::object(store.store_object_id(), self.object_id()), |
| LockKey::object(store.store_object_id(), source_id), |
| ], |
| Options::default(), |
| ) |
| .await |
| .map_err(map_to_status)?; |
| // Ensure under lock that the file still exists there. |
| match source_dir.directory.lookup(source_name).await.map_err(map_to_status)? { |
| Some((_, ObjectDescriptor::File)) => {} |
| None => return Err(zx::Status::NOT_FOUND), |
| _ => return Err(zx::Status::NOT_SUPPORTED), |
| }; |
| self.link_object(transaction, &name, source_id, ObjectDescriptor::File).await |
| } |
| |
| async fn unlink( |
| self: Arc<Self>, |
| name: &str, |
| must_be_directory: bool, |
| ) -> Result<(), zx::Status> { |
| let replace_context = self |
| .directory |
| .acquire_context_for_replace(None, name, true) |
| .await |
| .map_err(map_to_status)?; |
| let mut transaction = replace_context.transaction; |
| let object_descriptor = match replace_context.dst_id_and_descriptor { |
| Some((_, object_descriptor)) => object_descriptor, |
| None => return Err(zx::Status::NOT_FOUND), |
| }; |
| if let ObjectDescriptor::Directory = object_descriptor { |
| } else if must_be_directory { |
| return Err(zx::Status::NOT_DIR); |
| } |
| match directory::replace_child(&mut transaction, None, (self.directory(), name)) |
| .await |
| .map_err(map_to_status)? |
| { |
| ReplacedChild::None => return Err(zx::Status::NOT_FOUND), |
| ReplacedChild::ObjectWithRemainingLinks(..) => { |
| transaction |
| .commit_with_callback(|_| self.did_remove(name)) |
| .await |
| .map_err(map_to_status)?; |
| } |
| ReplacedChild::Object(id) => { |
| transaction |
| .commit_with_callback(|_| self.did_remove(name)) |
| .await |
| .map_err(map_to_status)?; |
| // If purging fails , we should still return success, since the file will appear |
| // unlinked at this point anyways. The file should be cleaned up on a later mount. |
| if let Err(e) = self.volume().maybe_purge_file(id).await { |
| warn!(error = ?e, "Failed to purge file"); |
| } |
| } |
| ReplacedChild::Directory(id) => { |
| transaction |
| .commit_with_callback(|_| { |
| self.did_remove(name); |
| self.volume().mark_directory_deleted(id); |
| }) |
| .await |
| .map_err(map_to_status)?; |
| } |
| } |
| Ok(()) |
| } |
| |
| async fn set_attrs( |
| &self, |
| flags: fio::NodeAttributeFlags, |
| attrs: fio::NodeAttributes, |
| ) -> Result<(), zx::Status> { |
| let creation_time = |
| flags.contains(fio::NodeAttributeFlags::CREATION_TIME).then(|| attrs.creation_time); |
| let modification_time = flags |
| .contains(fio::NodeAttributeFlags::MODIFICATION_TIME) |
| .then(|| attrs.modification_time); |
| if let (None, None) = (creation_time.as_ref(), modification_time.as_ref()) { |
| return Ok(()); |
| } |
| |
| self.update_attributes(fio::MutableNodeAttributes { |
| creation_time, |
| modification_time, |
| ..Default::default() |
| }) |
| .await |
| } |
| |
| async fn update_attributes( |
| &self, |
| attributes: fio::MutableNodeAttributes, |
| ) -> Result<(), zx::Status> { |
| let fs = self.store().filesystem(); |
| let mut transaction = fs |
| .clone() |
| .new_transaction( |
| lock_keys![LockKey::object( |
| self.store().store_object_id(), |
| self.directory.object_id() |
| )], |
| Options { borrow_metadata_space: true, ..Default::default() }, |
| ) |
| .await |
| .map_err(map_to_status)?; |
| self.directory |
| .update_attributes(&mut transaction, Some(&attributes), 0, Some(Timestamp::now())) |
| .await |
| .map_err(map_to_status)?; |
| transaction.commit().await.map_err(map_to_status)?; |
| Ok(()) |
| } |
| |
| async fn sync(&self) -> Result<(), zx::Status> { |
| // FDIO implements `syncfs` by calling sync on a directory, so replicate that behaviour. |
| self.volume() |
| .store() |
| .filesystem() |
| .sync(SyncOptions { flush_device: true, ..Default::default() }) |
| .await |
| .map_err(map_to_status) |
| } |
| |
| async fn rename( |
| self: Arc<Self>, |
| src_dir: Arc<dyn MutableDirectory>, |
| src_name: Path, |
| dst_name: Path, |
| ) -> Result<(), zx::Status> { |
| if !src_name.is_single_component() || !dst_name.is_single_component() { |
| return Err(zx::Status::INVALID_ARGS); |
| } |
| let (src, dst) = (src_name.peek().unwrap(), dst_name.peek().unwrap()); |
| let src_dir = |
| src_dir.into_any().downcast::<FxDirectory>().map_err(|_| Err(zx::Status::NOT_DIR))?; |
| |
| // Acquire the transaction that locks |src_dir|, |src_name|, |self|, and |dst_name| if they |
| // exist, and also the ID and type of dst and src. |
| let replace_context = self |
| .directory |
| .acquire_context_for_replace(Some((src_dir.directory(), src)), dst, false) |
| .await |
| .map_err(map_to_status)?; |
| let mut transaction = replace_context.transaction; |
| |
| if self.is_deleted() { |
| return Err(zx::Status::NOT_FOUND); |
| } |
| |
| let (moved_id, moved_descriptor) = |
| replace_context.src_id_and_descriptor.ok_or(zx::Status::NOT_FOUND)?; |
| |
| // Make sure the dst path is compatible with the moved node. |
| if let ObjectDescriptor::File = moved_descriptor { |
| if src_name.is_dir() || dst_name.is_dir() { |
| return Err(zx::Status::NOT_DIR); |
| } |
| } |
| |
| // Now that we've ensured that the dst path is compatible with the moved node, we can check |
| // for the trivial case. |
| if src_dir.object_id() == self.object_id() && src == dst { |
| return Ok(()); |
| } |
| |
| if let Some((_, dst_descriptor)) = replace_context.dst_id_and_descriptor.as_ref() { |
| // dst is being overwritten; make sure it's a file iff src is. |
| match (&moved_descriptor, dst_descriptor) { |
| (ObjectDescriptor::Directory, ObjectDescriptor::Directory) => {} |
| ( |
| ObjectDescriptor::File | ObjectDescriptor::Symlink, |
| ObjectDescriptor::File | ObjectDescriptor::Symlink, |
| ) => {} |
| (ObjectDescriptor::Directory, _) => return Err(zx::Status::NOT_DIR), |
| (ObjectDescriptor::File | ObjectDescriptor::Symlink, _) => { |
| return Err(zx::Status::NOT_FILE) |
| } |
| _ => return Err(zx::Status::IO_DATA_INTEGRITY), |
| } |
| } |
| |
| let moved_node = src_dir |
| .volume() |
| .get_or_load_node(moved_id, moved_descriptor.clone(), Some(src_dir.clone())) |
| .await |
| .map_err(map_to_status)?; |
| |
| if let ObjectDescriptor::Directory = moved_descriptor { |
| // Lastly, ensure that self isn't a (transitive) child of the moved node. |
| let mut node_opt = Some(self.clone()); |
| while let Some(node) = node_opt { |
| if node.object_id() == moved_node.object_id() { |
| return Err(zx::Status::INVALID_ARGS); |
| } |
| node_opt = node.parent(); |
| } |
| } |
| |
| let replace_result = directory::replace_child( |
| &mut transaction, |
| Some((src_dir.directory(), src)), |
| (self.directory(), dst), |
| ) |
| .await |
| .map_err(map_to_status)?; |
| |
| transaction |
| .commit_with_callback(|_| { |
| moved_node.set_parent(self.clone()); |
| src_dir.did_remove(src); |
| |
| match replace_result { |
| ReplacedChild::None => {} |
| ReplacedChild::ObjectWithRemainingLinks(..) | ReplacedChild::Object(_) => { |
| self.did_remove(dst); |
| } |
| ReplacedChild::Directory(id) => { |
| self.did_remove(dst); |
| self.volume().mark_directory_deleted(id); |
| } |
| } |
| self.did_add(dst, Some(moved_node)); |
| }) |
| .await |
| .map_err(map_to_status)?; |
| |
| if let ReplacedChild::Object(id) = replace_result { |
| self.volume().maybe_purge_file(id).await.map_err(map_to_status)?; |
| } |
| Ok(()) |
| } |
| |
| async fn create_symlink( |
| &self, |
| name: String, |
| target: Vec<u8>, |
| connection: Option<ServerEnd<fio::SymlinkMarker>>, |
| ) -> Result<(), zx::Status> { |
| let store = self.store(); |
| let dir = &self.directory; |
| let keys = lock_keys![LockKey::object(store.store_object_id(), dir.object_id())]; |
| let fs = store.filesystem(); |
| let mut transaction = |
| fs.new_transaction(keys, Options::default()).await.map_err(map_to_status)?; |
| if dir.lookup(&name).await.map_err(map_to_status)?.is_some() { |
| return Err(zx::Status::ALREADY_EXISTS); |
| } |
| let object_id = |
| dir.create_symlink(&mut transaction, &target, &name).await.map_err(map_to_status)?; |
| if let Some(connection) = connection { |
| if let GetResult::Placeholder(p) = self.volume().cache().get_or_reserve(object_id).await |
| { |
| transaction |
| .commit_with_callback(|_| { |
| let node = Arc::new(FxSymlink::new(self.volume().clone(), object_id)); |
| p.commit(&(node.clone() as Arc<dyn FxNode>)); |
| let scope = self.volume().scope(); |
| scope.spawn( |
| symlink::Connection::new(scope.clone(), node) |
| .run(fio::OpenFlags::RIGHT_READABLE.to_object_request(connection)), |
| ); |
| }) |
| .await |
| } else { |
| // The node already exists in the cache which could only happen if the filesystem is |
| // corrupt. |
| return Err(zx::Status::IO_DATA_INTEGRITY); |
| } |
| } else { |
| transaction.commit().await.map(|_| ()) |
| } |
| .map_err(map_to_status) |
| } |
| |
| async fn list_extended_attributes(&self) -> Result<Vec<Vec<u8>>, zx::Status> { |
| self.directory.list_extended_attributes().await.map_err(map_to_status) |
| } |
| |
| async fn get_extended_attribute(&self, name: Vec<u8>) -> Result<Vec<u8>, zx::Status> { |
| self.directory.get_extended_attribute(name).await.map_err(map_to_status) |
| } |
| |
| async fn set_extended_attribute( |
| &self, |
| name: Vec<u8>, |
| value: Vec<u8>, |
| mode: fio::SetExtendedAttributeMode, |
| ) -> Result<(), zx::Status> { |
| self.directory.set_extended_attribute(name, value, mode.into()).await.map_err(map_to_status) |
| } |
| |
| async fn remove_extended_attribute(&self, name: Vec<u8>) -> Result<(), zx::Status> { |
| self.directory.remove_extended_attribute(name).await.map_err(map_to_status) |
| } |
| } |
| |
| impl DirectoryEntry for FxDirectory { |
| fn entry_info(&self) -> EntryInfo { |
| EntryInfo::new(self.object_id(), fio::DirentType::Directory) |
| } |
| |
| fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), zx::Status> { |
| request.open_dir(self) |
| } |
| } |
| |
| #[async_trait] |
| impl vfs::node::Node for FxDirectory { |
| async fn get_attrs(&self) -> Result<fio::NodeAttributes, zx::Status> { |
| let props = self.directory.get_properties().await.map_err(map_to_status)?; |
| Ok(fio::NodeAttributes { |
| mode: fio::MODE_TYPE_DIRECTORY |
| | rights_to_posix_mode_bits(/*r*/ true, /*w*/ true, /*x*/ false), |
| id: self.directory.object_id(), |
| content_size: props.data_attribute_size, |
| storage_size: props.allocated_size, |
| // +1 for the '.' reference, and 1 for each sub-directory. |
| link_count: props.refs + 1 + props.sub_dirs, |
| creation_time: props.creation_time.as_nanos(), |
| modification_time: props.modification_time.as_nanos(), |
| }) |
| } |
| |
| async fn get_attributes( |
| &self, |
| requested_attributes: fio::NodeAttributesQuery, |
| ) -> Result<fio::NodeAttributes2, zx::Status> { |
| let props = self.directory.get_properties().await.map_err(map_to_status)?; |
| Ok(attributes!( |
| requested_attributes, |
| Mutable { |
| creation_time: props.creation_time.as_nanos(), |
| modification_time: props.modification_time.as_nanos(), |
| access_time: props.access_time.as_nanos(), |
| mode: props.posix_attributes.map(|a| a.mode), |
| uid: props.posix_attributes.map(|a| a.uid), |
| gid: props.posix_attributes.map(|a| a.gid), |
| rdev: props.posix_attributes.map(|a| a.rdev), |
| }, |
| Immutable { |
| protocols: fio::NodeProtocolKinds::DIRECTORY, |
| abilities: fio::Operations::GET_ATTRIBUTES |
| | fio::Operations::UPDATE_ATTRIBUTES |
| | fio::Operations::ENUMERATE |
| | fio::Operations::TRAVERSE |
| | fio::Operations::MODIFY_DIRECTORY, |
| content_size: props.data_attribute_size, |
| storage_size: props.allocated_size, |
| link_count: props.refs + 1 + props.sub_dirs, |
| id: self.directory.object_id(), |
| change_time: props.change_time.as_nanos(), |
| verity_enabled: false, |
| } |
| )) |
| } |
| |
| fn query_filesystem(&self) -> Result<fio::FilesystemInfo, zx::Status> { |
| let store = self.directory.store(); |
| Ok(info_to_filesystem_info( |
| store.filesystem().get_info(), |
| store.filesystem().block_size(), |
| store.object_count(), |
| self.volume().id(), |
| )) |
| } |
| } |
| |
| #[async_trait] |
| impl VfsDirectory for FxDirectory { |
| fn open( |
| self: Arc<Self>, |
| _scope: ExecutionScope, |
| flags: fio::OpenFlags, |
| path: Path, |
| server_end: ServerEnd<fio::NodeMarker>, |
| ) { |
| // Ignore the provided scope which might be for the parent pseudo filesystem and use the |
| // volume's scope instead. |
| let scope = self.volume().scope().clone(); |
| flags.to_object_request(server_end).spawn(&scope.clone(), move |object_request| { |
| Box::pin(async move { |
| let node = self.lookup(&flags, path).await.map_err(map_to_status)?; |
| if node.is::<FxDirectory>() { |
| object_request.create_connection( |
| scope, |
| node.downcast::<FxDirectory>().unwrap_or_else(|_| unreachable!()).take(), |
| flags, |
| MutableConnection::create, |
| ) |
| } else if node.is::<FxFile>() { |
| let node = node.downcast::<FxFile>().unwrap_or_else(|_| unreachable!()); |
| if flags.contains(fio::OpenFlags::RIGHT_WRITABLE) && node.verified_file() { |
| tracing::error!( |
| "Tried to open a verified file with the RIGHT_WRITABLE flag." |
| ); |
| return Err(zx::Status::NOT_SUPPORTED); |
| } |
| if flags.contains(fio::OpenFlags::BLOCK_DEVICE) { |
| if node.verified_file() { |
| tracing::error!("Tried to expose a verified file as a block device."); |
| return Err(zx::Status::NOT_SUPPORTED); |
| } |
| let mut server = |
| BlockServer::new(node, scope, object_request.take().into_channel()); |
| Ok(async move { |
| let _ = server.run().await; |
| } |
| .boxed()) |
| } else { |
| FxFile::create_connection_async(node, scope, flags, object_request) |
| } |
| } else if node.is::<FxSymlink>() { |
| let node = node.downcast::<FxSymlink>().unwrap_or_else(|_| unreachable!()); |
| object_request.create_connection( |
| scope.clone(), |
| node.take(), |
| flags, |
| |scope, symlink, protocols, object_request| { |
| symlink::Connection::create(scope, symlink, &protocols, object_request) |
| }, |
| ) |
| } else { |
| unreachable!(); |
| } |
| }) |
| }); |
| } |
| |
| fn open2( |
| self: Arc<Self>, |
| _scope: ExecutionScope, |
| path: Path, |
| protocols: fio::ConnectionProtocols, |
| object_request: ObjectRequestRef<'_>, |
| ) -> Result<(), zx::Status> { |
| // Ignore the provided scope which might be for the parent pseudo filesystem and use the |
| // volume's scope instead. |
| let scope = self.volume().scope().clone(); |
| object_request.take().spawn(&scope.clone(), move |object_request| { |
| Box::pin(async move { |
| let node = self.lookup(&protocols, path).await.map_err(map_to_status)?; |
| if node.is::<FxDirectory>() { |
| object_request.create_connection( |
| scope, |
| node.downcast::<FxDirectory>().unwrap_or_else(|_| unreachable!()).take(), |
| protocols, |
| MutableConnection::create, |
| ) |
| } else if node.is::<FxFile>() { |
| let node = node.downcast::<FxFile>().unwrap_or_else(|_| unreachable!()); |
| FxFile::create_connection_async(node, scope, protocols, object_request) |
| } else if node.is::<FxSymlink>() { |
| let node = node.downcast::<FxSymlink>().unwrap_or_else(|_| unreachable!()); |
| object_request.create_connection( |
| scope.clone(), |
| node.take(), |
| protocols, |
| |scope, symlink, protocols, object_request| { |
| symlink::Connection::create(scope, symlink, &protocols, object_request) |
| }, |
| ) |
| } else { |
| unreachable!(); |
| } |
| }) |
| }); |
| Ok(()) |
| } |
| |
| async fn read_dirents<'a>( |
| &'a self, |
| pos: &'a TraversalPosition, |
| mut sink: Box<dyn Sink>, |
| ) -> Result<(TraversalPosition, Box<dyn dirents_sink::Sealed>), zx::Status> { |
| if let TraversalPosition::End = pos { |
| return Ok((TraversalPosition::End, sink.seal())); |
| } else if let TraversalPosition::Index(_) = pos { |
| // The VFS should never send this to us, since we never return it here. |
| return Err(zx::Status::BAD_STATE); |
| } |
| |
| let store = self.store(); |
| let fs = store.filesystem(); |
| let _read_guard = fs |
| .lock_manager() |
| .read_lock(lock_keys![LockKey::object(store.store_object_id(), self.object_id())]) |
| .await; |
| if self.is_deleted() { |
| return Ok((TraversalPosition::End, sink.seal())); |
| } |
| |
| let starting_name = match pos { |
| TraversalPosition::Start => { |
| // Synthesize a "." entry if we're at the start of the stream. |
| match sink |
| .append(&EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory), ".") |
| { |
| AppendResult::Ok(new_sink) => sink = new_sink, |
| AppendResult::Sealed(sealed) => { |
| // Note that the VFS should have yielded an error since the first entry |
| // didn't fit. This is defensive in case the VFS' behaviour changes, so that |
| // we return a reasonable value. |
| return Ok((TraversalPosition::Start, sealed)); |
| } |
| } |
| "" |
| } |
| TraversalPosition::Name(name) => name, |
| _ => unreachable!(), |
| }; |
| |
| let layer_set = self.store().tree().layer_set(); |
| let mut merger = layer_set.merger(); |
| let mut iter = |
| self.directory.iter_from(&mut merger, starting_name).await.map_err(map_to_status)?; |
| while let Some((name, object_id, object_descriptor)) = iter.get() { |
| let entry_type = match object_descriptor { |
| ObjectDescriptor::File => fio::DirentType::File, |
| ObjectDescriptor::Directory => fio::DirentType::Directory, |
| ObjectDescriptor::Symlink => fio::DirentType::Symlink, |
| ObjectDescriptor::Volume => return Err(zx::Status::IO_DATA_INTEGRITY), |
| }; |
| let info = EntryInfo::new(object_id, entry_type); |
| match sink.append(&info, name) { |
| AppendResult::Ok(new_sink) => sink = new_sink, |
| AppendResult::Sealed(sealed) => { |
| // We did *not* add the current entry to the sink (e.g. because the sink was |
| // full), so mark |name| as the next position so that it's the first entry we |
| // process on a subsequent call of read_dirents. |
| // Note that entries inserted between the previous entry and this entry before |
| // the next call to read_dirents would not be included in the results (but |
| // there's no requirement to include them anyways). |
| return Ok((TraversalPosition::Name(name.to_string()), sealed)); |
| } |
| } |
| iter.advance().await.map_err(map_to_status)?; |
| } |
| Ok((TraversalPosition::End, sink.seal())) |
| } |
| |
| fn register_watcher( |
| self: Arc<Self>, |
| scope: ExecutionScope, |
| mask: fio::WatchMask, |
| watcher: DirectoryWatcher, |
| ) -> Result<(), zx::Status> { |
| let controller = |
| self.watchers.lock().unwrap().add(scope.clone(), self.clone(), mask, watcher); |
| if mask.contains(fio::WatchMask::EXISTING) && !self.is_deleted() { |
| scope.spawn(async move { |
| let layer_set = self.store().tree().layer_set(); |
| let mut merger = layer_set.merger(); |
| let mut iter = match self.directory.iter_from(&mut merger, "").await { |
| Ok(iter) => iter, |
| Err(e) => { |
| error!(error = ?e, "Failed to iterate directory for watch",); |
| // TODO(https://fxbug.dev/42178164): This really should close the watcher connection |
| // with an epitaph so that the watcher knows. |
| return; |
| } |
| }; |
| // TODO(https://fxbug.dev/42178165): It is possible that we'll duplicate entries that are added |
| // as we iterate over directories. I suspect fixing this might be non-trivial. |
| controller.send_event(&mut SingleNameEventProducer::existing(".")); |
| while let Some((name, _, _)) = iter.get() { |
| controller.send_event(&mut SingleNameEventProducer::existing(name)); |
| if let Err(e) = iter.advance().await { |
| error!(error = ?e, "Failed to iterate directory for watch",); |
| return; |
| } |
| } |
| controller.send_event(&mut SingleNameEventProducer::idle()); |
| }); |
| } |
| Ok(()) |
| } |
| |
| fn unregister_watcher(self: Arc<Self>, key: usize) { |
| self.watchers.lock().unwrap().remove(key); |
| } |
| } |
| |
| impl From<Directory<FxVolume>> for FxDirectory { |
| fn from(dir: Directory<FxVolume>) -> Self { |
| Self::new(None, dir) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use { |
| crate::{ |
| directory::FxDirectory, |
| file::FxFile, |
| fuchsia::testing::{ |
| close_dir_checked, close_file_checked, open2_dir, open2_dir_checked, open_dir, |
| open_dir_checked, open_file, open_file_checked, TestFixture, TestFixtureOptions, |
| }, |
| }, |
| assert_matches::assert_matches, |
| fidl::endpoints::{create_proxy, ClientEnd, Proxy, ServerEnd}, |
| fidl_fuchsia_io as fio, fuchsia_async as fasync, |
| fuchsia_fs::{ |
| directory::{DirEntry, DirentKind}, |
| file, |
| }, |
| fuchsia_zircon as zx, |
| futures::StreamExt, |
| fxfs::object_store::Timestamp, |
| rand::Rng, |
| std::{ |
| os::fd::AsRawFd, |
| sync::{ |
| atomic::{AtomicU64, Ordering}, |
| Arc, |
| }, |
| time::Duration, |
| }, |
| storage_device::{fake_device::FakeDevice, DeviceHolder}, |
| vfs::{common::rights_to_posix_mode_bits, node::Node, path::Path}, |
| }; |
| |
| #[fuchsia::test] |
| async fn test_open_root_dir() { |
| let fixture = TestFixture::new().await; |
| let root = fixture.root(); |
| let _: Vec<_> = root.query().await.expect("query failed"); |
| fixture.close().await; |
| } |
| |
| #[fuchsia::test] |
| async fn test_create_dir_persists() { |
| let mut device = DeviceHolder::new(FakeDevice::new(8192, 512)); |
| for i in 0..2 { |
| let fixture = TestFixture::open( |
| device, |
| TestFixtureOptions { |
| format: i == 0, |
| encrypted: true, |
| as_blob: false, |
| serve_volume: false, |
| }, |
| ) |
| .await; |
| let root = fixture.root(); |
| |
| let flags = if i == 0 { |
| fio::OpenFlags::CREATE | fio::OpenFlags::RIGHT_READABLE |
| } else { |
| fio::OpenFlags::RIGHT_READABLE |
| }; |
| let dir = open_dir_checked(&root, flags | fio::OpenFlags::DIRECTORY, "foo").await; |
| close_dir_checked(dir).await; |
| |
| device = fixture.close().await; |
| } |
| } |
| |
| #[fuchsia::test] |
| async fn test_open_nonexistent_file() { |
| let fixture = TestFixture::new().await; |
| let root = fixture.root(); |
| |
| assert_eq!( |
| open_file(&root, fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::NOT_DIRECTORY, "foo") |
| .await |
| .expect_err("Open succeeded") |
| .root_cause() |
| .downcast_ref::<zx::Status>() |
| .expect("No status"), |
| &zx::Status::NOT_FOUND, |
| ); |
| |
| fixture.close().await; |
| } |
| |
| #[fuchsia::test] |
| async fn test_create_file() { |
| let fixture = TestFixture::new().await; |
| let root = fixture.root(); |
| |
| let f = open_file_checked( |
| &root, |
| fio::OpenFlags::CREATE | fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::NOT_DIRECTORY, |
| "foo", |
| ) |
| .await; |
| close_file_checked(f).await; |
| |
| let f = open_file_checked( |
| &root, |
| fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::NOT_DIRECTORY, |
| "foo", |
| ) |
| .await; |
| close_file_checked(f).await; |
| |
| fixture.close().await; |
| } |
| |
| #[fuchsia::test] |
| async fn test_create_dir_nested() { |
| let fixture = TestFixture::new().await; |
| let root = fixture.root(); |
| |
| let d = open_dir_checked( |
| &root, |
| fio::OpenFlags::CREATE |
| | fio::OpenFlags::RIGHT_READABLE |
| | fio::OpenFlags::RIGHT_WRITABLE |
| | fio::OpenFlags::DIRECTORY, |
| "foo", |
| ) |
| .await; |
| close_dir_checked(d).await; |
| |
| let d = open_dir_checked( |
| &root, |
| fio::OpenFlags::CREATE | fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DIRECTORY, |
| "foo/bar", |
| ) |
| .await; |
| close_dir_checked(d).await; |
| |
| let d = open_dir_checked( |
| &root, |
| fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DIRECTORY, |
| "foo/bar", |
| ) |
| .await; |
| close_dir_checked(d).await; |
| |
| fixture.close().await; |
| } |
| |
| #[fuchsia::test] |
| async fn test_strict_create_file_fails_if_present() { |
| let fixture = TestFixture::new().await; |
| let root = fixture.root(); |
| |
| let f = open_file_checked( |
| &root, |
| fio::OpenFlags::CREATE |
| | fio::OpenFlags::CREATE_IF_ABSENT |
| | fio::OpenFlags::RIGHT_READABLE |
| | fio::OpenFlags::NOT_DIRECTORY, |
| "foo", |
| ) |
| .await; |
| close_file_checked(f).await; |
| |
| assert_eq!( |
| open_file( |
| &root, |
| fio::OpenFlags::CREATE |
| | fio::OpenFlags::CREATE_IF_ABSENT |
| | fio::OpenFlags::RIGHT_READABLE |
| | fio::OpenFlags::NOT_DIRECTORY, |
| "foo", |
| ) |
| .await |
| .expect_err("Open succeeded") |
| .root_cause() |
| .downcast_ref::<zx::Status>() |
| .expect("No status"), |
| &zx::Status::ALREADY_EXISTS, |
| ); |
| |
| fixture.close().await; |
| } |
| |
| #[fuchsia::test] |
| async fn test_unlink_file_with_no_refs_immediately_freed() { |
| let fixture = TestFixture::new().await; |
| let root = fixture.root(); |
| |
| let file = open_file_checked( |
| &root, |
| fio::OpenFlags::CREATE |
| | fio::OpenFlags::RIGHT_READABLE |
| | fio::OpenFlags::RIGHT_WRITABLE |
| | fio::OpenFlags::NOT_DIRECTORY, |
| "foo", |
| ) |
| .await; |
| |
| // Fill up the file with a lot of data, so we can verify that the extents are freed. |
| let buf = vec![0xaa as u8; 512]; |
| loop { |
| match file::write(&file, buf.as_slice()).await { |
| Ok(_) => {} |
| Err(e) => { |
| if let fuchsia_fs::file::WriteError::WriteError(status) = e { |
| if status == zx::Status::NO_SPACE { |
| break; |
| } |
| } |
| panic!("Unexpected write error {:?}", e); |
| } |
| } |
| } |
| |
| close_file_checked(file).await; |
| |
| root.unlink("foo", &fio::UnlinkOptions::default()) |
| .await |
| .expect("FIDL call failed") |
| .expect("unlink failed"); |
| |
| assert_eq!( |
| open_file(&root, fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::NOT_DIRECTORY, "foo") |
| .await |
| .expect_err("Open succeeded") |
| .root_cause() |
| .downcast_ref::<zx::Status>() |
| .expect("No status"), |
| &zx::Status::NOT_FOUND, |
| ); |
| |
| // Create another file so we can verify that the extents were actually freed. |
| let file = open_file_checked( |
| &root, |
| fio::OpenFlags::CREATE |
| | fio::OpenFlags::RIGHT_READABLE |
| | fio::OpenFlags::RIGHT_WRITABLE |
| | fio::OpenFlags::NOT_DIRECTORY, |
| "bar", |
| ) |
| .await; |
| let buf = vec![0xaa as u8; 8192]; |
| file::write(&file, buf.as_slice()).await.expect("Failed to write new file"); |
| close_file_checked(file).await; |
| |
| fixture.close().await; |
| } |
| |
| #[fuchsia::test] |
| async fn test_unlink_file() { |
| let fixture = TestFixture::new().await; |
| let root = fixture.root(); |
| |
| let file = open_file_checked( |
| &root, |
| fio::OpenFlags::CREATE |
| | fio::OpenFlags::RIGHT_READABLE |
| | fio::OpenFlags::RIGHT_WRITABLE |
| | fio::OpenFlags::NOT_DIRECTORY, |
| "foo", |
| ) |
| .await; |
| close_file_checked(file).await; |
| |
| root.unlink("foo", &fio::UnlinkOptions::default()) |
| .await |
| .expect("FIDL call failed") |
| .expect("unlink failed"); |
| |
| assert_eq!( |
| open_file(&root, fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::NOT_DIRECTORY, "foo") |
| .await |
| .expect_err("Open succeeded") |
| .root_cause() |
| .downcast_ref::<zx::Status>() |
| .expect("No status"), |
| &zx::Status::NOT_FOUND, |
| ); |
| |
| fixture.close().await; |
| } |
| |
| #[fuchsia::test] |
| async fn test_unlink_file_with_active_references() { |
| let fixture = TestFixture::new().await; |
| let root = fixture.root(); |
| |
| let file = open_file_checked( |
| &root, |
| fio::OpenFlags::CREATE |
| | fio::OpenFlags::RIGHT_READABLE |
| | fio::OpenFlags::RIGHT_WRITABLE |
| | fio::OpenFlags::NOT_DIRECTORY, |
| "foo", |
| ) |
| .await; |
| |
| let buf = vec![0xaa as u8; 512]; |
| file::write(&file, buf.as_slice()).await.expect("write failed"); |
| |
| root.unlink("foo", &fio::UnlinkOptions::default()) |
| .await |
| .expect("FIDL call failed") |
| .expect("unlink failed"); |
| |
| // The child should immediately appear unlinked... |
| assert_eq!( |
| open_file(&root, fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::NOT_DIRECTORY, "foo") |
| .await |
| .expect_err("Open succeeded") |
| .root_cause() |
| .downcast_ref::<zx::Status>() |
| .expect("No status"), |
| &zx::Status::NOT_FOUND, |
| ); |
| |
| // But its contents should still be readable from the other handle. |
| file.seek(fio::SeekOrigin::Start, 0) |
| .await |
| .expect("seek failed") |
| .map_err(zx::Status::from_raw) |
| .expect("seek error"); |
| let rbuf = file::read(&file).await.expect("read failed"); |
| assert_eq!(rbuf, buf); |
| close_file_checked(file).await; |
| |
| fixture.close().await; |
| } |
| |
| #[fuchsia::test] |
| async fn test_unlink_dir_with_children_fails() { |
| let fixture = TestFixture::new().await; |
| let root = fixture.root(); |
| |
| let dir = open_dir_checked( |
| &root, |
| fio::OpenFlags::CREATE |
| | fio::OpenFlags::RIGHT_READABLE |
| | fio::OpenFlags::RIGHT_WRITABLE |
| | fio::OpenFlags::DIRECTORY, |
| "foo", |
| ) |
| .await; |
| let f = open_file_checked( |
| &dir, |
| fio::OpenFlags::CREATE | fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DIRECTORY, |
| "bar", |
| ) |
| .await; |
| close_file_checked(f).await; |
| |
| assert_eq!( |
| zx::Status::from_raw( |
| root.unlink("foo", &fio::UnlinkOptions::default()) |
| .await |
| .expect("FIDL call failed") |
| .expect_err("unlink succeeded") |
| ), |
| zx::Status::NOT_EMPTY |
| ); |
| |
| dir.unlink("bar", &fio::UnlinkOptions::default()) |
| .await |
| .expect("FIDL call failed") |
| .expect("unlink failed"); |
| root.unlink("foo", &fio::UnlinkOptions::default()) |
| .await |
| .expect("FIDL call failed") |
| .expect("unlink failed"); |
| |
| close_dir_checked(dir).await; |
| |
| fixture.close().await; |
| } |
| |
| #[fuchsia::test] |
| async fn test_unlink_dir_makes_directory_immutable() { |
| let fixture = TestFixture::new().await; |
| let root = fixture.root(); |
| |
| let dir = open_dir_checked( |
| &root, |
| fio::OpenFlags::CREATE |
| | fio::OpenFlags::RIGHT_READABLE |
| | fio::OpenFlags::RIGHT_WRITABLE |
| | fio::OpenFlags::DIRECTORY, |
| "foo", |
| ) |
| .await; |
| |
| root.unlink("foo", &fio::UnlinkOptions::default()) |
| .await |
| .expect("FIDL call failed") |
| .expect("unlink failed"); |
| |
| assert_eq!( |
| open_file( |
| &dir, |
| fio::OpenFlags::RIGHT_READABLE |
| | fio::OpenFlags::CREATE |
| | fio::OpenFlags::NOT_DIRECTORY, |
| "bar" |
| ) |
| .await |
| .expect_err("Create file succeeded") |
| .root_cause() |
| .downcast_ref::<zx::Status>() |
| .expect("No status"), |
| &zx::Status::ACCESS_DENIED, |
| ); |
| |
| close_dir_checked(dir).await; |
| |
| fixture.close().await; |
| } |
| |
| #[fuchsia::test(threads = 10)] |
| async fn test_unlink_directory_with_children_race() { |
| let fixture = TestFixture::new().await; |
| let root = fixture.root(); |
| |
| const PARENT: &str = "foo"; |
| const CHILD: &str = "bar"; |
| const GRANDCHILD: &str = "baz"; |
| open_dir_checked( |
| &root, |
| fio::OpenFlags::CREATE |
| | fio::OpenFlags::RIGHT_READABLE |
| | fio::OpenFlags::RIGHT_WRITABLE |
| | fio::OpenFlags::DIRECTORY, |
| PARENT, |
| ) |
| .await; |
| |
| let open_parent = || async { |
| open_dir_checked( |
| &root, |
| fio::OpenFlags::RIGHT_READABLE |
| | fio::OpenFlags::RIGHT_WRITABLE |
| | fio::OpenFlags::DIRECTORY, |
| PARENT, |
| ) |
| .await |
| }; |
| let parent = open_parent().await; |
| |
| // Each iteration proceeds as follows: |
| // - Initialize a directory foo/bar/. (This might still be around from the previous |
| // iteration, which is fine.) |
| // - In one task, try to unlink foo/bar/. |
| // - In another task, try to add a file foo/bar/baz. |
| for _ in 0..100 { |
| let d = open_dir_checked( |
| &parent, |
| fio::OpenFlags::CREATE |
| | fio::OpenFlags::RIGHT_READABLE |
| | fio::OpenFlags::RIGHT_WRITABLE |
| | fio::OpenFlags::DIRECTORY, |
| CHILD, |
| ) |
| .await; |
| close_dir_checked(d).await; |
| |
| let parent = open_parent().await; |
| let deleter = fasync::Task::spawn(async move { |
| let wait_time = rand::thread_rng().gen_range(0..5); |
| fasync::Timer::new(Duration::from_millis(wait_time)).await; |
| match parent |
| .unlink(CHILD, &fio::UnlinkOptions::default()) |
| .await |
| .expect("FIDL call failed") |
| .map_err(zx::Status::from_raw) |
| { |
| Ok(()) => {} |
| Err(zx::Status::NOT_EMPTY) => {} |
| Err(e) => panic!("Unexpected status from unlink: {:?}", e), |
| }; |
| close_dir_checked(parent).await; |
| }); |
| |
| let parent = open_parent().await; |
| let writer = fasync::Task::spawn(async move { |
| let child_or = open_dir( |
| &parent, |
| fio::OpenFlags::RIGHT_READABLE |
| | fio::OpenFlags::RIGHT_WRITABLE |
| | fio::OpenFlags::DIRECTORY, |
| CHILD, |
| ) |
| .await; |
| if let Err(e) = &child_or { |
| // The directory was already deleted. |
| assert_eq!( |
| e.root_cause().downcast_ref::<zx::Status>().expect("No status"), |
| &zx::Status::NOT_FOUND |
| ); |
| close_dir_checked(parent).await; |
| return; |
| } |
| let child = child_or.unwrap(); |
| let _: Vec<_> = child.query().await.expect("query failed"); |
| match open_file( |
| &child, |
| fio::OpenFlags::CREATE |
| | fio::OpenFlags::RIGHT_READABLE |
| | fio::OpenFlags::NOT_DIRECTORY, |
| GRANDCHILD, |
| ) |
| .await |
| { |
| Ok(grandchild) => { |
| let _: Vec<_> = grandchild.query().await.expect("query failed"); |
| close_file_checked(grandchild).await; |
| // We added the child before the directory was deleted; go ahead and |
| // clean up. |
| child |
| .unlink(GRANDCHILD, &fio::UnlinkOptions::default()) |
| .await |
| .expect("FIDL call failed") |
| .expect("unlink failed"); |
| } |
| Err(e) => { |
| // The directory started to be deleted before we created a child. |
| // Make sure we get the right error. |
| assert_eq!( |
| e.root_cause().downcast_ref::<zx::Status>().expect("No status"), |
| &zx::Status::ACCESS_DENIED, |
| ); |
| } |
| }; |
| close_dir_checked(child).await; |
| close_dir_checked(parent).await; |
| }); |
| writer.await; |
| deleter.await; |
| } |
| |
| close_dir_checked(parent).await; |
| fixture.close().await; |
| } |
| |
| #[fuchsia::test] |
| async fn test_readdir() { |
| let fixture = TestFixture::new().await; |
| let root = fixture.root(); |
| |
| let open_dir = || { |
| open_dir_checked( |
| &root, |
| fio::OpenFlags::CREATE |
| | fio::OpenFlags::RIGHT_READABLE |
| | fio::OpenFlags::RIGHT_WRITABLE |
| | fio::OpenFlags::DIRECTORY, |
| "foo", |
| ) |
| }; |
| let parent = Arc::new(open_dir().await); |
| |
| let files = ["eenie", "meenie", "minie", "moe"]; |
| for file in &files { |
| let file = open_file_checked( |
| parent.as_ref(), |
| fio::OpenFlags::CREATE | fio::OpenFlags::NOT_DIRECTORY, |
| file, |
| ) |
| .await; |
| close_file_checked(file).await; |
| } |
| let dirs = ["fee", "fi", "fo", "fum"]; |
| for dir in &dirs { |
| let dir = open_dir_checked( |
| parent.as_ref(), |
| fio::OpenFlags::CREATE | fio::OpenFlags::DIRECTORY, |
| dir, |
| ) |
| .await; |
| close_dir_checked(dir).await; |
| } |
| { |
| parent |
| .create_symlink("symlink", b"target", None) |
| .await |
| .expect("FIDL call failed") |
| .expect("create_symlink failed"); |
| } |
| |
| let readdir = |dir: Arc<fio::DirectoryProxy>| async move { |
| let status = dir.rewind().await.expect("FIDL call failed"); |
| zx::Status::ok(status).expect("rewind failed"); |
| let (status, buf) = dir.read_dirents(fio::MAX_BUF).await.expect("FIDL call failed"); |
| zx::Status::ok(status).expect("read_dirents failed"); |
| let mut entries = vec![]; |
| for res in fuchsia_fs::directory::parse_dir_entries(&buf) { |
| entries.push(res.expect("Failed to parse entry")); |
| } |
| entries |
| }; |
| |
| let mut expected_entries = |
| vec![DirEntry { name: ".".to_owned(), kind: DirentKind::Directory }]; |
| expected_entries.extend( |
| files.iter().map(|&name| DirEntry { name: name.to_owned(), kind: DirentKind::File }), |
| ); |
| expected_entries.extend( |
| dirs.iter() |
| .map(|&name| DirEntry { name: name.to_owned(), kind: DirentKind::Directory }), |
| ); |
| expected_entries.push(DirEntry { name: "symlink".to_owned(), kind: DirentKind::Symlink }); |
| expected_entries.sort_unstable(); |
| assert_eq!(expected_entries, readdir(Arc::clone(&parent)).await); |
| |
| // Remove an entry. |
| parent |
| .unlink(&expected_entries.pop().unwrap().name, &fio::UnlinkOptions::default()) |
| .await |
| .expect("FIDL call failed") |
| .expect("unlink failed"); |
| |
| assert_eq!(expected_entries, readdir(Arc::clone(&parent)).await); |
| |
| close_dir_checked(Arc::try_unwrap(parent).unwrap()).await; |
| fixture.close().await; |
| } |
| |
| #[fuchsia::test] |
| async fn test_readdir_multiple_calls() { |
| let fixture = TestFixture::new().await; |
| let root = fixture.root(); |
| |
| let parent = open_dir_checked( |
| &root, |
| fio::OpenFlags::CREATE |
| | fio::OpenFlags::RIGHT_READABLE |
| | fio::OpenFlags::RIGHT_WRITABLE |
| | fio::OpenFlags::DIRECTORY, |
| "foo", |
| ) |
| .await; |
| |
| let files = ["a", "b"]; |
| for file in &files { |
| let file = open_file_checked( |
| &parent, |
| fio::OpenFlags::CREATE | fio::OpenFlags::NOT_DIRECTORY, |
| file, |
| ) |
| .await; |
| close_file_checked(file).await; |
| } |
| |
| // TODO(https://fxbug.dev/42177353): Magic number; can we get this from fuchsia.io? |
| const DIRENT_SIZE: u64 = 10; // inode: u64, size: u8, kind: u8 |
| const BUFFER_SIZE: u64 = DIRENT_SIZE + 2; // Enough space for a 2-byte name. |
| |
| let parse_entries = |buf| { |
| let mut entries = vec![]; |
| for res in fuchsia_fs::directory::parse_dir_entries(buf) { |
| entries.push(res.expect("Failed to parse entry")); |
| } |
| entries |
| }; |
| |
| let expected_entries = vec![ |
| DirEntry { name: ".".to_owned(), kind: DirentKind::Directory }, |
| DirEntry { name: "a".to_owned(), kind: DirentKind::File }, |
| ]; |
| let (status, buf) = parent.read_dirents(2 * BUFFER_SIZE).await.expect("FIDL call failed"); |
| zx::Status::ok(status).expect("read_dirents failed"); |
| assert_eq!(expected_entries, parse_entries(&buf)); |
| |
| let expected_entries = vec![DirEntry { name: "b".to_owned(), kind: DirentKind::File }]; |
| let (status, buf) = parent.read_dirents(2 * BUFFER_SIZE).await.expect("FIDL call failed"); |
| zx::Status::ok(status).expect("read_dirents failed"); |
| assert_eq!(expected_entries, parse_entries(&buf)); |
| |
| // Subsequent calls yield nothing. |
| let expected_entries: Vec<DirEntry> = vec![]; |
| let (status, buf) = parent.read_dirents(2 * BUFFER_SIZE).await.expect("FIDL call failed"); |
| zx::Status::ok(status).expect("read_dirents failed"); |
| assert_eq!(expected_entries, parse_entries(&buf)); |
| |
| close_dir_checked(parent).await; |
| fixture.close().await; |
| } |
| |
| #[fuchsia::test] |
| async fn test_set_attrs() { |
| let fixture = TestFixture::new().await; |
| let root = fixture.root(); |
| |
| let dir = open_dir_checked( |
| &root, |
| fio::OpenFlags::CREATE |
| | fio::OpenFlags::RIGHT_READABLE |
| | fio::OpenFlags::RIGHT_WRITABLE |
| | fio::OpenFlags::DIRECTORY, |
| "foo", |
| ) |
| .await; |
| |
| let (status, initial_attrs) = dir.get_attr().await.expect("FIDL call failed"); |
| zx::Status::ok(status).expect("get_attr failed"); |
| |
| let crtime = initial_attrs.creation_time ^ 1u64; |
| let mtime = initial_attrs.modification_time ^ 1u64; |
| |
| let mut attrs = initial_attrs.clone(); |
| attrs.creation_time = crtime; |
| attrs.modification_time = mtime; |
| let status = dir |
| .set_attr(fio::NodeAttributeFlags::CREATION_TIME, &attrs) |
| .await |
| .expect("FIDL call failed"); |
| zx::Status::ok(status).expect("set_attr failed"); |
| |
| let mut expected_attrs = initial_attrs.clone(); |
| expected_attrs.creation_time = crtime; // Only crtime is updated so far. |
| let (status, attrs) = dir.get_attr().await.expect("FIDL call failed"); |
| zx::Status::ok(status).expect("get_attr failed"); |
| assert_eq!(expected_attrs, attrs); |
| |
| let mut attrs = initial_attrs.clone(); |
| attrs.creation_time = 0u64; // This should be ignored since we don't set the flag. |
| attrs.modification_time = mtime; |
| let status = dir |
| .set_attr(fio::NodeAttributeFlags::MODIFICATION_TIME, &attrs) |
| .await |
| .expect("FIDL call failed"); |
| zx::Status::ok(status).expect("set_attr failed"); |
| |
| let mut expected_attrs = initial_attrs.clone(); |
| expected_attrs.creation_time = crtime; |
| expected_attrs.modification_time = mtime; |
| let (status, attrs) = dir.get_attr().await.expect("FIDL call failed"); |
| zx::Status::ok(status).expect("get_attr failed"); |
| assert_eq!(expected_attrs, attrs); |
| |
| close_dir_checked(dir).await; |
| fixture.close().await; |
| } |
| |
| #[fuchsia::test] |
| async fn test_symlink() { |
| let fixture = TestFixture::new().await; |
| |
| { |
| let root = fixture.root(); |
| |
| root.create_symlink("symlink", b"target", None) |
| .await |
| .expect("FIDL call failed") |
| .expect("create_symlink failed"); |
| |
| let (proxy, server_end) = |
| create_proxy::<fio::SymlinkMarker>().expect("create_proxy failed"); |
| root.open( |
| fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE, |
| fio::ModeType::empty(), |
| "symlink", |
| ServerEnd::new(server_end.into_channel()), |
| ) |
| .expect("open failed"); |
| |
| let on_open = proxy |
| .take_event_stream() |
| .next() |
| .await |
| .expect("missing OnOpen event") |
| .expect("failed to read event") |
| .into_on_open_(); |
| |
| if let Some((0, Some(node_info))) = on_open { |
| assert_matches!( |
| *node_info, |
| fio::NodeInfoDeprecated::Symlink(fio::SymlinkObject { target, .. }) |
| if target == b"target" |
| ); |
| } else { |
| panic!("Unexpected on_open {on_open:?}"); |
| } |
| |
| let (proxy, server_end) = |
| create_proxy::<fio::SymlinkMarker>().expect("create_proxy failed"); |
| root.create_symlink("symlink2", b"target2", Some(server_end)) |
| .await |
| .expect("FIDL call failed") |
| .expect("create_symlink failed"); |
| |
| let node_info = proxy.describe().await.expect("FIDL call failed"); |
| assert_matches!( |
| node_info, |
| fio::SymlinkInfo { target: Some(target), .. } if target == b"target2" |
| ); |
| |
| // Unlink the second symlink. |
| root.unlink("symlink2", &fio::UnlinkOptions::default()) |
| .await |
| .expect("FIDL call failed") |
| .expect("unlink failed"); |
| |
| // Rename over the first symlink. |
| open_file_checked( |
| &root, |
| fio::OpenFlags::CREATE |
| | fio::OpenFlags::RIGHT_READABLE |
| | fio::OpenFlags::RIGHT_WRITABLE, |
| "target", |
| ) |
| .await; |
| let (status, dst_token) = root.get_token().await.expect("FIDL call failed"); |
| zx::Status::ok(status).expect("get_token failed"); |
| root.rename("target", zx::Event::from(dst_token.unwrap()), "symlink") |
| .await |
| .expect("FIDL call failed") |
| .expect("rename failed"); |
| |
| let (status, _) = proxy.get_attr().await.expect("FIDL call failed"); |
| assert_eq!(zx::Status::from_raw(status), zx::Status::NOT_FOUND); |
| assert_matches!( |
| proxy.describe().await, |
| Err(fidl::Error::ClientChannelClosed { status: zx::Status::NOT_FOUND, .. }) |
| ); |
| } |
| |
| fixture.close().await; |
| } |
| |
| #[fuchsia::test] |
| async fn test_hard_link_to_symlink() { |
| let fixture = TestFixture::new().await; |
| |
| { |
| let root = fixture.root(); |
| |
| root.create_symlink("symlink", b"target", None) |
| .await |
| .expect("FIDL call failed") |
| .expect("create_symlink failed"); |
| |
| async fn open_symlink(root: &fio::DirectoryProxy, path: &str) -> fio::SymlinkProxy { |
| let (proxy, server_end) = |
| create_proxy::<fio::SymlinkMarker>().expect("create_proxy failed"); |
| root.open( |
| fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE, |
| fio::ModeType::empty(), |
| path, |
| ServerEnd::new(server_end.into_channel()), |
| ) |
| .expect("open failed"); |
| |
| let on_open = proxy |
| .take_event_stream() |
| .next() |
| .await |
| .expect("missing OnOpen event") |
| .expect("failed to read event") |
| .into_on_open_(); |
| |
| if let Some((0, Some(node_info))) = on_open { |
| assert_matches!( |
| *node_info, |
| fio::NodeInfoDeprecated::Symlink(fio::SymlinkObject { target, .. }) |
| if target == b"target" |
| ); |
| } else { |
| panic!("Unexpected on_open {on_open:?}"); |
| } |
| |
| proxy |
| } |
| |
| let proxy = open_symlink(&root, "symlink").await; |
| |
| let (status, dst_token) = root.get_token().await.expect("FIDL call failed"); |
| zx::Status::ok(status).expect("get_token failed"); |
| proxy |
| .link_into(zx::Event::from(dst_token.unwrap()), "symlink2") |
| .await |
| .expect("link_into (FIDL) failed") |
| .expect("link_into failed"); |
| |
| open_symlink(&root, "symlink2").await; |
| } |
| |
| fixture.close().await; |
| } |
| |
| #[fuchsia::test] |
| async fn test_symlink_stat() { |
| let fixture = TestFixture::new().await; |
| |
| { |
| let root = fixture.root(); |
| |
| root.create_symlink("symlink", b"target", None) |
| .await |
| .expect("FIDL call failed") |
| .expect("create_symlink failed"); |
| |
| let root = fuchsia_fs::directory::clone_no_describe(root, None) |
| .expect("clone_no_describe failed"); |
| |
| fasync::unblock(|| { |
| let root: std::os::fd::OwnedFd = |
| fdio::create_fd(root.into_channel().unwrap().into_zx_channel().into()) |
| .expect("create_fd failed"); |
| |
| let mut stat: libc::stat = unsafe { std::mem::zeroed() }; |
| let name = std::ffi::CString::new("symlink").expect("CString::new failed"); |
| assert_eq!( |
| unsafe { libc::fstatat(root.as_raw_fd(), name.as_ptr(), &mut stat, 0) }, |
| 0 |
| ); |
| }) |
| .await; |
| } |
| |
| fixture.close().await; |
| } |
| |
| #[fuchsia::test] |
| async fn test_remove_dir_all_with_symlink() { |
| // This test makes sure that remove_dir_all works. At time of writing remove_dir_all uses |
| // d_type from the directory entry to determine whether or not to recurse into directories, |
| // so this tests that is working correctly. |
| |
| let fixture = TestFixture::new().await; |
| |
| { |
| let root = fixture.root(); |
| |
| let dir = open_dir_checked( |
| &root, |
| fio::OpenFlags::CREATE |
| | fio::OpenFlags::RIGHT_READABLE |
| | fio::OpenFlags::RIGHT_WRITABLE |
| | fio::OpenFlags::DIRECTORY, |
| "dir", |
| ) |
| .await; |
| |
| dir.create_symlink("symlink", b"target", None) |
| .await |
| .expect("FIDL call failed") |
| .expect("create_symlink failed"); |
| |
| let namespace = fdio::Namespace::installed().expect("Unable to get namespace"); |
| static COUNTER: AtomicU64 = AtomicU64::new(0); |
| let path = format!("/test_symlink_stat.{}", COUNTER.fetch_add(1, Ordering::Relaxed)); |
| let root = fuchsia_fs::directory::clone_no_describe(root, None) |
| .expect("clone_no_describe failed"); |
| namespace |
| .bind(&path, ClientEnd::new(root.into_channel().unwrap().into_zx_channel())) |
| .expect("bind failed"); |
| let path_copy = path.clone(); |
| scopeguard::defer!({ |
| let _ = namespace.unbind(&path_copy); |
| }); |
| |
| fasync::unblock(move || { |
| assert_matches!(std::fs::remove_dir_all(&format!("{path}/dir")), Ok(())); |
| }) |
| .await; |
| } |
| |
| fixture.close().await; |
| } |
| |
| #[fuchsia::test] |
| async fn extended_attributes() { |
| let fixture = TestFixture::new().await; |
| let root = fixture.root(); |
| |
| let file = open_dir_checked( |
| &root, |
| fio::OpenFlags::CREATE |
| | fio::OpenFlags::RIGHT_READABLE |
| | fio::OpenFlags::RIGHT_WRITABLE |
| | fio::OpenFlags::DIRECTORY, |
| "foo", |
| ) |
| .await; |
| |
| let name = b"security.selinux"; |
| let value_vec = b"bar".to_vec(); |
| |
| { |
| let (iterator_client, iterator_server) = |
| fidl::endpoints::create_proxy::<fio::ExtendedAttributeIteratorMarker>().unwrap(); |
| file.list_extended_attributes(iterator_server).expect("Failed to make FIDL call"); |
| let (chunk, last) = iterator_client |
| .get_next() |
| .await |
| .expect("Failed to make FIDL call") |
| .expect("Failed to get next iterator chunk"); |
| assert!(last); |
| assert_eq!(chunk, Vec::<Vec<u8>>::new()); |
| } |
| assert_eq!( |
| file.get_extended_attribute(name) |
| .await |
| .expect("Failed to make FIDL call") |
| .expect_err("Got successful message back for missing attribute"), |
| zx::Status::NOT_FOUND.into_raw(), |
| ); |
| |
| file.set_extended_attribute( |
| name, |
| fio::ExtendedAttributeValue::Bytes(value_vec.clone()), |
| fio::SetExtendedAttributeMode::Set, |
| ) |
| .await |
| .expect("Failed to make FIDL call") |
| .expect("Failed to set extended attribute"); |
| |
| { |
| let (iterator_client, iterator_server) = |
| fidl::endpoints::create_proxy::<fio::ExtendedAttributeIteratorMarker>().unwrap(); |
| file.list_extended_attributes(iterator_server).expect("Failed to make FIDL call"); |
| let (chunk, last) = iterator_client |
| .get_next() |
| .await |
| .expect("Failed to make FIDL call") |
| .expect("Failed to get next iterator chunk"); |
| assert!(last); |
| assert_eq!(chunk, vec![name]); |
| } |
| assert_eq!( |
| file.get_extended_attribute(name) |
| .await |
| .expect("Failed to make FIDL call") |
| .expect("Failed to get extended attribute"), |
| fio::ExtendedAttributeValue::Bytes(value_vec) |
| ); |
| |
| file.remove_extended_attribute(name) |
| .await |
| .expect("Failed to make FIDL call") |
| .expect("Failed to remove extended attribute"); |
| |
| { |
| let (iterator_client, iterator_server) = |
| fidl::endpoints::create_proxy::<fio::ExtendedAttributeIteratorMarker>().unwrap(); |
| file.list_extended_attributes(iterator_server).expect("Failed to make FIDL call"); |
| let (chunk, last) = iterator_client |
| .get_next() |
| .await |
| .expect("Failed to make FIDL call") |
| .expect("Failed to get next iterator chunk"); |
| assert!(last); |
| assert_eq!(chunk, Vec::<Vec<u8>>::new()); |
| } |
| assert_eq!( |
| file.get_extended_attribute(name) |
| .await |
| .expect("Failed to make FIDL call") |
| .expect_err("Got successful message back for missing attribute"), |
| zx::Status::NOT_FOUND.into_raw(), |
| ); |
| |
| close_dir_checked(file).await; |
| fixture.close().await; |
| } |
| |
| #[fuchsia::test] |
| async fn extended_attribute_set_modes() { |
| let fixture = TestFixture::new().await; |
| let root = fixture.root(); |
| |
| let dir = open_dir_checked( |
| &root, |
| fio::OpenFlags::CREATE |
| | fio::OpenFlags::RIGHT_READABLE |
| | fio::OpenFlags::RIGHT_WRITABLE |
| | fio::OpenFlags::DIRECTORY, |
| "foo", |
| ) |
| .await; |
| |
| let name = b"security.selinux"; |
| let value_vec = b"bar".to_vec(); |
| let value2_vec = b"new value".to_vec(); |
| |
| // Can't replace an attribute that doesn't exist yet. |
| assert_eq!( |
| dir.set_extended_attribute( |
| name, |
| fio::ExtendedAttributeValue::Bytes(value_vec.clone()), |
| fio::SetExtendedAttributeMode::Replace |
| ) |
| .await |
| .expect("Failed to make FIDL call") |
| .expect_err("Got successful message back from replacing a nonexistent attribute"), |
| zx::Status::NOT_FOUND.into_raw() |
| ); |
| |
| // Create works when it doesn't exist. |
| dir.set_extended_attribute( |
| name, |
| fio::ExtendedAttributeValue::Bytes(value_vec.clone()), |
| fio::SetExtendedAttributeMode::Create, |
| ) |
| .await |
| .expect("Failed to make FIDL call") |
| .expect("Failed to set xattr with create"); |
| |
| // Create doesn't work once it exists though. |
| assert_eq!( |
| dir.set_extended_attribute( |
| name, |
| fio::ExtendedAttributeValue::Bytes(value2_vec.clone()), |
| fio::SetExtendedAttributeMode::Create |
| ) |
| .await |
| .expect("Failed to make FIDL call") |
| .expect_err("Got successful message back from replacing a nonexistent attribute"), |
| zx::Status::ALREADY_EXISTS.into_raw() |
| ); |
| |
| // But replace does. |
| dir.set_extended_attribute( |
| name, |
| fio::ExtendedAttributeValue::Bytes(value2_vec.clone()), |
| fio::SetExtendedAttributeMode::Replace, |
| ) |
| .await |
| .expect("Failed to make FIDL call") |
| .expect("Failed to set xattr with create"); |
| |
| close_dir_checked(dir).await; |
| fixture.close().await; |
| } |
| |
| #[fuchsia::test] |
| async fn test_create_dir_with_mutable_node_attributes() { |
| let fixture = TestFixture::new().await; |
| { |
| let root_dir = fixture.volume().root_dir(); |
| |
| let path_str = "foo"; |
| let path = Path::validate_and_split(path_str).unwrap(); |
| |
| let mode = fio::MODE_TYPE_DIRECTORY |
| | rights_to_posix_mode_bits(/*r*/ true, /*w*/ false, /*x*/ false); |
| let connection_protocols = fio::ConnectionProtocols::Node(fio::NodeOptions { |
| protocols: Some(fio::NodeProtocols { |
| directory: Some(fio::DirectoryProtocolOptions::default()), |
| ..Default::default() |
| }), |
| mode: Some(vfs::CreationMode::AllowExisting.into()), |
| create_attributes: Some(fio::MutableNodeAttributes { |
| mode: Some(mode), |
| ..Default::default() |
| }), |
| ..Default::default() |
| }); |
| |
| let dir = root_dir.lookup(&connection_protocols, path).await.expect("lookup failed"); |
| |
| let attrs = dir |
| .clone() |
| .into_any() |
| .downcast::<FxDirectory>() |
| .expect("Not a directory") |
| .get_attributes( |
| fio::NodeAttributesQuery::MODE |
| | fio::NodeAttributesQuery::UID |
| | fio::NodeAttributesQuery::ACCESS_TIME, |
| ) |
| .await |
| .expect("FIDL call failed"); |
| assert_eq!(attrs.mutable_attributes.mode.unwrap(), mode); |
| // Since the POSIX mode attribute was set, we expect default values for the other POSIX |
| // attributes. |
| assert_eq!(attrs.mutable_attributes.uid.unwrap(), 0); |
| // Expect these attributes to be None as they were not queried in `get_attributes(..)` |
| assert!(attrs.mutable_attributes.gid.is_none()); |
| assert!(attrs.mutable_attributes.rdev.is_none()); |
| assert!(attrs.mutable_attributes.access_time.is_some()); |
| } |
| fixture.close().await; |
| } |
| |
| #[fuchsia::test] |
| async fn test_create_dir_with_no_mutable_node_attributes() { |
| let fixture = TestFixture::new().await; |
| { |
| let root_dir = fixture.volume().root_dir(); |
| |
| let path_str = "foo"; |
| let path = Path::validate_and_split(path_str).unwrap(); |
| |
| let flags = fio::OpenFlags::DIRECTORY |
| | fio::OpenFlags::RIGHT_READABLE |
| | fio::OpenFlags::RIGHT_WRITABLE |
| | fio::OpenFlags::CREATE; |
| |
| let dir = root_dir.lookup(&flags, path).await.expect("lookup failed"); |
| |
| // `get_attrs()` returns a `fio::NodeAttributes` which includes `mode`. Since dir was |
| // not created with any mode bits, `get_attrs()` will return a `fio::NodeAttributes` |
| // with the default mode value. |
| let attrs = dir |
| .clone() |
| .into_any() |
| .downcast::<FxDirectory>() |
| .expect("Not a directory") |
| .get_attrs() |
| .await |
| .expect("FIDL call failed"); |
| let default_mode = fio::MODE_TYPE_DIRECTORY |
| | rights_to_posix_mode_bits(/*r*/ true, /*w*/ true, /*x*/ false); |
| assert_eq!(attrs.mode, default_mode); |
| } |
| fixture.close().await; |
| } |
| |
| #[fuchsia::test] |
| async fn test_create_dir_with_default_mutable_node_attributes() { |
| let fixture = TestFixture::new().await; |
| { |
| let root_dir = fixture.volume().root_dir(); |
| |
| let path_str = "foo"; |
| let path = Path::validate_and_split(path_str).unwrap(); |
| |
| let connection_protocols = fio::ConnectionProtocols::Node(fio::NodeOptions { |
| protocols: Some(fio::NodeProtocols { |
| directory: Some(fio::DirectoryProtocolOptions::default()), |
| ..Default::default() |
| }), |
| mode: Some(vfs::CreationMode::AllowExisting.into()), |
| create_attributes: Some(fio::MutableNodeAttributes { ..Default::default() }), |
| ..Default::default() |
| }); |
| |
| let dir = root_dir.lookup(&connection_protocols, path).await.expect("lookup failed"); |
| |
| let attrs = dir |
| .clone() |
| .into_any() |
| .downcast::<FxDirectory>() |
| .expect("Not a directory") |
| .get_attributes(fio::NodeAttributesQuery::MODE) |
| .await |
| .expect("FIDL call failed"); |
| // Although mode was requested, it was not set when creating the directory. So we |
| // expect None. |
| assert!(attrs.mutable_attributes.mode.is_none()); |
| // The attributes not requested should be None. |
| assert!(attrs.mutable_attributes.uid.is_none()); |
| assert!(attrs.mutable_attributes.gid.is_none()); |
| assert!(attrs.mutable_attributes.rdev.is_none()); |
| assert!(attrs.mutable_attributes.creation_time.is_none()); |
| assert!(attrs.mutable_attributes.modification_time.is_none()); |
| assert!(attrs.mutable_attributes.access_time.is_none()); |
| } |
| fixture.close().await; |
| } |
| |
| #[fuchsia::test] |
| async fn test_create_file_with_mutable_node_attributes() { |
| let fixture = TestFixture::new().await; |
| { |
| let root_dir = fixture.volume().root_dir(); |
| |
| let path_str = "foo"; |
| let path = Path::validate_and_split(path_str).unwrap(); |
| |
| let mode = fio::MODE_TYPE_FILE |
| | rights_to_posix_mode_bits(/*r*/ true, /*w*/ false, /*x*/ false); |
| let uid = 1; |
| let gid = 2; |
| let rdev = 3; |
| let modification_time = Timestamp::now().as_nanos(); |
| let connection_protocols = fio::ConnectionProtocols::Node(fio::NodeOptions { |
| protocols: Some(fio::NodeProtocols { |
| file: Some(fio::FileProtocolFlags::default()), |
| ..Default::default() |
| }), |
| mode: Some(vfs::CreationMode::AllowExisting.into()), |
| create_attributes: Some(fio::MutableNodeAttributes { |
| modification_time: Some(modification_time), |
| mode: Some(mode), |
| uid: Some(uid), |
| gid: Some(gid), |
| rdev: Some(rdev), |
| ..Default::default() |
| }), |
| ..Default::default() |
| }); |
| |
| let file = root_dir.lookup(&connection_protocols, path).await.expect("lookup failed"); |
| |
| let attributes = file |
| .clone() |
| .into_any() |
| .downcast::<FxFile>() |
| .expect("Not a file") |
| .get_attributes( |
| fio::NodeAttributesQuery::CREATION_TIME |
| | fio::NodeAttributesQuery::MODIFICATION_TIME |
| | fio::NodeAttributesQuery::CHANGE_TIME |
| | fio::NodeAttributesQuery::MODE |
| | fio::NodeAttributesQuery::UID |
| | fio::NodeAttributesQuery::GID |
| | fio::NodeAttributesQuery::RDEV, |
| ) |
| .await |
| .expect("FIDL call failed"); |
| assert_eq!(mode, attributes.mutable_attributes.mode.unwrap()); |
| assert_eq!(uid, attributes.mutable_attributes.uid.unwrap()); |
| assert_eq!(gid, attributes.mutable_attributes.gid.unwrap()); |
| assert_eq!(rdev, attributes.mutable_attributes.rdev.unwrap()); |
| assert_eq!(modification_time, attributes.mutable_attributes.modification_time.unwrap()); |
| assert!(attributes.mutable_attributes.creation_time.is_some()); |
| assert!(attributes.immutable_attributes.change_time.is_some()); |
| } |
| fixture.close().await; |
| } |
| |
| #[fuchsia::test] |
| async fn test_create_file_with_no_with_mutable_node_attributes() { |
| let fixture = TestFixture::new().await; |
| { |
| let root_dir = fixture.volume().root_dir(); |
| |
| let path_str = "foo"; |
| let path = Path::validate_and_split(path_str).unwrap(); |
| |
| let flags = fio::OpenFlags::CREATE |
| | fio::OpenFlags::NOT_DIRECTORY |
| | fio::OpenFlags::RIGHT_READABLE |
| | fio::OpenFlags::RIGHT_WRITABLE; |
| |
| let file = root_dir.lookup(&flags, path).await.expect("lookup failed"); |
| |
| let attrs = file |
| .clone() |
| .into_any() |
| .downcast::<FxFile>() |
| .expect("Not a file") |
| .get_attrs() |
| .await |
| .expect("FIDL call failed"); |
| let default_mode = fio::MODE_TYPE_FILE |
| | rights_to_posix_mode_bits(/*r*/ true, /*w*/ true, /*x*/ false); |
| assert_eq!(default_mode, attrs.mode); |
| } |
| fixture.close().await; |
| } |
| |
| #[fuchsia::test] |
| async fn test_create_file_with_default_mutable_node_attributes() { |
| let fixture = TestFixture::new().await; |
| { |
| let root_dir = fixture.volume().root_dir(); |
| |
| let path_str = "foo"; |
| let path = Path::validate_and_split(path_str).unwrap(); |
| |
| let connection_protocols = fio::ConnectionProtocols::Node(fio::NodeOptions { |
| protocols: Some(fio::NodeProtocols { |
| file: Some(fio::FileProtocolFlags::default()), |
| ..Default::default() |
| }), |
| mode: Some(vfs::CreationMode::AllowExisting.into()), |
| create_attributes: Some(fio::MutableNodeAttributes { ..Default::default() }), |
| ..Default::default() |
| }); |
| |
| let file = root_dir.lookup(&connection_protocols, path).await.expect("lookup failed"); |
| |
| let attrs = file |
| .clone() |
| .into_any() |
| .downcast::<FxFile>() |
| .expect("Not a directory") |
| .get_attributes(fio::NodeAttributesQuery::MODE) |
| .await |
| .expect("FIDL call failed"); |
| // Although mode was requested, it was not set when creating the directory. So we |
| // expect that it is None. |
| assert!(attrs.mutable_attributes.mode.is_none()); |
| // The attributes not requested should be None. |
| assert!(attrs.mutable_attributes.uid.is_none()); |
| assert!(attrs.mutable_attributes.gid.is_none()); |
| assert!(attrs.mutable_attributes.rdev.is_none()); |
| assert!(attrs.mutable_attributes.creation_time.is_none()); |
| assert!(attrs.mutable_attributes.modification_time.is_none()); |
| } |
| fixture.close().await; |
| } |
| |
| #[fuchsia::test] |
| async fn test_update_attributes_also_updates_ctime() { |
| let fixture = TestFixture::new().await; |
| let root = fixture.root(); |
| |
| let dir = open_dir_checked( |
| &root, |
| fio::OpenFlags::CREATE |
| | fio::OpenFlags::RIGHT_READABLE |
| | fio::OpenFlags::RIGHT_WRITABLE |
| | fio::OpenFlags::DIRECTORY, |
| "foo", |
| ) |
| .await; |
| |
| let (_mutable_attributes, immutable_attributes) = dir |
| .get_attributes(fio::NodeAttributesQuery::CHANGE_TIME) |
| .await |
| .expect("FIDL call failed") |
| .map_err(zx::ok) |
| .expect("get_attributes failed"); |
| |
| dir.update_attributes(&fio::MutableNodeAttributes { |
| modification_time: Some(Timestamp::now().as_nanos()), |
| mode: Some(111), |
| gid: Some(222), |
| ..Default::default() |
| }) |
| .await |
| .expect("FIDL call failed") |
| .map_err(zx::ok) |
| .expect("update_attributes failed"); |
| |
| let (_mutable_attributes, immutable_attributes_after_update) = dir |
| .get_attributes(fio::NodeAttributesQuery::CHANGE_TIME) |
| .await |
| .expect("FIDL call failed") |
| .map_err(zx::ok) |
| .expect("get_attributes failed"); |
| assert!(immutable_attributes_after_update.change_time > immutable_attributes.change_time); |
| fixture.close().await; |
| } |
| |
| #[fuchsia::test] |
| async fn test_open_deleted_self() { |
| let fixture = TestFixture::new().await; |
| let root = fixture.root(); |
| |
| let dir = |
| open_dir_checked(&root, fio::OpenFlags::CREATE | fio::OpenFlags::DIRECTORY, "foo") |
| .await; |
| |
| root.unlink("foo", &fio::UnlinkOptions::default()) |
| .await |
| .expect("FIDL call failed") |
| .expect("unlink failed"); |
| |
| assert_eq!( |
| open_dir(&root, fio::OpenFlags::DIRECTORY, "foo") |
| .await |
| .expect_err("Open succeeded") |
| .root_cause() |
| .downcast_ref::<zx::Status>() |
| .expect("No status"), |
| &zx::Status::NOT_FOUND, |
| ); |
| |
| open_dir_checked(&dir, fio::OpenFlags::DIRECTORY, ".").await; |
| |
| fixture.close().await; |
| } |
| |
| #[fuchsia::test] |
| async fn test_open2_deleted_self() { |
| let fixture = TestFixture::new().await; |
| let root = fixture.root(); |
| |
| const PATH: &str = "foo"; |
| |
| let dir = |
| open_dir_checked(&root, fio::OpenFlags::CREATE | fio::OpenFlags::DIRECTORY, PATH).await; |
| |
| root.unlink(PATH, &fio::UnlinkOptions::default()) |
| .await |
| .expect("FIDL call failed") |
| .expect("unlink failed"); |
| |
| assert_eq!( |
| open2_dir( |
| &root, |
| &fio::ConnectionProtocols::Node(fio::NodeOptions { |
| protocols: Some(fio::NodeProtocols { |
| directory: Some(fio::DirectoryProtocolOptions::default()), |
| ..Default::default() |
| }), |
| mode: Some(vfs::CreationMode::Never.into()), |
| flags: Some(fio::NodeFlags::GET_REPRESENTATION), |
| ..Default::default() |
| }), |
| PATH |
| ) |
| .await |
| .expect_err("Open2 succeeded") |
| .root_cause() |
| .downcast_ref::<zx::Status>() |
| .expect("No status"), |
| &zx::Status::NOT_FOUND, |
| ); |
| |
| open2_dir_checked( |
| &dir, |
| &fio::ConnectionProtocols::Node(fio::NodeOptions { |
| protocols: Some(fio::NodeProtocols { |
| directory: Some(fio::DirectoryProtocolOptions::default()), |
| ..Default::default() |
| }), |
| mode: Some(vfs::CreationMode::Never.into()), |
| flags: Some(fio::NodeFlags::GET_REPRESENTATION), |
| ..Default::default() |
| }), |
| ".", |
| ) |
| .await; |
| |
| fixture.close().await; |
| } |
| } |