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