1use super::durable::{EffectId, RunId};
6use super::fs::{ContainError, ContentPath, FileIdentity};
7use super::fuel::WorkBudget;
8use super::vfs::{Access, Placement, VfsMode};
9use super::{OpenFileGuard, QuotaCharge, QuotaExceeded, Vfs};
10use std::fs::File;
11use std::io::{BufWriter, Read, Seek, SeekFrom};
12use std::sync::Arc;
13
14#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
15pub enum ReaderOptions {
16 Bytes { chunk_size: usize },
17 Lines { maximum_bytes: u64 },
18}
19#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
20pub struct ReadPosition {
21 pub offset: u64,
22 pub bom_handled: bool,
23 pub closed: bool,
24}
25#[derive(serde::Serialize, serde::Deserialize)]
26pub enum ReadItem {
27 Bytes(#[serde(with = "super::effects::binary")] Vec<u8>),
28 Line(String),
29}
30#[derive(serde::Serialize, serde::Deserialize)]
31pub struct ReadOutput {
32 pub item: Option<ReadItem>,
33 #[serde(skip)]
34 pub(crate) _charge: Option<super::limits::HostBytes>,
35}
36impl ReadOutput {
37 pub fn new(item: Option<ReadItem>, reservation: super::limits::HostBytes) -> Self {
39 Self {
40 item,
41 _charge: Some(reservation),
42 }
43 }
44}
45
46#[derive(Debug, thiserror::Error)]
47pub enum FilesystemError {
48 #[error(transparent)]
49 Containment(#[from] ContainError),
50 #[error(transparent)]
51 Io(#[from] std::io::Error),
52 #[error(transparent)]
53 Quota(#[from] QuotaExceeded),
54 #[error("unsupported filesystem operation: {0}")]
55 Unsupported(&'static str),
56 #[error("{0}")]
57 Execution(wasmtime::Error),
58 #[error("{0}")]
59 Rejected(String),
60 #[error(transparent)]
61 Exhausted(#[from] super::fuel::Exhausted),
62 #[error("filesystem backend failed: {0}")]
63 Backend(Box<dyn std::error::Error + Send + Sync>),
64}
65
66impl FilesystemError {
67 pub fn after_effect(self) -> Self {
70 match self {
71 Self::Execution(error) if super::host::ends_the_run(&error) => Self::Execution(error),
72 Self::Exhausted(_) | Self::Backend(_) => self,
73 other => Self::Backend(Box::new(other)),
74 }
75 }
76}
77
78#[derive(Clone)]
79pub struct MountInfo {
80 pub path: String,
81 pub volume: String,
82 pub access: Access,
83 pub maximum_bytes: Option<u64>,
84}
85#[derive(Clone)]
86pub struct FilesystemInfo {
87 pub cwd: String,
88 pub mode: VfsMode,
89 pub access: Access,
90 pub volume: Option<String>,
91 pub maximum_bytes: Option<u64>,
92 pub mounts: Vec<MountInfo>,
93}
94
95#[derive(Clone, serde::Serialize)]
97pub struct DownloadOptions {
98 pub overwrite: bool,
99 pub maximum_bytes: u64,
100 pub request: [u8; 32],
101}
102
103#[async_trait::async_trait]
106pub trait Download: std::io::Write + Send {
107 fn bytes_written(&self) -> u64;
108 fn take_error(&mut self) -> Option<FilesystemError>;
110 async fn publish(self: Box<Self>, work: Arc<WorkBudget>) -> Result<(), FilesystemError>;
111}
112
113pub struct FileContents {
115 pub bytes: Vec<u8>,
116 pub _memory: super::limits::HostBytes,
117}
118
119pub struct DirectoryNames {
120 pub names: Vec<String>,
121 pub _memory: super::limits::HostBytes,
122}
123
124pub struct PreparedFileUpdate {
127 pub contents: Option<Vec<u8>>,
129 pub output: FileUpdateOutput,
130}
131
132pub struct FileUpdateOutput {
133 pub bytes: Vec<u8>,
134 pub memory: super::limits::HostBytes,
135}
136
137#[async_trait::async_trait]
141pub trait FileUpdate: Send {
142 fn saved_output(&self) -> Option<(&[u8], u64)>;
145 async fn publish(
149 self: Box<Self>,
150 work: Arc<WorkBudget>,
151 prepared: Option<PreparedFileUpdate>,
152 limits: &super::limits::TenantLimits,
153 ) -> Result<FileUpdateOutput, FilesystemError>;
154}
155impl FilesystemInfo {
156 pub fn from_vfs(vfs: &Vfs) -> Self {
157 Self {
158 cwd: vfs.cwd().into(),
159 mode: vfs.mode(),
160 access: vfs.access(),
161 volume: vfs.volume().map(str::to_owned),
162 maximum_bytes: vfs.quota().map(|q| q.limit()),
163 mounts: vfs
164 .mounts()
165 .iter()
166 .map(|m| MountInfo {
167 path: m.guest_path().into(),
168 volume: m.volume().into(),
169 access: m.access(),
170 maximum_bytes: m.quota().map(|q| q.limit()),
171 })
172 .collect(),
173 }
174 }
175}
176
177#[derive(serde::Serialize, serde::Deserialize)]
181pub struct FileReader {
182 pub run_id: RunId,
183 pub id: EffectId,
184 pub options: ReaderOptions,
185 #[serde(skip)]
186 pub(crate) local: Option<ReaderHandle>,
187}
188impl FileReader {
189 pub fn new(run_id: RunId, id: EffectId, options: ReaderOptions) -> Self {
190 Self {
191 run_id,
192 id,
193 options,
194 local: None,
195 }
196 }
197}
198
199#[derive(serde::Serialize, serde::Deserialize)]
200pub struct FileWriter {
201 pub run_id: RunId,
202 pub id: EffectId,
203 pub closed: bool,
204 #[serde(skip)]
205 pub(crate) local: Option<WriterHandle>,
206}
207impl FileWriter {
208 pub fn new(run_id: RunId, id: EffectId) -> Self {
209 Self {
210 run_id,
211 id,
212 closed: false,
213 local: None,
214 }
215 }
216}
217
218#[derive(serde::Serialize, serde::Deserialize)]
219pub struct DirectoryReader {
220 pub run_id: RunId,
221 pub id: EffectId,
222 #[serde(skip)]
223 pub(crate) local: Option<DirectoryHandle>,
224}
225impl DirectoryReader {
226 pub fn new(run_id: RunId, id: EffectId) -> Self {
227 Self {
228 run_id,
229 id,
230 local: None,
231 }
232 }
233}
234
235pub(crate) struct ReaderHandle {
237 pub(crate) file: Option<File>,
238 pub(crate) position: ReadPosition,
239 pub(crate) held: Option<OpenFileGuard>,
240}
241
242pub(crate) struct DirectoryHandle {
244 pub(crate) entries: Option<Box<dyn Iterator<Item = DirectoryEntry> + Send + Sync>>,
245}
246
247pub(crate) struct WriterHandle {
252 pub(crate) open: Option<OpenWrite>,
254 pub(crate) temp: TempFile,
259 pub(crate) final_path: ContentPath,
260}
261
262pub(crate) struct TempFile {
266 pub(crate) path: ContentPath,
267 pub(crate) identity: FileIdentity,
268}
269
270pub(crate) struct OpenWrite {
274 pub(crate) writer: BufWriter<File>,
275 pub(crate) disk_charge: QuotaCharge,
276 pub(crate) held: Option<OpenFileGuard>,
279}
280
281#[derive(Clone, serde::Serialize)]
282pub struct DirectoryEntry {
283 pub name: String,
284 pub path: String,
285 pub kind: &'static str,
286 pub bytes: u64,
287}
288impl<'de> serde::Deserialize<'de> for DirectoryEntry {
289 fn deserialize<D: serde::Deserializer<'de>>(decoder: D) -> Result<Self, D::Error> {
290 #[derive(serde::Deserialize)]
291 struct Entry {
292 name: String,
293 path: String,
294 kind: String,
295 bytes: u64,
296 }
297 let entry = Entry::deserialize(decoder)?;
298 let kind = match entry.kind.as_str() {
299 "file" => "file",
300 "directory" => "directory",
301 "symlink" => "symlink",
302 "other" => "other",
303 _ => return Err(serde::de::Error::custom("invalid filesystem entry kind")),
304 };
305 Ok(Self {
306 name: entry.name,
307 path: entry.path,
308 kind,
309 bytes: entry.bytes,
310 })
311 }
312}
313#[async_trait::async_trait]
314pub trait Filesystem: Send + Sync {
315 fn supports_download_replay(&self) -> bool {
317 false
318 }
319
320 fn supports_update_replay(&self) -> bool {
322 false
323 }
324 fn supports_resource_replay(&self) -> bool {
326 false
327 }
328
329 fn info(&self) -> FilesystemInfo;
330 fn cwd(&self) -> &str;
331 fn path_cost(&self, _path: &str) -> u64 {
333 0
334 }
335 async fn placement(&self, path: &str) -> Result<Placement, FilesystemError>;
337 async fn read_regular(
339 &self,
340 work: Option<Arc<WorkBudget>>,
341 path: &str,
342 maximum_bytes: u64,
343 limits: &super::limits::TenantLimits,
344 ) -> Result<FileContents, FilesystemError>;
345 async fn directory_names(
348 &self,
349 work: Option<Arc<WorkBudget>>,
350 path: &str,
351 maximum_entries: usize,
352 limits: &super::limits::TenantLimits,
353 ) -> Result<DirectoryNames, FilesystemError>;
354 async fn begin_download(
357 &self,
358 effect: &EffectId,
359 path: &str,
360 options: DownloadOptions,
361 work: Arc<WorkBudget>,
362 ) -> Result<Box<dyn Download>, FilesystemError>;
363 async fn begin_file_update(
367 &self,
368 effect_id: &EffectId,
369 path: &str,
370 request: [u8; 32],
371 limits: &super::limits::TenantLimits,
372 ) -> Result<Box<dyn FileUpdate>, FilesystemError>;
373 async fn metadata(
374 &self,
375 effect_id: &EffectId,
376 work: Option<Arc<WorkBudget>>,
377 path: &str,
378 follow: bool,
379 ) -> Result<cap_std::fs::Metadata, FilesystemError>;
380 async fn read(
381 &self,
382 effect_id: &EffectId,
383 work: Option<Arc<WorkBudget>>,
384 path: &str,
385 offset: u64,
386 maximum_bytes: u64,
387 ) -> Result<Vec<u8>, FilesystemError>;
388 async fn write(
390 &self,
391 effect_id: &EffectId,
392 work: Option<Arc<WorkBudget>>,
393 path: &str,
394 bytes: Vec<u8>,
395 ) -> Result<(), FilesystemError>;
396 async fn append(
398 &self,
399 effect_id: &EffectId,
400 work: Option<Arc<WorkBudget>>,
401 path: &str,
402 bytes: Vec<u8>,
403 ) -> Result<(), FilesystemError>;
404 async fn mkdir(
405 &self,
406 effect_id: &EffectId,
407 work: Option<Arc<WorkBudget>>,
408 path: &str,
409 recursive: bool,
410 ) -> Result<(), FilesystemError>;
411 async fn remove(
412 &self,
413 effect_id: &EffectId,
414 work: Option<Arc<WorkBudget>>,
415 path: &str,
416 recursive: bool,
417 budget: &mut dyn MutationBudget,
418 ) -> Result<(), FilesystemError>;
419 async fn move_entry(
420 &self,
421 effect_id: &EffectId,
422 work: Option<Arc<WorkBudget>>,
423 from: &str,
424 to: &str,
425 budget: &mut dyn MutationBudget,
426 ) -> Result<(), FilesystemError>;
427 async fn copy(
428 &self,
429 effect_id: &EffectId,
430 work: Option<Arc<WorkBudget>>,
431 from: &str,
432 to: &str,
433 recursive: bool,
434 budget: &mut dyn MutationBudget,
435 ) -> Result<(), FilesystemError>;
436 async fn next_reader(
437 &self,
438 effect_id: &EffectId,
439 work: Option<Arc<WorkBudget>>,
440 reader: &mut FileReader,
441 limits: &super::limits::TenantLimits,
442 ) -> Result<ReadOutput, FilesystemError>;
443 async fn close_reader(
444 &self,
445 effect_id: &EffectId,
446 work: Option<Arc<WorkBudget>>,
447 reader: &mut FileReader,
448 ) -> Result<(), FilesystemError>;
449 async fn next_directory(
450 &self,
451 effect_id: &EffectId,
452 work: Option<Arc<WorkBudget>>,
453 directory: &mut DirectoryReader,
454 limits: &super::limits::TenantLimits,
455 ) -> Result<Option<DirectoryEntry>, FilesystemError>;
456 async fn close_directory(
457 &self,
458 effect_id: &EffectId,
459 work: Option<Arc<WorkBudget>>,
460 directory: &mut DirectoryReader,
461 limits: &super::limits::TenantLimits,
462 ) -> Result<(), FilesystemError>;
463 async fn write_chunk(
464 &self,
465 effect_id: &EffectId,
466 work: Option<Arc<WorkBudget>>,
467 writer: &mut FileWriter,
468 bytes: Vec<u8>,
469 ) -> Result<(), FilesystemError>;
470 async fn close_writer(
472 &self,
473 effect_id: &EffectId,
474 work: Option<Arc<WorkBudget>>,
475 writer: &mut FileWriter,
476 ) -> Result<(), FilesystemError>;
477 async fn open_reader(
478 &self,
479 effect_id: &EffectId,
480 work: Option<Arc<WorkBudget>>,
481 path: &str,
482 options: ReaderOptions,
483 ) -> Result<FileReader, FilesystemError>;
484 async fn open_directory(
485 &self,
486 effect_id: &EffectId,
487 work: Option<Arc<WorkBudget>>,
488 path: &str,
489 recursive: bool,
490 limits: &super::limits::TenantLimits,
491 ) -> Result<DirectoryReader, FilesystemError>;
492 async fn open_writer(
493 &self,
494 effect_id: &EffectId,
495 work: Option<Arc<WorkBudget>>,
496 path: &str,
497 ) -> Result<FileWriter, FilesystemError>;
498}
499
500pub fn bounded_read(
501 mut reader: impl Read + Seek,
502 offset: u64,
503 maximum_bytes: u64,
504) -> Result<Vec<u8>, FilesystemError> {
505 reader.seek(SeekFrom::Start(offset))?;
506 let mut bytes = Vec::new();
507 let mut bounded = reader.take(maximum_bytes);
508 let mut chunk = [0; 8192];
509 loop {
510 let read = bounded.read(&mut chunk)?;
511 if read == 0 {
512 return Ok(bytes);
513 }
514 bytes
515 .try_reserve(read)
516 .map_err(|e| FilesystemError::Backend(Box::new(e)))?;
517 bytes.extend_from_slice(&chunk[..read]);
518 }
519}
520pub trait MutationBudget: Send {
523 fn begin(&mut self) -> wasmtime::Result<()>;
524 fn reserve(&mut self, bytes: u64) -> wasmtime::Result<()>;
525 fn end(&mut self);
526 fn effect(&mut self);
527 fn syscalls(&mut self, count: u64, settled: bool) -> wasmtime::Result<()>;
528 fn copied(&mut self, entries: u64, bytes: u64) -> wasmtime::Result<()>;
529}