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