Skip to main content

nucleus/filesystem/
tmpfs.rs

1use crate::error::{NucleusError, Result};
2use nix::mount::{mount, umount2, MntFlags, MsFlags};
3use std::path::{Path, PathBuf};
4use tracing::{debug, info};
5
6/// tmpfs mount manager
7pub struct TmpfsMount {
8    path: PathBuf,
9    size_bytes: Option<u64>,
10    mounted: bool,
11}
12
13impl TmpfsMount {
14    /// Create new tmpfs mount configuration
15    pub fn new<P: AsRef<Path>>(path: P, size_bytes: Option<u64>) -> Self {
16        Self {
17            path: path.as_ref().to_path_buf(),
18            size_bytes,
19            mounted: false,
20        }
21    }
22
23    /// Mount the tmpfs filesystem
24    ///
25    /// This implements the transition: unmounted -> mounted
26    pub fn mount(&mut self) -> Result<()> {
27        if self.mounted {
28            debug!("tmpfs already mounted at {:?}, skipping", self.path);
29            return Ok(());
30        }
31
32        info!("Mounting tmpfs at {:?}", self.path);
33
34        // Create mount point if it doesn't exist
35        if !self.path.exists() {
36            std::fs::create_dir_all(&self.path).map_err(|e| {
37                NucleusError::FilesystemError(format!(
38                    "Failed to create mount point {:?}: {}",
39                    self.path, e
40                ))
41            })?;
42        }
43
44        // Build mount options
45        let mut options = String::new();
46        if let Some(size) = self.size_bytes {
47            options = format!("size={}", size);
48        }
49
50        let options_cstr = if options.is_empty() {
51            None
52        } else {
53            Some(options.as_str())
54        };
55
56        // Mount tmpfs with nosuid, nodev for security
57        // Note: MS_NOEXEC is omitted so user binaries in /context/ can execute.
58        // Execution control is provided by seccomp + capability drop + Landlock.
59        let flags = MsFlags::MS_NOSUID | MsFlags::MS_NODEV;
60
61        mount(
62            Some("tmpfs"),
63            &self.path,
64            Some("tmpfs"),
65            flags,
66            options_cstr,
67        )
68        .map_err(|e| {
69            NucleusError::FilesystemError(format!(
70                "Failed to mount tmpfs at {:?}: {}",
71                self.path, e
72            ))
73        })?;
74
75        self.mounted = true;
76        info!("Successfully mounted tmpfs at {:?}", self.path);
77
78        Ok(())
79    }
80
81    /// Unmount the tmpfs filesystem
82    pub fn unmount(&mut self) -> Result<()> {
83        if !self.mounted {
84            debug!("tmpfs not mounted at {:?}, skipping", self.path);
85            return Ok(());
86        }
87
88        info!("Unmounting tmpfs at {:?}", self.path);
89
90        umount2(&self.path, MntFlags::MNT_DETACH).map_err(|e| {
91            NucleusError::FilesystemError(format!(
92                "Failed to unmount tmpfs at {:?}: {}",
93                self.path, e
94            ))
95        })?;
96
97        self.mounted = false;
98        info!("Successfully unmounted tmpfs at {:?}", self.path);
99
100        Ok(())
101    }
102
103    /// Get mount path
104    pub fn path(&self) -> &Path {
105        &self.path
106    }
107
108    /// Check if mounted
109    pub fn is_mounted(&self) -> bool {
110        self.mounted
111    }
112}
113
114impl Drop for TmpfsMount {
115    fn drop(&mut self) {
116        if self.mounted {
117            let _ = self.unmount();
118        }
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn test_tmpfs_mount_new() {
128        let mount = TmpfsMount::new("/tmp/test", Some(1024 * 1024));
129        assert!(!mount.is_mounted());
130        assert_eq!(mount.path(), Path::new("/tmp/test"));
131    }
132
133    // Note: Testing actual mounting requires root privileges
134    // These are tested in integration tests
135}