1use super::durable::{EffectId, RunId};
6use super::fs::ContainError;
7use super::fuel::WorkBudget;
8use super::vfs::{Access, Placement, VfsMode};
9use super::{QuotaExceeded, Vfs};
10use std::io::{Read, Seek, SeekFrom};
11use std::sync::Arc;
12
13#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
14pub enum ReaderOptions {
15 Bytes { chunk_size: usize },
16 Lines { maximum_bytes: u64 },
17}
18#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
19pub struct ReadPosition {
20 pub offset: u64,
21 pub bom_handled: bool,
22 pub closed: bool,
23}
24#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
25pub enum ResourceKind {
26 Reader(ReaderOptions),
27 Directory,
28 Writer,
29}
30#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
32pub struct ResourceDescriptor {
33 #[serde(rename = "scope")]
34 pub run_id: RunId,
35 pub id: EffectId,
36 pub kind: ResourceKind,
37}
38pub enum ReadItem {
39 Bytes(Vec<u8>),
40 Line(String),
41}
42pub struct ReadOutput {
43 pub item: Option<ReadItem>,
44 pub(crate) _charge: super::limits::HostBytes,
45}
46impl ReadOutput {
47 pub fn new(item: Option<ReadItem>, reservation: super::limits::HostBytes) -> Self {
49 Self {
50 item,
51 _charge: reservation,
52 }
53 }
54}
55
56#[derive(Debug, thiserror::Error)]
57pub enum FilesystemError {
58 #[error(transparent)]
59 Containment(#[from] ContainError),
60 #[error(transparent)]
61 Io(#[from] std::io::Error),
62 #[error(transparent)]
63 Quota(#[from] QuotaExceeded),
64 #[error("unsupported filesystem operation: {0}")]
65 Unsupported(&'static str),
66 #[error("{0}")]
67 Execution(wasmtime::Error),
68 #[error("{0}")]
69 Rejected(String),
70 #[error(transparent)]
71 Exhausted(#[from] super::fuel::Exhausted),
72 #[error("filesystem backend failed: {0}")]
73 Backend(Box<dyn std::error::Error + Send + Sync>),
74}
75
76#[derive(Clone)]
77pub struct MountInfo {
78 pub path: String,
79 pub volume: String,
80 pub access: Access,
81 pub maximum_bytes: Option<u64>,
82}
83#[derive(Clone)]
84pub struct FilesystemInfo {
85 pub cwd: String,
86 pub mode: VfsMode,
87 pub access: Access,
88 pub volume: Option<String>,
89 pub maximum_bytes: Option<u64>,
90 pub mounts: Vec<MountInfo>,
91}
92impl FilesystemInfo {
93 pub fn from_vfs(vfs: &Vfs) -> Self {
94 Self {
95 cwd: vfs.cwd().into(),
96 mode: vfs.mode(),
97 access: vfs.access(),
98 volume: vfs.volume().map(str::to_owned),
99 maximum_bytes: vfs.quota().map(|q| q.limit()),
100 mounts: vfs
101 .mounts()
102 .iter()
103 .map(|m| MountInfo {
104 path: m.guest_path().into(),
105 volume: m.volume().into(),
106 access: m.access(),
107 maximum_bytes: m.quota().map(|q| q.limit()),
108 })
109 .collect(),
110 }
111 }
112}
113
114#[async_trait::async_trait]
115pub trait FileReader: Send + Sync {
116 fn descriptor(&self) -> Option<ResourceDescriptor> {
117 None
118 }
119 async fn next(
120 &mut self,
121 effect_id: &EffectId,
122 work: Option<Arc<WorkBudget>>,
123 limits: &super::limits::TenantLimits,
124 ) -> Result<ReadOutput, FilesystemError>;
125 async fn close(
126 &mut self,
127 effect_id: &EffectId,
128 work: Option<Arc<WorkBudget>>,
129 ) -> Result<(), FilesystemError>;
130}
131
132#[async_trait::async_trait]
133pub trait FileWriter: Send + Sync {
134 fn descriptor(&self) -> Option<ResourceDescriptor> {
135 None
136 }
137 fn is_closed(&self) -> bool;
138 async fn write(
139 &mut self,
140 effect_id: &EffectId,
141 work: Option<Arc<WorkBudget>>,
142 bytes: Vec<u8>,
143 ) -> Result<(), FilesystemError>;
144 async fn close(
146 &mut self,
147 effect_id: &EffectId,
148 work: Option<Arc<WorkBudget>>,
149 ) -> Result<(), FilesystemError>;
150}
151
152#[derive(Clone)]
153pub struct DirectoryEntry {
154 pub name: String,
155 pub path: String,
156 pub kind: &'static str,
157 pub bytes: u64,
158}
159#[async_trait::async_trait]
160pub trait DirectoryReader: Send + Sync {
161 fn descriptor(&self) -> Option<ResourceDescriptor> {
162 None
163 }
164 async fn next_entry(
165 &mut self,
166 effect_id: &EffectId,
167 work: Option<Arc<WorkBudget>>,
168 limits: &super::limits::TenantLimits,
169 ) -> Result<Option<DirectoryEntry>, FilesystemError>;
170 fn retained_bytes(&self) -> u64;
171 async fn close(
172 &mut self,
173 effect_id: &EffectId,
174 work: Option<Arc<WorkBudget>>,
175 limits: &super::limits::TenantLimits,
176 ) -> Result<(), FilesystemError>;
177}
178
179#[async_trait::async_trait]
180pub trait Filesystem: Send + Sync {
181 fn info(&self) -> FilesystemInfo;
182 fn cwd(&self) -> &str;
183 fn path_cost(&self, _path: &str) -> u64 {
185 0
186 }
187 fn placement(&self, path: &str) -> Result<Placement, FilesystemError>;
188 async fn metadata(
189 &self,
190 effect_id: &EffectId,
191 work: Option<Arc<WorkBudget>>,
192 path: &str,
193 follow: bool,
194 ) -> Result<cap_std::fs::Metadata, FilesystemError>;
195 async fn read(
196 &self,
197 effect_id: &EffectId,
198 work: Option<Arc<WorkBudget>>,
199 path: &str,
200 offset: u64,
201 maximum_bytes: u64,
202 ) -> Result<Vec<u8>, FilesystemError>;
203 async fn write(
205 &self,
206 effect_id: &EffectId,
207 work: Option<Arc<WorkBudget>>,
208 path: &str,
209 bytes: Vec<u8>,
210 ) -> Result<(), FilesystemError>;
211 async fn append(
213 &self,
214 effect_id: &EffectId,
215 work: Option<Arc<WorkBudget>>,
216 path: &str,
217 bytes: Vec<u8>,
218 ) -> Result<(), FilesystemError>;
219 async fn mkdir(
220 &self,
221 effect_id: &EffectId,
222 work: Option<Arc<WorkBudget>>,
223 path: &str,
224 recursive: bool,
225 ) -> Result<(), FilesystemError>;
226 async fn remove(
227 &self,
228 effect_id: &EffectId,
229 work: Option<Arc<WorkBudget>>,
230 path: &str,
231 recursive: bool,
232 budget: &mut dyn MutationBudget,
233 ) -> Result<(), FilesystemError>;
234 async fn move_entry(
235 &self,
236 effect_id: &EffectId,
237 work: Option<Arc<WorkBudget>>,
238 from: &str,
239 to: &str,
240 budget: &mut dyn MutationBudget,
241 ) -> Result<(), FilesystemError>;
242 async fn copy(
243 &self,
244 effect_id: &EffectId,
245 work: Option<Arc<WorkBudget>>,
246 from: &str,
247 to: &str,
248 recursive: bool,
249 budget: &mut dyn MutationBudget,
250 ) -> Result<(), FilesystemError>;
251 fn restore_reader(
253 &self,
254 _descriptor: ResourceDescriptor,
255 ) -> Result<Box<dyn FileReader>, FilesystemError> {
256 Err(FilesystemError::Unsupported("reader restoration"))
257 }
258 fn restore_directory(
259 &self,
260 _descriptor: ResourceDescriptor,
261 ) -> Result<Box<dyn DirectoryReader>, FilesystemError> {
262 Err(FilesystemError::Unsupported("directory restoration"))
263 }
264 fn restore_writer(
265 &self,
266 _descriptor: ResourceDescriptor,
267 ) -> Result<Box<dyn FileWriter>, FilesystemError> {
268 Err(FilesystemError::Unsupported("writer restoration"))
269 }
270 async fn open_reader(
271 &self,
272 effect_id: &EffectId,
273 work: Option<Arc<WorkBudget>>,
274 path: &str,
275 options: ReaderOptions,
276 ) -> Result<Box<dyn FileReader>, FilesystemError>;
277 async fn open_directory(
278 &self,
279 effect_id: &EffectId,
280 work: Option<Arc<WorkBudget>>,
281 path: &str,
282 recursive: bool,
283 limits: &super::limits::TenantLimits,
284 ) -> Result<Box<dyn DirectoryReader>, FilesystemError>;
285 async fn open_writer(
286 &self,
287 effect_id: &EffectId,
288 work: Option<Arc<WorkBudget>>,
289 path: &str,
290 ) -> Result<Box<dyn FileWriter>, FilesystemError>;
291}
292
293pub fn bounded_read(
294 mut reader: impl Read + Seek,
295 offset: u64,
296 maximum_bytes: u64,
297) -> Result<Vec<u8>, FilesystemError> {
298 reader.seek(SeekFrom::Start(offset))?;
299 let mut bytes = Vec::new();
300 let mut bounded = reader.take(maximum_bytes);
301 let mut chunk = [0; 8192];
302 loop {
303 let read = bounded.read(&mut chunk)?;
304 if read == 0 {
305 return Ok(bytes);
306 }
307 bytes
308 .try_reserve(read)
309 .map_err(|e| FilesystemError::Backend(Box::new(e)))?;
310 bytes.extend_from_slice(&chunk[..read]);
311 }
312}
313pub trait MutationBudget: Send {
316 fn begin(&mut self) -> wasmtime::Result<()>;
317 fn reserve(&mut self, bytes: u64) -> wasmtime::Result<()>;
318 fn end(&mut self);
319 fn effect(&mut self);
320 fn syscalls(&mut self, count: u64, settled: bool) -> wasmtime::Result<()>;
321 fn copied(&mut self, entries: u64, bytes: u64) -> wasmtime::Result<()>;
322}