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