Skip to main content

fxfs_platform_testing/fuchsia/
directory.rs

1// Copyright 2021 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.
4
5use crate::fuchsia::device::BlockServer;
6use crate::fuchsia::dirent_cache::DirentCacheKey;
7use crate::fuchsia::errors::map_to_status;
8use crate::fuchsia::file::{FlushType, FxFile};
9use crate::fuchsia::node::{FxNode, GetResult, OpenedNode};
10use crate::fuchsia::symlink::FxSymlink;
11use crate::fuchsia::volume::{FxVolume, RootDir};
12use anyhow::{Error, bail};
13use either::{Left, Right};
14use fidl::endpoints::ServerEnd;
15use fidl_fuchsia_io as fio;
16use fidl_fuchsia_storage_block::BlockMarker;
17use fuchsia_sync::Mutex;
18use futures::future::BoxFuture;
19use fxfs::errors::FxfsError;
20use fxfs::filesystem::{SyncOptions, TruncateGuard};
21use fxfs::log::*;
22use fxfs::object_store::directory::{self, ReplacedChild};
23use fxfs::object_store::transaction::{
24    LockKey, Options, ReservationOptions, Transaction, lock_keys,
25};
26use fxfs::object_store::{self, Directory, ObjectDescriptor, ObjectStore, Timestamp};
27use fxfs_crypto::WrappingKeyId;
28use fxfs_macros::ToWeakNode;
29use std::any::Any;
30use std::sync::Arc;
31use vfs::directory::dirents_sink::{self, AppendResult};
32use vfs::directory::entry::{DirectoryEntry, EntryInfo, GetEntryInfo, OpenRequest};
33use vfs::directory::entry_container::{
34    Directory as VfsDirectory, DirectoryWatcher, MutableDirectory,
35};
36use vfs::directory::mutable::connection::MutableConnection;
37use vfs::directory::traversal_position::TraversalPosition;
38use vfs::directory::watchers::Watchers;
39use vfs::directory::watchers::event_producers::SingleNameEventProducer;
40use vfs::execution_scope::ExecutionScope;
41use vfs::path::Path;
42use vfs::{ObjectRequest, ObjectRequestRef, ProtocolsExt, ToObjectRequest, attributes, symlink};
43
44struct ReplaceWithPurgeResult<'a> {
45    transaction: Transaction<'a>,
46    truncate_guard: Option<TruncateGuard<'static>>,
47    replace_result: ReplacedChild,
48    moved_node: Option<Arc<dyn FxNode>>,
49    actual_src_name: String,
50    actual_dst_name: String,
51}
52
53#[derive(ToWeakNode)]
54pub struct FxDirectory {
55    // The root directory is the only directory which has no parent, and its parent can never
56    // change, hence the Option can go on the outside.
57    parent: Option<Mutex<Arc<FxDirectory>>>,
58    directory: object_store::Directory<FxVolume>,
59    watchers: Mutex<Watchers>,
60}
61
62impl RootDir for FxDirectory {
63    fn as_directory_entry(self: Arc<Self>) -> Arc<dyn DirectoryEntry> {
64        self
65    }
66
67    fn serve(self: Arc<Self>, flags: fio::Flags, server_end: ServerEnd<fio::DirectoryMarker>) {
68        let scope = self.volume().scope().clone();
69        vfs::directory::serve_on(self, flags, scope, server_end);
70    }
71
72    fn as_node(self: Arc<Self>) -> Arc<dyn FxNode> {
73        self as Arc<dyn FxNode>
74    }
75}
76
77impl FxDirectory {
78    pub(super) fn new(
79        parent: Option<Arc<FxDirectory>>,
80        directory: object_store::Directory<FxVolume>,
81    ) -> Self {
82        Self {
83            parent: parent.map(|p| Mutex::new(p)),
84            directory,
85            watchers: Mutex::new(Watchers::new()),
86        }
87    }
88
89    pub fn directory(&self) -> &object_store::Directory<FxVolume> {
90        &self.directory
91    }
92
93    pub fn volume(&self) -> &Arc<FxVolume> {
94        self.directory.owner()
95    }
96
97    pub fn store(&self) -> &ObjectStore {
98        self.directory.store()
99    }
100
101    pub fn is_deleted(&self) -> bool {
102        self.directory.is_deleted()
103    }
104
105    pub fn set_deleted(&self) {
106        self.directory.set_deleted();
107        self.watchers.lock().send_event(&mut SingleNameEventProducer::deleted());
108    }
109
110    async fn lookup(
111        self: &Arc<Self>,
112        protocols: &dyn ProtocolsExt,
113        mut path: Path,
114        request: &ObjectRequest,
115    ) -> Result<OpenedNode<dyn FxNode>, Error> {
116        if path.is_empty() {
117            return if protocols.create_unnamed_temporary_in_directory_path() {
118                self.create_unnamed_temporary_file(request.create_attributes()).await
119            } else {
120                Ok(OpenedNode::new(self.clone()))
121            };
122        }
123        let store = self.store();
124        let fs = store.filesystem();
125        let mut current_node = self.clone() as Arc<dyn FxNode>;
126        loop {
127            let last_segment = path.is_single_component();
128            let current_dir =
129                current_node.into_any().downcast::<FxDirectory>().map_err(|_| FxfsError::NotDir)?;
130            let name = path.next().unwrap();
131
132            // Create the transaction here if we might need to create the object so that we have a
133            // lock in place.
134            let keys = lock_keys![LockKey::object(
135                store.store_object_id(),
136                current_dir.directory.object_id()
137            )];
138            let create_object = last_segment
139                && matches!(
140                    protocols.creation_mode(),
141                    vfs::CreationMode::AllowExisting | vfs::CreationMode::Always
142                );
143            let transaction_or_guard = if create_object {
144                Left(store.new_transaction(keys, Options::default()).await?)
145            } else {
146                // When child objects are created, the object is created along with the
147                // directory entry in the same transaction, and so we need to hold a read lock
148                // over the lookup and open calls.
149                Right(fs.lock_manager().read_lock(keys).await)
150            };
151
152            let is_casefold = current_dir.directory.dir_type().is_casefold();
153            let child_descriptor = {
154                match self.directory.owner().dirent_cache().lookup(&(
155                    current_dir.object_id(),
156                    name,
157                    is_casefold,
158                )) {
159                    Some(node) => {
160                        let desc = node.object_descriptor();
161                        Some((node, desc))
162                    }
163                    None => {
164                        if let Some((object_id, object_descriptor, locked)) =
165                            current_dir.directory.lookup(name).await?
166                        {
167                            let child_node = self
168                                .volume()
169                                .get_or_load_node(
170                                    object_id,
171                                    object_descriptor.clone(),
172                                    Some(current_dir.clone()),
173                                )
174                                .await?;
175                            // Do not add a locked encrypted child to the dirent cache. That way, if
176                            // a user opens a locked directory, unlocks the directory, and then
177                            // reopens the directory, fxfs does not return the cached locked node.
178                            if !locked {
179                                self.directory.owner().dirent_cache().insert(
180                                    DirentCacheKey::new(
181                                        current_dir.object_id(),
182                                        name.to_owned(),
183                                        is_casefold,
184                                    ),
185                                    child_node.clone(),
186                                );
187                            }
188                            Some((child_node, object_descriptor))
189                        } else {
190                            None
191                        }
192                    }
193                }
194            };
195
196            match child_descriptor {
197                Some((child_node, object_descriptor)) => {
198                    if transaction_or_guard.is_left()
199                        && protocols.creation_mode() == vfs::CreationMode::Always
200                    {
201                        bail!(FxfsError::AlreadyExists);
202                    }
203                    if last_segment {
204                        if protocols.create_unnamed_temporary_in_directory_path() {
205                            if !matches!(object_descriptor, ObjectDescriptor::Directory) {
206                                bail!(FxfsError::WrongType);
207                            }
208                            let dir = child_node
209                                .into_any()
210                                .downcast::<FxDirectory>()
211                                .map_err(|_| FxfsError::Inconsistent)?;
212                            return dir
213                                .create_unnamed_temporary_file(request.create_attributes())
214                                .await;
215                        }
216
217                        match object_descriptor {
218                            ObjectDescriptor::Directory => {
219                                if !protocols.is_node() && !protocols.is_dir_allowed() {
220                                    if protocols.is_file_allowed() {
221                                        bail!(FxfsError::NotFile)
222                                    } else {
223                                        bail!(FxfsError::WrongType)
224                                    }
225                                }
226                            }
227                            ObjectDescriptor::File => {
228                                if !protocols.is_node() && !protocols.is_file_allowed() {
229                                    if protocols.is_dir_allowed() {
230                                        bail!(FxfsError::NotDir)
231                                    } else {
232                                        bail!(FxfsError::WrongType)
233                                    }
234                                }
235                            }
236                            ObjectDescriptor::Symlink => {
237                                if !protocols.is_node() && !protocols.is_symlink_allowed() {
238                                    bail!(FxfsError::WrongType)
239                                }
240                            }
241                            ObjectDescriptor::Volume => bail!(FxfsError::Inconsistent),
242                        }
243                    }
244                    current_node = child_node;
245                    if last_segment {
246                        // We must make sure to take an open-count whilst we are holding a read
247                        // lock.
248                        return Ok(OpenedNode::new(current_node));
249                    }
250                }
251                None => {
252                    if let Left(mut transaction) = transaction_or_guard {
253                        let new_node = current_dir
254                            .create_child(
255                                &mut transaction,
256                                name,
257                                protocols.create_directory(),
258                                request.create_attributes(),
259                            )
260                            .await?;
261                        if let GetResult::Placeholder(p) =
262                            self.volume().cache().get_or_reserve(new_node.object_id()).await
263                        {
264                            return transaction
265                                .commit_with_callback(|_| {
266                                    p.commit(&new_node);
267                                    current_dir.did_add(name, Some(new_node.clone()));
268                                    // NOTE: We don't take the open count until here in case the
269                                    // transaction fails.
270                                    OpenedNode::new(new_node)
271                                })
272                                .await;
273                        } else {
274                            // We created a node, but the object ID was already used in the cache,
275                            // which suggests a object ID was reused (which would either be a bug or
276                            // corruption).
277                            bail!(FxfsError::Inconsistent);
278                        }
279                    } else {
280                        bail!(FxfsError::NotFound);
281                    }
282                }
283            };
284        }
285    }
286
287    async fn create_child(
288        self: &Arc<Self>,
289        transaction: &mut Transaction<'_>,
290        name: &str,
291        create_dir: bool, // If false, creates a file.
292        create_attributes: Option<&fio::MutableNodeAttributes>,
293    ) -> Result<Arc<dyn FxNode>, Error> {
294        if create_dir {
295            let dir = Arc::new(FxDirectory::new(
296                Some(self.clone()),
297                self.directory.create_child_dir(transaction, name).await?,
298            ));
299            if let Some(attrs) = create_attributes {
300                dir.directory().handle().update_attributes(transaction, Some(&attrs), None).await?;
301            }
302            Ok(dir as Arc<dyn FxNode>)
303        } else {
304            let file = FxFile::new(self.directory.create_child_file(transaction, name).await?);
305            if let Some(attrs) = create_attributes {
306                file.handle()
307                    .uncached_handle()
308                    .update_attributes(transaction, Some(&attrs), None)
309                    .await?;
310            }
311            Ok(file as Arc<dyn FxNode>)
312        }
313    }
314
315    pub(crate) async fn create_unnamed_temporary_file(
316        self: &Arc<Self>,
317        create_attributes: Option<&fio::MutableNodeAttributes>,
318    ) -> Result<OpenedNode<dyn FxNode>, Error> {
319        let store = self.store();
320        let keys = lock_keys![LockKey::object(store.store_object_id(), self.directory.object_id())];
321        let mut transaction = store.new_transaction(keys, Options::default()).await?;
322        let file = FxFile::new(
323            self.directory.create_child_unnamed_temporary_file(&mut transaction).await?,
324        );
325        if let Some(attrs) = create_attributes {
326            file.handle()
327                .uncached_handle()
328                .update_attributes(&mut transaction, Some(&attrs), None)
329                .await?;
330        }
331        let GetResult::Placeholder(p) =
332            self.volume().cache().get_or_reserve(file.object_id()).await
333        else {
334            bail!(FxfsError::Inconsistent);
335        };
336        transaction
337            .commit_with_callback(|_| {
338                let file = file.open_as_temporary();
339                p.commit(&file);
340                file
341            })
342            .await
343    }
344
345    fn remove_from_dirent_cache(&self, name: &str) {
346        let is_casefold = self.directory.dir_type().is_casefold();
347        self.directory.owner().dirent_cache().remove(&(
348            self.directory.object_id(),
349            name,
350            is_casefold,
351        ));
352    }
353
354    /// Called to indicate a file or directory was removed from this directory.
355    pub(crate) fn did_remove(&self, name: &str) {
356        self.remove_from_dirent_cache(name);
357        self.watchers.lock().send_event(&mut SingleNameEventProducer::removed(name));
358    }
359
360    /// Called to indicate a file or directory was added to this directory.
361    pub(crate) fn did_add(&self, name: &str, node: Option<Arc<dyn FxNode>>) {
362        if let Some(node) = node {
363            let is_casefold = self.directory.dir_type().is_casefold();
364            self.directory.owner().dirent_cache().insert(
365                DirentCacheKey::new(self.directory.object_id(), name.to_owned(), is_casefold),
366                node,
367            );
368        }
369        self.watchers.lock().send_event(&mut SingleNameEventProducer::added(name));
370    }
371
372    /// As per fscrypt, files cannot be moved or linked across different encryption policies.
373    /// Appropriate locks must be held by the caller.
374    pub fn check_fscrypt_policy_equivalence(
375        &self,
376        source_wrapping_key_id: Option<WrappingKeyId>,
377    ) -> Result<(), zx::Status> {
378        if let Some(target_id) = self.directory().dir_type().wrapping_key_id() {
379            if Some(target_id) != source_wrapping_key_id {
380                return Err(zx::Status::BAD_STATE);
381            }
382        }
383        Ok(())
384    }
385
386    pub(crate) async fn link_object(
387        &self,
388        mut transaction: Transaction<'_>,
389        name: &str,
390        source_id: u64,
391        kind: ObjectDescriptor,
392    ) -> Result<(), zx::Status> {
393        let store = self.store();
394        if self.is_deleted() {
395            return Err(zx::Status::ACCESS_DENIED);
396        }
397        if self.directory.lookup(&name).await.map_err(map_to_status)?.is_some() {
398            return Err(zx::Status::ALREADY_EXISTS);
399        }
400        self.directory
401            .insert_child(&mut transaction, &name, source_id, kind.clone())
402            .await
403            .map_err(map_to_status)?;
404        store.adjust_refs(&mut transaction, source_id, 1).await.map_err(map_to_status)?;
405        transaction
406            .commit_with_callback(|_| self.did_add(&name, None))
407            .await
408            .map_err(map_to_status)?;
409        Ok(())
410    }
411
412    // Move graveyard object out from the graveyard and link it to this path. We only expect to do
413    // this when linking an unnamed temporary file for the first time.
414    pub(crate) async fn link_graveyard_object<F>(
415        &self,
416        mut transaction: Transaction<'_>,
417        name: &str,
418        source_id: u64,
419        kind: ObjectDescriptor,
420        transaction_callback: F,
421    ) -> Result<(), zx::Status>
422    where
423        F: FnOnce() + Send,
424    {
425        let store = self.store();
426        if self.is_deleted() {
427            return Err(zx::Status::ACCESS_DENIED);
428        }
429        if self.directory.lookup(&name).await.map_err(map_to_status)?.is_some() {
430            return Err(zx::Status::ALREADY_EXISTS);
431        }
432        // Move object out from the graveyard and place into record. As we are moving the object
433        // from one record to the other, the reference count should stay the same.
434        store.remove_from_graveyard(&mut transaction, source_id);
435        self.directory
436            .insert_child(&mut transaction, &name, source_id, kind.clone())
437            .await
438            .map_err(map_to_status)?;
439        transaction
440            .commit_with_callback(|_| {
441                transaction_callback();
442                self.did_add(&name, None);
443            })
444            .await
445            .map_err(map_to_status)?;
446        Ok(())
447    }
448
449    async fn link_impl(
450        self: Arc<Self>,
451        name: String,
452        source_dir: Arc<dyn Any + Send + Sync>,
453        source_name: &str,
454    ) -> Result<(), zx::Status> {
455        let source_dir = source_dir.downcast::<Self>().unwrap();
456        let store = self.store();
457        let mut source_id =
458            match source_dir.directory.lookup(source_name).await.map_err(map_to_status)? {
459                Some((object_id, ObjectDescriptor::File, _)) => object_id,
460                None => return Err(zx::Status::NOT_FOUND),
461                _ => return Err(zx::Status::NOT_SUPPORTED),
462            };
463        loop {
464            // We don't need a lock on the source directory, as it will be unchanged (unless it is
465            // the same as the destination directory). We just need a lock on the source object to
466            // ensure that it hasn't been simultaneously unlinked. This may race with a rename of
467            // the source file to somewhere else but that shouldn't matter. We need that lock anyway
468            // to update the ref count. Note, fscrypt does not require the source directory to be
469            // locked because a directory's wrapping key cannot change once the directory has
470            // entries.
471            let transaction = store
472                .new_transaction(
473                    lock_keys![
474                        LockKey::object(store.store_object_id(), self.object_id()),
475                        LockKey::object(store.store_object_id(), source_id),
476                    ],
477                    Options::default(),
478                )
479                .await
480                .map_err(map_to_status)?;
481            self.check_fscrypt_policy_equivalence(source_dir.directory().wrapping_key_id())?;
482            // Ensure under lock that the file still exists there.
483            match source_dir.directory.lookup(source_name).await.map_err(map_to_status)? {
484                Some((new_id, ObjectDescriptor::File, _)) => {
485                    if new_id == source_id {
486                        // We found the object that we got a lock on, it is still valid.
487                        return self
488                            .link_object(transaction, &name, source_id, ObjectDescriptor::File)
489                            .await;
490                    } else {
491                        source_id = new_id
492                    }
493                }
494                None => return Err(zx::Status::NOT_FOUND),
495                _ => return Err(zx::Status::NOT_SUPPORTED),
496            }
497        }
498    }
499
500    /// Acquires the transaction and executes `directory::replace_child_with_purge`.
501    ///
502    /// Returns `Ok(None)` if this is a trivial no-op rename (`src_dir == self` and `src == dst`).
503    ///
504    /// To avoid holding a directory `WriteLock` across extent-trimming disk I/O (which would
505    /// block concurrent lookups), this method optimistically attempts a single-transaction purge
506    /// while holding normal transaction locks, and retries once falling back to the graveyard if
507    /// a concurrent lookup opens the target file during the I/O window.
508    async fn replace_child_with_purge<'a>(
509        self: &'a Arc<Self>,
510        src: Option<(&'a Arc<FxDirectory>, &'a str)>,
511        dst: &'a str,
512        must_be_directory: bool,
513    ) -> Result<Option<ReplaceWithPurgeResult<'a>>, zx::Status> {
514        // In some cases, we can directly purge the deleted object's extents, bypassing the
515        // graveyard.  This reduces the latency of unlinking files.  We disable this for remote
516        // stores since remote stores *must* delete via the external filesystem's graveyard (they
517        // cannot directly interact with the allocator).
518        let mut allow_purge = !self.store().filesystem().is_remote();
519        loop {
520            let borrow_metadata_space = src.is_none();
521            // Acquire the transaction that locks |src_dir|, |src_name|, |self|, and |dst_name| if
522            // they exist, and also the ID and type of dst and src.
523            let replace_context = self
524                .directory
525                .acquire_context_for_replace(
526                    src.map(|(dir, name)| (dir.directory(), name)),
527                    dst,
528                    borrow_metadata_space,
529                )
530                .await
531                .map_err(map_to_status)?;
532            let mut transaction = replace_context.transaction;
533
534            let (moved_node, actual_src_name) = if let Some((src_dir, src_name)) = src {
535                if self.is_deleted() {
536                    return Err(zx::Status::NOT_FOUND);
537                }
538
539                let (moved_id, moved_descriptor) =
540                    replace_context.src_id_and_descriptor.clone().ok_or(zx::Status::NOT_FOUND)?;
541
542                // Make sure the dst path is compatible with the moved node.
543                if let ObjectDescriptor::File = moved_descriptor {
544                    if must_be_directory {
545                        return Err(zx::Status::NOT_DIR);
546                    }
547                }
548
549                // Now that we've ensured that the dst path is compatible with the moved node, we
550                // can check for the trivial case.
551                if src_dir.object_id() == self.object_id() && src_name == dst {
552                    return Ok(None);
553                }
554
555                if let Some((_, dst_descriptor)) = replace_context.dst_id_and_descriptor.as_ref() {
556                    // dst is being overwritten; make sure it's a file iff src is.
557                    match (&moved_descriptor, dst_descriptor) {
558                        (ObjectDescriptor::Directory, ObjectDescriptor::Directory) => {}
559                        (
560                            ObjectDescriptor::File | ObjectDescriptor::Symlink,
561                            ObjectDescriptor::File | ObjectDescriptor::Symlink,
562                        ) => {}
563                        (ObjectDescriptor::Directory, _) => return Err(zx::Status::NOT_DIR),
564                        (ObjectDescriptor::File | ObjectDescriptor::Symlink, _) => {
565                            return Err(zx::Status::NOT_FILE);
566                        }
567                        _ => return Err(zx::Status::IO_DATA_INTEGRITY),
568                    }
569                }
570
571                let moved_node = src_dir
572                    .volume()
573                    .get_or_load_node(moved_id, moved_descriptor.clone(), Some(src_dir.clone()))
574                    .await
575                    .map_err(map_to_status)?;
576
577                if let ObjectDescriptor::Directory = moved_descriptor {
578                    // Lastly, ensure that self isn't a (transitive) child of the moved node.
579                    let mut node_opt = Some(self.clone());
580                    while let Some(node) = node_opt {
581                        if node.object_id() == moved_node.object_id() {
582                            return Err(zx::Status::INVALID_ARGS);
583                        }
584                        node_opt = node.parent();
585                    }
586                }
587
588                // Use name from the replace_context if available (which preserves case-folding
589                // info). `src_name` comes from user supplied name which may have different case.
590                let actual_src_name =
591                    replace_context.src_name.as_deref().unwrap_or(src_name).to_owned();
592                (Some(moved_node), actual_src_name)
593            } else {
594                let (_child_id, object_descriptor) =
595                    replace_context.dst_id_and_descriptor.clone().ok_or(zx::Status::NOT_FOUND)?;
596                if let ObjectDescriptor::Directory = object_descriptor {
597                } else if must_be_directory {
598                    return Err(zx::Status::NOT_DIR);
599                }
600                (None, String::new())
601            };
602
603            // Use name from the replace_context if available (which preserves case-folding info).
604            // `dst` is user supplied name and may have different case.
605            let actual_dst_name = replace_context.dst_name.as_deref().unwrap_or(dst).to_owned();
606
607            // Closed files are flushed on close (`SyncMode::PreClose`), so `dirent_cache`
608            // typically holds the last strong `Arc<dyn FxNode>`. Evicting it here synchronously
609            // removes the node from `NodeCache`.
610            if allow_purge && replace_context.dst_id_and_descriptor.is_some() {
611                self.remove_from_dirent_cache(&actual_dst_name);
612            }
613            let can_purge = allow_purge
614                && match replace_context.dst_id_and_descriptor.as_ref() {
615                    Some((dst_id, ObjectDescriptor::File | ObjectDescriptor::Symlink)) => {
616                        !self.volume().cache().contains_key(*dst_id)
617                    }
618                    _ => false,
619                };
620
621            let replace_result = directory::replace_child_with_purge(
622                &mut transaction,
623                src.map(|(dir, name)| (dir.directory(), name)),
624                (self.directory(), dst),
625                can_purge,
626            )
627            .await
628            .map_err(map_to_status)?;
629
630            if let ReplacedChild::Purged(id) = replace_result {
631                // Now that extent-trimming disk I/O is complete, upgrade locks to `WriteLock` and
632                // wait for active readers to drain so no new lookups can start. If a concurrent
633                // lookup loaded the node into `NodeCache` (or repopulated `dirent_cache`) during
634                // the I/O window, drop the uncommitted transaction and retry once via the
635                // graveyard.
636                transaction.commit_prepare().await;
637                if self.volume().cache().contains_key(id) {
638                    allow_purge = false;
639                    continue;
640                }
641            }
642            return Ok(Some(ReplaceWithPurgeResult {
643                transaction,
644                truncate_guard: replace_context.truncate_guard,
645                replace_result,
646                moved_node,
647                actual_src_name,
648                actual_dst_name,
649            }));
650        }
651    }
652
653    async fn rename_impl(
654        self: Arc<Self>,
655        src_dir: Arc<dyn MutableDirectory>,
656        src_name: Path,
657        dst_name: Path,
658    ) -> Result<(), zx::Status> {
659        if !src_name.is_single_component() || !dst_name.is_single_component() {
660            return Err(zx::Status::INVALID_ARGS);
661        }
662        let (src, dst) = (src_name.peek().unwrap(), dst_name.peek().unwrap());
663        let src_dir =
664            src_dir.into_any().downcast::<FxDirectory>().map_err(|_| zx::Status::NOT_DIR)?;
665
666        let Some(ReplaceWithPurgeResult {
667            transaction,
668            truncate_guard,
669            replace_result,
670            moved_node,
671            actual_src_name,
672            actual_dst_name,
673        }) = self
674            .replace_child_with_purge(
675                Some((&src_dir, src)),
676                dst,
677                src_name.is_dir() || dst_name.is_dir(),
678            )
679            .await?
680        else {
681            return Ok(());
682        };
683        let moved_node = moved_node.unwrap();
684
685        transaction
686            .commit_with_callback(|_| {
687                moved_node.set_parent(self.clone());
688                src_dir.did_remove(&actual_src_name);
689
690                match replace_result {
691                    ReplacedChild::None => {}
692                    ReplacedChild::ObjectWithRemainingLinks(..)
693                    | ReplacedChild::Object(_)
694                    | ReplacedChild::Purged(_) => {
695                        self.did_remove(&actual_dst_name);
696                    }
697                    ReplacedChild::Directory(id) => {
698                        let store = self.store();
699                        store.filesystem().queue_tombstone_object(store.store_object_id(), id);
700                        self.did_remove(&actual_dst_name);
701                        self.volume().mark_directory_deleted(id);
702                    }
703                }
704                self.did_add(dst, Some(moved_node));
705            })
706            .await
707            .map_err(map_to_status)?;
708
709        if let ReplacedChild::Object(id) = replace_result {
710            self.volume()
711                .maybe_purge_file(id, truncate_guard.as_ref())
712                .await
713                .map_err(map_to_status)?;
714        }
715        Ok(())
716    }
717
718    pub(crate) async fn open_block_file(
719        self: &Arc<Self>,
720        name: &str,
721        server_end: ServerEnd<BlockMarker>,
722    ) {
723        let request = ObjectRequest::new(
724            fio::Flags::empty(),
725            &fio::Options::default(),
726            server_end.into_channel(),
727        );
728        let scope = self.volume().scope().clone();
729        let this = self.clone();
730        request
731            .handle_async(async move |request| {
732                let path = Path::validate_and_split(name).and_then(|p| {
733                    if p.is_single_component() { Ok(p) } else { Err(zx::Status::INVALID_ARGS) }
734                })?;
735                let node = this
736                    .lookup(&fio::Flags::empty(), path, request)
737                    .await
738                    .map_err(map_to_status)?;
739                if node.is::<FxFile>() {
740                    let file = node.downcast::<FxFile>().unwrap_or_else(|_| unreachable!());
741                    if file.is_verified_file() {
742                        log::error!("Tried to expose a verified file as a block device.");
743                        return Err(zx::Status::NOT_SUPPORTED);
744                    }
745                    let server = BlockServer::new(file, request.take().into_channel());
746                    scope.spawn(server.run());
747                    Ok(())
748                } else {
749                    Err(zx::Status::NOT_FILE)
750                }
751            })
752            .await
753    }
754}
755
756impl Drop for FxDirectory {
757    fn drop(&mut self) {
758        self.volume().cache().remove(self);
759    }
760}
761
762impl FxNode for FxDirectory {
763    fn object_id(&self) -> u64 {
764        self.directory.object_id()
765    }
766
767    fn parent(&self) -> Option<Arc<FxDirectory>> {
768        self.parent.as_ref().map(|p| p.lock().clone())
769    }
770
771    fn set_parent(&self, parent: Arc<FxDirectory>) {
772        match &self.parent {
773            Some(p) => *p.lock() = parent,
774            None => panic!("Called set_parent on root node"),
775        }
776    }
777
778    // If these ever do anything, BlobDirectory might need to be fixed.
779    fn open_count_add_one(&self) {}
780    fn open_count_sub_one(self: Arc<Self>) {}
781
782    fn object_descriptor(&self) -> ObjectDescriptor {
783        ObjectDescriptor::Directory
784    }
785}
786
787impl MutableDirectory for FxDirectory {
788    fn link<'a>(
789        self: Arc<Self>,
790        name: String,
791        source_dir: Arc<dyn Any + Send + Sync>,
792        source_name: &'a str,
793    ) -> BoxFuture<'a, Result<(), zx::Status>> {
794        Box::pin(self.link_impl(name, source_dir, source_name))
795    }
796
797    async fn unlink(
798        self: Arc<Self>,
799        name: &str,
800        must_be_directory: bool,
801    ) -> Result<(), zx::Status> {
802        let ReplaceWithPurgeResult {
803            transaction,
804            truncate_guard,
805            replace_result,
806            actual_dst_name,
807            ..
808        } = self
809            .replace_child_with_purge(None, name, must_be_directory)
810            .await?
811            .expect("unlink cannot be a trivial rename");
812
813        match replace_result {
814            ReplacedChild::None => return Err(zx::Status::NOT_FOUND),
815            ReplacedChild::ObjectWithRemainingLinks(..) | ReplacedChild::Purged(..) => {
816                transaction
817                    .commit_with_callback(|_| self.did_remove(&actual_dst_name))
818                    .await
819                    .map_err(map_to_status)?;
820            }
821            ReplacedChild::Object(id) => {
822                transaction
823                    .commit_with_callback(|_| self.did_remove(&actual_dst_name))
824                    .await
825                    .map_err(map_to_status)?;
826
827                // If purging fails, we should still return success, since the file will appear
828                // unlinked at this point anyways.  The file should be cleaned up on a later mount.
829                if let Err(e) = self.volume().maybe_purge_file(id, truncate_guard.as_ref()).await {
830                    warn!(error:? = e; "Failed to purge file");
831                }
832            }
833            ReplacedChild::Directory(id) => {
834                transaction
835                    .commit_with_callback(|_| {
836                        let store = self.store();
837                        store.filesystem().queue_tombstone_object(store.store_object_id(), id);
838                        self.did_remove(&actual_dst_name);
839                        self.volume().mark_directory_deleted(id);
840                    })
841                    .await
842                    .map_err(map_to_status)?;
843            }
844        }
845        Ok(())
846    }
847
848    async fn update_attributes(
849        &self,
850        attributes: fio::MutableNodeAttributes,
851    ) -> Result<(), zx::Status> {
852        // TODO(b/365630582): Reconsider doing this as part of the transaction below.
853        if let Some(casefold) = attributes.casefold {
854            self.directory.set_casefold(casefold).await.map_err(map_to_status)?;
855        }
856        let transaction = self
857            .store()
858            .new_transaction(
859                lock_keys![LockKey::object(
860                    self.store().store_object_id(),
861                    self.directory.object_id()
862                )],
863                Options { reservation: ReservationOptions::Borrowed, ..Default::default() },
864            )
865            .await
866            .map_err(map_to_status)?;
867
868        self.directory
869            .update_attributes(transaction, Some(&attributes), 0, Some(Timestamp::now()))
870            .await
871            .map_err(map_to_status)?;
872        Ok(())
873    }
874
875    async fn sync(&self) -> Result<(), zx::Status> {
876        // FDIO implements `syncfs` by calling sync on a directory, so replicate that behaviour.
877        self.volume().flush_all_files(FlushType::Sync).await;
878        self.volume()
879            .store()
880            .filesystem()
881            .sync(SyncOptions { flush_device: true, ..Default::default() })
882            .await
883            .map_err(map_to_status)
884    }
885
886    fn rename(
887        self: Arc<Self>,
888        src_dir: Arc<dyn MutableDirectory>,
889        src_name: Path,
890        dst_name: Path,
891    ) -> BoxFuture<'static, Result<(), zx::Status>> {
892        Box::pin(self.rename_impl(src_dir, src_name, dst_name))
893    }
894
895    async fn create_symlink(
896        &self,
897        name: String,
898        target: Vec<u8>,
899        connection: Option<ServerEnd<fio::SymlinkMarker>>,
900    ) -> Result<(), zx::Status> {
901        let store = self.store();
902        let dir = &self.directory;
903        let keys = lock_keys![LockKey::object(store.store_object_id(), dir.object_id())];
904        let mut transaction =
905            store.new_transaction(keys, Options::default()).await.map_err(map_to_status)?;
906        if dir.lookup(&name).await.map_err(map_to_status)?.is_some() {
907            return Err(zx::Status::ALREADY_EXISTS);
908        }
909        let object_id =
910            dir.create_symlink(&mut transaction, &target, &name).await.map_err(map_to_status)?;
911        if let Some(connection) = connection {
912            if let GetResult::Placeholder(p) = self.volume().cache().get_or_reserve(object_id).await
913            {
914                transaction
915                    .commit_with_callback(|_| {
916                        let node = Arc::new(FxSymlink::new(self.volume().clone(), object_id));
917                        p.commit(&(node.clone() as Arc<dyn FxNode>));
918                        let scope = self.volume().scope().clone();
919                        let flags =
920                            fio::Flags::PROTOCOL_SYMLINK | fio::PERM_READABLE | fio::PERM_WRITABLE;
921                        // Wrap in OpenedNode to set open_count to 1 for the connection.
922                        let opened_node = OpenedNode::new(node);
923                        // fio::Flags::FLAG_SEND_REPRESENTATION isn't specified so connection
924                        // creation is synchronous.
925                        symlink::Connection::create_sync(
926                            scope,
927                            opened_node.take(),
928                            flags,
929                            flags.to_object_request(connection),
930                        );
931                    })
932                    .await
933            } else {
934                // The node already exists in the cache which could only happen if the filesystem is
935                // corrupt.
936                return Err(zx::Status::IO_DATA_INTEGRITY);
937            }
938        } else {
939            transaction.commit().await.map(|_| ())
940        }
941        .map_err(map_to_status)
942    }
943}
944
945impl DirectoryEntry for FxDirectory {
946    fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), zx::Status> {
947        request.open_dir(self)
948    }
949
950    fn scope(&self) -> Option<ExecutionScope> {
951        Some(self.volume().scope().clone())
952    }
953}
954
955impl GetEntryInfo for FxDirectory {
956    fn entry_info(&self) -> EntryInfo {
957        EntryInfo::new(self.object_id(), fio::DirentType::Directory)
958    }
959}
960
961impl vfs::node::Node for FxDirectory {
962    async fn get_attributes(
963        &self,
964        requested_attributes: fio::NodeAttributesQuery,
965    ) -> Result<fio::NodeAttributes2, zx::Status> {
966        let mut props = self.directory.get_properties().await.map_err(map_to_status)?;
967
968        if requested_attributes.contains(fio::NodeAttributesQuery::PENDING_ACCESS_TIME_UPDATE) {
969            self.store()
970                .update_access_time(self.directory.object_id(), &mut props, || !self.is_deleted())
971                .await
972                .map_err(map_to_status)?;
973        }
974
975        Ok(attributes!(
976            requested_attributes,
977            Mutable {
978                creation_time: props.creation_time.as_nanos(),
979                modification_time: props.modification_time.as_nanos(),
980                access_time: props.access_time.as_nanos(),
981                mode: props.posix_attributes.map(|a| a.mode),
982                uid: props.posix_attributes.map(|a| a.uid),
983                gid: props.posix_attributes.map(|a| a.gid),
984                rdev: props.posix_attributes.map(|a| a.rdev),
985                casefold: self.directory.dir_type().is_casefold(),
986                selinux_context: self
987                    .directory
988                    .handle()
989                    .get_inline_selinux_context()
990                    .await
991                    .map_err(map_to_status)?,
992                encryption_policy: props.dir_type.fscrypt_policy(),
993            },
994            Immutable {
995                protocols: fio::NodeProtocolKinds::DIRECTORY,
996                abilities: fio::Operations::GET_ATTRIBUTES
997                    | fio::Operations::UPDATE_ATTRIBUTES
998                    | fio::Operations::ENUMERATE
999                    | fio::Operations::TRAVERSE
1000                    | fio::Operations::MODIFY_DIRECTORY,
1001                content_size: props.data_attribute_size,
1002                storage_size: props.allocated_size,
1003                link_count: props.refs + 1 + props.sub_dirs,
1004                id: self.directory.object_id(),
1005                change_time: props.change_time.as_nanos(),
1006                verity_enabled: false,
1007            }
1008        ))
1009    }
1010
1011    fn query_filesystem(&self) -> Result<fio::FilesystemInfo, zx::Status> {
1012        Ok(self.volume().filesystem_info_for_volume())
1013    }
1014
1015    async fn list_extended_attributes(&self) -> Result<Vec<Vec<u8>>, zx::Status> {
1016        self.directory.list_extended_attributes().await.map_err(map_to_status)
1017    }
1018
1019    async fn get_extended_attribute(&self, name: Vec<u8>) -> Result<Vec<u8>, zx::Status> {
1020        self.directory.get_extended_attribute(name).await.map_err(map_to_status)
1021    }
1022
1023    async fn set_extended_attribute(
1024        &self,
1025        name: Vec<u8>,
1026        value: Vec<u8>,
1027        mode: fio::SetExtendedAttributeMode,
1028    ) -> Result<(), zx::Status> {
1029        self.directory.set_extended_attribute(name, value, mode.into()).await.map_err(map_to_status)
1030    }
1031
1032    async fn remove_extended_attribute(&self, name: Vec<u8>) -> Result<(), zx::Status> {
1033        self.directory.remove_extended_attribute(name).await.map_err(map_to_status)
1034    }
1035}
1036
1037impl VfsDirectory for FxDirectory {
1038    fn deprecated_open(
1039        self: Arc<Self>,
1040        scope: ExecutionScope,
1041        flags: fio::OpenFlags,
1042        path: Path,
1043        server_end: ServerEnd<fio::NodeMarker>,
1044    ) {
1045        scope.clone().spawn(flags.to_object_request(server_end).handle_async(
1046            async move |object_request| {
1047                let node =
1048                    self.lookup(&flags, path, object_request).await.map_err(map_to_status)?;
1049                if node.is::<FxDirectory>() {
1050                    let directory =
1051                        node.downcast::<FxDirectory>().unwrap_or_else(|_| unreachable!()).take();
1052                    object_request
1053                        .create_connection::<MutableConnection<_>, _>(scope, directory, flags)
1054                        .await
1055                } else if node.is::<FxFile>() {
1056                    let node = node.downcast::<FxFile>().unwrap_or_else(|_| unreachable!());
1057                    if flags.contains(fio::OpenFlags::BLOCK_DEVICE) {
1058                        if node.is_verified_file() {
1059                            log::error!("Tried to expose a verified file as a block device.");
1060                            return Err(zx::Status::NOT_SUPPORTED);
1061                        }
1062                        if !flags.contains(fio::OpenFlags::RIGHT_READABLE) {
1063                            log::error!(
1064                                "Opening a file as block device requires at least RIGHT_READABLE."
1065                            );
1066                            return Err(zx::Status::ACCESS_DENIED);
1067                        }
1068                        let server = BlockServer::new(node, object_request.take().into_channel());
1069                        scope.spawn(server.run());
1070                        Ok(())
1071                    } else {
1072                        FxFile::create_connection_async(node, scope, flags, object_request).await
1073                    }
1074                } else if node.is::<FxSymlink>() {
1075                    let node = node.downcast::<FxSymlink>().unwrap_or_else(|_| unreachable!());
1076                    object_request
1077                        .create_connection::<symlink::Connection<_>, _>(
1078                            scope.clone(),
1079                            node.take(),
1080                            flags,
1081                        )
1082                        .await
1083                } else {
1084                    unreachable!();
1085                }
1086            },
1087        ));
1088    }
1089
1090    fn open(
1091        self: Arc<Self>,
1092        scope: ExecutionScope,
1093        path: Path,
1094        flags: fio::Flags,
1095        object_request: ObjectRequestRef<'_>,
1096    ) -> Result<(), zx::Status> {
1097        self.volume().scope().clone().spawn(object_request.take().handle_async(
1098            async move |object_request| self.open_async(scope, path, flags, object_request).await,
1099        ));
1100        Ok(())
1101    }
1102
1103    async fn open_async(
1104        self: Arc<Self>,
1105        scope: ExecutionScope,
1106        path: Path,
1107        flags: fio::Flags,
1108        object_request: ObjectRequestRef<'_>,
1109    ) -> Result<(), zx::Status> {
1110        let node = self.lookup(&flags, path, object_request).await.map_err(map_to_status)?;
1111        if node.is::<FxDirectory>() {
1112            let directory =
1113                node.downcast::<FxDirectory>().unwrap_or_else(|_| unreachable!()).take();
1114            object_request
1115                .create_connection::<MutableConnection<_>, _>(scope, directory, flags)
1116                .await
1117        } else if node.is::<FxFile>() {
1118            let file = node.downcast::<FxFile>().unwrap_or_else(|_| unreachable!());
1119            FxFile::create_connection_async(file, scope, flags, object_request).await
1120        } else if node.is::<FxSymlink>() {
1121            let symlink = node.downcast::<FxSymlink>().unwrap_or_else(|_| unreachable!());
1122            object_request
1123                .create_connection::<symlink::Connection<_>, _>(
1124                    scope.clone(),
1125                    symlink.take(),
1126                    flags,
1127                )
1128                .await
1129        } else {
1130            unreachable!();
1131        }
1132    }
1133
1134    async fn read_dirents(
1135        &self,
1136        pos: &TraversalPosition,
1137        mut sink: Box<dyn dirents_sink::Sink>,
1138    ) -> Result<(TraversalPosition, Box<dyn dirents_sink::Sealed>), zx::Status> {
1139        if let TraversalPosition::End = pos {
1140            return Ok((TraversalPosition::End, sink.seal()));
1141        } else if let TraversalPosition::Index(_) = pos {
1142            // The VFS should never send this to us, since we never return it here.
1143            return Err(zx::Status::BAD_STATE);
1144        }
1145
1146        let store = self.store();
1147        let fs = store.filesystem();
1148        let _read_guard = fs
1149            .lock_manager()
1150            .read_lock(lock_keys![LockKey::object(store.store_object_id(), self.object_id())])
1151            .await;
1152        if self.is_deleted() {
1153            return Ok((TraversalPosition::End, sink.seal()));
1154        }
1155
1156        let layer_set = self.store().tree().layer_set();
1157        let mut merger = layer_set.merger();
1158        let mut iter = match pos {
1159            TraversalPosition::Start => {
1160                // Synthesize a "." entry if we're at the start of the stream.
1161                match sink
1162                    .append(&EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory), ".")
1163                {
1164                    AppendResult::Ok(new_sink) => sink = new_sink,
1165                    AppendResult::Sealed(sealed) => {
1166                        // Note that the VFS should have yielded an error since the first entry
1167                        // didn't fit. This is defensive in case the VFS' behaviour changes, so that
1168                        // we return a reasonable value.
1169                        return Ok((TraversalPosition::Start, sealed));
1170                    }
1171                }
1172                self.directory.iter(&mut merger).await
1173            }
1174            TraversalPosition::Name(name) => self.directory.iter_from(&mut merger, name).await,
1175            TraversalPosition::Bytes(bytes) => {
1176                self.directory.iter_from_bytes(&mut merger, bytes).await
1177            }
1178            _ => unreachable!(),
1179        }
1180        .map_err(map_to_status)?;
1181        while let Some((name, object_id, object_descriptor)) = iter.get() {
1182            let entry_type = match object_descriptor {
1183                ObjectDescriptor::File => fio::DirentType::File,
1184                ObjectDescriptor::Directory => fio::DirentType::Directory,
1185                ObjectDescriptor::Symlink => fio::DirentType::Symlink,
1186                ObjectDescriptor::Volume => return Err(zx::Status::IO_DATA_INTEGRITY),
1187            };
1188
1189            let info = EntryInfo::new(object_id, entry_type);
1190            match sink.append(&info, &name) {
1191                AppendResult::Ok(new_sink) => sink = new_sink,
1192                AppendResult::Sealed(sealed) => {
1193                    // We did *not* add the current entry to the sink (e.g. because the sink was
1194                    // full), so mark |name| as the next position so that it's the first entry we
1195                    // process on a subsequent call of read_dirents.
1196                    // Note that entries inserted between the previous entry and this entry before
1197                    // the next call to read_dirents would not be included in the results (but
1198                    // there's no requirement to include them anyways).
1199                    return Ok((
1200                        iter.traversal_position(
1201                            |name| TraversalPosition::Name(name.to_string()),
1202                            |bytes| TraversalPosition::Bytes(bytes),
1203                        )
1204                        .unwrap(),
1205                        sealed,
1206                    ));
1207                }
1208            }
1209            iter.advance().await.map_err(map_to_status)?;
1210        }
1211
1212        Ok((TraversalPosition::End, sink.seal()))
1213    }
1214
1215    fn register_watcher(
1216        self: Arc<Self>,
1217        scope: ExecutionScope,
1218        mask: fio::WatchMask,
1219        watcher: DirectoryWatcher,
1220    ) -> Result<(), zx::Status> {
1221        let controller =
1222            self.watchers.lock().add(scope.clone(), self.clone(), mask, watcher).clone();
1223        if mask.contains(fio::WatchMask::EXISTING) && !self.is_deleted() {
1224            scope.spawn(async move {
1225                let layer_set = self.store().tree().layer_set();
1226                let mut merger = layer_set.merger();
1227                let mut iter = match self.directory.iter_from(&mut merger, "").await {
1228                    Ok(iter) => iter,
1229                    Err(e) => {
1230                        error!(error:? = e; "Failed to iterate directory for watch",);
1231                        // TODO(https://fxbug.dev/42178164): This really should close the watcher connection
1232                        // with an epitaph so that the watcher knows.
1233                        return;
1234                    }
1235                };
1236                // TODO(https://fxbug.dev/42178165): It is possible that we'll duplicate entries that are added
1237                // as we iterate over directories.  I suspect fixing this might be non-trivial.
1238                controller.send_event(&mut SingleNameEventProducer::existing("."));
1239                while let Some((name, _, _)) = iter.get() {
1240                    controller.send_event(&mut SingleNameEventProducer::existing(name));
1241                    if let Err(e) = iter.advance().await {
1242                        error!(error:? = e; "Failed to iterate directory for watch",);
1243                        return;
1244                    }
1245                }
1246                controller.send_event(&mut SingleNameEventProducer::idle());
1247            });
1248        }
1249        Ok(())
1250    }
1251
1252    fn unregister_watcher(self: Arc<Self>, key: usize) {
1253        self.watchers.lock().remove(key);
1254    }
1255}
1256
1257impl From<Directory<FxVolume>> for FxDirectory {
1258    fn from(dir: Directory<FxVolume>) -> Self {
1259        Self::new(None, dir)
1260    }
1261}
1262
1263#[cfg(test)]
1264mod tests {
1265    use crate::directory::FxDirectory;
1266    use crate::file::FxFile;
1267    use crate::fuchsia::testing::{
1268        TestFixture, TestFixtureOptions, close_dir_checked, close_file_checked, open_dir,
1269        open_dir_checked, open_file, open_file_checked,
1270    };
1271    use anyhow::bail;
1272    use assert_matches::assert_matches;
1273    use fidl::endpoints::{ClientEnd, Proxy, create_proxy};
1274    use fidl_fuchsia_io as fio;
1275    use fuchsia_async as fasync;
1276    use fuchsia_fs::directory::{DirEntry, DirentKind, WatchEvent, WatchMessage, Watcher};
1277    use fuchsia_fs::file;
1278    use futures::{StreamExt, join};
1279    use fxfs::lsm_tree::Query;
1280    use fxfs::lsm_tree::types::{ItemRef, LayerIterator};
1281    use fxfs::object_store::transaction::{LockKey, lock_keys};
1282    use fxfs::object_store::{ObjectKey, ObjectKeyData, ObjectValue, Timestamp};
1283    use fxfs_crypt_common::CryptBase;
1284    use fxfs_crypto::{FSCRYPT_PADDING, WrappingKeyId};
1285    use std::future::poll_fn;
1286    use std::os::fd::AsRawFd;
1287    use std::sync::Arc;
1288    use std::sync::atomic::{AtomicU64, Ordering};
1289    use std::task::Poll;
1290    use std::time::Duration;
1291    use storage_device::DeviceHolder;
1292    use storage_device::fake_device::FakeDevice;
1293    use vfs::ObjectRequest;
1294    use vfs::node::Node;
1295    use vfs::path::Path;
1296
1297    const WRAPPING_KEY_ID: WrappingKeyId = u128::to_le_bytes(2);
1298
1299    async fn yield_to_executor() {
1300        let mut done = false;
1301        poll_fn(|cx| {
1302            if done {
1303                Poll::Ready(())
1304            } else {
1305                done = true;
1306                cx.waker().wake_by_ref();
1307                Poll::Pending
1308            }
1309        })
1310        .await;
1311    }
1312
1313    #[fuchsia::test]
1314    async fn test_open_root_dir() {
1315        let fixture = TestFixture::new().await;
1316        let root = fixture.root();
1317        let _: Vec<_> = root.query().await.expect("query failed");
1318        fixture.close().await;
1319    }
1320
1321    #[fuchsia::test]
1322    async fn test_create_dir_persists() {
1323        let mut device = DeviceHolder::new(FakeDevice::new(8192, 512));
1324        for i in 0..2 {
1325            let fixture = TestFixture::open(
1326                device,
1327                TestFixtureOptions { format: i == 0, ..Default::default() },
1328            )
1329            .await;
1330            let root = fixture.root();
1331
1332            let flags = if i == 0 {
1333                fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_READABLE
1334            } else {
1335                fio::PERM_READABLE
1336            };
1337            let dir = open_dir_checked(
1338                &root,
1339                "foo",
1340                flags | fio::Flags::PROTOCOL_DIRECTORY,
1341                Default::default(),
1342            )
1343            .await;
1344            close_dir_checked(dir).await;
1345
1346            device = fixture.close().await;
1347        }
1348    }
1349
1350    #[fuchsia::test]
1351    async fn test_open_nonexistent_file() {
1352        let fixture = TestFixture::new().await;
1353        let root = fixture.root();
1354
1355        assert_eq!(
1356            open_file(
1357                &root,
1358                "foo",
1359                fio::PERM_READABLE | fio::Flags::PROTOCOL_FILE,
1360                &Default::default()
1361            )
1362            .await
1363            .expect_err("Open succeeded")
1364            .root_cause()
1365            .downcast_ref::<zx::Status>()
1366            .expect("No status"),
1367            &zx::Status::NOT_FOUND,
1368        );
1369
1370        fixture.close().await;
1371    }
1372
1373    #[fuchsia::test]
1374    async fn test_create_file() {
1375        let fixture = TestFixture::new().await;
1376        let root = fixture.root();
1377
1378        let f = open_file_checked(
1379            &root,
1380            "foo",
1381            fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_READABLE | fio::Flags::PROTOCOL_FILE,
1382            &Default::default(),
1383        )
1384        .await;
1385        close_file_checked(f).await;
1386
1387        let f = open_file_checked(
1388            &root,
1389            "foo",
1390            fio::PERM_READABLE | fio::Flags::PROTOCOL_FILE,
1391            &Default::default(),
1392        )
1393        .await;
1394        close_file_checked(f).await;
1395
1396        fixture.close().await;
1397    }
1398
1399    #[fuchsia::test]
1400    async fn test_create_dir_nested() {
1401        let fixture = TestFixture::new().await;
1402        let root = fixture.root();
1403
1404        let d = open_dir_checked(
1405            &root,
1406            "foo",
1407            fio::Flags::FLAG_MAYBE_CREATE
1408                | fio::PERM_READABLE
1409                | fio::PERM_WRITABLE
1410                | fio::Flags::PROTOCOL_DIRECTORY,
1411            Default::default(),
1412        )
1413        .await;
1414        close_dir_checked(d).await;
1415
1416        let d = open_dir_checked(
1417            &root,
1418            "foo/bar",
1419            fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_READABLE | fio::Flags::PROTOCOL_DIRECTORY,
1420            Default::default(),
1421        )
1422        .await;
1423        close_dir_checked(d).await;
1424
1425        let d = open_dir_checked(
1426            &root,
1427            "foo/bar",
1428            fio::PERM_READABLE | fio::Flags::PROTOCOL_DIRECTORY,
1429            Default::default(),
1430        )
1431        .await;
1432        close_dir_checked(d).await;
1433
1434        fixture.close().await;
1435    }
1436
1437    #[fuchsia::test]
1438    async fn test_strict_create_file_fails_if_present() {
1439        let fixture = TestFixture::new().await;
1440        let root = fixture.root();
1441
1442        let f = open_file_checked(
1443            &root,
1444            "foo",
1445            fio::Flags::FLAG_MAYBE_CREATE
1446                | fio::Flags::FLAG_MUST_CREATE
1447                | fio::PERM_READABLE
1448                | fio::Flags::PROTOCOL_FILE,
1449            &Default::default(),
1450        )
1451        .await;
1452        close_file_checked(f).await;
1453
1454        assert_eq!(
1455            open_file(
1456                &root,
1457                "foo",
1458                fio::Flags::FLAG_MAYBE_CREATE
1459                    | fio::Flags::FLAG_MUST_CREATE
1460                    | fio::PERM_READABLE
1461                    | fio::Flags::PROTOCOL_FILE,
1462                &Default::default()
1463            )
1464            .await
1465            .expect_err("Open succeeded")
1466            .root_cause()
1467            .downcast_ref::<zx::Status>()
1468            .expect("No status"),
1469            &zx::Status::ALREADY_EXISTS,
1470        );
1471
1472        fixture.close().await;
1473    }
1474
1475    #[fuchsia::test]
1476    async fn test_unlink_file_with_no_refs_immediately_freed() {
1477        let fixture = TestFixture::new().await;
1478        let root = fixture.root();
1479
1480        let file = open_file_checked(
1481            &root,
1482            "foo",
1483            fio::Flags::FLAG_MAYBE_CREATE
1484                | fio::PERM_READABLE
1485                | fio::PERM_WRITABLE
1486                | fio::Flags::PROTOCOL_FILE,
1487            &Default::default(),
1488        )
1489        .await;
1490
1491        // Fill up the file with a lot of data, so we can verify that the extents are freed.
1492        let buf = vec![0xaa as u8; 512];
1493        loop {
1494            match file::write(&file, buf.as_slice()).await {
1495                Ok(_) => {}
1496                Err(e) => {
1497                    if let fuchsia_fs::file::WriteError::WriteError(status) = e {
1498                        if status == zx::Status::NO_SPACE {
1499                            break;
1500                        }
1501                    }
1502                    panic!("Unexpected write error {:?}", e);
1503                }
1504            }
1505        }
1506
1507        close_file_checked(file).await;
1508
1509        root.unlink("foo", &fio::UnlinkOptions::default())
1510            .await
1511            .expect("FIDL call failed")
1512            .expect("unlink failed");
1513
1514        assert_eq!(
1515            open_file(
1516                &root,
1517                "foo",
1518                fio::PERM_READABLE | fio::Flags::PROTOCOL_FILE,
1519                &Default::default()
1520            )
1521            .await
1522            .expect_err("Open succeeded")
1523            .root_cause()
1524            .downcast_ref::<zx::Status>()
1525            .expect("No status"),
1526            &zx::Status::NOT_FOUND,
1527        );
1528
1529        // Create another file so we can verify that the extents were actually freed.
1530        let file = open_file_checked(
1531            &root,
1532            "bar",
1533            fio::Flags::FLAG_MAYBE_CREATE
1534                | fio::PERM_READABLE
1535                | fio::PERM_WRITABLE
1536                | fio::Flags::PROTOCOL_FILE,
1537            &Default::default(),
1538        )
1539        .await;
1540        let buf = vec![0xaa as u8; 8192];
1541        file::write(&file, buf.as_slice()).await.expect("Failed to write new file");
1542        close_file_checked(file).await;
1543
1544        fixture.close().await;
1545    }
1546
1547    #[fuchsia::test]
1548    async fn test_unlink_file() {
1549        let fixture = TestFixture::new().await;
1550        let root = fixture.root();
1551
1552        let file = open_file_checked(
1553            &root,
1554            "foo",
1555            fio::Flags::FLAG_MAYBE_CREATE
1556                | fio::PERM_READABLE
1557                | fio::PERM_WRITABLE
1558                | fio::Flags::PROTOCOL_FILE,
1559            &Default::default(),
1560        )
1561        .await;
1562        close_file_checked(file).await;
1563
1564        root.unlink("foo", &fio::UnlinkOptions::default())
1565            .await
1566            .expect("FIDL call failed")
1567            .expect("unlink failed");
1568
1569        assert_eq!(
1570            open_file(
1571                &root,
1572                "foo",
1573                fio::PERM_READABLE | fio::Flags::PROTOCOL_FILE,
1574                &Default::default()
1575            )
1576            .await
1577            .expect_err("Open succeeded")
1578            .root_cause()
1579            .downcast_ref::<zx::Status>()
1580            .expect("No status"),
1581            &zx::Status::NOT_FOUND,
1582        );
1583
1584        fixture.close().await;
1585    }
1586
1587    #[fuchsia::test]
1588    async fn test_unlink_file_with_active_references() {
1589        let fixture = TestFixture::new().await;
1590        let root = fixture.root();
1591
1592        let file = open_file_checked(
1593            &root,
1594            "foo",
1595            fio::Flags::FLAG_MAYBE_CREATE
1596                | fio::PERM_READABLE
1597                | fio::PERM_WRITABLE
1598                | fio::Flags::PROTOCOL_FILE,
1599            &Default::default(),
1600        )
1601        .await;
1602
1603        let buf = vec![0xaa as u8; 512];
1604        file::write(&file, buf.as_slice()).await.expect("write failed");
1605
1606        root.unlink("foo", &fio::UnlinkOptions::default())
1607            .await
1608            .expect("FIDL call failed")
1609            .expect("unlink failed");
1610
1611        // The child should immediately appear unlinked...
1612        assert_eq!(
1613            open_file(
1614                &root,
1615                "foo",
1616                fio::PERM_READABLE | fio::Flags::PROTOCOL_FILE,
1617                &Default::default()
1618            )
1619            .await
1620            .expect_err("Open succeeded")
1621            .root_cause()
1622            .downcast_ref::<zx::Status>()
1623            .expect("No status"),
1624            &zx::Status::NOT_FOUND,
1625        );
1626
1627        // But its contents should still be readable from the other handle.
1628        file.seek(fio::SeekOrigin::Start, 0)
1629            .await
1630            .expect("seek failed")
1631            .map_err(zx::Status::err_from_raw)
1632            .expect("seek error");
1633        let rbuf = file::read(&file).await.expect("read failed");
1634        assert_eq!(rbuf, buf);
1635        close_file_checked(file).await;
1636
1637        fixture.close().await;
1638    }
1639
1640    #[fuchsia::test]
1641    async fn test_unlink_dir_with_children_fails() {
1642        let fixture = TestFixture::new().await;
1643        let root = fixture.root();
1644
1645        let dir = open_dir_checked(
1646            &root,
1647            "foo",
1648            fio::Flags::FLAG_MAYBE_CREATE
1649                | fio::PERM_READABLE
1650                | fio::PERM_WRITABLE
1651                | fio::Flags::PROTOCOL_DIRECTORY,
1652            Default::default(),
1653        )
1654        .await;
1655        let f = open_file_checked(
1656            &dir,
1657            "bar",
1658            fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_READABLE,
1659            &Default::default(),
1660        )
1661        .await;
1662        close_file_checked(f).await;
1663
1664        assert_eq!(
1665            zx::Status::ok(
1666                root.unlink("foo", &fio::UnlinkOptions::default())
1667                    .await
1668                    .expect("FIDL call failed")
1669                    .expect_err("unlink succeeded")
1670            ),
1671            Err(zx::Status::NOT_EMPTY)
1672        );
1673
1674        dir.unlink("bar", &fio::UnlinkOptions::default())
1675            .await
1676            .expect("FIDL call failed")
1677            .expect("unlink failed");
1678        root.unlink("foo", &fio::UnlinkOptions::default())
1679            .await
1680            .expect("FIDL call failed")
1681            .expect("unlink failed");
1682
1683        close_dir_checked(dir).await;
1684
1685        fixture.close().await;
1686    }
1687
1688    #[fuchsia::test]
1689    async fn test_unlink_dir_makes_directory_immutable() {
1690        let fixture = TestFixture::new().await;
1691        let root = fixture.root();
1692
1693        let dir = open_dir_checked(
1694            &root,
1695            "foo",
1696            fio::Flags::FLAG_MAYBE_CREATE
1697                | fio::PERM_READABLE
1698                | fio::PERM_WRITABLE
1699                | fio::Flags::PROTOCOL_DIRECTORY,
1700            Default::default(),
1701        )
1702        .await;
1703
1704        root.unlink("foo", &fio::UnlinkOptions::default())
1705            .await
1706            .expect("FIDL call failed")
1707            .expect("unlink failed");
1708
1709        assert_eq!(
1710            open_file(
1711                &dir,
1712                "bar",
1713                fio::PERM_READABLE | fio::Flags::FLAG_MAYBE_CREATE | fio::Flags::PROTOCOL_FILE,
1714                &Default::default()
1715            )
1716            .await
1717            .expect_err("Create file succeeded")
1718            .root_cause()
1719            .downcast_ref::<zx::Status>()
1720            .expect("No status"),
1721            &zx::Status::ACCESS_DENIED,
1722        );
1723
1724        close_dir_checked(dir).await;
1725
1726        fixture.close().await;
1727    }
1728
1729    #[fuchsia::test(threads = 10)]
1730    async fn test_unlink_directory_with_children_race() {
1731        let fixture = TestFixture::new().await;
1732        let root = fixture.root();
1733
1734        const PARENT: &str = "foo";
1735        const CHILD: &str = "bar";
1736        const GRANDCHILD: &str = "baz";
1737        open_dir_checked(
1738            &root,
1739            PARENT,
1740            fio::Flags::FLAG_MAYBE_CREATE
1741                | fio::PERM_READABLE
1742                | fio::PERM_WRITABLE
1743                | fio::Flags::PROTOCOL_DIRECTORY,
1744            Default::default(),
1745        )
1746        .await;
1747
1748        let open_parent = || async {
1749            open_dir_checked(
1750                &root,
1751                PARENT,
1752                fio::PERM_READABLE | fio::PERM_WRITABLE | fio::Flags::PROTOCOL_DIRECTORY,
1753                Default::default(),
1754            )
1755            .await
1756        };
1757        let parent = open_parent().await;
1758
1759        // Each iteration proceeds as follows:
1760        //  - Initialize a directory foo/bar/. (This might still be around from the previous
1761        //    iteration, which is fine.)
1762        //  - In one task, try to unlink foo/bar/.
1763        //  - In another task, try to add a file foo/bar/baz.
1764        for _ in 0..100 {
1765            let d = open_dir_checked(
1766                &parent,
1767                CHILD,
1768                fio::Flags::FLAG_MAYBE_CREATE
1769                    | fio::PERM_READABLE
1770                    | fio::PERM_WRITABLE
1771                    | fio::Flags::PROTOCOL_DIRECTORY,
1772                Default::default(),
1773            )
1774            .await;
1775            close_dir_checked(d).await;
1776
1777            let parent = open_parent().await;
1778            let deleter = fasync::Task::spawn(async move {
1779                let wait_time = rand::random_range(0..5);
1780                fasync::Timer::new(Duration::from_millis(wait_time)).await;
1781                match parent
1782                    .unlink(CHILD, &fio::UnlinkOptions::default())
1783                    .await
1784                    .expect("FIDL call failed")
1785                    .map_err(zx::Status::err_from_raw)
1786                {
1787                    Ok(()) => {}
1788                    Err(zx::Status::NOT_EMPTY) => {}
1789                    Err(e) => panic!("Unexpected status from unlink: {:?}", e),
1790                };
1791                close_dir_checked(parent).await;
1792            });
1793
1794            let parent = open_parent().await;
1795            let writer = fasync::Task::spawn(async move {
1796                let child_or = open_dir(
1797                    &parent,
1798                    CHILD,
1799                    fio::PERM_READABLE | fio::PERM_WRITABLE | fio::Flags::PROTOCOL_DIRECTORY,
1800                    &Default::default(),
1801                )
1802                .await;
1803                if let Err(e) = &child_or {
1804                    // The directory was already deleted.
1805                    assert_eq!(
1806                        e.root_cause().downcast_ref::<zx::Status>().expect("No status"),
1807                        &zx::Status::NOT_FOUND
1808                    );
1809                    close_dir_checked(parent).await;
1810                    return;
1811                }
1812                let child = child_or.unwrap();
1813                let _: Vec<_> = child.query().await.expect("query failed");
1814                match open_file(
1815                    &child,
1816                    GRANDCHILD,
1817                    fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_READABLE | fio::Flags::PROTOCOL_FILE,
1818                    &Default::default(),
1819                )
1820                .await
1821                {
1822                    Ok(grandchild) => {
1823                        let _: Vec<_> = grandchild.query().await.expect("query failed");
1824                        close_file_checked(grandchild).await;
1825                        // We added the child before the directory was deleted; go ahead and
1826                        // clean up.
1827                        child
1828                            .unlink(GRANDCHILD, &fio::UnlinkOptions::default())
1829                            .await
1830                            .expect("FIDL call failed")
1831                            .expect("unlink failed");
1832                    }
1833                    Err(e) => {
1834                        // The directory started to be deleted before we created a child.
1835                        // Make sure we get the right error.
1836                        assert_eq!(
1837                            e.root_cause().downcast_ref::<zx::Status>().expect("No status"),
1838                            &zx::Status::ACCESS_DENIED,
1839                        );
1840                    }
1841                };
1842                close_dir_checked(child).await;
1843                close_dir_checked(parent).await;
1844            });
1845            writer.await;
1846            deleter.await;
1847        }
1848
1849        close_dir_checked(parent).await;
1850        fixture.close().await;
1851    }
1852
1853    #[fuchsia::test]
1854    async fn test_readdir() {
1855        let fixture = TestFixture::new().await;
1856        let root = fixture.root();
1857
1858        let open_dir = || {
1859            open_dir_checked(
1860                &root,
1861                "foo",
1862                fio::Flags::FLAG_MAYBE_CREATE
1863                    | fio::PERM_READABLE
1864                    | fio::PERM_WRITABLE
1865                    | fio::Flags::PROTOCOL_DIRECTORY,
1866                Default::default(),
1867            )
1868        };
1869        let parent = Arc::new(open_dir().await);
1870
1871        let files = ["eenie", "meenie", "minie", "moe"];
1872        for file in &files {
1873            let file = open_file_checked(
1874                parent.as_ref(),
1875                file,
1876                fio::Flags::FLAG_MAYBE_CREATE | fio::Flags::PROTOCOL_FILE,
1877                &Default::default(),
1878            )
1879            .await;
1880            close_file_checked(file).await;
1881        }
1882        let dirs = ["fee", "fi", "fo", "fum"];
1883        for dir in &dirs {
1884            let dir = open_dir_checked(
1885                parent.as_ref(),
1886                dir,
1887                fio::Flags::FLAG_MAYBE_CREATE | fio::Flags::PROTOCOL_DIRECTORY,
1888                Default::default(),
1889            )
1890            .await;
1891            close_dir_checked(dir).await;
1892        }
1893        {
1894            parent
1895                .create_symlink("symlink", b"target", None)
1896                .await
1897                .expect("FIDL call failed")
1898                .expect("create_symlink failed");
1899        }
1900
1901        let readdir = |dir: Arc<fio::DirectoryProxy>| async move {
1902            let status = dir.rewind().await.expect("FIDL call failed");
1903            zx::Status::ok(status).expect("rewind failed");
1904            let (status, buf) = dir.read_dirents(fio::MAX_BUF).await.expect("FIDL call failed");
1905            zx::Status::ok(status).expect("read_dirents failed");
1906            let mut entries = vec![];
1907            for res in fuchsia_fs::directory::parse_dir_entries(&buf) {
1908                entries.push(res.expect("Failed to parse entry"));
1909            }
1910            entries
1911        };
1912
1913        let mut expected_entries =
1914            vec![DirEntry { name: ".".to_owned(), kind: DirentKind::Directory }];
1915        expected_entries.extend(
1916            files.iter().map(|&name| DirEntry { name: name.to_owned(), kind: DirentKind::File }),
1917        );
1918        expected_entries.extend(
1919            dirs.iter()
1920                .map(|&name| DirEntry { name: name.to_owned(), kind: DirentKind::Directory }),
1921        );
1922        expected_entries.push(DirEntry { name: "symlink".to_owned(), kind: DirentKind::Symlink });
1923        expected_entries.sort_unstable();
1924        assert_eq!(expected_entries, readdir(Arc::clone(&parent)).await);
1925
1926        // Remove an entry.
1927        parent
1928            .unlink(&expected_entries.pop().unwrap().name, &fio::UnlinkOptions::default())
1929            .await
1930            .expect("FIDL call failed")
1931            .expect("unlink failed");
1932
1933        assert_eq!(expected_entries, readdir(Arc::clone(&parent)).await);
1934
1935        close_dir_checked(Arc::try_unwrap(parent).unwrap()).await;
1936        fixture.close().await;
1937    }
1938
1939    #[fuchsia::test]
1940    async fn test_readdir_multiple_calls() {
1941        let fixture = TestFixture::new().await;
1942        let root = fixture.root();
1943
1944        let parent = open_dir_checked(
1945            &root,
1946            "foo",
1947            fio::Flags::FLAG_MAYBE_CREATE
1948                | fio::PERM_READABLE
1949                | fio::PERM_WRITABLE
1950                | fio::Flags::PROTOCOL_DIRECTORY,
1951            Default::default(),
1952        )
1953        .await;
1954
1955        let files = ["a", "b"];
1956        for file in &files {
1957            let file = open_file_checked(
1958                &parent,
1959                file,
1960                fio::Flags::FLAG_MAYBE_CREATE | fio::Flags::PROTOCOL_FILE,
1961                &Default::default(),
1962            )
1963            .await;
1964            close_file_checked(file).await;
1965        }
1966
1967        // TODO(https://fxbug.dev/42177353): Magic number; can we get this from fuchsia.io?
1968        const DIRENT_SIZE: u64 = 10; // inode: u64, size: u8, kind: u8
1969        const BUFFER_SIZE: u64 = DIRENT_SIZE + 2; // Enough space for a 2-byte name.
1970
1971        let parse_entries = |buf| {
1972            let mut entries = vec![];
1973            for res in fuchsia_fs::directory::parse_dir_entries(buf) {
1974                entries.push(res.expect("Failed to parse entry"));
1975            }
1976            entries
1977        };
1978
1979        let expected_entries = vec![
1980            DirEntry { name: ".".to_owned(), kind: DirentKind::Directory },
1981            DirEntry { name: "a".to_owned(), kind: DirentKind::File },
1982        ];
1983        let (status, buf) = parent.read_dirents(2 * BUFFER_SIZE).await.expect("FIDL call failed");
1984        zx::Status::ok(status).expect("read_dirents failed");
1985        assert_eq!(expected_entries, parse_entries(&buf));
1986
1987        let expected_entries = vec![DirEntry { name: "b".to_owned(), kind: DirentKind::File }];
1988        let (status, buf) = parent.read_dirents(2 * BUFFER_SIZE).await.expect("FIDL call failed");
1989        zx::Status::ok(status).expect("read_dirents failed");
1990        assert_eq!(expected_entries, parse_entries(&buf));
1991
1992        // Subsequent calls yield nothing.
1993        let expected_entries: Vec<DirEntry> = vec![];
1994        let (status, buf) = parent.read_dirents(2 * BUFFER_SIZE).await.expect("FIDL call failed");
1995        zx::Status::ok(status).expect("read_dirents failed");
1996        assert_eq!(expected_entries, parse_entries(&buf));
1997
1998        close_dir_checked(parent).await;
1999        fixture.close().await;
2000    }
2001
2002    #[fuchsia::test]
2003    async fn test_set_large_extended_attribute_on_encrypted_directory() {
2004        let fixture = TestFixture::new().await;
2005        let crypt: Arc<CryptBase> = fixture.crypt().unwrap();
2006        let root = fixture.root();
2007        let open_dir = || {
2008            open_dir_checked(
2009                &root,
2010                "foo",
2011                fio::Flags::FLAG_MAYBE_CREATE
2012                    | fio::PERM_READABLE
2013                    | fio::PERM_WRITABLE
2014                    | fio::Flags::PROTOCOL_DIRECTORY,
2015                Default::default(),
2016            )
2017        };
2018
2019        let parent: Arc<fio::DirectoryProxy> = Arc::new(open_dir().await);
2020        crypt
2021            .add_wrapping_key(WRAPPING_KEY_ID, [1; 32].into())
2022            .expect("Failed to add wrapping key");
2023        parent
2024            .update_attributes(&fio::MutableNodeAttributes {
2025                encryption_policy: Some(fio::FscryptPolicy {
2026                    key_identifier: WRAPPING_KEY_ID,
2027                    flags: fxfs::object_store::LEGACY_FSCRYPT_FLAGS,
2028                }),
2029                ..Default::default()
2030            })
2031            .await
2032            .expect("FIDL call failed")
2033            .map_err(zx::ok)
2034            .expect("update_attributes failed");
2035        let dir = open_dir_checked(
2036            parent.as_ref(),
2037            "fee",
2038            fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE | fio::Flags::PROTOCOL_DIRECTORY,
2039            Default::default(),
2040        )
2041        .await;
2042
2043        let xattr_name = b"xattr_name";
2044        let value_vec = vec![0x3; 300];
2045
2046        dir.set_extended_attribute(
2047            xattr_name,
2048            fio::ExtendedAttributeValue::Bytes(value_vec.clone()),
2049            fio::SetExtendedAttributeMode::Set,
2050        )
2051        .await
2052        .expect("Failed to make FIDL call")
2053        .expect("Failed to set xattr with create");
2054
2055        let subdir = open_dir_checked(
2056            &dir,
2057            "fo",
2058            fio::Flags::FLAG_MAYBE_CREATE | fio::Flags::PROTOCOL_DIRECTORY,
2059            Default::default(),
2060        )
2061        .await;
2062        close_dir_checked(dir).await;
2063        close_dir_checked(subdir).await;
2064        close_dir_checked(Arc::try_unwrap(parent).unwrap()).await;
2065        let device = fixture.close().await;
2066        let new_fixture = TestFixture::new_with_device(device).await;
2067        let root = new_fixture.root();
2068        let open_dir = || {
2069            open_dir_checked(
2070                &root,
2071                "foo",
2072                fio::PERM_READABLE | fio::Flags::PROTOCOL_DIRECTORY,
2073                Default::default(),
2074            )
2075        };
2076        let parent: Arc<fio::DirectoryProxy> = Arc::new(open_dir().await);
2077
2078        let readdir = |dir: Arc<fio::DirectoryProxy>| async move {
2079            let status = dir.rewind().await.expect("FIDL call failed");
2080            zx::Status::ok(status).expect("rewind failed");
2081            let (status, buf) = dir.read_dirents(fio::MAX_BUF).await.expect("FIDL call failed");
2082            zx::Status::ok(status).expect("read_dirents failed");
2083            let mut entries = vec![];
2084            for res in fuchsia_fs::directory::parse_dir_entries(&buf) {
2085                entries.push(res.expect("Failed to parse entry"));
2086            }
2087            entries
2088        };
2089
2090        let encrypted_entries = readdir(Arc::clone(&parent)).await;
2091        let mut encrypted_name = String::new();
2092        for entry in encrypted_entries {
2093            if entry.name == ".".to_owned() {
2094                continue;
2095            } else {
2096                assert!(entry.name.len() >= FSCRYPT_PADDING);
2097                encrypted_name = entry.name;
2098                assert!(entry.kind == DirentKind::Directory)
2099            }
2100        }
2101
2102        let encrypted_dir = Arc::new(
2103            open_dir_checked(
2104                parent.as_ref(),
2105                &encrypted_name,
2106                fio::Flags::PROTOCOL_DIRECTORY | fio::PERM_READABLE,
2107                Default::default(),
2108            )
2109            .await,
2110        );
2111
2112        assert_eq!(
2113            encrypted_dir
2114                .get_extended_attribute(xattr_name)
2115                .await
2116                .expect("Failed to make FIDL call")
2117                .expect("Failed to get extended attribute"),
2118            fio::ExtendedAttributeValue::Bytes(value_vec)
2119        );
2120
2121        let encrypted_subdir_entries = readdir(Arc::clone(&encrypted_dir)).await;
2122        for entry in encrypted_subdir_entries {
2123            if entry.name == ".".to_owned() {
2124                continue;
2125            } else {
2126                assert!(entry.name.len() >= FSCRYPT_PADDING);
2127                assert!(entry.kind == DirentKind::Directory)
2128            }
2129        }
2130        close_dir_checked(Arc::try_unwrap(encrypted_dir).unwrap()).await;
2131        close_dir_checked(Arc::try_unwrap(parent).unwrap()).await;
2132        new_fixture.close().await;
2133    }
2134
2135    #[fuchsia::test]
2136    async fn test_set_large_extended_attribute_on_encrypted_file() {
2137        let fixture = TestFixture::new().await;
2138        let crypt: Arc<CryptBase> = fixture.crypt().unwrap();
2139        let root = fixture.root();
2140        let open_dir = || {
2141            open_dir_checked(
2142                &root,
2143                "foo",
2144                fio::Flags::FLAG_MAYBE_CREATE
2145                    | fio::PERM_READABLE
2146                    | fio::PERM_WRITABLE
2147                    | fio::Flags::PROTOCOL_DIRECTORY,
2148                Default::default(),
2149            )
2150        };
2151
2152        let parent: Arc<fio::DirectoryProxy> = Arc::new(open_dir().await);
2153        crypt
2154            .add_wrapping_key(WRAPPING_KEY_ID, [1; 32].into())
2155            .expect("Failed to add wrapping key");
2156        parent
2157            .update_attributes(&fio::MutableNodeAttributes {
2158                encryption_policy: Some(fio::FscryptPolicy {
2159                    key_identifier: WRAPPING_KEY_ID,
2160                    flags: fxfs::object_store::LEGACY_FSCRYPT_FLAGS,
2161                }),
2162                ..Default::default()
2163            })
2164            .await
2165            .expect("FIDL call failed")
2166            .map_err(zx::ok)
2167            .expect("update_attributes failed");
2168        let file = open_file_checked(
2169            parent.as_ref(),
2170            "fee",
2171            fio::Flags::FLAG_MAYBE_CREATE
2172                | fio::PERM_READABLE
2173                | fio::PERM_WRITABLE
2174                | fio::Flags::PROTOCOL_FILE,
2175            &Default::default(),
2176        )
2177        .await;
2178
2179        let buf = vec![0xaa as u8; 512];
2180        file::write(&file, buf.as_slice()).await.expect("write failed");
2181
2182        let xattr_name = b"xattr_name";
2183        let value_vec = vec![0x3; 300];
2184
2185        file.set_extended_attribute(
2186            xattr_name,
2187            fio::ExtendedAttributeValue::Bytes(value_vec.clone()),
2188            fio::SetExtendedAttributeMode::Set,
2189        )
2190        .await
2191        .expect("Failed to make FIDL call")
2192        .expect("Failed to set xattr with create");
2193
2194        close_file_checked(file).await;
2195        close_dir_checked(Arc::try_unwrap(parent).unwrap()).await;
2196        let device = fixture.close().await;
2197        let new_fixture = TestFixture::new_with_device(device).await;
2198        let crypt: Arc<CryptBase> = new_fixture.crypt().unwrap();
2199        let root = new_fixture.root();
2200        let open_dir = || {
2201            open_dir_checked(
2202                &root,
2203                "foo",
2204                fio::PERM_READABLE | fio::Flags::PROTOCOL_DIRECTORY,
2205                Default::default(),
2206            )
2207        };
2208        let parent: Arc<fio::DirectoryProxy> = Arc::new(open_dir().await);
2209
2210        let readdir = |dir: Arc<fio::DirectoryProxy>| async move {
2211            let status = dir.rewind().await.expect("FIDL call failed");
2212            zx::Status::ok(status).expect("rewind failed");
2213            let (status, buf) = dir.read_dirents(fio::MAX_BUF).await.expect("FIDL call failed");
2214            zx::Status::ok(status).expect("read_dirents failed");
2215            let mut entries = vec![];
2216            for res in fuchsia_fs::directory::parse_dir_entries(&buf) {
2217                entries.push(res.expect("Failed to parse entry"));
2218            }
2219            entries
2220        };
2221
2222        let encrypted_entries = readdir(Arc::clone(&parent)).await;
2223        let mut encrypted_name = String::new();
2224        for entry in encrypted_entries {
2225            if entry.name == ".".to_owned() {
2226                continue;
2227            } else {
2228                assert!(entry.name.len() >= FSCRYPT_PADDING);
2229                encrypted_name = entry.name;
2230                assert!(entry.kind == DirentKind::File)
2231            }
2232        }
2233
2234        let encrypted_file = Arc::new(
2235            open_file_checked(
2236                parent.as_ref(),
2237                &encrypted_name,
2238                fio::Flags::PROTOCOL_FILE | fio::PERM_READABLE,
2239                &Default::default(),
2240            )
2241            .await,
2242        );
2243
2244        assert_eq!(
2245            encrypted_file
2246                .get_extended_attribute(xattr_name)
2247                .await
2248                .expect("Failed to make FIDL call")
2249                .expect("Failed to get extended attribute"),
2250            fio::ExtendedAttributeValue::Bytes(value_vec)
2251        );
2252
2253        close_file_checked(Arc::try_unwrap(encrypted_file).unwrap()).await;
2254
2255        crypt
2256            .add_wrapping_key(WRAPPING_KEY_ID, [1; 32].into())
2257            .expect("Failed to add wrapping key");
2258
2259        let file = Arc::new(
2260            open_file_checked(
2261                parent.as_ref(),
2262                "fee",
2263                fio::Flags::PROTOCOL_FILE | fio::PERM_READABLE,
2264                &Default::default(),
2265            )
2266            .await,
2267        );
2268
2269        let rbuf = file::read(&file).await.expect("read failed");
2270        assert_eq!(rbuf, buf);
2271
2272        close_dir_checked(Arc::try_unwrap(parent).unwrap()).await;
2273        new_fixture.close().await;
2274    }
2275
2276    #[fuchsia::test]
2277    async fn test_encrypt_directory_in_unencrypted_volume() {
2278        let fixture = TestFixture::new_unencrypted().await;
2279        let root = fixture.root();
2280        let open_dir = || {
2281            open_dir_checked(
2282                &root,
2283                "foo",
2284                fio::Flags::FLAG_MAYBE_CREATE
2285                    | fio::PERM_READABLE
2286                    | fio::PERM_WRITABLE
2287                    | fio::Flags::PROTOCOL_DIRECTORY,
2288                Default::default(),
2289            )
2290        };
2291
2292        let parent: Arc<fio::DirectoryProxy> = Arc::new(open_dir().await);
2293        let _ = parent
2294            .update_attributes(&fio::MutableNodeAttributes {
2295                encryption_policy: Some(fio::FscryptPolicy {
2296                    key_identifier: WRAPPING_KEY_ID,
2297                    flags: fxfs::object_store::LEGACY_FSCRYPT_FLAGS,
2298                }),
2299                ..Default::default()
2300            })
2301            .await
2302            .expect("FIDL call failed")
2303            .map_err(zx::ok)
2304            .expect_err("encrypting a dir in an unencrypted volume should fail");
2305        close_dir_checked(Arc::try_unwrap(parent).unwrap()).await;
2306        fixture.close().await;
2307    }
2308
2309    #[fuchsia::test]
2310    async fn test_encrypt_directory_with_large_extended_attribute() {
2311        let fixture = TestFixture::new().await;
2312        let crypt: Arc<CryptBase> = fixture.crypt().unwrap();
2313        let root = fixture.root();
2314        let open_dir = || {
2315            open_dir_checked(
2316                &root,
2317                "foo",
2318                fio::Flags::FLAG_MAYBE_CREATE
2319                    | fio::PERM_READABLE
2320                    | fio::PERM_WRITABLE
2321                    | fio::Flags::PROTOCOL_DIRECTORY,
2322                Default::default(),
2323            )
2324        };
2325
2326        let parent: Arc<fio::DirectoryProxy> = Arc::new(open_dir().await);
2327
2328        let xattr_name = b"xattr_name";
2329        let value_vec = vec![0x3; 300];
2330        parent
2331            .set_extended_attribute(
2332                xattr_name,
2333                fio::ExtendedAttributeValue::Bytes(value_vec.clone()),
2334                fio::SetExtendedAttributeMode::Set,
2335            )
2336            .await
2337            .expect("Failed to make FIDL call")
2338            .expect("Failed to set xattr with create");
2339
2340        crypt
2341            .add_wrapping_key(WRAPPING_KEY_ID, [1; 32].into())
2342            .expect("Failed to add wrapping key");
2343        parent
2344            .update_attributes(&fio::MutableNodeAttributes {
2345                encryption_policy: Some(fio::FscryptPolicy {
2346                    key_identifier: WRAPPING_KEY_ID,
2347                    flags: fxfs::object_store::LEGACY_FSCRYPT_FLAGS,
2348                }),
2349                ..Default::default()
2350            })
2351            .await
2352            .expect("FIDL call failed")
2353            .map_err(zx::ok)
2354            .expect("update_attributes failed");
2355        let dir = open_dir_checked(
2356            parent.as_ref(),
2357            "fee",
2358            fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE | fio::Flags::PROTOCOL_DIRECTORY,
2359            Default::default(),
2360        )
2361        .await;
2362
2363        let subdir = open_dir_checked(
2364            &dir,
2365            "fo",
2366            fio::Flags::FLAG_MAYBE_CREATE | fio::Flags::PROTOCOL_DIRECTORY,
2367            Default::default(),
2368        )
2369        .await;
2370        close_dir_checked(dir).await;
2371        close_dir_checked(subdir).await;
2372        close_dir_checked(Arc::try_unwrap(parent).unwrap()).await;
2373        let device = fixture.close().await;
2374        let new_fixture = TestFixture::new_with_device(device).await;
2375        let root = new_fixture.root();
2376        let open_dir = || {
2377            open_dir_checked(
2378                &root,
2379                "foo",
2380                fio::PERM_READABLE | fio::Flags::PROTOCOL_DIRECTORY,
2381                Default::default(),
2382            )
2383        };
2384        let parent: Arc<fio::DirectoryProxy> = Arc::new(open_dir().await);
2385
2386        assert_eq!(
2387            parent
2388                .get_extended_attribute(xattr_name)
2389                .await
2390                .expect("Failed to make FIDL call")
2391                .expect("Failed to get extended attribute"),
2392            fio::ExtendedAttributeValue::Bytes(value_vec)
2393        );
2394
2395        let readdir = |dir: Arc<fio::DirectoryProxy>| async move {
2396            let status = dir.rewind().await.expect("FIDL call failed");
2397            zx::Status::ok(status).expect("rewind failed");
2398            let (status, buf) = dir.read_dirents(fio::MAX_BUF).await.expect("FIDL call failed");
2399            zx::Status::ok(status).expect("read_dirents failed");
2400            let mut entries = vec![];
2401            for res in fuchsia_fs::directory::parse_dir_entries(&buf) {
2402                entries.push(res.expect("Failed to parse entry"));
2403            }
2404            entries
2405        };
2406
2407        let encrypted_entries = readdir(Arc::clone(&parent)).await;
2408        let mut encrypted_name = None;
2409        for entry in encrypted_entries {
2410            if &entry.name == "." {
2411                continue;
2412            } else {
2413                assert!(entry.name.len() >= FSCRYPT_PADDING);
2414                assert!(encrypted_name.replace(entry.name).is_none());
2415                assert!(entry.kind == DirentKind::Directory)
2416            }
2417        }
2418
2419        let encrypted_dir = Arc::new(
2420            open_dir_checked(
2421                parent.as_ref(),
2422                &encrypted_name.as_ref().unwrap(),
2423                fio::Flags::PROTOCOL_DIRECTORY | fio::Flags::PERM_ENUMERATE,
2424                Default::default(),
2425            )
2426            .await,
2427        );
2428
2429        let encrypted_subdir_entries = readdir(Arc::clone(&encrypted_dir)).await;
2430        for entry in encrypted_subdir_entries {
2431            if &entry.name == "." {
2432                continue;
2433            } else {
2434                assert!(entry.name.len() >= FSCRYPT_PADDING);
2435                assert!(entry.kind == DirentKind::Directory)
2436            }
2437        }
2438        close_dir_checked(Arc::try_unwrap(encrypted_dir).unwrap()).await;
2439        close_dir_checked(Arc::try_unwrap(parent).unwrap()).await;
2440        new_fixture.close().await;
2441    }
2442
2443    #[fuchsia::test]
2444    async fn test_unlock_directory_during_readdir() {
2445        let fixture = TestFixture::new().await;
2446        let crypt: Arc<CryptBase> = fixture.crypt().unwrap();
2447        let root = fixture.root();
2448        let open_dir = || {
2449            open_dir_checked(
2450                &root,
2451                "foo",
2452                fio::Flags::FLAG_MAYBE_CREATE
2453                    | fio::PERM_READABLE
2454                    | fio::PERM_WRITABLE
2455                    | fio::Flags::PROTOCOL_DIRECTORY,
2456                Default::default(),
2457            )
2458        };
2459
2460        let parent: Arc<fio::DirectoryProxy> = Arc::new(open_dir().await);
2461        crypt
2462            .add_wrapping_key(WRAPPING_KEY_ID, [1; 32].into())
2463            .expect("Failed to add wrapping key");
2464        parent
2465            .update_attributes(&fio::MutableNodeAttributes {
2466                encryption_policy: Some(fio::FscryptPolicy {
2467                    key_identifier: WRAPPING_KEY_ID,
2468                    flags: fxfs::object_store::LEGACY_FSCRYPT_FLAGS,
2469                }),
2470                ..Default::default()
2471            })
2472            .await
2473            .expect("FIDL call failed")
2474            .map_err(zx::ok)
2475            .expect("update_attributes failed");
2476
2477        // Need enough entries such that multiple read_dirents calls are required to drain all the
2478        // entries.
2479        for i in 0..300 {
2480            let dir = open_dir_checked(
2481                parent.as_ref(),
2482                &format!("plaintext_{}", i),
2483                fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE | fio::Flags::PROTOCOL_DIRECTORY,
2484                Default::default(),
2485            )
2486            .await;
2487            close_dir_checked(dir).await;
2488        }
2489
2490        close_dir_checked(Arc::try_unwrap(parent).unwrap()).await;
2491        let device = fixture.close().await;
2492        let new_fixture = TestFixture::new_with_device(device).await;
2493        let crypt: Arc<CryptBase> = new_fixture.crypt().unwrap();
2494        let root = new_fixture.root();
2495        let open_dir = || {
2496            open_dir_checked(
2497                &root,
2498                "foo",
2499                fio::PERM_READABLE | fio::Flags::PROTOCOL_DIRECTORY,
2500                Default::default(),
2501            )
2502        };
2503        let parent: Arc<fio::DirectoryProxy> = Arc::new(open_dir().await);
2504
2505        let readdir = |dir: Arc<fio::DirectoryProxy>| async move {
2506            let (status, buf) = dir.read_dirents(fio::MAX_BUF).await.expect("FIDL call failed");
2507            zx::Status::ok(status).expect("read_dirents failed");
2508            let mut entries = vec![];
2509            for res in fuchsia_fs::directory::parse_dir_entries(&buf) {
2510                entries.push(res.expect("Failed to parse entry"));
2511            }
2512            entries
2513        };
2514
2515        let encrypted_entries = readdir(Arc::clone(&parent)).await;
2516        for entry in encrypted_entries {
2517            if entry.name == ".".to_owned() {
2518                continue;
2519            } else {
2520                assert!(entry.name.len() >= FSCRYPT_PADDING);
2521                assert!(!entry.name.starts_with("plaintext_"), "{entry:?} isn't encrypted!");
2522                assert!(entry.kind == DirentKind::Directory)
2523            }
2524        }
2525        crypt
2526            .add_wrapping_key(WRAPPING_KEY_ID, [1; 32].into())
2527            .expect("Failed to add wrapping key");
2528        let unencrypted_entries = readdir(Arc::clone(&parent)).await;
2529        for entry in unencrypted_entries {
2530            if entry.name == ".".to_owned() {
2531                continue;
2532            } else {
2533                assert!(entry.name.starts_with("plaintext_"), "{entry:?} is still encrypted!");
2534                assert!(entry.kind == DirentKind::Directory)
2535            }
2536        }
2537
2538        close_dir_checked(Arc::try_unwrap(parent).unwrap()).await;
2539        new_fixture.close().await;
2540    }
2541
2542    #[fuchsia::test]
2543    async fn test_readdir_locked_directory() {
2544        let fixture = TestFixture::new().await;
2545        let crypt: Arc<CryptBase> = fixture.crypt().unwrap();
2546        let root = fixture.root();
2547        let open_dir = || {
2548            open_dir_checked(
2549                &root,
2550                "foo",
2551                fio::Flags::FLAG_MAYBE_CREATE
2552                    | fio::PERM_READABLE
2553                    | fio::PERM_WRITABLE
2554                    | fio::Flags::PROTOCOL_DIRECTORY,
2555                Default::default(),
2556            )
2557        };
2558
2559        let parent: Arc<fio::DirectoryProxy> = Arc::new(open_dir().await);
2560        crypt
2561            .add_wrapping_key(WRAPPING_KEY_ID, [1; 32].into())
2562            .expect("Failed to add wrapping key");
2563        parent
2564            .update_attributes(&fio::MutableNodeAttributes {
2565                encryption_policy: Some(fio::FscryptPolicy {
2566                    key_identifier: WRAPPING_KEY_ID,
2567                    flags: fxfs::object_store::LEGACY_FSCRYPT_FLAGS,
2568                }),
2569                ..Default::default()
2570            })
2571            .await
2572            .expect("FIDL call failed")
2573            .map_err(zx::ok)
2574            .expect("update_attributes failed");
2575        let dir = open_dir_checked(
2576            parent.as_ref(),
2577            "fee",
2578            fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE | fio::Flags::PROTOCOL_DIRECTORY,
2579            Default::default(),
2580        )
2581        .await;
2582
2583        let subdir = open_dir_checked(
2584            &dir,
2585            "fo",
2586            fio::Flags::FLAG_MAYBE_CREATE | fio::Flags::PROTOCOL_DIRECTORY,
2587            Default::default(),
2588        )
2589        .await;
2590        close_dir_checked(dir).await;
2591        close_dir_checked(subdir).await;
2592
2593        let readdir = |dir: Arc<fio::DirectoryProxy>| async move {
2594            let status = dir.rewind().await.expect("FIDL call failed");
2595            zx::Status::ok(status).expect("rewind failed");
2596            let (status, buf) = dir.read_dirents(fio::MAX_BUF).await.expect("FIDL call failed");
2597            zx::Status::ok(status).expect("read_dirents failed");
2598            let mut entries = vec![];
2599            for res in fuchsia_fs::directory::parse_dir_entries(&buf) {
2600                entries.push(res.expect("Failed to parse entry"));
2601            }
2602            entries
2603        };
2604
2605        let mut expected_entries =
2606            vec![DirEntry { name: ".".to_owned(), kind: DirentKind::Directory }];
2607
2608        expected_entries.push(DirEntry { name: "fee".to_owned(), kind: DirentKind::Directory });
2609        expected_entries.sort_unstable();
2610        assert_eq!(expected_entries, readdir(Arc::clone(&parent)).await);
2611
2612        close_dir_checked(Arc::try_unwrap(parent).unwrap()).await;
2613        let device = fixture.close().await;
2614        let new_fixture = TestFixture::new_with_device(device).await;
2615        let root = new_fixture.root();
2616        let open_dir = || {
2617            open_dir_checked(
2618                &root,
2619                "foo",
2620                fio::PERM_READABLE | fio::Flags::PROTOCOL_DIRECTORY,
2621                Default::default(),
2622            )
2623        };
2624        let parent: Arc<fio::DirectoryProxy> = Arc::new(open_dir().await);
2625
2626        let encrypted_entries = readdir(Arc::clone(&parent)).await;
2627        let mut encrypted_name = String::new();
2628        for entry in encrypted_entries {
2629            if entry.name == ".".to_owned() {
2630                continue;
2631            } else {
2632                assert!(entry.name.len() >= FSCRYPT_PADDING);
2633                encrypted_name = entry.name;
2634                assert!(entry.kind == DirentKind::Directory)
2635            }
2636        }
2637
2638        let encrypted_dir = Arc::new(
2639            open_dir_checked(
2640                parent.as_ref(),
2641                &encrypted_name,
2642                fio::Flags::PROTOCOL_DIRECTORY | fio::Flags::PERM_ENUMERATE,
2643                Default::default(),
2644            )
2645            .await,
2646        );
2647
2648        let encrypted_subdir_entries = readdir(Arc::clone(&encrypted_dir)).await;
2649        for entry in encrypted_subdir_entries {
2650            if entry.name == ".".to_owned() {
2651                continue;
2652            } else {
2653                assert!(entry.name.len() >= FSCRYPT_PADDING);
2654                assert!(entry.kind == DirentKind::Directory)
2655            }
2656        }
2657        close_dir_checked(Arc::try_unwrap(encrypted_dir).unwrap()).await;
2658        close_dir_checked(Arc::try_unwrap(parent).unwrap()).await;
2659        new_fixture.close().await;
2660    }
2661
2662    #[fuchsia::test]
2663    async fn test_link_into_locked_directory_fails() {
2664        let fixture = TestFixture::new().await;
2665        let crypt: Arc<CryptBase> = fixture.crypt().unwrap();
2666        let root = fixture.root();
2667        let open_dir_1 = || {
2668            open_dir_checked(
2669                &root,
2670                "foo",
2671                fio::Flags::FLAG_MAYBE_CREATE
2672                    | fio::PERM_READABLE
2673                    | fio::PERM_WRITABLE
2674                    | fio::Flags::PROTOCOL_DIRECTORY,
2675                Default::default(),
2676            )
2677        };
2678
2679        let parent_1: Arc<fio::DirectoryProxy> = Arc::new(open_dir_1().await);
2680
2681        let open_dir_2 = || {
2682            open_dir_checked(
2683                &root,
2684                "foo_2",
2685                fio::Flags::FLAG_MAYBE_CREATE
2686                    | fio::PERM_READABLE
2687                    | fio::PERM_WRITABLE
2688                    | fio::Flags::PROTOCOL_DIRECTORY,
2689                Default::default(),
2690            )
2691        };
2692        let parent_2: Arc<fio::DirectoryProxy> = Arc::new(open_dir_2().await);
2693
2694        crypt
2695            .add_wrapping_key(WRAPPING_KEY_ID, [1; 32].into())
2696            .expect("Failed to add wrapping key");
2697        parent_1
2698            .update_attributes(&fio::MutableNodeAttributes {
2699                encryption_policy: Some(fio::FscryptPolicy {
2700                    key_identifier: WRAPPING_KEY_ID,
2701                    flags: fxfs::object_store::LEGACY_FSCRYPT_FLAGS,
2702                }),
2703                ..Default::default()
2704            })
2705            .await
2706            .expect("FIDL call failed")
2707            .map_err(zx::ok)
2708            .expect("update_attributes failed");
2709        parent_2
2710            .update_attributes(&fio::MutableNodeAttributes {
2711                encryption_policy: Some(fio::FscryptPolicy {
2712                    key_identifier: WRAPPING_KEY_ID,
2713                    flags: fxfs::object_store::LEGACY_FSCRYPT_FLAGS,
2714                }),
2715                ..Default::default()
2716            })
2717            .await
2718            .expect("FIDL call failed")
2719            .map_err(zx::ok)
2720            .expect("update_attributes failed");
2721        let file = open_file_checked(
2722            parent_1.as_ref(),
2723            "fee",
2724            fio::Flags::FLAG_MAYBE_CREATE,
2725            &Default::default(),
2726        )
2727        .await;
2728
2729        close_file_checked(file).await;
2730        close_dir_checked(Arc::try_unwrap(parent_1).unwrap()).await;
2731        close_dir_checked(Arc::try_unwrap(parent_2).unwrap()).await;
2732
2733        let device = fixture.close().await;
2734        let new_fixture = TestFixture::new_with_device(device).await;
2735        let root = new_fixture.root();
2736        let open_dir_1 = || {
2737            open_dir_checked(
2738                &root,
2739                "foo",
2740                fio::PERM_READABLE | fio::PERM_WRITABLE | fio::Flags::PROTOCOL_DIRECTORY,
2741                Default::default(),
2742            )
2743        };
2744        let parent_1: Arc<fio::DirectoryProxy> = Arc::new(open_dir_1().await);
2745
2746        let open_dir_2 = || {
2747            open_dir_checked(
2748                &root,
2749                "foo_2",
2750                fio::PERM_READABLE | fio::PERM_WRITABLE | fio::Flags::PROTOCOL_DIRECTORY,
2751                Default::default(),
2752            )
2753        };
2754        let parent_2: Arc<fio::DirectoryProxy> = Arc::new(open_dir_2().await);
2755
2756        let readdir = |dir: Arc<fio::DirectoryProxy>| async move {
2757            let status = dir.rewind().await.expect("FIDL call failed");
2758            zx::Status::ok(status).expect("rewind failed");
2759            let (status, buf) = dir.read_dirents(fio::MAX_BUF).await.expect("FIDL call failed");
2760            zx::Status::ok(status).expect("read_dirents failed");
2761            let mut entries = vec![];
2762            for res in fuchsia_fs::directory::parse_dir_entries(&buf) {
2763                entries.push(res.expect("Failed to parse entry"));
2764            }
2765            entries
2766        };
2767
2768        let encrypted_entries = readdir(Arc::clone(&parent_1)).await;
2769        let mut encrypted_name = String::new();
2770        for entry in encrypted_entries {
2771            if entry.name == ".".to_owned() {
2772                continue;
2773            } else {
2774                assert!(entry.name.len() >= FSCRYPT_PADDING);
2775                encrypted_name = entry.name;
2776                assert!(entry.kind == DirentKind::File)
2777            }
2778        }
2779
2780        let (status, parent_2_token) =
2781            parent_2.deprecated_get_token().await.expect("get token failed");
2782        zx::Status::ok(status).unwrap();
2783
2784        assert_eq!(
2785            parent_1
2786                .link(&encrypted_name, parent_2_token.unwrap().into(), "file_2")
2787                .await
2788                .expect("FIDL transport error"),
2789            zx::Status::ACCESS_DENIED.into_raw()
2790        );
2791
2792        close_dir_checked(Arc::try_unwrap(parent_1).unwrap()).await;
2793        close_dir_checked(Arc::try_unwrap(parent_2).unwrap()).await;
2794        new_fixture.close().await;
2795    }
2796
2797    #[fuchsia::test]
2798    async fn test_rename_in_locked_directory_fails() {
2799        let fixture = TestFixture::new().await;
2800        let crypt: Arc<CryptBase> = fixture.crypt().unwrap();
2801        let root = fixture.root();
2802        let open_dir_1 = || {
2803            open_dir_checked(
2804                &root,
2805                "foo",
2806                fio::Flags::FLAG_MAYBE_CREATE
2807                    | fio::PERM_READABLE
2808                    | fio::PERM_WRITABLE
2809                    | fio::Flags::PROTOCOL_DIRECTORY,
2810                Default::default(),
2811            )
2812        };
2813
2814        let parent_1: Arc<fio::DirectoryProxy> = Arc::new(open_dir_1().await);
2815
2816        let open_dir_2 = || {
2817            open_dir_checked(
2818                &root,
2819                "foo_2",
2820                fio::Flags::FLAG_MAYBE_CREATE
2821                    | fio::PERM_READABLE
2822                    | fio::PERM_WRITABLE
2823                    | fio::Flags::PROTOCOL_DIRECTORY,
2824                Default::default(),
2825            )
2826        };
2827        let parent_2: Arc<fio::DirectoryProxy> = Arc::new(open_dir_2().await);
2828
2829        crypt
2830            .add_wrapping_key(WRAPPING_KEY_ID, [1; 32].into())
2831            .expect("Failed to add wrapping key");
2832        parent_1
2833            .update_attributes(&fio::MutableNodeAttributes {
2834                encryption_policy: Some(fio::FscryptPolicy {
2835                    key_identifier: WRAPPING_KEY_ID,
2836                    flags: fxfs::object_store::LEGACY_FSCRYPT_FLAGS,
2837                }),
2838                ..Default::default()
2839            })
2840            .await
2841            .expect("FIDL call failed")
2842            .map_err(zx::ok)
2843            .expect("update_attributes failed");
2844        parent_2
2845            .update_attributes(&fio::MutableNodeAttributes {
2846                encryption_policy: Some(fio::FscryptPolicy {
2847                    key_identifier: WRAPPING_KEY_ID,
2848                    flags: fxfs::object_store::LEGACY_FSCRYPT_FLAGS,
2849                }),
2850                ..Default::default()
2851            })
2852            .await
2853            .expect("FIDL call failed")
2854            .map_err(zx::ok)
2855            .expect("update_attributes failed");
2856        let file = open_file_checked(
2857            parent_1.as_ref(),
2858            "fee",
2859            fio::Flags::FLAG_MAYBE_CREATE,
2860            &Default::default(),
2861        )
2862        .await;
2863
2864        close_file_checked(file).await;
2865        close_dir_checked(Arc::try_unwrap(parent_1).unwrap()).await;
2866        close_dir_checked(Arc::try_unwrap(parent_2).unwrap()).await;
2867
2868        let device = fixture.close().await;
2869        let new_fixture = TestFixture::new_with_device(device).await;
2870        let root = new_fixture.root();
2871        let open_dir_1 = || {
2872            open_dir_checked(
2873                &root,
2874                "foo",
2875                fio::PERM_READABLE | fio::PERM_WRITABLE | fio::Flags::PROTOCOL_DIRECTORY,
2876                Default::default(),
2877            )
2878        };
2879        let parent_1: Arc<fio::DirectoryProxy> = Arc::new(open_dir_1().await);
2880
2881        let open_dir_2 = || {
2882            open_dir_checked(
2883                &root,
2884                "foo_2",
2885                fio::PERM_READABLE | fio::PERM_WRITABLE | fio::Flags::PROTOCOL_DIRECTORY,
2886                Default::default(),
2887            )
2888        };
2889        let parent_2: Arc<fio::DirectoryProxy> = Arc::new(open_dir_2().await);
2890
2891        let readdir = |dir: Arc<fio::DirectoryProxy>| async move {
2892            let status = dir.rewind().await.expect("FIDL call failed");
2893            zx::Status::ok(status).expect("rewind failed");
2894            let (status, buf) = dir.read_dirents(fio::MAX_BUF).await.expect("FIDL call failed");
2895            zx::Status::ok(status).expect("read_dirents failed");
2896            let mut entries = vec![];
2897            for res in fuchsia_fs::directory::parse_dir_entries(&buf) {
2898                entries.push(res.expect("Failed to parse entry"));
2899            }
2900            entries
2901        };
2902
2903        let encrypted_entries = readdir(Arc::clone(&parent_1)).await;
2904        let mut encrypted_name = String::new();
2905        for entry in encrypted_entries {
2906            if entry.name == ".".to_owned() {
2907                continue;
2908            } else {
2909                assert!(entry.name.len() >= FSCRYPT_PADDING);
2910                encrypted_name = entry.name;
2911                assert!(entry.kind == DirentKind::File)
2912            }
2913        }
2914
2915        let parent_2_token =
2916            parent_2.get_token().await.expect("get token failed").expect("get_token failed");
2917
2918        // Rename cross-directory when locked should fail.
2919        assert_eq!(
2920            parent_1
2921                .rename(&encrypted_name, parent_2_token, "file_2")
2922                .await
2923                .expect("FIDL transport error"),
2924            Err(zx::Status::ACCESS_DENIED.into_raw())
2925        );
2926
2927        let parent_1_token =
2928            parent_1.get_token().await.expect("get token failed").expect("get_token failed");
2929
2930        // Rename same-directory when locked should fail.
2931        assert_eq!(
2932            parent_1
2933                .rename(&encrypted_name, parent_1_token, "file_2")
2934                .await
2935                .expect("FIDL transport error"),
2936            Err(zx::Status::ACCESS_DENIED.into_raw())
2937        );
2938
2939        close_dir_checked(Arc::try_unwrap(parent_1).unwrap()).await;
2940        close_dir_checked(Arc::try_unwrap(parent_2).unwrap()).await;
2941        new_fixture.close().await;
2942    }
2943
2944    #[fuchsia::test]
2945    async fn test_link_encrypted_file_into_directory_encrypted_with_different_key_fails() {
2946        let fixture = TestFixture::new().await;
2947        let crypt: Arc<CryptBase> = fixture.crypt().unwrap();
2948        let root = fixture.root();
2949        let open_dir_1 = || {
2950            open_dir_checked(
2951                &root,
2952                "foo",
2953                fio::Flags::FLAG_MAYBE_CREATE
2954                    | fio::PERM_READABLE
2955                    | fio::PERM_WRITABLE
2956                    | fio::Flags::PROTOCOL_DIRECTORY,
2957                Default::default(),
2958            )
2959        };
2960
2961        let parent_1: Arc<fio::DirectoryProxy> = Arc::new(open_dir_1().await);
2962
2963        let open_dir_2 = || {
2964            open_dir_checked(
2965                &root,
2966                "foo_2",
2967                fio::Flags::FLAG_MAYBE_CREATE
2968                    | fio::PERM_READABLE
2969                    | fio::PERM_WRITABLE
2970                    | fio::Flags::PROTOCOL_DIRECTORY,
2971                Default::default(),
2972            )
2973        };
2974        let parent_2: Arc<fio::DirectoryProxy> = Arc::new(open_dir_2().await);
2975
2976        crypt
2977            .add_wrapping_key(WRAPPING_KEY_ID, [1; 32].into())
2978            .expect("Failed to add wrapping key");
2979
2980        const WRAPPING_KEY_ID_2: WrappingKeyId = u128::to_le_bytes(3);
2981        crypt
2982            .add_wrapping_key(WRAPPING_KEY_ID_2, [2; 32].into())
2983            .expect("Failed to add wrapping key");
2984
2985        parent_1
2986            .update_attributes(&fio::MutableNodeAttributes {
2987                encryption_policy: Some(fio::FscryptPolicy {
2988                    key_identifier: WRAPPING_KEY_ID,
2989                    flags: fxfs::object_store::LEGACY_FSCRYPT_FLAGS,
2990                }),
2991                ..Default::default()
2992            })
2993            .await
2994            .expect("FIDL call failed")
2995            .map_err(zx::ok)
2996            .expect("update_attributes failed");
2997        parent_2
2998            .update_attributes(&fio::MutableNodeAttributes {
2999                encryption_policy: Some(fio::FscryptPolicy {
3000                    key_identifier: WRAPPING_KEY_ID_2,
3001                    flags: fxfs::object_store::LEGACY_FSCRYPT_FLAGS,
3002                }),
3003                ..Default::default()
3004            })
3005            .await
3006            .expect("FIDL call failed")
3007            .map_err(zx::ok)
3008            .expect("update_attributes failed");
3009        let file = open_file_checked(
3010            parent_1.as_ref(),
3011            "fee",
3012            fio::Flags::FLAG_MAYBE_CREATE,
3013            &Default::default(),
3014        )
3015        .await;
3016
3017        close_file_checked(file).await;
3018
3019        let (status, parent_2_token) =
3020            parent_2.deprecated_get_token().await.expect("get token failed");
3021        zx::Status::ok(status).unwrap();
3022
3023        assert_eq!(
3024            parent_1
3025                .link("fee", parent_2_token.unwrap().into(), "file_2")
3026                .await
3027                .expect("FIDL transport error"),
3028            zx::Status::BAD_STATE.into_raw()
3029        );
3030        close_dir_checked(Arc::try_unwrap(parent_1).unwrap()).await;
3031        close_dir_checked(Arc::try_unwrap(parent_2).unwrap()).await;
3032        fixture.close().await;
3033    }
3034
3035    #[fuchsia::test]
3036    async fn test_link_unencrypted_file_into_encrypted_directory_fails() {
3037        let fixture = TestFixture::new().await;
3038        let crypt: Arc<CryptBase> = fixture.crypt().unwrap();
3039        let root = fixture.root();
3040        let open_dir_1 = || {
3041            open_dir_checked(
3042                &root,
3043                "foo",
3044                fio::Flags::FLAG_MAYBE_CREATE
3045                    | fio::PERM_READABLE
3046                    | fio::PERM_WRITABLE
3047                    | fio::Flags::PROTOCOL_DIRECTORY,
3048                Default::default(),
3049            )
3050        };
3051
3052        let parent_1: Arc<fio::DirectoryProxy> = Arc::new(open_dir_1().await);
3053
3054        let open_dir_2 = || {
3055            open_dir_checked(
3056                &root,
3057                "foo_2",
3058                fio::Flags::FLAG_MAYBE_CREATE
3059                    | fio::PERM_READABLE
3060                    | fio::PERM_WRITABLE
3061                    | fio::Flags::PROTOCOL_DIRECTORY,
3062                Default::default(),
3063            )
3064        };
3065        let parent_2: Arc<fio::DirectoryProxy> = Arc::new(open_dir_2().await);
3066
3067        crypt
3068            .add_wrapping_key(WRAPPING_KEY_ID, [1; 32].into())
3069            .expect("Failed to add wrapping key");
3070
3071        parent_1
3072            .update_attributes(&fio::MutableNodeAttributes {
3073                encryption_policy: Some(fio::FscryptPolicy {
3074                    key_identifier: WRAPPING_KEY_ID,
3075                    flags: fxfs::object_store::LEGACY_FSCRYPT_FLAGS,
3076                }),
3077                ..Default::default()
3078            })
3079            .await
3080            .expect("FIDL call failed")
3081            .map_err(zx::ok)
3082            .expect("update_attributes failed");
3083
3084        let file = open_file_checked(
3085            parent_2.as_ref(),
3086            "fee",
3087            fio::Flags::FLAG_MAYBE_CREATE | fio::Flags::PROTOCOL_FILE,
3088            &Default::default(),
3089        )
3090        .await;
3091
3092        close_file_checked(file).await;
3093
3094        let (status, parent_1_token) =
3095            parent_1.deprecated_get_token().await.expect("get token failed");
3096        zx::Status::ok(status).unwrap();
3097
3098        assert_eq!(
3099            parent_2
3100                .link("fee", parent_1_token.unwrap().into(), "file")
3101                .await
3102                .expect("FIDL transport error"),
3103            zx::Status::BAD_STATE.into_raw()
3104        );
3105        close_dir_checked(Arc::try_unwrap(parent_1).unwrap()).await;
3106        close_dir_checked(Arc::try_unwrap(parent_2).unwrap()).await;
3107        fixture.close().await;
3108    }
3109
3110    #[fuchsia::test]
3111    async fn test_link_locked_directory_into_unencrypted_dir() {
3112        let fixture = TestFixture::new().await;
3113        let crypt: Arc<CryptBase> = fixture.crypt().unwrap();
3114        let root = fixture.root();
3115        let open_dir_1 = || {
3116            open_dir_checked(
3117                &root,
3118                "foo",
3119                fio::Flags::FLAG_MAYBE_CREATE
3120                    | fio::PERM_READABLE
3121                    | fio::PERM_WRITABLE
3122                    | fio::Flags::PROTOCOL_DIRECTORY,
3123                Default::default(),
3124            )
3125        };
3126
3127        let parent_1: Arc<fio::DirectoryProxy> = Arc::new(open_dir_1().await);
3128
3129        let open_dir_2 = || {
3130            open_dir_checked(
3131                &root,
3132                "foo_2",
3133                fio::Flags::FLAG_MAYBE_CREATE
3134                    | fio::PERM_READABLE
3135                    | fio::PERM_WRITABLE
3136                    | fio::Flags::PROTOCOL_DIRECTORY,
3137                Default::default(),
3138            )
3139        };
3140        let parent_2: Arc<fio::DirectoryProxy> = Arc::new(open_dir_2().await);
3141
3142        crypt
3143            .add_wrapping_key(WRAPPING_KEY_ID, [1; 32].into())
3144            .expect("Failed to add wrapping key");
3145        parent_1
3146            .update_attributes(&fio::MutableNodeAttributes {
3147                encryption_policy: Some(fio::FscryptPolicy {
3148                    key_identifier: WRAPPING_KEY_ID,
3149                    flags: fxfs::object_store::LEGACY_FSCRYPT_FLAGS,
3150                }),
3151                ..Default::default()
3152            })
3153            .await
3154            .expect("FIDL call failed")
3155            .map_err(zx::ok)
3156            .expect("update_attributes failed");
3157        let file = open_file_checked(
3158            parent_1.as_ref(),
3159            "fee",
3160            fio::Flags::FLAG_MAYBE_CREATE
3161                | fio::PERM_READABLE
3162                | fio::PERM_WRITABLE
3163                | fio::Flags::PROTOCOL_FILE,
3164            &Default::default(),
3165        )
3166        .await;
3167        let _ = file
3168            .write(&[8; 8192])
3169            .await
3170            .expect("FIDL call failed")
3171            .map_err(zx::Status::err_from_raw)
3172            .expect("write failed");
3173
3174        close_file_checked(file).await;
3175        close_dir_checked(Arc::try_unwrap(parent_1).unwrap()).await;
3176        close_dir_checked(Arc::try_unwrap(parent_2).unwrap()).await;
3177
3178        let device = fixture.close().await;
3179        let new_fixture = TestFixture::new_with_device(device).await;
3180        let root = new_fixture.root();
3181        let open_dir_1 = || {
3182            open_dir_checked(
3183                &root,
3184                "foo",
3185                fio::PERM_READABLE | fio::PERM_WRITABLE | fio::Flags::PROTOCOL_DIRECTORY,
3186                Default::default(),
3187            )
3188        };
3189        let parent_1: Arc<fio::DirectoryProxy> = Arc::new(open_dir_1().await);
3190
3191        let open_dir_2 = || {
3192            open_dir_checked(
3193                &root,
3194                "foo_2",
3195                fio::PERM_READABLE | fio::PERM_WRITABLE | fio::Flags::PROTOCOL_DIRECTORY,
3196                Default::default(),
3197            )
3198        };
3199        let parent_2: Arc<fio::DirectoryProxy> = Arc::new(open_dir_2().await);
3200
3201        let readdir = |dir: Arc<fio::DirectoryProxy>| async move {
3202            let status = dir.rewind().await.expect("FIDL call failed");
3203            zx::Status::ok(status).expect("rewind failed");
3204            let (status, buf) = dir.read_dirents(fio::MAX_BUF).await.expect("FIDL call failed");
3205            zx::Status::ok(status).expect("read_dirents failed");
3206            let mut entries = vec![];
3207            for res in fuchsia_fs::directory::parse_dir_entries(&buf) {
3208                entries.push(res.expect("Failed to parse entry"));
3209            }
3210            entries
3211        };
3212
3213        let encrypted_entries = readdir(Arc::clone(&parent_1)).await;
3214        let mut encrypted_name = String::new();
3215        for entry in encrypted_entries {
3216            if entry.name == ".".to_owned() {
3217                continue;
3218            } else {
3219                assert!(entry.name.len() >= FSCRYPT_PADDING);
3220                encrypted_name = entry.name;
3221                assert!(entry.kind == DirentKind::File)
3222            }
3223        }
3224
3225        let (status, parent_2_token) =
3226            parent_2.deprecated_get_token().await.expect("get token failed");
3227        zx::Status::ok(status).unwrap();
3228
3229        assert_eq!(
3230            parent_1
3231                .link(&encrypted_name, parent_2_token.unwrap().into(), "file_2")
3232                .await
3233                .expect("FIDL transport error"),
3234            zx::sys::ZX_OK
3235        );
3236
3237        let file =
3238            open_file_checked(parent_2.as_ref(), "file_2", fio::PERM_READABLE, &Default::default())
3239                .await;
3240        let (mutable_attributes, _immutable_attributes) = file
3241            .get_attributes(
3242                fio::NodeAttributesQuery::CONTENT_SIZE
3243                    | fio::NodeAttributesQuery::STORAGE_SIZE
3244                    | fio::NodeAttributesQuery::LINK_COUNT
3245                    | fio::NodeAttributesQuery::MODIFICATION_TIME
3246                    | fio::NodeAttributesQuery::CHANGE_TIME
3247                    | fio::NodeAttributesQuery::ENCRYPTION_POLICY,
3248            )
3249            .await
3250            .expect("FIDL call failed")
3251            .map_err(zx::Status::err_from_raw)
3252            .expect("get_attributes failed");
3253        assert_eq!(
3254            mutable_attributes.encryption_policy.map(|p| p.key_identifier),
3255            Some(WRAPPING_KEY_ID)
3256        );
3257        assert_eq!(
3258            file.read(fio::MAX_BUF)
3259                .await
3260                .expect("FIDL call failed")
3261                .expect_err("reading an encrypted file should fail"),
3262            zx::Status::BAD_STATE.into_raw()
3263        );
3264
3265        close_dir_checked(Arc::try_unwrap(parent_1).unwrap()).await;
3266        close_dir_checked(Arc::try_unwrap(parent_2).unwrap()).await;
3267        new_fixture.close().await;
3268    }
3269
3270    #[fuchsia::test]
3271    async fn test_encrypted_filename_does_not_have_slashes() {
3272        let fixture = TestFixture::new().await;
3273        let crypt: Arc<CryptBase> = fixture.crypt().unwrap();
3274        let root = fixture.root();
3275        let open_dir = || {
3276            open_dir_checked(
3277                &root,
3278                "foo",
3279                fio::Flags::FLAG_MAYBE_CREATE
3280                    | fio::PERM_READABLE
3281                    | fio::PERM_WRITABLE
3282                    | fio::Flags::PROTOCOL_DIRECTORY,
3283                Default::default(),
3284            )
3285        };
3286
3287        let parent: Arc<fio::DirectoryProxy> = Arc::new(open_dir().await);
3288        crypt
3289            .add_wrapping_key(WRAPPING_KEY_ID, [1; 32].into())
3290            .expect("Failed to add wrapping key");
3291        parent
3292            .update_attributes(&fio::MutableNodeAttributes {
3293                encryption_policy: Some(fio::FscryptPolicy {
3294                    key_identifier: WRAPPING_KEY_ID,
3295                    flags: fxfs::object_store::LEGACY_FSCRYPT_FLAGS,
3296                }),
3297                ..Default::default()
3298            })
3299            .await
3300            .expect("FIDL call failed")
3301            .map_err(zx::ok)
3302            .expect("update_attributes failed");
3303        const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
3304        for _ in 0..100 {
3305            let one_char = || CHARSET[rand::random_range(0..CHARSET.len())] as char;
3306            let filename: String = std::iter::repeat_with(one_char).take(100).collect();
3307            let dir = open_dir_checked(
3308                parent.as_ref(),
3309                &filename,
3310                fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE | fio::Flags::PROTOCOL_DIRECTORY,
3311                Default::default(),
3312            )
3313            .await;
3314            close_dir_checked(dir).await;
3315        }
3316
3317        close_dir_checked(Arc::try_unwrap(parent).unwrap()).await;
3318        let readdir = |dir: Arc<fio::DirectoryProxy>| async move {
3319            let status = dir.rewind().await.expect("FIDL call failed");
3320            zx::Status::ok(status).expect("rewind failed");
3321            let (status, buf) = dir.read_dirents(fio::MAX_BUF).await.expect("FIDL call failed");
3322            zx::Status::ok(status).expect("read_dirents failed");
3323            let mut entries = vec![];
3324            for res in fuchsia_fs::directory::parse_dir_entries(&buf) {
3325                entries.push(res.expect("Failed to parse entry"));
3326            }
3327            entries
3328        };
3329
3330        let device = fixture.close().await;
3331        let new_fixture = TestFixture::new_with_device(device).await;
3332        let root = new_fixture.root();
3333        let open_dir = || {
3334            open_dir_checked(
3335                &root,
3336                "foo",
3337                fio::PERM_READABLE | fio::Flags::PROTOCOL_DIRECTORY,
3338                Default::default(),
3339            )
3340        };
3341        let parent: Arc<fio::DirectoryProxy> = Arc::new(open_dir().await);
3342
3343        let encrypted_entries = readdir(Arc::clone(&parent)).await;
3344        for entry in encrypted_entries {
3345            if entry.name == ".".to_owned() {
3346                continue;
3347            } else {
3348                assert!(entry.name.len() >= FSCRYPT_PADDING);
3349                assert!(!entry.name.contains("/"));
3350                assert!(entry.kind == DirentKind::Directory)
3351            }
3352        }
3353
3354        close_dir_checked(Arc::try_unwrap(parent).unwrap()).await;
3355        new_fixture.close().await;
3356    }
3357
3358    #[fuchsia::test]
3359    async fn test_stat_locked_file() {
3360        let fixture = TestFixture::new().await;
3361        let crypt: Arc<CryptBase> = fixture.crypt().unwrap();
3362        let root = fixture.root();
3363        let open_dir = || {
3364            open_dir_checked(
3365                &root,
3366                "foo",
3367                fio::Flags::FLAG_MAYBE_CREATE
3368                    | fio::PERM_READABLE
3369                    | fio::PERM_WRITABLE
3370                    | fio::Flags::PROTOCOL_DIRECTORY,
3371                Default::default(),
3372            )
3373        };
3374        let parent = Arc::new(open_dir().await);
3375        crypt
3376            .add_wrapping_key(WRAPPING_KEY_ID, [1; 32].into())
3377            .expect("Failed to add wrapping key");
3378        parent
3379            .update_attributes(&fio::MutableNodeAttributes {
3380                encryption_policy: Some(fio::FscryptPolicy {
3381                    key_identifier: WRAPPING_KEY_ID,
3382                    flags: fxfs::object_store::LEGACY_FSCRYPT_FLAGS,
3383                }),
3384                ..Default::default()
3385            })
3386            .await
3387            .expect("FIDL call failed")
3388            .map_err(zx::ok)
3389            .expect("update_attributes failed");
3390
3391        let file = open_file_checked(
3392            parent.as_ref(),
3393            "file",
3394            fio::Flags::FLAG_MAYBE_CREATE | fio::Flags::PROTOCOL_FILE,
3395            &Default::default(),
3396        )
3397        .await;
3398
3399        close_file_checked(file).await;
3400        close_dir_checked(Arc::try_unwrap(parent).unwrap()).await;
3401
3402        let device = fixture.close().await;
3403        let new_fixture = TestFixture::new_with_device(device).await;
3404        let root = new_fixture.root();
3405        let open_dir = || {
3406            open_dir_checked(
3407                &root,
3408                "foo",
3409                fio::PERM_READABLE | fio::Flags::PROTOCOL_DIRECTORY,
3410                Default::default(),
3411            )
3412        };
3413        let parent: Arc<fio::DirectoryProxy> = Arc::new(open_dir().await);
3414        let (status, buf) = parent.read_dirents(fio::MAX_BUF).await.expect("FIDL call failed");
3415        zx::Status::ok(status).expect("read_dirents failed");
3416        let mut encrypted_entries = vec![];
3417        for res in fuchsia_fs::directory::parse_dir_entries(&buf) {
3418            encrypted_entries.push(res.expect("Failed to parse entry"));
3419        }
3420        let mut encrypted_name = String::new();
3421        for entry in encrypted_entries {
3422            if entry.name == ".".to_owned() {
3423                continue;
3424            } else {
3425                assert!(entry.name.len() >= FSCRYPT_PADDING);
3426                encrypted_name = entry.name;
3427                assert!(entry.kind == DirentKind::File)
3428            }
3429        }
3430
3431        let file = open_file_checked(
3432            parent.as_ref(),
3433            &encrypted_name,
3434            fio::Flags::PROTOCOL_FILE | fio::Flags::PERM_GET_ATTRIBUTES,
3435            &Default::default(),
3436        )
3437        .await;
3438        let (_mutable_attributes, _immutable_attributes) = file
3439            .get_attributes(
3440                fio::NodeAttributesQuery::CONTENT_SIZE
3441                    | fio::NodeAttributesQuery::STORAGE_SIZE
3442                    | fio::NodeAttributesQuery::LINK_COUNT
3443                    | fio::NodeAttributesQuery::MODIFICATION_TIME
3444                    | fio::NodeAttributesQuery::CHANGE_TIME,
3445            )
3446            .await
3447            .expect("FIDL call failed")
3448            .map_err(zx::Status::err_from_raw)
3449            .expect("get_attributes failed");
3450        close_file_checked(file).await;
3451        new_fixture.close().await;
3452    }
3453
3454    #[fuchsia::test]
3455    async fn test_unlink_locked_directory() {
3456        let fixture = TestFixture::new().await;
3457        let crypt: Arc<CryptBase> = fixture.crypt().unwrap();
3458        let root = fixture.root();
3459        let open_dir = || {
3460            open_dir_checked(
3461                &root,
3462                "foo",
3463                fio::Flags::FLAG_MAYBE_CREATE
3464                    | fio::PERM_READABLE
3465                    | fio::PERM_WRITABLE
3466                    | fio::Flags::PROTOCOL_DIRECTORY,
3467                Default::default(),
3468            )
3469        };
3470
3471        let parent: Arc<fio::DirectoryProxy> = Arc::new(open_dir().await);
3472        crypt
3473            .add_wrapping_key(WRAPPING_KEY_ID, [1; 32].into())
3474            .expect("Failed to add wrapping key");
3475        parent
3476            .update_attributes(&fio::MutableNodeAttributes {
3477                encryption_policy: Some(fio::FscryptPolicy {
3478                    key_identifier: WRAPPING_KEY_ID,
3479                    flags: fxfs::object_store::LEGACY_FSCRYPT_FLAGS,
3480                }),
3481                ..Default::default()
3482            })
3483            .await
3484            .expect("FIDL call failed")
3485            .map_err(zx::ok)
3486            .expect("update_attributes failed");
3487        let dir = open_dir_checked(
3488            parent.as_ref(),
3489            "fee",
3490            fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE | fio::Flags::PROTOCOL_DIRECTORY,
3491            Default::default(),
3492        )
3493        .await;
3494
3495        close_dir_checked(dir).await;
3496        close_dir_checked(Arc::try_unwrap(parent).unwrap()).await;
3497        let device = fixture.close().await;
3498        let new_fixture = TestFixture::new_with_device(device).await;
3499        let root = new_fixture.root();
3500        let open_dir = || {
3501            open_dir_checked(
3502                &root,
3503                "foo",
3504                fio::PERM_READABLE | fio::PERM_WRITABLE | fio::Flags::PROTOCOL_DIRECTORY,
3505                Default::default(),
3506            )
3507        };
3508        let parent: Arc<fio::DirectoryProxy> = Arc::new(open_dir().await);
3509
3510        let readdir = |dir: Arc<fio::DirectoryProxy>| async move {
3511            let status = dir.rewind().await.expect("FIDL call failed");
3512            zx::Status::ok(status).expect("rewind failed");
3513            let (status, buf) = dir.read_dirents(fio::MAX_BUF).await.expect("FIDL call failed");
3514            zx::Status::ok(status).expect("read_dirents failed");
3515            let mut entries = vec![];
3516            for res in fuchsia_fs::directory::parse_dir_entries(&buf) {
3517                entries.push(res.expect("Failed to parse entry"));
3518            }
3519            entries
3520        };
3521
3522        let encrypted_entries = readdir(Arc::clone(&parent)).await;
3523        let mut encrypted_name = String::new();
3524        for entry in encrypted_entries {
3525            if entry.name == ".".to_owned() {
3526                continue;
3527            } else {
3528                assert!(entry.name.len() >= FSCRYPT_PADDING);
3529                encrypted_name = entry.name;
3530                assert!(entry.kind == DirentKind::Directory)
3531            }
3532        }
3533
3534        parent
3535            .unlink(&encrypted_name, &fio::UnlinkOptions::default())
3536            .await
3537            .expect("FIDL call failed")
3538            .expect("unlink failed");
3539
3540        let encrypted_entries = readdir(Arc::clone(&parent)).await;
3541        let mut count = 0;
3542        for entry in encrypted_entries {
3543            if entry.name == ".".to_owned() {
3544                continue;
3545            } else {
3546                assert!(entry.name.len() >= FSCRYPT_PADDING);
3547                assert!(entry.kind == DirentKind::Directory)
3548            }
3549            count += 1;
3550        }
3551        assert_eq!(count, 0);
3552        close_dir_checked(Arc::try_unwrap(parent).unwrap()).await;
3553        new_fixture.close().await;
3554    }
3555
3556    #[fuchsia::test]
3557    async fn test_rename_within_locked_encrypted_directory() {
3558        let fixture = TestFixture::new().await;
3559        let crypt: Arc<CryptBase> = fixture.crypt().unwrap();
3560        let root = fixture.root();
3561        let open_dir = || {
3562            open_dir_checked(
3563                &root,
3564                "foo",
3565                fio::Flags::FLAG_MAYBE_CREATE
3566                    | fio::PERM_READABLE
3567                    | fio::PERM_WRITABLE
3568                    | fio::Flags::PROTOCOL_DIRECTORY,
3569                Default::default(),
3570            )
3571        };
3572
3573        let parent: Arc<fio::DirectoryProxy> = Arc::new(open_dir().await);
3574        crypt
3575            .add_wrapping_key(WRAPPING_KEY_ID, [1; 32].into())
3576            .expect("Failed to add wrapping key");
3577        parent
3578            .update_attributes(&fio::MutableNodeAttributes {
3579                encryption_policy: Some(fio::FscryptPolicy {
3580                    key_identifier: WRAPPING_KEY_ID,
3581                    flags: fxfs::object_store::LEGACY_FSCRYPT_FLAGS,
3582                }),
3583                ..Default::default()
3584            })
3585            .await
3586            .expect("FIDL call failed")
3587            .map_err(zx::ok)
3588            .expect("update_attributes failed");
3589        let dir = open_dir_checked(
3590            parent.as_ref(),
3591            "fee",
3592            fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE | fio::Flags::PROTOCOL_DIRECTORY,
3593            Default::default(),
3594        )
3595        .await;
3596
3597        close_dir_checked(dir).await;
3598        close_dir_checked(Arc::try_unwrap(parent).unwrap()).await;
3599        let device = fixture.close().await;
3600        let new_fixture = TestFixture::new_with_device(device).await;
3601        let crypt: Arc<CryptBase> = new_fixture.crypt().unwrap();
3602        let root = new_fixture.root();
3603        let open_dir = || {
3604            open_dir_checked(
3605                &root,
3606                "foo",
3607                fio::PERM_READABLE | fio::PERM_WRITABLE | fio::Flags::PROTOCOL_DIRECTORY,
3608                Default::default(),
3609            )
3610        };
3611        let parent: Arc<fio::DirectoryProxy> = Arc::new(open_dir().await);
3612
3613        let readdir = |dir: Arc<fio::DirectoryProxy>| async move {
3614            let status = dir.rewind().await.expect("FIDL call failed");
3615            zx::Status::ok(status).expect("rewind failed");
3616            let (status, buf) = dir.read_dirents(fio::MAX_BUF).await.expect("FIDL call failed");
3617            zx::Status::ok(status).expect("read_dirents failed");
3618            let mut entries = vec![];
3619            for res in fuchsia_fs::directory::parse_dir_entries(&buf) {
3620                entries.push(res.expect("Failed to parse entry"));
3621            }
3622            entries
3623        };
3624
3625        let encrypted_entries = readdir(Arc::clone(&parent)).await;
3626        let mut encrypted_name = String::new();
3627        for entry in encrypted_entries {
3628            if entry.name == ".".to_owned() {
3629                continue;
3630            } else {
3631                assert!(entry.name.len() >= FSCRYPT_PADDING);
3632                encrypted_name = entry.name;
3633                assert!(entry.kind == DirentKind::Directory)
3634            }
3635        }
3636
3637        let (status, dst_token) = parent.deprecated_get_token().await.expect("FIDL call failed");
3638        zx::Status::ok(status).expect("get_token failed");
3639        let new_encrypted_name = "aabbcc";
3640        parent
3641            .rename(&encrypted_name, zx::Event::from(dst_token.unwrap()), new_encrypted_name)
3642            .await
3643            .expect("FIDL call failed")
3644            .expect_err("rename should fail on a locked directory");
3645        let (status, dst_token) = parent.deprecated_get_token().await.expect("FIDL call failed");
3646        zx::Status::ok(status).expect("get_token failed");
3647        crypt
3648            .add_wrapping_key(WRAPPING_KEY_ID, [1; 32].into())
3649            .expect("Failed to add wrapping key");
3650        parent
3651            .rename("fee", zx::Event::from(dst_token.unwrap()), "new_fee")
3652            .await
3653            .expect("FIDL call failed")
3654            .expect("rename should fail on a locked directory");
3655
3656        let _dir = open_dir_checked(
3657            parent.as_ref(),
3658            "new_fee",
3659            fio::Flags::PROTOCOL_DIRECTORY,
3660            Default::default(),
3661        )
3662        .await;
3663        close_dir_checked(Arc::try_unwrap(parent).unwrap()).await;
3664        new_fixture.close().await;
3665    }
3666
3667    #[fuchsia::test]
3668    async fn test_link_symlink_into_encrypted_directory() {
3669        let fixture = TestFixture::new().await;
3670        let crypt: Arc<CryptBase> = fixture.crypt().unwrap();
3671        let root = fixture.root();
3672        let open_dir = || {
3673            open_dir_checked(
3674                &root,
3675                "foo",
3676                fio::Flags::FLAG_MAYBE_CREATE
3677                    | fio::PERM_READABLE
3678                    | fio::PERM_WRITABLE
3679                    | fio::Flags::PROTOCOL_DIRECTORY,
3680                Default::default(),
3681            )
3682        };
3683        let parent = Arc::new(open_dir().await);
3684        crypt
3685            .add_wrapping_key(WRAPPING_KEY_ID, [1; 32].into())
3686            .expect("Failed to add wrapping key");
3687        parent
3688            .update_attributes(&fio::MutableNodeAttributes {
3689                encryption_policy: Some(fio::FscryptPolicy {
3690                    key_identifier: WRAPPING_KEY_ID,
3691                    flags: fxfs::object_store::LEGACY_FSCRYPT_FLAGS,
3692                }),
3693                ..Default::default()
3694            })
3695            .await
3696            .expect("FIDL call failed")
3697            .map_err(zx::ok)
3698            .expect("update_attributes failed");
3699
3700        {
3701            parent
3702                .create_symlink("symlink", b"target", None)
3703                .await
3704                .expect("FIDL call failed")
3705                .expect("create_symlink failed");
3706
3707            async fn open_symlink(root: &fio::DirectoryProxy, path: &str) -> fio::SymlinkProxy {
3708                let (proxy, server_end) = create_proxy::<fio::SymlinkMarker>();
3709                root.open(
3710                    path,
3711                    fio::PERM_READABLE | fio::PERM_WRITABLE | fio::Flags::FLAG_SEND_REPRESENTATION,
3712                    &Default::default(),
3713                    server_end.into_channel(),
3714                )
3715                .expect("open failed");
3716
3717                let representation = proxy
3718                    .take_event_stream()
3719                    .next()
3720                    .await
3721                    .expect("missing Symlink event")
3722                    .expect("failed to read Symlink event")
3723                    .into_on_representation()
3724                    .expect("failed to decode OnRepresentation");
3725
3726                assert_matches!(representation,
3727                    fio::Representation::Symlink(fio::SymlinkInfo{
3728                        target: Some(target), ..
3729                    }) if target == b"target"
3730                );
3731
3732                proxy
3733            }
3734
3735            let proxy = open_symlink(&parent, "symlink").await;
3736
3737            let (status, dst_token) =
3738                parent.deprecated_get_token().await.expect("FIDL call failed");
3739            zx::Status::ok(status).expect("get_token failed");
3740            proxy
3741                .link_into(zx::Event::from(dst_token.unwrap()), "symlink2")
3742                .await
3743                .expect("link_into (FIDL) failed")
3744                .expect("link_into failed");
3745
3746            open_symlink(&parent, "symlink2").await;
3747        }
3748
3749        fixture.close().await;
3750    }
3751
3752    #[fuchsia::test]
3753    async fn test_link_symlink_into_locked_directory_fails() {
3754        let fixture = TestFixture::new().await;
3755        let crypt: Arc<CryptBase> = fixture.crypt().unwrap();
3756        let root = fixture.root();
3757        let open_dir = || {
3758            open_dir_checked(
3759                &root,
3760                "foo",
3761                fio::Flags::FLAG_MAYBE_CREATE
3762                    | fio::PERM_READABLE
3763                    | fio::PERM_WRITABLE
3764                    | fio::Flags::PROTOCOL_DIRECTORY,
3765                Default::default(),
3766            )
3767        };
3768        let parent = Arc::new(open_dir().await);
3769        crypt
3770            .add_wrapping_key(WRAPPING_KEY_ID, [1; 32].into())
3771            .expect("Failed to add wrapping key");
3772        parent
3773            .update_attributes(&fio::MutableNodeAttributes {
3774                encryption_policy: Some(fio::FscryptPolicy {
3775                    key_identifier: WRAPPING_KEY_ID,
3776                    flags: fxfs::object_store::LEGACY_FSCRYPT_FLAGS,
3777                }),
3778                ..Default::default()
3779            })
3780            .await
3781            .expect("FIDL call failed")
3782            .map_err(zx::ok)
3783            .expect("update_attributes failed");
3784        close_dir_checked(Arc::try_unwrap(parent).unwrap()).await;
3785
3786        let device = fixture.close().await;
3787        let new_fixture = TestFixture::new_with_device(device).await;
3788        let root = new_fixture.root();
3789        let open_dir = || {
3790            open_dir_checked(
3791                &root,
3792                "foo",
3793                fio::PERM_READABLE | fio::PERM_WRITABLE | fio::Flags::PROTOCOL_DIRECTORY,
3794                Default::default(),
3795            )
3796        };
3797        let parent: Arc<fio::DirectoryProxy> = Arc::new(open_dir().await);
3798        {
3799            root.create_symlink("symlink", b"target", None)
3800                .await
3801                .expect("FIDL call failed")
3802                .expect("create_symlink failed");
3803
3804            async fn open_symlink(root: &fio::DirectoryProxy, path: &str) -> fio::SymlinkProxy {
3805                let (proxy, server_end) = create_proxy::<fio::SymlinkMarker>();
3806                root.open(
3807                    path,
3808                    fio::PERM_READABLE | fio::PERM_WRITABLE | fio::Flags::FLAG_SEND_REPRESENTATION,
3809                    &Default::default(),
3810                    server_end.into_channel(),
3811                )
3812                .expect("open failed");
3813
3814                let representation = proxy
3815                    .take_event_stream()
3816                    .next()
3817                    .await
3818                    .expect("missing Symlink event")
3819                    .expect("failed to read Symlink event")
3820                    .into_on_representation()
3821                    .expect("failed to decode OnRepresentation");
3822
3823                assert_matches!(representation,
3824                    fio::Representation::Symlink(fio::SymlinkInfo{
3825                        target: Some(target), ..
3826                    }) if target == b"target"
3827                );
3828
3829                proxy
3830            }
3831
3832            let proxy = open_symlink(&root, "symlink").await;
3833
3834            let (status, dst_token) =
3835                parent.deprecated_get_token().await.expect("FIDL call failed");
3836            zx::Status::ok(status).expect("get_token failed");
3837            proxy
3838                .link_into(zx::Event::from(dst_token.unwrap()), "symlink2")
3839                .await
3840                .expect("link_into (FIDL) failed")
3841                .expect_err("linking into a locked directory should fail");
3842        }
3843
3844        new_fixture.close().await;
3845    }
3846
3847    #[fuchsia::test]
3848    async fn test_stat_locked_symlink() {
3849        let fixture = TestFixture::new().await;
3850        let crypt: Arc<CryptBase> = fixture.crypt().unwrap();
3851        let root = fixture.root();
3852        let open_dir = || {
3853            open_dir_checked(
3854                &root,
3855                "foo",
3856                fio::Flags::FLAG_MAYBE_CREATE
3857                    | fio::PERM_READABLE
3858                    | fio::PERM_WRITABLE
3859                    | fio::Flags::PROTOCOL_DIRECTORY,
3860                Default::default(),
3861            )
3862        };
3863        let parent = Arc::new(open_dir().await);
3864        crypt
3865            .add_wrapping_key(WRAPPING_KEY_ID, [1; 32].into())
3866            .expect("Failed to add wrapping key");
3867        parent
3868            .update_attributes(&fio::MutableNodeAttributes {
3869                encryption_policy: Some(fio::FscryptPolicy {
3870                    key_identifier: WRAPPING_KEY_ID,
3871                    flags: fxfs::object_store::LEGACY_FSCRYPT_FLAGS,
3872                }),
3873                ..Default::default()
3874            })
3875            .await
3876            .expect("FIDL call failed")
3877            .map_err(zx::ok)
3878            .expect("update_attributes failed");
3879
3880        // This is where we create the symlink
3881        parent
3882            .create_symlink("symlink", b"target", None)
3883            .await
3884            .expect("FIDL call failed")
3885            .expect("create_symlink failed");
3886
3887        close_dir_checked(Arc::try_unwrap(parent).unwrap()).await;
3888
3889        let device = fixture.close().await;
3890        let new_fixture = TestFixture::new_with_device(device).await;
3891        let root = new_fixture.root();
3892        let open_dir = || {
3893            open_dir_checked(
3894                &root,
3895                "foo",
3896                fio::PERM_READABLE | fio::Flags::PROTOCOL_DIRECTORY,
3897                Default::default(),
3898            )
3899        };
3900        let parent: Arc<fio::DirectoryProxy> = Arc::new(open_dir().await);
3901        let (status, buf) = parent.read_dirents(fio::MAX_BUF).await.expect("FIDL call failed");
3902        zx::Status::ok(status).expect("read_dirents failed");
3903        let mut encrypted_entries = vec![];
3904        for res in fuchsia_fs::directory::parse_dir_entries(&buf) {
3905            encrypted_entries.push(res.expect("Failed to parse entry"));
3906        }
3907        let mut encrypted_name = String::new();
3908        for entry in encrypted_entries {
3909            if entry.name == ".".to_owned() {
3910                continue;
3911            } else {
3912                assert!(entry.name.len() >= FSCRYPT_PADDING);
3913                encrypted_name = entry.name;
3914                assert!(entry.kind == DirentKind::Symlink)
3915            }
3916        }
3917        {
3918            let (symlink, server_end) = create_proxy::<fio::SymlinkMarker>();
3919            parent
3920                .open(
3921                    &encrypted_name,
3922                    fio::PERM_READABLE | fio::Flags::FLAG_SEND_REPRESENTATION,
3923                    &Default::default(),
3924                    server_end.into_channel(),
3925                )
3926                .expect("open failed");
3927
3928            let representation = symlink
3929                .take_event_stream()
3930                .next()
3931                .await
3932                .expect("missing Symlink event")
3933                .expect("failed to read Symlink event")
3934                .into_on_representation()
3935                .expect("failed to decode OnRepresentation");
3936            let mut encrypted_target = None;
3937            if let fio::Representation::Symlink(fio::SymlinkInfo { target: Some(target), .. }) =
3938                representation
3939            {
3940                encrypted_target = Some(target)
3941            };
3942
3943            let (_mutable, immutable) = symlink
3944                .get_attributes(fio::NodeAttributesQuery::CONTENT_SIZE)
3945                .await
3946                .expect("transport error on get_attributes")
3947                .expect("failed to get attributes on a locked symlink");
3948
3949            assert_eq!(immutable.content_size, encrypted_target.map(|x| x.len() as u64));
3950        }
3951
3952        new_fixture.close().await;
3953    }
3954
3955    #[fuchsia::test]
3956    async fn test_create_symlink_in_locked_directory() {
3957        let fixture = TestFixture::new().await;
3958        let crypt: Arc<CryptBase> = fixture.crypt().unwrap();
3959        let root = fixture.root();
3960        let open_dir = || {
3961            open_dir_checked(
3962                &root,
3963                "foo",
3964                fio::Flags::FLAG_MAYBE_CREATE
3965                    | fio::PERM_READABLE
3966                    | fio::PERM_WRITABLE
3967                    | fio::Flags::PROTOCOL_DIRECTORY,
3968                Default::default(),
3969            )
3970        };
3971        let parent = Arc::new(open_dir().await);
3972        crypt
3973            .add_wrapping_key(WRAPPING_KEY_ID, [1; 32].into())
3974            .expect("Failed to add wrapping key");
3975        parent
3976            .update_attributes(&fio::MutableNodeAttributes {
3977                encryption_policy: Some(fio::FscryptPolicy {
3978                    key_identifier: WRAPPING_KEY_ID,
3979                    flags: fxfs::object_store::LEGACY_FSCRYPT_FLAGS,
3980                }),
3981                ..Default::default()
3982            })
3983            .await
3984            .expect("FIDL call failed")
3985            .map_err(zx::ok)
3986            .expect("update_attributes failed");
3987
3988        close_dir_checked(Arc::try_unwrap(parent).unwrap()).await;
3989
3990        let device = fixture.close().await;
3991        let new_fixture = TestFixture::new_with_device(device).await;
3992        let root = new_fixture.root();
3993        let open_dir = || {
3994            open_dir_checked(
3995                &root,
3996                "foo",
3997                fio::PERM_READABLE | fio::Flags::PROTOCOL_DIRECTORY,
3998                Default::default(),
3999            )
4000        };
4001        let parent: Arc<fio::DirectoryProxy> = Arc::new(open_dir().await);
4002        parent
4003            .create_symlink("symlink", b"target", None)
4004            .await
4005            .expect("FIDL call failed")
4006            .expect_err("creating a symlink in a locked directory should fail");
4007
4008        new_fixture.close().await;
4009    }
4010
4011    #[fuchsia::test]
4012    async fn test_get_encryption_policy_on_locked_directory() {
4013        let fixture = TestFixture::new().await;
4014        let crypt: Arc<CryptBase> = fixture.crypt().unwrap();
4015        let root = fixture.root();
4016        let parent = open_dir_checked(
4017            &root,
4018            "foo",
4019            fio::Flags::FLAG_MAYBE_CREATE
4020                | fio::PERM_READABLE
4021                | fio::PERM_WRITABLE
4022                | fio::Flags::PROTOCOL_DIRECTORY,
4023            Default::default(),
4024        )
4025        .await;
4026        crypt
4027            .add_wrapping_key(WRAPPING_KEY_ID, [1; 32].into())
4028            .expect("Failed to add wrapping key");
4029        let expected_policy = fio::FscryptPolicy {
4030            key_identifier: WRAPPING_KEY_ID,
4031            flags: fio::FscryptPolicyFlags::PAD_16 | fio::FscryptPolicyFlags::IV_INO_LBLK_64,
4032        };
4033        parent
4034            .update_attributes(&fio::MutableNodeAttributes {
4035                encryption_policy: Some(expected_policy.clone()),
4036                ..Default::default()
4037            })
4038            .await
4039            .expect("FIDL call failed")
4040            .map_err(zx::ok)
4041            .expect("update_attributes failed");
4042
4043        let child = open_dir_checked(
4044            &parent,
4045            "child_dir",
4046            fio::Flags::FLAG_MUST_CREATE
4047                | fio::PERM_READABLE
4048                | fio::PERM_WRITABLE
4049                | fio::Flags::PROTOCOL_DIRECTORY,
4050            Default::default(),
4051        )
4052        .await;
4053        close_dir_checked(child).await;
4054        close_dir_checked(parent).await;
4055
4056        let device = fixture.close().await;
4057        // Reopen the filesystem without adding the wrapping key so the directories are locked.
4058        let new_fixture = TestFixture::new_with_device(device).await;
4059        let root = new_fixture.root();
4060        let locked_parent = open_dir_checked(
4061            &root,
4062            "foo",
4063            fio::PERM_READABLE | fio::Flags::PROTOCOL_DIRECTORY,
4064            Default::default(),
4065        )
4066        .await;
4067        let (mutable_attrs, _) = locked_parent
4068            .get_attributes(fio::NodeAttributesQuery::ENCRYPTION_POLICY)
4069            .await
4070            .expect("FIDL call failed")
4071            .map_err(zx::Status::err_from_raw)
4072            .expect("get_attributes failed on locked parent directory");
4073        assert_eq!(mutable_attrs.encryption_policy, Some(expected_policy.clone()));
4074
4075        let entries = fuchsia_fs::directory::readdir(&locked_parent).await.expect("readdir failed");
4076        assert_eq!(entries.len(), 1);
4077        let locked_child = open_dir_checked(
4078            &locked_parent,
4079            &entries[0].name,
4080            fio::PERM_READABLE | fio::Flags::PROTOCOL_DIRECTORY,
4081            Default::default(),
4082        )
4083        .await;
4084        let (child_mutable_attrs, _) = locked_child
4085            .get_attributes(fio::NodeAttributesQuery::ENCRYPTION_POLICY)
4086            .await
4087            .expect("FIDL call failed")
4088            .map_err(zx::Status::err_from_raw)
4089            .expect("get_attributes failed on locked child directory");
4090        assert_eq!(child_mutable_attrs.encryption_policy, Some(expected_policy));
4091
4092        close_dir_checked(locked_child).await;
4093        close_dir_checked(locked_parent).await;
4094        new_fixture.close().await;
4095    }
4096
4097    #[fuchsia::test]
4098    async fn test_symlink() {
4099        let fixture = TestFixture::new().await;
4100
4101        {
4102            let root = fixture.root();
4103
4104            root.create_symlink("symlink", b"target", None)
4105                .await
4106                .expect("FIDL call failed")
4107                .expect("create_symlink failed");
4108
4109            let (proxy, server_end) = create_proxy::<fio::SymlinkMarker>();
4110            root.open(
4111                "symlink",
4112                fio::PERM_READABLE | fio::Flags::FLAG_SEND_REPRESENTATION,
4113                &Default::default(),
4114                server_end.into_channel(),
4115            )
4116            .expect("open failed");
4117
4118            let representation = proxy
4119                .take_event_stream()
4120                .next()
4121                .await
4122                .expect("missing Symlink event")
4123                .expect("failed to read Symlink event")
4124                .into_on_representation()
4125                .expect("failed to decode OnRepresentation");
4126
4127            assert_matches!(representation,
4128                fio::Representation::Symlink(fio::SymlinkInfo{
4129                    target: Some(target), ..
4130                }) if target == b"target"
4131            );
4132
4133            let (proxy, server_end) = create_proxy::<fio::SymlinkMarker>();
4134            root.create_symlink("symlink2", b"target2", Some(server_end))
4135                .await
4136                .expect("FIDL call failed")
4137                .expect("create_symlink failed");
4138
4139            let node_info = proxy.describe().await.expect("FIDL call failed");
4140            assert_matches!(
4141                node_info,
4142                fio::SymlinkInfo { target: Some(target), .. } if target == b"target2"
4143            );
4144
4145            // Unlink the second symlink.
4146            root.unlink("symlink2", &fio::UnlinkOptions::default())
4147                .await
4148                .expect("FIDL call failed")
4149                .expect("unlink failed");
4150
4151            // Rename over the first symlink.
4152            open_file_checked(
4153                &root,
4154                "target",
4155                fio::Flags::FLAG_MAYBE_CREATE
4156                    | fio::PERM_READABLE
4157                    | fio::PERM_WRITABLE
4158                    | fio::Flags::PROTOCOL_FILE,
4159                &Default::default(),
4160            )
4161            .await;
4162            let (status, dst_token) = root.deprecated_get_token().await.expect("FIDL call failed");
4163            zx::Status::ok(status).expect("get_token failed");
4164            root.rename("target", zx::Event::from(dst_token.unwrap()), "symlink")
4165                .await
4166                .expect("FIDL call failed")
4167                .expect("rename failed");
4168
4169            proxy
4170                .get_attributes(fio::NodeAttributesQuery::empty())
4171                .await
4172                .expect("FIDL call failed")
4173                .expect("get_attributes failed");
4174            let node_info = proxy.describe().await.expect("FIDL call failed");
4175            assert_matches!(
4176                node_info,
4177                fio::SymlinkInfo { target: Some(target), .. } if target == b"target2"
4178            );
4179        }
4180
4181        fixture.close().await;
4182    }
4183
4184    #[fuchsia::test]
4185    async fn test_symlink_link_into_unlinked_fails() {
4186        let fixture = TestFixture::new().await;
4187        {
4188            let root = fixture.root();
4189
4190            // 1. Create a symlink.
4191            root.create_symlink("symlink", b"target", None)
4192                .await
4193                .expect("FIDL call failed")
4194                .expect("create_symlink failed");
4195
4196            // 2. Open a connection to it.
4197            let (proxy, server_end) = create_proxy::<fio::SymlinkMarker>();
4198            root.open(
4199                "symlink",
4200                fio::PERM_READABLE | fio::PERM_WRITABLE,
4201                &Default::default(),
4202                server_end.into_channel(),
4203            )
4204            .expect("open failed");
4205
4206            // 3. Unlink the symlink.
4207            root.unlink("symlink", &fio::UnlinkOptions::default())
4208                .await
4209                .expect("FIDL call failed")
4210                .expect("unlink failed");
4211
4212            // 4. Try to LinkInto to a new path.
4213            let dst_token =
4214                root.get_token().await.expect("FIDL call failed").expect("get_token failed");
4215
4216            let link_result = proxy.link_into(dst_token, "symlink_new").await;
4217            assert_matches!(
4218                link_result,
4219                Ok(Err(status)) if status == zx::Status::NOT_FOUND.into_raw()
4220            );
4221
4222            // 5. Verify that symlink_new does not exist.
4223            let (proxy_new, server_end_new) = create_proxy::<fio::SymlinkMarker>();
4224            root.open(
4225                "symlink_new",
4226                fio::PERM_READABLE,
4227                &Default::default(),
4228                server_end_new.into_channel(),
4229            )
4230            .expect("open failed");
4231
4232            let describe_result = proxy_new.describe().await;
4233            assert_matches!(describe_result, Err(_));
4234        }
4235        fixture.close().await;
4236    }
4237
4238    // Creates two files in an inner directory and creates a race between linking the first file
4239    // into another directory and renaming the second file over the first file. There is naive
4240    // TOCTOU bug since we need to take a lock on the source file being linked but we have to look
4241    // up what that file id is before we take any locks.
4242    #[fuchsia::test]
4243    async fn test_race_hard_link_with_unlink() {
4244        let fixture = TestFixture::new().await;
4245        {
4246            let root = fixture.root();
4247
4248            let inner = open_dir_checked(
4249                root,
4250                "bar",
4251                fio::Flags::FLAG_MAYBE_CREATE
4252                    | fio::PERM_READABLE
4253                    | fio::PERM_WRITABLE
4254                    | fio::Flags::PROTOCOL_DIRECTORY,
4255                Default::default(),
4256            )
4257            .await;
4258            let inner2 = open_dir_checked(
4259                &inner,
4260                ".",
4261                fio::PERM_READABLE | fio::PERM_WRITABLE | fio::Flags::PROTOCOL_DIRECTORY,
4262                Default::default(),
4263            )
4264            .await;
4265
4266            let file = open_file_checked(
4267                &inner,
4268                "foo",
4269                fio::Flags::FLAG_MAYBE_CREATE
4270                    | fio::PERM_READABLE
4271                    | fio::PERM_WRITABLE
4272                    | fio::Flags::PROTOCOL_FILE,
4273                &Default::default(),
4274            )
4275            .await;
4276            assert_eq!(
4277                file.write("valid".as_bytes()).await.expect("FIDL failed").expect("Write success"),
4278                5
4279            );
4280            close_file_checked(file).await;
4281
4282            let file = open_file_checked(
4283                &inner,
4284                "foo2",
4285                fio::Flags::FLAG_MAYBE_CREATE
4286                    | fio::PERM_READABLE
4287                    | fio::PERM_WRITABLE
4288                    | fio::Flags::PROTOCOL_FILE,
4289                &Default::default(),
4290            )
4291            .await;
4292            assert_eq!(
4293                file.write("valid".as_bytes()).await.expect("FIDL failed").expect("Write success"),
4294                5
4295            );
4296            close_file_checked(file).await;
4297
4298            let inner_token = inner
4299                .deprecated_get_token()
4300                .await
4301                .expect("fidl failed")
4302                .1
4303                .expect("get_token returned no handle");
4304            let root_token = root
4305                .deprecated_get_token()
4306                .await
4307                .expect("fidl failed")
4308                .1
4309                .expect("get_token returned no handle");
4310
4311            // Takes the lock on the destination dir of the link. Causing it to stall while trying
4312            // take the requisite lock to add a child there. A lock also needs to be taken on the
4313            // object being linked which will interfere with the rename call 50% of the time. Which
4314            // lock gets taken first depends on the sort order, which will be dependent on the
4315            // object ids for the two objects. So 50% of the time, this test can spuriously pass.
4316            let write_lock = fixture
4317                .fs()
4318                .lock_manager()
4319                .write_lock(lock_keys![LockKey::object(
4320                    fixture.volume().volume().store().store_object_id(),
4321                    fixture.volume().root_dir().directory().object_id()
4322                )])
4323                .await;
4324
4325            join!(
4326                async move {
4327                    // Ensure that the other the link task can stay blocked while locking until
4328                    // renaming is complete.
4329                    fasync::Timer::new(Duration::from_millis(50)).await;
4330                    let _lock = write_lock;
4331                },
4332                async move {
4333                    // Give time for the link call to do some initial lookups that the rename will
4334                    // invalidate.
4335                    fasync::Timer::new(Duration::from_millis(25)).await;
4336                    inner
4337                        .rename("foo2", inner_token.into(), "foo")
4338                        .await
4339                        .expect("FIDL call failed")
4340                        .expect("Rename failed");
4341                },
4342                async move {
4343                    // This link should always succeed. Since the rename should be atomic, we
4344                    // should either link in the old file or the new.
4345                    assert_eq!(
4346                        inner2.link("foo", root_token, "baz").await.expect("Fidl call"),
4347                        zx::sys::ZX_OK
4348                    );
4349                }
4350            );
4351        }
4352
4353        // Ensure that the file contents can be read back. If a race in object management happens
4354        // it may resurrect the object in the tree, but the extents will all still be missing.
4355        let file = open_file_checked(
4356            fixture.root(),
4357            "baz",
4358            fio::PERM_READABLE | fio::PERM_WRITABLE | fio::Flags::PROTOCOL_FILE,
4359            &Default::default(),
4360        )
4361        .await;
4362        let buff = file.read(5).await.expect("FIDL failed").expect("Read failed");
4363        close_file_checked(file).await;
4364        assert_eq!(buff.as_slice(), "valid".as_bytes());
4365        fixture.close().await;
4366    }
4367
4368    #[fuchsia::test]
4369    async fn test_hard_link_to_symlink() {
4370        let fixture = TestFixture::new().await;
4371
4372        {
4373            let root = fixture.root();
4374
4375            root.create_symlink("symlink", b"target", None)
4376                .await
4377                .expect("FIDL call failed")
4378                .expect("create_symlink failed");
4379
4380            async fn open_symlink(root: &fio::DirectoryProxy, path: &str) -> fio::SymlinkProxy {
4381                let (proxy, server_end) = create_proxy::<fio::SymlinkMarker>();
4382                root.open(
4383                    path,
4384                    fio::PERM_READABLE | fio::PERM_WRITABLE | fio::Flags::FLAG_SEND_REPRESENTATION,
4385                    &Default::default(),
4386                    server_end.into_channel(),
4387                )
4388                .expect("open failed");
4389
4390                let representation = proxy
4391                    .take_event_stream()
4392                    .next()
4393                    .await
4394                    .expect("missing Symlink event")
4395                    .expect("failed to read Symlink event")
4396                    .into_on_representation()
4397                    .expect("failed to decode OnRepresentation");
4398
4399                assert_matches!(representation,
4400                    fio::Representation::Symlink(fio::SymlinkInfo{
4401                        target: Some(target), ..
4402                    }) if target == b"target"
4403                );
4404
4405                proxy
4406            }
4407
4408            let proxy = open_symlink(&root, "symlink").await;
4409
4410            let (status, dst_token) = root.deprecated_get_token().await.expect("FIDL call failed");
4411            zx::Status::ok(status).expect("get_token failed");
4412            proxy
4413                .link_into(zx::Event::from(dst_token.unwrap()), "symlink2")
4414                .await
4415                .expect("link_into (FIDL) failed")
4416                .expect("link_into failed");
4417
4418            open_symlink(&root, "symlink2").await;
4419        }
4420
4421        fixture.close().await;
4422    }
4423
4424    #[fuchsia::test]
4425    async fn test_symlink_stat() {
4426        let fixture = TestFixture::new().await;
4427
4428        {
4429            let root = fixture.root();
4430
4431            root.create_symlink("symlink", b"target", None)
4432                .await
4433                .expect("FIDL call failed")
4434                .expect("create_symlink failed");
4435
4436            let root = fuchsia_fs::directory::clone(root).expect("clone failed");
4437
4438            fasync::unblock(|| {
4439                let root: std::os::fd::OwnedFd =
4440                    fdio::create_fd(root.into_channel().unwrap().into_zx_channel().into())
4441                        .expect("create_fd failed");
4442
4443                let mut stat = std::mem::MaybeUninit::<libc::stat>::uninit();
4444                let name = std::ffi::CString::new("symlink").expect("CString::new failed");
4445                assert_eq!(
4446                    // SAFETY: The owned fd and NUL-terminated name remain valid for the call.
4447                    // `stat` supplies aligned, exclusive storage for the output. Fuchsia's
4448                    // fstatat does not read its prior contents or retain either pointer; the
4449                    // test checks only the return code and never reads the output.
4450                    unsafe { libc::fstatat(root.as_raw_fd(), name.as_ptr(), stat.as_mut_ptr(), 0) },
4451                    0
4452                );
4453            })
4454            .await;
4455        }
4456
4457        fixture.close().await;
4458    }
4459
4460    #[fuchsia::test]
4461    async fn test_remove_dir_all_with_symlink() {
4462        // This test makes sure that remove_dir_all works.  At time of writing remove_dir_all uses
4463        // d_type from the directory entry to determine whether or not to recurse into directories,
4464        // so this tests that is working correctly.
4465
4466        let fixture = TestFixture::new().await;
4467
4468        {
4469            let root = fixture.root();
4470
4471            let dir = open_dir_checked(
4472                &root,
4473                "dir",
4474                fio::Flags::FLAG_MAYBE_CREATE
4475                    | fio::PERM_READABLE
4476                    | fio::PERM_WRITABLE
4477                    | fio::Flags::PROTOCOL_DIRECTORY,
4478                Default::default(),
4479            )
4480            .await;
4481
4482            dir.create_symlink("symlink", b"target", None)
4483                .await
4484                .expect("FIDL call failed")
4485                .expect("create_symlink failed");
4486
4487            let namespace = fdio::Namespace::installed().expect("Unable to get namespace");
4488            static COUNTER: AtomicU64 = AtomicU64::new(0);
4489            let path = format!("/test_symlink_stat.{}", COUNTER.fetch_add(1, Ordering::Relaxed));
4490            let root = fuchsia_fs::directory::clone(root).expect("clone failed");
4491            namespace
4492                .bind(&path, ClientEnd::new(root.into_channel().unwrap().into_zx_channel()))
4493                .expect("bind failed");
4494            let path_copy = path.clone();
4495            scopeguard::defer!({
4496                let _ = namespace.unbind(&path_copy);
4497            });
4498
4499            fasync::unblock(move || {
4500                assert_matches!(std::fs::remove_dir_all(&format!("{path}/dir")), Ok(()));
4501            })
4502            .await;
4503        }
4504
4505        fixture.close().await;
4506    }
4507
4508    #[fuchsia::test]
4509    async fn extended_attributes() {
4510        let fixture = TestFixture::new().await;
4511        let root = fixture.root();
4512
4513        let file = open_dir_checked(
4514            &root,
4515            "foo",
4516            fio::Flags::FLAG_MAYBE_CREATE
4517                | fio::PERM_READABLE
4518                | fio::PERM_WRITABLE
4519                | fio::Flags::PROTOCOL_DIRECTORY,
4520            Default::default(),
4521        )
4522        .await;
4523
4524        let name = b"security.selinux";
4525        let value_vec = b"bar".to_vec();
4526
4527        {
4528            let (iterator_client, iterator_server) =
4529                fidl::endpoints::create_proxy::<fio::ExtendedAttributeIteratorMarker>();
4530            file.list_extended_attributes(iterator_server).expect("Failed to make FIDL call");
4531            let (chunk, last) = iterator_client
4532                .get_next()
4533                .await
4534                .expect("Failed to make FIDL call")
4535                .expect("Failed to get next iterator chunk");
4536            assert!(last);
4537            assert_eq!(chunk, Vec::<Vec<u8>>::new());
4538        }
4539        assert_eq!(
4540            file.get_extended_attribute(name)
4541                .await
4542                .expect("Failed to make FIDL call")
4543                .expect_err("Got successful message back for missing attribute"),
4544            zx::Status::NOT_FOUND.into_raw(),
4545        );
4546
4547        file.set_extended_attribute(
4548            name,
4549            fio::ExtendedAttributeValue::Bytes(value_vec.clone()),
4550            fio::SetExtendedAttributeMode::Set,
4551        )
4552        .await
4553        .expect("Failed to make FIDL call")
4554        .expect("Failed to set extended attribute");
4555
4556        {
4557            let (iterator_client, iterator_server) =
4558                fidl::endpoints::create_proxy::<fio::ExtendedAttributeIteratorMarker>();
4559            file.list_extended_attributes(iterator_server).expect("Failed to make FIDL call");
4560            let (chunk, last) = iterator_client
4561                .get_next()
4562                .await
4563                .expect("Failed to make FIDL call")
4564                .expect("Failed to get next iterator chunk");
4565            assert!(last);
4566            assert_eq!(chunk, vec![name]);
4567        }
4568        assert_eq!(
4569            file.get_extended_attribute(name)
4570                .await
4571                .expect("Failed to make FIDL call")
4572                .expect("Failed to get extended attribute"),
4573            fio::ExtendedAttributeValue::Bytes(value_vec)
4574        );
4575
4576        file.remove_extended_attribute(name)
4577            .await
4578            .expect("Failed to make FIDL call")
4579            .expect("Failed to remove extended attribute");
4580
4581        {
4582            let (iterator_client, iterator_server) =
4583                fidl::endpoints::create_proxy::<fio::ExtendedAttributeIteratorMarker>();
4584            file.list_extended_attributes(iterator_server).expect("Failed to make FIDL call");
4585            let (chunk, last) = iterator_client
4586                .get_next()
4587                .await
4588                .expect("Failed to make FIDL call")
4589                .expect("Failed to get next iterator chunk");
4590            assert!(last);
4591            assert_eq!(chunk, Vec::<Vec<u8>>::new());
4592        }
4593        assert_eq!(
4594            file.get_extended_attribute(name)
4595                .await
4596                .expect("Failed to make FIDL call")
4597                .expect_err("Got successful message back for missing attribute"),
4598            zx::Status::NOT_FOUND.into_raw(),
4599        );
4600
4601        close_dir_checked(file).await;
4602        fixture.close().await;
4603    }
4604
4605    #[fuchsia::test]
4606    async fn extended_attribute_set_modes() {
4607        let fixture = TestFixture::new().await;
4608        let root = fixture.root();
4609
4610        let dir = open_dir_checked(
4611            &root,
4612            "foo",
4613            fio::Flags::FLAG_MAYBE_CREATE
4614                | fio::PERM_READABLE
4615                | fio::PERM_WRITABLE
4616                | fio::Flags::PROTOCOL_DIRECTORY,
4617            Default::default(),
4618        )
4619        .await;
4620
4621        let name = b"security.selinux";
4622        let value_vec = b"bar".to_vec();
4623        let value2_vec = b"new value".to_vec();
4624
4625        // Can't replace an attribute that doesn't exist yet.
4626        assert_eq!(
4627            dir.set_extended_attribute(
4628                name,
4629                fio::ExtendedAttributeValue::Bytes(value_vec.clone()),
4630                fio::SetExtendedAttributeMode::Replace
4631            )
4632            .await
4633            .expect("Failed to make FIDL call")
4634            .expect_err("Got successful message back from replacing a nonexistent attribute"),
4635            zx::Status::NOT_FOUND.into_raw()
4636        );
4637
4638        // Create works when it doesn't exist.
4639        dir.set_extended_attribute(
4640            name,
4641            fio::ExtendedAttributeValue::Bytes(value_vec.clone()),
4642            fio::SetExtendedAttributeMode::Create,
4643        )
4644        .await
4645        .expect("Failed to make FIDL call")
4646        .expect("Failed to set xattr with create");
4647
4648        // Create doesn't work once it exists though.
4649        assert_eq!(
4650            dir.set_extended_attribute(
4651                name,
4652                fio::ExtendedAttributeValue::Bytes(value2_vec.clone()),
4653                fio::SetExtendedAttributeMode::Create
4654            )
4655            .await
4656            .expect("Failed to make FIDL call")
4657            .expect_err("Got successful message back from replacing a nonexistent attribute"),
4658            zx::Status::ALREADY_EXISTS.into_raw()
4659        );
4660
4661        // But replace does.
4662        dir.set_extended_attribute(
4663            name,
4664            fio::ExtendedAttributeValue::Bytes(value2_vec.clone()),
4665            fio::SetExtendedAttributeMode::Replace,
4666        )
4667        .await
4668        .expect("Failed to make FIDL call")
4669        .expect("Failed to set xattr with create");
4670
4671        close_dir_checked(dir).await;
4672        fixture.close().await;
4673    }
4674
4675    #[fuchsia::test]
4676    async fn test_remove_large_xattr() {
4677        let fixture = TestFixture::new().await;
4678        {
4679            let root = fixture.root();
4680            let dir = open_dir_checked(
4681                &root,
4682                "foo",
4683                fio::Flags::FLAG_MAYBE_CREATE
4684                    | fio::PERM_READABLE
4685                    | fio::PERM_WRITABLE
4686                    | fio::Flags::PROTOCOL_DIRECTORY,
4687                Default::default(),
4688            )
4689            .await;
4690
4691            dir.set_extended_attribute(
4692                "name".as_bytes(),
4693                fio::ExtendedAttributeValue::Bytes(vec![17u8; 300]),
4694                fio::SetExtendedAttributeMode::Create,
4695            )
4696            .await
4697            .expect("FIDL call failed")
4698            .expect("Set xattr failed");
4699
4700            dir.remove_extended_attribute("name".as_bytes())
4701                .await
4702                .expect("FIDL call failed")
4703                .expect("Set xattr failed");
4704        }
4705        fixture.close().await;
4706    }
4707
4708    #[fuchsia::test]
4709    async fn test_create_dir_with_mutable_node_attributes() {
4710        let fixture = TestFixture::new().await;
4711        {
4712            let root_dir = fixture.volume().root_dir();
4713
4714            let path_str = "foo";
4715            let path = Path::validate_and_split(path_str).unwrap();
4716
4717            let (_proxy, server_end) = create_proxy::<fio::DirectoryMarker>();
4718            let mode: u32 = 0o123;
4719            let flags = fio::Flags::PROTOCOL_DIRECTORY | fio::Flags::FLAG_MAYBE_CREATE;
4720            let options = fio::Options {
4721                create_attributes: Some(fio::MutableNodeAttributes {
4722                    mode: Some(mode),
4723                    ..Default::default()
4724                }),
4725                ..Default::default()
4726            };
4727
4728            let request = ObjectRequest::new(flags, &options, server_end.into_channel());
4729            let dir = root_dir.lookup(&flags, path, &request).await.expect("lookup failed");
4730
4731            let attrs = dir
4732                .clone()
4733                .into_any()
4734                .downcast::<FxDirectory>()
4735                .expect("Not a directory")
4736                .get_attributes(
4737                    fio::NodeAttributesQuery::MODE
4738                        | fio::NodeAttributesQuery::UID
4739                        | fio::NodeAttributesQuery::ACCESS_TIME,
4740                )
4741                .await
4742                .expect("FIDL call failed");
4743            assert_eq!(attrs.mutable_attributes.mode.unwrap(), mode);
4744            // Since the POSIX mode attribute was set, we expect default values for the other POSIX
4745            // attributes.
4746            assert_eq!(attrs.mutable_attributes.uid.unwrap(), 0);
4747            // Expect these attributes to be None as they were not queried in `get_attributes(..)`
4748            assert!(attrs.mutable_attributes.gid.is_none());
4749            assert!(attrs.mutable_attributes.rdev.is_none());
4750            assert!(attrs.mutable_attributes.access_time.is_some());
4751        }
4752        fixture.close().await;
4753    }
4754
4755    #[fuchsia::test]
4756    async fn test_create_dir_with_default_mutable_node_attributes() {
4757        let fixture = TestFixture::new().await;
4758        {
4759            let root_dir = fixture.volume().root_dir();
4760
4761            let path_str = "foo";
4762            let path = Path::validate_and_split(path_str).unwrap();
4763
4764            let (_proxy, server_end) = create_proxy::<fio::DirectoryMarker>();
4765            let flags = fio::Flags::PROTOCOL_DIRECTORY | fio::Flags::FLAG_MAYBE_CREATE;
4766            let options = fio::Options {
4767                create_attributes: Some(fio::MutableNodeAttributes { ..Default::default() }),
4768                ..Default::default()
4769            };
4770
4771            let request = ObjectRequest::new(flags, &options, server_end.into_channel());
4772            let dir = root_dir.lookup(&flags, path, &request).await.expect("lookup failed");
4773
4774            let attrs = dir
4775                .clone()
4776                .into_any()
4777                .downcast::<FxDirectory>()
4778                .expect("Not a directory")
4779                .get_attributes(fio::NodeAttributesQuery::MODE)
4780                .await
4781                .expect("FIDL call failed");
4782            // Although mode was requested, it was not set when creating the directory. So we
4783            // expect None.
4784            assert!(attrs.mutable_attributes.mode.is_none());
4785            // The attributes not requested should be None.
4786            assert!(attrs.mutable_attributes.uid.is_none());
4787            assert!(attrs.mutable_attributes.gid.is_none());
4788            assert!(attrs.mutable_attributes.rdev.is_none());
4789            assert!(attrs.mutable_attributes.creation_time.is_none());
4790            assert!(attrs.mutable_attributes.modification_time.is_none());
4791            assert!(attrs.mutable_attributes.access_time.is_none());
4792        }
4793        fixture.close().await;
4794    }
4795
4796    #[fuchsia::test]
4797    async fn test_create_dir_using_flags_and_options() {
4798        let fixture = TestFixture::new().await;
4799        {
4800            let root_dir = fixture.volume().root_dir();
4801
4802            let path_str = "foo";
4803            let path = Path::validate_and_split(path_str).unwrap();
4804
4805            let (_proxy, server_end) = create_proxy::<fio::DirectoryMarker>();
4806            let mode: u32 = 0o123;
4807            let flags = fio::Flags::PROTOCOL_DIRECTORY | fio::Flags::FLAG_MAYBE_CREATE;
4808            let options = fio::Options {
4809                create_attributes: Some(fio::MutableNodeAttributes {
4810                    mode: Some(mode),
4811                    ..Default::default()
4812                }),
4813                ..Default::default()
4814            };
4815
4816            // Create directory node.
4817            let request = ObjectRequest::new(flags, &options, server_end.into());
4818            let dir = root_dir.lookup(&flags, path, &request).await.expect("lookup failed");
4819
4820            // Verify that the node was created with the attributes requested.
4821            let attrs = dir
4822                .clone()
4823                .into_any()
4824                .downcast::<FxDirectory>()
4825                .expect("Not a directory")
4826                .get_attributes(
4827                    fio::NodeAttributesQuery::MODE
4828                        | fio::NodeAttributesQuery::UID
4829                        | fio::NodeAttributesQuery::ACCESS_TIME,
4830                )
4831                .await
4832                .expect("FIDL call failed");
4833            assert_eq!(attrs.mutable_attributes.mode.unwrap(), mode);
4834            // Since the POSIX mode attribute was set, we expect default values for the other POSIX
4835            // attributes.
4836            assert_eq!(attrs.mutable_attributes.uid.unwrap(), 0);
4837            // Expect these attributes to be None as they were not queried in `get_attributes(..)`
4838            assert!(attrs.mutable_attributes.gid.is_none());
4839            assert!(attrs.mutable_attributes.rdev.is_none());
4840            assert!(attrs.mutable_attributes.access_time.is_some());
4841        }
4842        fixture.close().await;
4843    }
4844
4845    #[fuchsia::test]
4846    async fn test_create_file_with_mutable_node_attributes() {
4847        let fixture = TestFixture::new().await;
4848        {
4849            let root_dir = fixture.volume().root_dir();
4850
4851            let path_str = "foo";
4852            let path = Path::validate_and_split(path_str).unwrap();
4853
4854            let (_proxy, server_end) = create_proxy::<fio::FileMarker>();
4855            let mode: u32 = 0o123;
4856            let uid = 1;
4857            let gid = 2;
4858            let rdev = 3;
4859            let modification_time = Timestamp::now().as_nanos();
4860
4861            let flags = fio::Flags::PROTOCOL_FILE | fio::Flags::FLAG_MAYBE_CREATE;
4862            let options = fio::Options {
4863                create_attributes: Some(fio::MutableNodeAttributes {
4864                    modification_time: Some(modification_time),
4865                    mode: Some(mode),
4866                    uid: Some(uid),
4867                    gid: Some(gid),
4868                    rdev: Some(rdev),
4869                    ..Default::default()
4870                }),
4871                ..Default::default()
4872            };
4873
4874            let request = ObjectRequest::new(flags, &options, server_end.into_channel());
4875            let file = root_dir.lookup(&flags, path, &request).await.expect("lookup failed");
4876
4877            let attributes = file
4878                .clone()
4879                .into_any()
4880                .downcast::<FxFile>()
4881                .expect("Not a file")
4882                .get_attributes(
4883                    fio::NodeAttributesQuery::CREATION_TIME
4884                        | fio::NodeAttributesQuery::MODIFICATION_TIME
4885                        | fio::NodeAttributesQuery::CHANGE_TIME
4886                        | fio::NodeAttributesQuery::MODE
4887                        | fio::NodeAttributesQuery::UID
4888                        | fio::NodeAttributesQuery::GID
4889                        | fio::NodeAttributesQuery::RDEV,
4890                )
4891                .await
4892                .expect("FIDL call failed");
4893            assert_eq!(mode, attributes.mutable_attributes.mode.unwrap());
4894            assert_eq!(uid, attributes.mutable_attributes.uid.unwrap());
4895            assert_eq!(gid, attributes.mutable_attributes.gid.unwrap());
4896            assert_eq!(rdev, attributes.mutable_attributes.rdev.unwrap());
4897            assert_eq!(modification_time, attributes.mutable_attributes.modification_time.unwrap());
4898            assert!(attributes.mutable_attributes.creation_time.is_some());
4899            assert!(attributes.immutable_attributes.change_time.is_some());
4900        }
4901        fixture.close().await;
4902    }
4903
4904    #[fuchsia::test]
4905    async fn test_create_file_with_default_mutable_node_attributes() {
4906        let fixture = TestFixture::new().await;
4907        {
4908            let root_dir = fixture.volume().root_dir();
4909
4910            let path_str = "foo";
4911            let path = Path::validate_and_split(path_str).unwrap();
4912
4913            let (_proxy, server_end) = create_proxy::<fio::FileMarker>();
4914
4915            let flags = fio::Flags::PROTOCOL_FILE | fio::Flags::FLAG_MAYBE_CREATE;
4916            let options = Default::default();
4917
4918            let request = ObjectRequest::new(flags, &options, server_end.into_channel());
4919            let file = root_dir.lookup(&flags, path, &request).await.expect("lookup failed");
4920
4921            let attrs = file
4922                .clone()
4923                .into_any()
4924                .downcast::<FxFile>()
4925                .expect("Not a directory")
4926                .get_attributes(fio::NodeAttributesQuery::MODE)
4927                .await
4928                .expect("FIDL call failed");
4929            // Although mode was requested, it was not set when creating the directory. So we
4930            // expect that it is None.
4931            assert!(attrs.mutable_attributes.mode.is_none());
4932            // The attributes not requested should be None.
4933            assert!(attrs.mutable_attributes.uid.is_none());
4934            assert!(attrs.mutable_attributes.gid.is_none());
4935            assert!(attrs.mutable_attributes.rdev.is_none());
4936            assert!(attrs.mutable_attributes.creation_time.is_none());
4937            assert!(attrs.mutable_attributes.modification_time.is_none());
4938        }
4939        fixture.close().await;
4940    }
4941
4942    #[fuchsia::test]
4943    async fn test_create_file_using_flags_and_options() {
4944        let fixture = TestFixture::new().await;
4945        {
4946            let root_dir = fixture.volume().root_dir();
4947
4948            let path_str = "foo";
4949            let path = Path::validate_and_split(path_str).unwrap();
4950
4951            let (_proxy, server_end) = create_proxy::<fio::DirectoryMarker>();
4952            let mode: u32 = 0o123;
4953            let uid = 1;
4954            let gid = 2;
4955            let rdev = 3;
4956            let modification_time = Timestamp::now().as_nanos();
4957            let flags = fio::Flags::PROTOCOL_FILE | fio::Flags::FLAG_MAYBE_CREATE;
4958            let options = fio::Options {
4959                create_attributes: Some(fio::MutableNodeAttributes {
4960                    modification_time: Some(modification_time),
4961                    mode: Some(mode),
4962                    uid: Some(uid),
4963                    gid: Some(gid),
4964                    rdev: Some(rdev),
4965                    ..Default::default()
4966                }),
4967                ..Default::default()
4968            };
4969
4970            // Create file node.
4971            let request = ObjectRequest::new(flags, &options, server_end.into());
4972            let file = root_dir.lookup(&flags, path, &request).await.expect("lookup failed");
4973
4974            // Verify that the node was created with the attributes requested.
4975            let attributes = file
4976                .clone()
4977                .into_any()
4978                .downcast::<FxFile>()
4979                .expect("Not a file")
4980                .get_attributes(
4981                    fio::NodeAttributesQuery::CREATION_TIME
4982                        | fio::NodeAttributesQuery::MODIFICATION_TIME
4983                        | fio::NodeAttributesQuery::CHANGE_TIME
4984                        | fio::NodeAttributesQuery::MODE
4985                        | fio::NodeAttributesQuery::UID
4986                        | fio::NodeAttributesQuery::GID
4987                        | fio::NodeAttributesQuery::RDEV,
4988                )
4989                .await
4990                .expect("FIDL call failed");
4991            assert_eq!(mode, attributes.mutable_attributes.mode.unwrap());
4992            assert_eq!(uid, attributes.mutable_attributes.uid.unwrap());
4993            assert_eq!(gid, attributes.mutable_attributes.gid.unwrap());
4994            assert_eq!(rdev, attributes.mutable_attributes.rdev.unwrap());
4995            assert_eq!(modification_time, attributes.mutable_attributes.modification_time.unwrap());
4996            assert!(attributes.mutable_attributes.creation_time.is_some());
4997            assert!(attributes.immutable_attributes.change_time.is_some());
4998        }
4999        fixture.close().await;
5000    }
5001
5002    #[fuchsia::test]
5003    async fn test_update_attributes_also_updates_ctime() {
5004        let fixture = TestFixture::new().await;
5005        let root = fixture.root();
5006
5007        let dir = open_dir_checked(
5008            &root,
5009            "foo",
5010            fio::Flags::FLAG_MAYBE_CREATE
5011                | fio::PERM_READABLE
5012                | fio::PERM_WRITABLE
5013                | fio::Flags::PROTOCOL_DIRECTORY,
5014            Default::default(),
5015        )
5016        .await;
5017
5018        let (_mutable_attributes, immutable_attributes) = dir
5019            .get_attributes(fio::NodeAttributesQuery::CHANGE_TIME)
5020            .await
5021            .expect("FIDL call failed")
5022            .map_err(zx::ok)
5023            .expect("get_attributes failed");
5024
5025        dir.update_attributes(&fio::MutableNodeAttributes {
5026            modification_time: Some(Timestamp::now().as_nanos()),
5027            mode: Some(111),
5028            gid: Some(222),
5029            ..Default::default()
5030        })
5031        .await
5032        .expect("FIDL call failed")
5033        .map_err(zx::ok)
5034        .expect("update_attributes failed");
5035
5036        let (_mutable_attributes, immutable_attributes_after_update) = dir
5037            .get_attributes(fio::NodeAttributesQuery::CHANGE_TIME)
5038            .await
5039            .expect("FIDL call failed")
5040            .map_err(zx::ok)
5041            .expect("get_attributes failed");
5042        assert!(immutable_attributes_after_update.change_time > immutable_attributes.change_time);
5043        fixture.close().await;
5044    }
5045
5046    async fn open_to_get_selinux_context(
5047        root_dir: &fio::DirectoryProxy,
5048        path: &str,
5049        protocol: fio::Flags,
5050    ) -> Option<fio::SelinuxContext> {
5051        // Reopen, querying for the value.
5052        let flags =
5053            protocol | fio::Flags::FLAG_SEND_REPRESENTATION | fio::Flags::PERM_GET_ATTRIBUTES;
5054        let options = fio::Options {
5055            attributes: Some(fio::NodeAttributesQuery::SELINUX_CONTEXT),
5056            ..Default::default()
5057        };
5058        let (node, server_end) = create_proxy::<fio::NodeMarker>();
5059        root_dir.open(path, flags, &options, server_end.into_channel()).expect("Reopening node");
5060        let repr = node
5061            .take_event_stream()
5062            .next()
5063            .await
5064            .expect("Need representation")
5065            .expect("Failed to read")
5066            .into_on_representation()
5067            .unwrap();
5068        match repr {
5069            fio::Representation::Directory(fio::DirectoryInfo {
5070                attributes: Some(attr), ..
5071            })
5072            | fio::Representation::File(fio::FileInfo { attributes: Some(attr), .. })
5073            | fio::Representation::Symlink(fio::SymlinkInfo { attributes: Some(attr), .. }) => {
5074                attr.mutable_attributes.selinux_context
5075            }
5076            _ => panic!("Wrong type returned."),
5077        }
5078    }
5079
5080    #[fuchsia::test]
5081    async fn test_selinux_context_via_open() {
5082        const CONTEXT: &str = "valid";
5083        const CONTEXT2: &str = "also_valid";
5084        let node_info: Vec<(&str, fio::Flags)> =
5085            vec![("dir", fio::Flags::PROTOCOL_DIRECTORY), ("file", fio::Flags::PROTOCOL_FILE)];
5086        let fixture = TestFixture::new().await;
5087        {
5088            let root_dir = fixture.root();
5089
5090            for (path, protocol) in node_info {
5091                // Create node with the context.
5092                let flags = protocol
5093                    | fio::Flags::FLAG_SEND_REPRESENTATION
5094                    | fio::Flags::FLAG_MAYBE_CREATE
5095                    | fio::PERM_READABLE
5096                    | fio::PERM_WRITABLE;
5097                let options = fio::Options {
5098                    create_attributes: Some(fio::MutableNodeAttributes {
5099                        selinux_context: Some(fio::SelinuxContext::Data(CONTEXT.into())),
5100                        ..Default::default()
5101                    }),
5102                    ..Default::default()
5103                };
5104                let (node, server_end) = create_proxy::<fio::NodeMarker>();
5105                root_dir
5106                    .open(path, flags, &options, server_end.into_channel())
5107                    .expect("Creating node");
5108                // Check event stream to allow the creation to complete.
5109                assert!(
5110                    node.take_event_stream()
5111                        .next()
5112                        .await
5113                        .expect("Need representation")
5114                        .expect("Failed to read")
5115                        .into_on_representation()
5116                        .is_some()
5117                );
5118
5119                // Fetches the set value just fine.
5120                assert_eq!(
5121                    open_to_get_selinux_context(&root_dir, &path, protocol).await,
5122                    Some(fio::SelinuxContext::Data(CONTEXT.into()))
5123                );
5124
5125                // See that it is synced with the xattr.
5126                node.set_extended_attribute(
5127                    fio::SELINUX_CONTEXT_NAME.as_bytes(),
5128                    fio::ExtendedAttributeValue::Bytes(CONTEXT2.into()),
5129                    fio::SetExtendedAttributeMode::Replace,
5130                )
5131                .await
5132                .unwrap()
5133                .expect("Updating xattr");
5134                assert_eq!(
5135                    open_to_get_selinux_context(&root_dir, &path, protocol).await,
5136                    Some(fio::SelinuxContext::Data(CONTEXT2.into()))
5137                );
5138
5139                // Make it too long so that it must use the xattr interface.
5140                let vmo = zx::Vmo::create(4000).expect("Creating VMO");
5141                node.set_extended_attribute(
5142                    fio::SELINUX_CONTEXT_NAME.as_bytes(),
5143                    fio::ExtendedAttributeValue::Buffer(vmo),
5144                    fio::SetExtendedAttributeMode::Replace,
5145                )
5146                .await
5147                .unwrap()
5148                .expect("Updating xattr");
5149                assert_matches!(
5150                    open_to_get_selinux_context(&root_dir, &path, protocol).await,
5151                    Some(fio::SelinuxContext::UseExtendedAttributes(fio::EmptyStruct {}))
5152                );
5153
5154                node.remove_extended_attribute(fio::SELINUX_CONTEXT_NAME.as_bytes())
5155                    .await
5156                    .unwrap()
5157                    .expect("Deleting xattr");
5158                assert_matches!(
5159                    open_to_get_selinux_context(&root_dir, &path, protocol).await,
5160                    None
5161                );
5162            }
5163        }
5164        fixture.close().await;
5165    }
5166
5167    #[fuchsia::test]
5168    async fn test_selinux_context_via_open_symlink() {
5169        const CONTEXT: &str = "valid";
5170        let fixture = TestFixture::new().await;
5171        {
5172            let path = "symlink";
5173            let root_dir = fixture.root();
5174            // Create node with the context.
5175            let (node, server_end) = create_proxy::<fio::SymlinkMarker>();
5176            root_dir
5177                .create_symlink(path, ".".as_bytes(), Some(server_end))
5178                .await
5179                .expect("Fidl query")
5180                .expect("Create symlink");
5181
5182            node.set_extended_attribute(
5183                fio::SELINUX_CONTEXT_NAME.as_bytes(),
5184                fio::ExtendedAttributeValue::Bytes(CONTEXT.into()),
5185                fio::SetExtendedAttributeMode::Create,
5186            )
5187            .await
5188            .unwrap()
5189            .expect("Updating xattr");
5190
5191            // Fetches the set value just fine.
5192            assert_eq!(
5193                open_to_get_selinux_context(&root_dir, &path, fio::Flags::PROTOCOL_SYMLINK).await,
5194                Some(fio::SelinuxContext::Data(CONTEXT.into()))
5195            );
5196
5197            // Make it too long so that it must use the xattr interface.
5198            let vmo = zx::Vmo::create(4000).expect("Creating VMO");
5199            node.set_extended_attribute(
5200                fio::SELINUX_CONTEXT_NAME.as_bytes(),
5201                fio::ExtendedAttributeValue::Buffer(vmo),
5202                fio::SetExtendedAttributeMode::Replace,
5203            )
5204            .await
5205            .unwrap()
5206            .expect("Updating xattr");
5207            assert_matches!(
5208                open_to_get_selinux_context(&root_dir, &path, fio::Flags::PROTOCOL_SYMLINK).await,
5209                Some(fio::SelinuxContext::UseExtendedAttributes(fio::EmptyStruct {}))
5210            );
5211
5212            // Erase it comes back with empty string.
5213            node.remove_extended_attribute(fio::SELINUX_CONTEXT_NAME.as_bytes())
5214                .await
5215                .unwrap()
5216                .expect("Deleting xattr");
5217            assert_matches!(
5218                open_to_get_selinux_context(&root_dir, &path, fio::Flags::PROTOCOL_SYMLINK).await,
5219                None
5220            );
5221        }
5222        fixture.close().await;
5223    }
5224
5225    #[fuchsia::test]
5226    async fn test_open_deleted_self() {
5227        let fixture = TestFixture::new().await;
5228        let root = fixture.root();
5229
5230        let dir = open_dir_checked(
5231            &root,
5232            "foo",
5233            fio::Flags::FLAG_MAYBE_CREATE | fio::Flags::PROTOCOL_DIRECTORY,
5234            Default::default(),
5235        )
5236        .await;
5237
5238        root.unlink("foo", &fio::UnlinkOptions::default())
5239            .await
5240            .expect("FIDL call failed")
5241            .expect("unlink failed");
5242
5243        assert_eq!(
5244            open_dir(&root, "foo", fio::Flags::PROTOCOL_DIRECTORY, &Default::default())
5245                .await
5246                .expect_err("Open succeeded")
5247                .root_cause()
5248                .downcast_ref::<zx::Status>()
5249                .expect("No status"),
5250            &zx::Status::NOT_FOUND,
5251        );
5252
5253        open_dir_checked(&dir, ".", fio::Flags::PROTOCOL_DIRECTORY, Default::default()).await;
5254
5255        fixture.close().await;
5256    }
5257
5258    #[fuchsia::test]
5259    async fn test_open3_deleted_self() {
5260        let fixture = TestFixture::new().await;
5261        let root = fixture.root();
5262
5263        const PATH: &str = "foo";
5264
5265        let dir = open_dir_checked(
5266            &root,
5267            PATH,
5268            fio::Flags::FLAG_MAYBE_CREATE | fio::Flags::PROTOCOL_DIRECTORY,
5269            Default::default(),
5270        )
5271        .await;
5272
5273        root.unlink(PATH, &fio::UnlinkOptions::default())
5274            .await
5275            .expect("FIDL call failed")
5276            .expect("unlink failed");
5277
5278        assert_eq!(
5279            open_dir(
5280                &root,
5281                PATH,
5282                fio::Flags::PROTOCOL_DIRECTORY | fio::Flags::FLAG_SEND_REPRESENTATION,
5283                &fio::Options::default()
5284            )
5285            .await
5286            .expect_err("Open succeeded unexpectedly")
5287            .root_cause()
5288            .downcast_ref::<zx::Status>()
5289            .expect("No status"),
5290            &zx::Status::NOT_FOUND,
5291        );
5292
5293        open_dir_checked(
5294            &dir,
5295            ".",
5296            fio::Flags::PROTOCOL_DIRECTORY | fio::Flags::FLAG_SEND_REPRESENTATION,
5297            fio::Options::default(),
5298        )
5299        .await;
5300
5301        fixture.close().await;
5302    }
5303
5304    #[fuchsia::test]
5305    async fn test_update_attributes_persists() {
5306        const DIR: &str = "foo";
5307        let mtime = Some(Timestamp::now().as_nanos());
5308        let atime = Some(Timestamp::now().as_nanos());
5309        let mode = Some(111);
5310
5311        let device = {
5312            let fixture = TestFixture::new().await;
5313            let root = fixture.root();
5314
5315            let dir = open_dir_checked(
5316                &root,
5317                DIR,
5318                fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE | fio::Flags::PROTOCOL_DIRECTORY,
5319                Default::default(),
5320            )
5321            .await;
5322
5323            dir.update_attributes(&fio::MutableNodeAttributes {
5324                modification_time: mtime,
5325                access_time: atime,
5326                mode: mode,
5327                ..Default::default()
5328            })
5329            .await
5330            .expect("update_attributes FIDL call failed")
5331            .map_err(zx::ok)
5332            .expect("update_attributes failed");
5333
5334            // Calling close should flush the node attributes to the device.
5335            fixture.close().await
5336        };
5337
5338        let fixture =
5339            TestFixture::open(device, TestFixtureOptions { format: false, ..Default::default() })
5340                .await;
5341        let root = fixture.root();
5342        let dir = open_dir_checked(
5343            &root,
5344            DIR,
5345            fio::PERM_READABLE | fio::Flags::PROTOCOL_DIRECTORY,
5346            Default::default(),
5347        )
5348        .await;
5349
5350        let (mutable_attributes, _immutable_attributes) = dir
5351            .get_attributes(
5352                fio::NodeAttributesQuery::MODIFICATION_TIME
5353                    | fio::NodeAttributesQuery::ACCESS_TIME
5354                    | fio::NodeAttributesQuery::MODE,
5355            )
5356            .await
5357            .expect("update_attributesFIDL call failed")
5358            .map_err(zx::ok)
5359            .expect("get_attributes failed");
5360        assert_eq!(mutable_attributes.modification_time, mtime);
5361        assert_eq!(mutable_attributes.access_time, atime);
5362        assert_eq!(mutable_attributes.mode, mode);
5363        fixture.close().await;
5364    }
5365
5366    #[fuchsia::test]
5367    async fn test_atime_from_pending_access_time_update_request() {
5368        const DIR: &str = "foo";
5369
5370        let (device, expected_atime, expected_ctime) = {
5371            let fixture = TestFixture::new().await;
5372            let root = fixture.root();
5373
5374            let dir = open_dir_checked(
5375                &root,
5376                DIR,
5377                fio::Flags::FLAG_MAYBE_CREATE
5378                    | fio::PERM_WRITABLE
5379                    | fio::Flags::PROTOCOL_DIRECTORY
5380                    | fio::Flags::PERM_GET_ATTRIBUTES,
5381                fio::Options {
5382                    attributes: Some(fio::NodeAttributesQuery::CHANGE_TIME),
5383                    ..Default::default()
5384                },
5385            )
5386            .await;
5387
5388            let (mutable_attributes, immutable_attributes) = dir
5389                .get_attributes(
5390                    fio::NodeAttributesQuery::CHANGE_TIME
5391                        | fio::NodeAttributesQuery::ACCESS_TIME
5392                        | fio::NodeAttributesQuery::MODIFICATION_TIME,
5393                )
5394                .await
5395                .expect("update_attributes FIDL call failed")
5396                .map_err(zx::ok)
5397                .expect("get_attributes failed");
5398            let initial_ctime = immutable_attributes.change_time;
5399            let initial_atime = mutable_attributes.access_time;
5400            // When creating a node, ctime, mtime, and atime are all updated to the current time.
5401            assert_eq!(initial_atime, initial_ctime);
5402            assert_eq!(initial_atime, mutable_attributes.modification_time);
5403
5404            // Client manages atime and they signal to Fxfs that an access has occurred and it may
5405            // require an access time update. They do so by querying with
5406            // `fio::NodeAttributesQuery::PENDING_ACCESS_TIME_UPDATE`.
5407            let (mutable_attributes, immutable_attributes) = dir
5408                .get_attributes(
5409                    fio::NodeAttributesQuery::CHANGE_TIME
5410                        | fio::NodeAttributesQuery::ACCESS_TIME
5411                        | fio::NodeAttributesQuery::PENDING_ACCESS_TIME_UPDATE,
5412                )
5413                .await
5414                .expect("update_attributes FIDL call failed")
5415                .map_err(zx::ok)
5416                .expect("get_attributes failed");
5417            // atime will be updated as atime <= ctime (or mtime)
5418            assert!(initial_atime < mutable_attributes.access_time);
5419            let updated_atime = mutable_attributes.access_time;
5420            // Calling get_attributes with PENDING_ACCESS_TIME_UPDATE will trigger an update of
5421            // object attributes if access_time needs to be updated. Check that ctime isn't updated.
5422            assert_eq!(initial_ctime, immutable_attributes.change_time);
5423
5424            let (mutable_attributes, _) = dir
5425                .get_attributes(
5426                    fio::NodeAttributesQuery::ACCESS_TIME
5427                        | fio::NodeAttributesQuery::PENDING_ACCESS_TIME_UPDATE,
5428                )
5429                .await
5430                .expect("update_attributes FIDL call failed")
5431                .map_err(zx::ok)
5432                .expect("get_attributes failed");
5433            // atime will be not be updated as atime > ctime (or mtime)
5434            assert_eq!(updated_atime, mutable_attributes.access_time);
5435
5436            (fixture.close().await, mutable_attributes.access_time, initial_ctime)
5437        };
5438
5439        let fixture =
5440            TestFixture::open(device, TestFixtureOptions { format: false, ..Default::default() })
5441                .await;
5442        let root = fixture.root();
5443        let dir = open_dir_checked(
5444            &root,
5445            DIR,
5446            fio::PERM_READABLE | fio::Flags::PROTOCOL_DIRECTORY,
5447            Default::default(),
5448        )
5449        .await;
5450
5451        let (mutable_attributes, immutable_attributes) = dir
5452            .get_attributes(
5453                fio::NodeAttributesQuery::CHANGE_TIME | fio::NodeAttributesQuery::ACCESS_TIME,
5454            )
5455            .await
5456            .expect("update_attributesFIDL call failed")
5457            .map_err(zx::ok)
5458            .expect("get_attributes failed");
5459        assert_eq!(immutable_attributes.change_time, expected_ctime);
5460        assert_eq!(mutable_attributes.access_time, expected_atime);
5461        fixture.close().await;
5462    }
5463
5464    #[fuchsia::test]
5465    async fn test_directory_immediately_tombstoned() {
5466        let fixture = TestFixture::new().await;
5467        let root = fixture.root();
5468
5469        let dir = open_dir_checked(
5470            &root,
5471            "foo",
5472            fio::Flags::FLAG_MAYBE_CREATE
5473                | fio::PERM_WRITABLE
5474                | fio::Flags::PROTOCOL_DIRECTORY
5475                | fio::Flags::PERM_GET_ATTRIBUTES,
5476            fio::Options::default(),
5477        )
5478        .await;
5479
5480        let (_mutable, immutable) = dir
5481            .get_attributes(fio::NodeAttributesQuery::ID)
5482            .await
5483            .expect("transport error on get_attributes")
5484            .expect("get_attributes failed");
5485        let foo_object_id = immutable.id.unwrap();
5486
5487        let dir = open_dir_checked(
5488            &root,
5489            "bar",
5490            fio::Flags::FLAG_MAYBE_CREATE
5491                | fio::PERM_WRITABLE
5492                | fio::Flags::PROTOCOL_DIRECTORY
5493                | fio::Flags::PERM_GET_ATTRIBUTES,
5494            fio::Options::default(),
5495        )
5496        .await;
5497
5498        let (_mutable, immutable) = dir
5499            .get_attributes(fio::NodeAttributesQuery::ID)
5500            .await
5501            .expect("transport error on get_attributes")
5502            .expect("get_attributes failed");
5503        let bar_object_id = immutable.id.unwrap();
5504
5505        // Check rename.
5506        let (status, dst_token) = root.deprecated_get_token().await.expect("FIDL call failed");
5507        zx::Status::ok(status).expect("get_token failed");
5508        root.rename("foo", zx::Event::from(dst_token.unwrap()), "bar")
5509            .await
5510            .expect("FIDL call failed")
5511            .expect("rename failed");
5512
5513        // Allow the graveyard to run.
5514        yield_to_executor().await;
5515
5516        // The easiest way to verify the object has been deleted is to scan the LSM tree.
5517        let assert_not_found = async |oid| {
5518            let tree = fixture.volume().volume().store().tree();
5519            let layer_set = tree.layer_set();
5520            let mut merger = layer_set.merger();
5521            let mut iter = merger.query(Query::FullScan).await.unwrap();
5522            while let Some(item) = iter.get() {
5523                match item {
5524                    ItemRef { value: ObjectValue::None, .. } => {}
5525                    ItemRef {
5526                        key: ObjectKey { object_id, data: ObjectKeyData::Object }, ..
5527                    } => {
5528                        assert_ne!(*object_id, oid);
5529                    }
5530                    _ => {}
5531                }
5532                iter.advance().await.unwrap();
5533            }
5534        };
5535
5536        assert_not_found(bar_object_id).await;
5537
5538        // Now check unlink.
5539        root.unlink("bar", &Default::default())
5540            .await
5541            .expect("FIDL call failed")
5542            .expect("unlink failed");
5543
5544        assert_not_found(foo_object_id).await;
5545
5546        fixture.close().await;
5547    }
5548
5549    #[fuchsia::test]
5550    async fn test_failed_create_unnamed_file_transaction() {
5551        let fail = AtomicU64::new(0);
5552        let (mut hooks, fs_hooks) = fxfs::hooks::Hooks::new();
5553        hooks.set_pre_commit(|_| {
5554            if fail.load(Ordering::Relaxed) > 0 {
5555                fail.fetch_sub(1, Ordering::Relaxed);
5556                bail!("Aborted transaction");
5557            }
5558            Ok(())
5559        });
5560        let fixture = TestFixture::open(
5561            DeviceHolder::new(FakeDevice::new(16384, 512)),
5562            TestFixtureOptions { hooks: Some(fs_hooks), ..Default::default() },
5563        )
5564        .await;
5565        let root = fixture.root();
5566
5567        fail.fetch_add(1, Ordering::Relaxed);
5568
5569        let _dir = open_file(
5570            &root,
5571            ".",
5572            fio::Flags::FLAG_CREATE_AS_UNNAMED_TEMPORARY,
5573            &fio::Options::default(),
5574        )
5575        .await
5576        .expect_err("Create unexpectedly succeeded");
5577
5578        fixture.close().await;
5579    }
5580
5581    #[fuchsia::test]
5582    async fn test_update_access_time_on_deleted_directory() {
5583        let fixture = TestFixture::new().await;
5584        let root = fixture.root();
5585
5586        let dir = open_dir_checked(
5587            &root,
5588            "foo",
5589            fio::Flags::FLAG_MAYBE_CREATE
5590                | fio::PERM_READABLE
5591                | fio::PERM_WRITABLE
5592                | fio::Flags::PROTOCOL_DIRECTORY,
5593            fio::Options::default(),
5594        )
5595        .await;
5596
5597        root.unlink("foo", &fio::UnlinkOptions::default())
5598            .await
5599            .expect("FIDL call failed")
5600            .expect("unlink failed");
5601
5602        // Requesting PENDING_ACCESS_TIME_UPDATE should not fail even if the directory is deleted.
5603        dir.get_attributes(fio::NodeAttributesQuery::PENDING_ACCESS_TIME_UPDATE)
5604            .await
5605            .expect("FIDL call failed")
5606            .expect("get_attributes failed");
5607
5608        fixture.close().await;
5609    }
5610
5611    async fn enable_casefold(dir: &fio::DirectoryProxy) {
5612        dir.update_attributes(&fio::MutableNodeAttributes {
5613            casefold: Some(true),
5614            ..Default::default()
5615        })
5616        .await
5617        .expect("update_attributes FIDL call failed")
5618        .map_err(zx::ok)
5619        .expect("update_attributes failed");
5620    }
5621
5622    #[fuchsia::test]
5623    async fn test_casefold_cache_lookup_no_duplicates() {
5624        let fixture = TestFixture::new().await;
5625        let root = fixture.root();
5626
5627        let dir = open_dir_checked(
5628            &root,
5629            "dir",
5630            fio::Flags::FLAG_MAYBE_CREATE
5631                | fio::PERM_READABLE
5632                | fio::PERM_WRITABLE
5633                | fio::Flags::PROTOCOL_DIRECTORY,
5634            Default::default(),
5635        )
5636        .await;
5637        enable_casefold(&dir).await;
5638
5639        let file = open_file_checked(
5640            &dir,
5641            "foo",
5642            fio::Flags::FLAG_MAYBE_CREATE
5643                | fio::PERM_READABLE
5644                | fio::PERM_WRITABLE
5645                | fio::Flags::PROTOCOL_FILE,
5646            &Default::default(),
5647        )
5648        .await;
5649        close_file_checked(file).await;
5650
5651        let cache = fixture.volume().volume().dirent_cache();
5652        cache.clear();
5653
5654        let file = open_file_checked(
5655            &dir,
5656            "foo",
5657            fio::PERM_READABLE | fio::Flags::PROTOCOL_FILE,
5658            &Default::default(),
5659        )
5660        .await;
5661        close_file_checked(file).await;
5662
5663        let len_after_first_lookup = cache.len();
5664        assert!(len_after_first_lookup >= 1, "Cache should contain at least the looked up file");
5665
5666        let file = open_file_checked(
5667            &dir,
5668            "FOO",
5669            fio::PERM_READABLE | fio::Flags::PROTOCOL_FILE,
5670            &Default::default(),
5671        )
5672        .await;
5673        close_file_checked(file).await;
5674
5675        assert_eq!(
5676            cache.len(),
5677            len_after_first_lookup,
5678            "Cache size increased (duplicate entries found)"
5679        );
5680
5681        let file = open_file_checked(
5682            &dir,
5683            "Foo",
5684            fio::PERM_READABLE | fio::Flags::PROTOCOL_FILE,
5685            &Default::default(),
5686        )
5687        .await;
5688        close_file_checked(file).await;
5689
5690        assert_eq!(
5691            cache.len(),
5692            len_after_first_lookup,
5693            "Cache size increased (duplicate entries found)"
5694        );
5695
5696        fixture.close().await;
5697    }
5698
5699    #[fuchsia::test]
5700    async fn test_casefold_cache_rename_invalidation() {
5701        let fixture = TestFixture::new().await;
5702        let root = fixture.root();
5703
5704        let dir = open_dir_checked(
5705            &root,
5706            "dir",
5707            fio::Flags::FLAG_MAYBE_CREATE
5708                | fio::PERM_READABLE
5709                | fio::PERM_WRITABLE
5710                | fio::Flags::PROTOCOL_DIRECTORY,
5711            Default::default(),
5712        )
5713        .await;
5714        enable_casefold(&dir).await;
5715
5716        let file = open_file_checked(
5717            &dir,
5718            "foo",
5719            fio::Flags::FLAG_MAYBE_CREATE
5720                | fio::PERM_READABLE
5721                | fio::PERM_WRITABLE
5722                | fio::Flags::PROTOCOL_FILE,
5723            &Default::default(),
5724        )
5725        .await;
5726        close_file_checked(file).await;
5727
5728        let file = open_file_checked(
5729            &dir,
5730            "foo",
5731            fio::PERM_READABLE | fio::Flags::PROTOCOL_FILE,
5732            &Default::default(),
5733        )
5734        .await;
5735        close_file_checked(file).await;
5736
5737        let dst_token = dir.get_token().await.expect("FIDL call failed").expect("get_token failed");
5738        dir.rename("FOO", dst_token, "bar")
5739            .await
5740            .expect("Rename FIDL call failed")
5741            .expect("rename failed");
5742
5743        assert_matches!(
5744            open_file(
5745                &dir,
5746                "foo",
5747                fio::PERM_READABLE | fio::Flags::PROTOCOL_FILE,
5748                &Default::default()
5749            )
5750            .await
5751            .expect_err("Open \"foo\" succeeded after rename")
5752            .root_cause()
5753            .downcast_ref::<zx::Status>(),
5754            Some(&zx::Status::NOT_FOUND)
5755        );
5756
5757        let file = open_file_checked(
5758            &dir,
5759            "bar",
5760            fio::PERM_READABLE | fio::Flags::PROTOCOL_FILE,
5761            &Default::default(),
5762        )
5763        .await;
5764        close_file_checked(file).await;
5765
5766        fixture.close().await;
5767    }
5768
5769    #[fuchsia::test]
5770    async fn test_casefold_cache_unlink_invalidation() {
5771        let fixture = TestFixture::new().await;
5772        let root = fixture.root();
5773
5774        let dir = open_dir_checked(
5775            &root,
5776            "dir",
5777            fio::Flags::FLAG_MAYBE_CREATE
5778                | fio::PERM_READABLE
5779                | fio::PERM_WRITABLE
5780                | fio::Flags::PROTOCOL_DIRECTORY,
5781            Default::default(),
5782        )
5783        .await;
5784        enable_casefold(&dir).await;
5785
5786        let file = open_file_checked(
5787            &dir,
5788            "foo",
5789            fio::Flags::FLAG_MAYBE_CREATE
5790                | fio::PERM_READABLE
5791                | fio::PERM_WRITABLE
5792                | fio::Flags::PROTOCOL_FILE,
5793            &Default::default(),
5794        )
5795        .await;
5796        close_file_checked(file).await;
5797
5798        let file = open_file_checked(
5799            &dir,
5800            "foo",
5801            fio::PERM_READABLE | fio::Flags::PROTOCOL_FILE,
5802            &Default::default(),
5803        )
5804        .await;
5805        close_file_checked(file).await;
5806
5807        dir.unlink("FOO", &fio::UnlinkOptions::default())
5808            .await
5809            .expect("Unlink FIDL call failed")
5810            .expect("Unlink failed");
5811
5812        assert_matches!(
5813            open_file(
5814                &dir,
5815                "foo",
5816                fio::PERM_READABLE | fio::Flags::PROTOCOL_FILE,
5817                &Default::default()
5818            )
5819            .await
5820            .expect_err("Open \"foo\" succeeded after unlink")
5821            .root_cause()
5822            .downcast_ref::<zx::Status>(),
5823            Some(&zx::Status::NOT_FOUND)
5824        );
5825
5826        fixture.close().await;
5827    }
5828
5829    async fn assert_watch_event(
5830        watcher: &mut Watcher,
5831        expected_event: WatchEvent,
5832        expected_name: &str,
5833    ) {
5834        assert_eq!(
5835            watcher.next().await.unwrap().unwrap(),
5836            WatchMessage { event: expected_event, filename: expected_name.into() }
5837        );
5838    }
5839
5840    async fn set_up_casefold_dir_with_files(
5841        fixture: &TestFixture,
5842        dir_name: &str,
5843        files: &[&str],
5844    ) -> (fio::DirectoryProxy, Watcher) {
5845        let root = fixture.root();
5846        let dir = open_dir_checked(
5847            root,
5848            dir_name,
5849            fio::Flags::FLAG_MAYBE_CREATE
5850                | fio::PERM_READABLE
5851                | fio::PERM_WRITABLE
5852                | fio::Flags::PROTOCOL_DIRECTORY,
5853            Default::default(),
5854        )
5855        .await;
5856        enable_casefold(&dir).await;
5857
5858        for &file_name in files {
5859            let file = open_file_checked(
5860                &dir,
5861                file_name,
5862                fio::Flags::FLAG_MAYBE_CREATE
5863                    | fio::PERM_READABLE
5864                    | fio::PERM_WRITABLE
5865                    | fio::Flags::PROTOCOL_FILE,
5866                &Default::default(),
5867            )
5868            .await;
5869            close_file_checked(file).await;
5870        }
5871
5872        let mut watcher = Watcher::new(&dir).await.unwrap();
5873        assert_watch_event(&mut watcher, WatchEvent::EXISTING, ".").await;
5874
5875        let mut existing_files = files
5876            .iter()
5877            .map(|s| std::path::PathBuf::from(*s))
5878            .collect::<std::collections::HashSet<_>>();
5879        while !existing_files.is_empty() {
5880            let msg = watcher.next().await.unwrap().unwrap();
5881            assert_eq!(msg.event, WatchEvent::EXISTING);
5882            assert!(
5883                existing_files.remove(&msg.filename),
5884                "Unexpected existing file: {:?}",
5885                msg.filename
5886            );
5887        }
5888
5889        assert_watch_event(&mut watcher, WatchEvent::IDLE, "").await;
5890
5891        (dir, watcher)
5892    }
5893
5894    #[fuchsia::test]
5895    async fn test_casefold_rename_watcher_events() {
5896        let fixture = TestFixture::new().await;
5897        let (dir, mut watcher) = set_up_casefold_dir_with_files(&fixture, "dir", &["foo"]).await;
5898
5899        let dst_token = dir.get_token().await.expect("FIDL call failed").expect("get_token failed");
5900        dir.rename("FOO", dst_token, "BAR")
5901            .await
5902            .expect("Rename FIDL call failed")
5903            .expect("rename failed");
5904
5905        assert_watch_event(&mut watcher, WatchEvent::REMOVE_FILE, "foo").await;
5906        assert_watch_event(&mut watcher, WatchEvent::ADD_FILE, "BAR").await;
5907
5908        fixture.close().await;
5909    }
5910
5911    #[fuchsia::test]
5912    async fn test_casefold_rename_overwrite_watcher_events() {
5913        let fixture = TestFixture::new().await;
5914        let (dir, mut watcher) =
5915            set_up_casefold_dir_with_files(&fixture, "dir", &["foo", "bar"]).await;
5916
5917        let dst_token = dir.get_token().await.expect("FIDL call failed").expect("get_token failed");
5918        dir.rename("FOO", dst_token, "BAR")
5919            .await
5920            .expect("Rename FIDL call failed")
5921            .expect("rename failed");
5922
5923        assert_watch_event(&mut watcher, WatchEvent::REMOVE_FILE, "foo").await;
5924        assert_watch_event(&mut watcher, WatchEvent::REMOVE_FILE, "bar").await;
5925        assert_watch_event(&mut watcher, WatchEvent::ADD_FILE, "BAR").await;
5926
5927        fixture.close().await;
5928    }
5929
5930    #[fuchsia::test]
5931    async fn test_casefold_rename_cross_dir_overwrite_watcher_events() {
5932        let fixture = TestFixture::new().await;
5933        let (src_dir, mut src_watcher) =
5934            set_up_casefold_dir_with_files(&fixture, "src_dir", &["foo"]).await;
5935        let (dst_dir, mut dst_watcher) =
5936            set_up_casefold_dir_with_files(&fixture, "dst_dir", &["bar"]).await;
5937
5938        let dst_token =
5939            dst_dir.get_token().await.expect("FIDL call failed").expect("get_token failed");
5940        src_dir
5941            .rename("FOO", dst_token, "BAR")
5942            .await
5943            .expect("Rename FIDL call failed")
5944            .expect("rename failed");
5945
5946        assert_watch_event(&mut src_watcher, WatchEvent::REMOVE_FILE, "foo").await;
5947
5948        assert_watch_event(&mut dst_watcher, WatchEvent::REMOVE_FILE, "bar").await;
5949        assert_watch_event(&mut dst_watcher, WatchEvent::ADD_FILE, "BAR").await;
5950
5951        fixture.close().await;
5952    }
5953
5954    #[fuchsia::test]
5955    async fn test_casefold_unlink_watcher_events() {
5956        let fixture = TestFixture::new().await;
5957        let (dir, mut watcher) = set_up_casefold_dir_with_files(&fixture, "dir", &["foo"]).await;
5958
5959        dir.unlink("FOO", &fio::UnlinkOptions::default())
5960            .await
5961            .expect("Unlink FIDL call failed")
5962            .expect("Unlink failed");
5963
5964        assert_watch_event(&mut watcher, WatchEvent::REMOVE_FILE, "foo").await;
5965
5966        fixture.close().await;
5967    }
5968
5969    #[fuchsia::test]
5970    async fn test_casefold_rename_case_only_watcher_events() {
5971        let fixture = TestFixture::new().await;
5972        let (dir, mut watcher) = set_up_casefold_dir_with_files(&fixture, "dir", &["Foo"]).await;
5973
5974        let dst_token = dir.get_token().await.expect("FIDL call failed").expect("get_token failed");
5975        dir.rename("Foo", dst_token, "FOO")
5976            .await
5977            .expect("Rename FIDL call failed")
5978            .expect("rename failed");
5979
5980        assert_watch_event(&mut watcher, WatchEvent::REMOVE_FILE, "Foo").await;
5981        assert_watch_event(&mut watcher, WatchEvent::ADD_FILE, "FOO").await;
5982
5983        fixture.close().await;
5984    }
5985
5986    #[fuchsia::test]
5987    async fn test_unlink_purges_in_single_transaction_when_closed() {
5988        let fixture = TestFixture::new().await;
5989        let root = fixture.root();
5990        let volume = fixture.volume().volume();
5991        let store = volume.store();
5992        let root_dir = fixture
5993            .volume()
5994            .root()
5995            .clone()
5996            .into_any()
5997            .downcast::<FxDirectory>()
5998            .expect("Not a directory");
5999
6000        // 1. Verify that calling `remove_from_dirent_cache` on a closed file drops the last
6001        // strong reference and synchronously removes it from `NodeCache`.
6002        let file = open_file_checked(
6003            root,
6004            "cached_file",
6005            fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_READABLE | fio::PERM_WRITABLE,
6006            &fio::Options::default(),
6007        )
6008        .await;
6009        let (_mutable, immutable) = file
6010            .get_attributes(fio::NodeAttributesQuery::ID)
6011            .await
6012            .expect("transport error on get_attributes")
6013            .expect("get_attributes failed");
6014        let cached_id = immutable.id.unwrap();
6015        file::write(&file, b"hello").await.expect("write failed");
6016        close_file_checked(file).await;
6017
6018        // While the file is closed, `dirent_cache` still holds a strong `Arc<dyn FxNode>`, so
6019        // `NodeCache` contains the entry.
6020        assert!(volume.cache().contains_key(cached_id));
6021        root_dir.remove_from_dirent_cache("cached_file");
6022        assert!(!volume.cache().contains_key(cached_id));
6023
6024        // 2. Create another file, write to it, and close it without manually removing from
6025        // `dirent_cache`. Verify that `unlink` evicts it from `dirent_cache`, sees that it is no
6026        // longer in `NodeCache`, and purges it in a single transaction (never touching the
6027        // graveyard).
6028        let closed_file = open_file_checked(
6029            root,
6030            "closed_file",
6031            fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_READABLE | fio::PERM_WRITABLE,
6032            &fio::Options::default(),
6033        )
6034        .await;
6035        let (_mutable, immutable) = closed_file
6036            .get_attributes(fio::NodeAttributesQuery::ID)
6037            .await
6038            .expect("transport error on get_attributes")
6039            .expect("get_attributes failed");
6040        let closed_id = immutable.id.unwrap();
6041        file::write(&closed_file, b"hello").await.expect("write failed");
6042        close_file_checked(closed_file).await;
6043
6044        assert!(volume.cache().contains_key(closed_id));
6045        root.unlink("closed_file", &fio::UnlinkOptions::default())
6046            .await
6047            .expect("FIDL call failed")
6048            .expect("unlink failed");
6049        assert!(!volume.cache().contains_key(closed_id));
6050        // Because it was purged in a single transaction, no graveyard entry was ever inserted.
6051        assert!(
6052            store
6053                .tree()
6054                .find(&ObjectKey::graveyard_entry(store.graveyard_directory_object_id(), closed_id))
6055                .await
6056                .expect("find failed")
6057                .is_none()
6058        );
6059
6060        // 3. Contrast with unlinking an open file: since an open connection holds a strong
6061        // reference, `contains_key` remains true after `remove_from_dirent_cache`, so `unlink`
6062        // falls back to adding the file to the graveyard.
6063        let open_file = open_file_checked(
6064            root,
6065            "open_file",
6066            fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_READABLE | fio::PERM_WRITABLE,
6067            &fio::Options::default(),
6068        )
6069        .await;
6070        let (_mutable, immutable) = open_file
6071            .get_attributes(fio::NodeAttributesQuery::ID)
6072            .await
6073            .expect("transport error on get_attributes")
6074            .expect("get_attributes failed");
6075        let open_id = immutable.id.unwrap();
6076        file::write(&open_file, b"hello").await.expect("write failed");
6077
6078        root.unlink("open_file", &fio::UnlinkOptions::default())
6079            .await
6080            .expect("FIDL call failed")
6081            .expect("unlink failed");
6082        assert!(volume.cache().contains_key(open_id));
6083        assert!(
6084            store
6085                .tree()
6086                .find(&ObjectKey::graveyard_entry(store.graveyard_directory_object_id(), open_id))
6087                .await
6088                .expect("find failed")
6089                .is_some()
6090        );
6091        close_file_checked(open_file).await;
6092
6093        drop(root_dir);
6094        fixture.close().await;
6095    }
6096
6097    #[fuchsia::test]
6098    async fn test_symlink_removed_from_node_cache_on_drop() {
6099        let fixture = TestFixture::new().await;
6100        let root = fixture.root();
6101        let volume = fixture.volume().volume();
6102
6103        let (proxy, server_end) = create_proxy::<fio::SymlinkMarker>();
6104        root.create_symlink("test_symlink", b"target", Some(server_end))
6105            .await
6106            .expect("FIDL call failed")
6107            .expect("create_symlink failed");
6108
6109        let (_mutable, immutable) = proxy
6110            .get_attributes(fio::NodeAttributesQuery::ID)
6111            .await
6112            .expect("transport error on get_attributes")
6113            .expect("get_attributes failed");
6114        let symlink_id = immutable.id.unwrap();
6115
6116        // While the symlink connection is open, the node cache contains the entry.
6117        assert!(volume.cache().contains_key(symlink_id));
6118
6119        proxy
6120            .close()
6121            .await
6122            .expect("FIDL call failed")
6123            .map_err(zx::Status::err_from_raw)
6124            .expect("close failed");
6125
6126        // When the connection is closed, the strong reference is dropped, which invokes
6127        // FxSymlink's Drop implementation and removes it from the node cache.
6128        assert!(!volume.cache().contains_key(symlink_id));
6129
6130        fixture.close().await;
6131    }
6132
6133    #[fuchsia::test]
6134    async fn test_sync_flushes_file_data() {
6135        let fixture = TestFixture::new().await;
6136        let root = fixture.root();
6137        let volume = fixture.volume().volume();
6138
6139        let file = open_file_checked(
6140            root,
6141            "test_file",
6142            fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_READABLE | fio::PERM_WRITABLE,
6143            &fio::Options::default(),
6144        )
6145        .await;
6146
6147        let (_mutable, immutable) = file
6148            .get_attributes(fio::NodeAttributesQuery::ID)
6149            .await
6150            .expect("transport error on get_attributes")
6151            .expect("get_attributes failed");
6152        let file_id = immutable.id.unwrap();
6153
6154        let node = volume
6155            .cache()
6156            .get(file_id)
6157            .expect("Node should be cached")
6158            .into_any()
6159            .downcast::<FxFile>()
6160            .unwrap();
6161
6162        file::write(&file, b"hello world").await.expect("write failed");
6163
6164        assert!(node.handle().needs_flush());
6165
6166        root.sync()
6167            .await
6168            .expect("FIDL call failed")
6169            .map_err(zx::Status::err_from_raw)
6170            .expect("sync failed");
6171
6172        assert!(!node.handle().needs_flush());
6173
6174        close_file_checked(file).await;
6175        drop(node);
6176        fixture.close().await;
6177    }
6178
6179    #[fuchsia::test]
6180    async fn test_large_symlink_attributes_and_target() {
6181        let fixture = TestFixture::new().await;
6182        let root = fixture.root();
6183        let block_size = fixture.fs().block_size();
6184
6185        assert_eq!(
6186            root.create_symlink("too_big", &vec![b'a'; 4094], None)
6187                .await
6188                .expect("FIDL call failed")
6189                .expect_err("too big symlink target should fail"),
6190            zx::Status::BAD_PATH.into_raw(),
6191        );
6192
6193        let inline_target = vec![b'i'; 256];
6194        let spilled_target = vec![b's'; 4093];
6195
6196        let crypt: Arc<CryptBase> = fixture.crypt().unwrap();
6197        crypt
6198            .add_wrapping_key(WRAPPING_KEY_ID, [1; 32].into())
6199            .expect("Failed to add wrapping key");
6200        let fscrypt_dir = open_dir_checked(
6201            &root,
6202            "fscrypt_dir",
6203            fio::Flags::FLAG_MAYBE_CREATE
6204                | fio::PERM_READABLE
6205                | fio::PERM_WRITABLE
6206                | fio::Flags::PROTOCOL_DIRECTORY,
6207            Default::default(),
6208        )
6209        .await;
6210        fscrypt_dir
6211            .update_attributes(&fio::MutableNodeAttributes {
6212                encryption_policy: Some(fio::FscryptPolicy {
6213                    key_identifier: WRAPPING_KEY_ID,
6214                    flags: fxfs::object_store::LEGACY_FSCRYPT_FLAGS,
6215                }),
6216                ..Default::default()
6217            })
6218            .await
6219            .expect("FIDL call failed")
6220            .map_err(zx::ok)
6221            .expect("update_attributes failed");
6222
6223        for dir in [&root, &fscrypt_dir] {
6224            let (inline_proxy, inline_server) = create_proxy::<fio::SymlinkMarker>();
6225            dir.create_symlink("inline_symlink", &inline_target, Some(inline_server))
6226                .await
6227                .expect("FIDL call failed")
6228                .expect("create_symlink failed");
6229
6230            let (spilled_proxy, spilled_server) = create_proxy::<fio::SymlinkMarker>();
6231            dir.create_symlink("spilled_symlink", &spilled_target, Some(spilled_server))
6232                .await
6233                .expect("FIDL call failed")
6234                .expect("create_symlink failed");
6235
6236            let inline_info = inline_proxy.describe().await.expect("describe failed");
6237            assert_eq!(inline_info.target.as_deref(), Some(inline_target.as_slice()));
6238            let (_, inline_immutable) = inline_proxy
6239                .get_attributes(
6240                    fio::NodeAttributesQuery::CONTENT_SIZE | fio::NodeAttributesQuery::STORAGE_SIZE,
6241                )
6242                .await
6243                .expect("FIDL call failed")
6244                .expect("get_attributes failed");
6245            assert_eq!(inline_immutable.content_size, Some(256));
6246            assert_eq!(inline_immutable.storage_size, Some(0));
6247
6248            let spilled_info = spilled_proxy.describe().await.expect("describe failed");
6249            assert_eq!(spilled_info.target.as_deref(), Some(spilled_target.as_slice()));
6250            let (_, spilled_immutable) = spilled_proxy
6251                .get_attributes(
6252                    fio::NodeAttributesQuery::CONTENT_SIZE | fio::NodeAttributesQuery::STORAGE_SIZE,
6253                )
6254                .await
6255                .expect("FIDL call failed")
6256                .expect("get_attributes failed");
6257            assert_eq!(spilled_immutable.content_size, Some(4093));
6258            assert_eq!(spilled_immutable.storage_size, Some(block_size.get()));
6259        }
6260
6261        fixture.close().await;
6262    }
6263}