Skip to main content

starnix_core/fs/fuchsia/
remote_bundle.rs

1// Copyright 2023 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::update_info_from_attrs;
6use crate::mm::memory::MemoryObject;
7use crate::mm::{ProtectionFlags, VMEX_RESOURCE};
8use crate::task::{CurrentTask, EventHandler, Kernel, WaitCanceler, Waiter};
9use crate::vfs::fs_args::MountParams;
10use crate::vfs::{
11    CacheConfig, CacheMode, DEFAULT_BYTES_PER_BLOCK, DirEntry, DirectoryEntryType, DirentSink,
12    FileObject, FileOps, FileSystem, FileSystemHandle, FileSystemOps, FileSystemOptions, FileToken,
13    FsNode, FsNodeHandle, FsNodeInfo, FsNodeOps, FsStr, FsString, InputBuffer, OutputBuffer,
14    SeekTarget, StatxFlags, SymlinkTarget, ValueOrSize, default_seek, emit_dotdot,
15    fileops_impl_directory, fileops_impl_noop_sync, fileops_impl_seekable,
16    fs_node_impl_dir_readonly, fs_node_impl_not_dir, fs_node_impl_symlink,
17};
18use anyhow::{Error, anyhow};
19use ext4_metadata::{Metadata, Node, NodeInfo};
20use fidl_fuchsia_io as fio;
21use once_cell::sync::OnceCell;
22use starnix_logging::{impossible_error, log_warn, track_stub};
23use starnix_sync::{
24    DynamicLockDepRwLock, LockDepMutex, LockDepReadGuard, LockDepWriteGuard, RemoteBundleInnerLock,
25};
26use starnix_types::vfs::default_statfs;
27use starnix_uapi::auth::FsCred;
28use starnix_uapi::errors::{Errno, SourceContext};
29use starnix_uapi::file_mode::FileMode;
30use starnix_uapi::mount_flags::FileSystemFlags;
31use starnix_uapi::open_flags::OpenFlags;
32use starnix_uapi::vfs::FdEvents;
33use starnix_uapi::{errno, error, from_status_like_fdio, off_t, statfs};
34use std::io::Read;
35use std::sync::Arc;
36use std::sync::atomic::Ordering;
37use syncio::{zxio_node_attr_has_t, zxio_node_attributes_t};
38
39const REMOTE_BUNDLE_NODE_LRU_CAPACITY: usize = 1024;
40
41/// RemoteBundle is a remote, immutable filesystem that stores additional metadata that would
42/// otherwise not be available.  The metadata exists in the "metadata.v1" file, which contains
43/// directory, symbolic link and extended attribute information.  Only the content for files are
44/// accessed remotely as normal.
45pub struct RemoteBundle {
46    metadata: Metadata,
47    root: fio::DirectorySynchronousProxy,
48    rights: fio::Flags,
49}
50
51impl RemoteBundle {
52    /// Returns a new RemoteBundle filesystem whose path is looked up in the incoming namespace.
53    pub fn new_fs(
54        current_task: &CurrentTask,
55        mut options: FileSystemOptions,
56    ) -> Result<FileSystemHandle, Errno> {
57        let kernel = current_task.kernel();
58        let requested_path = std::str::from_utf8(&options.source)
59            .map_err(|_| errno!(EINVAL, "source path is not utf8"))?;
60        let (root_proxy, subdir) =
61            kernel.open_ns_dir(requested_path, fio::Flags::PROTOCOL_DIRECTORY)?;
62        options.source = subdir.into();
63        Self::new_fs_in_base(
64            current_task.kernel(),
65            &root_proxy,
66            options,
67            fio::PERM_READABLE | fio::PERM_EXECUTABLE,
68        )
69        .map_err(|e| errno!(EIO, format!("failed to mount remote bundle: {e}")))
70    }
71
72    /// Returns a new RemoteBundle filesystem that can be found at `options.source` relative to `base`.
73    pub fn new_fs_in_base(
74        kernel: &Kernel,
75        base: &fio::DirectorySynchronousProxy,
76        options: FileSystemOptions,
77        rights: fio::Flags,
78    ) -> Result<FileSystemHandle, Error> {
79        let (root, server_end) = fidl::endpoints::create_sync_proxy::<fio::DirectoryMarker>();
80        let path =
81            std::str::from_utf8(&options.source).map_err(|_| anyhow!("Source path is not utf8"))?;
82        base.open(path, rights, &Default::default(), server_end.into_channel())
83            .map_err(|e| anyhow!("Failed to open root: {}", e))?;
84
85        let metadata = {
86            let (file, server_end) = fidl::endpoints::create_endpoints::<fio::FileMarker>();
87            root.open(
88                "metadata.v1",
89                fio::PERM_READABLE,
90                &Default::default(),
91                server_end.into_channel(),
92            )
93            .source_context("open metadata file")?;
94            let mut file: std::fs::File = fdio::create_fd(file.into_channel().into_handle())
95                .source_context("create fd from metadata file (wrong mount path?)")?
96                .into();
97            let mut buf = Vec::new();
98            file.read_to_end(&mut buf).source_context("read metadata file")?;
99            Metadata::deserialize(&buf).source_context("deserialize metadata file")?
100        };
101
102        // Make sure the root node exists.
103        let root_node = metadata
104            .get(ext4_metadata::ROOT_INODE_NUM)
105            .ok_or_else(|| anyhow!("Root node does not exist in remote bundle"))?;
106        let root_info = to_fs_node_info(root_node);
107
108        if !rights.contains(fio::PERM_WRITABLE) {
109            options.flags.fetch_or(FileSystemFlags::RDONLY, Ordering::Relaxed);
110        }
111
112        let fs = FileSystem::new(
113            kernel,
114            CacheMode::Cached(CacheConfig { capacity: REMOTE_BUNDLE_NODE_LRU_CAPACITY }),
115            RemoteBundle { metadata, root, rights },
116            options,
117        )?;
118        fs.create_root_with_info(ext4_metadata::ROOT_INODE_NUM, DirectoryObject, root_info);
119        Ok(fs)
120    }
121
122    // Returns the bundle from the filesystem.  Panics if the filesystem isn't associated with a
123    // RemoteBundle.
124    fn from_fs(fs: &FileSystem) -> &RemoteBundle {
125        fs.downcast_ops::<RemoteBundle>().unwrap()
126    }
127
128    // Returns a reference to the node identified by `inode_num`.  Panics if the node is not found
129    // so this should only be used if the node is known to exist (e.g. the node must exist after
130    // `lookup` has run for the relevant node).
131    fn get_node(&self, inode_num: u64) -> &Node {
132        self.metadata.get(inode_num).unwrap()
133    }
134
135    fn get_xattr(&self, node: &FsNode, name: &FsStr) -> Result<ValueOrSize<Box<[u8]>>, Errno> {
136        let value = &self
137            .get_node(node.ino)
138            .extended_attributes
139            .get(&**name)
140            .ok_or_else(|| errno!(ENODATA))?[..];
141        Ok(Box::<[u8]>::from(&**value).into())
142    }
143
144    fn list_xattrs(&self, node: &FsNode) -> Result<ValueOrSize<Vec<FsString>>, Errno> {
145        Ok(self
146            .get_node(node.ino)
147            .extended_attributes
148            .keys()
149            .map(|k| FsString::from(&k[..]))
150            .collect::<Vec<_>>()
151            .into())
152    }
153}
154
155impl FileSystemOps for RemoteBundle {
156    fn statfs(&self, _fs: &FileSystem, _current_task: &CurrentTask) -> Result<statfs, Errno> {
157        const REMOTE_BUNDLE_FS_MAGIC: u32 = u32::from_be_bytes(*b"bndl");
158        let mut stats = default_statfs(REMOTE_BUNDLE_FS_MAGIC);
159        // Provide a placeholder non-zero block count to satisfy CTS StatFsTest.
160        // 100,000 blocks (approx 400MB with 4KB page size) is safe and adequate
161        // to prevent other applications from thinking the disk is full/too small.
162        track_stub!(TODO("https://fxbug.dev/540853536"), "RemoteBundle statfs block count");
163        stats.f_blocks = 100000;
164        Ok(stats)
165    }
166    fn name(&self) -> &'static FsStr {
167        "remote_bundle".into()
168    }
169    fn reconfigure(
170        &self,
171        fs: &FileSystem,
172        _current_task: &CurrentTask,
173        flags: FileSystemFlags,
174        params: &MountParams,
175    ) -> Result<(), Errno> {
176        if !params.is_empty() {
177            track_stub!(TODO("https://fxbug.dev/322875506"), "MS_REMOUNT: Updating data");
178        }
179        fs.options.flags.store(flags | FileSystemFlags::RDONLY, Ordering::Relaxed);
180        Ok(())
181    }
182}
183
184struct File {
185    inner: LockDepMutex<Inner, RemoteBundleInnerLock>,
186    exec_memory: OnceCell<Arc<MemoryObject>>,
187}
188
189enum Inner {
190    NeedsVmo(fio::FileSynchronousProxy),
191    Memory(Arc<MemoryObject>),
192}
193
194impl Inner {
195    fn get_memory(&mut self) -> Result<Arc<MemoryObject>, Errno> {
196        if let Inner::NeedsVmo(file) = &*self {
197            let memory = Arc::new(MemoryObject::from(
198                file.get_backing_memory(fio::VmoFlags::READ, zx::MonotonicInstant::INFINITE)
199                    .map_err(|err| errno!(EIO, format!("Error {err} on GetBackingMemory")))?
200                    .map_err(|s| from_status_like_fdio!(zx::Status::err_from_raw(s)))?,
201            ));
202            *self = Inner::Memory(memory);
203        }
204        let Inner::Memory(memory) = &*self else { unreachable!() };
205        Ok(memory.clone())
206    }
207}
208
209impl FsNodeOps for File {
210    fs_node_impl_not_dir!();
211
212    fn create_file_ops(
213        &self,
214        _node: &FsNode,
215        _current_task: &CurrentTask,
216        _flags: OpenFlags,
217    ) -> Result<Box<dyn FileOps>, Errno> {
218        let memory = self.inner.lock().get_memory()?;
219        let size = usize::try_from(memory.get_content_size()).unwrap();
220        Ok(Box::new(MemoryFile { memory, size }))
221    }
222
223    fn fetch_and_refresh_info<'a>(
224        &self,
225        _node: &FsNode,
226        _current_task: &CurrentTask,
227        info: &'a DynamicLockDepRwLock<FsNodeInfo>,
228        flags: StatxFlags,
229    ) -> Result<LockDepReadGuard<'a, FsNodeInfo>, Errno> {
230        if flags.contains(StatxFlags::AT_STATX_DONT_SYNC) {
231            return Ok(info.read());
232        }
233        let memory = self.inner.lock().get_memory()?;
234        let content_size = memory.get_content_size();
235        let attrs = zxio_node_attributes_t {
236            content_size: content_size,
237            // TODO(https://fxbug.dev/293607051): Plumb through storage size from underlying connection.
238            storage_size: content_size,
239            link_count: 1,
240            has: zxio_node_attr_has_t {
241                content_size: true,
242                storage_size: true,
243                link_count: true,
244                ..Default::default()
245            },
246            ..Default::default()
247        };
248        let mut info = info.write();
249        update_info_from_attrs(&mut info, &attrs);
250        Ok(LockDepWriteGuard::downgrade(info))
251    }
252
253    fn get_xattr(
254        &self,
255        node: &FsNode,
256        _current_task: &CurrentTask,
257        name: &FsStr,
258        _size: usize,
259    ) -> Result<ValueOrSize<Box<[u8]>>, Errno> {
260        let fs = node.fs();
261        let bundle = RemoteBundle::from_fs(&fs);
262        bundle.get_xattr(node, name)
263    }
264
265    fn list_xattrs(
266        &self,
267        node: &FsNode,
268        _current_task: &CurrentTask,
269        _size: usize,
270    ) -> Result<ValueOrSize<Vec<FsString>>, Errno> {
271        let fs = node.fs();
272        let bundle = RemoteBundle::from_fs(&fs);
273        bundle.list_xattrs(node)
274    }
275}
276
277// NB: This is different from MemoryRegularFile, which is designed to wrap a VMO that is owned and
278// managed by Starnix.  This struct is a wrapper around a pager-backed VMO received from the
279// filesystem backing the remote bundle.
280// MemoryRegularFile does its own content size management, which is (a) incompatible with the content
281// size management done for us by the remote filesystem, and (b) the content size is based on file
282// attributes in the case of MemoryRegularFile, which we've intentionally avoided querying here for
283// performance.  Specifically, MemoryFile is designed to be opened as fast as possible, and requiring
284// that we stat the file whilst opening it is counter to that goal.
285// Note that MemoryFile assumes that the underlying file is read-only and not resizable (which is the
286// case for remote bundles since they're stored as blobs).
287struct MemoryFile {
288    memory: Arc<MemoryObject>,
289    size: usize,
290}
291
292impl FileOps for MemoryFile {
293    fileops_impl_seekable!();
294    fileops_impl_noop_sync!();
295
296    fn read(
297        &self,
298        _file: &FileObject,
299        _current_task: &CurrentTask,
300        mut offset: usize,
301        data: &mut dyn OutputBuffer,
302    ) -> Result<usize, Errno> {
303        data.write_each(&mut |buf| {
304            let buflen = buf.len();
305            let buf = &mut buf[..std::cmp::min(self.size.saturating_sub(offset), buflen)];
306            if !buf.is_empty() {
307                self.memory
308                    .read_uninit(buf, offset as u64)
309                    .map_err(|status| from_status_like_fdio!(status))?;
310                offset += buf.len();
311            }
312            Ok(buf.len())
313        })
314    }
315
316    fn write(
317        &self,
318        _file: &FileObject,
319        _current_task: &CurrentTask,
320        _offset: usize,
321        _data: &mut dyn InputBuffer,
322    ) -> Result<usize, Errno> {
323        error!(EPERM)
324    }
325
326    fn get_memory(
327        &self,
328        file: &FileObject,
329        _current_task: &CurrentTask,
330        _length: Option<usize>,
331        prot: ProtectionFlags,
332    ) -> Result<Arc<MemoryObject>, Errno> {
333        if prot.contains(ProtectionFlags::EXEC) {
334            let file_node = file.node().downcast_ops::<File>().unwrap();
335            file_node
336                .exec_memory
337                .get_or_try_init(|| {
338                    self.memory
339                        .duplicate_handle(zx::Rights::SAME_RIGHTS)
340                        .and_then(|m| m.replace_as_executable(&VMEX_RESOURCE))
341                        .map(Arc::new)
342                        .map_err(impossible_error)
343                })
344                .cloned()
345        } else {
346            Ok(self.memory.clone())
347        }
348    }
349
350    fn get_file_token(
351        &self,
352        file: &FileObject,
353        _current_task: &CurrentTask,
354    ) -> Result<FileToken, Errno> {
355        let fs = file.node().fs();
356        let bundle = RemoteBundle::from_fs(&fs);
357        let ino = file.node().ino;
358        let (proxy, server_end) = fidl::endpoints::create_sync_proxy::<fio::FileMarker>();
359        bundle
360            .root
361            .open(&format!("{ino}"), bundle.rights, &Default::default(), server_end.into_channel())
362            .map_err(|_| errno!(EIO))?;
363        let token = proxy
364            .get_token(zx::MonotonicInstant::INFINITE)
365            .map_err(|err| errno!(EIO, format!("Error {err} on GetToken")))?
366            .map_err(|s| from_status_like_fdio!(zx::Status::err_from_raw(s)))?;
367        Ok(FileToken { token, offset: 0, keep_alive: proxy })
368    }
369
370    fn wait_async(
371        &self,
372        _file: &FileObject,
373        _current_task: &CurrentTask,
374        _waiter: &Waiter,
375        _events: FdEvents,
376        _handler: EventHandler,
377    ) -> Option<WaitCanceler> {
378        None
379    }
380
381    fn query_events(
382        &self,
383        _file: &FileObject,
384        _current_task: &CurrentTask,
385    ) -> Result<FdEvents, Errno> {
386        Ok(FdEvents::POLLIN)
387    }
388}
389
390struct DirectoryObject;
391
392impl FileOps for DirectoryObject {
393    fileops_impl_directory!();
394    fileops_impl_noop_sync!();
395
396    fn seek(
397        &self,
398        _file: &FileObject,
399        _current_task: &CurrentTask,
400        current_offset: off_t,
401        target: SeekTarget,
402    ) -> Result<off_t, Errno> {
403        default_seek(current_offset, target, || error!(EINVAL))
404    }
405
406    fn readdir(
407        &self,
408        file: &FileObject,
409        _current_task: &CurrentTask,
410        sink: &mut dyn DirentSink,
411    ) -> Result<(), Errno> {
412        emit_dotdot(file, sink)?;
413
414        let bundle = RemoteBundle::from_fs(&file.fs);
415        let child_iter = bundle
416            .get_node(file.node().ino)
417            .directory()
418            .ok_or_else(|| errno!(EIO))?
419            .children
420            .iter();
421
422        for (name, inode_num) in child_iter.skip(sink.offset() as usize - 2) {
423            let node = bundle.metadata.get(*inode_num).ok_or_else(|| errno!(EIO))?;
424            sink.add(
425                *inode_num,
426                sink.offset() + 1,
427                DirectoryEntryType::from_mode(FileMode::from_bits(node.mode.into())),
428                name.as_str().into(),
429            )?;
430        }
431
432        Ok(())
433    }
434}
435
436impl FsNodeOps for DirectoryObject {
437    fs_node_impl_dir_readonly!();
438
439    fn create_file_ops(
440        &self,
441        _node: &FsNode,
442        _current_task: &CurrentTask,
443        _flags: OpenFlags,
444    ) -> Result<Box<dyn FileOps>, Errno> {
445        Ok(Box::new(DirectoryObject))
446    }
447
448    fn lookup(
449        &self,
450        entry: &DirEntry,
451        _current_task: &CurrentTask,
452        name: &FsStr,
453    ) -> Result<FsNodeHandle, Errno> {
454        let name = std::str::from_utf8(name).map_err(|_| {
455            log_warn!("bad utf8 in pathname! remote filesystems can't handle this");
456            errno!(EINVAL)
457        })?;
458
459        let fs = entry.node.fs();
460        let bundle = RemoteBundle::from_fs(&fs);
461        let metadata = &bundle.metadata;
462        let ino = metadata
463            .lookup(entry.node.ino, name)
464            .map_err(|e| errno!(ENOENT, format!("Error: {e:?} opening {name}")))?;
465        let metadata_node = metadata.get(ino).ok_or_else(|| errno!(EIO))?;
466        let info = to_fs_node_info(metadata_node);
467
468        match metadata_node.info() {
469            NodeInfo::Symlink(_) => Ok(fs.create_node(ino, SymlinkObject, info)),
470            NodeInfo::Directory(_) => Ok(fs.create_node(ino, DirectoryObject, info)),
471            NodeInfo::File(_) => {
472                let (file, server_end) = fidl::endpoints::create_sync_proxy::<fio::FileMarker>();
473                bundle
474                    .root
475                    .open(
476                        &format!("{ino}"),
477                        bundle.rights,
478                        &Default::default(),
479                        server_end.into_channel(),
480                    )
481                    .map_err(|_| errno!(EIO))?;
482                Ok(fs.create_node(
483                    ino,
484                    File { inner: Inner::NeedsVmo(file).into(), exec_memory: OnceCell::new() },
485                    info,
486                ))
487            }
488        }
489    }
490
491    fn get_xattr(
492        &self,
493        node: &FsNode,
494        _current_task: &CurrentTask,
495        name: &FsStr,
496        _size: usize,
497    ) -> Result<ValueOrSize<Box<[u8]>>, Errno> {
498        let fs = node.fs();
499        let bundle = RemoteBundle::from_fs(&fs);
500        bundle.get_xattr(node, name)
501    }
502
503    fn list_xattrs(
504        &self,
505        node: &FsNode,
506        _current_task: &CurrentTask,
507        _size: usize,
508    ) -> Result<ValueOrSize<Vec<FsString>>, Errno> {
509        let fs = node.fs();
510        let bundle = RemoteBundle::from_fs(&fs);
511        bundle.list_xattrs(node)
512    }
513}
514
515struct SymlinkObject;
516
517impl FsNodeOps for SymlinkObject {
518    fs_node_impl_symlink!();
519
520    fn readlink(&self, node: &FsNode, _current_task: &CurrentTask) -> Result<SymlinkTarget, Errno> {
521        let fs = node.fs();
522        let bundle = RemoteBundle::from_fs(&fs);
523        let target = bundle.get_node(node.ino).symlink().ok_or_else(|| errno!(EIO))?.target.clone();
524        Ok(SymlinkTarget::Path(target.as_str().into()))
525    }
526
527    fn get_xattr(
528        &self,
529        node: &FsNode,
530        _current_task: &CurrentTask,
531        name: &FsStr,
532        _size: usize,
533    ) -> Result<ValueOrSize<Box<[u8]>>, Errno> {
534        let fs = node.fs();
535        let bundle = RemoteBundle::from_fs(&fs);
536        bundle.get_xattr(node, name)
537    }
538
539    fn list_xattrs(
540        &self,
541        node: &FsNode,
542        _current_task: &CurrentTask,
543        _size: usize,
544    ) -> Result<ValueOrSize<Vec<FsString>>, Errno> {
545        let fs = node.fs();
546        let bundle = RemoteBundle::from_fs(&fs);
547        bundle.list_xattrs(node)
548    }
549}
550
551fn to_fs_node_info(metadata_node: &ext4_metadata::Node) -> FsNodeInfo {
552    let mode = FileMode::from_bits(metadata_node.mode.into());
553    let owner = FsCred { uid: metadata_node.uid.into(), gid: metadata_node.gid.into() };
554    let mut info = FsNodeInfo::new(mode, owner);
555    // Set the information for directory and links. For file, they will be overwritten
556    // by the FsNodeOps on first access.
557    // For now, we just use some made up values. We might need to revisit this.
558    info.size = 1;
559    info.blocks = 1;
560    info.blksize = DEFAULT_BYTES_PER_BLOCK;
561    info.link_count = 1;
562    info
563}
564
565#[cfg(test)]
566mod test {
567    use crate::fs::fuchsia::RemoteBundle;
568    use crate::mm::ProtectionFlags;
569    use crate::testing::spawn_kernel_and_run_with_pkgfs;
570    use crate::vfs::buffers::VecOutputBuffer;
571    use crate::vfs::{
572        DirectoryEntryType, DirentSink, FileSystemOptions, FsStr, LookupContext, Namespace,
573        SymlinkMode, SymlinkTarget,
574    };
575    use fidl_fuchsia_io as fio;
576    use starnix_uapi::errors::Errno;
577    use starnix_uapi::file_mode::FileMode;
578    use starnix_uapi::open_flags::OpenFlags;
579    use starnix_uapi::{ino_t, off_t};
580    use std::collections::{HashMap, HashSet};
581    use std::sync::Arc;
582    use zx;
583
584    #[::fuchsia::test]
585    async fn test_read_image() {
586        spawn_kernel_and_run_with_pkgfs(async |current_task| {
587            let kernel = current_task.kernel();
588            let rights = fio::PERM_READABLE | fio::PERM_EXECUTABLE;
589            let (server, client) = zx::Channel::create();
590            fdio::open("/pkg", rights, server).expect("failed to open /pkg");
591            let fs = RemoteBundle::new_fs_in_base(
592                &kernel,
593                &fio::DirectorySynchronousProxy::new(client),
594                FileSystemOptions { source: "data/test-image".into(), ..Default::default() },
595                rights,
596            )
597            .expect("new_fs failed");
598            let ns = Namespace::new(fs);
599            let root = ns.root();
600            {
601                let info = root.entry.node.info();
602                assert_eq!(info.mode, FileMode::from_bits(0o40755));
603                assert_eq!(info.uid, 49152); // These values come from the test image generated in
604                assert_eq!(info.gid, 0); // ext4_to_pkg.
605            }
606            let mut context = LookupContext::new(SymlinkMode::NoFollow);
607
608            let test_dir = root
609                .lookup_child(&current_task, &mut context, "foo".into())
610                .expect("lookup failed");
611
612            let test_file_node = test_dir
613                .lookup_child(&current_task, &mut context, "file".into())
614                .expect("lookup failed");
615            let test_file =
616                test_file_node.open(&current_task, OpenFlags::RDONLY).expect("open failed");
617
618            let ro_mem1 = test_file
619                .get_memory(&current_task, None, ProtectionFlags::READ)
620                .expect("get_memory READ failed");
621            let rx_mem1 = test_file
622                .get_memory(&current_task, None, ProtectionFlags::READ | ProtectionFlags::EXEC)
623                .expect("get_memory READ|EXEC failed");
624            assert!(rx_mem1.get_rights().contains(zx::Rights::EXECUTE));
625
626            let test_file2 =
627                test_file_node.open(&current_task, OpenFlags::RDONLY).expect("open 2 failed");
628            let ro_mem2 = test_file2
629                .get_memory(&current_task, None, ProtectionFlags::READ)
630                .expect("get_memory READ 2 failed");
631            let rx_mem2 = test_file2
632                .get_memory(&current_task, None, ProtectionFlags::READ | ProtectionFlags::EXEC)
633                .expect("get_memory READ|EXEC 2 failed");
634            assert!(Arc::ptr_eq(&ro_mem1, &ro_mem2));
635            assert!(Arc::ptr_eq(&rx_mem1, &rx_mem2));
636
637            let mut buffer = VecOutputBuffer::new(64);
638            assert_eq!(test_file.read(&current_task, &mut buffer).expect("read failed"), 6);
639            let buffer: Vec<u8> = buffer.into();
640            assert_eq!(&buffer[..6], b"hello\n");
641
642            let file_token =
643                test_file.get_file_token(&current_task).expect("get_file_token failed");
644            assert_eq!(file_token.offset, 0);
645
646            assert_eq!(
647                &*test_file
648                    .node()
649                    .get_xattr(&current_task, &test_dir.mount, "user.a".into(), usize::MAX)
650                    .expect("get_xattr failed")
651                    .unwrap(),
652                b"apple"
653            );
654            assert_eq!(
655                &*test_file
656                    .node()
657                    .get_xattr(&current_task, &test_dir.mount, "user.b".into(), usize::MAX)
658                    .expect("get_xattr failed")
659                    .unwrap(),
660                b"ball"
661            );
662            assert_eq!(
663                test_file
664                    .node()
665                    .list_xattrs(&current_task, usize::MAX)
666                    .expect("list_xattr failed")
667                    .unwrap()
668                    .into_iter()
669                    .collect::<HashSet<_>>(),
670                ["user.a".into(), "user.b".into()].into_iter().collect::<HashSet<_>>(),
671            );
672
673            {
674                let info = test_file.node().info();
675                assert_eq!(info.mode, FileMode::from_bits(0o100640));
676                assert_eq!(info.uid, 49152); // These values come from the test image generated in
677                assert_eq!(info.gid, 24403); // ext4_to_pkg.
678            }
679
680            let test_symlink = test_dir
681                .lookup_child(&current_task, &mut context, "symlink".into())
682                .expect("lookup failed");
683
684            if let SymlinkTarget::Path(target) =
685                test_symlink.readlink(&current_task).expect("readlink failed")
686            {
687                assert_eq!(&target, "file");
688            } else {
689                panic!("unexpected symlink type");
690            }
691
692            let opened_dir = test_dir.open(&current_task, OpenFlags::RDONLY).expect("open failed");
693
694            struct Sink {
695                offset: off_t,
696                entries: HashMap<Vec<u8>, (ino_t, DirectoryEntryType)>,
697            }
698
699            impl DirentSink for Sink {
700                fn add(
701                    &mut self,
702                    inode_num: ino_t,
703                    offset: off_t,
704                    entry_type: DirectoryEntryType,
705                    name: &FsStr,
706                ) -> Result<(), Errno> {
707                    assert_eq!(offset, self.offset + 1);
708                    self.entries.insert(name.to_vec(), (inode_num, entry_type));
709                    self.offset = offset;
710                    Ok(())
711                }
712
713                fn offset(&self) -> off_t {
714                    self.offset
715                }
716            }
717
718            let mut sink = Sink { offset: 0, entries: HashMap::new() };
719            opened_dir.readdir(&current_task, &mut sink).expect("readdir failed");
720
721            assert_eq!(
722                sink.entries,
723                [
724                    (b".".into(), (test_dir.entry.node.ino, DirectoryEntryType::DIR)),
725                    (b"..".into(), (root.entry.node.ino, DirectoryEntryType::DIR)),
726                    (b"file".into(), (test_file.node().ino, DirectoryEntryType::REG)),
727                    (b"symlink".into(), (test_symlink.entry.node.ino, DirectoryEntryType::LNK))
728                ]
729                .into()
730            );
731        })
732        .await;
733    }
734}