Skip to main content

starnix_core/fs/fuchsia/
remote.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::fs::fuchsia::RemoteUnixDomainSocket;
6use crate::fs::fuchsia::remote_volume::RemoteVolume;
7use crate::fs::fuchsia::sync_file::{SyncFence, SyncFile, SyncPoint, Timeline};
8use crate::mm::memory::MemoryObject;
9use crate::mm::{ProtectionFlags, VMEX_RESOURCE};
10use crate::security;
11use crate::task::{CurrentTask, Kernel};
12use crate::vfs::buffers::{InputBuffer, OutputBuffer, with_iovec_segments};
13use crate::vfs::file_server::serve_file_tagged;
14use crate::vfs::fsverity::FsVerityState;
15use crate::vfs::socket::{Socket, SocketFile, ZxioBackedSocket};
16use crate::vfs::{
17    Anon, AppendLockWriteGuard, CacheMode, DEFAULT_BYTES_PER_BLOCK, DirectoryEntryType, DirentSink,
18    FallocMode, FileHandle, FileObject, FileOps, FileSystem, FileSystemHandle, FileSystemOps,
19    FileSystemOptions, FsNode, FsNodeFlags, FsNodeHandle, FsNodeInfo, FsNodeOps, FsStr, FsString,
20    LookupVec, RenameContext, SeekTarget, SymlinkTarget, XattrOp, XattrStorage, default_ioctl,
21    default_seek, fileops_impl_directory, fileops_impl_nonseekable, fileops_impl_noop_sync,
22    fileops_impl_seekable, fs_node_impl_not_dir, fs_node_impl_symlink, fs_node_impl_xattr_delegate,
23};
24use bstr::ByteSlice;
25use fidl::endpoints::DiscoverableProtocolMarker as _;
26use fidl_fuchsia_io as fio;
27use fidl_fuchsia_starnix_binder as fbinder;
28use fidl_fuchsia_unknown as funknown;
29use fuchsia_runtime::UtcInstant;
30use linux_uapi::SYNC_IOC_MAGIC;
31use once_cell::sync::OnceCell;
32use smallvec::{SmallVec, smallvec};
33use starnix_crypt::EncryptionKeyId;
34use starnix_logging::{CATEGORY_STARNIX_MM, impossible_error, log_warn};
35use starnix_sync::{
36    DynamicLockDepRwLock, FileOpsCore, FuchsiaRemoteTargetLock, LockDepReadGuard, LockDepRwLock,
37    LockDepWriteGuard, LockEqualOrBefore, Locked, Unlocked,
38};
39use starnix_syscalls::{SyscallArg, SyscallResult};
40use starnix_types::vfs::default_statfs;
41use starnix_uapi::auth::{Credentials, FsCred};
42use starnix_uapi::device_id::DeviceId;
43use starnix_uapi::errors::Errno;
44use starnix_uapi::file_mode::FileMode;
45use starnix_uapi::mount_flags::FileSystemFlags;
46use starnix_uapi::open_flags::OpenFlags;
47use starnix_uapi::{
48    __kernel_fsid_t, errno, error, from_status_like_fdio, fsverity_descriptor, mode, off_t, statfs,
49};
50use std::ops::ControlFlow;
51use std::sync::atomic::{AtomicU32, Ordering};
52use std::sync::{Arc, LazyLock};
53use sync_io_client::{RemoteIo, create_with_on_representation};
54use syncio::zxio::{
55    ZXIO_NODE_PROTOCOL_DIRECTORY, ZXIO_NODE_PROTOCOL_SYMLINK, ZXIO_OBJECT_TYPE_DATAGRAM_SOCKET,
56    ZXIO_OBJECT_TYPE_NONE, ZXIO_OBJECT_TYPE_PACKET_SOCKET, ZXIO_OBJECT_TYPE_RAW_SOCKET,
57    ZXIO_OBJECT_TYPE_STREAM_SOCKET, ZXIO_OBJECT_TYPE_SYNCHRONOUS_DATAGRAM_SOCKET, zxio_node_attr,
58};
59use syncio::{
60    AllocateMode, XattrSetMode, Zxio, zxio_fsverity_descriptor_t, zxio_node_attr_has_t,
61    zxio_node_attributes_t,
62};
63use zx::Counter;
64
65fn is_special(file_info: &fio::FileInfo) -> bool {
66    matches!(
67        file_info,
68        fio::FileInfo {
69            attributes:
70                Some(fio::NodeAttributes2 {
71                    mutable_attributes: fio::MutableNodeAttributes { mode: Some(mode), .. },
72                    ..
73                }),
74            ..
75        } if {
76            let mode = FileMode::from_bits(*mode);
77            mode.is_chr() || mode.is_blk() || mode.is_fifo() || mode.is_sock()
78        }
79    )
80}
81
82pub fn new_remote_fs(
83    locked: &mut Locked<Unlocked>,
84    current_task: &CurrentTask,
85    options: FileSystemOptions,
86) -> Result<FileSystemHandle, Errno> {
87    let kernel = current_task.kernel();
88    let requested_path = std::str::from_utf8(&options.source)
89        .map_err(|_| errno!(EINVAL, "source path is not utf8"))?;
90    let mut create_flags =
91        fio::PERM_READABLE | fio::Flags::FLAG_MAYBE_CREATE | fio::Flags::PROTOCOL_DIRECTORY;
92    if !options.flags.load(Ordering::Relaxed).contains(FileSystemFlags::RDONLY) {
93        create_flags |= fio::PERM_WRITABLE;
94    }
95    let (root_proxy, subdir) = kernel.open_ns_dir(requested_path, create_flags)?;
96
97    let subdir = if subdir.is_empty() { ".".to_string() } else { subdir };
98    let mut open_rights = fio::PERM_READABLE;
99    if !options.flags.load(Ordering::Relaxed).contains(FileSystemFlags::RDONLY) {
100        open_rights |= fio::PERM_WRITABLE;
101    }
102    let mut subdir_options = options;
103    subdir_options.source = subdir.into();
104    new_remotefs_in_root(locked, kernel, &root_proxy, subdir_options, open_rights)
105}
106
107/// Create a filesystem to access the content of the fuchsia directory available
108/// at `options.source` inside `root`.
109pub fn new_remotefs_in_root<L>(
110    locked: &mut Locked<L>,
111    kernel: &Kernel,
112    root: &fio::DirectorySynchronousProxy,
113    options: FileSystemOptions,
114    rights: fio::Flags,
115) -> Result<FileSystemHandle, Errno>
116where
117    L: LockEqualOrBefore<FileOpsCore>,
118{
119    let root = syncio::directory_open_directory_async(
120        root,
121        std::str::from_utf8(&options.source)
122            .map_err(|_| errno!(EINVAL, "source path is not utf8"))?,
123        rights,
124    )
125    .map_err(|e| errno!(EIO, format!("Failed to open root: {e}")))?;
126    RemoteFs::new_fs(locked, kernel, root.into_channel(), options, rights)
127}
128
129pub struct RemoteFs {
130    // If true, trust the remote file system's IDs (which requires that the remote file system does
131    // not span mounts).  This must be true to properly support hard links.  If this is false, the
132    // same node can end up having different IDs as it leaves and reenters the node cache.
133    // TODO(https://fxbug.dev/42081972): At the time of writing, package directories do not have
134    // unique IDs so this *must* be false in that case.
135    use_remote_ids: bool,
136
137    root_proxy: fio::DirectorySynchronousProxy,
138
139    // The rights used for the root node.
140    root_rights: fio::Flags,
141}
142
143impl RemoteFs {
144    /// Returns a reference to a RemoteFs given a reference to a FileSystem.
145    ///
146    /// # Panics
147    ///
148    /// This will panic if `fs`'s ops aren't `RemoteFs`, so this should only be called when this is
149    /// known to be the case.
150    fn from_fs(fs: &FileSystem) -> &RemoteFs {
151        if let Some(remote_vol) = fs.downcast_ops::<RemoteVolume>() {
152            remote_vol.remotefs()
153        } else {
154            fs.downcast_ops::<RemoteFs>().unwrap()
155        }
156    }
157}
158
159const REMOTE_FS_MAGIC: u32 = u32::from_be_bytes(*b"f.io");
160const DYNAMIC_FS_BYTES_PER_INODE_FALLBACK: u64 = 16384; // 16 KiB
161const SYNC_IOC_FILE_INFO: u8 = 4;
162const SYNC_IOC_MERGE: u8 = 3;
163
164impl FileSystemOps for RemoteFs {
165    fn statfs(
166        &self,
167        _locked: &mut Locked<FileOpsCore>,
168        _fs: &FileSystem,
169        _current_task: &CurrentTask,
170    ) -> Result<statfs, Errno> {
171        let (status, info) = self
172            .root_proxy
173            .query_filesystem(zx::MonotonicInstant::INFINITE)
174            .map_err(|_| errno!(EIO))?;
175        // Not all remote filesystems support `QueryFilesystem`, many return ZX_ERR_NOT_SUPPORTED.
176        if status == 0 {
177            if let Some(info) = info {
178                let (total_blocks, free_blocks) = if info.block_size > 0 {
179                    (
180                        (info.total_bytes / u64::from(info.block_size))
181                            .try_into()
182                            .unwrap_or(i64::MAX),
183                        ((info.total_bytes.saturating_sub(info.used_bytes))
184                            / u64::from(info.block_size))
185                        .try_into()
186                        .unwrap_or(i64::MAX),
187                    )
188                } else {
189                    (0, 0)
190                };
191
192                let total_nodes = std::cmp::min(
193                    info.total_nodes,
194                    info.total_bytes / DYNAMIC_FS_BYTES_PER_INODE_FALLBACK,
195                );
196                let free_nodes = total_nodes.saturating_sub(info.used_nodes);
197
198                let fsid = __kernel_fsid_t {
199                    val: [
200                        (info.fs_id & 0xffffffff) as i32,
201                        ((info.fs_id >> 32) & 0xffffffff) as i32,
202                    ],
203                };
204
205                return Ok(statfs {
206                    f_type: info.fs_type as i64,
207                    f_bsize: info.block_size.into(),
208                    f_blocks: total_blocks,
209                    f_bfree: free_blocks,
210                    f_bavail: free_blocks,
211                    f_files: total_nodes.try_into().unwrap_or(i64::MAX),
212                    f_ffree: free_nodes.try_into().unwrap_or(i64::MAX),
213                    f_fsid: fsid,
214                    f_namelen: info.max_filename_size.try_into().unwrap_or(0),
215                    f_frsize: info.block_size.into(),
216                    ..statfs::default()
217                });
218            }
219        }
220        Ok(default_statfs(REMOTE_FS_MAGIC))
221    }
222
223    fn name(&self) -> &'static FsStr {
224        "remotefs".into()
225    }
226
227    fn uses_external_node_ids(&self) -> bool {
228        self.use_remote_ids
229    }
230
231    fn rename(
232        &self,
233        _locked: &mut Locked<FileOpsCore>,
234        _fs: &FileSystem,
235        current_task: &CurrentTask,
236        context: &mut RenameContext<'_>,
237        old_name: &FsStr,
238        new_name: &FsStr,
239    ) -> Result<(), Errno> {
240        let renamed = &context.renamed.node;
241        let replaced = context.replaced.map(|r| &r.node);
242        let old_parent = &context.old_parent().node;
243        let new_parent = &context.new_parent().node;
244        let old_parent_info = context.old_parent_info();
245        let new_parent_info = context.new_parent_info();
246        // Renames should fail if the src or target directory is
247        // encrypted and locked.
248        old_parent.fail_if_locked(current_task, old_parent_info)?;
249        if let Some(info) = new_parent_info {
250            new_parent.fail_if_locked(current_task, info)?;
251        }
252
253        let Some((old_parent_ops, new_parent_ops)) =
254            old_parent.downcast_ops::<RemoteNode>().zip(new_parent.downcast_ops::<RemoteNode>())
255        else {
256            return error!(EXDEV);
257        };
258
259        let mut nodes: SmallVec<[&FsNode; 4]> =
260            smallvec![&***old_parent, &***new_parent, &***renamed];
261        if let Some(r) = replaced {
262            nodes.push(r);
263        }
264
265        will_dirty(&nodes, || {
266            old_parent_ops
267                .node
268                .io
269                .rename(get_name_str(old_name)?, &new_parent_ops.node.io, get_name_str(new_name)?)
270                .map_err(map_sync_io_client_error)
271        })
272    }
273
274    fn sync(
275        &self,
276        _locked: &mut Locked<FileOpsCore>,
277        _fs: &FileSystem,
278        _current_task: &CurrentTask,
279    ) -> Result<(), Errno> {
280        self.root_proxy
281            .sync(zx::MonotonicInstant::INFINITE)
282            .map_err(|_| errno!(EIO))?
283            .map_err(|status| map_sync_error(zx::Status::from_raw(status)))
284    }
285
286    fn manages_timestamps(&self) -> bool {
287        true
288    }
289
290    fn update_flags(
291        &self,
292        fs: &FileSystem,
293        _current_task: &CurrentTask,
294        mut flags: FileSystemFlags,
295    ) -> Result<(), Errno> {
296        if !self.root_rights.contains(fio::PERM_WRITABLE) {
297            flags |= FileSystemFlags::RDONLY;
298        }
299        fs.options.flags.store(flags, Ordering::Relaxed);
300        Ok(())
301    }
302}
303
304/// Factory is a helper that creates the appropriate node type when creating a node.  See
305/// LookupFactory below for a helper that is specialised for the lookup case.  All the functions
306/// will create nodes that are initially dirty which is intentional because not all attributes are
307/// fetched when creating nodes.
308struct Factory<'a> {
309    node_info: &'a mut FsNodeInfo,
310    assume_special: bool,
311}
312
313impl<'a> sync_io_client::Factory for Factory<'a> {
314    type Result = (Box<dyn FsNodeOps>, u64);
315
316    fn create_node(self, io: RemoteIo, info: fio::NodeInfo) -> Self::Result {
317        let attrs = get_attributes(&info.attributes);
318        let id = attrs.immutable_attributes.id.unwrap_or(fio::INO_UNKNOWN);
319        update_info_from_fidl(
320            self.node_info,
321            &attrs.mutable_attributes,
322            &attrs.immutable_attributes,
323        );
324        (Box::new(RemoteNode::new(io, true)), id)
325    }
326
327    fn create_directory(self, io: RemoteIo, info: fio::DirectoryInfo) -> Self::Result {
328        let attrs = get_attributes(&info.attributes);
329        let id = attrs.immutable_attributes.id.unwrap_or(fio::INO_UNKNOWN);
330        update_info_from_fidl(
331            self.node_info,
332            &attrs.mutable_attributes,
333            &attrs.immutable_attributes,
334        );
335        (Box::new(RemoteNode::new(io, true)), id)
336    }
337
338    fn create_file(self, io: RemoteIo, info: fio::FileInfo) -> Self::Result {
339        let is_special_node = self.assume_special || is_special(&info);
340        let attrs = get_attributes(&info.attributes);
341        let id = attrs.immutable_attributes.id.unwrap_or(fio::INO_UNKNOWN);
342        let ops: Box<dyn FsNodeOps> = if is_special_node {
343            Box::new(RemoteSpecialNode { node: BaseNode::new(io, true) })
344        } else {
345            Box::new(RemoteNode::new(io, true))
346        };
347        update_info_from_fidl(
348            self.node_info,
349            &attrs.mutable_attributes,
350            &attrs.immutable_attributes,
351        );
352        (ops, id)
353    }
354
355    fn create_symlink(self, io: RemoteIo, info: fio::SymlinkInfo) -> Self::Result {
356        let attrs = get_attributes(&info.attributes);
357        let id = attrs.immutable_attributes.id.unwrap_or(fio::INO_UNKNOWN);
358        let target = info.target.unwrap_or_default();
359        update_info_from_fidl(
360            self.node_info,
361            &attrs.mutable_attributes,
362            &attrs.immutable_attributes,
363        );
364        (Box::new(RemoteSymlink::new(BaseNode::new(io, true), target)), id)
365    }
366}
367
368/// LookupFactory is an optimised version of Factory which is used only for lookup.  All the
369/// functions will create nodes that are initially clean, which works because lookup always requests
370/// all attributes.
371struct LookupFactory<'a> {
372    fs: &'a FileSystemHandle,
373    current_task: &'a CurrentTask,
374}
375
376impl<'a> LookupFactory<'a> {
377    fn get_node(
378        &self,
379        io: RemoteIo,
380        attributes: &fio::NodeAttributes2,
381        create_ops: impl FnOnce(RemoteIo) -> Box<dyn FsNodeOps>,
382    ) -> Result<FsNodeHandle, Errno> {
383        let fs = self.fs;
384        let fs_ops = RemoteFs::from_fs(fs);
385        let fio::NodeAttributes2 { mutable_attributes: mutable, immutable_attributes: immutable } =
386            attributes;
387
388        let id = immutable.id.unwrap_or(fio::INO_UNKNOWN);
389        let node_id = if fs_ops.use_remote_ids {
390            if id == fio::INO_UNKNOWN {
391                return error!(ENOTSUP);
392            }
393            id
394        } else {
395            fs.allocate_ino()
396        };
397
398        let node = fs.get_or_create_node(node_id, || {
399            let uid = mutable.uid.unwrap_or(0);
400            let gid = mutable.gid.unwrap_or(0);
401            let owner = FsCred { uid, gid };
402            let rdev = DeviceId::from_bits(mutable.rdev.unwrap_or(0));
403            let fsverity_enabled = immutable.verity_enabled.unwrap_or(false);
404            let protocols = immutable.protocols.unwrap_or(fio::NodeProtocolKinds::empty());
405            // fsverity should not be enabled for non-file nodes.
406            if fsverity_enabled && !protocols.contains(fio::NodeProtocolKinds::FILE) {
407                return error!(EINVAL);
408            }
409
410            let ops = create_ops(io);
411            let child = FsNode::new_uncached(
412                node_id,
413                ops,
414                fs,
415                FsNodeInfo {
416                    rdev,
417                    ..FsNodeInfo::new(
418                        get_mode_from_fidl(mutable, immutable, fs_ops.root_rights),
419                        owner,
420                    )
421                },
422                FsNodeFlags::empty(),
423            );
424            if fsverity_enabled {
425                *child.fsverity.lock() = FsVerityState::FsVerity;
426            }
427            // This is valid to fail if we're using mount point labelling or the provided context
428            // string is invalid.
429            if let Some(fio::SelinuxContext::Data(data)) = mutable.selinux_context.as_ref() {
430                let _ = security::fs_node_notify_security_context(
431                    self.current_task,
432                    &child,
433                    FsStr::new(data),
434                );
435            }
436            Ok(child)
437        })?;
438
439        node.update_info(|info| update_info_from_fidl(info, mutable, immutable));
440
441        Ok(node)
442    }
443}
444
445impl<'a> sync_io_client::Factory for LookupFactory<'a> {
446    type Result = Result<FsNodeHandle, Errno>;
447
448    fn create_node(self, io: RemoteIo, info: fio::NodeInfo) -> Self::Result {
449        self.get_node(io, get_attributes(&info.attributes), |io| {
450            Box::new(RemoteNode::new(io, false))
451        })
452    }
453
454    fn create_directory(self, io: RemoteIo, info: fio::DirectoryInfo) -> Self::Result {
455        self.get_node(io, get_attributes(&info.attributes), |io| {
456            Box::new(RemoteNode::new(io, false))
457        })
458    }
459
460    fn create_file(self, io: RemoteIo, info: fio::FileInfo) -> Self::Result {
461        let is_special_node = is_special(&info);
462        self.get_node(io, get_attributes(&info.attributes), |io| {
463            if is_special_node {
464                Box::new(RemoteSpecialNode { node: BaseNode::new(io, false) })
465            } else {
466                Box::new(RemoteNode::new(io, false))
467            }
468        })
469    }
470
471    fn create_symlink(self, io: RemoteIo, mut info: fio::SymlinkInfo) -> Self::Result {
472        let mut target = info.target.take();
473        if target.is_none() {
474            return error!(EIO);
475        }
476        let node = self.get_node(io, get_attributes(&info.attributes), |io| {
477            Box::new(RemoteSymlink::new(BaseNode::new(io, false), target.take().unwrap()))
478        })?;
479        // Encrypted symlinks that use fscrypt can be read as encrypted links when no key is
480        // available.  When no key is available, directories will not cache their entries.  When,
481        // the key is subsequently provided, the next time the symlink is read, we will come through
482        // here, but since the node is cached, `get_or_create_node` will not create a new node
483        // which, if we were to do nothing, would mean we'd keep the encrypted value for the target.
484        // To address this, if no new node was created, we update the target of the existing node
485        // here.  Once the key has been provided, the entry will be cached with the directory and
486        // whilst the entry remains cached, `lookup` will not be called.
487        if let Some(target) = target
488            && let Some(symlink) = node.downcast_ops::<RemoteSymlink>()
489        {
490            *symlink.target.write() = target.into_boxed_slice();
491        }
492        Ok(node)
493    }
494}
495
496// A helper that makes it easy to deal with the rare case where no FIDL attributes are returned.
497fn get_attributes(attrs: &Option<fio::NodeAttributes2>) -> &fio::NodeAttributes2 {
498    static DEFAULT_NODE_ATTRIBUTES: LazyLock<fio::NodeAttributes2> =
499        LazyLock::new(|| fio::NodeAttributes2 {
500            mutable_attributes: Default::default(),
501            immutable_attributes: Default::default(),
502        });
503    attrs.as_ref().unwrap_or_else(|| &*DEFAULT_NODE_ATTRIBUTES)
504}
505
506impl RemoteFs {
507    pub(super) fn new(
508        root: zx::Channel,
509        root_rights: fio::Flags,
510    ) -> Result<(RemoteFs, Box<dyn FsNodeOps>, FsNodeInfo, u64), Errno> {
511        let (client_end, server_end) = zx::Channel::create();
512        let root_proxy = fio::DirectorySynchronousProxy::new(root);
513        root_proxy
514            .open(
515                ".",
516                fio::Flags::PROTOCOL_DIRECTORY
517                    | fio::PERM_READABLE
518                    | fio::Flags::PERM_INHERIT_WRITE
519                    | fio::Flags::PERM_INHERIT_EXECUTE
520                    | fio::Flags::FLAG_SEND_REPRESENTATION,
521                &fio::Options {
522                    attributes: Some(
523                        fio::NodeAttributesQuery::ID | fio::NodeAttributesQuery::WRAPPING_KEY_ID,
524                    ),
525                    ..Default::default()
526                },
527                server_end,
528            )
529            .map_err(|_| errno!(EIO))?;
530
531        // Use remote IDs if the filesystem is Fxfs which we know will give us unique IDs.  Hard
532        // links need to resolve to the same underlying FsNode, so we can only support hard links if
533        // the remote file system will give us unique IDs.  The IDs are also used as the key in
534        // caches, so we can't use remote IDs if the remote filesystem is not guaranteed to provide
535        // unique IDs, or if the remote filesystem spans multiple filesystems.
536        let (status, info) =
537            root_proxy.query_filesystem(zx::MonotonicInstant::INFINITE).map_err(|_| errno!(EIO))?;
538
539        // Be tolerant of errors here; many filesystems return `ZX_ERR_NOT_SUPPORTED`.
540        let use_remote_ids = status == 0
541            && info
542                .map(|i| i.fs_type == fidl_fuchsia_fs::VfsType::Fxfs.into_primitive())
543                .unwrap_or(false);
544
545        // The OnRepresentation response will return an initial set of `attrs`.
546        let mut node_info = FsNodeInfo::new(mode!(IFDIR, 0o777), FsCred::root());
547        let (remote_node, node_id) = create_with_on_representation(
548            client_end.into(),
549            Factory { node_info: &mut node_info, assume_special: false },
550        )
551        .map_err(map_sync_io_client_error)?;
552
553        Ok((RemoteFs { use_remote_ids, root_proxy, root_rights }, remote_node, node_info, node_id))
554    }
555
556    pub fn new_fs<L>(
557        locked: &mut Locked<L>,
558        kernel: &Kernel,
559        root: zx::Channel,
560        options: FileSystemOptions,
561        rights: fio::Flags,
562    ) -> Result<FileSystemHandle, Errno>
563    where
564        L: LockEqualOrBefore<FileOpsCore>,
565    {
566        let (remotefs, root_node, info, node_id) = RemoteFs::new(root, rights)?;
567
568        if !rights.contains(fio::PERM_WRITABLE) {
569            options.flags.fetch_or(FileSystemFlags::RDONLY, Ordering::Relaxed);
570        }
571        let use_remote_ids = remotefs.use_remote_ids;
572        let fs = FileSystem::new(
573            locked,
574            kernel,
575            CacheMode::Cached(kernel.fs_cache_config()),
576            remotefs,
577            options,
578        )?;
579
580        let node_id = if use_remote_ids { node_id } else { fs.allocate_ino() };
581        fs.create_root_with_info(node_id, root_node, info);
582
583        Ok(fs)
584    }
585
586    pub(super) fn use_remote_ids(&self) -> bool {
587        self.use_remote_ids
588    }
589}
590
591/// All nodes compose `BaseNode`.
592///
593/// NOTE: If new node types are created, the `TryFrom` implementation needs updating below.
594struct BaseNode {
595    /// The underlying I/O object for this remote node.
596    io: RemoteIo,
597
598    /// The number of active dirty operations on this node and whether the node info is in sync.
599    /// See the `will_dirty` function for semantics.
600    info_state: InfoState,
601}
602
603impl BaseNode {
604    fn new(io: RemoteIo, dirty: bool) -> Self {
605        Self { io, info_state: InfoState::new(dirty) }
606    }
607
608    fn fetch_and_refresh_info<'a>(
609        &self,
610        info: &'a DynamicLockDepRwLock<FsNodeInfo>,
611    ) -> Result<LockDepReadGuard<'a, FsNodeInfo>, Errno> {
612        self.info_state.maybe_refresh(
613            info,
614            |info| {
615                let mut query = NODE_INFO_ATTRIBUTES;
616                if info.read().pending_time_access_update {
617                    query |= fio::NodeAttributesQuery::PENDING_ACCESS_TIME_UPDATE;
618                }
619                let (mutable, immutable) =
620                    self.io.attr_get(query).map_err(map_sync_io_client_error)?;
621                let mut info = info.write();
622                info.pending_time_access_update = false;
623                update_info_from_fidl(&mut info, &mutable, &immutable);
624                Ok(LockDepWriteGuard::downgrade(info))
625            },
626            |info| Ok(info.read()),
627        )
628    }
629
630    fn update_attributes(&self, info: &FsNodeInfo, has: zxio_node_attr_has_t) -> Result<(), Errno> {
631        // Omit updating creation_time. By definition, there shouldn't be a change in creation_time.
632        will_dirty(&[self], || {
633            let res = self.io.attr_set(fio::MutableNodeAttributes {
634                modification_time: has
635                    .modification_time
636                    .then_some(info.time_modify.into_nanos() as u64),
637                access_time: has.access_time.then_some(info.time_access.into_nanos() as u64),
638                mode: has.mode.then_some(info.mode.bits()),
639                uid: has.uid.then_some(info.uid),
640                gid: has.gid.then_some(info.gid),
641                rdev: has.rdev.then_some(info.rdev.bits()),
642                casefold: has.casefold.then_some(info.casefold),
643                wrapping_key_id: if has.wrapping_key_id { info.wrapping_key_id } else { None },
644                ..Default::default()
645            });
646            res.map_err(|status| from_status_like_fdio!(status))
647        })
648    }
649}
650
651impl<'a> TryFrom<&'a FsNode> for &'a BaseNode {
652    type Error = ();
653    fn try_from(value: &FsNode) -> Result<&BaseNode, ()> {
654        value
655            .downcast_ops::<RemoteNode>()
656            .map(|n| &n.node)
657            .or_else(|| value.downcast_ops::<RemoteSpecialNode>().map(|n| &n.node))
658            .or_else(|| value.downcast_ops::<RemoteSymlink>().map(|n| &n.node))
659            .ok_or(())
660    }
661}
662
663/// This is the most common type of node.  It is used for files and directories.  Symlinks and
664/// special nodes use RemoteSymlink and RemoteSpecialNode respectively.
665struct RemoteNode {
666    node: BaseNode,
667}
668
669impl RemoteNode {
670    fn new(io: RemoteIo, dirty: bool) -> Self {
671        Self { node: BaseNode::new(io, dirty) }
672    }
673}
674
675/// Creates a file handle from a zx::NullableHandle.
676///
677/// The handle must be a channel, socket, vmo or debuglog object.  If the handle is a channel, then
678/// the channel must implement the `fuchsia.unknown/Queryable` protocol.  Not all protocols are
679/// supported; files and directories are, but symlinks are not.
680///
681/// The resulting object will be owned by root, and will have permissions derived from the `flags`
682/// used to open this object. This is not the same as the permissions set if the object was created
683/// using Starnix itself. We use this mainly for interfacing with objects created outside of Starnix
684/// where these flags represent the desired permissions already.
685pub fn new_remote_file<L>(
686    locked: &mut Locked<L>,
687    current_task: &CurrentTask,
688    handle: zx::NullableHandle,
689    flags: OpenFlags,
690) -> Result<FileHandle, Errno>
691where
692    L: LockEqualOrBefore<FileOpsCore>,
693{
694    let remote_creds = current_task.current_creds().clone();
695    let (attrs, ops) = remote_file_attrs_and_ops(current_task, handle, remote_creds)?;
696    let mut rights = fio::Flags::empty();
697    if flags.can_read() {
698        rights |= fio::PERM_READABLE;
699    }
700    if flags.can_write() {
701        rights |= fio::PERM_WRITABLE;
702    }
703    let mode = get_mode(&attrs, rights);
704    // TODO: https://fxbug.dev/407611229 - Give these nodes valid labels.
705    let mut info = FsNodeInfo::new(mode, FsCred::root());
706    update_info_from_attrs(&mut info, &attrs);
707    Ok(Anon::new_private_file_extended(locked, current_task, ops, flags, "[fuchsia:remote]", info))
708}
709
710/// Creates a FileOps from a zx::NullableHandle.
711///
712/// The handle must satisfy the same requirements as `new_remote_file`.
713pub fn new_remote_file_ops(
714    current_task: &CurrentTask,
715    handle: zx::NullableHandle,
716    creds: Arc<Credentials>,
717) -> Result<Box<dyn FileOps>, Errno> {
718    let (_, ops) = remote_file_attrs_and_ops(current_task, handle, creds)?;
719    Ok(ops)
720}
721
722fn remote_file_attrs_and_ops(
723    current_task: &CurrentTask,
724    mut handle: zx::NullableHandle,
725    remote_creds: Arc<Credentials>,
726) -> Result<(zxio_node_attr, Box<dyn FileOps>), Errno> {
727    let handle_type =
728        handle.basic_info().map_err(|status| from_status_like_fdio!(status))?.object_type;
729
730    if handle_type == zx::ObjectType::CHANNEL {
731        let channel = zx::Channel::from(handle);
732        let queryable = funknown::QueryableSynchronousProxy::new(channel);
733        let protocol = queryable.query(zx::MonotonicInstant::INFINITE).map_err(|_| errno!(EIO))?;
734        const UNIX_DOMAIN_SOCKET_PROTOCOL: &[u8] =
735            fbinder::UnixDomainSocketMarker::PROTOCOL_NAME.as_bytes();
736        const FILE_PROTOCOL: &[u8] = fio::FileMarker::PROTOCOL_NAME.as_bytes();
737        const DIRECTORY_PROTOCOL: &[u8] = fio::DirectoryMarker::PROTOCOL_NAME.as_bytes();
738        match &protocol[..] {
739            UNIX_DOMAIN_SOCKET_PROTOCOL => {
740                let socket_ops =
741                    RemoteUnixDomainSocket::new(queryable.into_channel(), remote_creds)?;
742                let socket = Socket::new_with_ops(Box::new(socket_ops))?;
743                let file_ops = SocketFile::new(socket);
744                let attr = zxio_node_attr {
745                    has: zxio_node_attr_has_t { mode: true, ..zxio_node_attr_has_t::default() },
746                    mode: 0o777 | FileMode::IFSOCK.bits(),
747                    ..zxio_node_attr::default()
748                };
749                return Ok((attr, file_ops));
750            }
751            FILE_PROTOCOL => {
752                let file_proxy = fio::FileSynchronousProxy::from(queryable.into_channel());
753                let info =
754                    file_proxy.describe(zx::MonotonicInstant::INFINITE).map_err(|_| errno!(EIO))?;
755                let io = RemoteIo::with_stream(
756                    file_proxy.into_channel().into(),
757                    info.stream.unwrap_or_else(|| zx::NullableHandle::invalid().into()),
758                );
759                let attr = io
760                    .attr_get_zxio(MODE_ATTRIBUTES | NODE_INFO_ATTRIBUTES)
761                    .map_err(map_sync_io_client_error)?;
762                return Ok((attr, Box::new(AnonymousRemoteFileObject::new(io))));
763            }
764            DIRECTORY_PROTOCOL => {
765                let io = RemoteIo::new(queryable.into_channel().into());
766                let attr = io
767                    .attr_get_zxio(MODE_ATTRIBUTES | NODE_INFO_ATTRIBUTES)
768                    .map_err(map_sync_io_client_error)?;
769                return Ok((
770                    attr,
771                    Box::new(RemoteDirectoryObject::new(io.into_proxy().into_channel().into())),
772                ));
773            }
774            _ => {
775                handle = queryable.into_channel().into_handle();
776                // Fall through for zxio.
777            }
778        }
779    } else if handle_type == zx::ObjectType::COUNTER {
780        let attr = zxio_node_attr::default();
781        let file_ops = Box::new(RemoteCounter::new(handle.into()));
782        return Ok((attr, file_ops));
783    }
784
785    // Otherwise, use zxio based objects.
786
787    // NOTE: If it's a channel, this will repeat the query, which is something we can optimize if we
788    // need to.
789    let zxio = Zxio::create(handle).map_err(|status| from_status_like_fdio!(status))?;
790    let mut attrs = zxio
791        .attr_get(zxio_node_attr_has_t {
792            protocols: true,
793            content_size: true,
794            storage_size: true,
795            link_count: true,
796            object_type: true,
797            ..Default::default()
798        })
799        .map_err(|status| from_status_like_fdio!(status))?;
800    let ops: Box<dyn FileOps> = match (handle_type, attrs.object_type) {
801        (zx::ObjectType::VMO, _) | (zx::ObjectType::DEBUGLOG, _) | (_, ZXIO_OBJECT_TYPE_NONE) => {
802            Box::new(RemoteZxioFileObject::new(zxio))
803        }
804        (zx::ObjectType::SOCKET, _)
805        | (_, ZXIO_OBJECT_TYPE_SYNCHRONOUS_DATAGRAM_SOCKET)
806        | (_, ZXIO_OBJECT_TYPE_DATAGRAM_SOCKET)
807        | (_, ZXIO_OBJECT_TYPE_STREAM_SOCKET)
808        | (_, ZXIO_OBJECT_TYPE_RAW_SOCKET)
809        | (_, ZXIO_OBJECT_TYPE_PACKET_SOCKET) => {
810            let socket_ops = ZxioBackedSocket::new_with_zxio(current_task, zxio);
811            let socket = Socket::new_with_ops(Box::new(socket_ops))?;
812            attrs.has.mode = true;
813            attrs.mode = FileMode::IFSOCK.bits();
814            SocketFile::new(socket)
815        }
816        _ => return error!(ENOTSUP),
817    };
818    Ok((attrs, ops))
819}
820
821pub fn create_fuchsia_pipe<L>(
822    locked: &mut Locked<L>,
823    current_task: &CurrentTask,
824    socket: zx::Socket,
825    flags: OpenFlags,
826) -> Result<FileHandle, Errno>
827where
828    L: LockEqualOrBefore<FileOpsCore>,
829{
830    new_remote_file(locked, current_task, socket.into(), flags)
831}
832
833// This only needs to include attributes that can be out of date.  There are other attributes that
834// we read when we first look up the node (see `lookup`).
835const NODE_INFO_ATTRIBUTES: fio::NodeAttributesQuery = fio::NodeAttributesQuery::CONTENT_SIZE
836    .union(fio::NodeAttributesQuery::STORAGE_SIZE)
837    .union(fio::NodeAttributesQuery::LINK_COUNT)
838    .union(fio::NodeAttributesQuery::MODIFICATION_TIME)
839    .union(fio::NodeAttributesQuery::CHANGE_TIME)
840    .union(fio::NodeAttributesQuery::ACCESS_TIME);
841
842/// Updates info from attrs if they are set.
843///
844// Keep in sync with `NODE_INFO_ATTRIBUTES`.
845pub(super) fn update_info_from_attrs(info: &mut FsNodeInfo, attrs: &zxio_node_attributes_t) {
846    // TODO - store these in FsNodeState and convert on fstat
847    if attrs.has.content_size {
848        info.size = attrs.content_size.try_into().unwrap_or(std::usize::MAX);
849    }
850    if attrs.has.storage_size {
851        info.blocks = usize::try_from(attrs.storage_size)
852            .unwrap_or(std::usize::MAX)
853            .div_ceil(DEFAULT_BYTES_PER_BLOCK)
854    }
855    info.blksize = DEFAULT_BYTES_PER_BLOCK;
856    if attrs.has.link_count {
857        info.link_count = attrs.link_count.try_into().unwrap_or(std::usize::MAX);
858    }
859    if attrs.has.modification_time {
860        info.time_modify =
861            UtcInstant::from_nanos(attrs.modification_time.try_into().unwrap_or(i64::MAX));
862    }
863    if attrs.has.change_time {
864        info.time_status_change =
865            UtcInstant::from_nanos(attrs.change_time.try_into().unwrap_or(i64::MAX));
866    }
867    if attrs.has.access_time {
868        info.time_access = UtcInstant::from_nanos(attrs.access_time.try_into().unwrap_or(i64::MAX));
869    }
870    // The following are only read once so they're not included in `NODE_INFO_ATTRIBUTES`.
871    if attrs.has.casefold {
872        info.casefold = attrs.casefold;
873    }
874    if attrs.has.wrapping_key_id {
875        info.wrapping_key_id = Some(attrs.wrapping_key_id);
876    }
877}
878
879/// Same as `update_info_from_attr` but uses FIDL.
880fn update_info_from_fidl(
881    info: &mut FsNodeInfo,
882    mutable: &fio::MutableNodeAttributes,
883    immutable: &fio::ImmutableNodeAttributes,
884) {
885    if let Some(content_size) = immutable.content_size {
886        info.size = content_size.try_into().unwrap_or(std::usize::MAX);
887    }
888    if let Some(storage_size) = immutable.storage_size {
889        info.blocks = usize::try_from(storage_size)
890            .unwrap_or(std::usize::MAX)
891            .div_ceil(DEFAULT_BYTES_PER_BLOCK);
892    }
893    info.blksize = DEFAULT_BYTES_PER_BLOCK;
894    if let Some(link_count) = immutable.link_count {
895        info.link_count = link_count.try_into().unwrap_or(std::usize::MAX);
896    }
897    if let Some(modification_time) = mutable.modification_time {
898        info.time_modify = UtcInstant::from_nanos(modification_time.try_into().unwrap_or(i64::MAX));
899    }
900    if let Some(change_time) = immutable.change_time {
901        info.time_status_change =
902            UtcInstant::from_nanos(change_time.try_into().unwrap_or(i64::MAX));
903    }
904    if !info.pending_time_access_update
905        && let Some(access_time) = mutable.access_time
906    {
907        info.time_access = UtcInstant::from_nanos(access_time.try_into().unwrap_or(i64::MAX));
908    }
909    // The following are only read once so they're not included in `NODE_INFO_ATTRIBUTES`.
910    if let Some(casefold) = mutable.casefold {
911        info.casefold = casefold;
912    }
913    if let Some(wrapping_key_id) = mutable.wrapping_key_id {
914        info.wrapping_key_id = Some(wrapping_key_id);
915    }
916}
917
918/// The attributes we need to request to compute the right mode.
919const MODE_ATTRIBUTES: fio::NodeAttributesQuery =
920    fio::NodeAttributesQuery::PROTOCOLS.union(fio::NodeAttributesQuery::MODE);
921
922// NOTE: Keep in sync with `MODE_ATTRIBUTES`.
923fn get_mode(attrs: &zxio_node_attributes_t, rights: fio::Flags) -> FileMode {
924    if attrs.protocols & ZXIO_NODE_PROTOCOL_SYMLINK != 0 {
925        // We don't set the mode for symbolic links , so we synthesize it instead.
926        FileMode::IFLNK | FileMode::ALLOW_ALL
927    } else if attrs.has.mode {
928        // If the filesystem supports POSIX mode bits, use that directly.
929        FileMode::from_bits(attrs.mode)
930    } else {
931        // The filesystem doesn't support the `mode` attribute, so synthesize it from the protocols
932        // this node supports, and the rights used to open it.
933        let is_directory =
934            attrs.protocols & ZXIO_NODE_PROTOCOL_DIRECTORY == ZXIO_NODE_PROTOCOL_DIRECTORY;
935        let mode = if is_directory { FileMode::IFDIR } else { FileMode::IFREG };
936        let mut permissions = FileMode::EMPTY;
937        if rights.contains(fio::PERM_READABLE) {
938            permissions |= FileMode::IRUSR;
939        }
940        if rights.contains(fio::PERM_WRITABLE) {
941            permissions |= FileMode::IWUSR;
942        }
943        if rights.contains(fio::PERM_EXECUTABLE) {
944            permissions |= FileMode::IXUSR;
945        }
946        // Make sure the same permissions are granted to user, group, and other.
947        permissions |= FileMode::from_bits((permissions.bits() >> 3) | (permissions.bits() >> 6));
948        mode | permissions
949    }
950}
951
952/// Same as `get_mode` but uses FIDL.
953fn get_mode_from_fidl(
954    mutable: &fio::MutableNodeAttributes,
955    immutable: &fio::ImmutableNodeAttributes,
956    rights: fio::Flags,
957) -> FileMode {
958    let protocols = immutable.protocols.unwrap_or(fio::NodeProtocolKinds::empty());
959    if protocols.contains(fio::NodeProtocolKinds::SYMLINK) {
960        // We don't set the mode for symbolic links , so we synthesize it instead.
961        FileMode::IFLNK | FileMode::ALLOW_ALL
962    } else if let Some(mode) = mutable.mode {
963        // If the filesystem supports POSIX mode bits, use that directly.
964        FileMode::from_bits(mode)
965    } else {
966        // The filesystem doesn't support the `mode` attribute, so synthesize it from the protocols
967        // this node supports, and the rights used to open it.
968        let is_directory = protocols.contains(fio::NodeProtocolKinds::DIRECTORY);
969        let mode = if is_directory { FileMode::IFDIR } else { FileMode::IFREG };
970        let mut permissions = FileMode::EMPTY;
971        if rights.contains(fio::PERM_READABLE) {
972            permissions |= FileMode::IRUSR;
973        }
974        if rights.contains(fio::PERM_WRITABLE) {
975            permissions |= FileMode::IWUSR;
976        }
977        if rights.contains(fio::PERM_EXECUTABLE) {
978            permissions |= FileMode::IXUSR;
979        }
980        // Make sure the same permissions are granted to user, group, and other.
981        permissions |= FileMode::from_bits((permissions.bits() >> 3) | (permissions.bits() >> 6));
982        mode | permissions
983    }
984}
985
986fn get_name_str<'a>(name_bytes: &'a FsStr) -> Result<&'a str, Errno> {
987    std::str::from_utf8(name_bytes.as_ref()).map_err(|_| {
988        log_warn!("bad utf8 in pathname! remote filesystems can't handle this");
989        errno!(EINVAL)
990    })
991}
992
993impl XattrStorage for BaseNode {
994    fn get_xattr(
995        &self,
996        _locked: &mut Locked<FileOpsCore>,
997        name: &FsStr,
998    ) -> Result<FsString, Errno> {
999        Ok(self
1000            .io
1001            .xattr_get(name)
1002            .map_err(|status| match status {
1003                zx::Status::NOT_FOUND => errno!(ENODATA),
1004                status => from_status_like_fdio!(status),
1005            })?
1006            .into())
1007    }
1008
1009    fn set_xattr(
1010        &self,
1011        _locked: &mut Locked<FileOpsCore>,
1012        name: &FsStr,
1013        value: &FsStr,
1014        op: XattrOp,
1015    ) -> Result<(), Errno> {
1016        let mode = match op {
1017            XattrOp::Set => XattrSetMode::Set,
1018            XattrOp::Create => XattrSetMode::Create,
1019            XattrOp::Replace => XattrSetMode::Replace,
1020        };
1021
1022        will_dirty(&[self], || {
1023            self.io.xattr_set(name, value, mode).map_err(|status| match status {
1024                zx::Status::NOT_FOUND => errno!(ENODATA),
1025                status => from_status_like_fdio!(status),
1026            })
1027        })
1028    }
1029
1030    fn remove_xattr(&self, _locked: &mut Locked<FileOpsCore>, name: &FsStr) -> Result<(), Errno> {
1031        will_dirty(&[self], || {
1032            self.io.xattr_remove(name).map_err(|status| match status {
1033                zx::Status::NOT_FOUND => errno!(ENODATA),
1034                _ => from_status_like_fdio!(status),
1035            })
1036        })
1037    }
1038
1039    fn list_xattrs(&self, _locked: &mut Locked<FileOpsCore>) -> Result<Vec<FsString>, Errno> {
1040        self.io
1041            .xattr_list()
1042            .map(|attrs| attrs.into_iter().map(FsString::new).collect::<Vec<_>>())
1043            .map_err(map_sync_io_client_error)
1044    }
1045}
1046
1047impl FsNodeOps for RemoteNode {
1048    fs_node_impl_xattr_delegate!(self, self.node);
1049
1050    fn create_file_ops(
1051        &self,
1052        _locked: &mut Locked<FileOpsCore>,
1053        node: &FsNode,
1054        current_task: &CurrentTask,
1055        flags: OpenFlags,
1056    ) -> Result<Box<dyn FileOps>, Errno> {
1057        {
1058            // It is safe to read the cached node info here because the `wrapping_key_id` is
1059            // fetched when the node is first opened, and updated when set. We don't expect this to
1060            // change out from under Starnix.
1061            let node_info = node.info();
1062            if node_info.mode.is_dir() {
1063                if let Some(wrapping_key_id) = node_info.wrapping_key_id {
1064                    if flags.can_write() {
1065                        // Locked encrypted directories cannot be opened with write access.
1066                        let crypt_service =
1067                            node.fs().crypt_service().ok_or_else(|| errno!(ENOKEY))?;
1068                        if !crypt_service.contains_key(EncryptionKeyId::from(wrapping_key_id)) {
1069                            return error!(ENOKEY);
1070                        }
1071                    }
1072                }
1073                // For directories we need to clone the connection because we rely on the seek
1074                // offset.
1075                return Ok(Box::new(RemoteDirectoryObject::new(
1076                    self.node
1077                        .io
1078                        .clone_proxy()
1079                        .map(|p| p.into_channel().into())
1080                        .map_err(map_sync_io_client_error)?,
1081                )));
1082            }
1083        }
1084
1085        // Locked encrypted files cannot be opened.
1086        node.fail_if_locked(current_task, &node.info())?;
1087
1088        // fsverity files cannot be opened in write mode, including while building.
1089        if flags.can_write() {
1090            node.fsverity.lock().check_writable()?;
1091        }
1092
1093        Ok(Box::new(RemoteFileObject::default()))
1094    }
1095
1096    fn sync(&self, _node: &FsNode, _current_task: &CurrentTask) -> Result<(), Errno> {
1097        self.node.io.sync().map_err(map_sync_io_client_error)
1098    }
1099
1100    fn mknod(
1101        &self,
1102        _locked: &mut Locked<FileOpsCore>,
1103        node: &FsNode,
1104        current_task: &CurrentTask,
1105        name: &FsStr,
1106        mode: FileMode,
1107        dev: DeviceId,
1108        owner: FsCred,
1109    ) -> Result<FsNodeHandle, Errno> {
1110        node.fail_if_locked(current_task, &node.info())?;
1111        let name = get_name_str(name)?;
1112
1113        let fs = node.fs();
1114        let fs_ops = RemoteFs::from_fs(&fs);
1115
1116        if !(mode.is_reg() || mode.is_chr() || mode.is_blk() || mode.is_fifo() || mode.is_sock()) {
1117            return error!(EINVAL, name);
1118        }
1119
1120        let mut node_info = FsNodeInfo { rdev: dev, ..FsNodeInfo::new(mode, owner) };
1121        let (ops, node_id) = will_dirty(&[&self.node], || {
1122            self.node
1123                .io
1124                .open(
1125                    name,
1126                    fio::Flags::FLAG_MUST_CREATE
1127                        | fio::Flags::PROTOCOL_FILE
1128                        | fio::PERM_READABLE
1129                        | fio::PERM_WRITABLE,
1130                    Some(fio::MutableNodeAttributes {
1131                        mode: Some(mode.bits()),
1132                        uid: Some(owner.uid),
1133                        gid: Some(owner.gid),
1134                        rdev: Some(dev.bits()),
1135                        ..Default::default()
1136                    }),
1137                    fio::NodeAttributesQuery::ID | fio::NodeAttributesQuery::WRAPPING_KEY_ID,
1138                    Factory { node_info: &mut node_info, assume_special: !mode.is_reg() },
1139                )
1140                .map_err(|status| from_status_like_fdio!(status, name))
1141        })?;
1142
1143        let node_id = if fs_ops.use_remote_ids { node_id } else { fs.allocate_ino() };
1144
1145        let child = fs.create_node(node_id, ops, node_info);
1146        Ok(child)
1147    }
1148
1149    fn mkdir(
1150        &self,
1151        _locked: &mut Locked<FileOpsCore>,
1152        node: &FsNode,
1153        current_task: &CurrentTask,
1154        name: &FsStr,
1155        mode: FileMode,
1156        owner: FsCred,
1157    ) -> Result<FsNodeHandle, Errno> {
1158        node.fail_if_locked(current_task, &node.info())?;
1159        let name = get_name_str(name)?;
1160
1161        let fs = node.fs();
1162        let fs_ops = RemoteFs::from_fs(&fs);
1163
1164        let mut node_info = FsNodeInfo::new(mode, owner);
1165        let (ops, node_id) = will_dirty(&[&self.node], || {
1166            self.node
1167                .io
1168                .open(
1169                    name,
1170                    fio::Flags::FLAG_MUST_CREATE
1171                        | fio::Flags::PROTOCOL_DIRECTORY
1172                        | fio::PERM_READABLE
1173                        | fio::PERM_WRITABLE,
1174                    Some(fio::MutableNodeAttributes {
1175                        mode: Some(mode.bits()),
1176                        uid: Some(owner.uid),
1177                        gid: Some(owner.gid),
1178                        ..Default::default()
1179                    }),
1180                    fio::NodeAttributesQuery::ID | fio::NodeAttributesQuery::WRAPPING_KEY_ID,
1181                    Factory { node_info: &mut node_info, assume_special: false },
1182                )
1183                .map_err(|status| from_status_like_fdio!(status, name))
1184        })?;
1185
1186        let node_id = if fs_ops.use_remote_ids { node_id } else { fs.allocate_ino() };
1187
1188        let child = fs.create_node(node_id, ops, node_info);
1189        Ok(child)
1190    }
1191
1192    fn lookup(
1193        &self,
1194        _locked: &mut Locked<FileOpsCore>,
1195        node: &FsNode,
1196        current_task: &CurrentTask,
1197        name: &FsStr,
1198    ) -> Result<FsNodeHandle, Errno> {
1199        let name = get_name_str(name)?;
1200
1201        let fs = node.fs();
1202        let fs_ops = RemoteFs::from_fs(&fs);
1203
1204        let mut query = MODE_ATTRIBUTES
1205            | NODE_INFO_ATTRIBUTES
1206            | fio::NodeAttributesQuery::ID
1207            | fio::NodeAttributesQuery::UID
1208            | fio::NodeAttributesQuery::GID
1209            | fio::NodeAttributesQuery::RDEV
1210            | fio::NodeAttributesQuery::WRAPPING_KEY_ID
1211            | fio::NodeAttributesQuery::VERITY_ENABLED
1212            | fio::NodeAttributesQuery::CASEFOLD;
1213
1214        if security::fs_is_xattr_labeled(node.fs()) {
1215            query |= fio::NodeAttributesQuery::SELINUX_CONTEXT;
1216        }
1217
1218        self.node
1219            .io
1220            .open(name, fs_ops.root_rights, None, query, LookupFactory { fs: &fs, current_task })
1221            .map_err(|status| from_status_like_fdio!(status, name))?
1222    }
1223
1224    fn has_lookup_pipelined(&self) -> bool {
1225        true
1226    }
1227
1228    fn lookup_pipelined(
1229        &self,
1230        _locked: &mut Locked<FileOpsCore>,
1231        node: &FsNode,
1232        current_task: &CurrentTask,
1233        names: &[&FsStr],
1234    ) -> LookupVec<Result<FsNodeHandle, Errno>> {
1235        let fs = node.fs();
1236        let fs_ops = RemoteFs::from_fs(&fs);
1237
1238        let mut query = MODE_ATTRIBUTES
1239            | NODE_INFO_ATTRIBUTES
1240            | fio::NodeAttributesQuery::ID
1241            | fio::NodeAttributesQuery::UID
1242            | fio::NodeAttributesQuery::GID
1243            | fio::NodeAttributesQuery::RDEV
1244            | fio::NodeAttributesQuery::WRAPPING_KEY_ID
1245            | fio::NodeAttributesQuery::VERITY_ENABLED
1246            | fio::NodeAttributesQuery::CASEFOLD;
1247
1248        if security::fs_is_xattr_labeled(node.fs()) {
1249            query |= fio::NodeAttributesQuery::SELINUX_CONTEXT;
1250        }
1251
1252        let names_str =
1253            match names.iter().map(|n| get_name_str(n)).collect::<Result<LookupVec<_>, Errno>>() {
1254                Ok(names_str) => names_str,
1255                Err(e) => return vec![Err(e)].into(),
1256            };
1257
1258        self.node
1259            .io
1260            .open_pipelined(&names_str, fs_ops.root_rights, query, || LookupFactory {
1261                fs: &fs,
1262                current_task,
1263            })
1264            .map(|r| r.map_err(|status| from_status_like_fdio!(status)).flatten())
1265            .collect()
1266    }
1267
1268    fn truncate(
1269        &self,
1270        _locked: &mut Locked<FileOpsCore>,
1271        _guard: &AppendLockWriteGuard<'_>,
1272        node: &FsNode,
1273        current_task: &CurrentTask,
1274        length: u64,
1275    ) -> Result<(), Errno> {
1276        node.fail_if_locked(current_task, &node.info())?;
1277
1278        let _guard = self.node.info_state.dirty_op_guard(true);
1279
1280        self.node.io.truncate(length).map_err(|status| from_status_like_fdio!(status))
1281    }
1282
1283    fn allocate(
1284        &self,
1285        _locked: &mut Locked<FileOpsCore>,
1286        _guard: &AppendLockWriteGuard<'_>,
1287        node: &FsNode,
1288        current_task: &CurrentTask,
1289        mode: FallocMode,
1290        offset: u64,
1291        length: u64,
1292    ) -> Result<(), Errno> {
1293        match mode {
1294            FallocMode::Allocate { keep_size: false } => {
1295                node.fail_if_locked(current_task, &node.info())?;
1296
1297                will_dirty(&[&self.node], || {
1298                    self.node
1299                        .io
1300                        .allocate(offset, length, AllocateMode::empty())
1301                        .map_err(|status| from_status_like_fdio!(status))
1302                })?;
1303                Ok(())
1304            }
1305            _ => error!(EINVAL),
1306        }
1307    }
1308
1309    fn fetch_and_refresh_info<'a>(
1310        &self,
1311        _locked: &mut Locked<FileOpsCore>,
1312        _node: &FsNode,
1313        _current_task: &CurrentTask,
1314        info: &'a DynamicLockDepRwLock<FsNodeInfo>,
1315    ) -> Result<LockDepReadGuard<'a, FsNodeInfo>, Errno> {
1316        self.node.fetch_and_refresh_info(info)
1317    }
1318
1319    fn update_attributes(
1320        &self,
1321        _locked: &mut Locked<FileOpsCore>,
1322        _node: &FsNode,
1323        _current_task: &CurrentTask,
1324        info: &FsNodeInfo,
1325        has: zxio_node_attr_has_t,
1326    ) -> Result<(), Errno> {
1327        // Attributes of regular remote nodes (files, directory) are valid to update.
1328        // Their metadata is stored and managed by the underlying Fuchsia filesystem.
1329        self.node.update_attributes(info, has)
1330    }
1331
1332    fn unlink(
1333        &self,
1334        _locked: &mut Locked<FileOpsCore>,
1335        node: &FsNode,
1336        _current_task: &CurrentTask,
1337        name: &FsStr,
1338        child: &FsNodeHandle,
1339    ) -> Result<(), Errno> {
1340        // We don't care about the child argument because 1. unlinking already takes the parent's
1341        // children lock, so we don't have to worry about conflicts on this path, and 2. the remote
1342        // filesystem tracks the link counts so we don't need to update them here.
1343        let name = get_name_str(name)?;
1344        will_dirty(&[node, child], || {
1345            self.node
1346                .io
1347                .unlink(name, fio::UnlinkFlags::empty())
1348                .map_err(|status| from_status_like_fdio!(status))
1349        })
1350    }
1351
1352    fn create_symlink(
1353        &self,
1354        _locked: &mut Locked<FileOpsCore>,
1355        node: &FsNode,
1356        current_task: &CurrentTask,
1357        name: &FsStr,
1358        target: &FsStr,
1359        owner: FsCred,
1360    ) -> Result<FsNodeHandle, Errno> {
1361        node.fail_if_locked(current_task, &node.info())?;
1362
1363        let name = get_name_str(name)?;
1364        let io = will_dirty(&[&self.node], || {
1365            self.node
1366                .io
1367                .create_symlink(name, target)
1368                .map_err(|status| from_status_like_fdio!(status))
1369        })?;
1370
1371        let fs = node.fs();
1372        let fs_ops = RemoteFs::from_fs(&fs);
1373
1374        let node_id = if fs_ops.use_remote_ids {
1375            io.attr_get(fio::NodeAttributesQuery::ID)
1376                .map_err(|status| from_status_like_fdio!(status))?
1377                .1
1378                .id
1379                .unwrap_or_default()
1380        } else {
1381            fs.allocate_ino()
1382        };
1383        Ok(fs.create_node(
1384            node_id,
1385            RemoteSymlink::new(BaseNode::new(io, true), target.as_bytes()),
1386            FsNodeInfo {
1387                size: target.len(),
1388                ..FsNodeInfo::new(FileMode::IFLNK | FileMode::ALLOW_ALL, owner)
1389            },
1390        ))
1391    }
1392
1393    fn create_tmpfile(
1394        &self,
1395        node: &FsNode,
1396        _current_task: &CurrentTask,
1397        mode: FileMode,
1398        owner: FsCred,
1399    ) -> Result<FsNodeHandle, Errno> {
1400        let fs = node.fs();
1401        let fs_ops = RemoteFs::from_fs(&fs);
1402
1403        if !mode.is_reg() {
1404            return error!(EINVAL);
1405        }
1406
1407        // `create_tmpfile` is used by O_TMPFILE. Note that
1408        // <https://man7.org/linux/man-pages/man2/open.2.html> states that if O_EXCL is specified
1409        // with O_TMPFILE, the temporary file created cannot be linked into the filesystem. Although
1410        // there exist fuchsia flags `fio::FLAG_TEMPORARY_AS_NOT_LINKABLE`, the starnix vfs already
1411        // handles this case and makes sure that the created file is not linkable. There is also no
1412        // way of passing the open flags to this function.
1413        let mut node_info = FsNodeInfo::new(mode, owner);
1414        let (ops, node_id) = will_dirty(&[&self.node], || {
1415            self.node
1416                .io
1417                .open(
1418                    ".",
1419                    fio::Flags::PROTOCOL_FILE
1420                        | fio::Flags::FLAG_CREATE_AS_UNNAMED_TEMPORARY
1421                        | fio::PERM_READABLE
1422                        | fio::PERM_WRITABLE,
1423                    Some(fio::MutableNodeAttributes {
1424                        mode: Some(mode.bits()),
1425                        uid: Some(owner.uid),
1426                        gid: Some(owner.gid),
1427                        ..Default::default()
1428                    }),
1429                    fio::NodeAttributesQuery::ID,
1430                    Factory { node_info: &mut node_info, assume_special: false },
1431                )
1432                .map_err(|status| from_status_like_fdio!(status))
1433        })?;
1434
1435        let node_id = if fs_ops.use_remote_ids { node_id } else { fs.allocate_ino() };
1436        Ok(fs.create_node(node_id, ops, node_info))
1437    }
1438
1439    fn link(
1440        &self,
1441        _locked: &mut Locked<FileOpsCore>,
1442        node: &FsNode,
1443        _current_task: &CurrentTask,
1444        name: &FsStr,
1445        child: &FsNodeHandle,
1446    ) -> Result<(), Errno> {
1447        if !RemoteFs::from_fs(&node.fs()).use_remote_ids {
1448            return error!(EPERM);
1449        }
1450        let name = get_name_str(name)?;
1451
1452        will_dirty(&[node, child], || {
1453            if let Some(child) = child.downcast_ops::<RemoteNode>() {
1454                child.node.io.link_into(&self.node.io, name).map_err(|status| match status {
1455                    zx::Status::BAD_STATE => errno!(EXDEV),
1456                    zx::Status::ACCESS_DENIED => errno!(ENOKEY),
1457                    s => from_status_like_fdio!(s),
1458                })
1459            } else if let Some(child) = child.downcast_ops::<RemoteSymlink>() {
1460                child.node.io.link_into(&self.node.io, name).map_err(|status| match status {
1461                    zx::Status::BAD_STATE => errno!(EXDEV),
1462                    zx::Status::ACCESS_DENIED => errno!(ENOKEY),
1463                    s => from_status_like_fdio!(s),
1464                })
1465            } else {
1466                error!(EXDEV)
1467            }
1468        })
1469    }
1470
1471    fn forget(
1472        self: Box<Self>,
1473        _locked: &mut Locked<FileOpsCore>,
1474        _current_task: &CurrentTask,
1475        info: FsNodeInfo,
1476    ) -> Result<(), Errno> {
1477        // Before forgetting this node, update atime if we need to.
1478        if info.pending_time_access_update {
1479            self.node
1480                .io
1481                .attr_get(fio::NodeAttributesQuery::PENDING_ACCESS_TIME_UPDATE)
1482                .map_err(|status| from_status_like_fdio!(status))?;
1483        }
1484        Ok(())
1485    }
1486
1487    fn enable_fsverity(
1488        &self,
1489        _locked: &mut Locked<FileOpsCore>,
1490        _node: &FsNode,
1491        _current_task: &CurrentTask,
1492        descriptor: &fsverity_descriptor,
1493    ) -> Result<(), Errno> {
1494        let descr = zxio_fsverity_descriptor_t {
1495            hash_algorithm: descriptor.hash_algorithm,
1496            salt_size: descriptor.salt_size,
1497            salt: descriptor.salt,
1498        };
1499        will_dirty(&[&self.node], || {
1500            self.node.io.enable_verity(&descr).map_err(|status| from_status_like_fdio!(status))
1501        })
1502    }
1503
1504    fn get_fsverity_descriptor(&self, log_blocksize: u8) -> Result<fsverity_descriptor, Errno> {
1505        let (_, attrs) = self
1506            .node
1507            .io
1508            .attr_get(
1509                fio::NodeAttributesQuery::CONTENT_SIZE
1510                    | fio::NodeAttributesQuery::OPTIONS
1511                    | fio::NodeAttributesQuery::ROOT_HASH,
1512            )
1513            .map_err(|status| from_status_like_fdio!(status))?;
1514        let fio::ImmutableNodeAttributes {
1515            content_size: Some(data_size),
1516            options:
1517                Some(fio::VerificationOptions {
1518                    hash_algorithm: Some(hash_algorithm),
1519                    salt: Some(salt),
1520                    ..
1521                }),
1522            root_hash: Some(root_hash),
1523            ..
1524        } = attrs
1525        else {
1526            return error!(ENODATA);
1527        };
1528        let mut descriptor = fsverity_descriptor {
1529            version: 1,
1530            hash_algorithm: hash_algorithm.into_primitive(),
1531            log_blocksize,
1532            __reserved_0x04: 0u32,
1533            data_size,
1534            ..Default::default()
1535        };
1536        if salt.len() > std::mem::size_of_val(&descriptor.salt)
1537            || root_hash.len() > std::mem::size_of_val(&descriptor.root_hash)
1538        {
1539            return error!(EIO);
1540        }
1541        descriptor.salt_size = salt.len() as u8;
1542        descriptor.salt[..salt.len()].copy_from_slice(&salt);
1543        descriptor.root_hash[..root_hash.len()].copy_from_slice(&root_hash);
1544        Ok(descriptor)
1545    }
1546
1547    fn get_size(
1548        &self,
1549        locked: &mut Locked<FileOpsCore>,
1550        node: &FsNode,
1551        current_task: &CurrentTask,
1552    ) -> Result<usize, Errno> {
1553        if self.node.info_state.is_size_accurate() {
1554            node.info()
1555        } else {
1556            node.fetch_and_refresh_info(locked, current_task)?
1557        }
1558        .size
1559        .try_into()
1560        .map_err(|_| errno!(EINVAL))
1561    }
1562}
1563
1564struct RemoteSpecialNode {
1565    node: BaseNode,
1566}
1567
1568impl FsNodeOps for RemoteSpecialNode {
1569    fs_node_impl_not_dir!();
1570    fs_node_impl_xattr_delegate!(self, self.node);
1571
1572    fn create_file_ops(
1573        &self,
1574        _locked: &mut Locked<FileOpsCore>,
1575        _node: &FsNode,
1576        _current_task: &CurrentTask,
1577        _flags: OpenFlags,
1578    ) -> Result<Box<dyn FileOps>, Errno> {
1579        unreachable!("Special nodes cannot be opened.");
1580    }
1581
1582    fn update_attributes(
1583        &self,
1584        _locked: &mut Locked<FileOpsCore>,
1585        _node: &FsNode,
1586        _current_task: &CurrentTask,
1587        info: &FsNodeInfo,
1588        has: zxio_node_attr_has_t,
1589    ) -> Result<(), Errno> {
1590        // Attributes of special remote nodes (sockets, devices, etc.) are valid to update.
1591        // Their metadata is stored and managed by the underlying Fuchsia filesystem.
1592        self.node.update_attributes(info, has)
1593    }
1594}
1595
1596struct RemoteDirectoryObject(sync_io_client::RemoteDirectory);
1597
1598impl RemoteDirectoryObject {
1599    fn new(proxy: fio::DirectorySynchronousProxy) -> Self {
1600        Self(sync_io_client::RemoteDirectory::new(proxy))
1601    }
1602}
1603
1604impl FileOps for RemoteDirectoryObject {
1605    fileops_impl_directory!();
1606
1607    fn seek(
1608        &self,
1609        _locked: &mut Locked<FileOpsCore>,
1610        _file: &FileObject,
1611        _current_task: &CurrentTask,
1612        current_offset: off_t,
1613        target: SeekTarget,
1614    ) -> Result<off_t, Errno> {
1615        Ok(self
1616            .0
1617            .seek(default_seek(current_offset, target, || error!(EINVAL))? as u64)
1618            .map_err(map_sync_io_client_error)? as i64)
1619    }
1620
1621    fn readdir(
1622        &self,
1623        _locked: &mut Locked<FileOpsCore>,
1624        file: &FileObject,
1625        _current_task: &CurrentTask,
1626        sink: &mut dyn DirentSink,
1627    ) -> Result<(), Errno> {
1628        match self
1629            .0
1630            .readdir(|mut inode_num, entry_type, name| {
1631                if name == b".." {
1632                    inode_num = if let Some(parent) = file.name.parent_within_mount() {
1633                        parent.node.ino
1634                    } else {
1635                        // For the root .. should have the same inode number as .
1636                        file.name.entry.node.ino
1637                    };
1638                }
1639                let entry_type = match entry_type {
1640                    fio::DirentType::Directory => DirectoryEntryType::DIR,
1641                    fio::DirentType::File => DirectoryEntryType::REG,
1642                    fio::DirentType::Symlink => DirectoryEntryType::LNK,
1643                    _ => DirectoryEntryType::UNKNOWN,
1644                };
1645                match sink.add(inode_num, sink.offset() + 1, entry_type, name.into()) {
1646                    Ok(()) => ControlFlow::Continue(()),
1647                    Err(e) => ControlFlow::Break(e),
1648                }
1649            })
1650            .map_err(map_sync_io_client_error)?
1651        {
1652            None => Ok(()),
1653            Some(e) => Err(e),
1654        }
1655    }
1656
1657    fn sync(&self, _file: &FileObject, _current_task: &CurrentTask) -> Result<(), Errno> {
1658        self.0.sync().map_err(map_sync_error)
1659    }
1660
1661    fn to_handle(
1662        &self,
1663        _file: &FileObject,
1664        _current_task: &CurrentTask,
1665    ) -> Result<Option<zx::NullableHandle>, Errno> {
1666        // If expose a handle to a directory to a Fuchsia component, we trust that it will not
1667        // modify the directory in a way that will confuse Starnix.
1668        self.0
1669            .clone_proxy()
1670            .map_err(map_sync_io_client_error)
1671            .map(|p| Some(p.into_channel().into()))
1672    }
1673}
1674
1675#[derive(Default)]
1676pub struct RemoteFileObject {
1677    /// Cached read-only VMO handle.
1678    read_only_memory: OnceCell<Arc<MemoryObject>>,
1679
1680    /// Cached read/exec VMO handle.
1681    read_exec_memory: OnceCell<Arc<MemoryObject>>,
1682}
1683
1684impl RemoteFileObject {
1685    /// # Panics
1686    ///
1687    /// This will panic if the node's ops are not `RemoteNode`; `AnonymousRemoteFileObject` should
1688    /// be used if this won't be the case.
1689    fn io(file: &FileObject) -> &RemoteIo {
1690        &file.node().downcast_ops::<RemoteNode>().unwrap().node.io
1691    }
1692}
1693
1694trait RemoteIoExt {
1695    fn read_to_output_buffer(
1696        &self,
1697        offset: u64,
1698        buffer: &mut dyn OutputBuffer,
1699    ) -> Result<usize, Errno>;
1700    fn write_from_input_buffer(
1701        &self,
1702        offset: u64,
1703        buffer: &mut dyn InputBuffer,
1704    ) -> Result<usize, Errno>;
1705    fn fetch_remote_memory(&self, prot: ProtectionFlags) -> Result<Arc<MemoryObject>, Errno>;
1706}
1707
1708impl RemoteIoExt for RemoteIo {
1709    fn read_to_output_buffer(
1710        &self,
1711        offset: u64,
1712        buffer: &mut dyn OutputBuffer,
1713    ) -> Result<usize, Errno> {
1714        if self.supports_vectored()
1715            && let Some(actual) = with_iovec_segments(buffer, |iovecs| {
1716                // SAFETY: The iovecs are known to point to userspace, so any damage we do here is
1717                // limited to userspace.  Zircon will catch faults and return an error.
1718                unsafe { self.readv(offset, iovecs).map_err(map_stream_error) }
1719            })
1720        {
1721            let actual = actual?;
1722            // SAFETY: we successfully read `actual` bytes directly to the user's buffer
1723            // segments.
1724            unsafe { buffer.advance(actual) }?;
1725            Ok(actual)
1726        } else {
1727            self.read(
1728                offset,
1729                buffer.available(),
1730                |data| buffer.write(&data),
1731                map_sync_io_client_error,
1732            )
1733        }
1734    }
1735
1736    fn write_from_input_buffer(
1737        &self,
1738        offset: u64,
1739        buffer: &mut dyn InputBuffer,
1740    ) -> Result<usize, Errno> {
1741        let actual = if self.supports_vectored()
1742            && let Some(actual) = with_iovec_segments(buffer, |iovecs| {
1743                self.writev(offset, iovecs).map_err(map_stream_error)
1744            }) {
1745            actual?
1746        } else {
1747            self.write(offset as u64, &buffer.peek_all()?).map_err(map_sync_io_client_error)?
1748        };
1749        buffer.advance(actual)?;
1750        Ok(actual)
1751    }
1752
1753    fn fetch_remote_memory(&self, prot: ProtectionFlags) -> Result<Arc<MemoryObject>, Errno> {
1754        let without_exec = self
1755            .vmo_get(prot.to_vmar_flags() - zx::VmarFlags::PERM_EXECUTE)
1756            .map_err(|status| from_status_like_fdio!(status))?;
1757        let all_flags = if prot.contains(ProtectionFlags::EXEC) {
1758            without_exec.replace_as_executable(&VMEX_RESOURCE).map_err(impossible_error)?
1759        } else {
1760            without_exec
1761        };
1762        Ok(Arc::new(MemoryObject::from(all_flags)))
1763    }
1764}
1765
1766impl FileOps for RemoteFileObject {
1767    fileops_impl_seekable!();
1768
1769    fn read(
1770        &self,
1771        _locked: &mut Locked<FileOpsCore>,
1772        file: &FileObject,
1773        _current_task: &CurrentTask,
1774        offset: usize,
1775        data: &mut dyn OutputBuffer,
1776    ) -> Result<usize, Errno> {
1777        Self::io(file).read_to_output_buffer(offset as u64, data)
1778    }
1779
1780    fn write(
1781        &self,
1782        _locked: &mut Locked<FileOpsCore>,
1783        file: &FileObject,
1784        _current_task: &CurrentTask,
1785        offset: usize,
1786        data: &mut dyn InputBuffer,
1787    ) -> Result<usize, Errno> {
1788        will_dirty(&[&***file.node()], || {
1789            let written = Self::io(file).write_from_input_buffer(offset as u64, data)?;
1790
1791            // If we increased the file size, we need to update that here so that `NodeInfo::size`
1792            // is accurate.  This is done so that we can optimize `get_size`.  If the file has been
1793            // truncated, then the size might not be accurate, but we track that separately and
1794            // `get_size` will fetch the file size from the remote end in that case.
1795            if written > 0 {
1796                file.node().update_info(|info| {
1797                    if offset + written > info.size {
1798                        info.size = offset + written;
1799                    }
1800                });
1801            }
1802
1803            Ok(written)
1804        })
1805    }
1806
1807    fn get_memory(
1808        &self,
1809        _locked: &mut Locked<FileOpsCore>,
1810        file: &FileObject,
1811        _current_task: &CurrentTask,
1812        _length: Option<usize>,
1813        prot: ProtectionFlags,
1814    ) -> Result<Arc<MemoryObject>, Errno> {
1815        fuchsia_trace::duration!(CATEGORY_STARNIX_MM, "RemoteFileGetVmo");
1816        let memory_cache = if prot == (ProtectionFlags::READ | ProtectionFlags::EXEC) {
1817            Some(&self.read_exec_memory)
1818        } else if prot == ProtectionFlags::READ {
1819            Some(&self.read_only_memory)
1820        } else {
1821            None
1822        };
1823
1824        let io = Self::io(file);
1825
1826        memory_cache
1827            .map(|c| c.get_or_try_init(|| io.fetch_remote_memory(prot)).cloned())
1828            .unwrap_or_else(|| io.fetch_remote_memory(prot))
1829    }
1830
1831    fn to_handle(
1832        &self,
1833        file: &FileObject,
1834        current_task: &CurrentTask,
1835    ) -> Result<Option<zx::NullableHandle>, Errno> {
1836        // To avoid cache coherency and security issues, we proxy remote files via the Starnix file
1837        // server.  This will incur a performance penalty which we can optimize later if we need to.
1838        serve_file_tagged(current_task, file, current_task.current_creds().clone(), "remote_files")
1839            .map(|c| Some(c.0.into_channel().into()))
1840    }
1841
1842    fn ioctl(
1843        &self,
1844        locked: &mut Locked<Unlocked>,
1845        file: &FileObject,
1846        current_task: &CurrentTask,
1847        request: u32,
1848        arg: SyscallArg,
1849    ) -> Result<SyscallResult, Errno> {
1850        default_ioctl(file, locked, current_task, request, arg)
1851    }
1852}
1853
1854/// A file object that is not attached to a `RemoteFs`, which means it stores its own `RemoteIo`.
1855pub struct AnonymousRemoteFileObject {
1856    io: RemoteIo,
1857
1858    /// Cached read-only VMO handle.
1859    read_only_memory: OnceCell<Arc<MemoryObject>>,
1860
1861    /// Cached read/exec VMO handle.
1862    read_exec_memory: OnceCell<Arc<MemoryObject>>,
1863}
1864
1865impl AnonymousRemoteFileObject {
1866    fn new(io: RemoteIo) -> Self {
1867        Self { io, read_only_memory: Default::default(), read_exec_memory: Default::default() }
1868    }
1869}
1870
1871impl FileOps for AnonymousRemoteFileObject {
1872    fileops_impl_seekable!();
1873
1874    fn read(
1875        &self,
1876        _locked: &mut Locked<FileOpsCore>,
1877        _file: &FileObject,
1878        _current_task: &CurrentTask,
1879        offset: usize,
1880        data: &mut dyn OutputBuffer,
1881    ) -> Result<usize, Errno> {
1882        self.io.read_to_output_buffer(offset as u64, data)
1883    }
1884
1885    fn write(
1886        &self,
1887        _locked: &mut Locked<FileOpsCore>,
1888        _file: &FileObject,
1889        _current_task: &CurrentTask,
1890        offset: usize,
1891        data: &mut dyn InputBuffer,
1892    ) -> Result<usize, Errno> {
1893        // As this is an anonymous file, there's no point marking the node info dirty because this
1894        // isn't backed by `RemoteNode` or `RemoteSymlink`.
1895        self.io.write_from_input_buffer(offset as u64, data)
1896    }
1897
1898    fn get_memory(
1899        &self,
1900        _locked: &mut Locked<FileOpsCore>,
1901        _file: &FileObject,
1902        _current_task: &CurrentTask,
1903        _length: Option<usize>,
1904        prot: ProtectionFlags,
1905    ) -> Result<Arc<MemoryObject>, Errno> {
1906        fuchsia_trace::duration!(CATEGORY_STARNIX_MM, "RemoteFileGetVmo");
1907        let memory_cache = if prot == (ProtectionFlags::READ | ProtectionFlags::EXEC) {
1908            Some(&self.read_exec_memory)
1909        } else if prot == ProtectionFlags::READ {
1910            Some(&self.read_only_memory)
1911        } else {
1912            None
1913        };
1914
1915        memory_cache
1916            .map(|c| c.get_or_try_init(|| self.io.fetch_remote_memory(prot)).cloned())
1917            .unwrap_or_else(|| self.io.fetch_remote_memory(prot))
1918    }
1919
1920    fn to_handle(
1921        &self,
1922        _file: &FileObject,
1923        _current_task: &CurrentTask,
1924    ) -> Result<Option<zx::NullableHandle>, Errno> {
1925        // This is an anonymous file (not backed by `RemoteNode`).  Any external updates to the
1926        // file's attributes will not be tracked by Starnix.
1927        self.io
1928            .clone_proxy()
1929            .map_err(map_sync_io_client_error)
1930            .map(|p| Some(p.into_channel().into()))
1931    }
1932
1933    fn sync(&self, _file: &FileObject, _current_task: &CurrentTask) -> Result<(), Errno> {
1934        self.io.sync().map_err(map_sync_io_client_error)
1935    }
1936
1937    fn ioctl(
1938        &self,
1939        locked: &mut Locked<Unlocked>,
1940        file: &FileObject,
1941        current_task: &CurrentTask,
1942        request: u32,
1943        arg: SyscallArg,
1944    ) -> Result<SyscallResult, Errno> {
1945        default_ioctl(file, locked, current_task, request, arg)
1946    }
1947}
1948
1949pub struct RemoteZxioFileObject {
1950    /// The underlying Zircon I/O object.  This is shared, so we must take care not to use any
1951    /// stateful methods on the underlying object (reading and writing is fine).
1952    zxio: Zxio,
1953
1954    /// Cached read-only VMO handle.
1955    read_only_memory: OnceCell<Arc<MemoryObject>>,
1956
1957    /// Cached read/exec VMO handle.
1958    read_exec_memory: OnceCell<Arc<MemoryObject>>,
1959}
1960
1961impl RemoteZxioFileObject {
1962    fn new(zxio: Zxio) -> RemoteZxioFileObject {
1963        RemoteZxioFileObject {
1964            zxio,
1965            read_only_memory: Default::default(),
1966            read_exec_memory: Default::default(),
1967        }
1968    }
1969
1970    fn fetch_remote_memory(&self, prot: ProtectionFlags) -> Result<Arc<MemoryObject>, Errno> {
1971        let without_exec = self
1972            .zxio
1973            .vmo_get(prot.to_vmar_flags() - zx::VmarFlags::PERM_EXECUTE)
1974            .map_err(|status| from_status_like_fdio!(status))?;
1975        let all_flags = if prot.contains(ProtectionFlags::EXEC) {
1976            without_exec.replace_as_executable(&VMEX_RESOURCE).map_err(impossible_error)?
1977        } else {
1978            without_exec
1979        };
1980        Ok(Arc::new(MemoryObject::from(all_flags)))
1981    }
1982}
1983
1984impl FileOps for RemoteZxioFileObject {
1985    fileops_impl_seekable!();
1986
1987    fn read(
1988        &self,
1989        _locked: &mut Locked<FileOpsCore>,
1990        _file: &FileObject,
1991        _current_task: &CurrentTask,
1992        offset: usize,
1993        data: &mut dyn OutputBuffer,
1994    ) -> Result<usize, Errno> {
1995        let offset = offset as u64;
1996        let read_bytes = with_iovec_segments::<_, syncio::zxio::zx_iovec, _>(data, |iovecs| {
1997            // SAFETY: The iovecs are valid for writing because they come from OutputBuffer.
1998            unsafe { self.zxio.readv_at(offset, iovecs).map_err(map_stream_error) }
1999        });
2000
2001        match read_bytes {
2002            Some(actual) => {
2003                let actual = actual?;
2004                // SAFETY: we successfully read `actual` bytes
2005                // directly to the user's buffer segments.
2006                unsafe { data.advance(actual) }?;
2007                Ok(actual)
2008            }
2009            None => {
2010                // Perform the (slower) operation by using an intermediate buffer.
2011                let total = data.available();
2012                let mut bytes = vec![0u8; total];
2013                let actual = self
2014                    .zxio
2015                    .read_at(offset, &mut bytes)
2016                    .map_err(|status| from_status_like_fdio!(status))?;
2017                data.write_all(&bytes[0..actual])
2018            }
2019        }
2020    }
2021
2022    fn write(
2023        &self,
2024        _locked: &mut Locked<FileOpsCore>,
2025        _file: &FileObject,
2026        _current_task: &CurrentTask,
2027        offset: usize,
2028        data: &mut dyn InputBuffer,
2029    ) -> Result<usize, Errno> {
2030        let offset = offset as u64;
2031        let write_bytes = with_iovec_segments::<_, syncio::zxio::zx_iovec, _>(data, |iovecs| {
2032            // SAFETY: The iovecs are valid for reading because they come from InputBuffer.
2033            unsafe { self.zxio.writev_at(offset, iovecs).map_err(map_stream_error) }
2034        });
2035
2036        match write_bytes {
2037            Some(actual) => {
2038                let actual = actual?;
2039                data.advance(actual)?;
2040                Ok(actual)
2041            }
2042            None => {
2043                // Perform the (slower) operation by using an intermediate buffer.
2044                let bytes = data.peek_all()?;
2045                let actual = self
2046                    .zxio
2047                    .write_at(offset, &bytes)
2048                    .map_err(|status| from_status_like_fdio!(status))?;
2049                data.advance(actual)?;
2050                Ok(actual)
2051            }
2052        }
2053    }
2054
2055    fn get_memory(
2056        &self,
2057        _locked: &mut Locked<FileOpsCore>,
2058        _file: &FileObject,
2059        _current_task: &CurrentTask,
2060        _length: Option<usize>,
2061        prot: ProtectionFlags,
2062    ) -> Result<Arc<MemoryObject>, Errno> {
2063        fuchsia_trace::duration!(CATEGORY_STARNIX_MM, "RemoteFileGetVmo");
2064        let memory_cache = if prot == (ProtectionFlags::READ | ProtectionFlags::EXEC) {
2065            Some(&self.read_exec_memory)
2066        } else if prot == ProtectionFlags::READ {
2067            Some(&self.read_only_memory)
2068        } else {
2069            None
2070        };
2071
2072        memory_cache
2073            .map(|c| c.get_or_try_init(|| self.fetch_remote_memory(prot)).cloned())
2074            .unwrap_or_else(|| self.fetch_remote_memory(prot))
2075    }
2076
2077    fn to_handle(
2078        &self,
2079        _file: &FileObject,
2080        _current_task: &CurrentTask,
2081    ) -> Result<Option<zx::NullableHandle>, Errno> {
2082        self.zxio.clone_handle().map(Some).map_err(|status| from_status_like_fdio!(status))
2083    }
2084
2085    fn sync(&self, _file: &FileObject, _current_task: &CurrentTask) -> Result<(), Errno> {
2086        self.zxio.sync().map_err(map_sync_error)
2087    }
2088
2089    fn ioctl(
2090        &self,
2091        locked: &mut Locked<Unlocked>,
2092        file: &FileObject,
2093        current_task: &CurrentTask,
2094        request: u32,
2095        arg: SyscallArg,
2096    ) -> Result<SyscallResult, Errno> {
2097        default_ioctl(file, locked, current_task, request, arg)
2098    }
2099}
2100
2101struct RemoteSymlink {
2102    node: BaseNode,
2103    target: LockDepRwLock<Box<[u8]>, FuchsiaRemoteTargetLock>,
2104}
2105
2106impl RemoteSymlink {
2107    fn new(node: BaseNode, target: impl Into<Box<[u8]>>) -> Self {
2108        Self { node, target: LockDepRwLock::new(target.into()) }
2109    }
2110}
2111
2112impl FsNodeOps for RemoteSymlink {
2113    fs_node_impl_symlink!();
2114    fs_node_impl_xattr_delegate!(self, self.node);
2115
2116    fn readlink(
2117        &self,
2118        _locked: &mut Locked<FileOpsCore>,
2119        _node: &FsNode,
2120        _current_task: &CurrentTask,
2121    ) -> Result<SymlinkTarget, Errno> {
2122        Ok(SymlinkTarget::Path(FsString::new(self.target.read().to_vec())))
2123    }
2124
2125    fn fetch_and_refresh_info<'a>(
2126        &self,
2127        _locked: &mut Locked<FileOpsCore>,
2128        _node: &FsNode,
2129        _current_task: &CurrentTask,
2130        info: &'a DynamicLockDepRwLock<FsNodeInfo>,
2131    ) -> Result<LockDepReadGuard<'a, FsNodeInfo>, Errno> {
2132        self.node.fetch_and_refresh_info(info)
2133    }
2134
2135    fn forget(
2136        self: Box<Self>,
2137        _locked: &mut Locked<FileOpsCore>,
2138        _current_task: &CurrentTask,
2139        info: FsNodeInfo,
2140    ) -> Result<(), Errno> {
2141        // Before forgetting this node, update atime if we need to.
2142        if info.pending_time_access_update {
2143            self.node
2144                .io
2145                .attr_get(fio::NodeAttributesQuery::PENDING_ACCESS_TIME_UPDATE)
2146                .map_err(|status| from_status_like_fdio!(status))?;
2147        }
2148        Ok(())
2149    }
2150}
2151
2152pub struct RemoteCounter {
2153    counter: Counter,
2154    koid: std::sync::OnceLock<zx::Koid>,
2155}
2156
2157impl RemoteCounter {
2158    fn new(counter: Counter) -> Self {
2159        Self { counter, koid: std::sync::OnceLock::new() }
2160    }
2161
2162    pub fn duplicate_handle(&self) -> Result<Counter, Errno> {
2163        self.counter.duplicate_handle(zx::Rights::SAME_RIGHTS).map_err(impossible_error)
2164    }
2165
2166    pub fn koid(&self) -> zx::Koid {
2167        *self.koid.get_or_init(|| self.counter.koid().unwrap())
2168    }
2169}
2170
2171impl FileOps for RemoteCounter {
2172    fileops_impl_nonseekable!();
2173    fileops_impl_noop_sync!();
2174
2175    fn read(
2176        &self,
2177        _locked: &mut Locked<FileOpsCore>,
2178        _file: &FileObject,
2179        _current_task: &CurrentTask,
2180        _offset: usize,
2181        _data: &mut dyn OutputBuffer,
2182    ) -> Result<usize, Errno> {
2183        error!(ENOTSUP)
2184    }
2185
2186    fn write(
2187        &self,
2188        _locked: &mut Locked<FileOpsCore>,
2189        _file: &FileObject,
2190        _current_task: &CurrentTask,
2191        _offset: usize,
2192        _data: &mut dyn InputBuffer,
2193    ) -> Result<usize, Errno> {
2194        error!(ENOTSUP)
2195    }
2196
2197    fn ioctl(
2198        &self,
2199        locked: &mut Locked<Unlocked>,
2200        file: &FileObject,
2201        current_task: &CurrentTask,
2202        request: u32,
2203        arg: SyscallArg,
2204    ) -> Result<SyscallResult, Errno> {
2205        let ioctl_type = (request >> 8) as u8;
2206        let ioctl_number = request as u8;
2207        if ioctl_type == SYNC_IOC_MAGIC
2208            && (ioctl_number == SYNC_IOC_FILE_INFO || ioctl_number == SYNC_IOC_MERGE)
2209        {
2210            let mut sync_points = Vec::with_capacity(1);
2211            let counter = self.duplicate_handle()?;
2212            // For other calls than SYNC_IOC_MERGE, the koid is never used, so we construct it
2213            // without fetching it, saving a Zircon syscall.
2214            let sp = if ioctl_number == SYNC_IOC_MERGE {
2215                SyncPoint::with_koid(Timeline::Hwc, counter.into(), self.koid())
2216            } else {
2217                SyncPoint::new(Timeline::Hwc, counter.into())
2218            };
2219            sync_points.push(sp);
2220            let sync_file_name: &[u8; 32] = b"remote counter\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
2221            let sync_file = SyncFile::new(*sync_file_name, SyncFence { sync_points });
2222            return sync_file.ioctl(locked, file, current_task, request, arg);
2223        }
2224
2225        error!(EINVAL)
2226    }
2227}
2228
2229#[track_caller]
2230fn map_sync_error(status: zx::Status) -> Errno {
2231    match status {
2232        zx::Status::NO_RESOURCES | zx::Status::NO_MEMORY | zx::Status::NO_SPACE => {
2233            errno!(ENOSPC)
2234        }
2235        zx::Status::INVALID_ARGS | zx::Status::NOT_FILE => errno!(EINVAL),
2236        zx::Status::BAD_HANDLE => errno!(EBADFD),
2237        zx::Status::NOT_SUPPORTED => errno!(ENOTSUP),
2238        zx::Status::INTERRUPTED_RETRY => errno!(EINTR),
2239        _ => errno!(EIO),
2240    }
2241}
2242
2243#[track_caller]
2244fn map_stream_error(status: zx::Status) -> Errno {
2245    match status {
2246        // zx::Stream may return invalid args or not found error because of invalid zx_iovec buffer
2247        // pointers.
2248        zx::Status::INVALID_ARGS | zx::Status::NOT_FOUND => errno!(EFAULT),
2249        status => from_status_like_fdio!(status),
2250    }
2251}
2252
2253#[track_caller]
2254fn map_sync_io_client_error(status: zx::Status) -> Errno {
2255    from_status_like_fdio!(status)
2256}
2257
2258/// Used to keep track of whether node info is in sync or dirty so that we can avoid communicating
2259/// exernally if we think the node information is in sync.
2260// The two top bits are special (see below).  The remaining bits are a count of the number of
2261// in-flight dirty operations.
2262struct InfoState(AtomicU32);
2263
2264impl InfoState {
2265    /// When this bit is set and the PENDING_REFRESH bit is *not* set, the node information is in
2266    /// sync with the external node.
2267    const IN_SYNC: u32 = 0x8000_0000;
2268
2269    /// When this bit is set in `info_state`, it means the node information is currently being
2270    /// refreshed.
2271    const PENDING_REFRESH: u32 = 0x4000_0000;
2272
2273    /// When this bit is set, it means the node has been truncated and so the size might not be
2274    /// accurate.
2275    const TRUNCATED: u32 = 0x2000_0000;
2276
2277    /// The remaining bits are used to track a count of the number of in-flight dirty operations.
2278    const COUNT_MASK: u32 = Self::TRUNCATED - 1;
2279
2280    fn new(dirty: bool) -> Self {
2281        Self(AtomicU32::new(if dirty { 0 } else { Self::IN_SYNC }))
2282    }
2283
2284    /// This guard should be taken whilst an operation that might result in dirty node information
2285    /// is in flight.  If `for_truncate` is true, this will also set the `TRUNCATED` bit.
2286    fn dirty_op_guard(&self, for_truncate: bool) -> DirtyOpGuard<'_> {
2287        // Increment the count indicating a dirty operation is in flight and also clear the
2288        // `IN_SYNC` bit to indicate the node information will need refreshing from its external
2289        // source.
2290        let mut current = self.0.load(Ordering::Relaxed);
2291        let for_truncate = if for_truncate { Self::TRUNCATED } else { 0 };
2292        loop {
2293            assert!(current & Self::COUNT_MASK != Self::COUNT_MASK); // Check overflow
2294            match self.0.compare_exchange_weak(
2295                current,
2296                ((current & !Self::IN_SYNC) + 1) | for_truncate,
2297                Ordering::Relaxed,
2298                Ordering::Relaxed,
2299            ) {
2300                Ok(_) => break,
2301                Err(old) => current = old,
2302            }
2303        }
2304        DirtyOpGuard(self)
2305    }
2306
2307    /// Calls `refresh` if node information needs to be refreshed, or `not_needed` if node
2308    /// information does not need refreshing.
2309    fn maybe_refresh<'a, T: 'a>(
2310        &self,
2311        info: &'a DynamicLockDepRwLock<FsNodeInfo>,
2312        refresh: impl FnOnce(&'a DynamicLockDepRwLock<FsNodeInfo>) -> Result<T, Errno>,
2313        not_needed: impl FnOnce(&'a DynamicLockDepRwLock<FsNodeInfo>) -> Result<T, Errno>,
2314    ) -> Result<T, Errno> {
2315        let mut current = self.0.load(Ordering::Relaxed);
2316
2317        // If node information is dirty, and there are no pending dirty operations, and there is no
2318        // other thread currently refreshing node information, we can set the bits indicating that a
2319        // refresh is pending.  We want to set the `IN_SYNC` bit here in case `will_dirty` runs
2320        // before we're done.
2321        //
2322        // NOTE: Multiple threads can be refreshing at the same time, but only one of them will
2323        // succeed in setting the `PENDING_REFRESH` bit.
2324        let mut did_set_pending_refresh = false;
2325        while current & !Self::TRUNCATED == 0 {
2326            match self.0.compare_exchange_weak(
2327                current,
2328                current | Self::IN_SYNC | Self::PENDING_REFRESH,
2329                Ordering::Relaxed,
2330                Ordering::Relaxed,
2331            ) {
2332                Ok(_) => {
2333                    did_set_pending_refresh = true;
2334                    break;
2335                }
2336                Err(old) => current = old,
2337            }
2338        }
2339
2340        // Skip the update if the cached information is in sync and there are no pending dirty
2341        // operations.  If there's a pending atime update, we'll skip updating that now; it
2342        // shouldn't be necessary and we can do it later.
2343        if current == Self::IN_SYNC {
2344            return not_needed(info);
2345        }
2346
2347        let result = refresh(info);
2348
2349        if did_set_pending_refresh {
2350            if result.is_ok() {
2351                // If the TRUNCATED bit was set, we can clear it now so long as no other dirty
2352                // operations took place.
2353                if current & Self::TRUNCATED != 0 {
2354                    // Assuming no other thread has changed the state, this is what we
2355                    // expect the current value to be.
2356                    let mut current = Self::TRUNCATED | Self::IN_SYNC | Self::PENDING_REFRESH;
2357                    while current == Self::TRUNCATED | Self::IN_SYNC | Self::PENDING_REFRESH {
2358                        match self.0.compare_exchange_weak(
2359                            current,
2360                            Self::IN_SYNC,
2361                            Ordering::Relaxed,
2362                            Ordering::Relaxed,
2363                        ) {
2364                            Ok(_) => return result,
2365                            Err(old) => current = old,
2366                        }
2367                    }
2368                    // In this case, we fall through and just clear the PENDING_REFRESH bit, but we
2369                    // leave the TRUNCATED bit untouched.
2370                }
2371                self.0.fetch_and(!Self::PENDING_REFRESH, Ordering::Relaxed);
2372            } else {
2373                // If there was an error, we should also clear the IN_SYNC bit to indicate the node
2374                // information is still dirty.
2375                self.0.fetch_and(!(Self::IN_SYNC | Self::PENDING_REFRESH), Ordering::Relaxed);
2376            }
2377        }
2378
2379        result
2380    }
2381
2382    /// Returns true if the size is accurate.
2383    fn is_size_accurate(&self) -> bool {
2384        // The size returned by `get_size` is accurate so long as the file hasn't been truncated.
2385        // If there are writes currently outstanding, then it's also not safe to return the current
2386        // size.  To understand why, consider the following scenario:
2387        //
2388        //    1. Thread A issues a write.
2389        //    2. Thread B performs a read which sees the write from thread A.
2390        //    3. Thread B now tries to seek to the end of the file.  It should be consistent with
2391        //       the read in #2.
2392        //
2393        // #3 needs to see the end-of-file as it is after the write, but it's possible that thread A
2394        // hasn't updated the size yet even though the write has been completed at the remote end.
2395        // For that reason, whilst there are potential writes outstanding, we must ask the remote
2396        // end for the size.
2397        let state = self.0.load(Ordering::Relaxed);
2398        state & (Self::TRUNCATED | Self::COUNT_MASK) == 0
2399    }
2400}
2401
2402struct DirtyOpGuard<'a>(&'a InfoState);
2403
2404impl Drop for DirtyOpGuard<'_> {
2405    fn drop(&mut self) {
2406        // Decrement the count we took when we created the guard.
2407        self.0.0.fetch_sub(1, Ordering::Relaxed);
2408    }
2409}
2410
2411/// A wrapper to be used around calls that will end up making node info dirty.
2412fn will_dirty<'a, N: TryInto<&'a BaseNode> + Copy, T>(nodes: &[N], f: impl FnOnce() -> T) -> T {
2413    // We are about to execute an operation that will make the cached information for one or more
2414    // nodes out of date, and we must deal with races.  If we mark the node as dirty first, another
2415    // thread could sneak in and refresh the node information before this operation has finished,
2416    // and then the information would be out of date.  If we only mark the node as dirty afterwards,
2417    // there is a window between when the operation completes and when we mark the node as dirty
2418    // where another thread could observe the changes caused by this operation, but still see old
2419    // node information.  So, the approach we take is to mark the node as dirty before the operation
2420    // starts, but indicate that this operation is ongoing.  Any threads that try and retrieve node
2421    // information will fetch fresh information, but, importantly, they'll leave the node marked as
2422    // dirty.  Once this operation has finished, we'll indicate this operation is no longer
2423    // in-flight, and then the next time information is refreshed, we'll mark the node information
2424    // as being in sync.
2425
2426    let _guards: SmallVec<[_; 4]> = nodes
2427        .iter()
2428        .filter_map(|n| N::try_into(*n).ok())
2429        .map(|n| n.info_state.dirty_op_guard(false))
2430        .collect();
2431
2432    f()
2433}
2434
2435#[cfg(test)]
2436mod test {
2437    use super::*;
2438    use crate::mm::PAGE_SIZE;
2439    use crate::task::dynamic_thread_spawner::SpawnRequestBuilder;
2440    use crate::testing::*;
2441    use crate::vfs::buffers::{VecInputBuffer, VecOutputBuffer};
2442    use crate::vfs::socket::{SocketFile, SocketMessageFlags};
2443    use crate::vfs::{EpollFileObject, LookupContext, Namespace, SymlinkMode, TimeUpdateType};
2444    use assert_matches::assert_matches;
2445    use fidl::endpoints::{ServerEnd, create_request_stream};
2446    use fidl_fuchsia_io as fio;
2447    use flyweights::FlyByteStr;
2448    use fuchsia_async as fasync;
2449    use fuchsia_runtime::UtcDuration;
2450    use futures::StreamExt;
2451    use fxfs_testing::{TestFixture, TestFixtureOptions};
2452    use starnix_sync::Mutex;
2453    use starnix_uapi::auth::Credentials;
2454    use starnix_uapi::errors::EINVAL;
2455    use starnix_uapi::file_mode::{AccessCheck, mode};
2456    use starnix_uapi::ino_t;
2457    use starnix_uapi::mount_flags::MountpointFlags;
2458    use starnix_uapi::open_flags::OpenFlags;
2459    use starnix_uapi::vfs::{EpollEvent, FdEvents};
2460    use std::sync::Barrier;
2461    use std::sync::atomic::{AtomicU32, AtomicUsize, Ordering};
2462    use storage_device::DeviceHolder;
2463    use storage_device::fake_device::FakeDevice;
2464
2465    #[::fuchsia::test]
2466    async fn test_remote_uds() {
2467        spawn_kernel_and_run(async |locked, current_task| {
2468            let (s1, s2) = zx::Socket::create_datagram();
2469            s2.write(&vec![0]).expect("write");
2470            let file = new_remote_file(locked, &current_task, s1.into(), OpenFlags::RDWR)
2471                .expect("new_remote_file");
2472            assert!(file.node().is_sock());
2473            let socket_ops = file.downcast_file::<SocketFile>().unwrap();
2474            let flags = SocketMessageFlags::CTRUNC
2475                | SocketMessageFlags::TRUNC
2476                | SocketMessageFlags::NOSIGNAL
2477                | SocketMessageFlags::CMSG_CLOEXEC;
2478            let mut buffer = VecOutputBuffer::new(1024);
2479            let info = socket_ops
2480                .recvmsg(locked, &current_task, &file, &mut buffer, flags, None)
2481                .expect("recvmsg");
2482            assert!(info.ancillary_data.is_empty());
2483            assert_eq!(info.message_length, 1);
2484        })
2485        .await;
2486    }
2487
2488    #[::fuchsia::test]
2489    async fn test_tree() {
2490        spawn_kernel_and_run(async |locked, current_task| {
2491            let kernel = current_task.kernel();
2492            let rights = fio::PERM_READABLE | fio::PERM_EXECUTABLE;
2493            let (server, client) = zx::Channel::create();
2494            fdio::open("/pkg", rights, server).expect("failed to open /pkg");
2495            let fs = RemoteFs::new_fs(
2496                locked,
2497                &kernel,
2498                client,
2499                FileSystemOptions { source: FlyByteStr::new(b"/pkg"), ..Default::default() },
2500                rights,
2501            )
2502            .unwrap();
2503            let ns = Namespace::new(fs);
2504            let root = ns.root();
2505            let mut context = LookupContext::default();
2506            assert_eq!(
2507                root.lookup_child(locked, &current_task, &mut context, "nib".into()).err(),
2508                Some(errno!(ENOENT))
2509            );
2510            let mut context = LookupContext::default();
2511            root.lookup_child(locked, &current_task, &mut context, "lib".into()).unwrap();
2512
2513            let mut context = LookupContext::default();
2514            let _test_file = root
2515                .lookup_child(
2516                    locked,
2517                    &current_task,
2518                    &mut context,
2519                    "data/tests/hello_starnix".into(),
2520                )
2521                .unwrap()
2522                .open(locked, &current_task, OpenFlags::RDONLY, AccessCheck::default())
2523                .unwrap();
2524        })
2525        .await;
2526    }
2527
2528    #[::fuchsia::test]
2529    async fn test_blocking_io() {
2530        spawn_kernel_and_run(async |locked, current_task| {
2531            let (client, server) = zx::Socket::create_stream();
2532            let pipe = create_fuchsia_pipe(locked, &current_task, client, OpenFlags::RDWR).unwrap();
2533
2534            let bytes = [0u8; 64];
2535            assert_eq!(bytes.len(), server.write(&bytes).unwrap());
2536
2537            // Spawn a kthread to get the right lock context.
2538            let bytes_read =
2539                pipe.read(locked, &current_task, &mut VecOutputBuffer::new(64)).unwrap();
2540
2541            assert_eq!(bytes_read, bytes.len());
2542        })
2543        .await;
2544    }
2545
2546    #[::fuchsia::test]
2547    async fn test_poll() {
2548        spawn_kernel_and_run(async |locked, current_task| {
2549            let (client, server) = zx::Socket::create_stream();
2550            let pipe = create_fuchsia_pipe(locked, &current_task, client, OpenFlags::RDWR)
2551                .expect("create_fuchsia_pipe");
2552            let server_zxio = Zxio::create(server.into_handle()).expect("Zxio::create");
2553
2554            assert_eq!(
2555                pipe.query_events(locked, &current_task),
2556                Ok(FdEvents::POLLOUT | FdEvents::POLLWRNORM)
2557            );
2558
2559            let epoll_object = EpollFileObject::new_file(locked, &current_task);
2560            let epoll_file = epoll_object.downcast_file::<EpollFileObject>().unwrap();
2561            let event = EpollEvent::new(FdEvents::POLLIN, 0);
2562            epoll_file
2563                .add(locked, &current_task, &pipe, &epoll_object, event)
2564                .expect("poll_file.add");
2565
2566            let fds = epoll_file
2567                .wait(locked, &current_task, 1, zx::MonotonicInstant::ZERO)
2568                .expect("wait");
2569            assert!(fds.is_empty());
2570
2571            assert_eq!(server_zxio.write(&[0]).expect("write"), 1);
2572
2573            assert_eq!(
2574                pipe.query_events(locked, &current_task),
2575                Ok(FdEvents::POLLOUT
2576                    | FdEvents::POLLWRNORM
2577                    | FdEvents::POLLIN
2578                    | FdEvents::POLLRDNORM)
2579            );
2580            let fds = epoll_file
2581                .wait(locked, &current_task, 1, zx::MonotonicInstant::ZERO)
2582                .expect("wait");
2583            assert_eq!(fds.len(), 1);
2584
2585            assert_eq!(
2586                pipe.read(locked, &current_task, &mut VecOutputBuffer::new(64)).expect("read"),
2587                1
2588            );
2589
2590            assert_eq!(
2591                pipe.query_events(locked, &current_task),
2592                Ok(FdEvents::POLLOUT | FdEvents::POLLWRNORM)
2593            );
2594            let fds = epoll_file
2595                .wait(locked, &current_task, 1, zx::MonotonicInstant::ZERO)
2596                .expect("wait");
2597            assert!(fds.is_empty());
2598        })
2599        .await;
2600    }
2601
2602    #[::fuchsia::test]
2603    async fn test_new_remote_directory() {
2604        spawn_kernel_and_run(async |locked, current_task| {
2605            let (server, client) = zx::Channel::create();
2606            fdio::open("/pkg", fio::PERM_READABLE | fio::PERM_EXECUTABLE, server)
2607                .expect("failed to open /pkg");
2608
2609            let fd = new_remote_file(locked, &current_task, client.into(), OpenFlags::RDWR)
2610                .expect("new_remote_file");
2611            assert!(fd.node().is_dir());
2612            assert!(fd.to_handle(&current_task).expect("to_handle").is_some());
2613        })
2614        .await;
2615    }
2616
2617    #[::fuchsia::test]
2618    async fn test_new_remote_file() {
2619        spawn_kernel_and_run(async |locked, current_task| {
2620            let (server, client) = zx::Channel::create();
2621            fdio::open("/pkg/meta/contents", fio::PERM_READABLE, server)
2622                .expect("failed to open /pkg/meta/contents");
2623
2624            let fd = new_remote_file(locked, &current_task, client.into(), OpenFlags::RDONLY)
2625                .expect("new_remote_file");
2626            assert!(!fd.node().is_dir());
2627            assert!(fd.to_handle(&current_task).expect("to_handle").is_some());
2628        })
2629        .await;
2630    }
2631
2632    #[::fuchsia::test]
2633    async fn test_new_remote_counter() {
2634        spawn_kernel_and_run(async |locked, current_task| {
2635            let counter = zx::Counter::create();
2636
2637            let fd = new_remote_file(locked, &current_task, counter.into(), OpenFlags::RDONLY)
2638                .expect("new_remote_file");
2639            assert!(fd.to_handle(&current_task).expect("to_handle").is_some());
2640        })
2641        .await;
2642    }
2643
2644    #[::fuchsia::test]
2645    async fn test_new_remote_vmo() {
2646        spawn_kernel_and_run(async |locked, current_task| {
2647            let vmo = zx::Vmo::create(*PAGE_SIZE).expect("Vmo::create");
2648            let fd = new_remote_file(locked, &current_task, vmo.into(), OpenFlags::RDWR)
2649                .expect("new_remote_file");
2650            assert!(!fd.node().is_dir());
2651            assert!(fd.to_handle(&current_task).expect("to_handle").is_some());
2652        })
2653        .await;
2654    }
2655
2656    #[::fuchsia::test(threads = 2)]
2657    async fn test_symlink() {
2658        let fixture = TestFixture::new().await;
2659        let (server, client) = zx::Channel::create();
2660        fixture.root().clone(server.into()).expect("clone failed");
2661
2662        const LINK_PATH: &'static str = "symlink";
2663        const LINK_TARGET: &'static str = "私は「UTF8」です";
2664        // We expect the reported size of the symlink to be the length of the target, in bytes,
2665        // *without* a null terminator. Most Linux systems assume UTF-8 encoding.
2666        const LINK_SIZE: usize = 22;
2667        assert_eq!(LINK_SIZE, LINK_TARGET.len());
2668
2669        spawn_kernel_and_run(async move |locked, current_task| {
2670            let kernel = current_task.kernel();
2671            let fs = RemoteFs::new_fs(
2672                locked,
2673                &kernel,
2674                client,
2675                FileSystemOptions { source: FlyByteStr::new(b"/"), ..Default::default() },
2676                fio::PERM_READABLE | fio::PERM_WRITABLE,
2677            )
2678            .expect("new_fs failed");
2679            let ns = Namespace::new(fs);
2680            let root = ns.root();
2681            let symlink_node = root
2682                .create_symlink(locked, &current_task, LINK_PATH.into(), LINK_TARGET.into())
2683                .expect("symlink failed");
2684            assert_matches!(&*symlink_node.entry.node.info(), FsNodeInfo { size: LINK_SIZE, .. });
2685
2686            let mut context = LookupContext::new(SymlinkMode::NoFollow);
2687            let child = root
2688                .lookup_child(locked, &current_task, &mut context, "symlink".into())
2689                .expect("lookup_child failed");
2690
2691            match child.readlink(locked, &current_task).expect("readlink failed") {
2692                SymlinkTarget::Path(path) => assert_eq!(path, LINK_TARGET),
2693                SymlinkTarget::Node(_) => panic!("readlink returned SymlinkTarget::Node"),
2694            }
2695            // Ensure the size stat reports matches what is expected.
2696            let stat_result = child.entry.node.stat(locked, &current_task).expect("stat failed");
2697            assert_eq!(stat_result.st_size as usize, LINK_SIZE);
2698        })
2699        .await;
2700
2701        // Simulate a second run to ensure the symlink was persisted correctly.
2702        let fixture = TestFixture::open(
2703            fixture.close().await,
2704            TestFixtureOptions { format: false, ..Default::default() },
2705        )
2706        .await;
2707        let (server, client) = zx::Channel::create();
2708        fixture.root().clone(server.into()).expect("clone failed after remount");
2709
2710        spawn_kernel_and_run(async move |locked, current_task| {
2711            let kernel = current_task.kernel();
2712            let fs = RemoteFs::new_fs(
2713                locked,
2714                &kernel,
2715                client,
2716                FileSystemOptions { source: FlyByteStr::new(b"/"), ..Default::default() },
2717                fio::PERM_READABLE | fio::PERM_WRITABLE,
2718            )
2719            .expect("new_fs failed after remount");
2720            let ns = Namespace::new(fs);
2721            let root = ns.root();
2722            let mut context = LookupContext::new(SymlinkMode::NoFollow);
2723            let child = root
2724                .lookup_child(locked, &current_task, &mut context, "symlink".into())
2725                .expect("lookup_child failed after remount");
2726
2727            match child.readlink(locked, &current_task).expect("readlink failed after remount") {
2728                SymlinkTarget::Path(path) => assert_eq!(path, LINK_TARGET),
2729                SymlinkTarget::Node(_) => {
2730                    panic!("readlink returned SymlinkTarget::Node after remount")
2731                }
2732            }
2733            // Ensure the size stat reports matches what is expected.
2734            let stat_result =
2735                child.entry.node.stat(locked, &current_task).expect("stat failed after remount");
2736            assert_eq!(stat_result.st_size as usize, LINK_SIZE);
2737        })
2738        .await;
2739
2740        fixture.close().await;
2741    }
2742
2743    #[::fuchsia::test]
2744    async fn test_mode_uid_gid_and_dev_persists() {
2745        const FILE_MODE: FileMode = mode!(IFREG, 0o467);
2746        const DIR_MODE: FileMode = mode!(IFDIR, 0o647);
2747        const BLK_MODE: FileMode = mode!(IFBLK, 0o746);
2748
2749        let fixture = TestFixture::new().await;
2750        let (server, client) = zx::Channel::create();
2751        fixture.root().clone(server.into()).expect("clone failed");
2752
2753        // Simulate a first run of starnix.
2754        spawn_kernel_and_run(async move |locked, current_task| {
2755            let kernel = current_task.kernel();
2756            let creds = Credentials::clone(&current_task.current_creds());
2757            current_task.set_creds(Credentials { euid: 1, fsuid: 1, egid: 2, fsgid: 2, ..creds });
2758            let rights = fio::PERM_READABLE | fio::PERM_WRITABLE;
2759            let fs = RemoteFs::new_fs(
2760                locked,
2761                &kernel,
2762                client,
2763                FileSystemOptions { source: FlyByteStr::new(b"/"), ..Default::default() },
2764                rights,
2765            )
2766            .expect("new_fs failed");
2767            let ns = Namespace::new(fs);
2768            current_task.fs().set_umask(FileMode::from_bits(0));
2769            ns.root()
2770                .create_node(locked, &current_task, "file".into(), FILE_MODE, DeviceId::NONE)
2771                .expect("create_node failed");
2772            ns.root()
2773                .create_node(locked, &current_task, "dir".into(), DIR_MODE, DeviceId::NONE)
2774                .expect("create_node failed");
2775            ns.root()
2776                .create_node(locked, &current_task, "dev".into(), BLK_MODE, DeviceId::RANDOM)
2777                .expect("create_node failed");
2778        })
2779        .await;
2780
2781        // Simulate a second run.
2782        let fixture = TestFixture::open(
2783            fixture.close().await,
2784            TestFixtureOptions { format: false, ..Default::default() },
2785        )
2786        .await;
2787
2788        let (server, client) = zx::Channel::create();
2789        fixture.root().clone(server.into()).expect("clone failed");
2790
2791        spawn_kernel_and_run(async move |locked, current_task| {
2792            let kernel = current_task.kernel();
2793            let rights = fio::PERM_READABLE | fio::PERM_WRITABLE;
2794            let fs = RemoteFs::new_fs(
2795                locked,
2796                &kernel,
2797                client,
2798                FileSystemOptions { source: FlyByteStr::new(b"/"), ..Default::default() },
2799                rights,
2800            )
2801            .expect("new_fs failed");
2802            let ns = Namespace::new(fs);
2803            let mut context = LookupContext::new(SymlinkMode::NoFollow);
2804            let child = ns
2805                .root()
2806                .lookup_child(locked, &current_task, &mut context, "file".into())
2807                .expect("lookup_child failed");
2808            assert_matches!(
2809                &*child.entry.node.info(),
2810                FsNodeInfo { mode: FILE_MODE, uid: 1, gid: 2, rdev: DeviceId::NONE, .. }
2811            );
2812            let child = ns
2813                .root()
2814                .lookup_child(locked, &current_task, &mut context, "dir".into())
2815                .expect("lookup_child failed");
2816            assert_matches!(
2817                &*child.entry.node.info(),
2818                FsNodeInfo { mode: DIR_MODE, uid: 1, gid: 2, rdev: DeviceId::NONE, .. }
2819            );
2820            let child = ns
2821                .root()
2822                .lookup_child(locked, &current_task, &mut context, "dev".into())
2823                .expect("lookup_child failed");
2824            assert_matches!(
2825                &*child.entry.node.info(),
2826                FsNodeInfo { mode: BLK_MODE, uid: 1, gid: 2, rdev: DeviceId::RANDOM, .. }
2827            );
2828        })
2829        .await;
2830        fixture.close().await;
2831    }
2832
2833    #[::fuchsia::test]
2834    async fn test_dot_dot_inode_numbers() {
2835        let fixture = TestFixture::new().await;
2836        let (server, client) = zx::Channel::create();
2837        fixture.root().clone(server.into()).expect("clone failed");
2838
2839        const MODE: FileMode = FileMode::from_bits(FileMode::IFDIR.bits() | 0o777);
2840
2841        spawn_kernel_and_run(async |locked, current_task| {
2842            let kernel = current_task.kernel();
2843            let rights = fio::PERM_READABLE | fio::PERM_WRITABLE;
2844            let fs = RemoteFs::new_fs(
2845                locked,
2846                &kernel,
2847                client,
2848                FileSystemOptions { source: FlyByteStr::new(b"/"), ..Default::default() },
2849                rights,
2850            )
2851            .expect("new_fs failed");
2852            let ns = Namespace::new(fs);
2853            current_task.fs().set_umask(FileMode::from_bits(0));
2854            let sub_dir1 = ns
2855                .root()
2856                .create_node(locked, &current_task, "dir".into(), MODE, DeviceId::NONE)
2857                .expect("create_node failed");
2858            let sub_dir2 = sub_dir1
2859                .create_node(locked, &current_task, "dir".into(), MODE, DeviceId::NONE)
2860                .expect("create_node failed");
2861
2862            let dir_handle = ns
2863                .root()
2864                .entry
2865                .open_anonymous(locked, &current_task, OpenFlags::RDONLY)
2866                .expect("open failed");
2867
2868            #[derive(Default)]
2869            struct Sink {
2870                offset: off_t,
2871                dot_dot_inode_num: u64,
2872            }
2873            impl DirentSink for Sink {
2874                fn add(
2875                    &mut self,
2876                    inode_num: ino_t,
2877                    offset: off_t,
2878                    entry_type: DirectoryEntryType,
2879                    name: &FsStr,
2880                ) -> Result<(), Errno> {
2881                    if name == ".." {
2882                        self.dot_dot_inode_num = inode_num;
2883                        assert_eq!(entry_type, DirectoryEntryType::DIR);
2884                    }
2885                    self.offset = offset;
2886                    Ok(())
2887                }
2888                fn offset(&self) -> off_t {
2889                    self.offset
2890                }
2891            }
2892            let mut sink = Sink::default();
2893            dir_handle.readdir(locked, &current_task, &mut sink).expect("readdir failed");
2894
2895            // inode_num for .. for the root should be the same as root.
2896            assert_eq!(sink.dot_dot_inode_num, ns.root().entry.node.ino);
2897
2898            let dir_handle = sub_dir1
2899                .entry
2900                .open_anonymous(locked, &current_task, OpenFlags::RDONLY)
2901                .expect("open failed");
2902            let mut sink = Sink::default();
2903            dir_handle.readdir(locked, &current_task, &mut sink).expect("readdir failed");
2904
2905            // inode_num for .. for the first sub directory should be the same as root.
2906            assert_eq!(sink.dot_dot_inode_num, ns.root().entry.node.ino);
2907
2908            let dir_handle = sub_dir2
2909                .entry
2910                .open_anonymous(locked, &current_task, OpenFlags::RDONLY)
2911                .expect("open failed");
2912            let mut sink = Sink::default();
2913            dir_handle.readdir(locked, &current_task, &mut sink).expect("readdir failed");
2914
2915            // inode_num for .. for the second subdir should be the first subdir.
2916            assert_eq!(sink.dot_dot_inode_num, sub_dir1.entry.node.ino);
2917        })
2918        .await;
2919        fixture.close().await;
2920    }
2921
2922    #[::fuchsia::test]
2923    async fn test_remote_special_node() {
2924        let fixture = TestFixture::new().await;
2925        let (server, client) = zx::Channel::create();
2926        fixture.root().clone(server.into()).expect("clone failed");
2927
2928        const FIFO_MODE: FileMode = FileMode::from_bits(FileMode::IFIFO.bits() | 0o777);
2929        const REG_MODE: FileMode = FileMode::from_bits(FileMode::IFREG.bits());
2930
2931        spawn_kernel_and_run(async |locked, current_task| {
2932            let kernel = current_task.kernel();
2933            let rights = fio::PERM_READABLE | fio::PERM_WRITABLE;
2934            let fs = RemoteFs::new_fs(
2935                locked,
2936                &kernel,
2937                client,
2938                FileSystemOptions { source: FlyByteStr::new(b"/"), ..Default::default() },
2939                rights,
2940            )
2941            .expect("new_fs failed");
2942            let ns = Namespace::new(fs);
2943            current_task.fs().set_umask(FileMode::from_bits(0));
2944            let root = ns.root();
2945
2946            // Create RemoteSpecialNode (e.g. FIFO)
2947            root.create_node(locked, &current_task, "fifo".into(), FIFO_MODE, DeviceId::NONE)
2948                .expect("create_node failed");
2949            let mut context = LookupContext::new(SymlinkMode::NoFollow);
2950            let fifo_node = root
2951                .lookup_child(locked, &current_task, &mut context, "fifo".into())
2952                .expect("lookup_child failed");
2953
2954            // Test that we get expected behaviour for RemoteSpecialNode operation, e.g.
2955            // test that truncate should return EINVAL
2956            match fifo_node.truncate(locked, &current_task, 0) {
2957                Ok(_) => {
2958                    panic!("truncate passed for special node")
2959                }
2960                Err(errno) if errno == EINVAL => {}
2961                Err(e) => {
2962                    panic!("truncate failed with error {:?}", e)
2963                }
2964            };
2965
2966            // Create regular RemoteNode
2967            root.create_node(locked, &current_task, "file".into(), REG_MODE, DeviceId::NONE)
2968                .expect("create_node failed");
2969            let mut context = LookupContext::new(SymlinkMode::NoFollow);
2970            let reg_node = root
2971                .lookup_child(locked, &current_task, &mut context, "file".into())
2972                .expect("lookup_child failed");
2973
2974            // We should be able to perform truncate on regular files
2975            reg_node.truncate(locked, &current_task, 0).expect("truncate failed");
2976        })
2977        .await;
2978        fixture.close().await;
2979    }
2980
2981    #[::fuchsia::test]
2982    async fn test_hard_link() {
2983        let fixture = TestFixture::new().await;
2984        let (server, client) = zx::Channel::create();
2985        fixture.root().clone(server.into()).expect("clone failed");
2986
2987        spawn_kernel_and_run(async move |locked, current_task| {
2988            let kernel = current_task.kernel();
2989            let rights = fio::PERM_READABLE | fio::PERM_WRITABLE;
2990            let fs = RemoteFs::new_fs(
2991                locked,
2992                &kernel,
2993                client,
2994                FileSystemOptions { source: FlyByteStr::new(b"/"), ..Default::default() },
2995                rights,
2996            )
2997            .expect("new_fs failed");
2998            let ns = Namespace::new(fs);
2999            current_task.fs().set_umask(FileMode::from_bits(0));
3000            let node = ns
3001                .root()
3002                .create_node(
3003                    locked,
3004                    &current_task,
3005                    "file1".into(),
3006                    mode!(IFREG, 0o666),
3007                    DeviceId::NONE,
3008                )
3009                .expect("create_node failed");
3010            ns.root()
3011                .entry
3012                .node
3013                .link(locked, &current_task, &ns.root().mount, "file2".into(), &node.entry.node)
3014                .expect("link failed");
3015        })
3016        .await;
3017
3018        let fixture = TestFixture::open(
3019            fixture.close().await,
3020            TestFixtureOptions { format: false, ..Default::default() },
3021        )
3022        .await;
3023
3024        let (server, client) = zx::Channel::create();
3025        fixture.root().clone(server.into()).expect("clone failed");
3026
3027        spawn_kernel_and_run(async move |locked, current_task| {
3028            let kernel = current_task.kernel();
3029            let rights = fio::PERM_READABLE | fio::PERM_WRITABLE;
3030            let fs = RemoteFs::new_fs(
3031                locked,
3032                &kernel,
3033                client,
3034                FileSystemOptions { source: FlyByteStr::new(b"/"), ..Default::default() },
3035                rights,
3036            )
3037            .expect("new_fs failed");
3038            let ns = Namespace::new(fs);
3039            let mut context = LookupContext::new(SymlinkMode::NoFollow);
3040            let child1 = ns
3041                .root()
3042                .lookup_child(locked, &current_task, &mut context, "file1".into())
3043                .expect("lookup_child failed");
3044            let child2 = ns
3045                .root()
3046                .lookup_child(locked, &current_task, &mut context, "file2".into())
3047                .expect("lookup_child failed");
3048            assert!(Arc::ptr_eq(&child1.entry.node, &child2.entry.node));
3049        })
3050        .await;
3051        fixture.close().await;
3052    }
3053
3054    #[::fuchsia::test]
3055    async fn test_lookup_on_fsverity_enabled_file() {
3056        let fixture = TestFixture::new().await;
3057        let (server, client) = zx::Channel::create();
3058        fixture.root().clone(server.into()).expect("clone failed");
3059
3060        const MODE: FileMode = FileMode::from_bits(FileMode::IFREG.bits() | 0o467);
3061
3062        spawn_kernel_and_run(async move |locked, current_task| {
3063            let kernel = current_task.kernel();
3064            let rights = fio::PERM_READABLE | fio::PERM_WRITABLE;
3065            let fs = RemoteFs::new_fs(
3066                locked,
3067                &kernel,
3068                client,
3069                FileSystemOptions { source: FlyByteStr::new(b"/"), ..Default::default() },
3070                rights,
3071            )
3072            .expect("new_fs failed");
3073            let ns = Namespace::new(fs);
3074            current_task.fs().set_umask(FileMode::from_bits(0));
3075            let file = ns
3076                .root()
3077                .create_node(locked, &current_task, "file".into(), MODE, DeviceId::NONE)
3078                .expect("create_node failed");
3079            // Enable verity on the file.
3080            let desc = fsverity_descriptor {
3081                version: 1,
3082                hash_algorithm: 1,
3083                salt_size: 32,
3084                log_blocksize: 12,
3085                ..Default::default()
3086            };
3087            file.entry
3088                .node
3089                .enable_fsverity(locked, current_task, &desc)
3090                .expect("enable fsverity failed");
3091        })
3092        .await;
3093
3094        // Tear down the kernel and open the file again. The file should no longer be cached.
3095        // Test that lookup works as expected for an fsverity-enabled file.
3096        let fixture = TestFixture::open(
3097            fixture.close().await,
3098            TestFixtureOptions { format: false, ..Default::default() },
3099        )
3100        .await;
3101        let (server, client) = zx::Channel::create();
3102        fixture.root().clone(server.into()).expect("clone failed");
3103
3104        spawn_kernel_and_run(async move |locked, current_task| {
3105            let kernel = current_task.kernel();
3106            let rights = fio::PERM_READABLE | fio::PERM_WRITABLE;
3107            let fs = RemoteFs::new_fs(
3108                locked,
3109                &kernel,
3110                client,
3111                FileSystemOptions { source: FlyByteStr::new(b"/"), ..Default::default() },
3112                rights,
3113            )
3114            .expect("new_fs failed");
3115            let ns = Namespace::new(fs);
3116            let mut context = LookupContext::new(SymlinkMode::NoFollow);
3117            let _child = ns
3118                .root()
3119                .lookup_child(locked, &current_task, &mut context, "file".into())
3120                .expect("lookup_child failed");
3121        })
3122        .await;
3123        fixture.close().await;
3124    }
3125
3126    #[::fuchsia::test]
3127    async fn test_update_attributes_persists() {
3128        let fixture = TestFixture::new().await;
3129        let (server, client) = zx::Channel::create();
3130        fixture.root().clone(server.into()).expect("clone failed");
3131
3132        const MODE: FileMode = FileMode::from_bits(FileMode::IFREG.bits() | 0o467);
3133
3134        spawn_kernel_and_run(async move |locked, current_task| {
3135            let kernel = current_task.kernel();
3136            let rights = fio::PERM_READABLE | fio::PERM_WRITABLE;
3137            let fs = RemoteFs::new_fs(
3138                locked,
3139                &kernel,
3140                client,
3141                FileSystemOptions { source: FlyByteStr::new(b"/"), ..Default::default() },
3142                rights,
3143            )
3144            .expect("new_fs failed");
3145            let ns = Namespace::new(fs);
3146            current_task.fs().set_umask(FileMode::from_bits(0));
3147            let file = ns
3148                .root()
3149                .create_node(locked, &current_task, "file".into(), MODE, DeviceId::NONE)
3150                .expect("create_node failed");
3151            // Change the mode, this change should persist
3152            file.entry
3153                .node
3154                .chmod(locked, &current_task, &file.mount, MODE | FileMode::ALLOW_ALL)
3155                .expect("chmod failed");
3156        })
3157        .await;
3158
3159        // Tear down the kernel and open the file again. Check that changes persisted.
3160        let fixture = TestFixture::open(
3161            fixture.close().await,
3162            TestFixtureOptions { format: false, ..Default::default() },
3163        )
3164        .await;
3165        let (server, client) = zx::Channel::create();
3166        fixture.root().clone(server.into()).expect("clone failed");
3167
3168        spawn_kernel_and_run(async move |locked, current_task| {
3169            let kernel = current_task.kernel();
3170            let rights = fio::PERM_READABLE | fio::PERM_WRITABLE;
3171            let fs = RemoteFs::new_fs(
3172                locked,
3173                &kernel,
3174                client,
3175                FileSystemOptions { source: FlyByteStr::new(b"/"), ..Default::default() },
3176                rights,
3177            )
3178            .expect("new_fs failed");
3179            let ns = Namespace::new(fs);
3180            let mut context = LookupContext::new(SymlinkMode::NoFollow);
3181            let child = ns
3182                .root()
3183                .lookup_child(locked, &current_task, &mut context, "file".into())
3184                .expect("lookup_child failed");
3185            assert_eq!(child.entry.node.info().mode, MODE | FileMode::ALLOW_ALL);
3186        })
3187        .await;
3188        fixture.close().await;
3189    }
3190
3191    #[::fuchsia::test]
3192    async fn test_statfs() {
3193        let fixture = TestFixture::new().await;
3194        let (server, client) = zx::Channel::create();
3195        fixture.root().clone(server.into()).expect("clone failed");
3196
3197        spawn_kernel_and_run(async move |locked, current_task| {
3198            let kernel = current_task.kernel();
3199            let rights = fio::PERM_READABLE | fio::PERM_WRITABLE;
3200            let fs = RemoteFs::new_fs(
3201                locked,
3202                &kernel,
3203                client,
3204                FileSystemOptions { source: FlyByteStr::new(b"/"), ..Default::default() },
3205                rights,
3206            )
3207            .expect("new_fs failed");
3208
3209            let statfs = fs.statfs(locked, &current_task).expect("statfs failed");
3210            assert!(statfs.f_type != 0);
3211            assert!(statfs.f_bsize > 0);
3212            assert!(statfs.f_blocks > 0);
3213            assert!(statfs.f_bfree > 0 && statfs.f_bfree <= statfs.f_blocks);
3214            assert!(statfs.f_files > 0);
3215            assert!(statfs.f_ffree > 0 && statfs.f_ffree <= statfs.f_files);
3216            assert!(statfs.f_fsid.val[0] != 0 || statfs.f_fsid.val[1] != 0);
3217            assert!(statfs.f_namelen > 0);
3218            assert!(statfs.f_frsize > 0);
3219        })
3220        .await;
3221
3222        fixture.close().await;
3223    }
3224
3225    #[::fuchsia::test]
3226    async fn test_allocate() {
3227        let fixture = TestFixture::new().await;
3228        let (server, client) = zx::Channel::create();
3229        fixture.root().clone(server.into()).expect("clone failed");
3230
3231        spawn_kernel_and_run(async move |locked, current_task| {
3232            let kernel = current_task.kernel();
3233            let rights = fio::PERM_READABLE | fio::PERM_WRITABLE;
3234            let fs = RemoteFs::new_fs(
3235                locked,
3236                &kernel,
3237                client,
3238                FileSystemOptions { source: FlyByteStr::new(b"/"), ..Default::default() },
3239                rights,
3240            )
3241            .expect("new_fs failed");
3242            let ns = Namespace::new(fs);
3243            current_task.fs().set_umask(FileMode::from_bits(0));
3244            let root = ns.root();
3245
3246            const REG_MODE: FileMode = FileMode::from_bits(FileMode::IFREG.bits());
3247            root.create_node(locked, &current_task, "file".into(), REG_MODE, DeviceId::NONE)
3248                .expect("create_node failed");
3249            let mut context = LookupContext::new(SymlinkMode::NoFollow);
3250            let reg_node = root
3251                .lookup_child(locked, &current_task, &mut context, "file".into())
3252                .expect("lookup_child failed");
3253
3254            reg_node
3255                .entry
3256                .node
3257                .fallocate(locked, &current_task, FallocMode::Allocate { keep_size: false }, 0, 20)
3258                .expect("truncate failed");
3259        })
3260        .await;
3261        fixture.close().await;
3262    }
3263
3264    #[::fuchsia::test]
3265    async fn test_allocate_overflow() {
3266        let fixture = TestFixture::new().await;
3267        let (server, client) = zx::Channel::create();
3268        fixture.root().clone(server.into()).expect("clone failed");
3269
3270        spawn_kernel_and_run(async move |locked, current_task| {
3271            let kernel = current_task.kernel();
3272            let rights = fio::PERM_READABLE | fio::PERM_WRITABLE;
3273            let fs = RemoteFs::new_fs(
3274                locked,
3275                &kernel,
3276                client,
3277                FileSystemOptions { source: FlyByteStr::new(b"/"), ..Default::default() },
3278                rights,
3279            )
3280            .expect("new_fs failed");
3281            let ns = Namespace::new(fs);
3282            current_task.fs().set_umask(FileMode::from_bits(0));
3283            let root = ns.root();
3284
3285            const REG_MODE: FileMode = FileMode::from_bits(FileMode::IFREG.bits());
3286            root.create_node(locked, &current_task, "file".into(), REG_MODE, DeviceId::NONE)
3287                .expect("create_node failed");
3288            let mut context = LookupContext::new(SymlinkMode::NoFollow);
3289            let reg_node = root
3290                .lookup_child(locked, &current_task, &mut context, "file".into())
3291                .expect("lookup_child failed");
3292
3293            reg_node
3294                .entry
3295                .node
3296                .fallocate(
3297                    locked,
3298                    &current_task,
3299                    FallocMode::Allocate { keep_size: false },
3300                    1,
3301                    u64::MAX,
3302                )
3303                .expect_err("truncate unexpectedly passed");
3304        })
3305        .await;
3306        fixture.close().await;
3307    }
3308
3309    #[::fuchsia::test]
3310    async fn test_time_modify_persists() {
3311        let fixture = TestFixture::new().await;
3312        let (server, client) = zx::Channel::create();
3313        fixture.root().clone(server.into()).expect("clone failed");
3314
3315        const MODE: FileMode = FileMode::from_bits(FileMode::IFREG.bits() | 0o467);
3316
3317        let last_modified = spawn_kernel_and_run(async move |locked, current_task| {
3318            let kernel = current_task.kernel();
3319            let rights = fio::PERM_READABLE | fio::PERM_WRITABLE;
3320            let fs = RemoteFs::new_fs(
3321                locked,
3322                &kernel,
3323                client,
3324                FileSystemOptions { source: FlyByteStr::new(b"/"), ..Default::default() },
3325                rights,
3326            )
3327            .expect("new_fs failed");
3328            let ns: Arc<Namespace> = Namespace::new(fs);
3329            current_task.fs().set_umask(FileMode::from_bits(0));
3330            let child = ns
3331                .root()
3332                .create_node(locked, &current_task, "file".into(), MODE, DeviceId::NONE)
3333                .expect("create_node failed");
3334            // Write to file (this should update mtime (time_modify))
3335            let file = child
3336                .open(locked, &current_task, OpenFlags::RDWR, AccessCheck::default())
3337                .expect("open failed");
3338            // Call `fetch_and_refresh_info(..)` to refresh `time_modify` with the time managed by the
3339            // underlying filesystem
3340            let time_before_write = child
3341                .entry
3342                .node
3343                .fetch_and_refresh_info(locked, &current_task)
3344                .expect("fetch_and_refresh_info failed")
3345                .time_modify;
3346            let write_bytes: [u8; 5] = [1, 2, 3, 4, 5];
3347            let written = file
3348                .write(locked, &current_task, &mut VecInputBuffer::new(&write_bytes))
3349                .expect("write failed");
3350            assert_eq!(written, write_bytes.len());
3351            let last_modified = child
3352                .entry
3353                .node
3354                .fetch_and_refresh_info(locked, &current_task)
3355                .expect("fetch_and_refresh_info failed")
3356                .time_modify;
3357            assert!(last_modified > time_before_write);
3358            last_modified
3359        })
3360        .await;
3361
3362        // Tear down the kernel and open the file again. Check that modification time is when we
3363        // last modified the contents of the file
3364        let fixture = TestFixture::open(
3365            fixture.close().await,
3366            TestFixtureOptions { format: false, ..Default::default() },
3367        )
3368        .await;
3369        let (server, client) = zx::Channel::create();
3370        fixture.root().clone(server.into()).expect("clone failed");
3371        let refreshed_modified_time = spawn_kernel_and_run(async move |locked, current_task| {
3372            let kernel = current_task.kernel();
3373            let rights = fio::PERM_READABLE | fio::PERM_WRITABLE;
3374            let fs = RemoteFs::new_fs(
3375                locked,
3376                &kernel,
3377                client,
3378                FileSystemOptions { source: FlyByteStr::new(b"/"), ..Default::default() },
3379                rights,
3380            )
3381            .expect("new_fs failed");
3382            let ns = Namespace::new(fs);
3383            let mut context = LookupContext::new(SymlinkMode::NoFollow);
3384            let child = ns
3385                .root()
3386                .lookup_child(locked, &current_task, &mut context, "file".into())
3387                .expect("lookup_child failed");
3388            let last_modified = child
3389                .entry
3390                .node
3391                .fetch_and_refresh_info(locked, &current_task)
3392                .expect("fetch_and_refresh_info failed")
3393                .time_modify;
3394            last_modified
3395        })
3396        .await;
3397        assert_eq!(last_modified, refreshed_modified_time);
3398
3399        fixture.close().await;
3400    }
3401
3402    #[::fuchsia::test]
3403    async fn test_update_atime_mtime() {
3404        let fixture = TestFixture::new().await;
3405        let (server, client) = zx::Channel::create();
3406        fixture.root().clone(server.into()).expect("clone failed");
3407
3408        const MODE: FileMode = FileMode::from_bits(FileMode::IFREG.bits() | 0o467);
3409
3410        spawn_kernel_and_run(async move |locked, current_task| {
3411            let kernel = current_task.kernel();
3412            let rights = fio::PERM_READABLE | fio::PERM_WRITABLE;
3413            let fs = RemoteFs::new_fs(
3414                locked,
3415                &kernel,
3416                client,
3417                FileSystemOptions { source: FlyByteStr::new(b"/"), ..Default::default() },
3418                rights,
3419            )
3420            .expect("new_fs failed");
3421            let ns: Arc<Namespace> = Namespace::new(fs);
3422            current_task.fs().set_umask(FileMode::from_bits(0));
3423            let child = ns
3424                .root()
3425                .create_node(locked, &current_task, "file".into(), MODE, DeviceId::NONE)
3426                .expect("create_node failed");
3427
3428            let info_original = child
3429                .entry
3430                .node
3431                .fetch_and_refresh_info(locked, &current_task)
3432                .expect("fetch_and_refresh_info failed")
3433                .clone();
3434
3435            child
3436                .entry
3437                .node
3438                .update_atime_mtime(
3439                    locked,
3440                    &current_task,
3441                    &child.mount,
3442                    TimeUpdateType::Time(UtcInstant::from_nanos(30)),
3443                    TimeUpdateType::Omit,
3444                )
3445                .expect("update_atime_mtime failed");
3446            let info_after_update = child
3447                .entry
3448                .node
3449                .fetch_and_refresh_info(locked, &current_task)
3450                .expect("fetch_and_refresh_info failed")
3451                .clone();
3452            assert_eq!(info_after_update.time_modify, info_original.time_modify);
3453            assert_eq!(info_after_update.time_access, UtcInstant::from_nanos(30));
3454
3455            child
3456                .entry
3457                .node
3458                .update_atime_mtime(
3459                    locked,
3460                    &current_task,
3461                    &child.mount,
3462                    TimeUpdateType::Omit,
3463                    TimeUpdateType::Time(UtcInstant::from_nanos(50)),
3464                )
3465                .expect("update_atime_mtime failed");
3466            let info_after_update2 = child
3467                .entry
3468                .node
3469                .fetch_and_refresh_info(locked, &current_task)
3470                .expect("fetch_and_refresh_info failed")
3471                .clone();
3472            assert_eq!(info_after_update2.time_modify, UtcInstant::from_nanos(50));
3473            assert_eq!(info_after_update2.time_access, UtcInstant::from_nanos(30));
3474        })
3475        .await;
3476        fixture.close().await;
3477    }
3478
3479    #[::fuchsia::test]
3480    async fn test_write_updates_mtime_ctime() {
3481        let fixture = TestFixture::new().await;
3482        let (server, client) = zx::Channel::create();
3483        fixture.root().clone(server.into()).expect("clone failed");
3484
3485        const MODE: FileMode = FileMode::from_bits(FileMode::IFREG.bits() | 0o467);
3486
3487        spawn_kernel_and_run(async move |locked, current_task| {
3488            let kernel = current_task.kernel();
3489            let rights = fio::PERM_READABLE | fio::PERM_WRITABLE;
3490            let fs = RemoteFs::new_fs(
3491                locked,
3492                &kernel,
3493                client,
3494                FileSystemOptions { source: FlyByteStr::new(b"/"), ..Default::default() },
3495                rights,
3496            )
3497            .expect("new_fs failed");
3498            let ns: Arc<Namespace> = Namespace::new(fs);
3499            current_task.fs().set_umask(FileMode::from_bits(0));
3500            let child = ns
3501                .root()
3502                .create_node(locked, &current_task, "file".into(), MODE, DeviceId::NONE)
3503                .expect("create_node failed");
3504            let file = child
3505                .open(locked, &current_task, OpenFlags::RDWR, AccessCheck::default())
3506                .expect("open failed");
3507            // Call `fetch_and_refresh_info(..)` to refresh ctime and mtime with the time managed by the
3508            // underlying filesystem
3509            let (ctime_before_write, mtime_before_write) = {
3510                let info = child
3511                    .entry
3512                    .node
3513                    .fetch_and_refresh_info(locked, &current_task)
3514                    .expect("fetch_and_refresh_info failed");
3515                (info.time_status_change, info.time_modify)
3516            };
3517
3518            // Writing to a file should update ctime and mtime
3519            let write_bytes: [u8; 5] = [1, 2, 3, 4, 5];
3520            let written = file
3521                .write(locked, &current_task, &mut VecInputBuffer::new(&write_bytes))
3522                .expect("write failed");
3523            assert_eq!(written, write_bytes.len());
3524
3525            // As Fxfs, the underlying filesystem in this test, can manage file timestamps,
3526            // we should not see an update in mtime and ctime without first refreshing the node with
3527            // the metadata from Fxfs.
3528            let (ctime_after_write_no_refresh, mtime_after_write_no_refresh) = {
3529                let info = child.entry.node.info();
3530                (info.time_status_change, info.time_modify)
3531            };
3532            assert_eq!(ctime_after_write_no_refresh, ctime_before_write);
3533            assert_eq!(mtime_after_write_no_refresh, mtime_before_write);
3534
3535            // Refresh information, we should see `info` with mtime and ctime from the remote
3536            // filesystem (assume this is true if the new timestamp values are greater than the ones
3537            // without the refresh).
3538            let (ctime_after_write_refresh, mtime_after_write_refresh) = {
3539                let info = child
3540                    .entry
3541                    .node
3542                    .fetch_and_refresh_info(locked, &current_task)
3543                    .expect("fetch_and_refresh_info failed");
3544                (info.time_status_change, info.time_modify)
3545            };
3546            assert_eq!(ctime_after_write_refresh, mtime_after_write_refresh);
3547            assert!(ctime_after_write_refresh > ctime_after_write_no_refresh);
3548        })
3549        .await;
3550        fixture.close().await;
3551    }
3552
3553    #[::fuchsia::test]
3554    async fn test_casefold_persists() {
3555        let fixture = TestFixture::new().await;
3556        let (server, client) = zx::Channel::create();
3557        fixture.root().clone(server.into()).expect("clone failed");
3558
3559        spawn_kernel_and_run(async move |locked, current_task| {
3560            let kernel = current_task.kernel();
3561            let rights = fio::PERM_READABLE | fio::PERM_WRITABLE;
3562            let fs = RemoteFs::new_fs(
3563                locked,
3564                &kernel,
3565                client,
3566                FileSystemOptions { source: FlyByteStr::new(b"/"), ..Default::default() },
3567                rights,
3568            )
3569            .expect("new_fs failed");
3570            let ns: Arc<Namespace> = Namespace::new(fs);
3571            let child = ns
3572                .root()
3573                .create_node(
3574                    locked,
3575                    &current_task,
3576                    "dir".into(),
3577                    FileMode::ALLOW_ALL.with_type(FileMode::IFDIR),
3578                    DeviceId::NONE,
3579                )
3580                .expect("create_node failed");
3581            child
3582                .entry
3583                .node
3584                .update_attributes(locked, &current_task, |info| {
3585                    info.casefold = true;
3586                    Ok(())
3587                })
3588                .expect("enable casefold")
3589        })
3590        .await;
3591
3592        // Tear down the kernel and open the dir again. Check that casefold is preserved.
3593        let fixture = TestFixture::open(
3594            fixture.close().await,
3595            TestFixtureOptions { format: false, ..Default::default() },
3596        )
3597        .await;
3598        let (server, client) = zx::Channel::create();
3599        fixture.root().clone(server.into()).expect("clone failed");
3600        let casefold = spawn_kernel_and_run(async move |locked, current_task| {
3601            let kernel = current_task.kernel();
3602            let rights = fio::PERM_READABLE | fio::PERM_WRITABLE;
3603            let fs = RemoteFs::new_fs(
3604                locked,
3605                &kernel,
3606                client,
3607                FileSystemOptions { source: FlyByteStr::new(b"/"), ..Default::default() },
3608                rights,
3609            )
3610            .expect("new_fs failed");
3611            let ns = Namespace::new(fs);
3612            let mut context = LookupContext::new(SymlinkMode::NoFollow);
3613            let child = ns
3614                .root()
3615                .lookup_child(locked, &current_task, &mut context, "dir".into())
3616                .expect("lookup_child failed");
3617            let casefold = child
3618                .entry
3619                .node
3620                .fetch_and_refresh_info(locked, &current_task)
3621                .expect("fetch_and_refresh_info failed")
3622                .casefold;
3623            casefold
3624        })
3625        .await;
3626        assert!(casefold);
3627
3628        fixture.close().await;
3629    }
3630
3631    #[::fuchsia::test]
3632    async fn test_pending_access_time() {
3633        const TEST_FILE: &str = "test_file";
3634
3635        let fixture = TestFixture::new().await;
3636        let (server, client) = zx::Channel::create();
3637        fixture.root().clone(server.into()).expect("clone failed");
3638        let (server, client2) = zx::Channel::create();
3639        fixture.root().clone(server.into()).expect("clone failed");
3640
3641        spawn_kernel_and_run(async move |locked, current_task| {
3642            let kernel = current_task.kernel.clone();
3643
3644            let atime3 = {
3645                let fs = RemoteFs::new_fs(
3646                    locked,
3647                    &kernel,
3648                    client,
3649                    FileSystemOptions {
3650                        source: FlyByteStr::new(b"/"),
3651                        flags: FileSystemFlags::empty().into(),
3652                        ..Default::default()
3653                    },
3654                    fio::PERM_READABLE | fio::PERM_WRITABLE,
3655                )
3656                .expect("new_fs failed");
3657
3658                let ns = Namespace::new_with_flags(fs, MountpointFlags::RELATIME);
3659                let child = ns
3660                    .root()
3661                    .open_create_node(
3662                        locked,
3663                        &current_task,
3664                        TEST_FILE.into(),
3665                        FileMode::ALLOW_ALL.with_type(FileMode::IFREG),
3666                        DeviceId::NONE,
3667                        OpenFlags::empty(),
3668                    )
3669                    .expect("create_node failed");
3670
3671                let atime1 = child.entry.node.info().time_access;
3672
3673                std::thread::sleep(std::time::Duration::from_micros(1));
3674
3675                let file_handle = child
3676                    .open(locked, &current_task, OpenFlags::RDWR, AccessCheck::default())
3677                    .expect("open failed");
3678
3679                file_handle
3680                    .read(locked, &current_task, &mut VecOutputBuffer::new(10))
3681                    .expect("read failed");
3682
3683                // Expect atime to have changed.
3684                let atime2 = child.entry.node.info().time_access;
3685                assert!(atime2 > atime1);
3686
3687                std::thread::sleep(std::time::Duration::from_micros(1));
3688
3689                file_handle
3690                    .read(locked, &current_task, &mut VecOutputBuffer::new(10))
3691                    .expect("read failed");
3692
3693                // And again.
3694                let atime3 = child.entry.node.info().time_access;
3695                assert!(atime3 > atime2);
3696
3697                atime3
3698            };
3699
3700            kernel.delayed_releaser.apply(locked.cast_locked(), current_task);
3701
3702            // After dropping the filesystem, the atime should have been persistently updated.
3703            let fs = RemoteFs::new_fs(
3704                locked,
3705                &kernel,
3706                client2,
3707                FileSystemOptions {
3708                    source: FlyByteStr::new(b"/"),
3709                    flags: FileSystemFlags::empty().into(),
3710                    ..Default::default()
3711                },
3712                fio::PERM_READABLE | fio::PERM_WRITABLE,
3713            )
3714            .expect("new_fs failed");
3715
3716            let ns = Namespace::new_with_flags(fs, MountpointFlags::RELATIME);
3717            let child = ns
3718                .root()
3719                .lookup_child(
3720                    locked,
3721                    &current_task,
3722                    &mut LookupContext::new(Default::default()),
3723                    TEST_FILE.into(),
3724                )
3725                .expect("lookup_child failed");
3726
3727            let atime4 = child.entry.node.info().time_access;
3728
3729            assert!(atime4 >= atime3);
3730        })
3731        .await;
3732
3733        fixture.close().await;
3734    }
3735
3736    #[::fuchsia::test]
3737    async fn test_read_chunking() {
3738        use futures::StreamExt;
3739        let (client, mut stream) = create_request_stream::<fio::FileMarker>();
3740        let content = vec![0xAB; (fio::MAX_TRANSFER_SIZE + 100) as usize];
3741        let content_clone = content.clone();
3742
3743        let _server_task = fasync::Task::spawn(async move {
3744            while let Some(Ok(request)) = stream.next().await {
3745                match request {
3746                    fio::FileRequest::ReadAt { count, offset, responder } => {
3747                        let start = offset as usize;
3748                        let end = std::cmp::min(start + count as usize, content_clone.len());
3749                        let data = if start < content_clone.len() {
3750                            &content_clone[start..end]
3751                        } else {
3752                            &[]
3753                        };
3754                        responder.send(Ok(data)).unwrap();
3755                    }
3756                    _ => panic!("Unexpected request: {:?}", request),
3757                }
3758            }
3759        });
3760
3761        fasync::unblock(move || {
3762            let io = RemoteIo::new(client.into_channel().into());
3763            let mut buffer = VecOutputBuffer::new(content.len());
3764            assert_eq!(
3765                io.read_to_output_buffer(0, &mut buffer).expect("read_at failed"),
3766                content.len()
3767            );
3768            assert_eq!(buffer.data(), content.as_slice());
3769        })
3770        .await;
3771    }
3772
3773    #[::fuchsia::test]
3774    async fn test_write_chunking() {
3775        let (client, mut stream) = create_request_stream::<fio::FileMarker>();
3776        let content = vec![0xCD; (fio::MAX_TRANSFER_SIZE + 100) as usize];
3777        let content2 = content.clone();
3778
3779        let server_task = fasync::Task::spawn(async move {
3780            let mut written = vec![0; content2.len()];
3781            while let Some(Ok(request)) = stream.next().await {
3782                match request {
3783                    fio::FileRequest::WriteAt { offset, data, responder, .. } => {
3784                        let offset = offset as usize;
3785                        written[offset..offset + data.len()].copy_from_slice(&data);
3786                        responder.send(Ok(data.len() as u64)).unwrap();
3787                    }
3788                    _ => panic!("Unexpected request: {:?}", request),
3789                }
3790            }
3791            assert_eq!(written, content2);
3792        });
3793
3794        fasync::unblock(move || {
3795            let io = RemoteIo::new(client.into_channel().into());
3796            let mut buffer = VecInputBuffer::new(&content);
3797            assert_eq!(
3798                io.write_from_input_buffer(0, &mut buffer).expect("write_at failed"),
3799                content.len()
3800            );
3801        })
3802        .await;
3803
3804        server_task.await;
3805    }
3806
3807    #[::fuchsia::test]
3808    async fn test_cached_attribute_refresh_behavior() {
3809        let (client, mut stream) = create_request_stream::<fio::FileMarker>();
3810        let barrier = Arc::new(Barrier::new(2));
3811        let barrier_clone = barrier.clone();
3812        let get_attrs_count = Arc::new(AtomicU32::new(0));
3813        let get_attrs_count_clone = get_attrs_count.clone();
3814
3815        let server_task = fasync::Task::spawn(async move {
3816            while let Some(Ok(request)) = stream.next().await {
3817                match request {
3818                    fio::FileRequest::GetAttributes { query: _, responder } => {
3819                        get_attrs_count_clone.fetch_add(1, Ordering::SeqCst);
3820                        let mutable_attrs = fio::MutableNodeAttributes { ..Default::default() };
3821                        let immutable_attrs = fio::ImmutableNodeAttributes {
3822                            id: Some(1),
3823                            link_count: Some(1),
3824                            ..Default::default()
3825                        };
3826                        responder.send(Ok((&mutable_attrs, &immutable_attrs))).unwrap();
3827                    }
3828                    fio::FileRequest::Resize { length: _, responder } => {
3829                        let barrier_clone = barrier_clone.clone();
3830                        fasync::Task::spawn(async move {
3831                            barrier_clone.async_wait().await;
3832                            barrier_clone.async_wait().await;
3833                            responder.send(Ok(())).unwrap();
3834                        })
3835                        .detach();
3836                    }
3837                    fio::FileRequest::Close { responder } => {
3838                        responder.send(Ok(())).unwrap();
3839                    }
3840                    _ => panic!("Unexpected request: {:?}", request),
3841                }
3842            }
3843        });
3844
3845        fasync::unblock(move || {
3846            let io = RemoteIo::new(client.into_channel().into());
3847            let node = BaseNode::new(io, false);
3848            let info =
3849                DynamicLockDepRwLock::new::<starnix_sync::FsNodeInfoLevel>(FsNodeInfo::default());
3850
3851            // 1. Initial fetch. Should return cached info immediately.
3852            assert_eq!(get_attrs_count.load(Ordering::SeqCst), 0);
3853            {
3854                let _info = node.fetch_and_refresh_info(&info).expect("fetch failed");
3855            }
3856            assert_eq!(get_attrs_count.load(Ordering::SeqCst), 0);
3857
3858            // 2. Spawn a thread to perform a dirty operation.
3859            std::thread::scope(|s| {
3860                s.spawn(|| {
3861                    will_dirty(&[&node], || {
3862                        node.io.truncate(0).expect("truncate failed");
3863                    });
3864                });
3865
3866                // Wait for the operation to start.
3867                barrier.wait();
3868
3869                // Now the node is dirty. Fetching attributes should trigger a request.
3870                {
3871                    let _info = node.fetch_and_refresh_info(&info).expect("fetch failed");
3872                }
3873                assert_eq!(get_attrs_count.load(Ordering::SeqCst), 1);
3874
3875                // A second fetch should trigger another request.
3876                {
3877                    let _info = node.fetch_and_refresh_info(&info).expect("fetch failed");
3878                }
3879                assert_eq!(get_attrs_count.load(Ordering::SeqCst), 2);
3880
3881                // Let the operation finish.
3882                barrier.wait();
3883            });
3884
3885            // 3. Operation finished. The next fetch should trigger a request.
3886            {
3887                let _info = node.fetch_and_refresh_info(&info).expect("fetch failed");
3888            }
3889            assert_eq!(get_attrs_count.load(Ordering::SeqCst), 3);
3890
3891            // 4. Subsequent fetch should return cached info.
3892            {
3893                let _info = node.fetch_and_refresh_info(&info).expect("fetch failed");
3894            }
3895            assert_eq!(get_attrs_count.load(Ordering::SeqCst), 3);
3896        })
3897        .await;
3898
3899        server_task.await;
3900    }
3901
3902    #[::fuchsia::test]
3903    async fn test_attribute_refresh_during_concurrent_dirty_operation() {
3904        let (client, mut stream) = create_request_stream::<fio::FileMarker>();
3905        let get_attrs_started = Arc::new(Barrier::new(2));
3906        let get_attrs_started_clone = get_attrs_started.clone();
3907        let finish_get_attrs = Arc::new(Barrier::new(2));
3908        let finish_get_attrs_clone = finish_get_attrs.clone();
3909
3910        let resize_started = Arc::new(Barrier::new(2));
3911        let resize_started_clone = resize_started.clone();
3912        let finish_resize = Arc::new(Barrier::new(2));
3913        let finish_resize_clone = finish_resize.clone();
3914
3915        let get_attrs_count = Arc::new(AtomicU32::new(0));
3916        let get_attrs_count_clone = get_attrs_count.clone();
3917
3918        let server_task = fasync::Task::spawn(async move {
3919            while let Some(Ok(request)) = stream.next().await {
3920                match request {
3921                    fio::FileRequest::GetAttributes { query: _, responder } => {
3922                        let count = get_attrs_count_clone.fetch_add(1, Ordering::SeqCst);
3923                        let finish_get_attrs_clone = finish_get_attrs_clone.clone();
3924                        let get_attrs_started_clone = get_attrs_started_clone.clone();
3925
3926                        fasync::Task::spawn(async move {
3927                            if count == 0 {
3928                                fasync::unblock(move || {
3929                                    get_attrs_started_clone.wait();
3930                                    finish_get_attrs_clone.wait();
3931                                })
3932                                .await;
3933                            }
3934                            let mutable_attrs = fio::MutableNodeAttributes { ..Default::default() };
3935                            let immutable_attrs = fio::ImmutableNodeAttributes {
3936                                id: Some(1),
3937                                link_count: Some(1),
3938                                ..Default::default()
3939                            };
3940                            responder.send(Ok((&mutable_attrs, &immutable_attrs))).unwrap();
3941                        })
3942                        .detach();
3943                    }
3944                    fio::FileRequest::Resize { length: _, responder } => {
3945                        let resize_started_clone = resize_started_clone.clone();
3946                        let finish_resize_clone = finish_resize_clone.clone();
3947                        fasync::Task::spawn(async move {
3948                            fasync::unblock(move || {
3949                                resize_started_clone.wait();
3950                                finish_resize_clone.wait();
3951                            })
3952                            .await;
3953                            responder.send(Ok(())).unwrap();
3954                        })
3955                        .detach();
3956                    }
3957                    fio::FileRequest::Close { responder } => {
3958                        responder.send(Ok(())).unwrap();
3959                    }
3960                    _ => panic!("Unexpected request: {:?}", request),
3961                }
3962            }
3963        });
3964
3965        fasync::unblock(move || {
3966            let io = RemoteIo::new(client.into_channel().into());
3967            let node = BaseNode::new(io, true);
3968            let info =
3969                DynamicLockDepRwLock::new::<starnix_sync::FsNodeInfoLevel>(FsNodeInfo::default());
3970
3971            std::thread::scope(|s| {
3972                // 1. Start Refresh Thread
3973                let refresh_thread = s.spawn(|| {
3974                    let _info = node.fetch_and_refresh_info(&info).expect("fetch failed");
3975                });
3976
3977                get_attrs_started.wait();
3978
3979                // 2. Start Dirty Thread
3980                let dirty_thread = s.spawn(|| {
3981                    will_dirty(&[&node], || {
3982                        node.io.truncate(0).expect("truncate failed");
3983                    });
3984                });
3985
3986                resize_started.wait();
3987
3988                // 3. Allow GetAttributes to finish
3989                finish_get_attrs.wait();
3990                refresh_thread.join().unwrap();
3991                assert_eq!(get_attrs_count.load(Ordering::SeqCst), 1);
3992
3993                // 4. Refresh #2 (Should fetch because dirty op is in flight)
3994                {
3995                    let _info = node.fetch_and_refresh_info(&info).expect("fetch failed");
3996                }
3997                assert_eq!(get_attrs_count.load(Ordering::SeqCst), 2);
3998
3999                // 5. Allow Dirty Op to finish
4000                finish_resize.wait();
4001                dirty_thread.join().unwrap();
4002
4003                // 6. Refresh #3 (Should fetch because dirty op finished, but state was 0)
4004                {
4005                    let _info = node.fetch_and_refresh_info(&info).expect("fetch failed");
4006                }
4007                assert_eq!(get_attrs_count.load(Ordering::SeqCst), 3);
4008
4009                // 7. Refresh #4 (Should be cached)
4010                {
4011                    let _info = node.fetch_and_refresh_info(&info).expect("fetch failed");
4012                }
4013                assert_eq!(get_attrs_count.load(Ordering::SeqCst), 3);
4014            });
4015        })
4016        .await;
4017
4018        server_task.await;
4019    }
4020
4021    #[::fuchsia::test]
4022    async fn test_update_attributes_invalidates_cache() {
4023        let (client, mut stream) = create_request_stream::<fio::DirectoryMarker>();
4024        let get_attrs_count = Arc::new(AtomicU32::new(0));
4025        let get_attrs_count_clone = get_attrs_count.clone();
4026
4027        let server_task = fasync::Task::spawn(async move {
4028            let mut sub_tasks = Vec::new();
4029            while let Some(Ok(request)) = stream.next().await {
4030                match request {
4031                    fio::DirectoryRequest::Open { path, object, flags, .. } => {
4032                        assert_eq!(path, ".", "Unexpected open() for non-self");
4033                        let get_attrs_count = get_attrs_count_clone.clone();
4034                        sub_tasks.push(fasync::Task::spawn(async move {
4035                            let (mut stream, control_handle) =
4036                                ServerEnd::<fio::DirectoryMarker>::new(object)
4037                                    .into_stream_and_control_handle();
4038                            assert!(flags.contains(fio::Flags::FLAG_SEND_REPRESENTATION));
4039
4040                            // The Representation provides the initial attributes to cache.
4041                            let mutable_attributes =
4042                                fio::MutableNodeAttributes { ..Default::default() };
4043                            let immutable_attributes = fio::ImmutableNodeAttributes {
4044                                id: Some(1),
4045                                link_count: Some(1),
4046                                ..Default::default()
4047                            };
4048                            let info = fio::DirectoryInfo {
4049                                attributes: Some(fio::NodeAttributes2 {
4050                                    mutable_attributes,
4051                                    immutable_attributes,
4052                                }),
4053                                ..Default::default()
4054                            };
4055                            let _ = control_handle
4056                                .send_on_representation(fio::Representation::Directory(info));
4057
4058                            while let Some(Ok(request)) = stream.next().await {
4059                                match request {
4060                                    fio::DirectoryRequest::GetAttributes {
4061                                        query: _,
4062                                        responder,
4063                                    } => {
4064                                        get_attrs_count.fetch_add(1, Ordering::SeqCst);
4065                                        let mutable_attrs =
4066                                            fio::MutableNodeAttributes { ..Default::default() };
4067                                        let immutable_attrs = fio::ImmutableNodeAttributes {
4068                                            id: Some(1),
4069                                            link_count: Some(1),
4070                                            ..Default::default()
4071                                        };
4072                                        responder
4073                                            .send(Ok((&mutable_attrs, &immutable_attrs)))
4074                                            .unwrap();
4075                                    }
4076                                    fio::DirectoryRequest::UpdateAttributes {
4077                                        payload: _,
4078                                        responder,
4079                                    } => {
4080                                        responder.send(Ok(())).unwrap();
4081                                    }
4082                                    fio::DirectoryRequest::Close { responder } => {
4083                                        responder.send(Ok(())).unwrap();
4084                                    }
4085                                    _ => {
4086                                        panic!("Unexpected request: {:?}", request)
4087                                    }
4088                                }
4089                            }
4090                        }));
4091                    }
4092                    fio::DirectoryRequest::Close { responder } => {
4093                        responder.send(Ok(())).unwrap();
4094                    }
4095                    fio::DirectoryRequest::QueryFilesystem { responder } => {
4096                        responder.send(0i32, None).unwrap();
4097                    }
4098                    _ => panic!("Unexpected request: {:?}", request),
4099                }
4100            }
4101
4102            for sub_task in sub_tasks {
4103                let _ = sub_task.await;
4104            }
4105        });
4106
4107        spawn_kernel_and_run(async move |locked, current_task| {
4108            let fs = RemoteFs::new_fs(
4109                locked,
4110                &current_task.kernel(),
4111                client.into_channel(),
4112                FileSystemOptions { source: FlyByteStr::new(b"."), ..Default::default() },
4113                fio::PERM_READABLE | fio::PERM_WRITABLE,
4114            )
4115            .expect("failed to mount test remote FS");
4116
4117            // 1. Initial fetch.
4118            {
4119                let _info = fs
4120                    .root()
4121                    .node
4122                    .fetch_and_refresh_info(locked, current_task)
4123                    .expect("fetch failed");
4124            }
4125            assert_eq!(get_attrs_count.load(Ordering::SeqCst), 1);
4126
4127            // 2. Second time should use cached information.
4128            {
4129                let _info = fs
4130                    .root()
4131                    .node
4132                    .fetch_and_refresh_info(locked, current_task)
4133                    .expect("fetch failed");
4134            }
4135            assert_eq!(get_attrs_count.load(Ordering::SeqCst), 1);
4136
4137            // 3. Update attributes. This should dirty the node.
4138            fs.root()
4139                .node
4140                .update_attributes(locked, current_task, |attrs| {
4141                    attrs.time_modify += UtcDuration::from_seconds(1);
4142                    Ok(())
4143                })
4144                .expect("update_attributes failed");
4145
4146            // 4. Fetch again. Should trigger a request.
4147            {
4148                let _info = fs
4149                    .root()
4150                    .node
4151                    .fetch_and_refresh_info(locked, current_task)
4152                    .expect("fetch failed");
4153            }
4154            assert_eq!(get_attrs_count.load(Ordering::SeqCst), 2);
4155        })
4156        .await;
4157
4158        server_task.await;
4159    }
4160
4161    trait AsyncBarrier {
4162        async fn async_wait(&self);
4163    }
4164
4165    impl AsyncBarrier for Arc<Barrier> {
4166        async fn async_wait(&self) {
4167            let this = self.clone();
4168            fasync::unblock(move || this.wait()).await;
4169        }
4170    }
4171
4172    #[derive(Default)]
4173    struct MockRemoteFs {
4174        get_attrs_count: AtomicU32,
4175        file_size: AtomicUsize,
4176        write_offsets: Mutex<Vec<u64>>,
4177        data: Mutex<Vec<u8>>,
4178        get_attrs_hook: Mutex<Option<futures::future::BoxFuture<'static, ()>>>,
4179        write_hook: Mutex<Option<futures::future::BoxFuture<'static, ()>>>,
4180    }
4181
4182    impl MockRemoteFs {
4183        async fn handle_file_requests(
4184            self: Arc<Self>,
4185            mut stream: fio::FileRequestStream,
4186            control_handle: fio::FileControlHandle,
4187        ) {
4188            let size = self.file_size.load(Ordering::SeqCst) as u64;
4189            let info = fio::FileInfo {
4190                attributes: Some(fio::NodeAttributes2 {
4191                    mutable_attributes: fio::MutableNodeAttributes { ..Default::default() },
4192                    immutable_attributes: fio::ImmutableNodeAttributes {
4193                        id: Some(2),
4194                        link_count: Some(1),
4195                        content_size: Some(size),
4196                        storage_size: Some(size),
4197                        ..Default::default()
4198                    },
4199                }),
4200                ..Default::default()
4201            };
4202            let _ = control_handle.send_on_representation(fio::Representation::File(info));
4203            while let Some(Ok(request)) = stream.next().await {
4204                match request {
4205                    fio::FileRequest::GetAttributes { responder, .. } => {
4206                        // Spawn a separate task so that we can handle concurrent calls.
4207                        let this = self.clone();
4208                        fasync::Task::spawn(async move {
4209                            this.get_attrs_count.fetch_add(1, Ordering::SeqCst);
4210                            let size = this.file_size.load(Ordering::SeqCst) as u64;
4211                            let hook = this.get_attrs_hook.lock().take();
4212                            if let Some(hook) = hook {
4213                                hook.await;
4214                            }
4215                            responder
4216                                .send(Ok((
4217                                    &fio::MutableNodeAttributes { ..Default::default() },
4218                                    &fio::ImmutableNodeAttributes {
4219                                        id: Some(2),
4220                                        link_count: Some(1),
4221                                        content_size: Some(size),
4222                                        storage_size: Some(size),
4223                                        ..Default::default()
4224                                    },
4225                                )))
4226                                .unwrap();
4227                        })
4228                        .detach();
4229                    }
4230                    fio::FileRequest::ReadAt { count, offset, responder } => {
4231                        let data = self.data.lock();
4232                        let start = std::cmp::min(offset as usize, data.len());
4233                        let end = std::cmp::min(start + count as usize, data.len());
4234                        responder.send(Ok(&data[start..end])).unwrap();
4235                    }
4236                    fio::FileRequest::WriteAt { offset, data, responder, .. } => {
4237                        // Spawn a separate task so that we can test concurrent writes.
4238                        let self_clone = Arc::clone(&self);
4239                        fasync::Task::spawn(async move {
4240                            self_clone.write_offsets.lock().push(offset);
4241                            let end = offset as usize + data.len();
4242                            {
4243                                let mut mock_data = self_clone.data.lock();
4244                                if end > mock_data.len() {
4245                                    mock_data.resize(end, 0);
4246                                }
4247                                mock_data[offset as usize..end].copy_from_slice(&data);
4248                            }
4249                            let mut current_size = self_clone.file_size.load(Ordering::SeqCst);
4250                            while end > current_size {
4251                                match self_clone.file_size.compare_exchange_weak(
4252                                    current_size,
4253                                    end,
4254                                    Ordering::SeqCst,
4255                                    Ordering::SeqCst,
4256                                ) {
4257                                    Ok(_) => break,
4258                                    Err(actual) => current_size = actual,
4259                                }
4260                            }
4261                            let hook = self_clone.write_hook.lock().take();
4262                            if let Some(hook) = hook {
4263                                hook.await;
4264                            }
4265                            responder.send(Ok(data.len() as u64)).unwrap();
4266                        })
4267                        .detach();
4268                    }
4269                    fio::FileRequest::Resize { length, responder, .. } => {
4270                        self.file_size.store(length as usize, Ordering::SeqCst);
4271                        responder.send(Ok(())).unwrap();
4272                    }
4273                    fio::FileRequest::Seek { origin, offset, responder } => {
4274                        let new_offset = match origin {
4275                            fio::SeekOrigin::Start => offset as u64,
4276                            fio::SeekOrigin::Current => 0,
4277                            fio::SeekOrigin::End => {
4278                                (self.file_size.load(Ordering::SeqCst) as i64 + offset) as u64
4279                            }
4280                        };
4281                        responder.send(Ok(new_offset)).unwrap();
4282                    }
4283                    fio::FileRequest::Close { responder } => {
4284                        responder.send(Ok(())).unwrap();
4285                    }
4286                    _ => {}
4287                }
4288            }
4289        }
4290
4291        async fn handle_directory_requests(
4292            self: Arc<Self>,
4293            mut stream: fio::DirectoryRequestStream,
4294            control_handle: fio::DirectoryControlHandle,
4295        ) {
4296            let info = fio::DirectoryInfo {
4297                attributes: Some(fio::NodeAttributes2 {
4298                    mutable_attributes: fio::MutableNodeAttributes { ..Default::default() },
4299                    immutable_attributes: fio::ImmutableNodeAttributes {
4300                        id: Some(1),
4301                        link_count: Some(1),
4302                        ..Default::default()
4303                    },
4304                }),
4305                ..Default::default()
4306            };
4307            let _ = control_handle.send_on_representation(fio::Representation::Directory(info));
4308            let mut file_tasks = Vec::new();
4309            while let Some(Ok(request)) = stream.next().await {
4310                match request {
4311                    fio::DirectoryRequest::Open { path, object, .. } => {
4312                        if path == "file" {
4313                            let self_clone = Arc::clone(&self);
4314                            file_tasks.push(fasync::Task::spawn(async move {
4315                                let (stream, control_handle) =
4316                                    ServerEnd::<fio::FileMarker>::new(object)
4317                                        .into_stream_and_control_handle();
4318                                self_clone.handle_file_requests(stream, control_handle).await;
4319                            }));
4320                        }
4321                    }
4322                    fio::DirectoryRequest::Close { responder } => {
4323                        responder.send(Ok(())).unwrap();
4324                    }
4325                    _ => {}
4326                }
4327            }
4328            for task in file_tasks {
4329                let _ = task.await;
4330            }
4331        }
4332
4333        async fn run(self: Arc<Self>, mut stream: fio::DirectoryRequestStream) {
4334            let mut sub_tasks = Vec::new();
4335            while let Some(Ok(request)) = stream.next().await {
4336                match request {
4337                    fio::DirectoryRequest::Open { path, object, .. } => {
4338                        if path == "." {
4339                            let self_clone = Arc::clone(&self);
4340                            sub_tasks.push(fasync::Task::spawn(async move {
4341                                let (stream, control_handle) =
4342                                    ServerEnd::<fio::DirectoryMarker>::new(object)
4343                                        .into_stream_and_control_handle();
4344                                self_clone.handle_directory_requests(stream, control_handle).await;
4345                            }));
4346                        }
4347                    }
4348                    fio::DirectoryRequest::Close { responder } => {
4349                        responder.send(Ok(())).unwrap();
4350                    }
4351                    fio::DirectoryRequest::QueryFilesystem { responder } => {
4352                        responder.send(0i32, None).unwrap();
4353                    }
4354                    _ => {}
4355                }
4356            }
4357            for sub_task in sub_tasks {
4358                let _ = sub_task.await;
4359            }
4360        }
4361    }
4362
4363    #[::fuchsia::test]
4364    async fn test_get_size_uses_cache_unless_truncated() {
4365        let (client, stream) = create_request_stream::<fio::DirectoryMarker>();
4366        let state = Arc::new(MockRemoteFs::default());
4367
4368        let server_task = fasync::Task::spawn(Arc::clone(&state).run(stream));
4369
4370        spawn_kernel_and_run(async move |locked, current_task| {
4371            let fs = RemoteFs::new_fs(
4372                locked,
4373                &current_task.kernel(),
4374                client.into_channel(),
4375                FileSystemOptions { source: FlyByteStr::new(b"."), ..Default::default() },
4376                fio::PERM_READABLE | fio::PERM_WRITABLE,
4377            )
4378            .expect("failed to mount test remote FS");
4379
4380            let ns = Namespace::new(fs);
4381            let root = ns.root();
4382
4383            let mut context = LookupContext::default();
4384            let file_node = root
4385                .lookup_child(locked, current_task, &mut context, "file".into())
4386                .expect("lookup failed");
4387
4388            // 1. Initial get_size.
4389            assert_eq!(state.get_attrs_count.load(Ordering::SeqCst), 0);
4390            {
4391                let _size =
4392                    file_node.entry.node.get_size(locked, current_task).expect("get_size failed");
4393            }
4394            assert_eq!(state.get_attrs_count.load(Ordering::SeqCst), 0);
4395
4396            // 2. Open in append mode and write.
4397            let file_handle = file_node
4398                .open(
4399                    locked,
4400                    current_task,
4401                    OpenFlags::RDWR | OpenFlags::APPEND,
4402                    AccessCheck::default(),
4403                )
4404                .expect("open failed");
4405
4406            {
4407                let mut data = VecInputBuffer::new(b"foo");
4408                let written =
4409                    file_handle.write(locked, current_task, &mut data).expect("write failed");
4410                assert_eq!(written, 3);
4411            }
4412            assert_eq!(
4413                file_node.entry.node.get_size(locked, current_task).expect("get_size failed"),
4414                3
4415            );
4416            assert_eq!(state.get_attrs_count.load(Ordering::SeqCst), 0);
4417            assert_eq!(*state.write_offsets.lock(), vec![0]);
4418
4419            // 3. Truncate. This should invalidate the cache.
4420            file_node
4421                .entry
4422                .node
4423                .truncate(locked, current_task, &file_node.mount, 0)
4424                .expect("truncate failed");
4425            assert_eq!(state.file_size.load(Ordering::SeqCst), 0);
4426
4427            // 4. get_size again. Should trigger a request.
4428            {
4429                let size =
4430                    file_node.entry.node.get_size(locked, current_task).expect("get_size failed");
4431                assert_eq!(size, 0);
4432            }
4433            assert_eq!(state.get_attrs_count.load(Ordering::SeqCst), 1);
4434
4435            // 5. Append again. It should append at offset 0.
4436            {
4437                let mut data = VecInputBuffer::new(b"bar");
4438                let written =
4439                    file_handle.write(locked, current_task, &mut data).expect("write failed");
4440                assert_eq!(written, 3);
4441            }
4442            assert_eq!(
4443                file_node.entry.node.get_size(locked, current_task).expect("get_size failed"),
4444                3
4445            );
4446            // write calls seek(End, 0) which calls get_size, which uses the cache if it was just
4447            // refreshed.
4448            assert_eq!(state.get_attrs_count.load(Ordering::SeqCst), 1);
4449            assert_eq!(*state.write_offsets.lock(), vec![0, 0]);
4450
4451            // 6. Truncate to 10 and append.
4452            file_node
4453                .entry
4454                .node
4455                .truncate(locked, current_task, &file_node.mount, 10)
4456                .expect("truncate failed");
4457            {
4458                let mut data = VecInputBuffer::new(b"baz");
4459                let written =
4460                    file_handle.write(locked, current_task, &mut data).expect("write failed");
4461                assert_eq!(written, 3);
4462            }
4463            // write calls seek(End, 0). Since truncate was called, cache is invalid.
4464            assert_eq!(state.get_attrs_count.load(Ordering::SeqCst), 2);
4465            assert_eq!(
4466                file_node.entry.node.get_size(locked, current_task).expect("get_size failed"),
4467                13
4468            );
4469            assert_eq!(*state.write_offsets.lock(), vec![0, 0, 10]);
4470        })
4471        .await;
4472
4473        server_task.await;
4474    }
4475
4476    #[::fuchsia::test]
4477    async fn test_get_size_during_refresh_after_truncate() {
4478        let (client, stream) = create_request_stream::<fio::DirectoryMarker>();
4479        let state = Arc::new(MockRemoteFs::default());
4480
4481        let server_task = fasync::Task::spawn(Arc::clone(&state).run(stream));
4482
4483        spawn_kernel_and_run(async move |locked, current_task| {
4484            let fs = RemoteFs::new_fs(
4485                locked,
4486                &current_task.kernel(),
4487                client.into_channel(),
4488                FileSystemOptions { source: FlyByteStr::new(b"."), ..Default::default() },
4489                fio::PERM_READABLE | fio::PERM_WRITABLE,
4490            )
4491            .expect("failed to mount test remote FS");
4492
4493            let ns = Namespace::new(fs);
4494            let root = ns.root();
4495
4496            let mut context = LookupContext::default();
4497            let file_node = root
4498                .lookup_child(locked, current_task, &mut context, "file".into())
4499                .expect("lookup failed");
4500
4501            // Fill cache.
4502            assert_eq!(
4503                file_node.entry.node.get_size(locked, current_task).expect("get_size failed"),
4504                0
4505            );
4506
4507            // Truncate to 10.
4508            file_node
4509                .entry
4510                .node
4511                .truncate(locked, current_task, &file_node.mount, 10)
4512                .expect("truncate failed");
4513
4514            // Set barrier to pause GetAttributes.
4515            let barrier = Arc::new(Barrier::new(2));
4516            {
4517                let barrier = barrier.clone();
4518                *state.get_attrs_hook.lock() = Some(Box::pin(async move {
4519                    barrier.async_wait().await;
4520                    barrier.async_wait().await;
4521                }));
4522            }
4523
4524            // Spawn thread to call get_size. It will pause when it hits the first barrier.
4525            let file_node_clone = file_node.clone();
4526            let (result1, request) = SpawnRequestBuilder::new()
4527                .with_sync_closure(move |locked, current_task| {
4528                    let size = file_node_clone
4529                        .entry
4530                        .node
4531                        .get_size(locked, current_task)
4532                        .expect("get_size failed");
4533                    assert_eq!(size, 10);
4534                })
4535                .build_with_async_result();
4536            current_task.kernel().kthreads.spawner().spawn_from_request(request);
4537
4538            // Wait for the first barrier to be reached.
4539            barrier.async_wait().await;
4540
4541            // Set up the next request so it unblocks the first request.
4542            *state.get_attrs_hook.lock() =
4543                Some(Box::pin(async move { barrier.async_wait().await }));
4544
4545            // Another get_size call should not use cached size (0) while refresh is in progress.
4546            let (result2, request) = SpawnRequestBuilder::new()
4547                .with_sync_closure(move |locked, current_task| {
4548                    let size = file_node
4549                        .entry
4550                        .node
4551                        .get_size(locked, current_task)
4552                        .expect("get_size failed");
4553                    assert_eq!(size, 10);
4554                })
4555                .build_with_async_result();
4556            current_task.kernel().kthreads.spawner().spawn_from_request(request);
4557
4558            result1.await.unwrap();
4559            result2.await.unwrap();
4560        })
4561        .await;
4562
4563        server_task.await;
4564    }
4565
4566    #[::fuchsia::test]
4567    async fn test_get_size_during_outstanding_write() {
4568        let (client, stream) = create_request_stream::<fio::DirectoryMarker>();
4569        let state = Arc::new(MockRemoteFs::default());
4570
4571        let server_task = fasync::Task::spawn(Arc::clone(&state).run(stream));
4572
4573        spawn_kernel_and_run(async move |locked, current_task| {
4574            let fs = RemoteFs::new_fs(
4575                locked,
4576                &current_task.kernel(),
4577                client.into_channel(),
4578                FileSystemOptions { source: FlyByteStr::new(b"."), ..Default::default() },
4579                fio::PERM_READABLE | fio::PERM_WRITABLE,
4580            )
4581            .expect("failed to mount test remote FS");
4582
4583            let ns = Namespace::new(fs);
4584            let root = ns.root();
4585
4586            let mut context = LookupContext::default();
4587            let file_node = root
4588                .lookup_child(locked, current_task, &mut context, "file".into())
4589                .expect("lookup failed");
4590
4591            // Open the file.
4592            let file_handle = file_node
4593                .open(locked, current_task, OpenFlags::RDWR, AccessCheck::default())
4594                .expect("open failed");
4595
4596            // Set hook to stall the write response.
4597            let barrier = Arc::new(Barrier::new(2));
4598            {
4599                let barrier = barrier.clone();
4600                *state.write_hook.lock() = Some(Box::pin(async move {
4601                    barrier.async_wait().await;
4602                    barrier.async_wait().await;
4603                }));
4604            }
4605
4606            // Start write in another thread.
4607            let file_handle_clone = file_handle.clone();
4608            current_task.kernel().kthreads.spawner().spawn_from_request(
4609                SpawnRequestBuilder::new()
4610                    .with_sync_closure(move |locked, current_task| {
4611                        let mut data = VecInputBuffer::new(b"hello");
4612                        file_handle_clone
4613                            .write(locked, current_task, &mut data)
4614                            .expect("write failed");
4615                    })
4616                    .build(),
4617            );
4618
4619            // Wait until the mock has processed the write and hit the hook.
4620            barrier.async_wait().await;
4621
4622            // On this thread, verify that a read sees the new data.
4623            {
4624                let mut data = VecOutputBuffer::new(5);
4625                let read =
4626                    file_handle.read_at(locked, current_task, 0, &mut data).expect("read failed");
4627                assert_eq!(read, 5);
4628                assert_eq!(data.data(), b"hello");
4629            }
4630
4631            // Now call get_size and it should see the correct size.
4632            let size =
4633                file_node.entry.node.get_size(locked, current_task).expect("get_size failed");
4634            assert_eq!(
4635                size, 5,
4636                "get_size should return the updated size even if a write is outstanding"
4637            );
4638
4639            // Unblock the write.
4640            barrier.async_wait().await;
4641        })
4642        .await;
4643
4644        server_task.await;
4645    }
4646
4647    #[test]
4648    fn test_info_state_initial_state() {
4649        let state = InfoState::new(true); // dirty
4650        assert_eq!(state.0.load(Ordering::Relaxed), 0);
4651
4652        let state = InfoState::new(false); // in sync
4653        assert_eq!(state.0.load(Ordering::Relaxed), InfoState::IN_SYNC);
4654    }
4655
4656    #[test]
4657    fn test_info_state_dirty_op_guard() {
4658        let state = InfoState::new(false);
4659        {
4660            let _guard = state.dirty_op_guard(false);
4661            assert_eq!(state.0.load(Ordering::Relaxed), 1); // IN_SYNC bit cleared, count 1
4662            assert!(!state.is_size_accurate());
4663        }
4664        assert_eq!(state.0.load(Ordering::Relaxed), 0);
4665        assert!(state.is_size_accurate());
4666
4667        {
4668            let _guard = state.dirty_op_guard(true);
4669            assert_eq!(state.0.load(Ordering::Relaxed), InfoState::TRUNCATED | 1);
4670            assert!(!state.is_size_accurate());
4671        }
4672        assert_eq!(state.0.load(Ordering::Relaxed), InfoState::TRUNCATED);
4673        assert!(!state.is_size_accurate());
4674
4675        {
4676            let _guard1 = state.dirty_op_guard(true);
4677            let _guard2 = state.dirty_op_guard(true);
4678            assert_eq!(state.0.load(Ordering::Relaxed), InfoState::TRUNCATED | 2);
4679            assert!(!state.is_size_accurate());
4680        }
4681        assert_eq!(state.0.load(Ordering::Relaxed), InfoState::TRUNCATED);
4682        assert!(!state.is_size_accurate());
4683    }
4684
4685    #[test]
4686    fn test_info_state_refresh_clears_truncated() {
4687        let state = InfoState::new(true);
4688        // Set TRUNCATED bit.
4689        {
4690            let _guard = state.dirty_op_guard(true);
4691        }
4692        assert_eq!(state.0.load(Ordering::Relaxed), InfoState::TRUNCATED);
4693
4694        let info =
4695            DynamicLockDepRwLock::new::<starnix_sync::FsNodeInfoLevel>(FsNodeInfo::default());
4696        state.maybe_refresh(&info, |_| Ok(()), |_| unreachable!()).unwrap();
4697
4698        assert_eq!(state.0.load(Ordering::Relaxed), InfoState::IN_SYNC);
4699        assert!(state.is_size_accurate());
4700    }
4701
4702    #[test]
4703    fn test_info_state_maybe_refresh_success() {
4704        let state = InfoState::new(true);
4705        let info =
4706            DynamicLockDepRwLock::new::<starnix_sync::FsNodeInfoLevel>(FsNodeInfo::default());
4707
4708        let res = state.maybe_refresh(&info, |_| Ok(42), |_| unreachable!());
4709        assert_eq!(res.unwrap(), 42);
4710        assert_eq!(state.0.load(Ordering::Relaxed), InfoState::IN_SYNC);
4711    }
4712
4713    #[test]
4714    fn test_info_state_maybe_refresh_error() {
4715        let state = InfoState::new(true);
4716        let info =
4717            DynamicLockDepRwLock::new::<starnix_sync::FsNodeInfoLevel>(FsNodeInfo::default());
4718
4719        let res: Result<u32, Errno> =
4720            state.maybe_refresh(&info, |_| error!(EIO), |_| unreachable!());
4721        assert!(res.is_err());
4722        assert_eq!(state.0.load(Ordering::Relaxed), 0); // Still dirty
4723    }
4724
4725    #[test]
4726    fn test_info_state_maybe_refresh_not_needed() {
4727        let state = InfoState::new(false); // in sync
4728        let info =
4729            DynamicLockDepRwLock::new::<starnix_sync::FsNodeInfoLevel>(FsNodeInfo::default());
4730        let res = state.maybe_refresh(&info, |_| unreachable!(), |_| Ok(123));
4731        assert_eq!(res.unwrap(), 123);
4732    }
4733
4734    #[test]
4735    fn test_info_state_concurrent_dirty_op_during_refresh() {
4736        let state = InfoState::new(true);
4737        let info =
4738            DynamicLockDepRwLock::new::<starnix_sync::FsNodeInfoLevel>(FsNodeInfo::default());
4739
4740        state
4741            .maybe_refresh(
4742                &info,
4743                |_| {
4744                    // Simulate a dirty op starting while refresh is in progress
4745                    let _guard = state.dirty_op_guard(false);
4746                    assert_eq!(state.0.load(Ordering::Relaxed), InfoState::PENDING_REFRESH | 1);
4747                    Ok(())
4748                },
4749                |_| unreachable!(),
4750            )
4751            .unwrap();
4752
4753        assert_eq!(state.0.load(Ordering::Relaxed), 0);
4754    }
4755
4756    #[::fuchsia::test]
4757    async fn test_sync() {
4758        let fixture = TestFixture::new().await;
4759        let (server, client) = zx::Channel::create();
4760        fixture.root().clone(server.into()).expect("clone failed");
4761
4762        spawn_kernel_and_run(async move |locked, current_task| {
4763            let kernel = current_task.kernel();
4764            let rights = fio::PERM_READABLE | fio::PERM_WRITABLE;
4765            let fs = RemoteFs::new_fs(
4766                locked,
4767                &kernel,
4768                client,
4769                FileSystemOptions { source: FlyByteStr::new(b"/"), ..Default::default() },
4770                rights,
4771            )
4772            .expect("new_fs failed");
4773            let ns = Namespace::new(fs);
4774            current_task.fs().set_umask(FileMode::from_bits(0));
4775            let root = ns.root();
4776
4777            const REG_MODE: FileMode = FileMode::from_bits(FileMode::IFREG.bits());
4778            root.create_node(locked, &current_task, "file".into(), REG_MODE, DeviceId::NONE)
4779                .expect("create_node failed");
4780            let mut context = LookupContext::new(SymlinkMode::NoFollow);
4781            let reg_node = root
4782                .lookup_child(locked, &current_task, &mut context, "file".into())
4783                .expect("lookup_child failed");
4784
4785            // sync should delegate to zxio and succeed
4786            reg_node
4787                .entry
4788                .node
4789                .ops()
4790                .sync(&reg_node.entry.node, &current_task)
4791                .expect("sync failed");
4792        })
4793        .await;
4794        fixture.close().await;
4795    }
4796
4797    #[::fuchsia::test]
4798    async fn test_msync_propagates_to_fxfs() {
4799        use crate::mm::MemoryAccessor;
4800        use crate::mm::syscalls::{sys_mmap, sys_msync};
4801        use crate::vfs::FdFlags;
4802        use starnix_uapi::user_address::UserAddress;
4803        use starnix_uapi::{MAP_SHARED, MS_SYNC, PROT_READ, PROT_WRITE};
4804
4805        // Counter to track Fxfs transactions
4806        let commit_count = Arc::new(AtomicUsize::new(0));
4807        let commit_count_clone = commit_count.clone();
4808
4809        // Open fixture with pre_commit_hook
4810        let fixture = TestFixture::open(
4811            DeviceHolder::new(FakeDevice::new(1024 * 1024, 512)),
4812            TestFixtureOptions {
4813                format: true,
4814                as_blob: false,
4815                encrypted: true,
4816                pre_commit_hook: Some(Box::new(move |_transaction| {
4817                    commit_count_clone.fetch_add(1, Ordering::SeqCst);
4818                    Ok(())
4819                })),
4820            },
4821        )
4822        .await;
4823
4824        let (server, client) = zx::Channel::create();
4825        fixture.root().clone(server.into()).expect("clone channel");
4826
4827        spawn_kernel_and_run(async move |locked, current_task| {
4828            // Setup RemoteFs
4829            let rights = fio::PERM_READABLE | fio::PERM_WRITABLE;
4830            let fs = RemoteFs::new_fs(
4831                locked,
4832                current_task.kernel(),
4833                client,
4834                FileSystemOptions { source: FlyByteStr::new(b"/test"), ..Default::default() },
4835                rights,
4836            )
4837            .expect("new_fs");
4838            let ns = Namespace::new(fs);
4839            let root = ns.root();
4840
4841            // Create and Open a file
4842            let node = root
4843                .create_node(
4844                    locked,
4845                    &current_task,
4846                    "test_file".into(),
4847                    mode!(IFREG, 0o666),
4848                    DeviceId::NONE,
4849                )
4850                .expect("create_node");
4851            let file_handle = node
4852                .open(locked, &current_task, OpenFlags::RDWR, AccessCheck::default())
4853                .expect("open");
4854            let fd = current_task
4855                .running_state()
4856                .files
4857                .add(locked, current_task, file_handle, FdFlags::empty())
4858                .expect("add file");
4859
4860            // Do mmap
4861            let len = *PAGE_SIZE as usize * 4;
4862            let mmap_addr = sys_mmap(
4863                locked,
4864                current_task,
4865                UserAddress::default(),
4866                len,
4867                PROT_READ | PROT_WRITE,
4868                MAP_SHARED,
4869                fd,
4870                0,
4871            )
4872            .expect("mmap");
4873
4874            // Modify memory (multiple pages)
4875            for i in 0..4 {
4876                let data = [0xAAu8; 1];
4877                current_task
4878                    .write_memory((mmap_addr + (i * *PAGE_SIZE as usize)).unwrap(), &data)
4879                    .expect("write memory");
4880            }
4881
4882            // Capture commit count before msync
4883            let commits_before_msync = commit_count.load(Ordering::SeqCst);
4884
4885            // invoke msync()
4886            sys_msync(locked, current_task, mmap_addr, len, MS_SYNC).expect("msync");
4887
4888            // Verify msync results
4889            let final_commits = commit_count.load(Ordering::SeqCst);
4890            assert!(
4891                final_commits > commits_before_msync,
4892                "msync should trigger Fxfs transaction. commits: {} -> {}",
4893                commits_before_msync,
4894                final_commits
4895            );
4896        })
4897        .await;
4898
4899        fixture.close().await;
4900    }
4901
4902    #[::fuchsia::test]
4903    async fn test_get_size() {
4904        let fixture = TestFixture::new().await;
4905        let (server, client) = zx::Channel::create();
4906        fixture.root().clone(server.into()).expect("clone failed");
4907
4908        spawn_kernel_and_run(async move |locked, current_task| {
4909            let kernel = current_task.kernel();
4910            let rights = fio::PERM_READABLE | fio::PERM_WRITABLE;
4911            let fs = RemoteFs::new_fs(
4912                locked,
4913                &kernel,
4914                client,
4915                FileSystemOptions { source: FlyByteStr::new(b"/"), ..Default::default() },
4916                rights,
4917            )
4918            .expect("new_fs failed");
4919            let ns = Namespace::new(fs);
4920            let root = ns.root();
4921
4922            const REG_MODE: FileMode = FileMode::from_bits(FileMode::IFREG.bits() | 0o666);
4923            let node = root
4924                .create_node(locked, &current_task, "file".into(), REG_MODE, DeviceId::NONE)
4925                .expect("create_node failed");
4926            let file = node
4927                .open(locked, &current_task, OpenFlags::RDWR, AccessCheck::default())
4928                .expect("open failed");
4929
4930            // Initial size should be 0.
4931            assert_eq!(
4932                node.entry.node.get_size(locked, &current_task).expect("get_size failed"),
4933                0
4934            );
4935
4936            // Write some data.
4937            let mut data = VecInputBuffer::new(b"hello");
4938            file.write(locked, &current_task, &mut data).expect("write failed");
4939
4940            // Size should be 5.
4941            assert_eq!(
4942                node.entry.node.get_size(locked, &current_task).expect("get_size failed"),
4943                5
4944            );
4945
4946            // Truncate to 10.
4947            node.truncate(locked, &current_task, 10).expect("truncate failed");
4948
4949            // Size should be 10.
4950            assert_eq!(
4951                node.entry.node.get_size(locked, &current_task).expect("get_size failed"),
4952                10
4953            );
4954
4955            // Truncate to 3.
4956            node.truncate(locked, &current_task, 3).expect("truncate failed");
4957
4958            // Size should be 3.
4959            assert_eq!(
4960                node.entry.node.get_size(locked, &current_task).expect("get_size failed"),
4961                3
4962            );
4963        })
4964        .await;
4965        fixture.close().await;
4966    }
4967}