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