Skip to main content

kaish_kernel/vfs/
builtin_fs.rs

1//! BuiltinFs — read-only VFS that lists builtins as file entries under `/v/bin/`.
2//!
3//! The entries are not executable and nothing here ever executes. `stat`
4//! reports no mode, so `test -x /v/bin/grep` answers NO, and `real_path`
5//! returns `None`, so there is no path for exec(2) to open. `read` returns a
6//! line opening with `#!`, which makes an entry look executable.
7//!
8//! Running a builtin goes by name through the `ToolRegistry` and never routes
9//! through this filesystem. `/v/bin` is an inventory to list and read, not a
10//! directory of programs. Reporting `0o111` would flip `test -x /v/bin/*` from
11//! NO to YES — a behavior change that wants its own decision.
12//!
13//! `read_only()` is `true`, and the closed `-w` default depends on it staying
14//! true — see `Filesystem::path_access`.
15
16use std::io;
17use std::path::{Path, PathBuf};
18use std::sync::Arc;
19
20use async_trait::async_trait;
21
22use crate::tools::ToolRegistry;
23use super::{DirEntry, Filesystem};
24
25/// A read-only filesystem that exposes registered builtins as file
26/// entries. Listable and readable; not executable — see the module docs.
27pub struct BuiltinFs {
28    tools: Arc<ToolRegistry>,
29}
30
31impl BuiltinFs {
32    pub fn new(tools: Arc<ToolRegistry>) -> Self {
33        Self { tools }
34    }
35}
36
37#[async_trait]
38impl Filesystem for BuiltinFs {
39    async fn read(&self, path: &Path) -> io::Result<Vec<u8>> {
40        let name = path.to_str().unwrap_or("");
41        if self.tools.get(name).is_some() {
42            Ok(format!("#!/v/bin — kaish builtin: {}\n", name).into_bytes())
43        } else {
44            Err(io::Error::new(io::ErrorKind::NotFound, "builtin not found"))
45        }
46    }
47
48    async fn write(&self, _path: &Path, _data: &[u8]) -> io::Result<()> {
49        Err(io::Error::new(io::ErrorKind::PermissionDenied, "/v/bin is read-only"))
50    }
51
52    async fn list(&self, path: &Path) -> io::Result<Vec<DirEntry>> {
53        let p = path.to_str().unwrap_or("");
54        if !p.is_empty() && p != "." {
55            return Err(io::Error::new(io::ErrorKind::NotFound, "not a directory"));
56        }
57        let mut entries: Vec<DirEntry> = self.tools.names().iter().map(|name| {
58            DirEntry::file(name.to_string(), 0)
59        }).collect();
60        entries.sort_by(|a, b| a.name.cmp(&b.name));
61        Ok(entries)
62    }
63
64    async fn stat(&self, path: &Path) -> io::Result<DirEntry> {
65        let name = path.to_str().unwrap_or("");
66        if name.is_empty() || name == "." {
67            return Ok(DirEntry::directory("."));
68        }
69        if self.tools.get(name).is_some() {
70            Ok(DirEntry::file(name, 0))
71        } else {
72            Err(io::Error::new(io::ErrorKind::NotFound, "builtin not found"))
73        }
74    }
75
76    async fn mkdir(&self, _path: &Path) -> io::Result<()> {
77        Err(io::Error::new(io::ErrorKind::PermissionDenied, "/v/bin is read-only"))
78    }
79
80    async fn remove(&self, _path: &Path) -> io::Result<()> {
81        Err(io::Error::new(io::ErrorKind::PermissionDenied, "/v/bin is read-only"))
82    }
83
84    fn read_only(&self) -> bool {
85        true
86    }
87
88    fn real_path(&self, _path: &Path) -> Option<PathBuf> {
89        None
90    }
91}