Skip to main content

fxfs_platform/fuchsia/
file.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::directory::FxDirectory;
6use crate::fuchsia::errors::map_to_status;
7use crate::fuchsia::node::{FxNode, OpenedNode};
8use crate::fuchsia::paged_object_handle::{BACKGROUND_FLUSH_THRESHOLD, PagedObjectHandle};
9use crate::fuchsia::pager::{
10    MarkDirtyRange, PageInRange, PagerBacked, PagerPacketReceiverRegistration, default_page_in,
11};
12use crate::fuchsia::volume::{FxVolume, READ_AHEAD_SIZE};
13use anyhow::Error;
14use fidl_fuchsia_io as fio;
15use fxfs::filesystem::{MAX_FILE_SIZE, SyncOptions};
16use fxfs::future_with_guard::FutureWithGuard;
17use fxfs::log::*;
18use fxfs::object_handle::{ObjectHandle, ReadObjectHandle};
19use fxfs::object_store::data_object_handle::OverwriteOptions;
20use fxfs::object_store::object_record::EncryptionKey;
21use fxfs::object_store::transaction::{LockKey, Options, lock_keys};
22use fxfs::object_store::{DataObjectHandle, FSCRYPT_KEY_ID, ObjectDescriptor};
23use fxfs_crypto::WrappingKeyId;
24use fxfs_macros::ToWeakNode;
25use fxfs_trace::{TraceFutureExt, trace_future_args};
26use std::fmt::{Debug, Formatter};
27use std::ops::Range;
28use std::sync::Arc;
29use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
30use storage_device::buffer;
31use storage_units::BlockSize;
32use vfs::directory::entry::{EntryInfo, GetEntryInfo};
33use vfs::directory::entry_container::MutableDirectory;
34use vfs::execution_scope::ExecutionScope;
35use vfs::file::{File, FileOptions, GetVmo, StreamIoConnection, SyncMode};
36use vfs::name::Name;
37use vfs::{ObjectRequestRef, ProtocolsExt, attributes};
38use zx::Status;
39
40/// In many operating systems, it is possible to delete a file with open handles. In this case the
41/// file will continue to use space on disk but will not openable and the storage it uses will be
42/// freed when the last handle to the file is closed.
43/// To provide this behaviour, we use this constant to denote files that are marked for deletion.
44///
45/// When the top bit of the open count is set, it means the file has been deleted and when the count
46/// drops to zero, it will be tombstoned.  Once it has dropped to zero, it cannot be opened again
47/// (assertions will fire).
48const TO_BE_PURGED: u64 = 1 << (u64::BITS - 1);
49
50/// This is the second most significant bit of `open_count`. It set, it indicates that the file is
51/// an unnamed temporary file (i.e. it lives in the graveyard *temporarily* and can be moved out if
52/// it was linked into the filesystem permanently). An unnamed temporary file can be linked into a
53/// directory, which gives it a name and makes it permanent. Internally, linking a regular file and
54/// an unnamed temporary file is handled slightly differently because the latter resides in the
55/// graveyard. We need to be able to identify if a file is an unnamed temporary file whenever there
56/// is an attempt to link it into a directory. Once it has been linked into the filesystem, it is no
57/// longer temporary (it does not reside in the graveyard anymore) and this bit will be set to 0.
58const IS_TEMPORARILY_IN_GRAVEYARD: u64 = 1 << (u64::BITS - 2);
59
60/// The file is dirty and needs to be flushed.  When this bit is set, we hold a strong count to
61/// ensure the file cannot be dropped.
62const IS_DIRTY: u64 = 1 << (u64::BITS - 3);
63
64/// An unnamed temporary file lives in the graveyard and has to marked to be purged to make sure
65/// that the storage this file uses will be freed when the last handle to it closes.
66const IS_UNNAMED_TEMPORARY: u64 = IS_TEMPORARILY_IN_GRAVEYARD | TO_BE_PURGED;
67
68/// The maximum value of open counts. The two most significant bits are used to indicate other
69/// information regarding the state of the file. See the consts defined above.
70const MAX_OPEN_COUNTS: u64 = IS_DIRTY - 1;
71
72#[derive(Clone, Copy)]
73struct State(u64);
74
75impl Debug for State {
76    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
77        f.debug_struct("State")
78            .field("open_count", &self.open_count())
79            .field("to_be_purged", &self.to_be_purged())
80            .field("is_temporarily_in_graveyard", &self.is_temporarily_in_graveyard())
81            .field("is_dirty", &self.is_dirty())
82            .finish()
83    }
84}
85
86impl State {
87    fn open_count(&self) -> u64 {
88        self.0 & MAX_OPEN_COUNTS
89    }
90
91    fn to_be_purged(&self) -> bool {
92        self.0 & TO_BE_PURGED != 0
93    }
94
95    fn is_temporarily_in_graveyard(&self) -> bool {
96        self.0 & IS_TEMPORARILY_IN_GRAVEYARD != 0
97    }
98
99    fn is_unnamed_temporary(&self) -> bool {
100        self.0 & IS_UNNAMED_TEMPORARY == IS_UNNAMED_TEMPORARY
101    }
102
103    fn will_be_tombstoned(&self) -> bool {
104        self.to_be_purged() && self.open_count() == 0
105    }
106
107    fn is_dirty(&self) -> bool {
108        self.0 & IS_DIRTY != 0
109    }
110}
111
112/// The type of the flush to reflect the caller intent.
113#[derive(Clone, Copy, Debug, Default, PartialEq)]
114pub enum FlushType {
115    /// The default sync type. This flushes everything it can and if there are errors then it
116    /// stops early and will try to flush the rest later.
117    #[default]
118    Sync,
119
120    /// When the file is being closed and the flush needs to clean everything up. If there are
121    /// errors then it will still return all resources even if that means data loss.
122    LastChance,
123
124    /// Flushing some pages in the background because there are a lot of dirty pages to push. May
125    /// not get all dirty pages.
126    Background,
127}
128
129/// FxFile represents an open connection to a file.
130#[derive(ToWeakNode)]
131pub struct FxFile {
132    handle: PagedObjectHandle,
133    state: AtomicU64,
134    pager_packet_receiver_registration: PagerPacketReceiverRegistration<Self>,
135    background_flush_running: AtomicBool,
136}
137
138#[fxfs_trace::trace]
139impl FxFile {
140    /// Creates a new regular FxFile.
141    pub fn new(handle: DataObjectHandle<FxVolume>) -> Arc<Self> {
142        let size = handle.get_size();
143        Arc::new_cyclic(|weak| {
144            let (vmo, pager_packet_receiver_registration) = handle
145                .owner()
146                .pager()
147                .create_vmo(
148                    weak.clone(),
149                    size,
150                    zx::VmoOptions::UNBOUNDED | zx::VmoOptions::TRAP_DIRTY,
151                )
152                .unwrap();
153            vmo.set_name(&zx::Name::new("fxfs-file").unwrap()).unwrap();
154            Self {
155                handle: PagedObjectHandle::new(handle, vmo),
156                state: AtomicU64::new(0),
157                pager_packet_receiver_registration,
158                background_flush_running: AtomicBool::new(false),
159            }
160        })
161    }
162
163    /// Creates a new connection on the given `scope`. May take a read lock on the object.
164    pub async fn create_connection_async(
165        this: OpenedNode<FxFile>,
166        scope: ExecutionScope,
167        flags: impl ProtocolsExt,
168        object_request: ObjectRequestRef<'_>,
169    ) -> Result<(), zx::Status> {
170        {
171            let mut guard = this.pager().recorder();
172            if let Some(recorder) = &mut (*guard) {
173                let _ = recorder.record_open(this.clone() as Arc<dyn FxNode>);
174            }
175        }
176        if let Some(rights) = flags.rights() {
177            if rights.intersects(fio::Operations::READ_BYTES | fio::Operations::WRITE_BYTES) {
178                if let Some(fut) = this.handle.pre_fetch_keys() {
179                    // Keep the object from being deleted until after the fetch is complete.
180                    let fs = this.handle.owner().store().filesystem();
181                    let read_lock = fs
182                        .clone()
183                        .lock_manager()
184                        .read_lock(lock_keys!(LockKey::object(
185                            this.handle.owner().store().store_object_id(),
186                            this.object_id()
187                        )))
188                        .await
189                        .into_owned(fs);
190                    this.handle.owner().scope().spawn(
191                        FutureWithGuard::new(read_lock, fut)
192                            .trace(trace_future_args!("FxFile::pre_fetch_keys")),
193                    );
194                }
195            }
196        }
197        object_request
198            .create_connection::<StreamIoConnection<_>, _>(scope, this.take(), flags)
199            .await
200    }
201
202    /// Open the file as a temporary.  The file must have just been created with no other open
203    /// counts.
204    pub fn open_as_temporary(self: Arc<Self>) -> OpenedNode<dyn FxNode> {
205        assert_eq!(self.state.swap(1 | IS_UNNAMED_TEMPORARY, Ordering::Relaxed), 0);
206        OpenedNode(self)
207    }
208
209    /// Mark the state as permanent (to be used when the file is currently marked as temporary).
210    pub fn mark_as_permanent(&self) {
211        assert!(
212            State(self.state.fetch_and(!IS_UNNAMED_TEMPORARY, Ordering::Relaxed))
213                .is_unnamed_temporary()
214        );
215    }
216
217    pub fn is_verified_file(&self) -> bool {
218        self.handle.uncached_handle().is_verified_file()
219    }
220
221    pub fn handle(&self) -> &PagedObjectHandle {
222        &self.handle
223    }
224
225    /// If this instance has not been marked to be purged, returns an OpenedNode instance.
226    /// If marked for purging, returns None.
227    pub fn into_opened_node(self: Arc<Self>) -> Option<OpenedNode<FxFile>> {
228        self.increment_open_count().then(|| OpenedNode(self))
229    }
230
231    /// Persists any unflushed data to disk.
232    ///
233    /// Flush may be triggered as a background task so this requires an OpenedNode to
234    /// ensure that we don't accidentally try to flush a file handle that is in the process of
235    /// being removed. (See use of cache in `FxVolume::flush_all_files`.)
236    #[trace]
237    pub async fn flush(this: &OpenedNode<FxFile>, flush_type: FlushType) -> Result<(), Error> {
238        this.handle.flush(flush_type).await.map(|_| ())
239    }
240
241    pub fn get_block_size(&self) -> BlockSize {
242        self.handle.block_size()
243    }
244
245    pub async fn is_allocated(&self, start_offset: u64) -> Result<(bool, u64), Status> {
246        self.handle.uncached_handle().is_allocated(start_offset).await.map_err(map_to_status)
247    }
248
249    /// Writes `content` to the file at `offset` directly through the uncached handle, bypassing the
250    /// pager cache.
251    ///
252    /// Both `offset` and `content.len()` must be block-aligned (`self.get_block_size()`). If the
253    /// write extends beyond the current end of the file, new extents are allocated and the file
254    /// size is grown to `offset + content.len()`.
255    // TODO(https://fxbug.dev/42171261): Might be better to have a cached/uncached mode for file and
256    // call this when in uncached mode.
257    pub async fn write_at_uncached(&self, offset: u64, content: &[u8]) -> Result<u64, Status> {
258        let mut buf = self.handle.uncached_handle().allocate_buffer(content.len()).await;
259        buf.copy_from_slice(content);
260        let _ = self
261            .handle
262            .uncached_handle()
263            .overwrite(
264                offset,
265                buf.as_mut(),
266                OverwriteOptions { allow_allocations: true, ..Default::default() },
267            )
268            .await
269            .map_err(map_to_status)?;
270        Ok(content.len() as u64)
271    }
272
273    /// Reads `length` bytes from the file starting at `offset` directly from the uncached handle,
274    /// bypassing the pager cache.
275    ///
276    /// Both `offset` and `length` must be block-aligned (`self.get_block_size()`). Any portion of
277    /// the requested range beyond the end of the file (or in unallocated ranges) is zero-filled in
278    /// the returned buffer.
279    // TODO(https://fxbug.dev/42171261): Might be better to have a cached/uncached mode for file and
280    // call this when in uncached mode.
281    pub async fn read_at_uncached(
282        &self,
283        offset: u64,
284        length: usize,
285    ) -> Result<buffer::Buffer<'_>, Status> {
286        let block_size = self.get_block_size();
287        if !block_size.is_aligned(offset) || !block_size.is_aligned(length as u64) {
288            return Err(Status::INVALID_ARGS);
289        }
290        let end = offset.checked_add(length as u64).ok_or(Status::INVALID_ARGS)?;
291        self.handle.read_uncached(offset..end).await.map_err(map_to_status)
292    }
293
294    pub fn get_size_uncached(&self) -> u64 {
295        self.handle.uncached_handle().get_size()
296    }
297
298    async fn fscrypt_wrapping_key_id(&self) -> Result<Option<WrappingKeyId>, zx::Status> {
299        if self.handle.store().is_encrypted() {
300            if let Some(key) = self
301                .handle
302                .store()
303                .get_keys(self.object_id())
304                .await
305                .map_err(map_to_status)?
306                .get(FSCRYPT_KEY_ID)
307            {
308                match key {
309                    EncryptionKey::Fxfs(fxfs_key) => {
310                        return Ok(Some(fxfs_key.wrapping_key_id));
311                    }
312                    EncryptionKey::FscryptInoLblk32File { key_identifier } => {
313                        return Ok(Some(*key_identifier));
314                    }
315                    EncryptionKey::FscryptInoLblk32Dir { .. } => {
316                        error!("Unexpected key type for file: {:?}", key);
317                        return Ok(None);
318                    }
319                    EncryptionKey::LegacyFxfs(_) => unreachable!(),
320                }
321            }
322        }
323        Ok(None)
324    }
325
326    /// Forcibly marks the file as clean.
327    pub fn force_clean(&self) {
328        let old = State(self.state.fetch_and(!IS_DIRTY, Ordering::Relaxed));
329        if old.is_dirty() {
330            if self.handle.needs_flush() {
331                warn!("File {} was forcibly marked clean; data may be lost", self.object_id(),);
332                self.handle.forget_dirty_pages();
333            }
334            // SAFETY: The IS_DIRTY bit means we took a reference.
335            unsafe {
336                let _ = Arc::from_raw(self);
337            }
338        }
339    }
340
341    // Increments the open count by 1. Returns true if successful.
342    #[must_use]
343    fn increment_open_count(&self) -> bool {
344        let mut old = self.load_state();
345        loop {
346            if old.will_be_tombstoned() {
347                return false;
348            }
349
350            assert!(old.open_count() < MAX_OPEN_COUNTS);
351
352            match self.state.compare_exchange_weak(
353                old.0,
354                old.0 + 1,
355                Ordering::Relaxed,
356                Ordering::Relaxed,
357            ) {
358                Ok(_) => return true,
359                Err(new_value) => old.0 = new_value,
360            }
361        }
362    }
363
364    fn load_state(&self) -> State {
365        State(self.state.load(Ordering::Relaxed))
366    }
367
368    /// Updates the state.  Calls `callback` to map the current state into the desired new state.
369    /// This handles any bookkeeping and actions that are required by the change of state.
370    fn update_state(self: &Arc<Self>, callback: impl Fn(State) -> State) {
371        let mut old = self.load_state();
372        loop {
373            let mut new = callback(old);
374            if new.will_be_tombstoned() {
375                // There is no point flushing if the file is to be tombstoned, so we can clear the
376                // IS_DIRTY bit.
377                new.0 &= !IS_DIRTY;
378            }
379            match self.state.compare_exchange_weak(
380                old.0,
381                new.0,
382                Ordering::Relaxed,
383                Ordering::Relaxed,
384            ) {
385                Ok(_) => {
386                    if !old.is_dirty() && new.is_dirty() {
387                        // The `IS_DIRTY` bit being set means we hold an extra `Arc` reference so
388                        // that the node isn't removed from the node cache whilst it still needs
389                        // flushing.  A background task will periodically try and flush the file.
390                        // When it flushes the file, it takes an open count, and then when it drops
391                        // the open count, if the file was successfully flushed, the `IS_DIRTY` bit
392                        // is cleared and the extra reference is dropped (see below).
393                        let _ = Arc::into_raw(self.clone());
394                    } else if old.is_dirty() && !new.is_dirty() {
395                        // SAFETY: The IS_DIRTY bit means we took a reference just above.
396                        unsafe {
397                            let _ = Arc::from_raw(Arc::as_ptr(&self));
398                        }
399                    }
400                    if new.will_be_tombstoned() {
401                        // This node is marked `TO_BE_PURGED` and there are no more references to
402                        // it. This file will be tombstoned. Actual purging is queued to be done
403                        // asynchronously. We don't need to do any flushing in this case - if the
404                        // file is going to be deleted anyway, there is no point.
405                        self.handle.forget_dirty_pages();
406                        let store = self.handle.store();
407                        store
408                            .filesystem()
409                            .graveyard()
410                            .queue_tombstone_object(store.store_object_id(), self.object_id());
411                    }
412                    return;
413                }
414                Err(v) => old.0 = v,
415            }
416        }
417    }
418}
419
420impl Drop for FxFile {
421    fn drop(&mut self) {
422        let volume = self.handle.owner();
423        volume.cache().remove(self);
424    }
425}
426
427impl FxNode for FxFile {
428    fn object_id(&self) -> u64 {
429        self.handle.object_id()
430    }
431
432    fn parent(&self) -> Option<Arc<FxDirectory>> {
433        unreachable!(); // Add a parent back-reference if needed.
434    }
435
436    fn set_parent(&self, _parent: Arc<FxDirectory>) {
437        // NOP
438    }
439
440    fn open_count_add_one(&self) {
441        assert!(self.increment_open_count());
442    }
443
444    fn open_count_sub_one(self: Arc<Self>) {
445        self.update_state(|old| {
446            let mut new = State(old.0 - 1);
447
448            // If the file is dirty, we need to hold a strong reference to make sure the file
449            // doesn't go away until it has been flushed.
450            if new.open_count() == 0 && !new.to_be_purged() {
451                if self.handle.needs_flush() {
452                    new.0 |= IS_DIRTY;
453                } else {
454                    new.0 &= !IS_DIRTY;
455                }
456            }
457
458            new
459        });
460    }
461
462    fn object_descriptor(&self) -> ObjectDescriptor {
463        ObjectDescriptor::File
464    }
465
466    fn terminate(&self) {
467        self.pager_packet_receiver_registration.stop_watching_for_zero_children();
468    }
469
470    fn mark_to_be_purged(self: Arc<Self>) {
471        self.update_state(|old| State(old.0 | TO_BE_PURGED));
472    }
473}
474
475impl GetEntryInfo for FxFile {
476    fn entry_info(&self) -> EntryInfo {
477        EntryInfo::new(self.object_id(), fio::DirentType::File)
478    }
479}
480
481impl vfs::node::Node for FxFile {
482    async fn get_attributes(
483        &self,
484        requested_attributes: fio::NodeAttributesQuery,
485    ) -> Result<fio::NodeAttributes2, zx::Status> {
486        let needs_props = requested_attributes.intersects(
487            !(fio::NodeAttributesQuery::PROTOCOLS
488                | fio::NodeAttributesQuery::ABILITIES
489                | fio::NodeAttributesQuery::ID),
490        );
491        let mut props = if needs_props {
492            Some(self.handle.get_properties().await.map_err(map_to_status)?)
493        } else {
494            None
495        };
496
497        // In most cases, the reference count of objects can be used as the link count. There are
498        // two cases where this is not the case - for unnamed temporary files and unlink files with
499        // no more open references to it. For these two cases, the link count should be zero (the
500        // object reference count is one as they live in the graveyard). In both cases,
501        // `TO_BE_PURGED` will be set and `refs` is one.
502        let to_be_purged = self.load_state().to_be_purged();
503        let link_count =
504            props.as_ref().map(|p| if to_be_purged && p.refs == 1 { 0 } else { p.refs });
505
506        if requested_attributes.contains(fio::NodeAttributesQuery::PENDING_ACCESS_TIME_UPDATE) {
507            self.handle
508                .store()
509                .update_access_time(self.handle.object_id(), props.as_mut().unwrap(), || true)
510                .await
511                .map_err(map_to_status)?;
512        }
513
514        let (verification_options, root_hash) = if requested_attributes.intersects(
515            fio::NodeAttributesQuery::OPTIONS.union(fio::NodeAttributesQuery::ROOT_HASH),
516        ) {
517            self.handle.uncached_handle().get_descriptor().unzip()
518        } else {
519            (None, None)
520        };
521
522        let mut abilities = fio::Operations::GET_ATTRIBUTES
523            | fio::Operations::UPDATE_ATTRIBUTES
524            | fio::Operations::READ_BYTES;
525        if !self.is_verified_file() && !self.handle.is_read_only() {
526            abilities |= fio::Operations::WRITE_BYTES;
527        }
528
529        Ok(attributes!(
530            requested_attributes,
531            Mutable {
532                creation_time: props.as_ref().map(|p| p.creation_time.as_nanos()),
533                modification_time: props.as_ref().map(|p| p.modification_time.as_nanos()),
534                access_time: props.as_ref().map(|p| p.access_time.as_nanos()),
535                mode: props.as_ref().and_then(|p| p.posix_attributes.map(|a| a.mode)),
536                uid: props.as_ref().and_then(|p| p.posix_attributes.map(|a| a.uid)),
537                gid: props.as_ref().and_then(|p| p.posix_attributes.map(|a| a.gid)),
538                rdev: props.as_ref().and_then(|p| p.posix_attributes.map(|a| a.rdev)),
539                selinux_context: self
540                    .handle
541                    .uncached_handle()
542                    .get_inline_selinux_context()
543                    .await
544                    .map_err(map_to_status)?,
545                wrapping_key_id: self.fscrypt_wrapping_key_id().await?,
546            },
547            Immutable {
548                protocols: fio::NodeProtocolKinds::FILE,
549                abilities: abilities,
550                content_size: self.handle.get_size(),
551                storage_size: props.as_ref().map(|p| p.allocated_size),
552                link_count: link_count,
553                id: self.handle.object_id(),
554                change_time: props.as_ref().map(|p| p.change_time.as_nanos()),
555                options: verification_options,
556                root_hash: root_hash,
557                verity_enabled: self.is_verified_file(),
558            }
559        ))
560    }
561
562    fn will_clone(&self) {
563        self.open_count_add_one();
564    }
565
566    fn close(self: Arc<Self>) {
567        self.open_count_sub_one();
568    }
569
570    async fn link_into(
571        self: Arc<Self>,
572        destination_dir: Arc<dyn MutableDirectory>,
573        name: Name,
574    ) -> Result<(), zx::Status> {
575        let dir = destination_dir.into_any().downcast::<FxDirectory>().unwrap();
576        let store = self.handle.store();
577        let object_id = self.object_id();
578        let transaction = store
579            .new_transaction(
580                lock_keys![
581                    LockKey::object(store.store_object_id(), object_id),
582                    LockKey::object(store.store_object_id(), dir.object_id()),
583                ],
584                Options::default(),
585            )
586            .await
587            .map_err(map_to_status)?;
588
589        dir.check_fscrypt_policy_equivalence(self.fscrypt_wrapping_key_id().await?)?;
590
591        let state = self.load_state();
592        let is_unnamed_temporary = state.is_unnamed_temporary();
593        let to_be_purged = state.to_be_purged();
594        if is_unnamed_temporary {
595            // Remove object from graveyard and link it to `name`.
596            dir.link_graveyard_object(transaction, &name, object_id, ObjectDescriptor::File, || {
597                self.mark_as_permanent()
598            })
599            .await
600        } else {
601            // Check that we're not unlinked.
602            if to_be_purged {
603                return Err(zx::Status::NOT_FOUND);
604            }
605            dir.link_object(transaction, &name, object_id, ObjectDescriptor::File).await
606        }
607    }
608
609    fn query_filesystem(&self) -> Result<fio::FilesystemInfo, Status> {
610        Ok(self.handle.owner().filesystem_info_for_volume())
611    }
612
613    async fn list_extended_attributes(&self) -> Result<Vec<Vec<u8>>, Status> {
614        self.handle.store_handle().list_extended_attributes().await.map_err(map_to_status)
615    }
616
617    async fn get_extended_attribute(&self, name: Vec<u8>) -> Result<Vec<u8>, Status> {
618        self.handle.store_handle().get_extended_attribute(name).await.map_err(map_to_status)
619    }
620
621    async fn set_extended_attribute(
622        &self,
623        name: Vec<u8>,
624        value: Vec<u8>,
625        mode: fio::SetExtendedAttributeMode,
626    ) -> Result<(), Status> {
627        self.handle
628            .store_handle()
629            .set_extended_attribute(name, value, mode.into())
630            .await
631            .map_err(map_to_status)
632    }
633
634    async fn remove_extended_attribute(&self, name: Vec<u8>) -> Result<(), Status> {
635        self.handle.store_handle().remove_extended_attribute(name).await.map_err(map_to_status)
636    }
637}
638
639impl File for FxFile {
640    fn writable(&self) -> bool {
641        true
642    }
643
644    async fn open_file(&self, _options: &FileOptions) -> Result<(), Status> {
645        Ok(())
646    }
647
648    async fn truncate(&self, length: u64) -> Result<(), Status> {
649        self.handle.truncate(length).await.map_err(map_to_status)?;
650        Ok(())
651    }
652
653    async fn enable_verity(&self, options: fio::VerificationOptions) -> Result<(), Status> {
654        // `set_read_only()` MUST be called before `flush()`. `flush()` acquires and releases
655        // the truncate lock (`truncate_guard`), acting as a barrier to ensure that any
656        // subsequent `truncate()` call will observe `read_only == true` under `truncate_guard`
657        // and fail.
658        self.handle.set_read_only();
659        self.handle.flush(FlushType::Sync).await.map_err(map_to_status)?;
660        self.handle.uncached_handle().enable_verity(options).await.map_err(map_to_status)
661    }
662
663    // Returns a VMO handle that supports paging.
664    async fn get_backing_memory(&self, flags: fio::VmoFlags) -> Result<zx::Vmo, Status> {
665        // We do not support executable VMO handles.
666        if flags.contains(fio::VmoFlags::EXECUTE) {
667            error!("get_backing_memory does not support execute rights!");
668            return Err(Status::NOT_SUPPORTED);
669        }
670
671        // If a file is verity-enabled (or read-only), disallow requests for writable reference
672        // VMOs. Note that we have no way of revoking access to VMO handles that were already
673        // exposed prior to `enable_verity()` being called; we must ensure that any subsequent
674        // writes to such VMOs fail via pager `mark_dirty()` checks.
675        if flags.contains(fio::VmoFlags::WRITE)
676            && !flags.contains(fio::VmoFlags::PRIVATE_CLONE)
677            && (self.is_verified_file() || self.handle.is_read_only())
678        {
679            return Err(Status::ACCESS_DENIED);
680        }
681
682        let vmo = self.handle.vmo();
683        let mut rights = zx::Rights::BASIC | zx::Rights::MAP | zx::Rights::GET_PROPERTY;
684        if flags.contains(fio::VmoFlags::READ) {
685            rights |= zx::Rights::READ;
686        }
687        if flags.contains(fio::VmoFlags::WRITE) {
688            rights |= zx::Rights::WRITE;
689        }
690
691        let child_vmo = if flags.contains(fio::VmoFlags::PRIVATE_CLONE) {
692            // Allow for the VMO's content size and name to be changed even without ZX_RIGHT_WRITE.
693            rights |= zx::Rights::SET_PROPERTY;
694            let mut child_options = zx::VmoChildOptions::SNAPSHOT_AT_LEAST_ON_WRITE;
695            if flags.contains(fio::VmoFlags::WRITE) {
696                child_options |= zx::VmoChildOptions::RESIZABLE;
697                rights |= zx::Rights::RESIZE;
698            }
699            vmo.create_child(child_options, 0, vmo.get_stream_size()?)?
700        } else {
701            vmo.create_child(zx::VmoChildOptions::REFERENCE, 0, 0)?
702        };
703
704        let child_vmo = child_vmo.replace_handle(rights)?;
705        if self.handle.owner().pager().watch_for_zero_children(self).map_err(map_to_status)? {
706            // Take an open count so that we keep this object alive if it is unlinked.
707            self.open_count_add_one();
708        }
709        Ok(child_vmo)
710    }
711
712    async fn get_size(&self) -> Result<u64, Status> {
713        Ok(self.handle.get_size())
714    }
715
716    async fn update_attributes(
717        &self,
718        attributes: fio::MutableNodeAttributes,
719    ) -> Result<(), Status> {
720        if attributes == fio::MutableNodeAttributes::default() {
721            return Ok(());
722        }
723
724        self.handle.update_attributes(&attributes).await.map_err(map_to_status)?;
725        Ok(())
726    }
727
728    async fn allocate(
729        &self,
730        offset: u64,
731        length: u64,
732        _mode: fio::AllocateMode,
733    ) -> Result<(), Status> {
734        // NB: FILE_BIG is used so the error converts to EFBIG when passed through starnix, which
735        // is the required error code when the requested range is larger than the file size.
736        let range = offset..offset.checked_add(length).ok_or(Status::FILE_BIG)?;
737        self.handle.allocate(range).await.map_err(map_to_status)
738    }
739
740    async fn sync(&self, mode: SyncMode) -> Result<(), Status> {
741        self.handle.flush(FlushType::Sync).await.map_err(map_to_status)?;
742
743        // TODO(https://fxbug.dev/42178163): at the moment, this doesn't send a flush to the device, which
744        // doesn't match minfs.
745        if mode == SyncMode::Normal {
746            self.handle
747                .store()
748                .filesystem()
749                .sync(SyncOptions::default())
750                .await
751                .map_err(map_to_status)?;
752        }
753
754        Ok(())
755    }
756}
757
758#[fxfs_trace::trace]
759impl PagerBacked for FxFile {
760    fn try_keep_open(self: Arc<Self>) -> Result<OpenedNode<Self>, Arc<Self>> {
761        let mut old = self.load_state();
762        loop {
763            if old.open_count() == 0 {
764                return Err(self);
765            }
766
767            assert!(old.open_count() < MAX_OPEN_COUNTS);
768
769            match self.state.compare_exchange_weak(
770                old.0,
771                old.0 + 1,
772                Ordering::Relaxed,
773                Ordering::Relaxed,
774            ) {
775                Ok(_) => return Ok(OpenedNode(self)),
776                Err(new_value) => old.0 = new_value,
777            }
778        }
779    }
780
781    fn pager(&self) -> &crate::pager::Pager {
782        self.handle.owner().pager()
783    }
784
785    fn pager_packet_receiver_registration(&self) -> &PagerPacketReceiverRegistration<Self> {
786        &self.pager_packet_receiver_registration
787    }
788
789    fn vmo(&self) -> &zx::Vmo {
790        self.handle.vmo()
791    }
792
793    fn page_in(self: Arc<Self>, range: PageInRange<Self>) {
794        default_page_in(self, range, READ_AHEAD_SIZE);
795    }
796
797    #[trace]
798    fn mark_dirty(self: Arc<Self>, range: MarkDirtyRange<Self>) {
799        let (valid_pages, invalid_pages) = range.split(MAX_FILE_SIZE);
800        if let Some(invalid_pages) = invalid_pages {
801            invalid_pages.report_failure(zx::Status::FILE_BIG);
802        }
803        let range = match valid_pages {
804            Some(range) => range,
805            None => return,
806        };
807
808        let byte_count = range.len();
809        self.handle.owner().clone().report_pager_dirty(byte_count, move || {
810            match self.handle.mark_dirty(range) {
811                Ok(dirty_bytes) => {
812                    // If there's a whole batch worth to write. Just write it. Spurious failures
813                    // here are fine. This is best effort so keep it cheap.
814                    if dirty_bytes > BACKGROUND_FLUSH_THRESHOLD
815                        && !self.background_flush_running.swap(true, Ordering::Relaxed)
816                    {
817                        let owner = self.handle.owner().clone();
818                        owner.spawn(async move {
819                            // Ignore the result, the flush call already logs the errors.
820                            let _ = self.handle.flush(FlushType::Background).await;
821                            // If this future gets dropped before resetting this it means the
822                            // volume is shutting down anyways.
823                            self.background_flush_running.store(false, Ordering::Relaxed);
824                        });
825                    }
826                }
827                Err(_) => {
828                    // Undo the report of the dirty pages since mark_dirty failed.
829                    self.handle.owner().report_pager_clean(byte_count)
830                }
831            }
832        });
833    }
834
835    fn on_zero_children(self: Arc<Self>) {
836        // Drop the open count that we took in `get_backing_memory`.
837        self.open_count_sub_one();
838    }
839
840    fn byte_size(&self) -> u64 {
841        self.handle.uncached_size()
842    }
843
844    #[trace("len" => (range.end - range.start))]
845    async fn aligned_read(&self, range: Range<u64>) -> Result<buffer::Buffer<'_>, Error> {
846        let buffer = self.handle.read_uncached(range).await?;
847        Ok(buffer)
848    }
849}
850
851impl GetVmo for FxFile {
852    const PAGER_ON_FIDL_EXECUTOR: bool = true;
853
854    fn get_vmo(&self) -> &zx::Vmo {
855        self.vmo()
856    }
857}
858
859#[cfg(test)]
860mod tests {
861    use super::FxFile;
862    use crate::fuchsia::paged_object_handle::BACKGROUND_FLUSH_THRESHOLD;
863    use crate::fuchsia::testing::{
864        TestFixture, TestFixtureOptions, close_file_checked, open_dir_checked, open_file,
865        open_file_checked,
866    };
867    use anyhow::format_err;
868    use fidl_fuchsia_io as fio;
869    use fsverity_merkle::{FsVerityHasher, FsVerityHasherOptions};
870    use fuchsia_async::{self as fasync, unblock};
871    use fuchsia_fs::file;
872    use futures::join;
873    use fxfs::fsck::fsck;
874    use fxfs::object_handle::INVALID_OBJECT_ID;
875    use fxfs::object_store::Timestamp;
876    use fxfs_crypto::WrappingKeyId;
877    use rand::{RngExt as _, rng};
878    use std::sync::Arc;
879    use std::sync::atomic::{self, AtomicBool};
880    use std::time::Duration;
881    use storage_device::DeviceHolder;
882    use storage_device::fake_device::FakeDevice;
883    use storage_units::page_size;
884    use zx::Status;
885
886    const WRAPPING_KEY_ID: WrappingKeyId = u128::to_le_bytes(123);
887
888    #[fuchsia::test(threads = 10)]
889    async fn test_empty_file() {
890        let fixture = TestFixture::new().await;
891        let root = fixture.root();
892
893        let file = open_file_checked(
894            &root,
895            "foo",
896            fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_READABLE | fio::Flags::PROTOCOL_FILE,
897            &Default::default(),
898        )
899        .await;
900
901        let buf = file
902            .read(fio::MAX_BUF)
903            .await
904            .expect("FIDL call failed")
905            .map_err(Status::err_from_raw)
906            .expect("read failed");
907        assert!(buf.is_empty());
908
909        let (mutable_attrs, immutable_attrs) = file
910            .get_attributes(fio::NodeAttributesQuery::all())
911            .await
912            .expect("FIDL call failed")
913            .expect("GetAttributes failed");
914        assert_ne!(immutable_attrs.id.unwrap(), INVALID_OBJECT_ID);
915        assert_eq!(immutable_attrs.content_size.unwrap(), 0u64);
916        assert_eq!(immutable_attrs.storage_size.unwrap(), 0u64);
917        assert_eq!(immutable_attrs.link_count.unwrap(), 1u64);
918        assert_ne!(mutable_attrs.creation_time.unwrap(), 0u64);
919        assert_ne!(mutable_attrs.modification_time.unwrap(), 0u64);
920        assert_eq!(mutable_attrs.creation_time.unwrap(), mutable_attrs.modification_time.unwrap());
921
922        close_file_checked(file).await;
923        fixture.close().await;
924    }
925
926    #[fuchsia::test(threads = 10)]
927    async fn test_write_read() {
928        let fixture = TestFixture::new().await;
929        let root = fixture.root();
930
931        let file = open_file_checked(
932            &root,
933            "foo",
934            fio::Flags::FLAG_MAYBE_CREATE
935                | fio::PERM_READABLE
936                | fio::PERM_WRITABLE
937                | fio::Flags::PROTOCOL_FILE,
938            &Default::default(),
939        )
940        .await;
941
942        let inputs = vec!["hello, ", "world!"];
943        let expected_output = "hello, world!";
944        for input in inputs {
945            let bytes_written = file
946                .write(input.as_bytes())
947                .await
948                .expect("write failed")
949                .map_err(Status::err_from_raw)
950                .expect("File write was successful");
951            assert_eq!(bytes_written as usize, input.as_bytes().len());
952        }
953
954        let buf = file
955            .read_at(fio::MAX_BUF, 0)
956            .await
957            .expect("read_at failed")
958            .map_err(Status::err_from_raw)
959            .expect("File read was successful");
960        assert_eq!(buf.len(), expected_output.as_bytes().len());
961        assert!(buf.iter().eq(expected_output.as_bytes().iter()));
962
963        let (_, immutable_attributes) = file
964            .get_attributes(
965                fio::NodeAttributesQuery::CONTENT_SIZE | fio::NodeAttributesQuery::STORAGE_SIZE,
966            )
967            .await
968            .expect("FIDL call failed")
969            .expect("get_attributes failed");
970
971        assert_eq!(
972            immutable_attributes.content_size.unwrap(),
973            expected_output.as_bytes().len() as u64
974        );
975        assert_eq!(immutable_attributes.storage_size.unwrap(), fixture.fs().block_size());
976
977        let () = file
978            .sync()
979            .await
980            .expect("FIDL call failed")
981            .map_err(Status::err_from_raw)
982            .expect("sync failed");
983
984        let (_, immutable_attributes) = file
985            .get_attributes(
986                fio::NodeAttributesQuery::CONTENT_SIZE | fio::NodeAttributesQuery::STORAGE_SIZE,
987            )
988            .await
989            .expect("FIDL call failed")
990            .expect("get_attributes failed");
991
992        assert_eq!(
993            immutable_attributes.content_size.unwrap(),
994            expected_output.as_bytes().len() as u64
995        );
996        assert_eq!(immutable_attributes.storage_size.unwrap(), fixture.fs().block_size());
997
998        close_file_checked(file).await;
999        fixture.close().await;
1000    }
1001
1002    #[fuchsia::test(threads = 10)]
1003    async fn test_page_in() {
1004        let input = "hello, world!";
1005        let reused_device = {
1006            let fixture = TestFixture::new().await;
1007            let root = fixture.root();
1008
1009            let file = open_file_checked(
1010                &root,
1011                "foo",
1012                fio::Flags::FLAG_MAYBE_CREATE
1013                    | fio::PERM_READABLE
1014                    | fio::PERM_WRITABLE
1015                    | fio::Flags::PROTOCOL_FILE,
1016                &Default::default(),
1017            )
1018            .await;
1019
1020            let bytes_written = file
1021                .write(input.as_bytes())
1022                .await
1023                .expect("write failed")
1024                .map_err(Status::err_from_raw)
1025                .expect("File write was successful");
1026            assert_eq!(bytes_written as usize, input.as_bytes().len());
1027            assert!(file.sync().await.expect("Sync failed").is_ok());
1028
1029            close_file_checked(file).await;
1030            fixture.close().await
1031        };
1032
1033        let fixture = TestFixture::open(
1034            reused_device,
1035            TestFixtureOptions { format: false, ..Default::default() },
1036        )
1037        .await;
1038        let root = fixture.root();
1039
1040        let file = open_file_checked(
1041            &root,
1042            "foo",
1043            fio::PERM_READABLE | fio::Flags::PROTOCOL_FILE,
1044            &Default::default(),
1045        )
1046        .await;
1047
1048        let vmo =
1049            file.get_backing_memory(fio::VmoFlags::READ).await.expect("Fidl failure").unwrap();
1050        let mut readback = vec![0; input.as_bytes().len()];
1051        assert!(vmo.read(&mut readback, 0).is_ok());
1052        assert_eq!(input.as_bytes(), readback);
1053
1054        close_file_checked(file).await;
1055        fixture.close().await;
1056    }
1057
1058    #[fuchsia::test(threads = 10)]
1059    async fn test_page_in_io_error() {
1060        let mut device = FakeDevice::new(8192, 512);
1061        let succeed_requests = Arc::new(AtomicBool::new(true));
1062        let succeed_requests_clone = succeed_requests.clone();
1063        device.set_op_callback(Box::new(move |_| {
1064            if succeed_requests_clone.load(atomic::Ordering::Relaxed) {
1065                Ok(())
1066            } else {
1067                Err(format_err!("Fake error."))
1068            }
1069        }));
1070
1071        let input = "hello, world!";
1072        let reused_device = {
1073            let fixture = TestFixture::open(
1074                DeviceHolder::new(device),
1075                TestFixtureOptions { format: true, ..Default::default() },
1076            )
1077            .await;
1078            let root = fixture.root();
1079
1080            let file = open_file_checked(
1081                &root,
1082                "foo",
1083                fio::Flags::FLAG_MAYBE_CREATE
1084                    | fio::PERM_READABLE
1085                    | fio::PERM_WRITABLE
1086                    | fio::Flags::PROTOCOL_FILE,
1087                &Default::default(),
1088            )
1089            .await;
1090
1091            let bytes_written = file
1092                .write(input.as_bytes())
1093                .await
1094                .expect("write failed")
1095                .map_err(Status::err_from_raw)
1096                .expect("File write was successful");
1097            assert_eq!(bytes_written as usize, input.as_bytes().len());
1098
1099            close_file_checked(file).await;
1100            fixture.close().await
1101        };
1102
1103        let fixture = TestFixture::open(
1104            reused_device,
1105            TestFixtureOptions { format: false, ..Default::default() },
1106        )
1107        .await;
1108        let root = fixture.root();
1109
1110        let file = open_file_checked(
1111            &root,
1112            "foo",
1113            fio::PERM_READABLE | fio::Flags::PROTOCOL_FILE,
1114            &Default::default(),
1115        )
1116        .await;
1117
1118        let vmo =
1119            file.get_backing_memory(fio::VmoFlags::READ).await.expect("Fidl failure").unwrap();
1120        succeed_requests.store(false, atomic::Ordering::Relaxed);
1121        let mut readback = vec![0; input.as_bytes().len()];
1122        assert!(vmo.read(&mut readback, 0).is_err());
1123
1124        succeed_requests.store(true, atomic::Ordering::Relaxed);
1125        close_file_checked(file).await;
1126        fixture.close().await;
1127    }
1128
1129    #[fuchsia::test(threads = 10)]
1130    async fn test_writes_persist() {
1131        let mut device = DeviceHolder::new(FakeDevice::new(8192, 512));
1132        for i in 0..2 {
1133            let fixture = TestFixture::open(
1134                device,
1135                TestFixtureOptions { format: i == 0, ..Default::default() },
1136            )
1137            .await;
1138            let root = fixture.root();
1139
1140            let flags = if i == 0 {
1141                fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_READABLE | fio::PERM_WRITABLE
1142            } else {
1143                fio::PERM_READABLE | fio::PERM_WRITABLE
1144            };
1145            let file = open_file_checked(
1146                &root,
1147                "foo",
1148                flags | fio::Flags::PROTOCOL_FILE,
1149                &Default::default(),
1150            )
1151            .await;
1152
1153            if i == 0 {
1154                let _: u64 = file
1155                    .write(&vec![0xaa as u8; 8192])
1156                    .await
1157                    .expect("FIDL call failed")
1158                    .map_err(Status::err_from_raw)
1159                    .expect("File write was successful");
1160            } else {
1161                let buf = file
1162                    .read(8192)
1163                    .await
1164                    .expect("FIDL call failed")
1165                    .map_err(Status::err_from_raw)
1166                    .expect("File read was successful");
1167                assert_eq!(buf, vec![0xaa as u8; 8192]);
1168            }
1169
1170            let (_, immutable_attributes) = file
1171                .get_attributes(
1172                    fio::NodeAttributesQuery::CONTENT_SIZE | fio::NodeAttributesQuery::STORAGE_SIZE,
1173                )
1174                .await
1175                .expect("FIDL call failed")
1176                .expect("get_attributes failed");
1177
1178            assert_eq!(immutable_attributes.content_size.unwrap(), 8192u64);
1179            assert_eq!(immutable_attributes.storage_size.unwrap(), 8192u64);
1180
1181            close_file_checked(file).await;
1182            device = fixture.close().await;
1183        }
1184    }
1185
1186    #[fuchsia::test(threads = 10)]
1187    async fn test_append() {
1188        let fixture = TestFixture::new().await;
1189        let root = fixture.root();
1190
1191        let inputs = vec!["hello, ", "world!"];
1192        let expected_output = "hello, world!";
1193        for input in inputs {
1194            let file = open_file_checked(
1195                &root,
1196                "foo",
1197                fio::Flags::FLAG_MAYBE_CREATE
1198                    | fio::PERM_READABLE
1199                    | fio::PERM_WRITABLE
1200                    | fio::Flags::FILE_APPEND
1201                    | fio::Flags::PROTOCOL_FILE,
1202                &Default::default(),
1203            )
1204            .await;
1205
1206            let bytes_written = file
1207                .write(input.as_bytes())
1208                .await
1209                .expect("FIDL call failed")
1210                .map_err(Status::err_from_raw)
1211                .expect("File write was successful");
1212            assert_eq!(bytes_written as usize, input.as_bytes().len());
1213            close_file_checked(file).await;
1214        }
1215
1216        let file = open_file_checked(
1217            &root,
1218            "foo",
1219            fio::PERM_READABLE | fio::Flags::PROTOCOL_FILE,
1220            &Default::default(),
1221        )
1222        .await;
1223        let buf = file
1224            .read_at(fio::MAX_BUF, 0)
1225            .await
1226            .expect("FIDL call failed")
1227            .map_err(Status::err_from_raw)
1228            .expect("File read was successful");
1229        assert_eq!(buf.len(), expected_output.as_bytes().len());
1230        assert_eq!(&buf[..], expected_output.as_bytes());
1231
1232        let (_, immutable_attributes) = file
1233            .get_attributes(
1234                fio::NodeAttributesQuery::CONTENT_SIZE | fio::NodeAttributesQuery::STORAGE_SIZE,
1235            )
1236            .await
1237            .expect("FIDL call failed")
1238            .expect("get_attributes failed");
1239
1240        assert_eq!(
1241            immutable_attributes.content_size.unwrap(),
1242            expected_output.as_bytes().len() as u64
1243        );
1244        assert_eq!(immutable_attributes.storage_size.unwrap(), fixture.fs().block_size());
1245
1246        close_file_checked(file).await;
1247        fixture.close().await;
1248    }
1249
1250    #[fuchsia::test(threads = 10)]
1251    async fn test_seek() {
1252        let fixture = TestFixture::new().await;
1253        let root = fixture.root();
1254
1255        let file = open_file_checked(
1256            &root,
1257            "foo",
1258            fio::Flags::FLAG_MAYBE_CREATE
1259                | fio::PERM_READABLE
1260                | fio::PERM_WRITABLE
1261                | fio::Flags::PROTOCOL_FILE,
1262            &Default::default(),
1263        )
1264        .await;
1265
1266        let input = "hello, world!";
1267        let _: u64 = file
1268            .write(input.as_bytes())
1269            .await
1270            .expect("FIDL call failed")
1271            .map_err(Status::err_from_raw)
1272            .expect("File write was successful");
1273
1274        {
1275            let offset = file
1276                .seek(fio::SeekOrigin::Start, 0)
1277                .await
1278                .expect("FIDL call failed")
1279                .map_err(Status::err_from_raw)
1280                .expect("seek was successful");
1281            assert_eq!(offset, 0);
1282            let buf = file
1283                .read(5)
1284                .await
1285                .expect("FIDL call failed")
1286                .map_err(Status::err_from_raw)
1287                .expect("File read was successful");
1288            assert!(buf.iter().eq("hello".as_bytes().iter()));
1289        }
1290        {
1291            let offset = file
1292                .seek(fio::SeekOrigin::Current, 2)
1293                .await
1294                .expect("FIDL call failed")
1295                .map_err(Status::err_from_raw)
1296                .expect("seek was successful");
1297            assert_eq!(offset, 7);
1298            let buf = file
1299                .read(5)
1300                .await
1301                .expect("FIDL call failed")
1302                .map_err(Status::err_from_raw)
1303                .expect("File read was successful");
1304            assert!(buf.iter().eq("world".as_bytes().iter()));
1305        }
1306        {
1307            let offset = file
1308                .seek(fio::SeekOrigin::Current, -5)
1309                .await
1310                .expect("FIDL call failed")
1311                .map_err(Status::err_from_raw)
1312                .expect("seek was successful");
1313            assert_eq!(offset, 7);
1314            let buf = file
1315                .read(5)
1316                .await
1317                .expect("FIDL call failed")
1318                .map_err(Status::err_from_raw)
1319                .expect("File read was successful");
1320            assert!(buf.iter().eq("world".as_bytes().iter()));
1321        }
1322        {
1323            let offset = file
1324                .seek(fio::SeekOrigin::End, -1)
1325                .await
1326                .expect("FIDL call failed")
1327                .map_err(Status::err_from_raw)
1328                .expect("seek was successful");
1329            assert_eq!(offset, 12);
1330            let buf = file
1331                .read(1)
1332                .await
1333                .expect("FIDL call failed")
1334                .map_err(Status::err_from_raw)
1335                .expect("File read was successful");
1336            assert!(buf.iter().eq("!".as_bytes().iter()));
1337        }
1338
1339        close_file_checked(file).await;
1340        fixture.close().await;
1341    }
1342
1343    #[fuchsia::test(threads = 10)]
1344    async fn test_resize_extend() {
1345        let fixture = TestFixture::new().await;
1346        let root = fixture.root();
1347
1348        let file = open_file_checked(
1349            &root,
1350            "foo",
1351            fio::Flags::FLAG_MAYBE_CREATE
1352                | fio::PERM_READABLE
1353                | fio::PERM_WRITABLE
1354                | fio::Flags::PROTOCOL_FILE,
1355            &Default::default(),
1356        )
1357        .await;
1358
1359        let input = "hello, world!";
1360        let len: usize = 16 * 1024;
1361
1362        let _: u64 = file
1363            .write(input.as_bytes())
1364            .await
1365            .expect("FIDL call failed")
1366            .map_err(Status::err_from_raw)
1367            .expect("File write was successful");
1368
1369        let offset = file
1370            .seek(fio::SeekOrigin::Start, 0)
1371            .await
1372            .expect("FIDL call failed")
1373            .map_err(Status::err_from_raw)
1374            .expect("Seek was successful");
1375        assert_eq!(offset, 0);
1376
1377        let () = file
1378            .resize(len as u64)
1379            .await
1380            .expect("resize failed")
1381            .map_err(Status::err_from_raw)
1382            .expect("resize error");
1383
1384        let mut expected_buf = vec![0 as u8; len];
1385        expected_buf[..input.as_bytes().len()].copy_from_slice(input.as_bytes());
1386
1387        let buf = file::read(&file).await.expect("File read was successful");
1388        assert_eq!(buf.len(), len);
1389        assert_eq!(buf, expected_buf);
1390
1391        // Write something at the end of the gap.
1392        expected_buf[len - 1..].copy_from_slice("a".as_bytes());
1393
1394        let _: u64 = file
1395            .write_at("a".as_bytes(), (len - 1) as u64)
1396            .await
1397            .expect("FIDL call failed")
1398            .map_err(Status::err_from_raw)
1399            .expect("File write was successful");
1400
1401        let offset = file
1402            .seek(fio::SeekOrigin::Start, 0)
1403            .await
1404            .expect("FIDL call failed")
1405            .map_err(Status::err_from_raw)
1406            .expect("Seek was successful");
1407        assert_eq!(offset, 0);
1408
1409        let buf = file::read(&file).await.expect("File read was successful");
1410        assert_eq!(buf.len(), len);
1411        assert_eq!(buf, expected_buf);
1412
1413        close_file_checked(file).await;
1414        fixture.close().await;
1415    }
1416
1417    #[fuchsia::test(threads = 10)]
1418    async fn test_resize_shrink() {
1419        let fixture = TestFixture::new().await;
1420        let root = fixture.root();
1421
1422        let file = open_file_checked(
1423            &root,
1424            "foo",
1425            fio::Flags::FLAG_MAYBE_CREATE
1426                | fio::PERM_READABLE
1427                | fio::PERM_WRITABLE
1428                | fio::Flags::PROTOCOL_FILE,
1429            &Default::default(),
1430        )
1431        .await;
1432
1433        let len: usize = 2 * 1024;
1434        let input = {
1435            let mut v = vec![0 as u8; len];
1436            for i in 0..v.len() {
1437                v[i] = ('a' as u8) + (i % 13) as u8;
1438            }
1439            v
1440        };
1441        let short_len: usize = 513;
1442
1443        file::write(&file, &input).await.expect("File write was successful");
1444
1445        let () = file
1446            .resize(short_len as u64)
1447            .await
1448            .expect("resize failed")
1449            .map_err(Status::err_from_raw)
1450            .expect("resize error");
1451
1452        let offset = file
1453            .seek(fio::SeekOrigin::Start, 0)
1454            .await
1455            .expect("FIDL call failed")
1456            .map_err(Status::err_from_raw)
1457            .expect("Seek was successful");
1458        assert_eq!(offset, 0);
1459
1460        let buf = file::read(&file).await.expect("File read was successful");
1461        assert_eq!(buf.len(), short_len);
1462        assert_eq!(buf, input[..short_len]);
1463
1464        // Resize to the original length and verify the data's zeroed.
1465        let () = file
1466            .resize(len as u64)
1467            .await
1468            .expect("resize failed")
1469            .map_err(Status::err_from_raw)
1470            .expect("resize error");
1471
1472        let expected_buf = {
1473            let mut v = vec![0 as u8; len];
1474            v[..short_len].copy_from_slice(&input[..short_len]);
1475            v
1476        };
1477
1478        let offset = file
1479            .seek(fio::SeekOrigin::Start, 0)
1480            .await
1481            .expect("seek failed")
1482            .map_err(Status::err_from_raw)
1483            .expect("Seek was successful");
1484        assert_eq!(offset, 0);
1485
1486        let buf = file::read(&file).await.expect("File read was successful");
1487        assert_eq!(buf.len(), len);
1488        assert_eq!(buf, expected_buf);
1489
1490        close_file_checked(file).await;
1491        fixture.close().await;
1492    }
1493
1494    #[fuchsia::test(threads = 10)]
1495    async fn test_resize_shrink_repeated() {
1496        let fixture = TestFixture::new().await;
1497        let root = fixture.root();
1498
1499        let file = open_file_checked(
1500            &root,
1501            "foo",
1502            fio::Flags::FLAG_MAYBE_CREATE
1503                | fio::PERM_READABLE
1504                | fio::PERM_WRITABLE
1505                | fio::Flags::PROTOCOL_FILE,
1506            &Default::default(),
1507        )
1508        .await;
1509
1510        let orig_len: usize = 4 * 1024;
1511        let mut len = orig_len;
1512        let input = {
1513            let mut v = vec![0 as u8; len];
1514            for i in 0..v.len() {
1515                v[i] = ('a' as u8) + (i % 13) as u8;
1516            }
1517            v
1518        };
1519        let short_len: usize = 513;
1520
1521        file::write(&file, &input).await.expect("File write was successful");
1522
1523        while len > short_len {
1524            len -= std::cmp::min(len - short_len, 512);
1525            let () = file
1526                .resize(len as u64)
1527                .await
1528                .expect("resize failed")
1529                .map_err(Status::err_from_raw)
1530                .expect("resize error");
1531        }
1532
1533        let offset = file
1534            .seek(fio::SeekOrigin::Start, 0)
1535            .await
1536            .expect("Seek failed")
1537            .map_err(Status::err_from_raw)
1538            .expect("Seek was successful");
1539        assert_eq!(offset, 0);
1540
1541        let buf = file::read(&file).await.expect("File read was successful");
1542        assert_eq!(buf.len(), short_len);
1543        assert_eq!(buf, input[..short_len]);
1544
1545        // Resize to the original length and verify the data's zeroed.
1546        let () = file
1547            .resize(orig_len as u64)
1548            .await
1549            .expect("resize failed")
1550            .map_err(Status::err_from_raw)
1551            .expect("resize error");
1552
1553        let expected_buf = {
1554            let mut v = vec![0 as u8; orig_len];
1555            v[..short_len].copy_from_slice(&input[..short_len]);
1556            v
1557        };
1558
1559        let offset = file
1560            .seek(fio::SeekOrigin::Start, 0)
1561            .await
1562            .expect("seek failed")
1563            .map_err(Status::err_from_raw)
1564            .expect("Seek was successful");
1565        assert_eq!(offset, 0);
1566
1567        let buf = file::read(&file).await.expect("File read was successful");
1568        assert_eq!(buf.len(), orig_len);
1569        assert_eq!(buf, expected_buf);
1570
1571        close_file_checked(file).await;
1572        fixture.close().await;
1573    }
1574
1575    #[fuchsia::test(threads = 10)]
1576    async fn test_unlink_with_open_race() {
1577        let fixture = Arc::new(TestFixture::new().await);
1578        let fixture1 = fixture.clone();
1579        let fixture2 = fixture.clone();
1580        let fixture3 = fixture.clone();
1581        let done = Arc::new(AtomicBool::new(false));
1582        let done1 = done.clone();
1583        let done2 = done.clone();
1584        join!(
1585            fasync::Task::spawn(async move {
1586                let root = fixture1.root();
1587                while !done1.load(atomic::Ordering::Relaxed) {
1588                    let file = open_file_checked(
1589                        &root,
1590                        "foo",
1591                        fio::Flags::FLAG_MAYBE_CREATE
1592                            | fio::PERM_READABLE
1593                            | fio::PERM_WRITABLE
1594                            | fio::Flags::PROTOCOL_FILE,
1595                        &Default::default(),
1596                    )
1597                    .await;
1598                    let _: u64 = file
1599                        .write(b"hello")
1600                        .await
1601                        .expect("write failed")
1602                        .map_err(Status::err_from_raw)
1603                        .expect("write error");
1604                }
1605            }),
1606            fasync::Task::spawn(async move {
1607                let root = fixture2.root();
1608                while !done2.load(atomic::Ordering::Relaxed) {
1609                    let file = open_file_checked(
1610                        &root,
1611                        "foo",
1612                        fio::Flags::FLAG_MAYBE_CREATE
1613                            | fio::PERM_READABLE
1614                            | fio::PERM_WRITABLE
1615                            | fio::Flags::PROTOCOL_FILE,
1616                        &Default::default(),
1617                    )
1618                    .await;
1619                    let _: u64 = file
1620                        .write(b"hello")
1621                        .await
1622                        .expect("write failed")
1623                        .map_err(Status::err_from_raw)
1624                        .expect("write error");
1625                }
1626            }),
1627            fasync::Task::spawn(async move {
1628                let root = fixture3.root();
1629                for _ in 0..300 {
1630                    let file = open_file_checked(
1631                        &root,
1632                        "foo",
1633                        fio::Flags::FLAG_MAYBE_CREATE
1634                            | fio::PERM_READABLE
1635                            | fio::PERM_WRITABLE
1636                            | fio::Flags::PROTOCOL_FILE,
1637                        &Default::default(),
1638                    )
1639                    .await;
1640                    assert_eq!(
1641                        file.close().await.expect("FIDL call failed").map_err(Status::err_from_raw),
1642                        Ok(())
1643                    );
1644                    root.unlink("foo", &fio::UnlinkOptions::default())
1645                        .await
1646                        .expect("FIDL call failed")
1647                        .expect("unlink failed");
1648                }
1649                done.store(true, atomic::Ordering::Relaxed);
1650            })
1651        );
1652
1653        Arc::try_unwrap(fixture).unwrap_or_else(|_| panic!()).close().await;
1654    }
1655
1656    #[fuchsia::test(threads = 10)]
1657    async fn test_get_backing_memory_shared_vmo_right_write() {
1658        let fixture = TestFixture::new().await;
1659        let root = fixture.root();
1660
1661        let file = open_file_checked(
1662            &root,
1663            "foo",
1664            fio::Flags::FLAG_MAYBE_CREATE
1665                | fio::PERM_READABLE
1666                | fio::PERM_WRITABLE
1667                | fio::Flags::PROTOCOL_FILE,
1668            &Default::default(),
1669        )
1670        .await;
1671
1672        file.resize(4096)
1673            .await
1674            .expect("resize failed")
1675            .map_err(Status::err_from_raw)
1676            .expect("resize error");
1677
1678        let vmo = file
1679            .get_backing_memory(fio::VmoFlags::SHARED_BUFFER | fio::VmoFlags::READ)
1680            .await
1681            .expect("Failed to make FIDL call")
1682            .map_err(Status::err_from_raw)
1683            .expect("Failed to get VMO");
1684        let err = vmo.write(&[0, 1, 2, 3], 0).expect_err("VMO should not be writable");
1685        assert_eq!(Status::ACCESS_DENIED, err);
1686
1687        let vmo = file
1688            .get_backing_memory(
1689                fio::VmoFlags::SHARED_BUFFER | fio::VmoFlags::READ | fio::VmoFlags::WRITE,
1690            )
1691            .await
1692            .expect("Failed to make FIDL call")
1693            .map_err(Status::err_from_raw)
1694            .expect("Failed to get VMO");
1695        vmo.write(&[0, 1, 2, 3], 0).expect("VMO should be writable");
1696
1697        close_file_checked(file).await;
1698        fixture.close().await;
1699    }
1700
1701    #[fuchsia::test(threads = 10)]
1702    async fn test_get_backing_memory_shared_vmo_right_read() {
1703        let fixture = TestFixture::new().await;
1704        let root = fixture.root();
1705
1706        let file = open_file_checked(
1707            &root,
1708            "foo",
1709            fio::Flags::FLAG_MAYBE_CREATE
1710                | fio::PERM_READABLE
1711                | fio::PERM_WRITABLE
1712                | fio::Flags::PROTOCOL_FILE,
1713            &Default::default(),
1714        )
1715        .await;
1716
1717        file.resize(4096)
1718            .await
1719            .expect("resize failed")
1720            .map_err(Status::err_from_raw)
1721            .expect("resize error");
1722
1723        let mut data = [0u8; 4];
1724        let vmo = file
1725            .get_backing_memory(fio::VmoFlags::SHARED_BUFFER)
1726            .await
1727            .expect("Failed to make FIDL call")
1728            .map_err(Status::err_from_raw)
1729            .expect("Failed to get VMO");
1730        let err = vmo.read(&mut data, 0).expect_err("VMO should not be readable");
1731        assert_eq!(Status::ACCESS_DENIED, err);
1732
1733        let vmo = file
1734            .get_backing_memory(fio::VmoFlags::SHARED_BUFFER | fio::VmoFlags::READ)
1735            .await
1736            .expect("Failed to make FIDL call")
1737            .map_err(Status::err_from_raw)
1738            .expect("Failed to get VMO");
1739        vmo.read(&mut data, 0).expect("VMO should be readable");
1740
1741        close_file_checked(file).await;
1742        fixture.close().await;
1743    }
1744
1745    #[fuchsia::test(threads = 10)]
1746    async fn test_get_backing_memory_shared_vmo_resize() {
1747        let fixture = TestFixture::new().await;
1748        let root = fixture.root();
1749
1750        let file = open_file_checked(
1751            &root,
1752            "foo",
1753            fio::Flags::FLAG_MAYBE_CREATE
1754                | fio::PERM_READABLE
1755                | fio::PERM_WRITABLE
1756                | fio::Flags::PROTOCOL_FILE,
1757            &Default::default(),
1758        )
1759        .await;
1760
1761        let vmo = file
1762            .get_backing_memory(
1763                fio::VmoFlags::SHARED_BUFFER | fio::VmoFlags::READ | fio::VmoFlags::WRITE,
1764            )
1765            .await
1766            .expect("Failed to make FIDL call")
1767            .map_err(Status::err_from_raw)
1768            .expect("Failed to get VMO");
1769
1770        // No RESIZE right.
1771        let err = vmo.set_size(4096).expect_err("VMO should not be resizable");
1772        assert_eq!(Status::UNAVAILABLE, err);
1773        // No SET_PROPERTY right.
1774        let err =
1775            vmo.set_content_size(&10).expect_err("content size should not be directly modifiable");
1776        assert_eq!(Status::ACCESS_DENIED, err);
1777
1778        close_file_checked(file).await;
1779        fixture.close().await;
1780    }
1781
1782    #[fuchsia::test(threads = 10)]
1783    async fn test_get_backing_memory_private_vmo_resize() {
1784        let fixture = TestFixture::new().await;
1785        let root = fixture.root();
1786
1787        let file = open_file_checked(
1788            &root,
1789            "foo",
1790            fio::Flags::FLAG_MAYBE_CREATE
1791                | fio::PERM_READABLE
1792                | fio::PERM_WRITABLE
1793                | fio::Flags::PROTOCOL_FILE,
1794            &Default::default(),
1795        )
1796        .await;
1797
1798        let vmo = file
1799            .get_backing_memory(
1800                fio::VmoFlags::PRIVATE_CLONE | fio::VmoFlags::READ | fio::VmoFlags::WRITE,
1801            )
1802            .await
1803            .expect("Failed to make FIDL call")
1804            .map_err(Status::err_from_raw)
1805            .expect("Failed to get VMO");
1806        vmo.set_size(10).expect("VMO should be resizable");
1807        vmo.set_content_size(&20).expect("content size should be modifiable");
1808        vmo.set_stream_size(20).expect("stream size should be modifiable");
1809
1810        let vmo = file
1811            .get_backing_memory(fio::VmoFlags::PRIVATE_CLONE | fio::VmoFlags::READ)
1812            .await
1813            .expect("Failed to make FIDL call")
1814            .map_err(Status::err_from_raw)
1815            .expect("Failed to get VMO");
1816        let err = vmo.set_size(10).expect_err("VMO should not be resizable");
1817        assert_eq!(err, Status::ACCESS_DENIED);
1818        // This zeroes pages, which can't be done on a read-only VMO.
1819        vmo.set_stream_size(20).expect_err("stream size is not modifiable");
1820        vmo.set_content_size(&20).expect_err("content is not modifiable");
1821
1822        close_file_checked(file).await;
1823        fixture.close().await;
1824    }
1825
1826    #[fuchsia::test(threads = 10)]
1827    async fn extended_attributes() {
1828        let fixture = TestFixture::new().await;
1829        let root = fixture.root();
1830
1831        let file = open_file_checked(
1832            &root,
1833            "foo",
1834            fio::Flags::FLAG_MAYBE_CREATE
1835                | fio::PERM_READABLE
1836                | fio::PERM_WRITABLE
1837                | fio::Flags::PROTOCOL_FILE,
1838            &Default::default(),
1839        )
1840        .await;
1841
1842        let name = b"security.selinux";
1843        let value_vec = b"bar".to_vec();
1844
1845        {
1846            let (iterator_client, iterator_server) =
1847                fidl::endpoints::create_proxy::<fio::ExtendedAttributeIteratorMarker>();
1848            file.list_extended_attributes(iterator_server).expect("Failed to make FIDL call");
1849            let (chunk, last) = iterator_client
1850                .get_next()
1851                .await
1852                .expect("Failed to make FIDL call")
1853                .expect("Failed to get next iterator chunk");
1854            assert!(last);
1855            assert_eq!(chunk, Vec::<Vec<u8>>::new());
1856        }
1857        assert_eq!(
1858            file.get_extended_attribute(name)
1859                .await
1860                .expect("Failed to make FIDL call")
1861                .expect_err("Got successful message back for missing attribute"),
1862            Status::NOT_FOUND.into_raw(),
1863        );
1864
1865        file.set_extended_attribute(
1866            name,
1867            fio::ExtendedAttributeValue::Bytes(value_vec.clone()),
1868            fio::SetExtendedAttributeMode::Set,
1869        )
1870        .await
1871        .expect("Failed to make FIDL call")
1872        .expect("Failed to set extended attribute");
1873
1874        {
1875            let (iterator_client, iterator_server) =
1876                fidl::endpoints::create_proxy::<fio::ExtendedAttributeIteratorMarker>();
1877            file.list_extended_attributes(iterator_server).expect("Failed to make FIDL call");
1878            let (chunk, last) = iterator_client
1879                .get_next()
1880                .await
1881                .expect("Failed to make FIDL call")
1882                .expect("Failed to get next iterator chunk");
1883            assert!(last);
1884            assert_eq!(chunk, vec![name]);
1885        }
1886        assert_eq!(
1887            file.get_extended_attribute(name)
1888                .await
1889                .expect("Failed to make FIDL call")
1890                .expect("Failed to get extended attribute"),
1891            fio::ExtendedAttributeValue::Bytes(value_vec)
1892        );
1893
1894        file.remove_extended_attribute(name)
1895            .await
1896            .expect("Failed to make FIDL call")
1897            .expect("Failed to remove extended attribute");
1898
1899        {
1900            let (iterator_client, iterator_server) =
1901                fidl::endpoints::create_proxy::<fio::ExtendedAttributeIteratorMarker>();
1902            file.list_extended_attributes(iterator_server).expect("Failed to make FIDL call");
1903            let (chunk, last) = iterator_client
1904                .get_next()
1905                .await
1906                .expect("Failed to make FIDL call")
1907                .expect("Failed to get next iterator chunk");
1908            assert!(last);
1909            assert_eq!(chunk, Vec::<Vec<u8>>::new());
1910        }
1911        assert_eq!(
1912            file.get_extended_attribute(name)
1913                .await
1914                .expect("Failed to make FIDL call")
1915                .expect_err("Got successful message back for missing attribute"),
1916            Status::NOT_FOUND.into_raw(),
1917        );
1918
1919        close_file_checked(file).await;
1920        fixture.close().await;
1921    }
1922
1923    #[fuchsia::test]
1924    async fn test_flush_when_closed_from_on_zero_children() {
1925        let fixture = TestFixture::new().await;
1926        let root = fixture.root();
1927
1928        let file = open_file_checked(
1929            &root,
1930            "foo",
1931            fio::Flags::FLAG_MAYBE_CREATE
1932                | fio::PERM_READABLE
1933                | fio::PERM_WRITABLE
1934                | fio::Flags::PROTOCOL_FILE,
1935            &Default::default(),
1936        )
1937        .await;
1938
1939        file.resize(50).await.expect("resize (FIDL) failed").expect("resize failed");
1940
1941        {
1942            let vmo = file
1943                .get_backing_memory(fio::VmoFlags::READ | fio::VmoFlags::WRITE)
1944                .await
1945                .expect("get_backing_memory (FIDL) failed")
1946                .map_err(Status::err_from_raw)
1947                .expect("get_backing_memory failed");
1948
1949            std::mem::drop(file);
1950
1951            fasync::unblock(move || vmo.write(b"hello", 0).expect("write failed")).await;
1952        }
1953
1954        fixture.close().await;
1955    }
1956
1957    #[fuchsia::test]
1958    async fn test_background_flush() {
1959        let fixture = TestFixture::open(
1960            DeviceHolder::new(FakeDevice::new(65536, 512)),
1961            TestFixtureOptions::default(),
1962        )
1963        .await;
1964        {
1965            let root = fixture.root();
1966
1967            let file = open_file_checked(
1968                &root,
1969                "foo",
1970                fio::Flags::FLAG_MAYBE_CREATE
1971                    | fio::PERM_READABLE
1972                    | fio::PERM_WRITABLE
1973                    | fio::Flags::PROTOCOL_FILE,
1974                &Default::default(),
1975            )
1976            .await;
1977
1978            let stream = file.describe().await.unwrap().stream.unwrap();
1979            let file_id = file
1980                .get_attributes(fio::NodeAttributesQuery::ID)
1981                .await
1982                .unwrap()
1983                .unwrap()
1984                .1
1985                .id
1986                .unwrap();
1987            // Block background flush completion by holding the truncate lock.
1988            let truncate_guard = fixture
1989                .fs()
1990                .truncate_guard(fixture.volume().volume().store().store_object_id(), file_id)
1991                .await;
1992
1993            let file_obj = fixture
1994                .volume()
1995                .volume()
1996                .cache()
1997                .get(file_id)
1998                .unwrap()
1999                .into_any()
2000                .downcast::<FxFile>()
2001                .unwrap();
2002            let file_clone = file_obj.clone();
2003
2004            unblock(move || {
2005                let page_size = page_size().get();
2006                let mut offset: u64 = 0;
2007                while !file_clone
2008                    .background_flush_running
2009                    .load(std::sync::atomic::Ordering::Relaxed)
2010                {
2011                    assert!(
2012                        offset <= BACKGROUND_FLUSH_THRESHOLD * 2,
2013                        "Background flush not triggering"
2014                    );
2015                    stream
2016                        .write_at(zx::StreamWriteOptions::empty(), offset, &[0, 1, 2, 3, 4])
2017                        .expect("write should succeed");
2018                    offset += page_size;
2019                }
2020            })
2021            .await;
2022
2023            // Release the truncate lock to unblock the writing, wait for the flush to complete.
2024            std::mem::drop(truncate_guard);
2025            const MAX_WAIT: Duration = Duration::from_secs(10);
2026            let wait_increments = Duration::from_millis(100);
2027            let mut total_waited = Duration::ZERO;
2028            while file_obj.background_flush_running.load(std::sync::atomic::Ordering::Relaxed) {
2029                total_waited += wait_increments;
2030                assert!(total_waited < MAX_WAIT);
2031                fasync::Timer::new(wait_increments).await;
2032            }
2033        }
2034
2035        fixture.close().await;
2036    }
2037
2038    #[fuchsia::test]
2039    async fn test_get_attributes_fsverity_enabled_file() {
2040        let fixture = TestFixture::new().await;
2041        let root = fixture.root();
2042
2043        let file = open_file_checked(
2044            &root,
2045            "foo",
2046            fio::Flags::FLAG_MAYBE_CREATE
2047                | fio::PERM_READABLE
2048                | fio::PERM_WRITABLE
2049                | fio::Flags::PROTOCOL_FILE,
2050            &Default::default(),
2051        )
2052        .await;
2053
2054        let mut data: Vec<u8> = vec![0x00u8; 1052672];
2055        rng().fill(&mut data[..]);
2056
2057        for chunk in data.chunks(8192) {
2058            file.write(chunk)
2059                .await
2060                .expect("FIDL call failed")
2061                .map_err(Status::err_from_raw)
2062                .expect("write failed");
2063        }
2064
2065        let tree = fsverity_merkle::MerkleTree::from_data(
2066            &data,
2067            FsVerityHasher::Sha256(FsVerityHasherOptions::new(vec![0xFF; 8], 4096)),
2068        );
2069        let expected_root = tree.root().to_vec();
2070
2071        let expected_descriptor = fio::VerificationOptions {
2072            hash_algorithm: Some(fio::HashAlgorithm::Sha256),
2073            salt: Some(vec![0xFF; 8]),
2074            ..Default::default()
2075        };
2076
2077        file.enable_verity(&expected_descriptor)
2078            .await
2079            .expect("FIDL transport error")
2080            .expect("enable verity failed");
2081
2082        let (_, immutable_attributes) = file
2083            .get_attributes(
2084                fio::NodeAttributesQuery::ROOT_HASH
2085                    | fio::NodeAttributesQuery::OPTIONS
2086                    | fio::NodeAttributesQuery::ABILITIES,
2087            )
2088            .await
2089            .expect("FIDL call failed")
2090            .map_err(Status::err_from_raw)
2091            .expect("get_attributes failed");
2092
2093        assert_eq!(
2094            immutable_attributes
2095                .options
2096                .expect("verification options not present in immutable attributes"),
2097            expected_descriptor
2098        );
2099        assert_eq!(
2100            immutable_attributes.root_hash.expect("root hash not present in immutable attributes"),
2101            expected_root
2102        );
2103        assert_eq!(
2104            immutable_attributes.abilities.expect("abilities not present"),
2105            fio::Operations::GET_ATTRIBUTES
2106                | fio::Operations::UPDATE_ATTRIBUTES
2107                | fio::Operations::READ_BYTES
2108        );
2109
2110        fixture.close().await;
2111    }
2112
2113    /// Verify that once we enable verity on a file, it can never be written to or resized.
2114    /// This applies even to connections that have [`fio::PERM_WRITABLE`].
2115    #[fuchsia::test]
2116    async fn test_write_fail_fsverity_enabled_file() {
2117        let fixture = TestFixture::new().await;
2118        let root = fixture.root();
2119
2120        let file = open_file_checked(
2121            &root,
2122            "foo",
2123            fio::Flags::FLAG_MAYBE_CREATE
2124                | fio::PERM_READABLE
2125                | fio::PERM_WRITABLE
2126                | fio::Flags::PROTOCOL_FILE,
2127            &Default::default(),
2128        )
2129        .await;
2130
2131        file.write(&[8; 8192])
2132            .await
2133            .expect("FIDL call failed")
2134            .map_err(Status::err_from_raw)
2135            .expect("write failed");
2136
2137        // Obtain a writable VMO before enabling verity.
2138        let existing_vmo = file
2139            .get_backing_memory(fio::VmoFlags::READ | fio::VmoFlags::WRITE)
2140            .await
2141            .expect("FIDL transport error")
2142            .map_err(Status::err_from_raw)
2143            .expect("get_backing_memory failed");
2144
2145        let descriptor = fio::VerificationOptions {
2146            hash_algorithm: Some(fio::HashAlgorithm::Sha256),
2147            salt: Some(vec![0xFF; 8]),
2148            ..Default::default()
2149        };
2150
2151        file.enable_verity(&descriptor)
2152            .await
2153            .expect("FIDL transport error")
2154            .expect("enable verity failed");
2155
2156        // Writes via an existing writable VMO obtained before enabling verity should fail at
2157        // the pager level.
2158        fasync::unblock(move || {
2159            existing_vmo
2160                .write(&[2; 8192], 0)
2161                .expect_err("write via existing VMO succeeded on fsverity-enabled file");
2162        })
2163        .await;
2164
2165        async fn assert_file_is_not_writable(file: &fio::FileProxy) {
2166            // Writes via FIDL should fail
2167            file.write(&[2; 8192])
2168                .await
2169                .expect("FIDL transport error")
2170                .map_err(Status::err_from_raw)
2171                .expect_err("write succeeded on fsverity-enabled file");
2172            // Getting a writable VMO without PRIVATE_CLONE should fail
2173            file.get_backing_memory(fio::VmoFlags::READ | fio::VmoFlags::WRITE)
2174                .await
2175                .expect("FIDL transport error")
2176                .map_err(Status::err_from_raw)
2177                .expect_err("get_backing_memory with WRITE succeeded on fsverity-enabled file");
2178            // Getting a private clone with WRITE should succeed and not modify the file
2179            let vmo = file
2180                .get_backing_memory(
2181                    fio::VmoFlags::READ | fio::VmoFlags::WRITE | fio::VmoFlags::PRIVATE_CLONE,
2182                )
2183                .await
2184                .expect("FIDL transport error")
2185                .map_err(Status::err_from_raw)
2186                .expect("get_backing_memory with PRIVATE_CLONE failed");
2187            fasync::unblock(move || {
2188                vmo.write(&[2; 8192], 0).expect("write via private clone VMO failed");
2189            })
2190            .await;
2191            // Truncation should fail
2192            file.resize(1)
2193                .await
2194                .expect("FIDL transport error")
2195                .map_err(Status::err_from_raw)
2196                .expect_err("resize succeeded on fsverity-enabled file");
2197        }
2198
2199        assert_file_is_not_writable(&file).await;
2200        close_file_checked(file).await;
2201
2202        // Ensure that even if new writable connections are created, those also cannot write.
2203        let file =
2204            open_file(&root, "foo", fio::PERM_READABLE | fio::PERM_WRITABLE, &Default::default())
2205                .await
2206                .expect("failed to open fsverity-enabled file");
2207        assert_file_is_not_writable(&file).await;
2208        close_file_checked(file).await;
2209
2210        // Reopen the filesystem and ensure that the file can't be written to.
2211        let device = fixture.close().await;
2212        device.ensure_unique();
2213        device.reopen(false);
2214        let fixture =
2215            TestFixture::open(device, TestFixtureOptions { format: false, ..Default::default() })
2216                .await;
2217
2218        let root = fixture.root();
2219        let file =
2220            open_file(&root, "foo", fio::PERM_READABLE | fio::PERM_WRITABLE, &Default::default())
2221                .await
2222                .expect("failed to open fsverity-enabled file");
2223        assert_file_is_not_writable(&file).await;
2224        close_file_checked(file).await;
2225
2226        fixture.close().await;
2227    }
2228
2229    #[fuchsia::test]
2230    async fn test_truncate_fail_fsverity_enabled_file() {
2231        let fixture = TestFixture::new().await;
2232        let root = fixture.root();
2233
2234        let file = open_file_checked(
2235            &root,
2236            "foo",
2237            fio::Flags::FLAG_MAYBE_CREATE
2238                | fio::PERM_READABLE
2239                | fio::PERM_WRITABLE
2240                | fio::Flags::PROTOCOL_FILE,
2241            &Default::default(),
2242        )
2243        .await;
2244
2245        file.write(&[8; 8192])
2246            .await
2247            .expect("FIDL call failed")
2248            .map_err(Status::err_from_raw)
2249            .expect("write failed");
2250
2251        let descriptor = fio::VerificationOptions {
2252            hash_algorithm: Some(fio::HashAlgorithm::Sha256),
2253            salt: Some(vec![0xFF; 8]),
2254            ..Default::default()
2255        };
2256
2257        file.enable_verity(&descriptor)
2258            .await
2259            .expect("FIDL transport error")
2260            .expect("enable verity failed");
2261
2262        // Shrinking via FIDL should fail
2263        file.resize(1)
2264            .await
2265            .expect("FIDL transport error")
2266            .map_err(Status::err_from_raw)
2267            .expect_err("shrink succeeded on fsverity-enabled file");
2268
2269        // Growing via FIDL should fail
2270        file.resize(16384)
2271            .await
2272            .expect("FIDL transport error")
2273            .map_err(Status::err_from_raw)
2274            .expect_err("grow succeeded on fsverity-enabled file");
2275
2276        close_file_checked(file).await;
2277        fixture.close().await;
2278    }
2279
2280    #[fuchsia::test]
2281    async fn test_fsverity_enabled_file_verified_reads() {
2282        let mut data: Vec<u8> = vec![0x00u8; 1052672];
2283        rng().fill(&mut data[..]);
2284        let mut num_chunks = 0;
2285
2286        let reused_device = {
2287            let fixture = TestFixture::new().await;
2288            let root = fixture.root();
2289
2290            let file = open_file_checked(
2291                &root,
2292                "foo",
2293                fio::Flags::FLAG_MAYBE_CREATE
2294                    | fio::PERM_READABLE
2295                    | fio::PERM_WRITABLE
2296                    | fio::Flags::PROTOCOL_FILE,
2297                &Default::default(),
2298            )
2299            .await;
2300
2301            for chunk in data.chunks(fio::MAX_BUF as usize) {
2302                file.write(chunk)
2303                    .await
2304                    .expect("FIDL call failed")
2305                    .map_err(Status::err_from_raw)
2306                    .expect("write failed");
2307                num_chunks += 1;
2308            }
2309
2310            let descriptor = fio::VerificationOptions {
2311                hash_algorithm: Some(fio::HashAlgorithm::Sha256),
2312                salt: Some(vec![0xFF; 8]),
2313                ..Default::default()
2314            };
2315
2316            file.enable_verity(&descriptor)
2317                .await
2318                .expect("FIDL transport error")
2319                .expect("enable verity failed");
2320
2321            assert!(file.sync().await.expect("Sync failed").is_ok());
2322            close_file_checked(file).await;
2323            fixture.close().await
2324        };
2325
2326        let fixture = TestFixture::open(
2327            reused_device,
2328            TestFixtureOptions { format: false, ..Default::default() },
2329        )
2330        .await;
2331        let root = fixture.root();
2332
2333        let file = open_file_checked(
2334            &root,
2335            "foo",
2336            fio::PERM_READABLE | fio::Flags::PROTOCOL_FILE,
2337            &Default::default(),
2338        )
2339        .await;
2340
2341        for chunk in 0..num_chunks {
2342            let buffer = file
2343                .read(fio::MAX_BUF)
2344                .await
2345                .expect("transport error on read")
2346                .expect("read failed");
2347            let start = chunk * fio::MAX_BUF as usize;
2348            assert_eq!(&buffer, &data[start..start + buffer.len()]);
2349        }
2350
2351        fixture.close().await;
2352    }
2353
2354    #[fuchsia::test]
2355    async fn test_enabling_verity_on_verified_file_fails() {
2356        let reused_device = {
2357            let fixture = TestFixture::new().await;
2358            let root = fixture.root();
2359
2360            let file = open_file_checked(
2361                &root,
2362                "foo",
2363                fio::Flags::FLAG_MAYBE_CREATE
2364                    | fio::PERM_READABLE
2365                    | fio::PERM_WRITABLE
2366                    | fio::Flags::PROTOCOL_FILE,
2367                &Default::default(),
2368            )
2369            .await;
2370
2371            file.write(&[1; 8192])
2372                .await
2373                .expect("FIDL call failed")
2374                .map_err(Status::err_from_raw)
2375                .expect("write failed");
2376
2377            let descriptor = fio::VerificationOptions {
2378                hash_algorithm: Some(fio::HashAlgorithm::Sha256),
2379                salt: Some(vec![0xFF; 8]),
2380                ..Default::default()
2381            };
2382
2383            file.enable_verity(&descriptor)
2384                .await
2385                .expect("FIDL transport error")
2386                .expect("enable verity failed");
2387
2388            file.enable_verity(&descriptor)
2389                .await
2390                .expect("FIDL transport error")
2391                .expect_err("enabling verity on a verity-enabled file should fail.");
2392
2393            assert!(file.sync().await.expect("Sync failed").is_ok());
2394            close_file_checked(file).await;
2395            fixture.close().await
2396        };
2397
2398        let fixture = TestFixture::open(
2399            reused_device,
2400            TestFixtureOptions { format: false, ..Default::default() },
2401        )
2402        .await;
2403        let root = fixture.root();
2404
2405        let file = open_file_checked(
2406            &root,
2407            "foo",
2408            fio::PERM_READABLE | fio::Flags::PROTOCOL_FILE,
2409            &Default::default(),
2410        )
2411        .await;
2412
2413        let descriptor = fio::VerificationOptions {
2414            hash_algorithm: Some(fio::HashAlgorithm::Sha256),
2415            salt: Some(vec![0xFF; 8]),
2416            ..Default::default()
2417        };
2418
2419        file.enable_verity(&descriptor)
2420            .await
2421            .expect("FIDL transport error")
2422            .expect_err("enabling verity on a verity-enabled file should fail.");
2423
2424        close_file_checked(file).await;
2425        fixture.close().await;
2426    }
2427
2428    #[fuchsia::test]
2429    async fn test_get_attributes_fsverity_not_enabled() {
2430        let fixture = TestFixture::new().await;
2431        let root = fixture.root();
2432
2433        let file = open_file_checked(
2434            &root,
2435            "foo",
2436            fio::Flags::FLAG_MAYBE_CREATE
2437                | fio::PERM_READABLE
2438                | fio::PERM_WRITABLE
2439                | fio::Flags::PROTOCOL_FILE,
2440            &Default::default(),
2441        )
2442        .await;
2443
2444        let mut data: Vec<u8> = vec![0x00u8; 8192];
2445        rng().fill(&mut data[..]);
2446
2447        file.write(&data)
2448            .await
2449            .expect("FIDL call failed")
2450            .map_err(Status::err_from_raw)
2451            .expect("write failed");
2452
2453        let () = file
2454            .sync()
2455            .await
2456            .expect("FIDL call failed")
2457            .map_err(Status::err_from_raw)
2458            .expect("sync failed");
2459
2460        let (_, immutable_attributes) = file
2461            .get_attributes(fio::NodeAttributesQuery::ROOT_HASH | fio::NodeAttributesQuery::OPTIONS)
2462            .await
2463            .expect("FIDL call failed")
2464            .map_err(Status::err_from_raw)
2465            .expect("get_attributes failed");
2466
2467        assert_eq!(immutable_attributes.options, None);
2468        assert_eq!(immutable_attributes.root_hash, None);
2469
2470        fixture.close().await;
2471    }
2472
2473    #[fuchsia::test]
2474    async fn test_update_attributes_also_updates_ctime() {
2475        let fixture = TestFixture::new().await;
2476        let root = fixture.root();
2477
2478        let file = open_file_checked(
2479            &root,
2480            "foo",
2481            fio::Flags::FLAG_MAYBE_CREATE
2482                | fio::PERM_READABLE
2483                | fio::PERM_WRITABLE
2484                | fio::Flags::PROTOCOL_FILE,
2485            &Default::default(),
2486        )
2487        .await;
2488
2489        // Writing to file should update ctime
2490        file.write("hello, world!".as_bytes())
2491            .await
2492            .expect("FIDL call failed")
2493            .map_err(Status::err_from_raw)
2494            .expect("write failed");
2495        let (_mutable_attributes, immutable_attributes) = file
2496            .get_attributes(fio::NodeAttributesQuery::CHANGE_TIME)
2497            .await
2498            .expect("FIDL call failed")
2499            .map_err(Status::err_from_raw)
2500            .expect("get_attributes failed");
2501        let ctime_after_write = immutable_attributes.change_time;
2502
2503        // Updating file attributes updates ctime as well
2504        file.update_attributes(&fio::MutableNodeAttributes {
2505            mode: Some(111),
2506            gid: Some(222),
2507            ..Default::default()
2508        })
2509        .await
2510        .expect("FIDL call failed")
2511        .map_err(Status::err_from_raw)
2512        .expect("update_attributes failed");
2513        let (_mutable_attributes, immutable_attributes) = file
2514            .get_attributes(fio::NodeAttributesQuery::CHANGE_TIME)
2515            .await
2516            .expect("FIDL call failed")
2517            .map_err(Status::err_from_raw)
2518            .expect("get_attributes failed");
2519        let ctime_after_update = immutable_attributes.change_time;
2520        assert!(ctime_after_update > ctime_after_write);
2521
2522        // Flush metadata
2523        file.sync()
2524            .await
2525            .expect("FIDL call failed")
2526            .map_err(Status::err_from_raw)
2527            .expect("sync failed");
2528        let (_mutable_attributes, immutable_attributes) = file
2529            .get_attributes(fio::NodeAttributesQuery::CHANGE_TIME)
2530            .await
2531            .expect("FIDL call failed")
2532            .map_err(Status::err_from_raw)
2533            .expect("get_attributes failed");
2534        let ctime_after_sync = immutable_attributes.change_time;
2535        assert_eq!(ctime_after_sync, ctime_after_update);
2536        fixture.close().await;
2537    }
2538
2539    #[fuchsia::test]
2540    async fn test_unnamed_temporary_file_can_read_and_write_to_it() {
2541        let fixture = TestFixture::new().await;
2542        let root = fixture.root();
2543
2544        let tmpfile = open_file_checked(
2545            &root,
2546            ".",
2547            fio::Flags::PROTOCOL_FILE
2548                | fio::Flags::FLAG_CREATE_AS_UNNAMED_TEMPORARY
2549                | fio::PERM_READABLE
2550                | fio::PERM_WRITABLE,
2551            &fio::Options::default(),
2552        )
2553        .await;
2554
2555        let buf = vec![0xaa as u8; 8];
2556        file::write(&tmpfile, buf.as_slice()).await.expect("Failed to write to file");
2557
2558        tmpfile
2559            .seek(fio::SeekOrigin::Start, 0)
2560            .await
2561            .expect("seek failed")
2562            .map_err(zx::Status::err_from_raw)
2563            .expect("seek error");
2564        let read_buf = file::read(&tmpfile).await.expect("read failed");
2565        assert_eq!(read_buf, buf);
2566
2567        fixture.close().await;
2568    }
2569
2570    #[fuchsia::test]
2571    async fn test_unnamed_temporary_file_get_space_back_after_closing_file() {
2572        let fixture = TestFixture::new().await;
2573        let root = fixture.root();
2574
2575        let tmpfile = open_file_checked(
2576            &root,
2577            ".",
2578            fio::Flags::PROTOCOL_FILE
2579                | fio::Flags::FLAG_CREATE_AS_UNNAMED_TEMPORARY
2580                | fio::PERM_WRITABLE,
2581            &fio::Options::default(),
2582        )
2583        .await;
2584
2585        const BUFFER_SIZE: u64 = 1024 * 1024;
2586        let buf = vec![0xaa as u8; BUFFER_SIZE as usize];
2587        file::write(&tmpfile, buf.as_slice()).await.expect("Failed to write to file");
2588
2589        let info_after_writing_to_tmpfile = root
2590            .query_filesystem()
2591            .await
2592            .expect("Failed wire call to query filesystem")
2593            .1
2594            .expect("Failed to query filesystem");
2595
2596        close_file_checked(tmpfile).await;
2597
2598        // We will get space back soon after closing the file buy maybe not immediately.
2599        for i in 1..50 {
2600            let info = root
2601                .query_filesystem()
2602                .await
2603                .expect("Failed wire call to query filesystem")
2604                .1
2605                .expect("Failed to query filesystem");
2606
2607            // We should claim back at least that amount of data we wrote to the file. There might
2608            // be some metadata left that will not be removed until compaction.
2609            if info_after_writing_to_tmpfile.used_bytes - info.used_bytes >= BUFFER_SIZE {
2610                break;
2611            }
2612            if i == 49 {
2613                panic!("Did not get space back from unnamed temporary file after closing it.");
2614            }
2615        }
2616
2617        fixture.close().await;
2618    }
2619
2620    #[fuchsia::test]
2621    async fn test_unnamed_temporary_file_get_space_back_after_closing_device() {
2622        const BUFFER_SIZE: u64 = 1024 * 1024;
2623
2624        let (reused_device, info_after_writing_to_tmpfile) = {
2625            let fixture = TestFixture::new().await;
2626            let root = fixture.root();
2627
2628            let tmpfile = open_file_checked(
2629                &root,
2630                ".",
2631                fio::Flags::PROTOCOL_FILE
2632                    | fio::Flags::FLAG_CREATE_AS_UNNAMED_TEMPORARY
2633                    | fio::PERM_WRITABLE,
2634                &fio::Options::default(),
2635            )
2636            .await;
2637
2638            let buf = vec![0xaa as u8; BUFFER_SIZE as usize];
2639            file::write(&tmpfile, buf.as_slice()).await.expect("Failed to write to file");
2640
2641            let info_after_writing_to_tmpfile = root
2642                .query_filesystem()
2643                .await
2644                .expect("Failed wire call to query filesystem")
2645                .1
2646                .expect("Failed to query filesystem");
2647
2648            (fixture.close().await, info_after_writing_to_tmpfile)
2649        };
2650
2651        let fixture = TestFixture::open(
2652            reused_device,
2653            TestFixtureOptions { format: false, ..Default::default() },
2654        )
2655        .await;
2656        let root = fixture.root();
2657
2658        let info = root
2659            .query_filesystem()
2660            .await
2661            .expect("Failed wire call to query filesystem")
2662            .1
2663            .expect("Failed to query filesystem");
2664
2665        // We should claim back at least that amount of data we wrote to the file after rebooting
2666        // device. There might be some metadata left that will not be removed until compaction.
2667        assert!(info_after_writing_to_tmpfile.used_bytes - info.used_bytes >= BUFFER_SIZE);
2668
2669        fixture.close().await;
2670    }
2671
2672    #[fuchsia::test]
2673    async fn test_unnamed_temporary_file_can_link_into() {
2674        const FILE1: &str = "foo";
2675        const FILE2: &str = "bar";
2676        const BUFFER_SIZE: u64 = 1024 * 1024;
2677        let buf = vec![0xaa as u8; BUFFER_SIZE as usize];
2678
2679        let reused_device = {
2680            let fixture = TestFixture::new().await;
2681            let root = fixture.root();
2682
2683            let tmpfile = open_file_checked(
2684                &root,
2685                ".",
2686                fio::Flags::PROTOCOL_FILE
2687                    | fio::Flags::FLAG_CREATE_AS_UNNAMED_TEMPORARY
2688                    | fio::PERM_READABLE
2689                    | fio::PERM_WRITABLE,
2690                &fio::Options::default(),
2691            )
2692            .await;
2693
2694            // Link temporary unnamed file into filesystem, making it permanent.
2695            let (status, dst_token) = root.get_token().await.expect("FIDL call failed");
2696            zx::Status::ok(status).expect("get_token failed");
2697            tmpfile
2698                .link_into(zx::Event::from(dst_token.unwrap()), FILE1)
2699                .await
2700                .expect("link_into wire message failed")
2701                .map_err(zx::Status::err_from_raw)
2702                .expect("link_into failed");
2703
2704            // We should be able to link the temporary file proxy multiple times.
2705            let (status, dst_token) = root.get_token().await.expect("FIDL call failed");
2706            zx::Status::ok(status).expect("get_token failed");
2707            tmpfile
2708                .link_into(zx::Event::from(dst_token.unwrap()), FILE2)
2709                .await
2710                .expect("link_into wire message failed")
2711                .map_err(zx::Status::err_from_raw)
2712                .expect("link_into failed");
2713
2714            // Write to tmpfile, we should see the contents of it when reading from FILE1 or FILE2.
2715            file::write(&tmpfile, buf.as_slice()).await.expect("Failed to write to file");
2716
2717            root.unlink(FILE1, &fio::UnlinkOptions::default())
2718                .await
2719                .expect("unlink wire call failed")
2720                .map_err(zx::Status::err_from_raw)
2721                .expect("unlink failed");
2722            fixture.close().await
2723        };
2724
2725        let fixture = TestFixture::open(
2726            reused_device,
2727            TestFixtureOptions { format: false, ..Default::default() },
2728        )
2729        .await;
2730        let root = fixture.root();
2731
2732        // FILE1 was unlinked, so we should not be able to open a connection to it.
2733        assert_eq!(
2734            open_file(
2735                &root,
2736                FILE1,
2737                fio::PERM_READABLE | fio::Flags::PROTOCOL_FILE,
2738                &fio::Options::default()
2739            )
2740            .await
2741            .expect_err("Open succeeded unexpectedly")
2742            .root_cause()
2743            .downcast_ref::<zx::Status>()
2744            .expect("No status"),
2745            &zx::Status::NOT_FOUND,
2746        );
2747
2748        // The temporary unnamed file was linked to FILE2. We should find the same contents written
2749        // to it.
2750        let permanent_file = open_file_checked(
2751            &root,
2752            FILE2,
2753            fio::Flags::PROTOCOL_FILE | fio::PERM_READABLE,
2754            &fio::Options::default(),
2755        )
2756        .await;
2757        permanent_file
2758            .seek(fio::SeekOrigin::Start, 0)
2759            .await
2760            .expect("seek wire message failed")
2761            .map_err(zx::Status::err_from_raw)
2762            .expect("seek error");
2763        let read_buf = file::read(&permanent_file).await.expect("read failed");
2764        assert!(read_buf == buf);
2765
2766        fsck(fixture.fs().clone()).await.expect("fsck failed");
2767
2768        fixture.close().await;
2769    }
2770
2771    #[fuchsia::test]
2772    async fn test_unnamed_temporary_file_in_encrypted_directory() {
2773        let fixture = TestFixture::new().await;
2774        let root = fixture.root();
2775
2776        // Set up encrypted directory
2777        let crypt = fixture.crypt().unwrap();
2778        let encrypted_directory = open_dir_checked(
2779            &root,
2780            "encrypted_directory",
2781            fio::Flags::FLAG_MAYBE_CREATE
2782                | fio::Flags::PROTOCOL_DIRECTORY
2783                | fio::PERM_READABLE
2784                | fio::PERM_WRITABLE,
2785            fio::Options::default(),
2786        )
2787        .await;
2788        crypt.add_wrapping_key(WRAPPING_KEY_ID, [1; 32].into()).unwrap();
2789        encrypted_directory
2790            .update_attributes(&fio::MutableNodeAttributes {
2791                wrapping_key_id: Some(WRAPPING_KEY_ID),
2792                ..Default::default()
2793            })
2794            .await
2795            .expect("update_attributes wire call failed")
2796            .map_err(zx::ok)
2797            .expect("update_attributes failed");
2798
2799        // Create a temporary unnamed file in that directory, it should have the same wrapping key.
2800        let encryped_tmpfile = open_file_checked(
2801            &encrypted_directory,
2802            ".",
2803            fio::Flags::PROTOCOL_FILE
2804                | fio::Flags::FLAG_CREATE_AS_UNNAMED_TEMPORARY
2805                | fio::PERM_READABLE
2806                | fio::PERM_WRITABLE,
2807            &fio::Options::default(),
2808        )
2809        .await;
2810        let (mutable_attributes, _immutable_attributes) = encryped_tmpfile
2811            .get_attributes(fio::NodeAttributesQuery::WRAPPING_KEY_ID)
2812            .await
2813            .expect("get_attributes wire call failed")
2814            .map_err(zx::Status::err_from_raw)
2815            .expect("get_attributes failed");
2816        assert_eq!(mutable_attributes.wrapping_key_id, Some(WRAPPING_KEY_ID));
2817
2818        // Similar to a regular file, linking a temporary unnamed file into the directory will only
2819        // work if they have the same wrapping key ID.
2820        let (status, dst_token) = encrypted_directory.get_token().await.expect("FIDL call failed");
2821        zx::Status::ok(status).expect("get_token failed");
2822        encryped_tmpfile
2823            .link_into(zx::Event::from(dst_token.unwrap()), "foo")
2824            .await
2825            .expect("link_into wire message failed")
2826            .expect("link_into failed");
2827
2828        let unencryped_tmpfile = open_file_checked(
2829            &root,
2830            ".",
2831            fio::Flags::PROTOCOL_FILE
2832                | fio::Flags::FLAG_CREATE_AS_UNNAMED_TEMPORARY
2833                | fio::PERM_READABLE
2834                | fio::PERM_WRITABLE,
2835            &fio::Options::default(),
2836        )
2837        .await;
2838        let (mutable_attributes, _immutable_attributes) = unencryped_tmpfile
2839            .get_attributes(fio::NodeAttributesQuery::WRAPPING_KEY_ID)
2840            .await
2841            .expect("get_attributes wire call failed")
2842            .map_err(zx::Status::err_from_raw)
2843            .expect("get_attributes failed");
2844        assert_eq!(mutable_attributes.wrapping_key_id, None);
2845        let (status, dst_token) = encrypted_directory.get_token().await.expect("FIDL call failed");
2846        zx::Status::ok(status).expect("get_token failed");
2847        assert_eq!(
2848            unencryped_tmpfile
2849                .link_into(zx::Event::from(dst_token.unwrap()), "bar")
2850                .await
2851                .expect("link_into wire message failed")
2852                .map_err(zx::Status::err_from_raw)
2853                .expect_err("link_into passed unexpectedly"),
2854            zx::Status::BAD_STATE,
2855        );
2856
2857        fixture.close().await;
2858    }
2859
2860    #[fuchsia::test]
2861    async fn test_unnamed_temporary_file_in_locked_directory() {
2862        let fixture = TestFixture::new().await;
2863        let root = fixture.root();
2864
2865        // Set up encrypted directory
2866        let crypt = fixture.crypt().unwrap();
2867        let encrypted_directory = open_dir_checked(
2868            &root,
2869            "encrypted_directory",
2870            fio::Flags::FLAG_MAYBE_CREATE
2871                | fio::Flags::PROTOCOL_DIRECTORY
2872                | fio::PERM_READABLE
2873                | fio::PERM_WRITABLE,
2874            fio::Options::default(),
2875        )
2876        .await;
2877        crypt.add_wrapping_key(WRAPPING_KEY_ID, [1; 32].into()).unwrap();
2878        encrypted_directory
2879            .update_attributes(&fio::MutableNodeAttributes {
2880                wrapping_key_id: Some(WRAPPING_KEY_ID),
2881                ..Default::default()
2882            })
2883            .await
2884            .expect("update_attributes wire call failed")
2885            .map_err(zx::ok)
2886            .expect("update_attributes failed");
2887
2888        // This locks the directory
2889        crypt.forget_wrapping_key(&WRAPPING_KEY_ID).unwrap();
2890
2891        // Open unnamed temporary file in a locked directory and should return (key) UNAVAILABLE.
2892        assert_eq!(
2893            open_file(
2894                &encrypted_directory,
2895                ".",
2896                fio::Flags::PROTOCOL_FILE
2897                    | fio::Flags::FLAG_CREATE_AS_UNNAMED_TEMPORARY
2898                    | fio::PERM_READABLE
2899                    | fio::PERM_WRITABLE,
2900                &fio::Options::default()
2901            )
2902            .await
2903            .expect_err("Open succeeded unexpectedly")
2904            .root_cause()
2905            .downcast_ref::<zx::Status>()
2906            .expect("No status"),
2907            &zx::Status::UNAVAILABLE,
2908        );
2909        fixture.close().await;
2910    }
2911
2912    #[fuchsia::test]
2913    async fn test_unnamed_temporary_file_link_into_with_race() {
2914        let fixture = TestFixture::new().await;
2915        let root = fixture.root();
2916
2917        for i in 1..100 {
2918            let tmpfile = open_file_checked(
2919                &root,
2920                ".",
2921                fio::Flags::PROTOCOL_FILE
2922                    | fio::Flags::FLAG_CREATE_AS_UNNAMED_TEMPORARY
2923                    | fio::PERM_READABLE
2924                    | fio::PERM_WRITABLE,
2925                &fio::Options::default(),
2926            )
2927            .await;
2928
2929            // Clone tmpfile proxy to use in the separate threads.
2930            let (tmpfile_clone1, tmpfile_server1) =
2931                fidl::endpoints::create_proxy::<fio::FileMarker>();
2932            tmpfile.clone(tmpfile_server1.into_channel().into()).expect("clone failed");
2933            let (tmpfile_clone2, tmpfile_server2) =
2934                fidl::endpoints::create_proxy::<fio::FileMarker>();
2935            tmpfile.clone(tmpfile_server2.into_channel().into()).expect("clone failed");
2936
2937            // Get the open connection to the sub directory which we would attempt to link the
2938            // unnamed temporary file into.
2939            let sub_dir = open_dir_checked(
2940                &root,
2941                "A",
2942                fio::Flags::PROTOCOL_DIRECTORY
2943                    | fio::PERM_READABLE
2944                    | fio::PERM_WRITABLE
2945                    | fio::Flags::FLAG_MAYBE_CREATE,
2946                fio::Options::default(),
2947            )
2948            .await;
2949
2950            // Get tokens to the sub directory to use for `link_into`.
2951            let (status, dst_token1) = sub_dir.get_token().await.expect("FIDL call failed");
2952            zx::Status::ok(status).expect("get_token failed");
2953            let (status, dst_token2) = sub_dir.get_token().await.expect("FIDL call failed");
2954            zx::Status::ok(status).expect("get_token failed");
2955
2956            join!(
2957                fasync::Task::spawn(async move {
2958                    tmpfile_clone1
2959                        .link_into(zx::Event::from(dst_token1.unwrap()), &(2 * i).to_string())
2960                        .await
2961                        .expect("link_into wire message failed")
2962                        .expect("link_into failed");
2963                }),
2964                fasync::Task::spawn(async move {
2965                    tmpfile_clone2
2966                        .link_into(zx::Event::from(dst_token2.unwrap()), &(2 * i + 1).to_string())
2967                        .await
2968                        .expect("link_into wire message failed")
2969                        .expect("link_into failed");
2970                })
2971            );
2972            let (_, immutable_attributes) = tmpfile
2973                .get_attributes(fio::NodeAttributesQuery::LINK_COUNT)
2974                .await
2975                .expect("Failed get_attributes wire call")
2976                .expect("get_attributes failed");
2977            assert_eq!(immutable_attributes.link_count.unwrap(), 2);
2978            close_file_checked(tmpfile).await;
2979        }
2980        fixture.close().await;
2981    }
2982
2983    #[fuchsia::test]
2984    async fn test_update_attributes_persists() {
2985        const FILE: &str = "foo";
2986        let mtime = Some(Timestamp::now().as_nanos());
2987        let atime = Some(Timestamp::now().as_nanos());
2988        let mode = Some(111);
2989
2990        let device = {
2991            let fixture = TestFixture::new().await;
2992            let root = fixture.root();
2993
2994            let file = open_file_checked(
2995                &root,
2996                FILE,
2997                fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE | fio::Flags::PROTOCOL_FILE,
2998                &fio::Options::default(),
2999            )
3000            .await;
3001
3002            file.update_attributes(&fio::MutableNodeAttributes {
3003                modification_time: mtime,
3004                access_time: atime,
3005                mode: Some(111),
3006                ..Default::default()
3007            })
3008            .await
3009            .expect("update_attributes FIDL call failed")
3010            .map_err(zx::ok)
3011            .expect("update_attributes failed");
3012
3013            // Calling close should flush the node attributes to the device.
3014            fixture.close().await
3015        };
3016
3017        let fixture =
3018            TestFixture::open(device, TestFixtureOptions { format: false, ..Default::default() })
3019                .await;
3020        let root = fixture.root();
3021        let file = open_file_checked(
3022            &root,
3023            FILE,
3024            fio::PERM_READABLE | fio::Flags::PROTOCOL_FILE,
3025            &fio::Options::default(),
3026        )
3027        .await;
3028
3029        let (mutable_attributes, _immutable_attributes) = file
3030            .get_attributes(
3031                fio::NodeAttributesQuery::MODIFICATION_TIME
3032                    | fio::NodeAttributesQuery::ACCESS_TIME
3033                    | fio::NodeAttributesQuery::MODE,
3034            )
3035            .await
3036            .expect("update_attributesFIDL call failed")
3037            .map_err(zx::ok)
3038            .expect("get_attributes failed");
3039        assert_eq!(mutable_attributes.modification_time, mtime);
3040        assert_eq!(mutable_attributes.access_time, atime);
3041        assert_eq!(mutable_attributes.mode, mode);
3042        fixture.close().await;
3043    }
3044
3045    #[fuchsia::test]
3046    async fn test_atime_from_pending_access_time_update_request() {
3047        const FILE: &str = "foo";
3048
3049        let (device, expected_atime, expected_ctime) = {
3050            let fixture = TestFixture::new().await;
3051            let root = fixture.root();
3052
3053            let file = open_file_checked(
3054                &root,
3055                FILE,
3056                fio::Flags::FLAG_MAYBE_CREATE
3057                    | fio::PERM_WRITABLE
3058                    | fio::Flags::PROTOCOL_FILE
3059                    | fio::Flags::PERM_GET_ATTRIBUTES,
3060                &fio::Options::default(),
3061            )
3062            .await;
3063
3064            let (mutable_attributes, immutable_attributes) = file
3065                .get_attributes(
3066                    fio::NodeAttributesQuery::CHANGE_TIME
3067                        | fio::NodeAttributesQuery::ACCESS_TIME
3068                        | fio::NodeAttributesQuery::MODIFICATION_TIME,
3069                )
3070                .await
3071                .expect("update_attributes FIDL call failed")
3072                .map_err(zx::ok)
3073                .expect("get_attributes failed");
3074            let initial_ctime = immutable_attributes.change_time;
3075            let initial_atime = mutable_attributes.access_time;
3076            // When creating a file, ctime, mtime, and atime are all updated to the current time.
3077            assert_eq!(initial_atime, initial_ctime);
3078            assert_eq!(initial_atime, mutable_attributes.modification_time);
3079
3080            // Client manages atime and they signal to Fxfs that a file access has occurred and it
3081            // may require an access time update. They do so by querying with
3082            // `fio::NodeAttributesQuery::PENDING_ACCESS_TIME_UPDATE`.
3083            let (mutable_attributes, immutable_attributes) = file
3084                .get_attributes(
3085                    fio::NodeAttributesQuery::CHANGE_TIME
3086                        | fio::NodeAttributesQuery::ACCESS_TIME
3087                        | fio::NodeAttributesQuery::PENDING_ACCESS_TIME_UPDATE,
3088                )
3089                .await
3090                .expect("update_attributes FIDL call failed")
3091                .map_err(zx::ok)
3092                .expect("get_attributes failed");
3093            // atime will be updated as atime <= ctime (or mtime)
3094            assert!(initial_atime < mutable_attributes.access_time);
3095            let updated_atime = mutable_attributes.access_time;
3096            // Calling get_attributes with PENDING_ACCESS_TIME_UPDATE will trigger an update of
3097            // object attributes if access_time needs to be updated. Check that ctime isn't updated.
3098            assert_eq!(initial_ctime, immutable_attributes.change_time);
3099
3100            let (mutable_attributes, _) = file
3101                .get_attributes(
3102                    fio::NodeAttributesQuery::ACCESS_TIME
3103                        | fio::NodeAttributesQuery::PENDING_ACCESS_TIME_UPDATE,
3104                )
3105                .await
3106                .expect("update_attributes FIDL call failed")
3107                .map_err(zx::ok)
3108                .expect("get_attributes failed");
3109            // atime will be not be updated as atime > ctime (or mtime)
3110            assert_eq!(updated_atime, mutable_attributes.access_time);
3111
3112            (fixture.close().await, mutable_attributes.access_time, initial_ctime)
3113        };
3114
3115        let fixture =
3116            TestFixture::open(device, TestFixtureOptions { format: false, ..Default::default() })
3117                .await;
3118        let root = fixture.root();
3119        let file = open_file_checked(
3120            &root,
3121            FILE,
3122            fio::PERM_READABLE | fio::Flags::PROTOCOL_FILE,
3123            &fio::Options::default(),
3124        )
3125        .await;
3126
3127        // Make sure that the pending atime update persisted.
3128        let (mutable_attributes, immutable_attributes) = file
3129            .get_attributes(
3130                fio::NodeAttributesQuery::CHANGE_TIME | fio::NodeAttributesQuery::ACCESS_TIME,
3131            )
3132            .await
3133            .expect("update_attributesFIDL call failed")
3134            .map_err(zx::ok)
3135            .expect("get_attributes failed");
3136
3137        assert_eq!(immutable_attributes.change_time, expected_ctime);
3138        assert_eq!(mutable_attributes.access_time, expected_atime);
3139        fixture.close().await;
3140    }
3141
3142    #[fuchsia::test(threads = 10)]
3143    async fn test_delete_with_dirty_bytes_and_no_open_handles() {
3144        let fixture = TestFixture::new().await;
3145        let root = fixture.root();
3146
3147        let file = open_file_checked(
3148            &root,
3149            "foo",
3150            fio::Flags::FLAG_MAYBE_CREATE
3151                | fio::PERM_READABLE
3152                | fio::PERM_WRITABLE
3153                | fio::Flags::PROTOCOL_FILE,
3154            &Default::default(),
3155        )
3156        .await;
3157
3158        file.resize(4096)
3159            .await
3160            .expect("resize failed")
3161            .map_err(Status::err_from_raw)
3162            .expect("resize error");
3163
3164        let vmo = file
3165            .get_backing_memory(
3166                fio::VmoFlags::SHARED_BUFFER | fio::VmoFlags::READ | fio::VmoFlags::WRITE,
3167            )
3168            .await
3169            .expect("Failed to make FIDL call")
3170            .map_err(Status::err_from_raw)
3171            .expect("Failed to get VMO");
3172
3173        // Flush the file so that the file isn't dirty.
3174        file.sync().await.unwrap().unwrap();
3175
3176        // Close the file handle.
3177        drop(file);
3178
3179        // Allow time for the close to be noticed.
3180        fasync::Timer::new(std::time::Duration::from_millis(10)).await;
3181
3182        // Modify the file through the VMO (this should mark it dirty).
3183        vmo.write(&[1, 2, 3, 4], 0).expect("vmo write failed");
3184
3185        // Drop the VMO so that the open count reaches zero, but unlike in the close case, this
3186        // will not flush the file immediately.
3187        drop(vmo);
3188
3189        // Allow time for the VMO being dropped to be noticed.
3190        fasync::Timer::new(std::time::Duration::from_millis(10)).await;
3191
3192        // Delete the file.  The file should be dirty at this point.
3193        root.unlink("foo", &fio::UnlinkOptions::default())
3194            .await
3195            .expect("unlink failed")
3196            .map_err(Status::err_from_raw)
3197            .expect("unlink error");
3198
3199        fixture.close().await;
3200    }
3201
3202    // Ensures that closing a file connection immediately blocks any future stream writes to the
3203    // underlying VMO with a BAD_STATE error, and that the file can still be safely unlinked
3204    // once the stream write has been rejected and cleanup has occurred.
3205    #[fuchsia::test]
3206    async fn test_close_file_before_writing_to_stream() {
3207        const FILE_NAME: &str = "foo";
3208
3209        let fixture = TestFixture::new().await;
3210        let root = fixture.root();
3211        let file = open_file_checked(
3212            &root,
3213            FILE_NAME,
3214            fio::Flags::FLAG_MAYBE_CREATE
3215                | fio::PERM_READABLE
3216                | fio::PERM_WRITABLE
3217                | fio::Flags::PROTOCOL_FILE,
3218            &Default::default(),
3219        )
3220        .await;
3221
3222        let stream = file.describe().await.unwrap().stream.unwrap();
3223
3224        close_file_checked(file).await;
3225
3226        unblock(move || {
3227            stream
3228                .write_at(zx::StreamWriteOptions::empty(), 0, &[1, 2, 3, 4])
3229                .expect_err("Write should get BAD_STATE");
3230        })
3231        .await;
3232
3233        // Wait a bit to ensure that the stream has been closed and the zero children signal has
3234        // been processed.
3235        fasync::Timer::new(Duration::from_millis(100)).await;
3236
3237        // Now unlink the file.
3238        root.unlink(FILE_NAME, &fio::UnlinkOptions::default())
3239            .await
3240            .expect("unlink wire call failed")
3241            .expect("unlink failed");
3242
3243        fixture.close().await;
3244    }
3245
3246    // Ensures that closing a file connection blocks stream writes, but reopening the file
3247    // restores write capabilities. By using a duplicated stream handle from the first open,
3248    // we prove that when the file is reopened, the pager is marked open again, and writes
3249    // on the original stream handle succeed once more. This also confirms that the file remains
3250    // in the Fxfs node caches and is correctly reused when reopened.
3251    #[fuchsia::test]
3252    async fn test_close_and_reopen_file_stream() {
3253        const FILE_NAME: &str = "foo";
3254
3255        let fixture = TestFixture::new().await;
3256        let root = fixture.root();
3257        let file = open_file_checked(
3258            &root,
3259            FILE_NAME,
3260            fio::Flags::FLAG_MAYBE_CREATE
3261                | fio::PERM_READABLE
3262                | fio::PERM_WRITABLE
3263                | fio::Flags::PROTOCOL_FILE,
3264            &Default::default(),
3265        )
3266        .await;
3267
3268        let page_size = page_size();
3269        file.resize(8 * page_size)
3270            .await
3271            .expect("resize failed")
3272            .map_err(Status::err_from_raw)
3273            .expect("resize error");
3274
3275        let stream1 = file.describe().await.unwrap().stream.unwrap();
3276        let stream1_dup = stream1.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
3277
3278        close_file_checked(file).await;
3279
3280        // Stream writes on the duplicated stream handle should now fail because all active
3281        // connections are closed and the handle open status is false.
3282        // Write at offset 0 (page 0).
3283        let stream1_dup_clone = stream1_dup.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
3284        unblock(move || {
3285            stream1_dup_clone
3286                .write_at(zx::StreamWriteOptions::empty(), 0, &[1, 2, 3, 4])
3287                .expect_err("Write should get BAD_STATE");
3288        })
3289        .await;
3290
3291        // Open the file again. This retrieves the node from the dirent cache and sets its pager
3292        // status to open, enabling stream writes on all stream handles pointing to its VMO.
3293        let file2 = open_file_checked(
3294            &root,
3295            FILE_NAME,
3296            fio::Flags::PROTOCOL_FILE | fio::PERM_READABLE | fio::PERM_WRITABLE,
3297            &Default::default(),
3298        )
3299        .await;
3300
3301        let stream2 = file2.describe().await.unwrap().stream.unwrap();
3302
3303        // Now both the old duplicated stream and the new stream should succeed.
3304        // Write at page-separated offsets to prevent kernel dirty page caching from hiding races:
3305        // Page 1 and Page 2.
3306        let stream1_dup_clone2 = stream1_dup.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
3307        let stream2_clone = stream2.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
3308        unblock(move || {
3309            stream1_dup_clone2
3310                .write_at(zx::StreamWriteOptions::empty(), 1 * page_size, &[5, 6, 7, 8])
3311                .expect("Write on re-opened stream 1 dup should succeed");
3312            stream2_clone
3313                .write_at(zx::StreamWriteOptions::empty(), 2 * page_size, &[9, 10, 11, 12])
3314                .expect("Write on new stream 2 should succeed");
3315        })
3316        .await;
3317
3318        // Close the reopened connection.
3319        close_file_checked(file2).await;
3320
3321        // Writes on both streams should fail again.
3322        // Write at new page-separated offsets:
3323        // Page 3 and Page 4.
3324        unblock(move || {
3325            stream1_dup
3326                .write_at(zx::StreamWriteOptions::empty(), 3 * page_size, &[13, 14, 15, 16])
3327                .expect_err("Write on stream 1 dup should fail after final close");
3328            stream2
3329                .write_at(zx::StreamWriteOptions::empty(), 4 * page_size, &[17, 18, 19, 20])
3330                .expect_err("Write on stream 2 should fail after final close");
3331        })
3332        .await;
3333
3334        fixture.close().await;
3335    }
3336
3337    use test_case::test_case;
3338
3339    #[test_case(
3340        fxfs_crypto::EncryptionKey::Fxfs(fxfs_crypto::FxfsKey {
3341            wrapping_key_id: WRAPPING_KEY_ID,
3342            key: fxfs_crypto::WrappedKeyBytes::from([0xff; fxfs_crypto::FXFS_WRAPPED_KEY_SIZE]),
3343        });
3344        "fxfs"
3345    )]
3346    #[test_case(
3347        fxfs_crypto::EncryptionKey::FscryptInoLblk32File {
3348            key_identifier: WRAPPING_KEY_ID,
3349        };
3350        "fscrypt_file"
3351    )]
3352    #[fuchsia::test]
3353    async fn test_supported_wrapping_key_ids(key: fxfs_crypto::EncryptionKey) {
3354        use fxfs::object_store::transaction::{LockKey, Mutation, Options, lock_keys};
3355        use fxfs::object_store::{FSCRYPT_KEY_ID, ObjectKey, ObjectValue};
3356
3357        let fixture = TestFixture::new().await;
3358        let root = fixture.root();
3359
3360        let file = open_file_checked(
3361            &root,
3362            "key_test_file",
3363            fio::Flags::FLAG_MAYBE_CREATE
3364                | fio::Flags::PROTOCOL_FILE
3365                | fio::PERM_READABLE
3366                | fio::PERM_WRITABLE,
3367            &fio::Options::default(),
3368        )
3369        .await;
3370
3371        let (_, immutable_attributes) = file
3372            .get_attributes(fio::NodeAttributesQuery::ID)
3373            .await
3374            .expect("get_attributes wire call failed")
3375            .map_err(zx::Status::err_from_raw)
3376            .expect("get_attributes failed");
3377        let file_id = immutable_attributes.id.unwrap();
3378
3379        let store = fixture.volume().volume().store();
3380        let mut transaction = store
3381            .new_transaction(
3382                lock_keys![LockKey::object(store.store_object_id(), file_id)],
3383                Options::default(),
3384            )
3385            .await
3386            .expect("new_transaction failed");
3387
3388        transaction.add(
3389            store.store_object_id(),
3390            Mutation::replace_or_insert_object(
3391                ObjectKey::keys(file_id),
3392                ObjectValue::Keys(vec![(FSCRYPT_KEY_ID, key)].into()),
3393            ),
3394        );
3395        transaction.commit().await.expect("commit failed");
3396
3397        let (mutable_attributes, _) = file
3398            .get_attributes(fio::NodeAttributesQuery::WRAPPING_KEY_ID)
3399            .await
3400            .expect("get_attributes wire call failed")
3401            .map_err(zx::Status::err_from_raw)
3402            .expect("get_attributes failed");
3403
3404        assert_eq!(mutable_attributes.wrapping_key_id, Some(WRAPPING_KEY_ID));
3405
3406        fixture.close().await;
3407    }
3408
3409    #[fuchsia::test(threads = 3)]
3410    async fn test_teardown_with_pending_shrink_and_commit_failure() {
3411        use fxfs::errors::FxfsError;
3412
3413        let fail = Arc::new(AtomicBool::new(false));
3414        let fail_clone = fail.clone();
3415        let (mut hooks, fs_hooks) = fxfs::hooks::Hooks::new();
3416        hooks.set_pre_commit(move |_| {
3417            if fail_clone.load(std::sync::atomic::Ordering::Relaxed) {
3418                Err(FxfsError::Unavailable.into())
3419            } else {
3420                Ok(())
3421            }
3422        });
3423        let fixture = TestFixture::open(
3424            DeviceHolder::new(FakeDevice::new(16384, 512)),
3425            TestFixtureOptions { encrypted: false, hooks: Some(fs_hooks), ..Default::default() },
3426        )
3427        .await;
3428
3429        let root = fixture.root();
3430        let file = open_file_checked(
3431            &root,
3432            "test_file",
3433            fio::Flags::FLAG_MAYBE_CREATE
3434                | fio::Flags::PROTOCOL_FILE
3435                | fio::PERM_READABLE
3436                | fio::PERM_WRITABLE,
3437            &fio::Options::default(),
3438        )
3439        .await;
3440
3441        // Grow the file to 4 pages and sync so the on-disk size is 4 pages.
3442        let page_size = page_size();
3443        file.resize(page_size * 4).await.unwrap().expect("resize failed");
3444        file.sync().await.unwrap().expect("sync failed");
3445
3446        // Now cause commits to fail. All changes will be pending on the handle.
3447        fail.store(true, std::sync::atomic::Ordering::Relaxed);
3448
3449        // Dirty the first 2 pages.
3450        let stream = file.describe().await.unwrap().stream.unwrap();
3451        unblock(move || {
3452            for i in 0..2 {
3453                stream
3454                    .write_at(zx::StreamWriteOptions::empty(), i * page_size, &[1u8])
3455                    .expect("write_at failed");
3456            }
3457        })
3458        .await;
3459
3460        // Shrink the file to 2 pages so that a pending shrink is recorded on the handle.
3461        file.resize(page_size * 2).await.unwrap().expect("resize failed");
3462
3463        // Close the client-side file connection and drop object reference so only the
3464        // volume's IS_DIRTY raw Arc keeps the file alive.
3465        drop(file);
3466
3467        // Closing the fixture terminates the volume, which attempts a LastChance flush on all files.
3468        // When that fails, we want to make sure that all the dirty pages still get returned to the
3469        // dirty page tracking and allocator. There is an assert in the `PagedObjectHandle::drop`
3470        // that will otherwise trigger.
3471        fixture.close().await;
3472    }
3473}