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