use crate::file::FatFile;
use crate::filesystem::{FatFilesystem, FatFilesystemInner};
use crate::node::{Closer, FatNode, Node, WeakFatNode};
use crate::refs::{FatfsDirRef, FatfsFileRef};
use crate::types::{Dir, DirEntry, File};
use crate::util::{dos_to_unix_time, fatfs_error_to_status, unix_to_dos_time};
use fatfs::validate_filename;
use fidl::endpoints::ServerEnd;
use fidl_fuchsia_io as fio;
use futures::future::BoxFuture;
use std::borrow::Borrow;
use std::cell::UnsafeCell;
use std::collections::HashMap;
use std::fmt::Debug;
use std::hash::{Hash, Hasher};
use std::pin::Pin;
use std::sync::{Arc, RwLock};
use vfs::directory::dirents_sink::{self, AppendResult, Sink};
use vfs::directory::entry::{DirectoryEntry, EntryInfo, GetEntryInfo, OpenRequest};
use vfs::directory::entry_container::{Directory, DirectoryWatcher, MutableDirectory};
use vfs::directory::mutable::connection::MutableConnection;
use vfs::directory::traversal_position::TraversalPosition;
use vfs::directory::watchers::event_producers::{SingleNameEventProducer, StaticVecEventProducer};
use vfs::directory::watchers::Watchers;
use vfs::execution_scope::ExecutionScope;
use vfs::file::FidlIoConnection;
use vfs::path::Path;
use vfs::{attributes, ObjectRequestRef, ProtocolsExt as _, ToObjectRequest};
use zx::Status;
fn check_open_flags_for_existing_entry(flags: fio::OpenFlags) -> Result<(), Status> {
if flags.intersects(fio::OpenFlags::CREATE_IF_ABSENT) {
return Err(Status::ALREADY_EXISTS);
}
Ok(())
}
struct FatDirectoryData {
parent: Option<Arc<FatDirectory>>,
children: HashMap<InsensitiveString, WeakFatNode>,
deleted: bool,
watchers: Watchers,
name: String,
}
struct InsensitiveString(String);
impl Hash for InsensitiveString {
fn hash<H: Hasher>(&self, hasher: &mut H) {
for c in self.0.chars().flat_map(|c| c.to_uppercase()) {
hasher.write_u32(c as u32);
}
}
}
impl PartialEq for InsensitiveString {
fn eq(&self, other: &Self) -> bool {
self.0
.chars()
.flat_map(|c| c.to_uppercase())
.eq(other.0.chars().flat_map(|c| c.to_uppercase()))
}
}
impl Eq for InsensitiveString {}
pub(crate) trait InsensitiveStringRef {
fn as_str(&self) -> &str;
}
impl<'a> Borrow<dyn InsensitiveStringRef + 'a> for InsensitiveString {
fn borrow(&self) -> &(dyn InsensitiveStringRef + 'a) {
self
}
}
impl<'a> Eq for (dyn InsensitiveStringRef + 'a) {}
impl<'a> PartialEq for (dyn InsensitiveStringRef + 'a) {
fn eq(&self, other: &dyn InsensitiveStringRef) -> bool {
self.as_str()
.chars()
.flat_map(|c| c.to_uppercase())
.eq(other.as_str().chars().flat_map(|c| c.to_uppercase()))
}
}
impl<'a> Hash for (dyn InsensitiveStringRef + 'a) {
fn hash<H: Hasher>(&self, hasher: &mut H) {
for c in self.as_str().chars().flat_map(|c| c.to_uppercase()) {
hasher.write_u32(c as u32);
}
}
}
impl InsensitiveStringRef for &str {
fn as_str(&self) -> &str {
self
}
}
impl InsensitiveStringRef for InsensitiveString {
fn as_str(&self) -> &str {
&self.0
}
}
pub struct FatDirectory {
dir: UnsafeCell<FatfsDirRef>,
filesystem: Pin<Arc<FatFilesystem>>,
data: RwLock<FatDirectoryData>,
}
unsafe impl Sync for FatDirectory {}
unsafe impl Send for FatDirectory {}
enum ExistingRef<'a, 'b> {
None,
File(&'a mut crate::types::File<'b>),
Dir(&'a mut crate::types::Dir<'b>),
}
impl FatDirectory {
pub(crate) fn new(
dir: FatfsDirRef,
parent: Option<Arc<FatDirectory>>,
filesystem: Pin<Arc<FatFilesystem>>,
name: String,
) -> Arc<Self> {
Arc::new(FatDirectory {
dir: UnsafeCell::new(dir),
filesystem,
data: RwLock::new(FatDirectoryData {
parent,
children: HashMap::new(),
deleted: false,
watchers: Watchers::new(),
name,
}),
})
}
pub(crate) fn fs(&self) -> &Pin<Arc<FatFilesystem>> {
&self.filesystem
}
pub(crate) fn borrow_dir<'a>(&self, fs: &'a FatFilesystemInner) -> Result<&Dir<'a>, Status> {
unsafe { self.dir.get().as_ref() }.unwrap().borrow(fs).ok_or(Status::BAD_HANDLE)
}
pub(crate) fn borrow_dir_mut<'a>(&self, fs: &'a FatFilesystemInner) -> Option<&mut Dir<'a>> {
unsafe { self.dir.get().as_mut() }.unwrap().borrow_mut(fs)
}
pub(crate) fn find_child<'a>(
&'a self,
fs: &'a FatFilesystemInner,
name: &str,
) -> Result<Option<DirEntry<'a>>, Status> {
if self.data.read().unwrap().deleted {
return Ok(None);
}
let dir = self.borrow_dir(fs)?;
for entry in dir.iter().into_iter() {
let entry = entry?;
if entry.eq_name(name) {
return Ok(Some(entry));
}
}
Ok(None)
}
pub fn remove_child(&self, fs: &FatFilesystemInner, name: &str) -> Option<FatNode> {
let node = self.cache_remove(fs, name);
if let Some(node) = node {
node.detach(fs);
Some(node)
} else {
None
}
}
pub fn add_child(
self: &Arc<Self>,
fs: &FatFilesystemInner,
name: String,
child: FatNode,
) -> Result<(), Status> {
child.attach(self.clone(), &name, fs)?;
let mut data = self.data.write().unwrap();
if let Some(node) = data.children.insert(InsensitiveString(name), child.downgrade()) {
assert!(node.upgrade().is_none(), "conflicting cache entries with the same name")
}
Ok(())
}
pub(crate) fn cache_remove(&self, _fs: &FatFilesystemInner, name: &str) -> Option<FatNode> {
let mut data = self.data.write().unwrap();
data.children.remove(&name as &dyn InsensitiveStringRef).and_then(|entry| entry.upgrade())
}
pub fn cache_get(&self, name: &str) -> Option<FatNode> {
let data = self.data.read().unwrap();
data.children.get(&name as &dyn InsensitiveStringRef).and_then(|entry| entry.upgrade())
}
fn lookup(
self: &Arc<Self>,
flags: fio::OpenFlags,
mut path: Path,
closer: &mut Closer<'_>,
) -> Result<FatNode, Status> {
let mut cur_entry = FatNode::Dir(self.clone());
while !path.is_empty() {
let child_flags =
if path.is_single_component() { flags } else { fio::OpenFlags::DIRECTORY };
match cur_entry {
FatNode::Dir(entry) => {
let name = path.next().unwrap();
validate_filename(name)?;
cur_entry = entry.clone().open_child(name, child_flags, closer)?;
}
FatNode::File(_) => {
return Err(Status::NOT_DIR);
}
};
}
Ok(cur_entry)
}
fn lookup_with_open3_flags(
self: &Arc<Self>,
flags: fio::Flags,
mut path: Path,
closer: &mut Closer<'_>,
) -> Result<FatNode, Status> {
let mut current_entry = FatNode::Dir(self.clone());
while !path.is_empty() {
let child_flags =
if path.is_single_component() { flags } else { fio::Flags::PROTOCOL_DIRECTORY };
match current_entry {
FatNode::Dir(entry) => {
let name = path.next().unwrap();
validate_filename(name)?;
current_entry = entry.clone().open3_child(name, child_flags, closer)?;
}
FatNode::File(_) => {
return Err(Status::NOT_DIR);
}
};
}
Ok(current_entry)
}
pub(crate) fn open_child(
self: &Arc<Self>,
name: &str,
flags: fio::OpenFlags,
closer: &mut Closer<'_>,
) -> Result<FatNode, Status> {
let fs_lock = self.filesystem.lock().unwrap();
if let Some(entry) = self.cache_get(name) {
check_open_flags_for_existing_entry(flags)?;
entry.open_ref(&fs_lock)?;
return Ok(closer.add(entry));
};
let mut created = false;
let node = {
let entry = self.find_child(&fs_lock, name)?;
if let Some(entry) = entry {
check_open_flags_for_existing_entry(flags)?;
if entry.is_dir() {
self.add_directory(entry.to_dir(), name, closer)
} else {
self.add_file(entry.to_file(), name, closer)
}
} else if flags.intersects(fio::OpenFlags::CREATE) {
created = true;
let dir = self.borrow_dir(&fs_lock)?;
if flags.intersects(fio::OpenFlags::DIRECTORY) {
let dir = dir.create_dir(name).map_err(fatfs_error_to_status)?;
self.add_directory(dir, name, closer)
} else {
let file = dir.create_file(name).map_err(fatfs_error_to_status)?;
self.add_file(file, name, closer)
}
} else {
return Err(Status::NOT_FOUND);
}
};
let mut data = self.data.write().unwrap();
data.children.insert(InsensitiveString(name.to_owned()), node.downgrade());
if created {
data.watchers.send_event(&mut SingleNameEventProducer::added(name));
self.filesystem.mark_dirty();
}
Ok(node)
}
pub(crate) fn open3_child(
self: &Arc<Self>,
name: &str,
flags: fio::Flags,
closer: &mut Closer<'_>,
) -> Result<FatNode, Status> {
let fs_lock = self.filesystem.lock().unwrap();
if let Some(entry) = self.cache_get(name) {
if flags.creation_mode() == vfs::CreationMode::Always {
return Err(Status::ALREADY_EXISTS);
}
entry.open_ref(&fs_lock)?;
return Ok(closer.add(entry));
};
let mut created_entry = false;
let node = match self.find_child(&fs_lock, name)? {
Some(entry) => {
if flags.creation_mode() == vfs::CreationMode::Always {
return Err(Status::ALREADY_EXISTS);
}
if entry.is_dir() {
self.add_directory(entry.to_dir(), name, closer)
} else {
self.add_file(entry.to_file(), name, closer)
}
}
None => {
if flags.creation_mode() == vfs::CreationMode::Never {
return Err(Status::NOT_FOUND);
}
created_entry = true;
let dir = self.borrow_dir(&fs_lock)?;
if flags.intersects(fio::Flags::PROTOCOL_DIRECTORY) {
let dir = dir.create_dir(name).map_err(fatfs_error_to_status)?;
self.add_directory(dir, name, closer)
} else {
let file = dir.create_file(name).map_err(fatfs_error_to_status)?;
self.add_file(file, name, closer)
}
}
};
let mut data = self.data.write().unwrap();
data.children.insert(InsensitiveString(name.to_owned()), node.downgrade());
if created_entry {
data.watchers.send_event(&mut SingleNameEventProducer::added(name));
self.filesystem.mark_dirty();
}
Ok(node)
}
pub(crate) fn is_deleted(&self) -> bool {
self.data.read().unwrap().deleted
}
pub(crate) fn did_remove(&self, name: &str) {
self.data.write().unwrap().watchers.send_event(&mut SingleNameEventProducer::removed(name));
}
pub(crate) fn did_add(&self, name: &str) {
self.data.write().unwrap().watchers.send_event(&mut SingleNameEventProducer::added(name));
}
fn rename_internal(
&self,
filesystem: &FatFilesystemInner,
src_dir: &Arc<FatDirectory>,
src_name: &str,
dst_name: &str,
existing: ExistingRef<'_, '_>,
) -> Result<(), Status> {
let src_fatfs_dir = src_dir.borrow_dir(&filesystem)?;
let dst_fatfs_dir = self.borrow_dir(&filesystem)?;
match existing {
ExistingRef::None => {
src_fatfs_dir
.rename(src_name, &dst_fatfs_dir, dst_name)
.map_err(fatfs_error_to_status)?;
}
ExistingRef::File(file) => {
src_fatfs_dir
.rename_over_file(src_name, &dst_fatfs_dir, dst_name, file)
.map_err(fatfs_error_to_status)?;
}
ExistingRef::Dir(dir) => {
src_fatfs_dir
.rename_over_dir(src_name, &dst_fatfs_dir, dst_name, dir)
.map_err(fatfs_error_to_status)?;
}
}
src_dir.did_remove(src_name);
self.did_add(dst_name);
src_dir.fs().mark_dirty();
Ok(())
}
fn rename_locked(
self: &Arc<Self>,
filesystem: &FatFilesystemInner,
src_dir: &Arc<FatDirectory>,
src_name: &str,
dst_name: &str,
src_is_dir: bool,
closer: &mut Closer<'_>,
) -> Result<(), Status> {
if Arc::ptr_eq(&src_dir, self)
&& (&src_name as &dyn InsensitiveStringRef) == (&dst_name as &dyn InsensitiveStringRef)
{
if src_name != dst_name {
return self.rename_internal(
&filesystem,
src_dir,
src_name,
dst_name,
ExistingRef::None,
);
}
return Ok(());
}
if let Some(src_node) = src_dir.cache_get(src_name) {
if let FatNode::Dir(dir) = &src_node {
if Arc::ptr_eq(&dir, self) {
return Err(Status::INVALID_ARGS);
}
let mut dest = self.clone();
loop {
let next_dir = if let Some(parent) = &dest.data.read().unwrap().parent {
if Arc::ptr_eq(&dir, parent) {
return Err(Status::INVALID_ARGS);
}
parent.clone()
} else {
break;
};
dest = next_dir;
}
}
src_node.flush_dir_entry(filesystem)?;
}
let mut dir;
let mut file;
let mut existing_node = self.cache_get(dst_name);
let existing = match existing_node {
None => {
self.open_ref(filesystem)?;
closer.add(FatNode::Dir(self.clone()));
match self.find_child(filesystem, dst_name)? {
Some(ref dir_entry) => {
if dir_entry.is_dir() {
dir = Some(dir_entry.to_dir());
ExistingRef::Dir(dir.as_mut().unwrap())
} else {
file = Some(dir_entry.to_file());
ExistingRef::File(file.as_mut().unwrap())
}
}
None => ExistingRef::None,
}
}
Some(ref mut node) => {
node.open_ref(filesystem)?;
closer.add(node.clone());
match node {
FatNode::Dir(ref mut node_dir) => {
ExistingRef::Dir(node_dir.borrow_dir_mut(filesystem).unwrap())
}
FatNode::File(ref mut node_file) => {
ExistingRef::File(node_file.borrow_file_mut(filesystem).unwrap())
}
}
}
};
match existing {
ExistingRef::File(_) => {
if src_is_dir {
return Err(Status::NOT_DIR);
}
}
ExistingRef::Dir(_) => {
if !src_is_dir {
return Err(Status::NOT_FILE);
}
}
ExistingRef::None => {}
}
self.rename_internal(&filesystem, src_dir, src_name, dst_name, existing)?;
if let Some(_) = existing_node {
self.cache_remove(&filesystem, &dst_name).unwrap().did_delete();
}
if let Some(node) = src_dir.remove_child(&filesystem, &src_name) {
self.add_child(&filesystem, dst_name.to_owned(), node)
.unwrap_or_else(|e| panic!("Rename failed, but fatfs says it didn't? - {:?}", e));
}
Ok(())
}
fn add_directory(
self: &Arc<Self>,
dir: Dir<'_>,
name: &str,
closer: &mut Closer<'_>,
) -> FatNode {
let dir_ref = unsafe { FatfsDirRef::from(dir) };
closer.add(FatNode::Dir(FatDirectory::new(
dir_ref,
Some(self.clone()),
self.filesystem.clone(),
name.to_owned(),
)))
}
fn add_file(self: &Arc<Self>, file: File<'_>, name: &str, closer: &mut Closer<'_>) -> FatNode {
let file_ref = unsafe { FatfsFileRef::from(file) };
closer.add(FatNode::File(FatFile::new(
file_ref,
self.clone(),
self.filesystem.clone(),
name.to_owned(),
)))
}
}
impl Node for FatDirectory {
fn detach(&self, fs: &FatFilesystemInner) {
let dir = unsafe { self.dir.get().as_mut() }.unwrap();
dir.take(fs);
}
fn attach(
&self,
new_parent: Arc<FatDirectory>,
name: &str,
fs: &FatFilesystemInner,
) -> Result<(), Status> {
let mut data = self.data.write().unwrap();
data.name = name.to_owned();
let dir = unsafe { self.dir.get().as_mut().unwrap() };
unsafe { dir.maybe_reopen(fs, Some(&new_parent), name)? };
assert!(data.parent.replace(new_parent).is_some());
Ok(())
}
fn did_delete(&self) {
let mut data = self.data.write().unwrap();
data.parent.take();
data.watchers.send_event(&mut SingleNameEventProducer::deleted());
data.deleted = true;
}
fn open_ref(&self, fs: &FatFilesystemInner) -> Result<(), Status> {
let data = self.data.read().unwrap();
let dir_ref = unsafe { self.dir.get().as_mut() }.unwrap();
unsafe { dir_ref.open(&fs, data.parent.as_ref(), &data.name) }
}
fn shut_down(&self, fs: &FatFilesystemInner) -> Result<(), Status> {
unsafe { self.dir.get().as_mut() }.unwrap().take(fs);
let mut data = self.data.write().unwrap();
for (_, child) in data.children.drain() {
if let Some(child) = child.upgrade() {
child.shut_down(fs)?;
}
}
Ok(())
}
fn flush_dir_entry(&self, fs: &FatFilesystemInner) -> Result<(), Status> {
if let Some(ref mut dir) = self.borrow_dir_mut(fs) {
dir.flush_dir_entry().map_err(fatfs_error_to_status)?;
}
Ok(())
}
fn close_ref(&self, fs: &FatFilesystemInner) {
unsafe { self.dir.get().as_mut() }.unwrap().close(fs);
}
}
impl Debug for FatDirectory {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FatDirectory").field("parent", &self.data.read().unwrap().parent).finish()
}
}
impl MutableDirectory for FatDirectory {
async fn unlink(self: Arc<Self>, name: &str, must_be_directory: bool) -> Result<(), Status> {
let fs_lock = self.filesystem.lock().unwrap();
let parent = self.borrow_dir(&fs_lock)?;
let mut existing_node = self.cache_get(name);
let mut done = false;
match existing_node {
Some(FatNode::File(ref mut file)) => {
if must_be_directory {
return Err(Status::NOT_DIR);
}
if let Some(file) = file.borrow_file_mut(&fs_lock) {
parent.unlink_file(file).map_err(fatfs_error_to_status)?;
done = true;
}
}
Some(FatNode::Dir(ref mut dir)) => {
if let Some(dir) = dir.borrow_dir_mut(&fs_lock) {
parent.unlink_dir(dir).map_err(fatfs_error_to_status)?;
done = true;
}
}
None => {
if must_be_directory {
let entry = self.find_child(&fs_lock, name)?;
if !entry.ok_or(Status::NOT_FOUND)?.is_dir() {
return Err(Status::NOT_DIR);
}
}
}
}
if !done {
parent.remove(name).map_err(fatfs_error_to_status)?;
}
if existing_node.is_some() {
self.cache_remove(&fs_lock, name);
}
match existing_node {
Some(FatNode::File(ref mut file)) => file.did_delete(),
Some(FatNode::Dir(ref mut dir)) => dir.did_delete(),
None => {}
}
self.filesystem.mark_dirty();
self.data.write().unwrap().watchers.send_event(&mut SingleNameEventProducer::removed(name));
Ok(())
}
async fn update_attributes(
&self,
attributes: fio::MutableNodeAttributes,
) -> Result<(), Status> {
const SUPPORTED_MUTABLE_ATTRIBUTES: fio::NodeAttributesQuery =
fio::NodeAttributesQuery::CREATION_TIME
.union(fio::NodeAttributesQuery::MODIFICATION_TIME);
if !SUPPORTED_MUTABLE_ATTRIBUTES
.contains(vfs::common::mutable_node_attributes_to_query(&attributes))
{
return Err(Status::NOT_SUPPORTED);
}
let fs_lock = self.filesystem.lock().unwrap();
let dir = self.borrow_dir_mut(&fs_lock).ok_or(Status::BAD_HANDLE)?;
if let Some(creation_time) = attributes.creation_time {
dir.set_created(unix_to_dos_time(creation_time));
}
if let Some(modification_time) = attributes.modification_time {
dir.set_modified(unix_to_dos_time(modification_time));
}
self.filesystem.mark_dirty();
Ok(())
}
async fn sync(&self) -> Result<(), Status> {
Ok(())
}
fn rename(
self: Arc<Self>,
src_dir: Arc<dyn MutableDirectory>,
src_path: Path,
dst_path: Path,
) -> BoxFuture<'static, Result<(), Status>> {
Box::pin(async move {
let src_dir =
src_dir.into_any().downcast::<FatDirectory>().map_err(|_| Status::INVALID_ARGS)?;
if self.is_deleted() {
return Err(Status::NOT_FOUND);
}
let src_name = src_path.peek().unwrap();
validate_filename(src_name).map_err(fatfs_error_to_status)?;
let dst_name = dst_path.peek().unwrap();
validate_filename(dst_name).map_err(fatfs_error_to_status)?;
let mut closer = Closer::new(&self.filesystem);
let filesystem = self.filesystem.lock().unwrap();
let entry = src_dir.find_child(&filesystem, &src_name)?;
if entry.is_none() {
return Err(Status::NOT_FOUND);
}
let src_is_dir = entry.unwrap().is_dir();
if (dst_path.is_dir() || src_path.is_dir()) && !src_is_dir {
return Err(Status::NOT_DIR);
}
self.rename_locked(&filesystem, &src_dir, src_name, dst_name, src_is_dir, &mut closer)
})
}
}
impl DirectoryEntry for FatDirectory {
fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), Status> {
request.open_dir(self)
}
}
impl GetEntryInfo for FatDirectory {
fn entry_info(&self) -> EntryInfo {
EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)
}
}
impl vfs::node::Node for FatDirectory {
async fn get_attributes(
&self,
requested_attributes: fio::NodeAttributesQuery,
) -> Result<fio::NodeAttributes2, Status> {
let fs_lock = self.filesystem.lock().unwrap();
let dir = self.borrow_dir(&fs_lock)?;
let creation_time = dos_to_unix_time(dir.created());
let modification_time = dos_to_unix_time(dir.modified());
Ok(attributes!(
requested_attributes,
Mutable { creation_time: creation_time, modification_time: modification_time },
Immutable {
protocols: fio::NodeProtocolKinds::DIRECTORY,
abilities: fio::Operations::GET_ATTRIBUTES
| fio::Operations::UPDATE_ATTRIBUTES
| fio::Operations::ENUMERATE
| fio::Operations::TRAVERSE
| fio::Operations::MODIFY_DIRECTORY,
}
))
}
fn close(self: Arc<Self>) {
self.close_ref(&self.filesystem.lock().unwrap());
}
fn query_filesystem(&self) -> Result<fio::FilesystemInfo, Status> {
self.filesystem.query_filesystem()
}
fn will_clone(&self) {
self.open_ref(&self.filesystem.lock().unwrap()).unwrap();
}
}
impl Directory for FatDirectory {
fn open(
self: Arc<Self>,
scope: ExecutionScope,
flags: fio::OpenFlags,
path: Path,
server_end: ServerEnd<fio::NodeMarker>,
) {
let mut closer = Closer::new(&self.filesystem);
flags.to_object_request(server_end).handle(|object_request| {
match self.lookup(flags, path, &mut closer)? {
FatNode::Dir(entry) => {
let () = entry
.open_ref(&self.filesystem.lock().unwrap())
.expect("entry should already be open");
object_request.spawn_connection(scope, entry, flags, MutableConnection::create)
}
FatNode::File(entry) => {
let () = entry
.open_ref(&self.filesystem.lock().unwrap())
.expect("entry should already be open");
entry.create_connection(scope, flags, object_request)
}
}
});
}
fn open3(
self: Arc<Self>,
scope: ExecutionScope,
path: Path,
flags: fio::Flags,
object_request: ObjectRequestRef<'_>,
) -> Result<(), Status> {
let mut closer = Closer::new(&self.filesystem);
match self.lookup_with_open3_flags(flags, path, &mut closer)? {
FatNode::Dir(entry) => {
let () = entry.open_ref(&self.filesystem.lock().unwrap())?;
object_request.spawn_connection(scope, entry, flags, MutableConnection::create)
}
FatNode::File(entry) => {
let () = entry.open_ref(&self.filesystem.lock().unwrap())?;
object_request.spawn_connection(scope, entry, flags, FidlIoConnection::create)
}
}
}
async fn read_dirents<'a>(
&'a self,
pos: &'a TraversalPosition,
sink: Box<dyn Sink>,
) -> Result<(TraversalPosition, Box<dyn dirents_sink::Sealed>), Status> {
if self.is_deleted() {
return Ok((TraversalPosition::End, sink.seal()));
}
let fs_lock = self.filesystem.lock().unwrap();
let dir = self.borrow_dir(&fs_lock)?;
if let TraversalPosition::End = pos {
return Ok((TraversalPosition::End, sink.seal()));
}
let filter = |name: &str| match pos {
TraversalPosition::Start => true,
TraversalPosition::Name(next_name) => name >= next_name.as_str(),
_ => false,
};
let mut entries: Vec<_> = dir
.iter()
.filter_map(|maybe_entry| {
maybe_entry
.map(|entry| {
let name = entry.file_name();
if &name == ".." || !filter(&name) {
None
} else {
let entry_type = if entry.is_dir() {
fio::DirentType::Directory
} else {
fio::DirentType::File
};
Some((name, EntryInfo::new(fio::INO_UNKNOWN, entry_type)))
}
})
.transpose()
})
.collect::<std::io::Result<Vec<_>>>()?;
if self.data.read().unwrap().parent.is_none() && filter(".") {
entries.push((
".".to_owned(),
EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory),
));
}
entries.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
let mut cur_sink = sink;
for (name, info) in entries.into_iter() {
let result = cur_sink.append(&info, &name.clone());
match result {
AppendResult::Ok(new_sink) => cur_sink = new_sink,
AppendResult::Sealed(sealed) => {
return Ok((TraversalPosition::Name(name), sealed));
}
}
}
return Ok((TraversalPosition::End, cur_sink.seal()));
}
fn register_watcher(
self: Arc<Self>,
scope: ExecutionScope,
mask: fio::WatchMask,
watcher: DirectoryWatcher,
) -> Result<(), Status> {
let fs_lock = self.filesystem.lock().unwrap();
let mut data = self.data.write().unwrap();
let is_deleted = data.deleted;
let is_root = data.parent.is_none();
let controller = data.watchers.add(scope, self.clone(), mask, watcher);
if mask.contains(fio::WatchMask::EXISTING) && !is_deleted {
let entries = {
let dir = self.borrow_dir(&fs_lock)?;
let synthesized_dot = if is_root {
Some(Ok(".".to_owned()))
} else {
None
};
synthesized_dot
.into_iter()
.chain(dir.iter().filter_map(|maybe_entry| {
maybe_entry
.map(|entry| {
let name = entry.file_name();
if &name == ".." {
None
} else {
Some(name)
}
})
.transpose()
}))
.collect::<std::io::Result<Vec<String>>>()
.map_err(fatfs_error_to_status)?
};
controller.send_event(&mut StaticVecEventProducer::existing(entries));
}
controller.send_event(&mut SingleNameEventProducer::idle());
Ok(())
}
fn unregister_watcher(self: Arc<Self>, key: usize) {
self.data.write().unwrap().watchers.remove(key);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::{TestDiskContents, TestFatDisk};
use assert_matches::assert_matches;
use futures::TryStreamExt;
use scopeguard::defer;
use vfs::directory::dirents_sink::Sealed;
use vfs::node::Node as _;
use vfs::ObjectRequest;
const TEST_DISK_SIZE: u64 = 2048 << 10; #[fuchsia::test(allow_stalls = false)]
async fn test_link_fails() {
let disk = TestFatDisk::empty_disk(TEST_DISK_SIZE);
let structure = TestDiskContents::dir().add_child("test_file", "test file contents".into());
structure.create(&disk.root_dir());
let fs = disk.into_fatfs();
let dir = fs.get_fatfs_root();
dir.open_ref(&fs.filesystem().lock().unwrap()).expect("open_ref failed");
defer! { dir.close_ref(&fs.filesystem().lock().unwrap()) }
assert_eq!(
dir.clone().link("test2".to_owned(), dir.clone(), "test3").await.unwrap_err(),
Status::NOT_SUPPORTED
);
}
#[derive(Clone)]
struct DummySink {
max_size: usize,
entries: Vec<(String, EntryInfo)>,
sealed: bool,
}
impl DummySink {
pub fn new(max_size: usize) -> Self {
DummySink { max_size, entries: Vec::with_capacity(max_size), sealed: false }
}
fn from_sealed(sealed: Box<dyn dirents_sink::Sealed>) -> Box<DummySink> {
sealed.into()
}
}
impl From<Box<dyn dirents_sink::Sealed>> for Box<DummySink> {
fn from(sealed: Box<dyn dirents_sink::Sealed>) -> Self {
sealed.open().downcast::<DummySink>().unwrap()
}
}
impl Sink for DummySink {
fn append(mut self: Box<Self>, entry: &EntryInfo, name: &str) -> AppendResult {
assert!(!self.sealed);
if self.entries.len() == self.max_size {
AppendResult::Sealed(self.seal())
} else {
self.entries.push((name.to_owned(), entry.clone()));
AppendResult::Ok(self)
}
}
fn seal(mut self: Box<Self>) -> Box<dyn Sealed> {
self.sealed = true;
self
}
}
impl Sealed for DummySink {
fn open(self: Box<Self>) -> Box<dyn std::any::Any> {
self
}
}
#[fuchsia::test]
fn test_read_dirents_small_sink() {
let disk = TestFatDisk::empty_disk(TEST_DISK_SIZE);
let structure = TestDiskContents::dir()
.add_child("test_file", "test file contents".into())
.add_child("aaa", "this file is first".into())
.add_child("qwerty", "hello".into())
.add_child("directory", TestDiskContents::dir().add_child("a", "test".into()));
structure.create(&disk.root_dir());
let fs = disk.into_fatfs();
let dir = fs.get_fatfs_root();
dir.open_ref(&fs.filesystem().lock().unwrap()).expect("open_ref failed");
defer! { dir.close_ref(&fs.filesystem().lock().unwrap()) }
let (pos, sealed) = futures::executor::block_on(
dir.clone().read_dirents(&TraversalPosition::Start, Box::new(DummySink::new(4))),
)
.expect("read_dirents failed");
assert_eq!(
DummySink::from_sealed(sealed).entries,
vec![
(".".to_owned(), EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)),
("aaa".to_owned(), EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::File)),
(
"directory".to_owned(),
EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)
),
("qwerty".to_owned(), EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::File)),
]
);
let (_, sealed) =
futures::executor::block_on(dir.read_dirents(&pos, Box::new(DummySink::new(4))))
.expect("read_dirents failed");
assert_eq!(
DummySink::from_sealed(sealed).entries,
vec![("test_file".to_owned(), EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::File)),]
);
}
#[fuchsia::test]
fn test_read_dirents_big_sink() {
let disk = TestFatDisk::empty_disk(TEST_DISK_SIZE);
let structure = TestDiskContents::dir()
.add_child("test_file", "test file contents".into())
.add_child("aaa", "this file is first".into())
.add_child("qwerty", "hello".into())
.add_child("directory", TestDiskContents::dir().add_child("a", "test".into()));
structure.create(&disk.root_dir());
let fs = disk.into_fatfs();
let dir = fs.get_fatfs_root();
dir.open_ref(&fs.filesystem().lock().unwrap()).expect("open_ref failed");
defer! { dir.close_ref(&fs.filesystem().lock().unwrap()) }
let (_, sealed) = futures::executor::block_on(
dir.read_dirents(&TraversalPosition::Start, Box::new(DummySink::new(30))),
)
.expect("read_dirents failed");
assert_eq!(
DummySink::from_sealed(sealed).entries,
vec![
(".".to_owned(), EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)),
("aaa".to_owned(), EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::File)),
(
"directory".to_owned(),
EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)
),
("qwerty".to_owned(), EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::File)),
("test_file".to_owned(), EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::File)),
]
);
}
#[fuchsia::test]
fn test_read_dirents_with_entry_that_sorts_before_dot() {
let disk = TestFatDisk::empty_disk(TEST_DISK_SIZE);
let structure = TestDiskContents::dir().add_child("!", "!".into());
structure.create(&disk.root_dir());
let fs = disk.into_fatfs();
let dir = fs.get_fatfs_root();
dir.open_ref(&fs.filesystem().lock().unwrap()).expect("open_ref failed");
defer! { dir.close_ref(&fs.filesystem().lock().unwrap()) }
let (pos, sealed) = futures::executor::block_on(
dir.clone().read_dirents(&TraversalPosition::Start, Box::new(DummySink::new(1))),
)
.expect("read_dirents failed");
assert_eq!(
DummySink::from_sealed(sealed).entries,
vec![("!".to_owned(), EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::File))]
);
let (_, sealed) =
futures::executor::block_on(dir.read_dirents(&pos, Box::new(DummySink::new(1))))
.expect("read_dirents failed");
assert_eq!(
DummySink::from_sealed(sealed).entries,
vec![(".".to_owned(), EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)),]
);
}
#[fuchsia::test(allow_stalls = false)]
async fn test_reopen_root() {
let disk = TestFatDisk::empty_disk(TEST_DISK_SIZE);
let structure = TestDiskContents::dir().add_child("test", "Hello".into());
structure.create(&disk.root_dir());
let fs = disk.into_fatfs();
let dir = fs.get_root().expect("get_root OK");
let scope = ExecutionScope::new();
let (proxy, server_end) = fidl::endpoints::create_proxy::<fio::NodeMarker>();
dir.clone().open(scope.clone(), fio::OpenFlags::RIGHT_READABLE, Path::dot(), server_end);
let scope_clone = scope.clone();
proxy
.close()
.await
.expect("Send request OK")
.map_err(Status::from_raw)
.expect("First close OK");
let (proxy, server_end) = fidl::endpoints::create_proxy::<fio::NodeMarker>();
dir.clone().open(
scope_clone,
fio::OpenFlags::RIGHT_READABLE,
Path::validate_and_split("test").unwrap(),
server_end,
);
proxy
.close()
.await
.expect("Send request OK")
.map_err(Status::from_raw)
.expect("Second close OK");
dir.close();
}
#[fuchsia::test(allow_stalls = false)]
async fn test_reopen3_root() {
let disk = TestFatDisk::empty_disk(TEST_DISK_SIZE);
let structure = TestDiskContents::dir().add_child("test", "Hello".into());
structure.create(&disk.root_dir());
let fs = disk.into_fatfs();
let root = fs.get_root().expect("get_root failed");
let scope = ExecutionScope::new();
let (proxy, server_end) = fidl::endpoints::create_proxy::<fio::NodeMarker>();
let flags = fio::Flags::PERM_READ;
ObjectRequest::new3(flags, &fio::Options::default(), server_end.into())
.handle(|request| root.clone().open3(scope.clone(), Path::dot(), flags, request));
proxy
.close()
.await
.expect("FIDL call failed")
.map_err(Status::from_raw)
.expect("First close failed");
let (proxy, server_end) = fidl::endpoints::create_proxy::<fio::NodeMarker>();
ObjectRequest::new3(flags, &fio::Options::default(), server_end.into()).handle(|request| {
root.clone().open3(
scope.clone(),
Path::validate_and_split("test").unwrap(),
flags,
request,
)
});
proxy
.close()
.await
.expect("FIDL call failed")
.map_err(Status::from_raw)
.expect("Second close failed");
root.close();
}
#[fuchsia::test(allow_stalls = false)]
async fn test_open3_already_exists() {
let disk = TestFatDisk::empty_disk(TEST_DISK_SIZE);
let structure = TestDiskContents::dir().add_child("test", "Hello".into());
structure.create(&disk.root_dir());
let fs = disk.into_fatfs();
let root = fs.get_root().expect("get_root failed");
let scope = ExecutionScope::new();
let (proxy, server_end) = fidl::endpoints::create_proxy::<fio::NodeMarker>();
let flags = fio::Flags::PERM_READ
| fio::Flags::FLAG_MUST_CREATE
| fio::Flags::FLAG_SEND_REPRESENTATION;
ObjectRequest::new3(flags, &fio::Options::default(), server_end.into()).handle(|request| {
root.clone().open3(
scope.clone(),
Path::validate_and_split("test").unwrap(),
flags,
request,
)
});
let event =
proxy.take_event_stream().try_next().await.expect_err("open2 passed unexpectedly");
assert_matches!(
event,
fidl::Error::ClientChannelClosed { status: Status::ALREADY_EXISTS, .. }
);
root.close();
}
#[fuchsia::test(allow_stalls = false)]
async fn test_update_attributes_directory() {
let disk = TestFatDisk::empty_disk(TEST_DISK_SIZE);
let structure = TestDiskContents::dir().add_child("test", "Hello".into());
structure.create(&disk.root_dir());
let fs = disk.into_fatfs();
let root = fs.get_root().expect("get_root failed");
let scope = ExecutionScope::new();
let (proxy, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
let flags = fio::Flags::PERM_READ | fio::Flags::PERM_SET_ATTRIBUTES;
ObjectRequest::new3(flags, &fio::Options::default(), server_end.into()).handle(|request| {
root.clone().open3(
scope.clone(),
Path::validate_and_split(".").unwrap(),
flags,
request,
)
});
let mut new_attrs = fio::MutableNodeAttributes {
creation_time: Some(
std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.expect("SystemTime before UNIX EPOCH")
.as_nanos()
.try_into()
.unwrap(),
),
..Default::default()
};
proxy
.update_attributes(&new_attrs)
.await
.expect("FIDL call failed")
.map_err(Status::from_raw)
.expect("update attributes failed");
new_attrs.mode = Some(123);
let status = proxy
.update_attributes(&new_attrs)
.await
.expect("FIDL call failed")
.map_err(Status::from_raw)
.expect_err("update unsupported attributes passed unexpectedly");
assert_eq!(status, Status::NOT_SUPPORTED);
root.close();
}
#[fuchsia::test(allow_stalls = false)]
async fn test_update_attributes_file() {
let disk = TestFatDisk::empty_disk(TEST_DISK_SIZE);
let structure = TestDiskContents::dir().add_child("test_file", "Hello".into());
structure.create(&disk.root_dir());
let fs = disk.into_fatfs();
let root = fs.get_root().expect("get_root failed");
let scope = ExecutionScope::new();
let (proxy, server_end) = fidl::endpoints::create_proxy::<fio::FileMarker>();
let flags =
fio::Flags::PERM_READ | fio::Flags::PERM_SET_ATTRIBUTES | fio::Flags::PROTOCOL_FILE;
ObjectRequest::new3(flags, &fio::Options::default(), server_end.into()).handle(|request| {
root.clone().open3(
scope.clone(),
Path::validate_and_split("test_file").unwrap(),
flags,
request,
)
});
let mut new_attrs = fio::MutableNodeAttributes {
creation_time: Some(
std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.expect("SystemTime before UNIX EPOCH")
.as_nanos()
.try_into()
.unwrap(),
),
..Default::default()
};
proxy
.update_attributes(&new_attrs)
.await
.expect("FIDL call failed")
.map_err(Status::from_raw)
.expect("update attributes failed");
new_attrs.mode = Some(123);
let status = proxy
.update_attributes(&new_attrs)
.await
.expect("FIDL call failed")
.map_err(Status::from_raw)
.expect_err("update unsupported attributes passed unexpectedly");
assert_eq!(status, Status::NOT_SUPPORTED);
root.close();
}
}