Skip to main content

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};
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.map_err(fatfs_error_to_status)?;
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).map_err(fatfs_error_to_status)?;
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).map_err(fatfs_error_to_status)?;
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(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(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(
962        &self,
963        pos: &TraversalPosition,
964        sink: Box<dyn dirents_sink::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            .map_err(fatfs_error_to_status)?;
1005
1006        // If it's the root directory, we need to synthesize a "." entry if appropriate.
1007        if self.data.read().parent.is_none() && filter(".") {
1008            entries.push((
1009                ".".to_owned(),
1010                EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory),
1011            ));
1012        }
1013
1014        // Sort them by alphabetical order.
1015        entries.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
1016
1017        // Iterate through the entries, adding them one by one to the sink.
1018        let mut cur_sink = sink;
1019        for (name, info) in entries.into_iter() {
1020            let result = cur_sink.append(&info, &name.clone());
1021
1022            match result {
1023                AppendResult::Ok(new_sink) => cur_sink = new_sink,
1024                AppendResult::Sealed(sealed) => {
1025                    return Ok((TraversalPosition::Name(name), sealed));
1026                }
1027            }
1028        }
1029
1030        return Ok((TraversalPosition::End, cur_sink.seal()));
1031    }
1032
1033    fn register_watcher(
1034        self: Arc<Self>,
1035        scope: ExecutionScope,
1036        mask: fio::WatchMask,
1037        watcher: DirectoryWatcher,
1038    ) -> Result<(), Status> {
1039        let fs_lock = self.filesystem.lock();
1040        let mut data = self.data.write();
1041        let is_deleted = data.deleted;
1042        let is_root = data.parent.is_none();
1043        let controller = data.watchers.add(scope, self.clone(), mask, watcher);
1044        if mask.contains(fio::WatchMask::EXISTING) && !is_deleted {
1045            let entries = {
1046                let dir = self.borrow_dir(&fs_lock)?;
1047                let synthesized_dot = if is_root {
1048                    // We need to synthesize a "." entry.
1049                    Some(Ok(".".to_owned()))
1050                } else {
1051                    None
1052                };
1053                synthesized_dot
1054                    .into_iter()
1055                    .chain(dir.iter().filter_map(|maybe_entry| {
1056                        maybe_entry
1057                            .map(|entry| {
1058                                let name = entry.file_name();
1059                                if &name == ".." { None } else { Some(name) }
1060                            })
1061                            .transpose()
1062                    }))
1063                    .collect::<std::io::Result<Vec<String>>>()
1064                    .map_err(fatfs_error_to_status)?
1065            };
1066            controller.send_event(&mut StaticVecEventProducer::existing(entries));
1067        }
1068        controller.send_event(&mut SingleNameEventProducer::idle());
1069        Ok(())
1070    }
1071
1072    fn unregister_watcher(self: Arc<Self>, key: usize) {
1073        self.data.write().watchers.remove(key);
1074    }
1075}
1076
1077#[cfg(test)]
1078mod tests {
1079    // We only test things here that aren't covered by fs_tests.
1080    use super::*;
1081    use crate::tests::{TestDiskContents, TestFatDisk};
1082    use assert_matches::assert_matches;
1083    use futures::TryStreamExt;
1084    use scopeguard::defer;
1085    use vfs::ObjectRequest;
1086    use vfs::directory::dirents_sink::{Sealed, Sink};
1087    use vfs::node::Node as _;
1088
1089    const TEST_DISK_SIZE: u64 = 2048 << 10; // 2048K
1090
1091    #[fuchsia::test(allow_stalls = false)]
1092    async fn test_link_fails() {
1093        let disk = TestFatDisk::empty_disk(TEST_DISK_SIZE);
1094        let structure = TestDiskContents::dir().add_child("test_file", "test file contents".into());
1095        structure.create(&disk.root_dir());
1096
1097        let fs = disk.into_fatfs();
1098        let dir = fs.get_fatfs_root();
1099        dir.open_ref(&fs.filesystem().lock()).expect("open_ref failed");
1100        defer! { dir.close_ref(&fs.filesystem().lock()) }
1101        assert_eq!(
1102            dir.clone().link("test2".to_owned(), dir.clone(), "test3").await.unwrap_err(),
1103            Status::NOT_SUPPORTED
1104        );
1105    }
1106
1107    #[derive(Clone)]
1108    struct DummySink {
1109        max_size: usize,
1110        entries: Vec<(String, EntryInfo)>,
1111        sealed: bool,
1112    }
1113
1114    impl DummySink {
1115        pub fn new(max_size: usize) -> Self {
1116            DummySink { max_size, entries: Vec::with_capacity(max_size), sealed: false }
1117        }
1118
1119        fn from_sealed(sealed: Box<dyn dirents_sink::Sealed>) -> Box<DummySink> {
1120            sealed.into()
1121        }
1122    }
1123
1124    impl From<Box<dyn dirents_sink::Sealed>> for Box<DummySink> {
1125        fn from(sealed: Box<dyn dirents_sink::Sealed>) -> Self {
1126            sealed.open().downcast::<DummySink>().unwrap()
1127        }
1128    }
1129
1130    impl Sink for DummySink {
1131        fn append(mut self: Box<Self>, entry: &EntryInfo, name: &str) -> AppendResult {
1132            assert!(!self.sealed);
1133            if self.entries.len() == self.max_size {
1134                AppendResult::Sealed(self.seal())
1135            } else {
1136                self.entries.push((name.to_owned(), entry.clone()));
1137                AppendResult::Ok(self)
1138            }
1139        }
1140
1141        fn seal(mut self: Box<Self>) -> Box<dyn Sealed> {
1142            self.sealed = true;
1143            self
1144        }
1145    }
1146
1147    impl Sealed for DummySink {
1148        fn open(self: Box<Self>) -> Box<dyn std::any::Any> {
1149            self
1150        }
1151    }
1152
1153    #[fuchsia::test]
1154    /// Test with a sink that can't handle the entire directory in one go.
1155    fn test_read_dirents_small_sink() {
1156        let disk = TestFatDisk::empty_disk(TEST_DISK_SIZE);
1157        let structure = TestDiskContents::dir()
1158            .add_child("test_file", "test file contents".into())
1159            .add_child("aaa", "this file is first".into())
1160            .add_child("qwerty", "hello".into())
1161            .add_child("directory", TestDiskContents::dir().add_child("a", "test".into()));
1162        structure.create(&disk.root_dir());
1163
1164        let fs = disk.into_fatfs();
1165        let dir = fs.get_fatfs_root();
1166
1167        dir.open_ref(&fs.filesystem().lock()).expect("open_ref failed");
1168        defer! { dir.close_ref(&fs.filesystem().lock()) }
1169
1170        let (pos, sealed) = futures::executor::block_on(
1171            dir.clone().read_dirents(&TraversalPosition::Start, Box::new(DummySink::new(4))),
1172        )
1173        .expect("read_dirents failed");
1174        assert_eq!(
1175            DummySink::from_sealed(sealed).entries,
1176            vec![
1177                (".".to_owned(), EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)),
1178                ("aaa".to_owned(), EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::File)),
1179                (
1180                    "directory".to_owned(),
1181                    EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)
1182                ),
1183                ("qwerty".to_owned(), EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::File)),
1184            ]
1185        );
1186
1187        // Read the next two entries.
1188        let (_, sealed) =
1189            futures::executor::block_on(dir.read_dirents(&pos, Box::new(DummySink::new(4))))
1190                .expect("read_dirents failed");
1191        assert_eq!(
1192            DummySink::from_sealed(sealed).entries,
1193            vec![("test_file".to_owned(), EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::File)),]
1194        );
1195    }
1196
1197    #[fuchsia::test]
1198    /// Test with a sink that can hold everything.
1199    fn test_read_dirents_big_sink() {
1200        let disk = TestFatDisk::empty_disk(TEST_DISK_SIZE);
1201        let structure = TestDiskContents::dir()
1202            .add_child("test_file", "test file contents".into())
1203            .add_child("aaa", "this file is first".into())
1204            .add_child("qwerty", "hello".into())
1205            .add_child("directory", TestDiskContents::dir().add_child("a", "test".into()));
1206        structure.create(&disk.root_dir());
1207
1208        let fs = disk.into_fatfs();
1209        let dir = fs.get_fatfs_root();
1210
1211        dir.open_ref(&fs.filesystem().lock()).expect("open_ref failed");
1212        defer! { dir.close_ref(&fs.filesystem().lock()) }
1213
1214        let (_, sealed) = futures::executor::block_on(
1215            dir.read_dirents(&TraversalPosition::Start, Box::new(DummySink::new(30))),
1216        )
1217        .expect("read_dirents failed");
1218        assert_eq!(
1219            DummySink::from_sealed(sealed).entries,
1220            vec![
1221                (".".to_owned(), EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)),
1222                ("aaa".to_owned(), EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::File)),
1223                (
1224                    "directory".to_owned(),
1225                    EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)
1226                ),
1227                ("qwerty".to_owned(), EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::File)),
1228                ("test_file".to_owned(), EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::File)),
1229            ]
1230        );
1231    }
1232
1233    #[fuchsia::test]
1234    fn test_read_dirents_with_entry_that_sorts_before_dot() {
1235        let disk = TestFatDisk::empty_disk(TEST_DISK_SIZE);
1236        let structure = TestDiskContents::dir().add_child("!", "!".into());
1237        structure.create(&disk.root_dir());
1238
1239        let fs = disk.into_fatfs();
1240        let dir = fs.get_fatfs_root();
1241
1242        dir.open_ref(&fs.filesystem().lock()).expect("open_ref failed");
1243        defer! { dir.close_ref(&fs.filesystem().lock()) }
1244
1245        let (pos, sealed) = futures::executor::block_on(
1246            dir.clone().read_dirents(&TraversalPosition::Start, Box::new(DummySink::new(1))),
1247        )
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::File))]
1252        );
1253
1254        let (_, sealed) =
1255            futures::executor::block_on(dir.read_dirents(&pos, Box::new(DummySink::new(1))))
1256                .expect("read_dirents failed");
1257        assert_eq!(
1258            DummySink::from_sealed(sealed).entries,
1259            vec![(".".to_owned(), EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)),]
1260        );
1261    }
1262
1263    #[fuchsia::test(allow_stalls = false)]
1264    async fn test_deprecated_reopen_root() {
1265        let disk = TestFatDisk::empty_disk(TEST_DISK_SIZE);
1266        let structure = TestDiskContents::dir().add_child("test", "Hello".into());
1267        structure.create(&disk.root_dir());
1268
1269        let fs = disk.into_fatfs();
1270        let dir = fs.get_root().expect("get_root OK");
1271
1272        let proxy = vfs::directory::serve_read_only(dir.clone(), ExecutionScope::new());
1273        proxy
1274            .close()
1275            .await
1276            .expect("Send request OK")
1277            .map_err(Status::from_raw)
1278            .expect("First close OK");
1279
1280        let proxy = vfs::directory::serve_read_only(dir.clone(), ExecutionScope::new());
1281        proxy
1282            .close()
1283            .await
1284            .expect("Send request OK")
1285            .map_err(Status::from_raw)
1286            .expect("Second close OK");
1287        dir.close();
1288    }
1289
1290    #[fuchsia::test(allow_stalls = false)]
1291    async fn test_reopen_root() {
1292        let disk = TestFatDisk::empty_disk(TEST_DISK_SIZE);
1293        let structure = TestDiskContents::dir().add_child("test", "Hello".into());
1294        structure.create(&disk.root_dir());
1295
1296        let fs = disk.into_fatfs();
1297        let root = fs.get_root().expect("get_root failed");
1298
1299        // Open and close root.
1300        let proxy = vfs::directory::serve_read_only(root.clone(), ExecutionScope::new());
1301        proxy
1302            .close()
1303            .await
1304            .expect("FIDL call failed")
1305            .map_err(Status::from_raw)
1306            .expect("First close failed");
1307
1308        // Re-open and close root at "test".
1309        let proxy = vfs::serve_directory(
1310            root.clone(),
1311            Path::validate_and_split("test").unwrap(),
1312            ExecutionScope::new(),
1313            fio::PERM_READABLE,
1314        );
1315        proxy
1316            .close()
1317            .await
1318            .expect("FIDL call failed")
1319            .map_err(Status::from_raw)
1320            .expect("Second close failed");
1321
1322        root.close();
1323    }
1324
1325    #[fuchsia::test(allow_stalls = false)]
1326    async fn test_open_already_exists() {
1327        let disk = TestFatDisk::empty_disk(TEST_DISK_SIZE);
1328        let structure = TestDiskContents::dir().add_child("test", "Hello".into());
1329        structure.create(&disk.root_dir());
1330
1331        let fs = disk.into_fatfs();
1332        let root = fs.get_root().expect("get_root failed");
1333
1334        let scope = ExecutionScope::new();
1335        let (proxy, server_end) = fidl::endpoints::create_proxy::<fio::NodeMarker>();
1336        let flags = fio::PERM_READABLE
1337            | fio::Flags::FLAG_MUST_CREATE
1338            | fio::Flags::FLAG_SEND_REPRESENTATION;
1339        ObjectRequest::new(flags, &fio::Options::default(), server_end.into()).handle(|request| {
1340            root.clone().open(
1341                scope.clone(),
1342                Path::validate_and_split("test").unwrap(),
1343                flags,
1344                request,
1345            )
1346        });
1347
1348        let event =
1349            proxy.take_event_stream().try_next().await.expect_err("open passed unexpectedly");
1350
1351        assert_matches!(
1352            event,
1353            fidl::Error::ClientChannelClosed { status: Status::ALREADY_EXISTS, .. }
1354        );
1355
1356        root.close();
1357    }
1358
1359    #[fuchsia::test(allow_stalls = false)]
1360    async fn test_update_attributes_directory() {
1361        let disk = TestFatDisk::empty_disk(TEST_DISK_SIZE);
1362        let structure = TestDiskContents::dir().add_child("test", "Hello".into());
1363        structure.create(&disk.root_dir());
1364
1365        let fs = disk.into_fatfs();
1366        let root = fs.get_root().expect("get_root failed");
1367        let proxy = vfs::directory::serve(
1368            root.clone(),
1369            ExecutionScope::new(),
1370            fio::PERM_READABLE | fio::PERM_WRITABLE,
1371        );
1372
1373        let mut new_attrs = fio::MutableNodeAttributes {
1374            creation_time: Some(
1375                std::time::SystemTime::now()
1376                    .duration_since(std::time::SystemTime::UNIX_EPOCH)
1377                    .expect("SystemTime before UNIX EPOCH")
1378                    .as_nanos()
1379                    .try_into()
1380                    .unwrap(),
1381            ),
1382            ..Default::default()
1383        };
1384        proxy
1385            .update_attributes(&new_attrs)
1386            .await
1387            .expect("FIDL call failed")
1388            .map_err(Status::from_raw)
1389            .expect("update attributes failed");
1390
1391        new_attrs.mode = Some(123);
1392        let status = proxy
1393            .update_attributes(&new_attrs)
1394            .await
1395            .expect("FIDL call failed")
1396            .map_err(Status::from_raw)
1397            .expect_err("update unsupported attributes passed unexpectedly");
1398        assert_eq!(status, Status::NOT_SUPPORTED);
1399        root.close();
1400    }
1401
1402    #[fuchsia::test(allow_stalls = false)]
1403    async fn test_update_attributes_file() {
1404        let disk = TestFatDisk::empty_disk(TEST_DISK_SIZE);
1405        let structure = TestDiskContents::dir().add_child("test_file", "Hello".into());
1406        structure.create(&disk.root_dir());
1407
1408        let fs = disk.into_fatfs();
1409        let root = fs.get_root().expect("get_root failed");
1410        let proxy = vfs::serve_file(
1411            root.clone(),
1412            Path::validate_and_split("test_file").unwrap(),
1413            ExecutionScope::new(),
1414            fio::PERM_WRITABLE,
1415        );
1416
1417        let mut new_attrs = fio::MutableNodeAttributes {
1418            creation_time: Some(
1419                std::time::SystemTime::now()
1420                    .duration_since(std::time::SystemTime::UNIX_EPOCH)
1421                    .expect("SystemTime before UNIX EPOCH")
1422                    .as_nanos()
1423                    .try_into()
1424                    .unwrap(),
1425            ),
1426            ..Default::default()
1427        };
1428        proxy
1429            .update_attributes(&new_attrs)
1430            .await
1431            .expect("FIDL call failed")
1432            .map_err(Status::from_raw)
1433            .expect("update attributes failed");
1434
1435        new_attrs.mode = Some(123);
1436        let status = proxy
1437            .update_attributes(&new_attrs)
1438            .await
1439            .expect("FIDL call failed")
1440            .map_err(Status::from_raw)
1441            .expect_err("update unsupported attributes passed unexpectedly");
1442        assert_eq!(status, Status::NOT_SUPPORTED);
1443        root.close();
1444    }
1445}