starnix_core/fs/
tmpfs.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::mm::PAGE_SIZE;
6use crate::security;
7use crate::task::{CurrentTask, Kernel};
8use crate::vfs::memory_directory::MemoryDirectoryFile;
9use crate::vfs::{
10    CacheMode, DirEntry, DirEntryHandle, FileOps, FileSystem, FileSystemHandle, FileSystemOps,
11    FileSystemOptions, FsNode, FsNodeHandle, FsNodeInfo, FsNodeOps, FsStr, FsString,
12    MemoryRegularNode, MemoryXattrStorage, SymlinkNode, XattrStorage as _, fs_args,
13    fs_node_impl_not_dir, fs_node_impl_xattr_delegate,
14};
15use starnix_logging::{log_warn, track_stub};
16use starnix_sync::{FileOpsCore, LockEqualOrBefore, Locked, Unlocked};
17use starnix_types::vfs::default_statfs;
18use starnix_uapi::auth::FsCred;
19use starnix_uapi::device_type::DeviceType;
20use starnix_uapi::errors::Errno;
21use starnix_uapi::file_mode::{FileMode, mode};
22use starnix_uapi::open_flags::OpenFlags;
23use starnix_uapi::seal_flags::SealFlags;
24use starnix_uapi::{TMPFS_MAGIC, error, gid_t, statfs, uid_t};
25use std::collections::BTreeMap;
26use std::sync::Arc;
27use std::sync::atomic::{AtomicU32, Ordering};
28
29pub struct TmpFs(&'static FsStr);
30
31impl FileSystemOps for Arc<TmpFs> {
32    fn statfs(
33        &self,
34        _locked: &mut Locked<FileOpsCore>,
35        _fs: &FileSystem,
36        _current_task: &CurrentTask,
37    ) -> Result<statfs, Errno> {
38        Ok(statfs {
39            // Pretend we have a ton of free space.
40            f_blocks: 0x100000000,
41            f_bavail: 0x100000000,
42            f_bfree: 0x100000000,
43            ..default_statfs(TMPFS_MAGIC)
44        })
45    }
46    fn name(&self) -> &'static FsStr {
47        self.0
48    }
49
50    fn rename(
51        &self,
52        _locked: &mut Locked<FileOpsCore>,
53        _fs: &FileSystem,
54        _current_task: &CurrentTask,
55        old_parent: &FsNodeHandle,
56        _old_name: &FsStr,
57        new_parent: &FsNodeHandle,
58        _new_name: &FsStr,
59        renamed: &FsNodeHandle,
60        replaced: Option<&FsNodeHandle>,
61    ) -> Result<(), Errno> {
62        fn child_count(node: &FsNodeHandle) -> &AtomicU32 {
63            // The following cast are safe, unless something is seriously wrong:
64            // - The filesystem should not be asked to rename node that it doesn't handle.
65            // - Parents in a rename operation need to be directories.
66            // - TmpFsDirectory is the ops for directories in this filesystem.
67            &node.downcast_ops::<TmpFsDirectory>().unwrap().child_count
68        }
69        if let Some(replaced) = replaced {
70            if replaced.is_dir() {
71                // Ensures that replaces is empty.
72                if child_count(replaced).load(Ordering::Acquire) != 0 {
73                    return error!(ENOTEMPTY);
74                }
75            }
76        }
77        child_count(old_parent).fetch_sub(1, Ordering::Release);
78        child_count(new_parent).fetch_add(1, Ordering::Release);
79        if renamed.is_dir() {
80            old_parent.update_info(|info| {
81                info.link_count -= 1;
82            });
83            new_parent.update_info(|info| {
84                info.link_count += 1;
85            });
86        }
87        // Fix the wrong changes to new_parent due to the fact that the target element has
88        // been replaced instead of added.
89        if let Some(replaced) = replaced {
90            if replaced.is_dir() {
91                new_parent.update_info(|info| {
92                    info.link_count -= 1;
93                });
94            }
95            child_count(new_parent).fetch_sub(1, Ordering::Release);
96        }
97        Ok(())
98    }
99
100    fn exchange(
101        &self,
102        _fs: &FileSystem,
103        _current_task: &CurrentTask,
104        node1: &FsNodeHandle,
105        parent1: &FsNodeHandle,
106        _name1: &FsStr,
107        node2: &FsNodeHandle,
108        parent2: &FsNodeHandle,
109        _name2: &FsStr,
110    ) -> Result<(), Errno> {
111        if node1.is_dir() {
112            parent1.update_info(|info| {
113                info.link_count -= 1;
114            });
115            parent2.update_info(|info| {
116                info.link_count += 1;
117            });
118        }
119
120        if node2.is_dir() {
121            parent1.update_info(|info| {
122                info.link_count += 1;
123            });
124            parent2.update_info(|info| {
125                info.link_count -= 1;
126            });
127        }
128
129        Ok(())
130    }
131}
132
133pub fn tmp_fs(
134    locked: &mut Locked<Unlocked>,
135    current_task: &CurrentTask,
136    options: FileSystemOptions,
137) -> Result<FileSystemHandle, Errno> {
138    TmpFs::new_fs_with_options(locked, &current_task.kernel(), options)
139}
140
141impl TmpFs {
142    pub fn new_fs<L>(locked: &mut Locked<L>, kernel: &Kernel) -> FileSystemHandle
143    where
144        L: LockEqualOrBefore<FileOpsCore>,
145    {
146        Self::new_fs_with_options(locked, kernel, Default::default())
147            .expect("empty options cannot fail")
148    }
149
150    pub fn new_fs_with_name<L>(
151        locked: &mut Locked<L>,
152        kernel: &Kernel,
153        name: &'static FsStr,
154    ) -> FileSystemHandle
155    where
156        L: LockEqualOrBefore<FileOpsCore>,
157    {
158        Self::new_fs_with_options_and_name(locked, kernel, Default::default(), name)
159            .expect("empty options cannot fail")
160    }
161
162    pub fn new_fs_with_options<L>(
163        locked: &mut Locked<L>,
164        kernel: &Kernel,
165        options: FileSystemOptions,
166    ) -> Result<FileSystemHandle, Errno>
167    where
168        L: LockEqualOrBefore<FileOpsCore>,
169    {
170        Self::new_fs_with_options_and_name(locked, kernel, options, "tmpfs".into())
171    }
172
173    fn new_fs_with_options_and_name<L>(
174        locked: &mut Locked<L>,
175        kernel: &Kernel,
176        options: FileSystemOptions,
177        name: &'static FsStr,
178    ) -> Result<FileSystemHandle, Errno>
179    where
180        L: LockEqualOrBefore<FileOpsCore>,
181    {
182        let fs =
183            FileSystem::new(locked, kernel, CacheMode::Permanent, Arc::new(TmpFs(name)), options)?;
184        let mut mount_options = fs.options.params.clone();
185        let mode = if let Some(mode) = mount_options.remove(b"mode") {
186            FileMode::from_string(mode.as_ref())?
187        } else {
188            mode!(IFDIR, 0o777)
189        };
190        let uid = if let Some(uid) = mount_options.remove(b"uid") {
191            fs_args::parse::<uid_t>(uid.as_ref())?
192        } else {
193            0
194        };
195        let gid = if let Some(gid) = mount_options.remove(b"gid") {
196            fs_args::parse::<gid_t>(gid.as_ref())?
197        } else {
198            0
199        };
200        let root_ino = fs.allocate_ino();
201        let mut info = FsNodeInfo::new(mode!(IFDIR, 0o777), FsCred { uid, gid });
202        info.chmod(mode);
203        fs.create_root_with_info(root_ino, TmpFsDirectory::new(), info);
204
205        if !mount_options.is_empty() {
206            track_stub!(
207                TODO("https://fxbug.dev/322873419"),
208                "unknown tmpfs options, see logs for strings"
209            );
210            log_warn!("Unknown tmpfs options: {}", mount_options);
211        }
212
213        Ok(fs)
214    }
215
216    pub fn set_initial_content(kernel: &Kernel, fs: &FileSystemHandle, data: TmpFsData) {
217        fn create_dir_entry_from_data(
218            kernel: &Kernel,
219            fs: &FileSystemHandle,
220            data: TmpFsData,
221            this: Option<DirEntryHandle>,
222            name: FsString,
223        ) -> DirEntryHandle {
224            // TODO: https://fxbug.dev/455771186 - Revise FsNode initialization to better ensure
225            // that all the things are appropriately labeled.
226            let new_direntry = |node, parent, name| {
227                let dir_entry = DirEntry::new(node, parent, name);
228                security::fs_node_init_with_dentry_deferred(kernel, &dir_entry);
229                dir_entry
230            };
231
232            match data.node_type {
233                TmpFsNodeType::Link(target) => {
234                    assert!(this.is_none());
235                    let node = TmpFsDirectory::new_symlink(fs, target.as_ref(), data.owner);
236                    new_direntry(node, None, name)
237                }
238                TmpFsNodeType::Directory(children) => {
239                    let this = this.unwrap_or_else(|| {
240                        let info = FsNodeInfo::new(mode!(IFDIR, data.perm), data.owner);
241                        let node = fs.create_node_and_allocate_node_id(TmpFsDirectory::new(), info);
242                        new_direntry(node, None, name.clone())
243                    });
244                    this.node
245                        .downcast_ops::<TmpFsDirectory>()
246                        .expect("directory must be from tmpfs")
247                        .child_count
248                        .fetch_add(children.len() as u32, Ordering::Release);
249                    let children = children
250                        .into_iter()
251                        .map(|(name, data)| {
252                            let child =
253                                create_dir_entry_from_data(kernel, fs, data, None, name.clone());
254                            (name, child)
255                        })
256                        .collect::<BTreeMap<_, _>>();
257                    this.set_children(children);
258                    this
259                }
260            }
261        }
262
263        create_dir_entry_from_data(kernel, fs, data, Some(Arc::clone(fs.root())), "".into());
264    }
265}
266
267pub enum TmpFsNodeType {
268    Link(FsString),
269    Directory(BTreeMap<FsString, TmpFsData>),
270}
271
272pub struct TmpFsData {
273    pub owner: FsCred,
274    pub perm: u32,
275    pub node_type: TmpFsNodeType,
276}
277
278pub struct TmpFsDirectory {
279    xattrs: MemoryXattrStorage,
280    child_count: AtomicU32,
281}
282
283impl TmpFsDirectory {
284    pub fn new() -> Self {
285        Self { xattrs: MemoryXattrStorage::default(), child_count: AtomicU32::new(0) }
286    }
287
288    fn new_symlink(fs: &Arc<FileSystem>, target: &FsStr, owner: FsCred) -> FsNodeHandle {
289        let (link, info) = SymlinkNode::new(target, owner);
290        fs.create_node_and_allocate_node_id(link, info)
291    }
292}
293
294fn create_child_node(
295    parent: &FsNode,
296    mode: FileMode,
297    dev: DeviceType,
298    owner: FsCred,
299) -> Result<FsNodeHandle, Errno> {
300    let ops: Box<dyn FsNodeOps> = match mode.fmt() {
301        FileMode::IFREG => Box::new(MemoryRegularNode::new()?),
302        FileMode::IFIFO | FileMode::IFBLK | FileMode::IFCHR | FileMode::IFSOCK => {
303            Box::new(TmpFsSpecialNode::new())
304        }
305        _ => return error!(EACCES),
306    };
307    let mut info = FsNodeInfo::new(mode, owner);
308    info.rdev = dev;
309    // blksize is PAGE_SIZE for in memory node.
310    info.blksize = *PAGE_SIZE as usize;
311    let child = parent.fs().create_node_and_allocate_node_id(ops, info);
312    if mode.fmt() == FileMode::IFREG {
313        // For files created in tmpfs, forbid sealing, by sealing the seal operation.
314        child.write_guard_state.lock().enable_sealing(SealFlags::SEAL);
315    }
316    Ok(child)
317}
318
319impl FsNodeOps for TmpFsDirectory {
320    fs_node_impl_xattr_delegate!(self, self.xattrs);
321
322    fn create_file_ops(
323        &self,
324        _locked: &mut Locked<FileOpsCore>,
325        _node: &FsNode,
326        _current_task: &CurrentTask,
327        _flags: OpenFlags,
328    ) -> Result<Box<dyn FileOps>, Errno> {
329        Ok(Box::new(MemoryDirectoryFile::new()))
330    }
331
332    fn mkdir(
333        &self,
334        _locked: &mut Locked<FileOpsCore>,
335        node: &FsNode,
336        _current_task: &CurrentTask,
337        _name: &FsStr,
338        mode: FileMode,
339        owner: FsCred,
340    ) -> Result<FsNodeHandle, Errno> {
341        node.update_info(|info| {
342            info.link_count += 1;
343        });
344        self.child_count.fetch_add(1, Ordering::Release);
345        Ok(node
346            .fs()
347            .create_node_and_allocate_node_id(TmpFsDirectory::new(), FsNodeInfo::new(mode, owner)))
348    }
349
350    fn mknod(
351        &self,
352        _locked: &mut Locked<FileOpsCore>,
353        node: &FsNode,
354        _current_task: &CurrentTask,
355        _name: &FsStr,
356        mode: FileMode,
357        dev: DeviceType,
358        owner: FsCred,
359    ) -> Result<FsNodeHandle, Errno> {
360        let child = create_child_node(node, mode, dev, owner)?;
361        self.child_count.fetch_add(1, Ordering::Release);
362        Ok(child)
363    }
364
365    fn create_symlink(
366        &self,
367        _locked: &mut Locked<FileOpsCore>,
368        node: &FsNode,
369        _current_task: &CurrentTask,
370        _name: &FsStr,
371        target: &FsStr,
372        owner: FsCred,
373    ) -> Result<FsNodeHandle, Errno> {
374        self.child_count.fetch_add(1, Ordering::Release);
375        Ok(Self::new_symlink(&node.fs(), target, owner))
376    }
377
378    fn create_tmpfile(
379        &self,
380        node: &FsNode,
381        _current_task: &CurrentTask,
382        mode: FileMode,
383        owner: FsCred,
384    ) -> Result<FsNodeHandle, Errno> {
385        assert!(mode.is_reg());
386        create_child_node(node, mode, DeviceType::NONE, owner)
387    }
388
389    fn link(
390        &self,
391        _locked: &mut Locked<FileOpsCore>,
392        _node: &FsNode,
393        _current_task: &CurrentTask,
394        _name: &FsStr,
395        child: &FsNodeHandle,
396    ) -> Result<(), Errno> {
397        child.update_info(|info| {
398            info.link_count += 1;
399        });
400        self.child_count.fetch_add(1, Ordering::Release);
401        Ok(())
402    }
403
404    fn unlink(
405        &self,
406        _locked: &mut Locked<FileOpsCore>,
407        node: &FsNode,
408        _current_task: &CurrentTask,
409        _name: &FsStr,
410        child_to_unlink: &FsNodeHandle,
411    ) -> Result<(), Errno> {
412        if child_to_unlink.is_dir() {
413            // The following cast is safe, unless something is seriously wrong:
414            // - The filesystem should not be asked to unlink a node that it doesn't handle.
415            // - The child has already been determined to be a directory.
416            // - TmpFsDirectory is the ops for directories in this filesystem.
417            let child_count =
418                &child_to_unlink.downcast_ops::<TmpFsDirectory>().unwrap().child_count;
419            if child_count.load(Ordering::Relaxed) != 0 {
420                return error!(ENOTEMPTY);
421            }
422
423            node.update_info(|info| {
424                info.link_count -= 1;
425            });
426        }
427        child_to_unlink.update_info(|info| {
428            info.link_count -= 1;
429        });
430        self.child_count.fetch_sub(1, Ordering::Release);
431        Ok(())
432    }
433}
434
435struct TmpFsSpecialNode {
436    xattrs: MemoryXattrStorage,
437}
438
439impl TmpFsSpecialNode {
440    pub fn new() -> Self {
441        Self { xattrs: MemoryXattrStorage::default() }
442    }
443}
444
445impl FsNodeOps for TmpFsSpecialNode {
446    fs_node_impl_not_dir!();
447    fs_node_impl_xattr_delegate!(self, self.xattrs);
448
449    fn create_file_ops(
450        &self,
451        _locked: &mut Locked<FileOpsCore>,
452        _node: &FsNode,
453        _current_task: &CurrentTask,
454        _flags: OpenFlags,
455    ) -> Result<Box<dyn FileOps>, Errno> {
456        unreachable!("Special nodes cannot be opened.");
457    }
458}
459
460#[cfg(test)]
461mod test {
462    use super::*;
463    use crate::testing::spawn_kernel_and_run;
464    use crate::vfs::buffers::{VecInputBuffer, VecOutputBuffer};
465    use crate::vfs::fs_args::MountParams;
466    use crate::vfs::{FdNumber, UnlinkKind};
467    use starnix_uapi::errno;
468    use starnix_uapi::file_mode::AccessCheck;
469    use starnix_uapi::mount_flags::MountFlags;
470    use starnix_uapi::vfs::ResolveFlags;
471    use zerocopy::IntoBytes;
472
473    #[::fuchsia::test]
474    async fn test_tmpfs() {
475        spawn_kernel_and_run(async |locked, current_task| {
476            let kernel = current_task.kernel();
477            let fs = TmpFs::new_fs(locked, &kernel);
478            let root = fs.root();
479            let usr = root.create_dir(locked, &current_task, "usr".into()).unwrap();
480            let _etc = root.create_dir(locked, &current_task, "etc".into()).unwrap();
481            let _usr_bin = usr.create_dir(locked, &current_task, "bin".into()).unwrap();
482            let mut names = root.copy_child_names();
483            names.sort();
484            assert!(names.iter().eq(["etc", "usr"].iter()));
485        })
486        .await;
487    }
488
489    #[::fuchsia::test]
490    async fn test_write_read() {
491        spawn_kernel_and_run(async |locked, current_task| {
492            let path = "test.bin";
493            let _file = current_task
494                .fs()
495                .root()
496                .create_node(
497                    locked,
498                    &current_task,
499                    path.into(),
500                    mode!(IFREG, 0o777),
501                    DeviceType::NONE,
502                )
503                .unwrap();
504
505            let wr_file = current_task.open_file(locked, path.into(), OpenFlags::RDWR).unwrap();
506
507            let test_seq = 0..10000u16;
508            let test_vec = test_seq.collect::<Vec<_>>();
509            let test_bytes = test_vec.as_slice().as_bytes();
510
511            let written =
512                wr_file.write(locked, &current_task, &mut VecInputBuffer::new(test_bytes)).unwrap();
513            assert_eq!(written, test_bytes.len());
514
515            let mut read_buffer = VecOutputBuffer::new(test_bytes.len() + 1);
516            let read = wr_file.read_at(locked, &current_task, 0, &mut read_buffer).unwrap();
517            assert_eq!(read, test_bytes.len());
518            assert_eq!(test_bytes, read_buffer.data());
519        })
520        .await;
521    }
522
523    #[::fuchsia::test]
524    async fn test_read_past_eof() {
525        spawn_kernel_and_run(async |locked, current_task| {
526            // Open an empty file
527            let path = "test.bin";
528            let _file = current_task
529                .fs()
530                .root()
531                .create_node(
532                    locked,
533                    &current_task,
534                    path.into(),
535                    mode!(IFREG, 0o777),
536                    DeviceType::NONE,
537                )
538                .unwrap();
539            let rd_file = current_task.open_file(locked, path.into(), OpenFlags::RDONLY).unwrap();
540
541            // Verify that attempting to read past the EOF (i.e. at a non-zero offset) returns 0
542            let buffer_size = 0x10000;
543            let mut output_buffer = VecOutputBuffer::new(buffer_size);
544            let test_offset = 100;
545            let result =
546                rd_file.read_at(locked, &current_task, test_offset, &mut output_buffer).unwrap();
547            assert_eq!(result, 0);
548        })
549        .await;
550    }
551
552    #[::fuchsia::test]
553    async fn test_permissions() {
554        spawn_kernel_and_run(async |locked, current_task| {
555            let path = "test.bin";
556            let file = current_task
557                .open_file_at(
558                    locked,
559                    FdNumber::AT_FDCWD,
560                    path.into(),
561                    OpenFlags::CREAT | OpenFlags::RDONLY,
562                    FileMode::from_bits(0o777),
563                    ResolveFlags::empty(),
564                    AccessCheck::default(),
565                )
566                .expect("failed to create file");
567            assert_eq!(
568                0,
569                file.read(locked, &current_task, &mut VecOutputBuffer::new(0))
570                    .expect("failed to read")
571            );
572
573            assert!(file.write(locked, &current_task, &mut VecInputBuffer::new(&[])).is_err());
574
575            let file = current_task
576                .open_file_at(
577                    locked,
578                    FdNumber::AT_FDCWD,
579                    path.into(),
580                    OpenFlags::WRONLY,
581                    FileMode::EMPTY,
582                    ResolveFlags::empty(),
583                    AccessCheck::default(),
584                )
585                .expect("failed to open file WRONLY");
586
587            assert!(file.read(locked, &current_task, &mut VecOutputBuffer::new(0)).is_err());
588
589            assert_eq!(
590                0,
591                file.write(locked, &current_task, &mut VecInputBuffer::new(&[]))
592                    .expect("failed to write")
593            );
594
595            let file = current_task
596                .open_file_at(
597                    locked,
598                    FdNumber::AT_FDCWD,
599                    path.into(),
600                    OpenFlags::RDWR,
601                    FileMode::EMPTY,
602                    ResolveFlags::empty(),
603                    AccessCheck::default(),
604                )
605                .expect("failed to open file RDWR");
606
607            assert_eq!(
608                0,
609                file.read(locked, &current_task, &mut VecOutputBuffer::new(0))
610                    .expect("failed to read")
611            );
612
613            assert_eq!(
614                0,
615                file.write(locked, &current_task, &mut VecInputBuffer::new(&[]))
616                    .expect("failed to write")
617            );
618        })
619        .await;
620    }
621
622    #[::fuchsia::test]
623    async fn test_persistence() {
624        spawn_kernel_and_run(async |locked, current_task| {
625            {
626                let root = &current_task.fs().root().entry;
627                let usr = root
628                    .create_dir(locked, &current_task, "usr".into())
629                    .expect("failed to create usr");
630                root.create_dir(locked, &current_task, "etc".into())
631                    .expect("failed to create usr/etc");
632                usr.create_dir(locked, &current_task, "bin".into())
633                    .expect("failed to create usr/bin");
634            }
635
636            // At this point, all the nodes are dropped.
637
638            current_task
639                .open_file(locked, "/usr/bin".into(), OpenFlags::RDONLY | OpenFlags::DIRECTORY)
640                .expect("failed to open /usr/bin");
641            assert_eq!(
642                errno!(ENOENT),
643                current_task
644                    .open_file(locked, "/usr/bin/test.txt".into(), OpenFlags::RDWR)
645                    .unwrap_err()
646            );
647            current_task
648                .open_file_at(
649                    locked,
650                    FdNumber::AT_FDCWD,
651                    "/usr/bin/test.txt".into(),
652                    OpenFlags::RDWR | OpenFlags::CREAT,
653                    FileMode::from_bits(0o777),
654                    ResolveFlags::empty(),
655                    AccessCheck::default(),
656                )
657                .expect("failed to create test.txt");
658            let txt = current_task
659                .open_file(locked, "/usr/bin/test.txt".into(), OpenFlags::RDWR)
660                .expect("failed to open test.txt");
661
662            let usr_bin = current_task
663                .open_file(locked, "/usr/bin".into(), OpenFlags::RDONLY)
664                .expect("failed to open /usr/bin");
665            usr_bin
666                .name
667                .unlink(locked, &current_task, "test.txt".into(), UnlinkKind::NonDirectory, false)
668                .expect("failed to unlink test.text");
669            assert_eq!(
670                errno!(ENOENT),
671                current_task
672                    .open_file(locked, "/usr/bin/test.txt".into(), OpenFlags::RDWR)
673                    .unwrap_err()
674            );
675            assert_eq!(
676                errno!(ENOENT),
677                usr_bin
678                    .name
679                    .unlink(
680                        locked,
681                        &current_task,
682                        "test.txt".into(),
683                        UnlinkKind::NonDirectory,
684                        false
685                    )
686                    .unwrap_err()
687            );
688
689            assert_eq!(
690                0,
691                txt.read(locked, &current_task, &mut VecOutputBuffer::new(0))
692                    .expect("failed to read")
693            );
694            std::mem::drop(txt);
695            assert_eq!(
696                errno!(ENOENT),
697                current_task
698                    .open_file(locked, "/usr/bin/test.txt".into(), OpenFlags::RDWR)
699                    .unwrap_err()
700            );
701            std::mem::drop(usr_bin);
702
703            let usr = current_task
704                .open_file(locked, "/usr".into(), OpenFlags::RDONLY)
705                .expect("failed to open /usr");
706            assert_eq!(
707                errno!(ENOENT),
708                current_task.open_file(locked, "/usr/foo".into(), OpenFlags::RDONLY).unwrap_err()
709            );
710            usr.name
711                .unlink(locked, &current_task, "bin".into(), UnlinkKind::Directory, false)
712                .expect("failed to unlink /usr/bin");
713        })
714        .await;
715    }
716
717    #[::fuchsia::test]
718    async fn test_data() {
719        spawn_kernel_and_run(async |locked, current_task| {
720            let kernel = current_task.kernel();
721            let fs = TmpFs::new_fs_with_options(
722                locked,
723                &kernel,
724                FileSystemOptions {
725                    source: Default::default(),
726                    flags: MountFlags::empty(),
727                    params: MountParams::parse(b"mode=0123,uid=42,gid=84".into())
728                        .expect("parsed correctly"),
729                },
730            )
731            .expect("new_fs");
732            let info = fs.root().node.info();
733            assert_eq!(info.mode, mode!(IFDIR, 0o123));
734            assert_eq!(info.uid, 42);
735            assert_eq!(info.gid, 84);
736        })
737        .await;
738    }
739}