fuchsia_fatfs/
directory.rs

1// Copyright 2020 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4use crate::file::FatFile;
5use crate::filesystem::{FatFilesystem, FatFilesystemInner};
6use crate::node::{Closer, FatNode, Node, WeakFatNode};
7use crate::refs::{FatfsDirRef, FatfsFileRef, Guard, GuardMut, Wrapper};
8use crate::types::{Dir, DirEntry, File};
9use crate::util::{
10    dos_date_to_unix_time, dos_to_unix_time, fatfs_error_to_status, unix_to_dos_time,
11};
12use fatfs::validate_filename;
13use fidl::endpoints::ServerEnd;
14use fidl_fuchsia_io as fio;
15use fuchsia_sync::RwLock;
16use futures::future::BoxFuture;
17use std::borrow::Borrow;
18use std::cell::RefCell;
19use std::collections::HashMap;
20use std::fmt::Debug;
21use std::hash::{Hash, Hasher};
22use std::pin::Pin;
23use std::sync::Arc;
24use vfs::directory::dirents_sink::{self, AppendResult, Sink};
25use vfs::directory::entry::{DirectoryEntry, EntryInfo, GetEntryInfo, OpenRequest};
26use vfs::directory::entry_container::{Directory, DirectoryWatcher, MutableDirectory};
27use vfs::directory::mutable::connection::MutableConnection;
28use vfs::directory::traversal_position::TraversalPosition;
29use vfs::directory::watchers::event_producers::{SingleNameEventProducer, StaticVecEventProducer};
30use vfs::directory::watchers::Watchers;
31use vfs::execution_scope::ExecutionScope;
32use vfs::file::FidlIoConnection;
33use vfs::path::Path;
34use vfs::{attributes, ObjectRequestRef, ProtocolsExt as _, ToObjectRequest};
35use zx::Status;
36
37fn check_open_flags_for_existing_entry(flags: fio::OpenFlags) -> Result<(), Status> {
38    if flags.intersects(fio::OpenFlags::CREATE_IF_ABSENT) {
39        return Err(Status::ALREADY_EXISTS);
40    }
41    // Other flags are verified by VFS's new_connection_validate_flags method.
42    Ok(())
43}
44
45struct FatDirectoryData {
46    /// The parent directory of this entry. Might be None if this is the root directory,
47    /// or if this directory has been deleted.
48    parent: Option<Arc<FatDirectory>>,
49    /// We keep a cache of `FatDirectory`/`FatFile`s to ensure
50    /// there is only ever one canonical version of each. This means
51    /// we can use the reference count in the Arc<> to make sure rename, etc. operations are safe.
52    children: HashMap<InsensitiveString, WeakFatNode>,
53    /// True if this directory has been deleted.
54    deleted: bool,
55    watchers: Watchers,
56    /// Name of this directory. TODO: we should be able to change to HashSet.
57    name: String,
58}
59
60// Whilst it's tempting to use the unicase crate, at time of writing, it had its own case tables,
61// which might not match Rust's built-in tables (which is what fatfs uses).  It's important what we
62// do here is consistent with the fatfs crate.  It would be nice if that were consistent with other
63// implementations, but it probably isn't the end of the world if it isn't since we shouldn't have
64// clients using obscure ranges of Unicode.
65struct InsensitiveString(String);
66
67impl Hash for InsensitiveString {
68    fn hash<H: Hasher>(&self, hasher: &mut H) {
69        for c in self.0.chars().flat_map(|c| c.to_uppercase()) {
70            hasher.write_u32(c as u32);
71        }
72    }
73}
74
75impl PartialEq for InsensitiveString {
76    fn eq(&self, other: &Self) -> bool {
77        self.0
78            .chars()
79            .flat_map(|c| c.to_uppercase())
80            .eq(other.0.chars().flat_map(|c| c.to_uppercase()))
81    }
82}
83
84impl Eq for InsensitiveString {}
85
86// A trait that allows us to find entries in our hash table using &str.
87pub(crate) trait InsensitiveStringRef {
88    fn as_str(&self) -> &str;
89}
90
91impl<'a> Borrow<dyn InsensitiveStringRef + 'a> for InsensitiveString {
92    fn borrow(&self) -> &(dyn InsensitiveStringRef + 'a) {
93        self
94    }
95}
96
97impl<'a> Eq for dyn InsensitiveStringRef + 'a {}
98
99impl<'a> PartialEq for dyn InsensitiveStringRef + 'a {
100    fn eq(&self, other: &dyn InsensitiveStringRef) -> bool {
101        self.as_str()
102            .chars()
103            .flat_map(|c| c.to_uppercase())
104            .eq(other.as_str().chars().flat_map(|c| c.to_uppercase()))
105    }
106}
107
108impl<'a> Hash for dyn InsensitiveStringRef + 'a {
109    fn hash<H: Hasher>(&self, hasher: &mut H) {
110        for c in self.as_str().chars().flat_map(|c| c.to_uppercase()) {
111            hasher.write_u32(c as u32);
112        }
113    }
114}
115
116impl InsensitiveStringRef for &str {
117    fn as_str(&self) -> &str {
118        self
119    }
120}
121
122impl InsensitiveStringRef for InsensitiveString {
123    fn as_str(&self) -> &str {
124        &self.0
125    }
126}
127
128/// This wraps a directory on the FAT volume.
129pub struct FatDirectory {
130    /// The underlying directory.
131    dir: RefCell<FatfsDirRef>,
132    /// We synchronise all accesses to directory on filesystem's lock().
133    /// We always acquire the filesystem lock before the data lock, if the data lock is also going
134    /// to be acquired.
135    filesystem: Pin<Arc<FatFilesystem>>,
136    /// Other information about this FatDirectory that shares a lock.
137    /// This should always be acquired after the filesystem lock if the filesystem lock is also
138    /// going to be acquired.
139    data: RwLock<FatDirectoryData>,
140}
141
142// The only member that isn't `Sync + Send` is the `dir` member.
143// `dir` is protected by the lock on `filesystem`, so we can safely
144// implement Sync + Send for FatDirectory.
145unsafe impl Sync for FatDirectory {}
146unsafe impl Send for FatDirectory {}
147
148enum ExistingRef<'a, 'b> {
149    None,
150    File(&'a mut crate::types::File<'b>),
151    Dir(&'a mut crate::types::Dir<'b>),
152}
153
154impl FatDirectory {
155    /// Create a new FatDirectory.
156    pub(crate) fn new(
157        dir: FatfsDirRef,
158        parent: Option<Arc<FatDirectory>>,
159        filesystem: Pin<Arc<FatFilesystem>>,
160        name: String,
161    ) -> Arc<Self> {
162        Arc::new(FatDirectory {
163            dir: RefCell::new(dir),
164            filesystem,
165            data: RwLock::new(FatDirectoryData {
166                parent,
167                children: HashMap::new(),
168                deleted: false,
169                watchers: Watchers::new(),
170                name,
171            }),
172        })
173    }
174
175    pub(crate) fn fs(&self) -> &Pin<Arc<FatFilesystem>> {
176        &self.filesystem
177    }
178
179    /// Borrow the underlying fatfs `Dir` that corresponds to this directory.
180    pub(crate) fn borrow_dir<'a>(
181        &'a self,
182        fs: &'a FatFilesystemInner,
183    ) -> Result<Guard<'a, FatfsDirRef>, Status> {
184        let dir = self.dir.borrow();
185        if dir.get(fs).is_none() {
186            Err(Status::BAD_HANDLE)
187        } else {
188            Ok(Guard::new(fs, dir))
189        }
190    }
191
192    /// Borrow the underlying fatfs `Dir` that corresponds to this directory.
193    pub(crate) fn borrow_dir_mut<'a>(
194        &'a self,
195        fs: &'a FatFilesystemInner,
196    ) -> Option<GuardMut<'a, FatfsDirRef>> {
197        let dir = self.dir.borrow_mut();
198        if dir.get(fs).is_none() {
199            None
200        } else {
201            Some(GuardMut::new(fs, dir))
202        }
203    }
204
205    /// Gets a child directory entry from the underlying fatfs implementation.
206    pub(crate) fn find_child<'a>(
207        &'a self,
208        fs: &'a FatFilesystemInner,
209        name: &str,
210    ) -> Result<Option<DirEntry<'a>>, Status> {
211        if self.data.read().deleted {
212            return Ok(None);
213        }
214        let dir = self.borrow_dir(fs)?;
215        for entry in dir.iter().into_iter() {
216            let entry = entry?;
217            if entry.eq_name(name) {
218                return Ok(Some(entry));
219            }
220        }
221        Ok(None)
222    }
223
224    /// Remove and detach a child node from this FatDirectory, returning it if it exists in the
225    /// cache.  The caller must ensure that the corresponding filesystem entry is removed to prevent
226    /// the item being added back to the cache, and must later attach() the returned node somewhere.
227    pub fn remove_child(&self, fs: &FatFilesystemInner, name: &str) -> Option<FatNode> {
228        let node = self.cache_remove(fs, name);
229        if let Some(node) = node {
230            node.detach(fs);
231            Some(node)
232        } else {
233            None
234        }
235    }
236
237    /// Add and attach a child node to this FatDirectory. The caller needs to make sure that the
238    /// entry corresponds to a node on the filesystem, and that there is no existing entry with
239    /// that name in the cache.
240    pub fn add_child(
241        self: &Arc<Self>,
242        fs: &FatFilesystemInner,
243        name: String,
244        child: FatNode,
245    ) -> Result<(), Status> {
246        child.attach(self.clone(), &name, fs)?;
247        // We only add back to the cache if the above succeeds, otherwise we have no
248        // interest in serving more connections to a file that doesn't exist.
249        let mut data = self.data.write();
250        // TODO: need to delete cache entries somewhere.
251        if let Some(node) = data.children.insert(InsensitiveString(name), child.downgrade()) {
252            assert!(node.upgrade().is_none(), "conflicting cache entries with the same name")
253        }
254        Ok(())
255    }
256
257    /// Remove a child entry from the cache, if it exists. The caller must hold the fs lock, as
258    /// otherwise another thread could immediately add the entry back to the cache.
259    pub(crate) fn cache_remove(&self, _fs: &FatFilesystemInner, name: &str) -> Option<FatNode> {
260        let mut data = self.data.write();
261        data.children.remove(&name as &dyn InsensitiveStringRef).and_then(|entry| entry.upgrade())
262    }
263
264    /// Lookup a child entry in the cache.
265    pub fn cache_get(&self, name: &str) -> Option<FatNode> {
266        // Note that we don't remove an entry even if its Arc<> has
267        // gone away, to allow us to use the read-only lock here and avoid races.
268        let data = self.data.read();
269        data.children.get(&name as &dyn InsensitiveStringRef).and_then(|entry| entry.upgrade())
270    }
271
272    fn lookup(
273        self: &Arc<Self>,
274        flags: fio::OpenFlags,
275        mut path: Path,
276        closer: &mut Closer<'_>,
277    ) -> Result<FatNode, Status> {
278        let mut cur_entry = FatNode::Dir(self.clone());
279
280        while !path.is_empty() {
281            let child_flags =
282                if path.is_single_component() { flags } else { fio::OpenFlags::DIRECTORY };
283
284            match cur_entry {
285                FatNode::Dir(entry) => {
286                    let name = path.next().unwrap();
287                    validate_filename(name)?;
288                    cur_entry = entry.clone().open_child(name, child_flags, closer)?;
289                }
290                FatNode::File(_) => {
291                    return Err(Status::NOT_DIR);
292                }
293            };
294        }
295
296        Ok(cur_entry)
297    }
298
299    fn lookup_with_open3_flags(
300        self: &Arc<Self>,
301        flags: fio::Flags,
302        mut path: Path,
303        closer: &mut Closer<'_>,
304    ) -> Result<FatNode, Status> {
305        let mut current_entry = FatNode::Dir(self.clone());
306
307        while !path.is_empty() {
308            let child_flags =
309                if path.is_single_component() { flags } else { fio::Flags::PROTOCOL_DIRECTORY };
310
311            match current_entry {
312                FatNode::Dir(entry) => {
313                    let name = path.next().unwrap();
314                    validate_filename(name)?;
315                    current_entry = entry.clone().open3_child(name, child_flags, closer)?;
316                }
317                FatNode::File(_) => {
318                    return Err(Status::NOT_DIR);
319                }
320            };
321        }
322
323        Ok(current_entry)
324    }
325
326    /// Open a child entry with the given name.
327    /// Flags can be any of the following, matching their fuchsia.io definitions:
328    /// * OPEN_FLAG_CREATE
329    /// * OPEN_FLAG_CREATE_IF_ABSENT
330    /// * OPEN_FLAG_DIRECTORY
331    /// * OPEN_FLAG_NOT_DIRECTORY
332    pub(crate) fn open_child(
333        self: &Arc<Self>,
334        name: &str,
335        flags: fio::OpenFlags,
336        closer: &mut Closer<'_>,
337    ) -> Result<FatNode, Status> {
338        let fs_lock = self.filesystem.lock();
339        // First, check the cache.
340        if let Some(entry) = self.cache_get(name) {
341            check_open_flags_for_existing_entry(flags)?;
342            entry.open_ref(&fs_lock)?;
343            return Ok(closer.add(entry));
344        };
345
346        let mut created = false;
347        let node = {
348            // Cache failed - try the real filesystem.
349            let entry = self.find_child(&fs_lock, name)?;
350            if let Some(entry) = entry {
351                check_open_flags_for_existing_entry(flags)?;
352                if entry.is_dir() {
353                    self.add_directory(entry.to_dir(), name, closer)
354                } else {
355                    self.add_file(entry.to_file(), name, closer)
356                }
357            } else if flags.intersects(fio::OpenFlags::CREATE) {
358                // Child entry does not exist, but we've been asked to create it.
359                created = true;
360                let dir = self.borrow_dir(&fs_lock)?;
361                if flags.intersects(fio::OpenFlags::DIRECTORY) {
362                    let dir = dir.create_dir(name).map_err(fatfs_error_to_status)?;
363                    self.add_directory(dir, name, closer)
364                } else {
365                    let file = dir.create_file(name).map_err(fatfs_error_to_status)?;
366                    self.add_file(file, name, closer)
367                }
368            } else {
369                // Not creating, and no existing entry => not found.
370                return Err(Status::NOT_FOUND);
371            }
372        };
373
374        let mut data = self.data.write();
375        data.children.insert(InsensitiveString(name.to_owned()), node.downgrade());
376        if created {
377            data.watchers.send_event(&mut SingleNameEventProducer::added(name));
378            self.filesystem.mark_dirty();
379        }
380
381        Ok(node)
382    }
383
384    pub(crate) fn open3_child(
385        self: &Arc<Self>,
386        name: &str,
387        flags: fio::Flags,
388        closer: &mut Closer<'_>,
389    ) -> Result<FatNode, Status> {
390        if flags.create_unnamed_temporary_in_directory_path() {
391            return Err(Status::NOT_SUPPORTED);
392        }
393        let fs_lock = self.filesystem.lock();
394
395        // Check if the entry already exists in the cache.
396        if let Some(entry) = self.cache_get(name) {
397            if flags.creation_mode() == vfs::CreationMode::Always {
398                return Err(Status::ALREADY_EXISTS);
399            }
400            entry.open_ref(&fs_lock)?;
401            return Ok(closer.add(entry));
402        };
403
404        let mut created_entry = false;
405        let node = match self.find_child(&fs_lock, name)? {
406            Some(entry) => {
407                if flags.creation_mode() == vfs::CreationMode::Always {
408                    return Err(Status::ALREADY_EXISTS);
409                }
410                if entry.is_dir() {
411                    self.add_directory(entry.to_dir(), name, closer)
412                } else {
413                    self.add_file(entry.to_file(), name, closer)
414                }
415            }
416            None => {
417                if flags.creation_mode() == vfs::CreationMode::Never {
418                    return Err(Status::NOT_FOUND);
419                }
420                created_entry = true;
421                let dir = self.borrow_dir(&fs_lock)?;
422
423                // Create directory if the directory protocol was explicitly specified.
424                if flags.intersects(fio::Flags::PROTOCOL_DIRECTORY) {
425                    let dir = dir.create_dir(name).map_err(fatfs_error_to_status)?;
426                    self.add_directory(dir, name, closer)
427                } else {
428                    let file = dir.create_file(name).map_err(fatfs_error_to_status)?;
429                    self.add_file(file, name, closer)
430                }
431            }
432        };
433
434        let mut data = self.data.write();
435        data.children.insert(InsensitiveString(name.to_owned()), node.downgrade());
436        if created_entry {
437            data.watchers.send_event(&mut SingleNameEventProducer::added(name));
438            self.filesystem.mark_dirty();
439        }
440
441        Ok(node)
442    }
443
444    /// True if this directory has been deleted.
445    pub(crate) fn is_deleted(&self) -> bool {
446        self.data.read().deleted
447    }
448
449    /// Called to indicate a file or directory was removed from this directory.
450    pub(crate) fn did_remove(&self, name: &str) {
451        self.data.write().watchers.send_event(&mut SingleNameEventProducer::removed(name));
452    }
453
454    /// Called to indicate a file or directory was added to this directory.
455    pub(crate) fn did_add(&self, name: &str) {
456        self.data.write().watchers.send_event(&mut SingleNameEventProducer::added(name));
457    }
458
459    /// Do a simple rename of the file, without unlinking dst.
460    /// This assumes that either "dst" and "src" are the same file, or that "dst" has already been
461    /// unlinked.
462    fn rename_internal(
463        &self,
464        filesystem: &FatFilesystemInner,
465        src_dir: &Arc<FatDirectory>,
466        src_name: &str,
467        dst_name: &str,
468        existing: ExistingRef<'_, '_>,
469    ) -> Result<(), Status> {
470        // We're ready to go: remove the entry from the source cache, and close the reference to
471        // the underlying file (this ensures all pending writes, etc. have been flushed).
472        // We remove the entry with rename() below, and hold the filesystem lock so nothing will
473        // put the entry back in the cache. After renaming we also re-attach the entry to its
474        // parent.
475
476        // Do the rename.
477        let src_fatfs_dir = src_dir.borrow_dir(&filesystem)?;
478        let dst_fatfs_dir = self.borrow_dir(&filesystem)?;
479
480        match existing {
481            ExistingRef::None => {
482                src_fatfs_dir
483                    .rename(src_name, &dst_fatfs_dir, dst_name)
484                    .map_err(fatfs_error_to_status)?;
485            }
486            ExistingRef::File(file) => {
487                src_fatfs_dir
488                    .rename_over_file(src_name, &dst_fatfs_dir, dst_name, file)
489                    .map_err(fatfs_error_to_status)?;
490            }
491            ExistingRef::Dir(dir) => {
492                src_fatfs_dir
493                    .rename_over_dir(src_name, &dst_fatfs_dir, dst_name, dir)
494                    .map_err(fatfs_error_to_status)?;
495            }
496        }
497
498        src_dir.did_remove(src_name);
499        self.did_add(dst_name);
500
501        src_dir.fs().mark_dirty();
502
503        // TODO: do the watcher event for existing.
504
505        Ok(())
506    }
507
508    /// Helper for rename which returns FatNodes that need to be dropped without the fs lock held.
509    fn rename_locked(
510        self: &Arc<Self>,
511        filesystem: &FatFilesystemInner,
512        src_dir: &Arc<FatDirectory>,
513        src_name: &str,
514        dst_name: &str,
515        src_is_dir: bool,
516        closer: &mut Closer<'_>,
517    ) -> Result<(), Status> {
518        // Renaming a file to itself is trivial, but we do it after we've checked that the file
519        // exists and that src and dst have the same type.
520        if Arc::ptr_eq(&src_dir, self)
521            && (&src_name as &dyn InsensitiveStringRef) == (&dst_name as &dyn InsensitiveStringRef)
522        {
523            if src_name != dst_name {
524                // Cases don't match - we don't unlink, but we still need to fix the file's LFN.
525                return self.rename_internal(
526                    &filesystem,
527                    src_dir,
528                    src_name,
529                    dst_name,
530                    ExistingRef::None,
531                );
532            }
533            return Ok(());
534        }
535
536        // It's not legal to move a directory into itself or any child of itself.
537        if let Some(src_node) = src_dir.cache_get(src_name) {
538            if let FatNode::Dir(dir) = &src_node {
539                if Arc::ptr_eq(&dir, self) {
540                    return Err(Status::INVALID_ARGS);
541                }
542                // Walk the parents of the destination and make sure it doesn't match the source.
543                let mut dest = self.clone();
544                loop {
545                    let next_dir = if let Some(parent) = &dest.data.read().parent {
546                        if Arc::ptr_eq(&dir, parent) {
547                            return Err(Status::INVALID_ARGS);
548                        }
549                        parent.clone()
550                    } else {
551                        break;
552                    };
553                    dest = next_dir;
554                }
555            }
556            src_node.flush_dir_entry(filesystem)?;
557        }
558
559        let mut existing_node = self.cache_get(dst_name);
560        let remove_from_cache = existing_node.is_some();
561        let mut dir;
562        let mut file;
563        let mut borrowed_dir;
564        let mut borrowed_file;
565        let existing = match existing_node {
566            None => {
567                self.open_ref(filesystem)?;
568                closer.add(FatNode::Dir(self.clone()));
569                match self.find_child(filesystem, dst_name)? {
570                    Some(ref dir_entry) => {
571                        if dir_entry.is_dir() {
572                            dir = Some(dir_entry.to_dir());
573                            ExistingRef::Dir(dir.as_mut().unwrap())
574                        } else {
575                            file = Some(dir_entry.to_file());
576                            ExistingRef::File(file.as_mut().unwrap())
577                        }
578                    }
579                    None => ExistingRef::None,
580                }
581            }
582            Some(ref mut node) => {
583                node.open_ref(filesystem)?;
584                closer.add(node.clone());
585                match node {
586                    FatNode::Dir(ref mut node_dir) => {
587                        // Within `rename_internal` we will attempt to borrow the source and
588                        // destination directories. This can't be the destination directory, but we
589                        // must check that the directory here is not the same as the source
590                        // directory.
591                        if Arc::ptr_eq(node_dir, src_dir) {
592                            return Err(Status::INVALID_ARGS);
593                        }
594                        borrowed_dir = node_dir.borrow_dir_mut(filesystem).unwrap();
595                        ExistingRef::Dir(&mut *borrowed_dir)
596                    }
597                    FatNode::File(ref mut node_file) => {
598                        borrowed_file = node_file.borrow_file_mut(filesystem).unwrap();
599                        ExistingRef::File(&mut *borrowed_file)
600                    }
601                }
602            }
603        };
604
605        match existing {
606            ExistingRef::File(_) => {
607                if src_is_dir {
608                    return Err(Status::NOT_DIR);
609                }
610            }
611            ExistingRef::Dir(_) => {
612                if !src_is_dir {
613                    return Err(Status::NOT_FILE);
614                }
615            }
616            ExistingRef::None => {}
617        }
618
619        self.rename_internal(&filesystem, src_dir, src_name, dst_name, existing)?;
620
621        if remove_from_cache {
622            self.cache_remove(&filesystem, &dst_name).unwrap().did_delete();
623        }
624
625        // We suceeded in renaming, so now move the nodes around.
626        if let Some(node) = src_dir.remove_child(&filesystem, &src_name) {
627            self.add_child(&filesystem, dst_name.to_owned(), node)
628                .unwrap_or_else(|e| panic!("Rename failed, but fatfs says it didn't? - {:?}", e));
629        }
630
631        Ok(())
632    }
633
634    // Helper that adds a directory to the FatFilesystem
635    fn add_directory(
636        self: &Arc<Self>,
637        dir: Dir<'_>,
638        name: &str,
639        closer: &mut Closer<'_>,
640    ) -> FatNode {
641        // This is safe because we give the FatDirectory a FatFilesystem which ensures that the
642        // FatfsDirRef will not outlive its FatFilesystem.
643        let dir_ref = unsafe { FatfsDirRef::from(dir) };
644        closer.add(FatNode::Dir(FatDirectory::new(
645            dir_ref,
646            Some(self.clone()),
647            self.filesystem.clone(),
648            name.to_owned(),
649        )))
650    }
651
652    // Helper that adds a file to the FatFilesystem
653    fn add_file(self: &Arc<Self>, file: File<'_>, name: &str, closer: &mut Closer<'_>) -> FatNode {
654        // This is safe because we give the FatFile a FatFilesystem which ensures that the
655        // FatfsFileRef will not outlive its FatFilesystem.
656        let file_ref = unsafe { FatfsFileRef::from(file) };
657        closer.add(FatNode::File(FatFile::new(
658            file_ref,
659            self.clone(),
660            self.filesystem.clone(),
661            name.to_owned(),
662        )))
663    }
664}
665
666impl Node for FatDirectory {
667    /// Flush to disk and invalidate the reference that's contained within this FatDir.
668    /// Any operations on the directory will return Status::BAD_HANDLE until it is re-attached.
669    fn detach(&self, fs: &FatFilesystemInner) {
670        // This causes a flush to disk when the underlying fatfs Dir is dropped.
671        self.dir.borrow_mut().take(fs);
672    }
673
674    /// Re-open the underlying `FatfsDirRef` this directory represents, and attach to the given
675    /// parent.
676    fn attach(
677        &self,
678        new_parent: Arc<FatDirectory>,
679        name: &str,
680        fs: &FatFilesystemInner,
681    ) -> Result<(), Status> {
682        let mut data = self.data.write();
683        data.name = name.to_owned();
684
685        // Safe because we have a reference to the FatFilesystem.
686        unsafe { self.dir.borrow_mut().maybe_reopen(fs, Some(&new_parent), name)? };
687
688        assert!(data.parent.replace(new_parent).is_some());
689        Ok(())
690    }
691
692    fn did_delete(&self) {
693        let mut data = self.data.write();
694        data.parent.take();
695        data.watchers.send_event(&mut SingleNameEventProducer::deleted());
696        data.deleted = true;
697    }
698
699    fn open_ref(&self, fs: &FatFilesystemInner) -> Result<(), Status> {
700        let data = self.data.read();
701        unsafe { self.dir.borrow_mut().open(&fs, data.parent.as_ref(), &data.name) }
702    }
703
704    fn shut_down(&self, fs: &FatFilesystemInner) -> Result<(), Status> {
705        self.dir.borrow_mut().take(fs);
706        let mut data = self.data.write();
707        for (_, child) in data.children.drain() {
708            if let Some(child) = child.upgrade() {
709                child.shut_down(fs)?;
710            }
711        }
712        Ok(())
713    }
714
715    fn flush_dir_entry(&self, fs: &FatFilesystemInner) -> Result<(), Status> {
716        if let Some(ref mut dir) = self.borrow_dir_mut(fs) {
717            dir.flush_dir_entry().map_err(fatfs_error_to_status)?;
718        }
719        Ok(())
720    }
721
722    fn close_ref(&self, fs: &FatFilesystemInner) {
723        self.dir.borrow_mut().close(fs);
724    }
725}
726
727impl Debug for FatDirectory {
728    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
729        f.debug_struct("FatDirectory").field("parent", &self.data.read().parent).finish()
730    }
731}
732
733impl MutableDirectory for FatDirectory {
734    async fn unlink(self: Arc<Self>, name: &str, must_be_directory: bool) -> Result<(), Status> {
735        let fs_lock = self.filesystem.lock();
736        let parent = self.borrow_dir(&fs_lock)?;
737        let mut existing_node = self.cache_get(name);
738        let mut done = false;
739        match existing_node {
740            Some(FatNode::File(ref mut file)) => {
741                if must_be_directory {
742                    return Err(Status::NOT_DIR);
743                }
744                if let Some(mut file) = file.borrow_file_mut(&fs_lock) {
745                    parent.unlink_file(&mut *file).map_err(fatfs_error_to_status)?;
746                    done = true;
747                }
748            }
749            Some(FatNode::Dir(ref mut dir)) => {
750                if let Some(mut dir) = dir.borrow_dir_mut(&fs_lock) {
751                    parent.unlink_dir(&mut *dir).map_err(fatfs_error_to_status)?;
752                    done = true;
753                }
754            }
755            None => {
756                if must_be_directory {
757                    let entry = self.find_child(&fs_lock, name)?;
758                    if !entry.ok_or(Status::NOT_FOUND)?.is_dir() {
759                        return Err(Status::NOT_DIR);
760                    }
761                }
762            }
763        }
764        if !done {
765            parent.remove(name).map_err(fatfs_error_to_status)?;
766        }
767        if existing_node.is_some() {
768            self.cache_remove(&fs_lock, name);
769        }
770        match existing_node {
771            Some(FatNode::File(ref mut file)) => file.did_delete(),
772            Some(FatNode::Dir(ref mut dir)) => dir.did_delete(),
773            None => {}
774        }
775
776        self.filesystem.mark_dirty();
777        self.data.write().watchers.send_event(&mut SingleNameEventProducer::removed(name));
778        Ok(())
779    }
780
781    async fn update_attributes(
782        &self,
783        attributes: fio::MutableNodeAttributes,
784    ) -> Result<(), Status> {
785        const SUPPORTED_MUTABLE_ATTRIBUTES: fio::NodeAttributesQuery =
786            fio::NodeAttributesQuery::CREATION_TIME
787                .union(fio::NodeAttributesQuery::MODIFICATION_TIME);
788
789        if !SUPPORTED_MUTABLE_ATTRIBUTES
790            .contains(vfs::common::mutable_node_attributes_to_query(&attributes))
791        {
792            return Err(Status::NOT_SUPPORTED);
793        }
794
795        let fs_lock = self.filesystem.lock();
796        let mut dir = self.borrow_dir_mut(&fs_lock).ok_or(Status::BAD_HANDLE)?;
797        if let Some(creation_time) = attributes.creation_time {
798            dir.set_created(unix_to_dos_time(creation_time));
799        }
800        if let Some(modification_time) = attributes.modification_time {
801            dir.set_modified(unix_to_dos_time(modification_time));
802        }
803
804        self.filesystem.mark_dirty();
805        Ok(())
806    }
807
808    async fn sync(&self) -> Result<(), Status> {
809        // TODO(https://fxbug.dev/42132904): Support sync on root of fatfs volume.
810        Ok(())
811    }
812
813    fn rename(
814        self: Arc<Self>,
815        src_dir: Arc<dyn MutableDirectory>,
816        src_path: Path,
817        dst_path: Path,
818    ) -> BoxFuture<'static, Result<(), Status>> {
819        Box::pin(async move {
820            let src_dir =
821                src_dir.into_any().downcast::<FatDirectory>().map_err(|_| Status::INVALID_ARGS)?;
822            if self.is_deleted() {
823                // Can't rename into a deleted folder.
824                return Err(Status::NOT_FOUND);
825            }
826
827            let src_name = src_path.peek().unwrap();
828            validate_filename(src_name).map_err(fatfs_error_to_status)?;
829            let dst_name = dst_path.peek().unwrap();
830            validate_filename(dst_name).map_err(fatfs_error_to_status)?;
831
832            let mut closer = Closer::new(&self.filesystem);
833            let filesystem = self.filesystem.lock();
834
835            // Figure out if src is a directory.
836            let entry = src_dir.find_child(&filesystem, &src_name)?;
837            if entry.is_none() {
838                // No such src (if we don't return NOT_FOUND here, fatfs will return it when we
839                // call rename() later).
840                return Err(Status::NOT_FOUND);
841            }
842            let src_is_dir = entry.unwrap().is_dir();
843            if (dst_path.is_dir() || src_path.is_dir()) && !src_is_dir {
844                // The caller wanted a directory (src or dst), but src is not a directory. This is
845                // an error.
846                return Err(Status::NOT_DIR);
847            }
848
849            self.rename_locked(&filesystem, &src_dir, src_name, dst_name, src_is_dir, &mut closer)
850        })
851    }
852}
853
854impl DirectoryEntry for FatDirectory {
855    fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), Status> {
856        request.open_dir(self)
857    }
858}
859
860impl GetEntryInfo for FatDirectory {
861    fn entry_info(&self) -> EntryInfo {
862        EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)
863    }
864}
865
866impl vfs::node::Node for FatDirectory {
867    async fn get_attributes(
868        &self,
869        requested_attributes: fio::NodeAttributesQuery,
870    ) -> Result<fio::NodeAttributes2, Status> {
871        let fs_lock = self.filesystem.lock();
872        let dir = self.borrow_dir(&fs_lock)?;
873
874        let creation_time = dos_to_unix_time(dir.created());
875        let modification_time = dos_to_unix_time(dir.modified());
876        let access_time = dos_date_to_unix_time(dir.accessed());
877
878        Ok(attributes!(
879            requested_attributes,
880            Mutable {
881                creation_time: creation_time,
882                modification_time: modification_time,
883                access_time: access_time
884            },
885            Immutable {
886                protocols: fio::NodeProtocolKinds::DIRECTORY,
887                abilities: fio::Operations::GET_ATTRIBUTES
888                    | fio::Operations::UPDATE_ATTRIBUTES
889                    | fio::Operations::ENUMERATE
890                    | fio::Operations::TRAVERSE
891                    | fio::Operations::MODIFY_DIRECTORY,
892                link_count: 1, // FAT does not support hard links, so there is always 1 "link".
893            }
894        ))
895    }
896
897    fn close(self: Arc<Self>) {
898        self.close_ref(&self.filesystem.lock());
899    }
900
901    fn query_filesystem(&self) -> Result<fio::FilesystemInfo, Status> {
902        self.filesystem.query_filesystem()
903    }
904
905    fn will_clone(&self) {
906        self.open_ref(&self.filesystem.lock()).unwrap();
907    }
908}
909
910impl Directory for FatDirectory {
911    fn deprecated_open(
912        self: Arc<Self>,
913        scope: ExecutionScope,
914        flags: fio::OpenFlags,
915        path: Path,
916        server_end: ServerEnd<fio::NodeMarker>,
917    ) {
918        let mut closer = Closer::new(&self.filesystem);
919
920        flags.to_object_request(server_end).handle(|object_request| {
921            match self.lookup(flags, path, &mut closer)? {
922                FatNode::Dir(entry) => {
923                    let () = entry
924                        .open_ref(&self.filesystem.lock())
925                        .expect("entry should already be open");
926                    object_request
927                        .take()
928                        .create_connection_sync::<MutableConnection<_>, _>(scope, entry, flags);
929                    Ok(())
930                }
931                FatNode::File(entry) => {
932                    let () = entry.open_ref(&self.filesystem.lock())?;
933                    object_request
934                        .take()
935                        .create_connection_sync::<FidlIoConnection<_>, _>(scope, entry, flags);
936                    Ok(())
937                }
938            }
939        });
940    }
941
942    fn open(
943        self: Arc<Self>,
944        scope: ExecutionScope,
945        path: Path,
946        flags: fio::Flags,
947        object_request: ObjectRequestRef<'_>,
948    ) -> Result<(), Status> {
949        let mut closer = Closer::new(&self.filesystem);
950
951        match self.lookup_with_open3_flags(flags, path, &mut closer)? {
952            FatNode::Dir(entry) => {
953                let () = entry.open_ref(&self.filesystem.lock())?;
954                object_request
955                    .take()
956                    .create_connection_sync::<MutableConnection<_>, _>(scope, entry, flags);
957                Ok(())
958            }
959            FatNode::File(entry) => {
960                let () = entry.open_ref(&self.filesystem.lock())?;
961                object_request
962                    .take()
963                    .create_connection_sync::<FidlIoConnection<_>, _>(scope, entry, flags);
964                Ok(())
965            }
966        }
967    }
968
969    async fn read_dirents<'a>(
970        &'a self,
971        pos: &'a TraversalPosition,
972        sink: Box<dyn Sink>,
973    ) -> Result<(TraversalPosition, Box<dyn dirents_sink::Sealed>), Status> {
974        if self.is_deleted() {
975            return Ok((TraversalPosition::End, sink.seal()));
976        }
977
978        let fs_lock = self.filesystem.lock();
979        let dir = self.borrow_dir(&fs_lock)?;
980
981        if let TraversalPosition::End = pos {
982            return Ok((TraversalPosition::End, sink.seal()));
983        }
984
985        let filter = |name: &str| match pos {
986            TraversalPosition::Start => true,
987            TraversalPosition::Name(next_name) => name >= next_name.as_str(),
988            _ => false,
989        };
990
991        // Get all the entries in this directory.
992        let mut entries: Vec<_> = dir
993            .iter()
994            .filter_map(|maybe_entry| {
995                maybe_entry
996                    .map(|entry| {
997                        let name = entry.file_name();
998                        if &name == ".." || !filter(&name) {
999                            None
1000                        } else {
1001                            let entry_type = if entry.is_dir() {
1002                                fio::DirentType::Directory
1003                            } else {
1004                                fio::DirentType::File
1005                            };
1006                            Some((name, EntryInfo::new(fio::INO_UNKNOWN, entry_type)))
1007                        }
1008                    })
1009                    .transpose()
1010            })
1011            .collect::<std::io::Result<Vec<_>>>()?;
1012
1013        // If it's the root directory, we need to synthesize a "." entry if appropriate.
1014        if self.data.read().parent.is_none() && filter(".") {
1015            entries.push((
1016                ".".to_owned(),
1017                EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory),
1018            ));
1019        }
1020
1021        // Sort them by alphabetical order.
1022        entries.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
1023
1024        // Iterate through the entries, adding them one by one to the sink.
1025        let mut cur_sink = sink;
1026        for (name, info) in entries.into_iter() {
1027            let result = cur_sink.append(&info, &name.clone());
1028
1029            match result {
1030                AppendResult::Ok(new_sink) => cur_sink = new_sink,
1031                AppendResult::Sealed(sealed) => {
1032                    return Ok((TraversalPosition::Name(name), sealed));
1033                }
1034            }
1035        }
1036
1037        return Ok((TraversalPosition::End, cur_sink.seal()));
1038    }
1039
1040    fn register_watcher(
1041        self: Arc<Self>,
1042        scope: ExecutionScope,
1043        mask: fio::WatchMask,
1044        watcher: DirectoryWatcher,
1045    ) -> Result<(), Status> {
1046        let fs_lock = self.filesystem.lock();
1047        let mut data = self.data.write();
1048        let is_deleted = data.deleted;
1049        let is_root = data.parent.is_none();
1050        let controller = data.watchers.add(scope, self.clone(), mask, watcher);
1051        if mask.contains(fio::WatchMask::EXISTING) && !is_deleted {
1052            let entries = {
1053                let dir = self.borrow_dir(&fs_lock)?;
1054                let synthesized_dot = if is_root {
1055                    // We need to synthesize a "." entry.
1056                    Some(Ok(".".to_owned()))
1057                } else {
1058                    None
1059                };
1060                synthesized_dot
1061                    .into_iter()
1062                    .chain(dir.iter().filter_map(|maybe_entry| {
1063                        maybe_entry
1064                            .map(|entry| {
1065                                let name = entry.file_name();
1066                                if &name == ".." {
1067                                    None
1068                                } else {
1069                                    Some(name)
1070                                }
1071                            })
1072                            .transpose()
1073                    }))
1074                    .collect::<std::io::Result<Vec<String>>>()
1075                    .map_err(fatfs_error_to_status)?
1076            };
1077            controller.send_event(&mut StaticVecEventProducer::existing(entries));
1078        }
1079        controller.send_event(&mut SingleNameEventProducer::idle());
1080        Ok(())
1081    }
1082
1083    fn unregister_watcher(self: Arc<Self>, key: usize) {
1084        self.data.write().watchers.remove(key);
1085    }
1086}
1087
1088#[cfg(test)]
1089mod tests {
1090    // We only test things here that aren't covered by fs_tests.
1091    use super::*;
1092    use crate::tests::{TestDiskContents, TestFatDisk};
1093    use assert_matches::assert_matches;
1094    use futures::TryStreamExt;
1095    use scopeguard::defer;
1096    use vfs::directory::dirents_sink::Sealed;
1097    use vfs::node::Node as _;
1098    use vfs::ObjectRequest;
1099
1100    const TEST_DISK_SIZE: u64 = 2048 << 10; // 2048K
1101
1102    #[fuchsia::test(allow_stalls = false)]
1103    async fn test_link_fails() {
1104        let disk = TestFatDisk::empty_disk(TEST_DISK_SIZE);
1105        let structure = TestDiskContents::dir().add_child("test_file", "test file contents".into());
1106        structure.create(&disk.root_dir());
1107
1108        let fs = disk.into_fatfs();
1109        let dir = fs.get_fatfs_root();
1110        dir.open_ref(&fs.filesystem().lock()).expect("open_ref failed");
1111        defer! { dir.close_ref(&fs.filesystem().lock()) }
1112        assert_eq!(
1113            dir.clone().link("test2".to_owned(), dir.clone(), "test3").await.unwrap_err(),
1114            Status::NOT_SUPPORTED
1115        );
1116    }
1117
1118    #[derive(Clone)]
1119    struct DummySink {
1120        max_size: usize,
1121        entries: Vec<(String, EntryInfo)>,
1122        sealed: bool,
1123    }
1124
1125    impl DummySink {
1126        pub fn new(max_size: usize) -> Self {
1127            DummySink { max_size, entries: Vec::with_capacity(max_size), sealed: false }
1128        }
1129
1130        fn from_sealed(sealed: Box<dyn dirents_sink::Sealed>) -> Box<DummySink> {
1131            sealed.into()
1132        }
1133    }
1134
1135    impl From<Box<dyn dirents_sink::Sealed>> for Box<DummySink> {
1136        fn from(sealed: Box<dyn dirents_sink::Sealed>) -> Self {
1137            sealed.open().downcast::<DummySink>().unwrap()
1138        }
1139    }
1140
1141    impl Sink for DummySink {
1142        fn append(mut self: Box<Self>, entry: &EntryInfo, name: &str) -> AppendResult {
1143            assert!(!self.sealed);
1144            if self.entries.len() == self.max_size {
1145                AppendResult::Sealed(self.seal())
1146            } else {
1147                self.entries.push((name.to_owned(), entry.clone()));
1148                AppendResult::Ok(self)
1149            }
1150        }
1151
1152        fn seal(mut self: Box<Self>) -> Box<dyn Sealed> {
1153            self.sealed = true;
1154            self
1155        }
1156    }
1157
1158    impl Sealed for DummySink {
1159        fn open(self: Box<Self>) -> Box<dyn std::any::Any> {
1160            self
1161        }
1162    }
1163
1164    #[fuchsia::test]
1165    /// Test with a sink that can't handle the entire directory in one go.
1166    fn test_read_dirents_small_sink() {
1167        let disk = TestFatDisk::empty_disk(TEST_DISK_SIZE);
1168        let structure = TestDiskContents::dir()
1169            .add_child("test_file", "test file contents".into())
1170            .add_child("aaa", "this file is first".into())
1171            .add_child("qwerty", "hello".into())
1172            .add_child("directory", TestDiskContents::dir().add_child("a", "test".into()));
1173        structure.create(&disk.root_dir());
1174
1175        let fs = disk.into_fatfs();
1176        let dir = fs.get_fatfs_root();
1177
1178        dir.open_ref(&fs.filesystem().lock()).expect("open_ref failed");
1179        defer! { dir.close_ref(&fs.filesystem().lock()) }
1180
1181        let (pos, sealed) = futures::executor::block_on(
1182            dir.clone().read_dirents(&TraversalPosition::Start, Box::new(DummySink::new(4))),
1183        )
1184        .expect("read_dirents failed");
1185        assert_eq!(
1186            DummySink::from_sealed(sealed).entries,
1187            vec![
1188                (".".to_owned(), EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)),
1189                ("aaa".to_owned(), EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::File)),
1190                (
1191                    "directory".to_owned(),
1192                    EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)
1193                ),
1194                ("qwerty".to_owned(), EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::File)),
1195            ]
1196        );
1197
1198        // Read the next two entries.
1199        let (_, sealed) =
1200            futures::executor::block_on(dir.read_dirents(&pos, Box::new(DummySink::new(4))))
1201                .expect("read_dirents failed");
1202        assert_eq!(
1203            DummySink::from_sealed(sealed).entries,
1204            vec![("test_file".to_owned(), EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::File)),]
1205        );
1206    }
1207
1208    #[fuchsia::test]
1209    /// Test with a sink that can hold everything.
1210    fn test_read_dirents_big_sink() {
1211        let disk = TestFatDisk::empty_disk(TEST_DISK_SIZE);
1212        let structure = TestDiskContents::dir()
1213            .add_child("test_file", "test file contents".into())
1214            .add_child("aaa", "this file is first".into())
1215            .add_child("qwerty", "hello".into())
1216            .add_child("directory", TestDiskContents::dir().add_child("a", "test".into()));
1217        structure.create(&disk.root_dir());
1218
1219        let fs = disk.into_fatfs();
1220        let dir = fs.get_fatfs_root();
1221
1222        dir.open_ref(&fs.filesystem().lock()).expect("open_ref failed");
1223        defer! { dir.close_ref(&fs.filesystem().lock()) }
1224
1225        let (_, sealed) = futures::executor::block_on(
1226            dir.read_dirents(&TraversalPosition::Start, Box::new(DummySink::new(30))),
1227        )
1228        .expect("read_dirents failed");
1229        assert_eq!(
1230            DummySink::from_sealed(sealed).entries,
1231            vec![
1232                (".".to_owned(), EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)),
1233                ("aaa".to_owned(), EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::File)),
1234                (
1235                    "directory".to_owned(),
1236                    EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)
1237                ),
1238                ("qwerty".to_owned(), EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::File)),
1239                ("test_file".to_owned(), EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::File)),
1240            ]
1241        );
1242    }
1243
1244    #[fuchsia::test]
1245    fn test_read_dirents_with_entry_that_sorts_before_dot() {
1246        let disk = TestFatDisk::empty_disk(TEST_DISK_SIZE);
1247        let structure = TestDiskContents::dir().add_child("!", "!".into());
1248        structure.create(&disk.root_dir());
1249
1250        let fs = disk.into_fatfs();
1251        let dir = fs.get_fatfs_root();
1252
1253        dir.open_ref(&fs.filesystem().lock()).expect("open_ref failed");
1254        defer! { dir.close_ref(&fs.filesystem().lock()) }
1255
1256        let (pos, sealed) = futures::executor::block_on(
1257            dir.clone().read_dirents(&TraversalPosition::Start, Box::new(DummySink::new(1))),
1258        )
1259        .expect("read_dirents failed");
1260        assert_eq!(
1261            DummySink::from_sealed(sealed).entries,
1262            vec![("!".to_owned(), EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::File))]
1263        );
1264
1265        let (_, sealed) =
1266            futures::executor::block_on(dir.read_dirents(&pos, Box::new(DummySink::new(1))))
1267                .expect("read_dirents failed");
1268        assert_eq!(
1269            DummySink::from_sealed(sealed).entries,
1270            vec![(".".to_owned(), EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)),]
1271        );
1272    }
1273
1274    #[fuchsia::test(allow_stalls = false)]
1275    async fn test_deprecated_reopen_root() {
1276        let disk = TestFatDisk::empty_disk(TEST_DISK_SIZE);
1277        let structure = TestDiskContents::dir().add_child("test", "Hello".into());
1278        structure.create(&disk.root_dir());
1279
1280        let fs = disk.into_fatfs();
1281        let dir = fs.get_root().expect("get_root OK");
1282
1283        let proxy = vfs::directory::serve_read_only(dir.clone());
1284        proxy
1285            .close()
1286            .await
1287            .expect("Send request OK")
1288            .map_err(Status::from_raw)
1289            .expect("First close OK");
1290
1291        let proxy = vfs::directory::serve_read_only(dir.clone());
1292        proxy
1293            .close()
1294            .await
1295            .expect("Send request OK")
1296            .map_err(Status::from_raw)
1297            .expect("Second close OK");
1298        dir.close();
1299    }
1300
1301    #[fuchsia::test(allow_stalls = false)]
1302    async fn test_reopen_root() {
1303        let disk = TestFatDisk::empty_disk(TEST_DISK_SIZE);
1304        let structure = TestDiskContents::dir().add_child("test", "Hello".into());
1305        structure.create(&disk.root_dir());
1306
1307        let fs = disk.into_fatfs();
1308        let root = fs.get_root().expect("get_root failed");
1309
1310        // Open and close root.
1311        let proxy = vfs::directory::serve_read_only(root.clone());
1312        proxy
1313            .close()
1314            .await
1315            .expect("FIDL call failed")
1316            .map_err(Status::from_raw)
1317            .expect("First close failed");
1318
1319        // Re-open and close root at "test".
1320        let proxy = vfs::serve_directory(
1321            root.clone(),
1322            Path::validate_and_split("test").unwrap(),
1323            fio::PERM_READABLE,
1324        );
1325        proxy
1326            .close()
1327            .await
1328            .expect("FIDL call failed")
1329            .map_err(Status::from_raw)
1330            .expect("Second close failed");
1331
1332        root.close();
1333    }
1334
1335    #[fuchsia::test(allow_stalls = false)]
1336    async fn test_open_already_exists() {
1337        let disk = TestFatDisk::empty_disk(TEST_DISK_SIZE);
1338        let structure = TestDiskContents::dir().add_child("test", "Hello".into());
1339        structure.create(&disk.root_dir());
1340
1341        let fs = disk.into_fatfs();
1342        let root = fs.get_root().expect("get_root failed");
1343
1344        let scope = ExecutionScope::new();
1345        let (proxy, server_end) = fidl::endpoints::create_proxy::<fio::NodeMarker>();
1346        let flags = fio::PERM_READABLE
1347            | fio::Flags::FLAG_MUST_CREATE
1348            | fio::Flags::FLAG_SEND_REPRESENTATION;
1349        ObjectRequest::new(flags, &fio::Options::default(), server_end.into()).handle(|request| {
1350            root.clone().open(
1351                scope.clone(),
1352                Path::validate_and_split("test").unwrap(),
1353                flags,
1354                request,
1355            )
1356        });
1357
1358        let event =
1359            proxy.take_event_stream().try_next().await.expect_err("open passed unexpectedly");
1360
1361        assert_matches!(
1362            event,
1363            fidl::Error::ClientChannelClosed { status: Status::ALREADY_EXISTS, .. }
1364        );
1365
1366        root.close();
1367    }
1368
1369    #[fuchsia::test(allow_stalls = false)]
1370    async fn test_update_attributes_directory() {
1371        let disk = TestFatDisk::empty_disk(TEST_DISK_SIZE);
1372        let structure = TestDiskContents::dir().add_child("test", "Hello".into());
1373        structure.create(&disk.root_dir());
1374
1375        let fs = disk.into_fatfs();
1376        let root = fs.get_root().expect("get_root failed");
1377        let proxy = vfs::directory::serve(root.clone(), fio::PERM_READABLE | fio::PERM_WRITABLE);
1378
1379        let mut new_attrs = fio::MutableNodeAttributes {
1380            creation_time: Some(
1381                std::time::SystemTime::now()
1382                    .duration_since(std::time::SystemTime::UNIX_EPOCH)
1383                    .expect("SystemTime before UNIX EPOCH")
1384                    .as_nanos()
1385                    .try_into()
1386                    .unwrap(),
1387            ),
1388            ..Default::default()
1389        };
1390        proxy
1391            .update_attributes(&new_attrs)
1392            .await
1393            .expect("FIDL call failed")
1394            .map_err(Status::from_raw)
1395            .expect("update attributes failed");
1396
1397        new_attrs.mode = Some(123);
1398        let status = proxy
1399            .update_attributes(&new_attrs)
1400            .await
1401            .expect("FIDL call failed")
1402            .map_err(Status::from_raw)
1403            .expect_err("update unsupported attributes passed unexpectedly");
1404        assert_eq!(status, Status::NOT_SUPPORTED);
1405        root.close();
1406    }
1407
1408    #[fuchsia::test(allow_stalls = false)]
1409    async fn test_update_attributes_file() {
1410        let disk = TestFatDisk::empty_disk(TEST_DISK_SIZE);
1411        let structure = TestDiskContents::dir().add_child("test_file", "Hello".into());
1412        structure.create(&disk.root_dir());
1413
1414        let fs = disk.into_fatfs();
1415        let root = fs.get_root().expect("get_root failed");
1416        let proxy = vfs::serve_file(
1417            root.clone(),
1418            Path::validate_and_split("test_file").unwrap(),
1419            fio::PERM_WRITABLE,
1420        );
1421
1422        let mut new_attrs = fio::MutableNodeAttributes {
1423            creation_time: Some(
1424                std::time::SystemTime::now()
1425                    .duration_since(std::time::SystemTime::UNIX_EPOCH)
1426                    .expect("SystemTime before UNIX EPOCH")
1427                    .as_nanos()
1428                    .try_into()
1429                    .unwrap(),
1430            ),
1431            ..Default::default()
1432        };
1433        proxy
1434            .update_attributes(&new_attrs)
1435            .await
1436            .expect("FIDL call failed")
1437            .map_err(Status::from_raw)
1438            .expect("update attributes failed");
1439
1440        new_attrs.mode = Some(123);
1441        let status = proxy
1442            .update_attributes(&new_attrs)
1443            .await
1444            .expect("FIDL call failed")
1445            .map_err(Status::from_raw)
1446            .expect_err("update unsupported attributes passed unexpectedly");
1447        assert_eq!(status, Status::NOT_SUPPORTED);
1448        root.close();
1449    }
1450}