Skip to main content

strat9_kernel/vfs/
mount.rs

1//! Mount table and path resolution.
2//!
3//! Maps path prefixes to schemes (like Plan 9 namespaces).
4
5use super::scheme::DynScheme;
6use crate::syscall::error::SyscallError;
7use alloc::{string::String, vec::Vec};
8use spin::{Lazy, RwLock};
9
10/// A mount point binding a path prefix to a scheme.
11#[derive(Clone)]
12struct Mount {
13    /// Path prefix (e.g., "/dev", "/net").
14    prefix: String,
15    /// Scheme handling this mount.
16    scheme: DynScheme,
17}
18
19/// Global mount table.
20pub struct MountTable {
21    mounts: Vec<Mount>,
22}
23
24impl MountTable {
25    /// Creates a new instance.
26    pub fn new() -> Self {
27        MountTable { mounts: Vec::new() }
28    }
29
30    /// Mount a scheme at a path prefix.
31    ///
32    /// Returns an error if the prefix is already mounted.
33    pub fn mount(&mut self, prefix: &str, scheme: DynScheme) -> Result<(), SyscallError> {
34        if prefix.is_empty() || !prefix.starts_with('/') {
35            return Err(SyscallError::InvalidArgument);
36        }
37
38        // Normalize path (remove trailing slash except for root)
39        let prefix = if prefix.len() > 1 && prefix.ends_with('/') {
40            &prefix[..prefix.len() - 1]
41        } else {
42            prefix
43        };
44
45        // Check for duplicate mount
46        if self.mounts.iter().any(|m| m.prefix == prefix) {
47            return Err(SyscallError::AlreadyExists);
48        }
49
50        self.mounts.push(Mount {
51            prefix: String::from(prefix),
52            scheme,
53        });
54
55        // Sort by prefix length (longest first) for correct resolution
56        self.mounts
57            .sort_by(|a, b| b.prefix.len().cmp(&a.prefix.len()));
58
59        Ok(())
60    }
61
62    /// Unmount a path prefix.
63    pub fn unmount(&mut self, prefix: &str) -> Result<(), SyscallError> {
64        let pos = self
65            .mounts
66            .iter()
67            .position(|m| m.prefix == prefix)
68            .ok_or(SyscallError::BadHandle)?;
69        self.mounts.remove(pos);
70        Ok(())
71    }
72
73    /// Resolve a path to (scheme, relative_path).
74    ///
75    /// Returns the longest matching mount point.
76    pub fn resolve(&self, path: &str) -> Result<(DynScheme, String), SyscallError> {
77        if !path.starts_with('/') {
78            return Err(SyscallError::InvalidArgument);
79        }
80
81        for mount in &self.mounts {
82            if path == mount.prefix {
83                return Ok((mount.scheme.clone(), String::new()));
84            } else if path.starts_with(&mount.prefix) {
85                let is_root = mount.prefix == "/";
86                let next_byte = path.as_bytes().get(mount.prefix.len());
87                if is_root || next_byte == Some(&b'/') {
88                    let relative = if is_root {
89                        &path[1..]
90                    } else {
91                        &path[mount.prefix.len() + 1..]
92                    };
93                    return Ok((mount.scheme.clone(), String::from(relative)));
94                }
95            }
96        }
97
98        Err(SyscallError::NotFound)
99    }
100
101    /// List all mount points.
102    pub fn list(&self) -> Vec<String> {
103        self.mounts.iter().map(|m| m.prefix.clone()).collect()
104    }
105}
106
107static GLOBAL_MOUNTS: Lazy<RwLock<MountTable>> = Lazy::new(|| RwLock::new(MountTable::new()));
108
109/// Mount a scheme at a global path.
110pub fn mount(prefix: &str, scheme: DynScheme) -> Result<(), SyscallError> {
111    GLOBAL_MOUNTS.write().mount(prefix, scheme)
112}
113
114/// Unmount a global path.
115pub fn unmount(prefix: &str) -> Result<(), SyscallError> {
116    GLOBAL_MOUNTS.write().unmount(prefix)
117}
118
119/// Resolve a path using the global mount table.
120pub fn resolve(path: &str) -> Result<(DynScheme, String), SyscallError> {
121    GLOBAL_MOUNTS.read().resolve(path)
122}
123
124/// List all global mount points.
125pub fn list_mounts() -> Vec<String> {
126    GLOBAL_MOUNTS.read().list()
127}
128
129// ============================================================================
130// Per-Process Namespace (future extension)
131// ============================================================================
132
133/// Per-process namespace (private mount table).
134///
135/// Currently unused but reserved for future per-process namespaces.
136pub struct Namespace {
137    mounts: MountTable,
138}
139
140impl Namespace {
141    /// Creates a new instance.
142    pub fn new() -> Self {
143        Namespace {
144            mounts: MountTable::new(),
145        }
146    }
147
148    /// Clone the global mount table.
149    pub fn from_global() -> Self {
150        let global = GLOBAL_MOUNTS.read();
151        Namespace {
152            mounts: MountTable {
153                mounts: global.mounts.clone(),
154            },
155        }
156    }
157
158    /// Performs the mount operation.
159    pub fn mount(&mut self, prefix: &str, scheme: DynScheme) -> Result<(), SyscallError> {
160        self.mounts.mount(prefix, scheme)
161    }
162
163    /// Performs the unmount operation.
164    pub fn unmount(&mut self, prefix: &str) -> Result<(), SyscallError> {
165        self.mounts.unmount(prefix)
166    }
167
168    /// Performs the resolve operation.
169    pub fn resolve(&self, path: &str) -> Result<(DynScheme, String), SyscallError> {
170        self.mounts.resolve(path)
171    }
172}
173
174impl Default for Namespace {
175    /// Builds a default instance.
176    fn default() -> Self {
177        Self::new()
178    }
179}