1use crate::engine::error::{VfsError, VfsResult};
2use crate::engine::types::{Dentry, SnapshotId, VirtualStat};
3use async_trait::async_trait;
4
5#[async_trait]
6pub trait VirtualFileSystem: Send + Sync {
7 async fn read_file(&self, path: &str) -> VfsResult<Vec<u8>>;
8
9 async fn read_text(&self, path: &str) -> VfsResult<String> {
10 String::from_utf8(self.read_file(path).await?)
11 .map_err(|_| VfsError::einval(format!("file is not valid UTF-8: {path}")))
12 }
13
14 async fn read_dir(&self, path: &str) -> VfsResult<Vec<String>>;
15 async fn read_dir_with_types(&self, path: &str) -> VfsResult<Vec<Dentry>>;
16 async fn write_file(&self, path: &str, content: &[u8]) -> VfsResult<()>;
17 async fn create_dir(&self, path: &str) -> VfsResult<()>;
18 async fn mkdir(&self, path: &str, recursive: bool) -> VfsResult<()>;
19 async fn mknod(&self, path: &str, mode: u32, rdev: u64) -> VfsResult<()> {
20 let _ = (mode, rdev);
21 Err(VfsError::eopnotsupp(format!(
22 "special inode creation is not supported for {path}"
23 )))
24 }
25 async fn exists(&self, path: &str) -> bool;
26 async fn stat(&self, path: &str) -> VfsResult<VirtualStat>;
27 async fn lstat(&self, path: &str) -> VfsResult<VirtualStat>;
28 async fn remove_file(&self, path: &str) -> VfsResult<()>;
29 async fn remove_dir(&self, path: &str) -> VfsResult<()>;
30 async fn rename(&self, old_path: &str, new_path: &str) -> VfsResult<()>;
31 async fn realpath(&self, path: &str) -> VfsResult<String>;
32 async fn symlink(&self, target: &str, link_path: &str) -> VfsResult<()>;
33 async fn readlink(&self, path: &str) -> VfsResult<String>;
34 async fn link(&self, old_path: &str, new_path: &str) -> VfsResult<()>;
35 async fn chmod(&self, path: &str, mode: u32) -> VfsResult<()>;
36 async fn chown(&self, path: &str, uid: u32, gid: u32) -> VfsResult<()>;
37 async fn lchown(&self, path: &str, uid: u32, gid: u32) -> VfsResult<()> {
38 self.chown(path, uid, gid).await
39 }
40 async fn get_xattr(&self, path: &str, name: &str, follow_symlinks: bool) -> VfsResult<Vec<u8>> {
41 let _ = (name, follow_symlinks);
42 Err(VfsError::eopnotsupp(format!(
43 "extended attributes are not supported for {path}"
44 )))
45 }
46 async fn list_xattrs(&self, path: &str, follow_symlinks: bool) -> VfsResult<Vec<String>> {
47 let _ = follow_symlinks;
48 Err(VfsError::eopnotsupp(format!(
49 "extended attributes are not supported for {path}"
50 )))
51 }
52 async fn set_xattr(
53 &self,
54 path: &str,
55 name: &str,
56 value: &[u8],
57 flags: u32,
58 follow_symlinks: bool,
59 ) -> VfsResult<()> {
60 let _ = (name, value, flags, follow_symlinks);
61 Err(VfsError::eopnotsupp(format!(
62 "extended attributes are not supported for {path}"
63 )))
64 }
65 async fn remove_xattr(&self, path: &str, name: &str, follow_symlinks: bool) -> VfsResult<()> {
66 let _ = (name, follow_symlinks);
67 Err(VfsError::eopnotsupp(format!(
68 "extended attributes are not supported for {path}"
69 )))
70 }
71 async fn utimes(&self, path: &str, atime_ms: u64, mtime_ms: u64) -> VfsResult<()>;
72 async fn set_atime(&self, path: &str, atime_ms: u64) -> VfsResult<()> {
74 let stat = self.stat(path).await?;
75 let mtime_ms = if stat.mtime.sec < 0 {
76 0
77 } else {
78 (stat.mtime.sec as u64)
79 .saturating_mul(1_000)
80 .saturating_add(u64::from(stat.mtime.nsec / 1_000_000))
81 };
82 self.utimes(path, atime_ms, mtime_ms).await
83 }
84 async fn truncate(&self, path: &str, length: u64) -> VfsResult<()>;
85 async fn allocate(&self, path: &str, offset: u64, length: u64) -> VfsResult<()> {
86 const ALLOCATION_CHUNK_BYTES: u64 = 64 * 1024;
87
88 let end = offset
89 .checked_add(length)
90 .ok_or_else(|| VfsError::einval("allocation range overflows"))?;
91 if length == 0 {
92 return Ok(());
93 }
94 let stat = self.stat(path).await?;
95 if end > stat.size {
96 self.truncate(path, end).await?;
97 }
98 let mut cursor = offset;
99 while cursor < end {
100 let chunk_len = (end - cursor).min(ALLOCATION_CHUNK_BYTES);
101 let chunk_len = usize::try_from(chunk_len)
102 .map_err(|_| VfsError::einval("allocation chunk is too large"))?;
103 let mut bytes = self.pread(path, cursor, chunk_len).await?;
104 bytes.resize(chunk_len, 0);
105 self.pwrite(path, &bytes, cursor).await?;
106 cursor += chunk_len as u64;
107 }
108 Ok(())
109 }
110 async fn insert_range(&self, path: &str, offset: u64, length: u64) -> VfsResult<()> {
111 validate_shift_range(offset, length)?;
112 let size = self.stat(path).await?.size;
113 if offset >= size {
114 return Err(VfsError::einval("insert range offset must be before EOF"));
115 }
116 let tail_len = usize::try_from(size - offset)
117 .map_err(|_| VfsError::einval("insert range tail is too large"))?;
118 let tail = self.pread(path, offset, tail_len).await?;
119 self.truncate(
120 path,
121 size.checked_add(length)
122 .ok_or_else(|| VfsError::einval("insert range size overflows"))?,
123 )
124 .await?;
125 self.pwrite(path, &tail, offset + length).await?;
126 self.punch_hole(path, offset, length).await
127 }
128 async fn collapse_range(&self, path: &str, offset: u64, length: u64) -> VfsResult<()> {
129 validate_shift_range(offset, length)?;
130 let size = self.stat(path).await?.size;
131 let end = offset
132 .checked_add(length)
133 .ok_or_else(|| VfsError::einval("collapse range overflows"))?;
134 if end >= size {
135 return Err(VfsError::einval("collapse range must end before EOF"));
136 }
137 let tail_len = usize::try_from(size - end)
138 .map_err(|_| VfsError::einval("collapse range tail is too large"))?;
139 let tail = self.pread(path, end, tail_len).await?;
140 self.pwrite(path, &tail, offset).await?;
141 self.truncate(path, size - length).await
142 }
143 async fn zero_range(
145 &self,
146 path: &str,
147 offset: u64,
148 length: u64,
149 keep_size: bool,
150 ) -> VfsResult<()> {
151 let end = offset
152 .checked_add(length)
153 .ok_or_else(|| VfsError::einval("zero range overflows"))?;
154 if length == 0 {
155 return Err(VfsError::einval("zero range length must be nonzero"));
156 }
157 let original_size = self.stat(path).await?.size;
158 self.allocate(path, offset, length).await?;
159 let zero_end = if keep_size {
160 end.min(original_size)
161 } else {
162 end
163 };
164 let mut cursor = offset.min(zero_end);
165 while cursor < zero_end {
166 let chunk_len = usize::try_from((zero_end - cursor).min(64 * 1024))
167 .map_err(|_| VfsError::einval("zero range chunk is too large"))?;
168 self.pwrite(path, &vec![0; chunk_len], cursor).await?;
169 cursor += chunk_len as u64;
170 }
171 if keep_size && self.stat(path).await?.size != original_size {
172 self.truncate(path, original_size).await?;
173 }
174 Ok(())
175 }
176 async fn punch_hole(&self, path: &str, offset: u64, length: u64) -> VfsResult<()> {
179 const PUNCH_CHUNK_BYTES: u64 = 64 * 1024;
180
181 let requested_end = offset
182 .checked_add(length)
183 .ok_or_else(|| VfsError::einval("hole-punch range overflows"))?;
184 let size = self.stat(path).await?.size;
185 let end = requested_end.min(size);
186 let mut cursor = offset.min(size);
187 while cursor < end {
188 let chunk_len = (end - cursor).min(PUNCH_CHUNK_BYTES) as usize;
189 self.pwrite(path, &vec![0; chunk_len], cursor).await?;
190 cursor += chunk_len as u64;
191 }
192 Ok(())
193 }
194 async fn allocated_ranges(&self, path: &str) -> VfsResult<Vec<(u64, u64)>> {
196 Err(VfsError::eopnotsupp(format!(
197 "extent mapping is not supported for {path}"
198 )))
199 }
200 async fn unwritten_ranges(&self, _path: &str) -> VfsResult<Vec<(u64, u64)>> {
202 Ok(Vec::new())
203 }
204 async fn pread(&self, path: &str, offset: u64, length: usize) -> VfsResult<Vec<u8>>;
205 async fn pwrite(&self, path: &str, content: &[u8], offset: u64) -> VfsResult<()>;
206 async fn append(&self, path: &str, content: &[u8]) -> VfsResult<u64>;
207
208 async fn sync(&self, _path: &str) -> VfsResult<()> {
209 Ok(())
210 }
211
212 async fn shutdown(&self) -> VfsResult<()> {
213 Ok(())
214 }
215}
216
217fn validate_shift_range(offset: u64, length: u64) -> VfsResult<()> {
218 const ALIGNMENT: u64 = 512;
219 if length == 0 || !offset.is_multiple_of(ALIGNMENT) || !length.is_multiple_of(ALIGNMENT) {
220 return Err(VfsError::einval(
221 "insert/collapse range requires a nonzero 512-byte-aligned range",
222 ));
223 }
224 Ok(())
225}
226
227#[async_trait]
228pub trait Snapshottable: Send + Sync {
229 async fn snapshot(&self, root: u64) -> VfsResult<SnapshotId>;
230 async fn fork(&self, snap: SnapshotId) -> VfsResult<u64>;
231}