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