1use alloc::boxed::Box;
25use alloc::collections::VecDeque;
26use alloc::rc::Rc;
27use alloc::string::{String, ToString};
28use alloc::vec::Vec;
29use core::cell::{Cell, RefCell};
30use core::fmt::Write as _;
31use core::future::Future;
32use core::pin::Pin;
33use core::task::{Context, Poll, Waker};
34use wasmtime::component::{Component, Linker, Resource, ResourceTable};
35use wasmtime::{Engine, Result, Store, bail};
36use wasmtime_wasi_io::{
37 IoView,
38 bytes::Bytes,
39 poll::{DynPollable, Pollable, subscribe},
40 streams::{DynInputStream, DynOutputStream, InputStream, OutputStream},
41};
42
43#[unsafe(no_mangle)]
48pub unsafe extern "C" fn run_wasi(
49 out_buf: *mut u8,
50 out_size: *mut usize,
51 wasi_component: *const u8,
52 wasi_component_size: usize,
53) -> usize {
54 unsafe {
55 let buf = core::slice::from_raw_parts_mut(out_buf, *out_size);
56 let wasi_component = core::slice::from_raw_parts(wasi_component, wasi_component_size);
57 match run(wasi_component) {
58 Ok(output) => {
59 let len = buf.len().min(output.len());
60 buf[..len].copy_from_slice(&output.as_bytes()[..len]);
61 *out_size = len;
62 return 0;
63 }
64 Err(e) => {
65 let msg = format!("{e:?}");
66 let len = buf.len().min(msg.len());
67 buf[..len].copy_from_slice(&msg.as_bytes()[..len]);
68 *out_size = len;
69 return 1;
70 }
71 }
72 }
73}
74
75fn run(wasi_component: &[u8]) -> Result<String> {
76 let config = super::config();
77 let engine = Engine::new(&config)?;
80
81 let component = match deserialize(&engine, wasi_component)? {
83 Some(c) => c,
84 None => return Ok("cannot load native code - requires virtual memory".to_string()),
85 };
86
87 let mut linker = Linker::new(&engine);
91 wasmtime_wasi_io::add_to_linker_async(&mut linker)?;
92 add_to_linker_async(&mut linker)?;
93
94 let instance_pre = linker.instantiate_pre(&component)?;
96 let command_pre = CommandPre::new(instance_pre)?;
98
99 let clock = Clock::new();
101
102 block_on(clock.clone(), async move {
104 let ctx = ExampleCtx {
105 table: ResourceTable::new(),
106 clock,
107 stdout: WriteLog::new(),
108 stderr: WriteLog::new(),
109 };
110 let mut store = Store::new(&engine, ctx);
111 let instance = command_pre.instantiate_async(&mut store).await?;
113 instance
114 .wasi_cli_run()
115 .call_run(&mut store)
116 .await?
117 .map_err(|()| wasmtime::format_err!("wasi cli run returned error"))?;
118
119 store.into_data().output()
120 })
121}
122
123fn deserialize(engine: &Engine, component: &[u8]) -> Result<Option<Component>> {
124 match unsafe { Component::deserialize(engine, component) } {
125 Ok(component) => Ok(Some(component)),
126 Err(e) => {
127 if !cfg!(feature = "custom")
133 && e.to_string()
134 .contains("requires virtual memory to be enabled")
135 {
136 Ok(None)
137 } else {
138 Err(e)
139 }
140 }
141 }
142}
143
144wasmtime::component::bindgen!({
147 path: "../../../crates/wasi/src/p2/wit",
148 world: "wasi:cli/command",
149 imports: { default: trappable },
150 exports: { default: async },
151 require_store_data_send: true,
152 with: {
158 "wasi:io": wasmtime_wasi_io::bindings::wasi::io,
159 }
160});
161
162pub struct ExampleCtx {
167 table: ResourceTable,
168 clock: Clock,
169 stdout: WriteLog,
170 stderr: WriteLog,
171}
172
173impl IoView for ExampleCtx {
176 fn table(&mut self) -> &mut ResourceTable {
177 &mut self.table
178 }
179}
180
181impl ExampleCtx {
182 fn output(&self) -> Result<String> {
186 let mut out = String::new();
187 let stdout = self.stdout.log.borrow();
188 if !stdout.is_empty() {
189 write!(&mut out, "stdout:\n")?;
190 for chunk in stdout.iter() {
191 write!(&mut out, "{}", String::from_utf8_lossy(chunk))?;
192 }
193 }
194 let stderr = self.stderr.log.borrow();
195 if !stderr.is_empty() {
196 write!(&mut out, "stderr:\n")?;
197 for chunk in stderr.iter() {
198 write!(&mut out, "{}", String::from_utf8_lossy(chunk))?;
199 }
200 }
201 Ok(out)
202 }
203}
204
205pub fn add_to_linker_async(linker: &mut Linker<ExampleCtx>) -> Result<()> {
210 type Data = wasmtime::component::HasSelf<ExampleCtx>;
211
212 wasi::clocks::monotonic_clock::add_to_linker::<_, Data>(linker, |t| t)?;
213 wasi::clocks::wall_clock::add_to_linker::<_, Data>(linker, |t| t)?;
214 wasi::cli::environment::add_to_linker::<_, Data>(linker, |t| t)?;
215 wasi::cli::exit::add_to_linker::<_, Data>(linker, &Default::default(), |t| t)?;
216 wasi::cli::stdin::add_to_linker::<_, Data>(linker, |t| t)?;
217 wasi::cli::stdout::add_to_linker::<_, Data>(linker, |t| t)?;
218 wasi::cli::stderr::add_to_linker::<_, Data>(linker, |t| t)?;
219 wasi::random::random::add_to_linker::<_, Data>(linker, |t| t)?;
220 wasi::filesystem::preopens::add_to_linker::<_, Data>(linker, |t| t)?;
221 wasi::filesystem::types::add_to_linker::<_, Data>(linker, |t| t)?;
222 Ok(())
223}
224
225#[derive(Clone)]
228struct Clock(Rc<Cell<u64>>);
229impl Clock {
230 fn new() -> Self {
231 Clock(Rc::new(Cell::new(0)))
232 }
233 fn get(&self) -> u64 {
234 self.0.get()
235 }
236 fn set(&self, to: u64) {
237 self.0.set(to)
238 }
239 fn timer(&self, due: u64) -> Deadline {
240 Deadline {
241 clock: self.clone(),
242 due,
243 }
244 }
245}
246unsafe impl Send for Clock {}
248unsafe impl Sync for Clock {}
249
250#[derive(Clone)]
253struct Deadline {
254 clock: Clock,
255 due: u64,
256}
257impl Future for Deadline {
258 type Output = ();
259 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
260 let now = self.clock.get();
261 if now < self.due {
262 Executor::current().push_deadline(self.due, cx.waker().clone());
263 Poll::Pending
264 } else {
265 Poll::Ready(())
266 }
267 }
268}
269#[wasmtime_wasi_io::async_trait]
270impl Pollable for Deadline {
271 async fn ready(&mut self) {
272 self.clone().await
273 }
274}
275
276struct NeverReadable;
279#[wasmtime_wasi_io::async_trait]
280impl Pollable for NeverReadable {
281 async fn ready(&mut self) {
282 struct Pending;
283 impl Future for Pending {
284 type Output = ();
285 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
286 Poll::Pending
287 }
288 }
289 Pending.await
290 }
291}
292impl InputStream for NeverReadable {
293 fn read(&mut self, _: usize) -> wasmtime_wasi_io::streams::StreamResult<Bytes> {
294 unreachable!("never ready for reading")
295 }
296}
297
298#[derive(Clone)]
303struct WriteLog {
304 log: Rc<RefCell<VecDeque<Bytes>>>,
305}
306impl WriteLog {
307 fn new() -> Self {
308 Self {
309 log: Rc::new(RefCell::new(VecDeque::new())),
310 }
311 }
312}
313unsafe impl Send for WriteLog {}
315unsafe impl Sync for WriteLog {}
316
317impl OutputStream for WriteLog {
318 fn check_write(&mut self) -> wasmtime_wasi_io::streams::StreamResult<usize> {
319 Ok(usize::MAX)
320 }
321 fn write(&mut self, contents: Bytes) -> wasmtime_wasi_io::streams::StreamResult<()> {
322 self.log.borrow_mut().push_back(contents);
323 Ok(())
324 }
325 fn flush(&mut self) -> wasmtime_wasi_io::streams::StreamResult<()> {
326 Ok(())
327 }
328}
329#[wasmtime_wasi_io::async_trait]
330impl Pollable for WriteLog {
331 async fn ready(&mut self) {
332 }
334}
335
336static EXECUTOR: ExecutorGlobal = ExecutorGlobal::new();
339
340struct ExecutorGlobal(RefCell<Option<Executor>>);
343impl ExecutorGlobal {
344 const fn new() -> Self {
345 ExecutorGlobal(RefCell::new(None))
346 }
347}
348unsafe impl Send for ExecutorGlobal {}
350unsafe impl Sync for ExecutorGlobal {}
351
352struct Executor(Rc<RefCell<ExecutorInner>>);
355
356impl Executor {
357 pub fn new() -> Self {
358 Executor(Rc::new(RefCell::new(ExecutorInner {
359 schedule: Vec::new(),
360 })))
361 }
362 pub fn current() -> Self {
363 Executor(
364 EXECUTOR
365 .0
366 .borrow_mut()
367 .as_ref()
368 .expect("Executor::current must be called within block_on")
369 .0
370 .clone(),
371 )
372 }
373 pub fn push_deadline(&mut self, due: u64, waker: Waker) {
374 self.0.borrow_mut().schedule.push((due, waker))
375 }
376}
377
378struct ExecutorInner {
381 schedule: Vec<(u64, Waker)>,
382}
383
384impl ExecutorInner {
385 fn earliest_deadline(&self) -> Option<u64> {
388 self.schedule.iter().map(|(due, _)| due).min().copied()
389 }
390 fn ready_deadlines(&mut self, now: u64) -> Vec<Waker> {
394 let mut i = 0;
395 let mut wakers = Vec::new();
396 while i < self.schedule.len() {
399 if let Some((due, _)) = self.schedule.get(i) {
400 if *due <= now {
401 let (_, waker) = self.schedule.remove(i);
402 wakers.push(waker);
403 } else {
404 i += 1;
405 }
406 } else {
407 break;
408 }
409 }
410 wakers
411 }
412}
413
414fn block_on<R>(clock: Clock, f: impl Future<Output = Result<R>> + Send + 'static) -> Result<R> {
415 if EXECUTOR.0.borrow_mut().is_some() {
417 panic!("cannot block_on while executor is running!")
418 }
419 let executor = Executor::new();
420 *EXECUTOR.0.borrow_mut() = Some(Executor(executor.0.clone()));
421
422 let mut cx = Context::from_waker(Waker::noop());
424 let mut f = core::pin::pin!(f);
425
426 let r = 'outer: loop {
428 const POLLS_PER_CLOCK: usize = 200;
433 for _ in 0..POLLS_PER_CLOCK {
434 match f.as_mut().poll(&mut cx) {
435 Poll::Pending => {}
436 Poll::Ready(r) => break 'outer r,
437 }
438 }
439
440 if let Some(sleep_until) = executor.0.borrow().earliest_deadline() {
445 clock.set(sleep_until);
446 } else {
447 clock.set(clock.get() + 1);
448 }
449
450 for waker in executor.0.borrow_mut().ready_deadlines(clock.get()) {
452 waker.wake()
453 }
454 };
455
456 let _ = EXECUTOR
458 .0
459 .borrow_mut()
460 .take()
461 .expect("executor vacated global while running");
462 r
463}
464
465impl wasi::clocks::monotonic_clock::Host for ExampleCtx {
470 fn now(&mut self) -> Result<wasi::clocks::monotonic_clock::Instant> {
471 Ok(self.clock.get())
472 }
473 fn resolution(&mut self) -> Result<wasi::clocks::monotonic_clock::Duration> {
474 Ok(1)
475 }
476 fn subscribe_duration(
477 &mut self,
478 duration: wasi::clocks::monotonic_clock::Duration,
479 ) -> Result<Resource<DynPollable>> {
480 self.subscribe_instant(self.clock.get() + duration)
481 }
482 fn subscribe_instant(
483 &mut self,
484 deadline: wasi::clocks::monotonic_clock::Instant,
485 ) -> Result<Resource<DynPollable>> {
486 let timer = self.clock.timer(deadline);
487 let deadline = self.table().push(timer)?;
488 Ok(subscribe(self.table(), deadline)?)
489 }
490}
491
492impl wasi::clocks::wall_clock::Host for ExampleCtx {
493 fn now(&mut self) -> Result<wasi::clocks::wall_clock::Datetime> {
494 let now = self.clock.get();
497 let seconds = now / 1_000_000_000;
498 let nanoseconds = (now - (seconds * 1_000_000_000)) as u32;
499 Ok(wasi::clocks::wall_clock::Datetime {
500 seconds,
501 nanoseconds,
502 })
503 }
504 fn resolution(&mut self) -> Result<wasi::clocks::wall_clock::Datetime> {
505 Ok(wasi::clocks::wall_clock::Datetime {
506 seconds: 0,
507 nanoseconds: 1,
508 })
509 }
510}
511
512impl wasi::cli::environment::Host for ExampleCtx {
514 fn get_arguments(&mut self) -> Result<Vec<String>> {
515 Ok(Vec::new())
516 }
517 fn get_environment(&mut self) -> Result<Vec<(String, String)>> {
518 Ok(Vec::new())
519 }
520 fn initial_cwd(&mut self) -> Result<Option<String>> {
521 Ok(None)
522 }
523}
524
525impl wasi::cli::exit::Host for ExampleCtx {
531 fn exit(&mut self, code: Result<(), ()>) -> Result<()> {
532 if code.is_ok() {
533 bail!("wasi exit success")
534 } else {
535 bail!("wasi exit error")
536 }
537 }
538 fn exit_with_code(&mut self, _: u8) -> Result<()> {
542 unreachable!("this unstable func is not added to the linker");
543 }
544}
545
546impl wasi::cli::stdin::Host for ExampleCtx {
547 fn get_stdin(&mut self) -> Result<Resource<DynInputStream>> {
548 let stdin: DynInputStream = Box::new(NeverReadable);
549 Ok(self.table().push(stdin)?)
550 }
551}
552
553impl wasi::cli::stdout::Host for ExampleCtx {
554 fn get_stdout(&mut self) -> Result<Resource<DynOutputStream>> {
555 let stdout: DynOutputStream = Box::new(self.stdout.clone());
556 Ok(self.table().push(stdout)?)
557 }
558}
559
560impl wasi::cli::stderr::Host for ExampleCtx {
561 fn get_stderr(&mut self) -> Result<Resource<DynOutputStream>> {
562 let stderr: DynOutputStream = Box::new(self.stderr.clone());
563 Ok(self.table().push(stderr)?)
564 }
565}
566
567impl wasi::random::random::Host for ExampleCtx {
570 fn get_random_bytes(&mut self, len: u64) -> Result<Vec<u8>> {
571 let mut vec = Vec::new();
572 vec.resize(len as usize, 0u8);
573 Ok(vec)
574 }
575 fn get_random_u64(&mut self) -> Result<u64> {
576 Ok(0)
577 }
578}
579
580impl wasi::filesystem::preopens::Host for ExampleCtx {
585 fn get_directories(
586 &mut self,
587 ) -> Result<Vec<(Resource<wasi::filesystem::types::Descriptor>, String)>> {
588 Ok(Vec::new())
591 }
592}
593
594impl wasi::filesystem::types::HostDescriptor for ExampleCtx {
596 fn read_via_stream(
597 &mut self,
598 _: Resource<wasi::filesystem::types::Descriptor>,
599 _: u64,
600 ) -> Result<Result<Resource<DynInputStream>, wasi::filesystem::types::ErrorCode>> {
601 unreachable!("no filesystem")
602 }
603 fn write_via_stream(
604 &mut self,
605 _: Resource<wasi::filesystem::types::Descriptor>,
606 _: u64,
607 ) -> Result<Result<Resource<DynOutputStream>, wasi::filesystem::types::ErrorCode>> {
608 unreachable!("no filesystem")
609 }
610 fn append_via_stream(
611 &mut self,
612 _: Resource<wasi::filesystem::types::Descriptor>,
613 ) -> Result<Result<Resource<DynOutputStream>, wasi::filesystem::types::ErrorCode>> {
614 unreachable!("no filesystem")
615 }
616 fn advise(
617 &mut self,
618 _: Resource<wasi::filesystem::types::Descriptor>,
619 _: u64,
620 _: u64,
621 _: wasi::filesystem::types::Advice,
622 ) -> Result<Result<(), wasi::filesystem::types::ErrorCode>> {
623 unreachable!("no filesystem")
624 }
625 fn sync_data(
626 &mut self,
627 _: Resource<wasi::filesystem::types::Descriptor>,
628 ) -> Result<Result<(), wasi::filesystem::types::ErrorCode>> {
629 unreachable!("no filesystem")
630 }
631 fn get_flags(
632 &mut self,
633 _: Resource<wasi::filesystem::types::Descriptor>,
634 ) -> Result<Result<wasi::filesystem::types::DescriptorFlags, wasi::filesystem::types::ErrorCode>>
635 {
636 unreachable!("no filesystem")
637 }
638 fn get_type(
639 &mut self,
640 _: Resource<wasi::filesystem::types::Descriptor>,
641 ) -> Result<Result<wasi::filesystem::types::DescriptorType, wasi::filesystem::types::ErrorCode>>
642 {
643 unreachable!("no filesystem")
644 }
645 fn set_size(
646 &mut self,
647 _: Resource<wasi::filesystem::types::Descriptor>,
648 _: u64,
649 ) -> Result<Result<(), wasi::filesystem::types::ErrorCode>> {
650 unreachable!("no filesystem")
651 }
652 fn set_times(
653 &mut self,
654 _: Resource<wasi::filesystem::types::Descriptor>,
655 _: wasi::filesystem::types::NewTimestamp,
656 _: wasi::filesystem::types::NewTimestamp,
657 ) -> Result<Result<(), wasi::filesystem::types::ErrorCode>> {
658 unreachable!("no filesystem")
659 }
660 fn read(
661 &mut self,
662 _: Resource<wasi::filesystem::types::Descriptor>,
663 _: u64,
664 _: u64,
665 ) -> Result<Result<(Vec<u8>, bool), wasi::filesystem::types::ErrorCode>> {
666 unreachable!("no filesystem")
667 }
668 fn write(
669 &mut self,
670 _: Resource<wasi::filesystem::types::Descriptor>,
671 _: Vec<u8>,
672 _: u64,
673 ) -> Result<Result<u64, wasi::filesystem::types::ErrorCode>> {
674 unreachable!("no filesystem")
675 }
676
677 fn read_directory(
678 &mut self,
679 _: Resource<wasi::filesystem::types::Descriptor>,
680 ) -> Result<
681 Result<
682 Resource<wasi::filesystem::types::DirectoryEntryStream>,
683 wasi::filesystem::types::ErrorCode,
684 >,
685 > {
686 unreachable!("no filesystem")
687 }
688 fn sync(
689 &mut self,
690 _: Resource<wasi::filesystem::types::Descriptor>,
691 ) -> Result<Result<(), wasi::filesystem::types::ErrorCode>> {
692 unreachable!("no filesystem")
693 }
694 fn create_directory_at(
695 &mut self,
696 _: Resource<wasi::filesystem::types::Descriptor>,
697 _: String,
698 ) -> Result<Result<(), wasi::filesystem::types::ErrorCode>> {
699 unreachable!("no filesystem")
700 }
701 fn stat(
702 &mut self,
703 _: Resource<wasi::filesystem::types::Descriptor>,
704 ) -> Result<Result<wasi::filesystem::types::DescriptorStat, wasi::filesystem::types::ErrorCode>>
705 {
706 unreachable!("no filesystem")
707 }
708 fn stat_at(
709 &mut self,
710 _: Resource<wasi::filesystem::types::Descriptor>,
711 _: wasi::filesystem::types::PathFlags,
712 _: String,
713 ) -> Result<Result<wasi::filesystem::types::DescriptorStat, wasi::filesystem::types::ErrorCode>>
714 {
715 unreachable!("no filesystem")
716 }
717 fn set_times_at(
718 &mut self,
719 _: Resource<wasi::filesystem::types::Descriptor>,
720 _: wasi::filesystem::types::PathFlags,
721 _: String,
722 _: wasi::filesystem::types::NewTimestamp,
723 _: wasi::filesystem::types::NewTimestamp,
724 ) -> Result<Result<(), wasi::filesystem::types::ErrorCode>> {
725 unreachable!("no filesystem")
726 }
727 fn link_at(
728 &mut self,
729 _: Resource<wasi::filesystem::types::Descriptor>,
730 _: wasi::filesystem::types::PathFlags,
731 _: String,
732 _: Resource<wasi::filesystem::types::Descriptor>,
733 _: String,
734 ) -> Result<Result<(), wasi::filesystem::types::ErrorCode>> {
735 unreachable!("no filesystem")
736 }
737 fn open_at(
738 &mut self,
739 _: Resource<wasi::filesystem::types::Descriptor>,
740 _: wasi::filesystem::types::PathFlags,
741 _: String,
742 _: wasi::filesystem::types::OpenFlags,
743 _: wasi::filesystem::types::DescriptorFlags,
744 ) -> Result<
745 Result<Resource<wasi::filesystem::types::Descriptor>, wasi::filesystem::types::ErrorCode>,
746 > {
747 unreachable!("no filesystem")
748 }
749 fn readlink_at(
750 &mut self,
751 _: Resource<wasi::filesystem::types::Descriptor>,
752 _: String,
753 ) -> Result<Result<String, wasi::filesystem::types::ErrorCode>> {
754 unreachable!("no filesystem")
755 }
756 fn remove_directory_at(
757 &mut self,
758 _: Resource<wasi::filesystem::types::Descriptor>,
759 _: String,
760 ) -> Result<Result<(), wasi::filesystem::types::ErrorCode>> {
761 unreachable!("no filesystem")
762 }
763 fn rename_at(
764 &mut self,
765 _: Resource<wasi::filesystem::types::Descriptor>,
766 _: String,
767 _: Resource<wasi::filesystem::types::Descriptor>,
768 _: String,
769 ) -> Result<Result<(), wasi::filesystem::types::ErrorCode>> {
770 unreachable!("no filesystem")
771 }
772 fn symlink_at(
773 &mut self,
774 _: Resource<wasi::filesystem::types::Descriptor>,
775 _: String,
776 _: String,
777 ) -> Result<Result<(), wasi::filesystem::types::ErrorCode>> {
778 unreachable!("no filesystem")
779 }
780 fn unlink_file_at(
781 &mut self,
782 _: Resource<wasi::filesystem::types::Descriptor>,
783 _: String,
784 ) -> Result<Result<(), wasi::filesystem::types::ErrorCode>> {
785 unreachable!("no filesystem")
786 }
787 fn is_same_object(
788 &mut self,
789 _: Resource<wasi::filesystem::types::Descriptor>,
790 _: Resource<wasi::filesystem::types::Descriptor>,
791 ) -> Result<bool> {
792 unreachable!("no filesystem")
793 }
794 fn metadata_hash(
795 &mut self,
796 _: Resource<wasi::filesystem::types::Descriptor>,
797 ) -> Result<
798 Result<wasi::filesystem::types::MetadataHashValue, wasi::filesystem::types::ErrorCode>,
799 > {
800 unreachable!("no filesystem")
801 }
802 fn metadata_hash_at(
803 &mut self,
804 _: Resource<wasi::filesystem::types::Descriptor>,
805 _: wasi::filesystem::types::PathFlags,
806 _: String,
807 ) -> Result<
808 Result<wasi::filesystem::types::MetadataHashValue, wasi::filesystem::types::ErrorCode>,
809 > {
810 unreachable!("no filesystem")
811 }
812
813 fn drop(&mut self, _: Resource<wasi::filesystem::types::Descriptor>) -> Result<()> {
814 unreachable!("no filesystem")
815 }
816}
817impl wasi::filesystem::types::HostDirectoryEntryStream for ExampleCtx {
820 fn read_directory_entry(
821 &mut self,
822 _: Resource<wasi::filesystem::types::DirectoryEntryStream>,
823 ) -> Result<
824 Result<Option<wasi::filesystem::types::DirectoryEntry>, wasi::filesystem::types::ErrorCode>,
825 > {
826 unreachable!("no filesystem")
827 }
828 fn drop(&mut self, _: Resource<wasi::filesystem::types::DirectoryEntryStream>) -> Result<()> {
829 unreachable!("no filesystem")
830 }
831}
832
833impl wasi::filesystem::types::Host for ExampleCtx {
836 fn filesystem_error_code(
837 &mut self,
838 _: Resource<wasmtime_wasi_io::streams::Error>,
839 ) -> Result<Option<wasi::filesystem::types::ErrorCode>> {
840 Ok(None)
841 }
842}