Skip to main content

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