Skip to main content

starnix_core/fs/
devtmpfs.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::device::DeviceMode;
6use crate::device::kobject::DeviceMetadata;
7use crate::fs::tmpfs::TmpFs;
8use crate::security;
9use crate::task::dynamic_thread_spawner::SpawnRequestBuilder;
10use crate::task::{CurrentTask, Kernel};
11use crate::vfs::{
12    DirEntryHandle, FileSystemHandle, FileSystemOptions, FsNodeHandle, FsStr, LookupContext,
13    MountInfo, NamespaceNode, path,
14};
15use starnix_uapi::auth::FsCred;
16use starnix_uapi::device_id::DeviceId;
17use starnix_uapi::errors::Errno;
18use starnix_uapi::file_mode::mode;
19use std::sync::OnceLock;
20
21pub fn dev_tmp_fs(
22    current_task: &CurrentTask,
23    _options: FileSystemOptions,
24) -> Result<FileSystemHandle, Errno> {
25    Ok(DevTmpFs::from_kernel(current_task.kernel()))
26}
27
28pub struct DevTmpFs(());
29
30#[derive(Default)]
31struct DevTmpFsHandle(OnceLock<FileSystemHandle>);
32
33impl DevTmpFs {
34    pub fn from_kernel(kernel: &Kernel) -> FileSystemHandle {
35        assert!(kernel.features.devtmpfs, "devtmpfs must be enabled in KernelFeatures");
36        kernel.expando.get::<DevTmpFsHandle>().0.get_or_init(|| Self::init(kernel)).clone()
37    }
38
39    fn from_kthread(current_task: &CurrentTask) -> FileSystemHandle {
40        let kernel = current_task.kernel();
41        assert!(kernel.features.devtmpfs, "devtmpfs must be enabled in KernelFeatures");
42        kernel
43            .expando
44            .get::<DevTmpFsHandle>()
45            .0
46            .get_or_init(|| Self::init_on_kthread(current_task))
47            .clone()
48    }
49
50    fn init(kernel: &Kernel) -> FileSystemHandle {
51        let (result_fn, req) = SpawnRequestBuilder::new()
52            .with_debug_name("devtmpfs-init")
53            .with_sync_closure(Self::init_on_kthread)
54            .build_with_sync_result();
55        kernel.kthreads.spawner().spawn_from_request(req);
56        result_fn().unwrap()
57    }
58
59    fn init_on_kthread(current_task: &CurrentTask) -> FileSystemHandle {
60        let kernel = current_task.kernel();
61        let fs = TmpFs::new_fs_with_name(kernel, "devtmpfs".into());
62        fs.root().node.update_info(|info| info.chmod(mode!(IFDIR, 0o755)));
63        security::file_system_resolve_security(current_task, &fs)
64            .expect("resolve devtmpfs security");
65        let root = fs.root();
66        devtmpfs_get_or_create_directory_at(current_task, root, "shm".into()).unwrap();
67        devtmpfs_get_or_create_directory_at(current_task, root, "pts".into()).unwrap();
68        let create_symlink = |dir: &FsNodeHandle, mount: &MountInfo, name: &FsStr| {
69            let target = "/proc/self/fd".into();
70            dir.create_symlink(current_task, mount, name, target, FsCred::root())
71        };
72        let mount = MountInfo::detached();
73        root.create_entry(current_task, &mount, "fd".into(), create_symlink).unwrap();
74        fs
75    }
76}
77
78/// Creates a device node in the devtmpfs filesystem.
79///
80/// This function executes the creation in a background thread and blocks the
81/// calling thread until the device node is fully created. This blocking behavior
82/// matches Linux.
83pub fn devtmpfs_create_device(
84    kernel: &Kernel,
85    device_metadata: DeviceMetadata,
86) -> Result<(), Errno> {
87    let closure = move |current_task: &CurrentTask| {
88        devtmpfs_create_device_internal(current_task, &device_metadata)
89    };
90    let (result_fn, req) = SpawnRequestBuilder::new()
91        .with_debug_name("devtmpfs-create-device")
92        .with_sync_closure(closure)
93        .build_with_sync_result();
94    kernel.kthreads.spawner().spawn_from_request(req);
95    result_fn()?
96}
97
98fn devtmpfs_create_device_internal(
99    current_task: &CurrentTask,
100    device_metadata: &DeviceMetadata,
101) -> Result<(), Errno> {
102    let separator_pos = device_metadata.devname.iter().rposition(|&c| c == path::SEPARATOR);
103    let (device_path, device_name) = match separator_pos {
104        Some(pos) => device_metadata.devname.split_at(pos + 1),
105        None => (&[] as &[u8], device_metadata.devname.as_slice()),
106    };
107    let parent_dir = device_path
108        .split(|&c| c == path::SEPARATOR)
109        // Avoid EEXIST for 'foo//bar' and the last directory name.
110        .filter(|dir_name| dir_name.len() > 0)
111        .try_fold(DevTmpFs::from_kthread(current_task).root().clone(), |parent_dir, dir_name| {
112            devtmpfs_get_or_create_directory_at(current_task, &parent_dir, dir_name.into())
113        })?;
114    devtmpfs_create_device_node(
115        current_task,
116        parent_dir,
117        device_name.into(),
118        device_metadata.mode,
119        device_metadata.devt,
120    )
121}
122
123fn devtmpfs_get_or_create_directory_at(
124    current_task: &CurrentTask,
125    parent_dir: &DirEntryHandle,
126    dir_name: &FsStr,
127) -> Result<DirEntryHandle, Errno> {
128    let create_dir = |dir: &FsNodeHandle, mount: &MountInfo, name: &FsStr| {
129        let mode = mode!(IFDIR, 0o755);
130        dir.create_node(current_task, mount, name, mode, DeviceId::NONE, FsCred::root())
131    };
132    let mount = MountInfo::detached();
133    let (entry, _status) =
134        parent_dir.get_or_create_entry(current_task, &mount, dir_name, create_dir)?;
135    Ok(entry)
136}
137
138fn devtmpfs_create_device_node(
139    current_task: &CurrentTask,
140    parent_dir: DirEntryHandle,
141    device_name: &FsStr,
142    device_mode: DeviceMode,
143    devt: DeviceId,
144) -> Result<(), Errno> {
145    let mode = match device_mode {
146        DeviceMode::Char => mode!(IFCHR, 0o666),
147        DeviceMode::Block => mode!(IFBLK, 0o666),
148    };
149    // This creates content inside the temporary FS. This doesn't depend on the mount
150    // information.
151    parent_dir.create_entry(
152        current_task,
153        &MountInfo::detached(),
154        device_name,
155        |dir, mount, name| dir.create_node(current_task, mount, name, mode, devt, FsCred::root()),
156    )?;
157    Ok(())
158}
159
160pub fn devtmpfs_remove_path(current_task: &CurrentTask, path: &FsStr) -> Result<(), Errno> {
161    let root_node =
162        NamespaceNode::new_anonymous(DevTmpFs::from_kernel(current_task.kernel()).root().clone());
163    let mut context = LookupContext::default();
164    let (parent_node, device_name) = current_task.lookup_parent(&mut context, &root_node, path)?;
165    parent_node.entry.remove_child(device_name.into(), &current_task.kernel().mounts);
166    Ok(())
167}