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