1use super::{FsDescriptorWrapper, FsDirectoryEntryStreamWrapper, VfsHostState};
6use bytes::Bytes;
7use fs_core::Fs;
8use std::sync::Arc;
9use wasmtime::component::Resource;
10use wasmtime_wasi::p2::bindings::sync::filesystem::types::ErrorCode;
11use wasmtime_wasi::p2::{
12 InputStream as HostInputStream, OutputStream as HostOutputStream, Pollable as Subscribe,
13 StreamError, StreamResult,
14};
15use wasmtime_wasi::TrappableError;
16
17#[cfg(feature = "s3-sync")]
18use super::SyncHooks;
19
20const O_RDONLY: u32 = 0;
22const O_WRONLY: u32 = 1;
23const O_RDWR: u32 = 2;
24const O_CREAT: u32 = 0o100;
25const O_TRUNC: u32 = 0o1000;
26
27pub struct FsInputStreamWrapper {
29 shared_vfs: Arc<Fs>,
31 fd: u32,
33 offset: u64,
35 #[cfg(feature = "s3-sync")]
37 path: Option<String>,
38 #[cfg(feature = "s3-sync")]
40 sync_hooks: Option<Arc<dyn SyncHooks>>,
41 #[cfg(feature = "s3-sync")]
43 s3_refreshed: bool,
44}
45
46impl FsInputStreamWrapper {
47 pub fn new(shared_vfs: Arc<Fs>, fd: u32, offset: u64) -> Self {
48 Self {
49 shared_vfs,
50 fd,
51 offset,
52 #[cfg(feature = "s3-sync")]
53 path: None,
54 #[cfg(feature = "s3-sync")]
55 sync_hooks: None,
56 #[cfg(feature = "s3-sync")]
57 s3_refreshed: false,
58 }
59 }
60
61 #[cfg(feature = "s3-sync")]
63 pub fn new_with_sync(
64 shared_vfs: Arc<Fs>,
65 fd: u32,
66 offset: u64,
67 path: Option<String>,
68 sync_hooks: Option<Arc<dyn SyncHooks>>,
69 ) -> Self {
70 Self {
71 shared_vfs,
72 fd,
73 offset,
74 path,
75 sync_hooks,
76 s3_refreshed: false,
77 }
78 }
79}
80
81pub struct FsOutputStreamWrapper {
83 shared_vfs: Arc<Fs>,
85 fd: u32,
87 offset: Option<u64>,
89 #[cfg(feature = "s3-sync")]
91 path: Option<String>,
92 #[cfg(feature = "s3-sync")]
94 sync_hooks: Option<Arc<dyn SyncHooks>>,
95}
96
97impl FsOutputStreamWrapper {
98 pub fn new(shared_vfs: Arc<Fs>, fd: u32, offset: Option<u64>) -> Self {
99 Self {
100 shared_vfs,
101 fd,
102 offset,
103 #[cfg(feature = "s3-sync")]
104 path: None,
105 #[cfg(feature = "s3-sync")]
106 sync_hooks: None,
107 }
108 }
109
110 #[cfg(feature = "s3-sync")]
112 pub fn new_with_sync(
113 shared_vfs: Arc<Fs>,
114 fd: u32,
115 offset: Option<u64>,
116 path: Option<String>,
117 sync_hooks: Option<Arc<dyn SyncHooks>>,
118 ) -> Self {
119 Self {
120 shared_vfs,
121 fd,
122 offset,
123 path,
124 sync_hooks,
125 }
126 }
127
128 pub fn new_with_path(
130 shared_vfs: Arc<Fs>,
131 fd: u32,
132 offset: Option<u64>,
133 #[cfg(feature = "s3-sync")] path: Option<String>,
134 #[cfg(not(feature = "s3-sync"))] _path: Option<String>,
135 ) -> Self {
136 Self {
137 shared_vfs,
138 fd,
139 offset,
140 #[cfg(feature = "s3-sync")]
141 path,
142 #[cfg(feature = "s3-sync")]
143 sync_hooks: None,
144 }
145 }
146}
147
148impl wasmtime_wasi::p2::bindings::sync::filesystem::types::Host for VfsHostState {
149 fn filesystem_error_code(
150 &mut self,
151 err: Resource<wasmtime::Error>,
152 ) -> Result<Option<ErrorCode>, wasmtime::Error> {
153 let _error = self.table.get(&err)?;
154 Ok(None)
155 }
156
157 fn convert_error_code(
158 &mut self,
159 err: TrappableError<wasmtime_wasi::p2::bindings::filesystem::types::ErrorCode>,
160 ) -> Result<wasmtime_wasi::p2::bindings::sync::filesystem::types::ErrorCode, wasmtime::Error>
161 {
162 let nonsync_error = err.downcast()?;
163
164 use wasmtime_wasi::p2::bindings::filesystem::types::ErrorCode as NonSync;
165 use wasmtime_wasi::p2::bindings::sync::filesystem::types::ErrorCode as Sync;
166
167 let sync_error = match nonsync_error {
168 NonSync::Access => Sync::Access,
169 NonSync::WouldBlock => Sync::WouldBlock,
170 NonSync::Already => Sync::Already,
171 NonSync::BadDescriptor => Sync::BadDescriptor,
172 NonSync::Busy => Sync::Busy,
173 NonSync::Deadlock => Sync::Deadlock,
174 NonSync::Quota => Sync::Quota,
175 NonSync::Exist => Sync::Exist,
176 NonSync::FileTooLarge => Sync::FileTooLarge,
177 NonSync::IllegalByteSequence => Sync::IllegalByteSequence,
178 NonSync::InProgress => Sync::InProgress,
179 NonSync::Interrupted => Sync::Interrupted,
180 NonSync::Invalid => Sync::Invalid,
181 NonSync::Io => Sync::Io,
182 NonSync::IsDirectory => Sync::IsDirectory,
183 NonSync::Loop => Sync::Loop,
184 NonSync::TooManyLinks => Sync::TooManyLinks,
185 NonSync::MessageSize => Sync::MessageSize,
186 NonSync::NameTooLong => Sync::NameTooLong,
187 NonSync::NoDevice => Sync::NoDevice,
188 NonSync::NoEntry => Sync::NoEntry,
189 NonSync::NoLock => Sync::NoLock,
190 NonSync::InsufficientMemory => Sync::InsufficientMemory,
191 NonSync::InsufficientSpace => Sync::InsufficientSpace,
192 NonSync::NotDirectory => Sync::NotDirectory,
193 NonSync::NotEmpty => Sync::NotEmpty,
194 NonSync::NotRecoverable => Sync::NotRecoverable,
195 NonSync::Unsupported => Sync::Unsupported,
196 NonSync::NoTty => Sync::NoTty,
197 NonSync::NoSuchDevice => Sync::NoSuchDevice,
198 NonSync::Overflow => Sync::Overflow,
199 NonSync::NotPermitted => Sync::NotPermitted,
200 NonSync::Pipe => Sync::Pipe,
201 NonSync::ReadOnly => Sync::ReadOnly,
202 NonSync::InvalidSeek => Sync::InvalidSeek,
203 NonSync::TextFileBusy => Sync::TextFileBusy,
204 NonSync::CrossDevice => Sync::CrossDevice,
205 };
206
207 Ok(sync_error)
208 }
209}
210
211impl VfsHostState {
212 fn get_fs_descriptor(
214 &self,
215 host_desc: &Resource<wasmtime_wasi::p2::bindings::filesystem::types::Descriptor>,
216 ) -> Result<(u32, Option<String>), wasmtime::Error> {
217 let rep = host_desc.rep();
218 let wrapper_resource: Resource<FsDescriptorWrapper> = Resource::new_borrow(rep);
219 let wrapper = self
220 .table
221 .get(&wrapper_resource)
222 .map_err(|e| wasmtime::format_err!("Failed to get descriptor from table: {}", e))?;
223 Ok((wrapper.fd, wrapper.path.clone()))
224 }
225
226 fn resolve_path(&self, dir_path: &Option<String>, relative_path: &str) -> String {
228 match dir_path {
229 Some(dir) if dir == "/" => format!("/{}", relative_path.trim_start_matches('/')),
230 Some(dir) => format!(
231 "{}/{}",
232 dir.trim_end_matches('/'),
233 relative_path.trim_start_matches('/')
234 ),
235 None => format!("/{}", relative_path.trim_start_matches('/')),
236 }
237 }
238}
239
240#[async_trait::async_trait]
241impl Subscribe for FsInputStreamWrapper {
242 async fn ready(&mut self) {
243 }
245}
246
247impl HostInputStream for FsInputStreamWrapper {
248 fn read(&mut self, size: usize) -> StreamResult<Bytes> {
249 #[cfg(feature = "s3-sync")]
251 if !self.s3_refreshed {
252 if let (Some(ref hooks), Some(ref path)) = (&self.sync_hooks, &self.path) {
253 hooks.on_read(path);
254 }
255 self.s3_refreshed = true;
256 }
257
258 let offset = self.offset;
259
260 let mut buf = vec![0u8; size];
262 let n = self
263 .shared_vfs
264 .read_at(self.fd, offset, &mut buf)
265 .map_err(|e| {
266 StreamError::LastOperationFailed(wasmtime::format_err!("read_at failed: {:?}", e))
267 })?;
268
269 if n == 0 && size > 0 {
270 return Err(StreamError::Closed);
271 }
272
273 buf.truncate(n);
274 self.offset += n as u64;
275 Ok(Bytes::from(buf))
276 }
277
278 fn skip(&mut self, nelem: usize) -> StreamResult<usize> {
279 self.offset += nelem as u64;
281 Ok(nelem)
282 }
283}
284
285#[async_trait::async_trait]
286impl Subscribe for FsOutputStreamWrapper {
287 async fn ready(&mut self) {
288 }
290}
291
292impl HostOutputStream for FsOutputStreamWrapper {
293 fn write(&mut self, bytes: Bytes) -> StreamResult<()> {
294 match self.offset {
295 Some(offset) => {
296 let n = self
298 .shared_vfs
299 .write_at(self.fd, offset, &bytes)
300 .map_err(|e| {
301 StreamError::LastOperationFailed(wasmtime::format_err!(
302 "write_at failed: {:?}",
303 e
304 ))
305 })?;
306
307 self.offset = Some(offset + n as u64);
308 }
309 None => {
310 self.shared_vfs.append_write(self.fd, &bytes).map_err(|e| {
312 StreamError::LastOperationFailed(wasmtime::format_err!(
313 "append_write failed: {:?}",
314 e
315 ))
316 })?;
317 }
318 }
319
320 Ok(())
324 }
325
326 fn flush(&mut self) -> StreamResult<()> {
327 Ok(())
329 }
330
331 fn check_write(&mut self) -> StreamResult<usize> {
332 Ok(1024 * 1024) }
335}
336
337#[cfg(feature = "s3-sync")]
339impl Drop for FsOutputStreamWrapper {
340 fn drop(&mut self) {
341 if let (Some(ref hooks), Some(ref path)) = (&self.sync_hooks, &self.path) {
343 hooks.on_write(path);
344 }
345 }
346}
347
348impl wasmtime_wasi::p2::bindings::sync::filesystem::types::HostDescriptor for VfsHostState {
349 fn read(
350 &mut self,
351 self_: Resource<wasmtime_wasi::p2::bindings::filesystem::types::Descriptor>,
352 len: u64,
353 offset: u64,
354 ) -> Result<
355 (Vec<u8>, bool),
356 TrappableError<wasmtime_wasi::p2::bindings::filesystem::types::ErrorCode>,
357 > {
358 let (fd, _) = self
359 .get_fs_descriptor(&self_)
360 .map_err(TrappableError::trap)?;
361
362 let mut buf = vec![0u8; len as usize];
364 let n = self
365 .shared_vfs
366 .read_at(fd, offset, &mut buf)
367 .map_err(convert_fs_error_to_trappable)?;
368
369 buf.truncate(n);
370 let eof = n < len as usize;
371 Ok((buf, eof))
372 }
373
374 fn write(
375 &mut self,
376 self_: Resource<wasmtime_wasi::p2::bindings::filesystem::types::Descriptor>,
377 buffer: Vec<u8>,
378 offset: u64,
379 ) -> Result<u64, TrappableError<wasmtime_wasi::p2::bindings::filesystem::types::ErrorCode>>
380 {
381 let (fd, _) = self
382 .get_fs_descriptor(&self_)
383 .map_err(TrappableError::trap)?;
384
385 let n = self
387 .shared_vfs
388 .write_at(fd, offset, &buffer)
389 .map_err(convert_fs_error_to_trappable)?;
390
391 Ok(n as u64)
392 }
393
394 fn drop(
395 &mut self,
396 rep: Resource<wasmtime_wasi::p2::bindings::filesystem::types::Descriptor>,
397 ) -> Result<(), wasmtime::Error> {
398 let wrapper_resource: Resource<FsDescriptorWrapper> = Resource::new_own(rep.rep());
399 let wrapper = self.table.delete(wrapper_resource)?;
400
401 let _ = self.shared_vfs.close(wrapper.fd); Ok(())
404 }
405
406 fn read_via_stream(
407 &mut self,
408 self_: Resource<wasmtime_wasi::p2::bindings::filesystem::types::Descriptor>,
409 offset: u64,
410 ) -> Result<
411 Resource<wasmtime_wasi::p2::DynInputStream>,
412 TrappableError<wasmtime_wasi::p2::bindings::filesystem::types::ErrorCode>,
413 > {
414 let (fd, _path) = self
415 .get_fs_descriptor(&self_)
416 .map_err(TrappableError::trap)?;
417
418 #[cfg(feature = "s3-sync")]
419 let wrapper = FsInputStreamWrapper::new_with_sync(
420 Arc::clone(&self.shared_vfs),
421 fd,
422 offset,
423 _path,
424 self.sync_hooks.clone(),
425 );
426
427 #[cfg(not(feature = "s3-sync"))]
428 let wrapper = FsInputStreamWrapper::new(Arc::clone(&self.shared_vfs), fd, offset);
429
430 let resource = self
431 .table
432 .push(Box::new(wrapper) as wasmtime_wasi::p2::DynInputStream)
433 .map_err(TrappableError::trap)?;
434
435 Ok(resource)
436 }
437
438 fn write_via_stream(
439 &mut self,
440 self_: Resource<wasmtime_wasi::p2::bindings::filesystem::types::Descriptor>,
441 offset: u64,
442 ) -> Result<
443 Resource<wasmtime_wasi::p2::DynOutputStream>,
444 TrappableError<wasmtime_wasi::p2::bindings::filesystem::types::ErrorCode>,
445 > {
446 let (fd, path) = self
447 .get_fs_descriptor(&self_)
448 .map_err(TrappableError::trap)?;
449
450 #[cfg(feature = "s3-sync")]
451 let wrapper = FsOutputStreamWrapper::new_with_sync(
452 Arc::clone(&self.shared_vfs),
453 fd,
454 Some(offset),
455 path,
456 self.sync_hooks.clone(),
457 );
458 #[cfg(not(feature = "s3-sync"))]
459 let wrapper = FsOutputStreamWrapper::new_with_path(
460 Arc::clone(&self.shared_vfs),
461 fd,
462 Some(offset),
463 path,
464 );
465
466 let resource = self
467 .table
468 .push(Box::new(wrapper) as wasmtime_wasi::p2::DynOutputStream)
469 .map_err(TrappableError::trap)?;
470
471 Ok(resource)
472 }
473
474 fn append_via_stream(
475 &mut self,
476 self_: Resource<wasmtime_wasi::p2::bindings::filesystem::types::Descriptor>,
477 ) -> Result<
478 Resource<wasmtime_wasi::p2::DynOutputStream>,
479 TrappableError<wasmtime_wasi::p2::bindings::filesystem::types::ErrorCode>,
480 > {
481 let (fd, path) = self
482 .get_fs_descriptor(&self_)
483 .map_err(TrappableError::trap)?;
484
485 #[cfg(feature = "s3-sync")]
487 let wrapper = FsOutputStreamWrapper::new_with_sync(
488 Arc::clone(&self.shared_vfs),
489 fd,
490 None,
491 path,
492 self.sync_hooks.clone(),
493 );
494 #[cfg(not(feature = "s3-sync"))]
495 let wrapper =
496 FsOutputStreamWrapper::new_with_path(Arc::clone(&self.shared_vfs), fd, None, path);
497
498 let resource = self
499 .table
500 .push(Box::new(wrapper) as wasmtime_wasi::p2::DynOutputStream)
501 .map_err(TrappableError::trap)?;
502
503 Ok(resource)
504 }
505
506 fn advise(
507 &mut self,
508 _self_: Resource<wasmtime_wasi::p2::bindings::filesystem::types::Descriptor>,
509 _offset: u64,
510 _len: u64,
511 _advice: wasmtime_wasi::p2::bindings::sync::filesystem::types::Advice,
512 ) -> Result<(), TrappableError<wasmtime_wasi::p2::bindings::filesystem::types::ErrorCode>> {
513 Ok(())
515 }
516
517 fn sync_data(
518 &mut self,
519 _self_: Resource<wasmtime_wasi::p2::bindings::filesystem::types::Descriptor>,
520 ) -> Result<(), TrappableError<wasmtime_wasi::p2::bindings::filesystem::types::ErrorCode>> {
521 Ok(())
523 }
524
525 fn get_flags(
526 &mut self,
527 _self_: Resource<wasmtime_wasi::p2::bindings::filesystem::types::Descriptor>,
528 ) -> Result<
529 wasmtime_wasi::p2::bindings::sync::filesystem::types::DescriptorFlags,
530 TrappableError<wasmtime_wasi::p2::bindings::filesystem::types::ErrorCode>,
531 > {
532 use wasmtime_wasi::p2::bindings::sync::filesystem::types::DescriptorFlags;
534 Ok(DescriptorFlags::READ | DescriptorFlags::WRITE)
535 }
536
537 fn get_type(
538 &mut self,
539 self_: Resource<wasmtime_wasi::p2::bindings::filesystem::types::Descriptor>,
540 ) -> Result<
541 wasmtime_wasi::p2::bindings::sync::filesystem::types::DescriptorType,
542 TrappableError<wasmtime_wasi::p2::bindings::filesystem::types::ErrorCode>,
543 > {
544 let (fd, _) = self
545 .get_fs_descriptor(&self_)
546 .map_err(TrappableError::trap)?;
547
548 let meta = self
549 .shared_vfs
550 .fstat(fd)
551 .map_err(convert_fs_error_to_trappable)?;
552
553 use wasmtime_wasi::p2::bindings::sync::filesystem::types::DescriptorType;
554 if meta.is_dir {
555 Ok(DescriptorType::Directory)
556 } else {
557 Ok(DescriptorType::RegularFile)
558 }
559 }
560
561 fn set_size(
562 &mut self,
563 self_: Resource<wasmtime_wasi::p2::bindings::filesystem::types::Descriptor>,
564 size: u64,
565 ) -> Result<(), TrappableError<wasmtime_wasi::p2::bindings::filesystem::types::ErrorCode>> {
566 let (fd, _) = self
567 .get_fs_descriptor(&self_)
568 .map_err(TrappableError::trap)?;
569
570 self.shared_vfs
571 .ftruncate(fd, size)
572 .map_err(convert_fs_error_to_trappable)?;
573
574 Ok(())
575 }
576
577 fn set_times(
578 &mut self,
579 _self_: Resource<wasmtime_wasi::p2::bindings::filesystem::types::Descriptor>,
580 _data_access_timestamp: wasmtime_wasi::p2::bindings::sync::filesystem::types::NewTimestamp,
581 _data_modification_timestamp: wasmtime_wasi::p2::bindings::sync::filesystem::types::NewTimestamp,
582 ) -> Result<(), TrappableError<wasmtime_wasi::p2::bindings::filesystem::types::ErrorCode>> {
583 Err(convert_sync_to_nonsync_error(ErrorCode::Unsupported))
585 }
586
587 fn set_times_at(
588 &mut self,
589 _self_: Resource<wasmtime_wasi::p2::bindings::filesystem::types::Descriptor>,
590 _path_flags: wasmtime_wasi::p2::bindings::sync::filesystem::types::PathFlags,
591 _path: String,
592 _data_access_timestamp: wasmtime_wasi::p2::bindings::sync::filesystem::types::NewTimestamp,
593 _data_modification_timestamp: wasmtime_wasi::p2::bindings::sync::filesystem::types::NewTimestamp,
594 ) -> Result<(), TrappableError<wasmtime_wasi::p2::bindings::filesystem::types::ErrorCode>> {
595 Err(convert_sync_to_nonsync_error(ErrorCode::Unsupported))
597 }
598
599 fn read_directory(
600 &mut self,
601 self_: Resource<wasmtime_wasi::p2::bindings::filesystem::types::Descriptor>,
602 ) -> Result<
603 Resource<wasmtime_wasi::p2::bindings::filesystem::types::DirectoryEntryStream>,
604 TrappableError<wasmtime_wasi::p2::bindings::filesystem::types::ErrorCode>,
605 > {
606 let (fd, _) = self
607 .get_fs_descriptor(&self_)
608 .map_err(TrappableError::trap)?;
609
610 let entries = self
612 .shared_vfs
613 .readdir_fd(fd)
614 .map_err(convert_fs_error_to_trappable)?;
615
616 let wrapper = FsDirectoryEntryStreamWrapper {
618 entries,
619 position: 0,
620 };
621 let wrapper_resource: Resource<FsDirectoryEntryStreamWrapper> = self.table.push(wrapper)?;
622
623 const _: () = {
625 use std::mem::{align_of, size_of};
626 assert!(
627 size_of::<Resource<FsDirectoryEntryStreamWrapper>>()
628 == size_of::<
629 Resource<
630 wasmtime_wasi::p2::bindings::filesystem::types::DirectoryEntryStream,
631 >,
632 >()
633 );
634 assert!(
635 align_of::<Resource<FsDirectoryEntryStreamWrapper>>()
636 == align_of::<
637 Resource<
638 wasmtime_wasi::p2::bindings::filesystem::types::DirectoryEntryStream,
639 >,
640 >()
641 );
642 };
643 let host_stream = unsafe {
644 std::mem::transmute::<
645 Resource<FsDirectoryEntryStreamWrapper>,
646 Resource<wasmtime_wasi::p2::bindings::filesystem::types::DirectoryEntryStream>,
647 >(wrapper_resource)
648 };
649 Ok(host_stream)
650 }
651
652 fn sync(
653 &mut self,
654 _self_: Resource<wasmtime_wasi::p2::bindings::filesystem::types::Descriptor>,
655 ) -> Result<(), TrappableError<wasmtime_wasi::p2::bindings::filesystem::types::ErrorCode>> {
656 Ok(())
657 }
658
659 fn create_directory_at(
660 &mut self,
661 self_: Resource<wasmtime_wasi::p2::bindings::filesystem::types::Descriptor>,
662 path: String,
663 ) -> Result<(), TrappableError<wasmtime_wasi::p2::bindings::filesystem::types::ErrorCode>> {
664 let (_, dir_path) = self
665 .get_fs_descriptor(&self_)
666 .map_err(TrappableError::trap)?;
667 let full_path = self.resolve_path(&dir_path, &path);
668
669 self.shared_vfs
670 .mkdir(&full_path)
671 .map_err(convert_fs_error_to_trappable)?;
672
673 Ok(())
674 }
675
676 fn stat(
677 &mut self,
678 self_: Resource<wasmtime_wasi::p2::bindings::filesystem::types::Descriptor>,
679 ) -> Result<
680 wasmtime_wasi::p2::bindings::sync::filesystem::types::DescriptorStat,
681 TrappableError<wasmtime_wasi::p2::bindings::filesystem::types::ErrorCode>,
682 > {
683 let (fd, _) = self
684 .get_fs_descriptor(&self_)
685 .map_err(TrappableError::trap)?;
686
687 let meta = self
688 .shared_vfs
689 .fstat(fd)
690 .map_err(convert_fs_error_to_trappable)?;
691
692 Ok(convert_metadata_to_stat(&meta))
693 }
694
695 fn stat_at(
696 &mut self,
697 self_: Resource<wasmtime_wasi::p2::bindings::filesystem::types::Descriptor>,
698 _path_flags: wasmtime_wasi::p2::bindings::sync::filesystem::types::PathFlags,
699 path: String,
700 ) -> Result<
701 wasmtime_wasi::p2::bindings::sync::filesystem::types::DescriptorStat,
702 TrappableError<wasmtime_wasi::p2::bindings::filesystem::types::ErrorCode>,
703 > {
704 let (_, dir_path) = self
705 .get_fs_descriptor(&self_)
706 .map_err(TrappableError::trap)?;
707 let full_path = self.resolve_path(&dir_path, &path);
708
709 let meta = self
710 .shared_vfs
711 .stat(&full_path)
712 .map_err(convert_fs_error_to_trappable)?;
713
714 Ok(convert_metadata_to_stat(&meta))
715 }
716
717 fn link_at(
718 &mut self,
719 _self_: Resource<wasmtime_wasi::p2::bindings::filesystem::types::Descriptor>,
720 _old_path_flags: wasmtime_wasi::p2::bindings::sync::filesystem::types::PathFlags,
721 _old_path: String,
722 _new_descriptor: Resource<wasmtime_wasi::p2::bindings::filesystem::types::Descriptor>,
723 _new_path: String,
724 ) -> Result<(), TrappableError<wasmtime_wasi::p2::bindings::filesystem::types::ErrorCode>> {
725 Err(convert_sync_to_nonsync_error(ErrorCode::Unsupported))
727 }
728
729 fn open_at(
730 &mut self,
731 self_: Resource<wasmtime_wasi::p2::bindings::filesystem::types::Descriptor>,
732 _path_flags: wasmtime_wasi::p2::bindings::sync::filesystem::types::PathFlags,
733 path: String,
734 open_flags: wasmtime_wasi::p2::bindings::sync::filesystem::types::OpenFlags,
735 flags: wasmtime_wasi::p2::bindings::sync::filesystem::types::DescriptorFlags,
736 ) -> Result<
737 Resource<wasmtime_wasi::p2::bindings::filesystem::types::Descriptor>,
738 TrappableError<wasmtime_wasi::p2::bindings::filesystem::types::ErrorCode>,
739 > {
740 let (_, dir_path) = self
741 .get_fs_descriptor(&self_)
742 .map_err(TrappableError::trap)?;
743 let full_path = self.resolve_path(&dir_path, &path);
744
745 #[cfg(feature = "s3-sync")]
748 if let Some(ref hooks) = self.sync_hooks {
749 hooks.on_open(&full_path);
750 }
751
752 let mut fs_flags = 0u32;
754
755 use wasmtime_wasi::p2::bindings::sync::filesystem::types::DescriptorFlags;
757 if flags.contains(DescriptorFlags::READ) && flags.contains(DescriptorFlags::WRITE) {
758 fs_flags |= O_RDWR;
759 } else if flags.contains(DescriptorFlags::WRITE) {
760 fs_flags |= O_WRONLY;
761 } else {
762 fs_flags |= O_RDONLY;
763 }
764
765 use wasmtime_wasi::p2::bindings::sync::filesystem::types::OpenFlags;
767 if open_flags.contains(OpenFlags::CREATE) {
768 fs_flags |= O_CREAT;
769 }
770 if open_flags.contains(OpenFlags::TRUNCATE) {
771 fs_flags |= O_TRUNC;
772 }
773
774 let fd = self
775 .shared_vfs
776 .open_path_with_flags(&full_path, fs_flags)
777 .map_err(convert_fs_error_to_trappable)?;
778
779 let wrapper = FsDescriptorWrapper {
782 fd,
783 path: Some(full_path),
784 };
785 let wrapper_resource: Resource<FsDescriptorWrapper> = self.table.push(wrapper)?;
786
787 const _: () = {
789 use std::mem::{align_of, size_of};
790 assert!(
791 size_of::<Resource<FsDescriptorWrapper>>()
792 == size_of::<
793 Resource<wasmtime_wasi::p2::bindings::filesystem::types::Descriptor>,
794 >()
795 );
796 assert!(
797 align_of::<Resource<FsDescriptorWrapper>>()
798 == align_of::<
799 Resource<wasmtime_wasi::p2::bindings::filesystem::types::Descriptor>,
800 >()
801 );
802 };
803 let host_descriptor = unsafe {
804 std::mem::transmute::<
805 Resource<FsDescriptorWrapper>,
806 Resource<wasmtime_wasi::p2::bindings::filesystem::types::Descriptor>,
807 >(wrapper_resource)
808 };
809 Ok(host_descriptor)
810 }
811
812 fn readlink_at(
813 &mut self,
814 _self_: Resource<wasmtime_wasi::p2::bindings::filesystem::types::Descriptor>,
815 _path: String,
816 ) -> Result<String, TrappableError<wasmtime_wasi::p2::bindings::filesystem::types::ErrorCode>>
817 {
818 Err(convert_sync_to_nonsync_error(ErrorCode::Unsupported))
820 }
821
822 fn remove_directory_at(
823 &mut self,
824 self_: Resource<wasmtime_wasi::p2::bindings::filesystem::types::Descriptor>,
825 path: String,
826 ) -> Result<(), TrappableError<wasmtime_wasi::p2::bindings::filesystem::types::ErrorCode>> {
827 let (_, dir_path) = self
828 .get_fs_descriptor(&self_)
829 .map_err(TrappableError::trap)?;
830 let full_path = self.resolve_path(&dir_path, &path);
831
832 self.shared_vfs
833 .rmdir(&full_path)
834 .map_err(convert_fs_error_to_trappable)?;
835
836 Ok(())
837 }
838
839 fn rename_at(
840 &mut self,
841 self_: Resource<wasmtime_wasi::p2::bindings::filesystem::types::Descriptor>,
842 old_path: String,
843 _new_descriptor: Resource<wasmtime_wasi::p2::bindings::filesystem::types::Descriptor>,
844 new_path: String,
845 ) -> Result<(), TrappableError<wasmtime_wasi::p2::bindings::filesystem::types::ErrorCode>> {
846 let (_, dir_path) = self
847 .get_fs_descriptor(&self_)
848 .map_err(TrappableError::trap)?;
849 let old_full = self.resolve_path(&dir_path, &old_path);
850 let new_full = self.resolve_path(&dir_path, &new_path);
851
852 self.shared_vfs
853 .rename(&old_full, &new_full)
854 .map_err(convert_fs_error_to_trappable)?;
855
856 Ok(())
857 }
858
859 fn symlink_at(
860 &mut self,
861 _self_: Resource<wasmtime_wasi::p2::bindings::filesystem::types::Descriptor>,
862 _old_path: String,
863 _new_path: String,
864 ) -> Result<(), TrappableError<wasmtime_wasi::p2::bindings::filesystem::types::ErrorCode>> {
865 Err(convert_sync_to_nonsync_error(ErrorCode::Unsupported))
867 }
868
869 fn unlink_file_at(
870 &mut self,
871 self_: Resource<wasmtime_wasi::p2::bindings::filesystem::types::Descriptor>,
872 path: String,
873 ) -> Result<(), TrappableError<wasmtime_wasi::p2::bindings::filesystem::types::ErrorCode>> {
874 let (_, dir_path) = self
875 .get_fs_descriptor(&self_)
876 .map_err(TrappableError::trap)?;
877 let full_path = self.resolve_path(&dir_path, &path);
878
879 self.shared_vfs
880 .unlink(&full_path)
881 .map_err(convert_fs_error_to_trappable)?;
882
883 #[cfg(feature = "s3-sync")]
885 if let Some(ref hooks) = self.sync_hooks {
886 hooks.on_delete(&full_path);
887 }
888
889 Ok(())
890 }
891
892 fn is_same_object(
893 &mut self,
894 self_: Resource<wasmtime_wasi::p2::bindings::filesystem::types::Descriptor>,
895 other: Resource<wasmtime_wasi::p2::bindings::filesystem::types::Descriptor>,
896 ) -> Result<bool, wasmtime::Error> {
897 let (fd1, _) = self.get_fs_descriptor(&self_)?;
898 let (fd2, _) = self.get_fs_descriptor(&other)?;
899 Ok(fd1 == fd2)
900 }
901
902 fn metadata_hash(
903 &mut self,
904 self_: Resource<wasmtime_wasi::p2::bindings::filesystem::types::Descriptor>,
905 ) -> Result<
906 wasmtime_wasi::p2::bindings::sync::filesystem::types::MetadataHashValue,
907 TrappableError<wasmtime_wasi::p2::bindings::filesystem::types::ErrorCode>,
908 > {
909 let (fd, _) = self
910 .get_fs_descriptor(&self_)
911 .map_err(TrappableError::trap)?;
912
913 let meta = self
914 .shared_vfs
915 .fstat(fd)
916 .map_err(convert_fs_error_to_trappable)?;
917
918 let lower = meta.size;
920 let upper = meta.modified;
921
922 Ok(
923 wasmtime_wasi::p2::bindings::sync::filesystem::types::MetadataHashValue {
924 lower,
925 upper,
926 },
927 )
928 }
929
930 fn metadata_hash_at(
931 &mut self,
932 self_: Resource<wasmtime_wasi::p2::bindings::filesystem::types::Descriptor>,
933 _path_flags: wasmtime_wasi::p2::bindings::sync::filesystem::types::PathFlags,
934 path: String,
935 ) -> Result<
936 wasmtime_wasi::p2::bindings::sync::filesystem::types::MetadataHashValue,
937 TrappableError<wasmtime_wasi::p2::bindings::filesystem::types::ErrorCode>,
938 > {
939 let (_, dir_path) = self
940 .get_fs_descriptor(&self_)
941 .map_err(TrappableError::trap)?;
942 let full_path = self.resolve_path(&dir_path, &path);
943
944 let meta = self
945 .shared_vfs
946 .stat(&full_path)
947 .map_err(convert_fs_error_to_trappable)?;
948
949 let lower = meta.size;
950 let upper = meta.modified;
951
952 Ok(
953 wasmtime_wasi::p2::bindings::sync::filesystem::types::MetadataHashValue {
954 lower,
955 upper,
956 },
957 )
958 }
959}
960
961impl wasmtime_wasi::p2::bindings::sync::filesystem::types::HostDirectoryEntryStream
962 for VfsHostState
963{
964 fn read_directory_entry(
965 &mut self,
966 self_: Resource<wasmtime_wasi::p2::bindings::filesystem::types::DirectoryEntryStream>,
967 ) -> Result<
968 Option<wasmtime_wasi::p2::bindings::sync::filesystem::types::DirectoryEntry>,
969 TrappableError<wasmtime_wasi::p2::bindings::filesystem::types::ErrorCode>,
970 > {
971 let rep = self_.rep();
972 let wrapper_resource: Resource<FsDirectoryEntryStreamWrapper> = Resource::new_borrow(rep);
973
974 let wrapper = self.table.get_mut(&wrapper_resource).map_err(|e| {
976 TrappableError::trap(wasmtime::format_err!(
977 "Failed to get directory stream from table: {}",
978 e
979 ))
980 })?;
981
982 if wrapper.position >= wrapper.entries.len() {
983 return Ok(None);
984 }
985
986 let (name, is_dir) = wrapper.entries[wrapper.position].clone();
987 wrapper.position += 1;
988
989 use wasmtime_wasi::p2::bindings::sync::filesystem::types::DescriptorType;
990 let type_ = if is_dir {
991 DescriptorType::Directory
992 } else {
993 DescriptorType::RegularFile
994 };
995
996 Ok(Some(
997 wasmtime_wasi::p2::bindings::sync::filesystem::types::DirectoryEntry { type_, name },
998 ))
999 }
1000
1001 fn drop(
1002 &mut self,
1003 rep: Resource<wasmtime_wasi::p2::bindings::filesystem::types::DirectoryEntryStream>,
1004 ) -> Result<(), wasmtime::Error> {
1005 let wrapper_resource: Resource<FsDirectoryEntryStreamWrapper> =
1006 Resource::new_own(rep.rep());
1007 self.table.delete(wrapper_resource)?;
1008 Ok(())
1009 }
1010}
1011
1012fn convert_sync_to_nonsync_error(
1014 sync_error: wasmtime_wasi::p2::bindings::sync::filesystem::types::ErrorCode,
1015) -> TrappableError<wasmtime_wasi::p2::bindings::filesystem::types::ErrorCode> {
1016 use wasmtime_wasi::p2::bindings::filesystem::types::ErrorCode as NonSync;
1017 use wasmtime_wasi::p2::bindings::sync::filesystem::types::ErrorCode as Sync;
1018
1019 let nonsync_error = match sync_error {
1020 Sync::Access => NonSync::Access,
1021 Sync::WouldBlock => NonSync::WouldBlock,
1022 Sync::Already => NonSync::Already,
1023 Sync::BadDescriptor => NonSync::BadDescriptor,
1024 Sync::Busy => NonSync::Busy,
1025 Sync::Deadlock => NonSync::Deadlock,
1026 Sync::Quota => NonSync::Quota,
1027 Sync::Exist => NonSync::Exist,
1028 Sync::FileTooLarge => NonSync::FileTooLarge,
1029 Sync::IllegalByteSequence => NonSync::IllegalByteSequence,
1030 Sync::InProgress => NonSync::InProgress,
1031 Sync::Interrupted => NonSync::Interrupted,
1032 Sync::Invalid => NonSync::Invalid,
1033 Sync::Io => NonSync::Io,
1034 Sync::IsDirectory => NonSync::IsDirectory,
1035 Sync::Loop => NonSync::Loop,
1036 Sync::TooManyLinks => NonSync::TooManyLinks,
1037 Sync::MessageSize => NonSync::MessageSize,
1038 Sync::NameTooLong => NonSync::NameTooLong,
1039 Sync::NoDevice => NonSync::NoDevice,
1040 Sync::NoEntry => NonSync::NoEntry,
1041 Sync::NoLock => NonSync::NoLock,
1042 Sync::InsufficientMemory => NonSync::InsufficientMemory,
1043 Sync::InsufficientSpace => NonSync::InsufficientSpace,
1044 Sync::NotDirectory => NonSync::NotDirectory,
1045 Sync::NotEmpty => NonSync::NotEmpty,
1046 Sync::NotRecoverable => NonSync::NotRecoverable,
1047 Sync::Unsupported => NonSync::Unsupported,
1048 Sync::NoTty => NonSync::NoTty,
1049 Sync::NoSuchDevice => NonSync::NoSuchDevice,
1050 Sync::Overflow => NonSync::Overflow,
1051 Sync::NotPermitted => NonSync::NotPermitted,
1052 Sync::Pipe => NonSync::Pipe,
1053 Sync::ReadOnly => NonSync::ReadOnly,
1054 Sync::InvalidSeek => NonSync::InvalidSeek,
1055 Sync::TextFileBusy => NonSync::TextFileBusy,
1056 Sync::CrossDevice => NonSync::CrossDevice,
1057 };
1058
1059 TrappableError::from(nonsync_error)
1060}
1061
1062fn convert_fs_error_to_trappable(
1064 error: fs_core::FsError,
1065) -> TrappableError<wasmtime_wasi::p2::bindings::filesystem::types::ErrorCode> {
1066 let sync_error = super::convert_fs_error(error);
1067 convert_sync_to_nonsync_error(sync_error)
1068}
1069
1070fn convert_metadata_to_stat(
1072 meta: &fs_core::Metadata,
1073) -> wasmtime_wasi::p2::bindings::sync::filesystem::types::DescriptorStat {
1074 use wasmtime_wasi::p2::bindings::sync::filesystem::types::{
1075 Datetime, DescriptorStat, DescriptorType,
1076 };
1077
1078 DescriptorStat {
1079 type_: if meta.is_dir {
1080 DescriptorType::Directory
1081 } else {
1082 DescriptorType::RegularFile
1083 },
1084 link_count: 1,
1085 size: meta.size,
1086 data_access_timestamp: Some(Datetime {
1087 seconds: meta.modified,
1088 nanoseconds: 0,
1089 }),
1090 data_modification_timestamp: Some(Datetime {
1091 seconds: meta.modified,
1092 nanoseconds: 0,
1093 }),
1094 status_change_timestamp: Some(Datetime {
1095 seconds: meta.created,
1096 nanoseconds: 0,
1097 }),
1098 }
1099}