Skip to main content

vfs/posix/
single_symlink_fs.rs

1use super::vfs::{
2    VfsError, VfsResult, VirtualDirEntry, VirtualFileSystem, VirtualStat, VirtualUtimeSpec, S_IFLNK,
3};
4
5/// Tiny read-only filesystem whose root inode is a single symbolic link.
6///
7/// Package projection uses this for managed leaves such as
8/// `/opt/agentos/bin/<cmd>` and `/opt/agentos/pkgs/<pkg>/current`. A normal
9/// `MemoryFileSystem` cannot model that shape because its root inode is always
10/// a directory. Keeping each managed symlink as its own leaf mount lets the
11/// parent directories remain writable overlay directories, so user-installed
12/// commands and packages can coexist beside managed entries.
13#[derive(Debug, Clone)]
14pub struct SingleSymlinkFileSystem {
15    target: String,
16}
17
18impl SingleSymlinkFileSystem {
19    pub fn new(target: impl Into<String>) -> Self {
20        Self {
21            target: target.into(),
22        }
23    }
24
25    pub fn target(&self) -> &str {
26        &self.target
27    }
28
29    fn root_stat(&self) -> VirtualStat {
30        VirtualStat {
31            mode: S_IFLNK | 0o777,
32            size: self.target.len() as u64,
33            blocks: 0,
34            dev: 9101,
35            rdev: 0,
36            is_directory: false,
37            is_symbolic_link: true,
38            atime_ms: 0,
39            atime_nsec: 0,
40            mtime_ms: 0,
41            mtime_nsec: 0,
42            ctime_ms: 0,
43            ctime_nsec: 0,
44            birthtime_ms: 0,
45            ino: 1,
46            nlink: 1,
47            uid: 1000,
48            gid: 1000,
49        }
50    }
51
52    fn not_found(path: &str) -> VfsError {
53        VfsError::new("ENOENT", format!("no such file or directory, '{path}'"))
54    }
55
56    fn readonly(op: &str, path: &str) -> VfsError {
57        VfsError::new(
58            "EROFS",
59            format!("read-only single-symlink filesystem, {op} '{path}'"),
60        )
61    }
62}
63
64impl VirtualFileSystem for SingleSymlinkFileSystem {
65    fn read_file(&mut self, path: &str) -> VfsResult<Vec<u8>> {
66        Err(Self::not_found(path))
67    }
68
69    fn read_dir(&mut self, path: &str) -> VfsResult<Vec<String>> {
70        Err(VfsError::new(
71            "ENOTDIR",
72            format!("not a directory, readdir '{path}'"),
73        ))
74    }
75
76    fn read_dir_with_types(&mut self, path: &str) -> VfsResult<Vec<VirtualDirEntry>> {
77        Err(VfsError::new(
78            "ENOTDIR",
79            format!("not a directory, readdir '{path}'"),
80        ))
81    }
82
83    fn write_file(&mut self, path: &str, _content: impl Into<Vec<u8>>) -> VfsResult<()> {
84        Err(Self::readonly("write", path))
85    }
86
87    fn create_dir(&mut self, path: &str) -> VfsResult<()> {
88        Err(Self::readonly("mkdir", path))
89    }
90
91    fn mkdir(&mut self, path: &str, _recursive: bool) -> VfsResult<()> {
92        Err(Self::readonly("mkdir", path))
93    }
94
95    fn exists(&self, path: &str) -> bool {
96        path == "/"
97    }
98
99    fn stat(&mut self, path: &str) -> VfsResult<VirtualStat> {
100        self.lstat(path)
101    }
102
103    fn remove_file(&mut self, path: &str) -> VfsResult<()> {
104        Err(Self::readonly("unlink", path))
105    }
106
107    fn remove_dir(&mut self, path: &str) -> VfsResult<()> {
108        Err(Self::readonly("rmdir", path))
109    }
110
111    fn rename(&mut self, old_path: &str, new_path: &str) -> VfsResult<()> {
112        Err(VfsError::new(
113            "EROFS",
114            format!("read-only single-symlink filesystem, rename '{old_path}' to '{new_path}'"),
115        ))
116    }
117
118    fn realpath(&self, path: &str) -> VfsResult<String> {
119        if path == "/" {
120            return Err(VfsError::new(
121                "ELOOP",
122                "single-symlink root must be resolved by the mount table",
123            ));
124        }
125        Err(Self::not_found(path))
126    }
127
128    fn symlink(&mut self, target: &str, link_path: &str) -> VfsResult<()> {
129        Err(VfsError::new(
130            "EROFS",
131            format!("read-only single-symlink filesystem, symlink '{link_path}' -> '{target}'"),
132        ))
133    }
134
135    fn read_link(&self, path: &str) -> VfsResult<String> {
136        if path == "/" {
137            Ok(self.target.clone())
138        } else {
139            Err(Self::not_found(path))
140        }
141    }
142
143    fn lstat(&self, path: &str) -> VfsResult<VirtualStat> {
144        if path == "/" {
145            Ok(self.root_stat())
146        } else {
147            Err(Self::not_found(path))
148        }
149    }
150
151    fn link(&mut self, old_path: &str, new_path: &str) -> VfsResult<()> {
152        Err(VfsError::new(
153            "EROFS",
154            format!("read-only single-symlink filesystem, link '{old_path}' to '{new_path}'"),
155        ))
156    }
157
158    fn chmod(&mut self, path: &str, _mode: u32) -> VfsResult<()> {
159        Err(Self::readonly("chmod", path))
160    }
161
162    fn chown(&mut self, path: &str, _uid: u32, _gid: u32) -> VfsResult<()> {
163        Err(Self::readonly("chown", path))
164    }
165
166    fn utimes(&mut self, path: &str, _atime_ms: u64, _mtime_ms: u64) -> VfsResult<()> {
167        Err(Self::readonly("utimes", path))
168    }
169
170    fn utimes_spec(
171        &mut self,
172        path: &str,
173        _atime: VirtualUtimeSpec,
174        _mtime: VirtualUtimeSpec,
175        _follow_symlinks: bool,
176    ) -> VfsResult<()> {
177        Err(Self::readonly("utimes", path))
178    }
179
180    fn truncate(&mut self, path: &str, _length: u64) -> VfsResult<()> {
181        Err(Self::readonly("truncate", path))
182    }
183
184    fn pread(&mut self, path: &str, _offset: u64, _length: usize) -> VfsResult<Vec<u8>> {
185        Err(Self::not_found(path))
186    }
187}