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