1use alloc::boxed::Box;
25use alloc::collections::VecDeque;
26use alloc::rc::Rc;
27use alloc::string::{String, ToString};
28use alloc::vec::Vec;
29use anyhow::{bail, Result};
30use core::cell::{Cell, RefCell};
31use core::fmt::Write as _;
32use core::future::Future;
33use core::pin::Pin;
34use core::task::{Context, Poll, Waker};
35use wasmtime::component::{Component, Linker, Resource, ResourceTable};
36use wasmtime::{Config, Engine, Store};
37use wasmtime_wasi_io::{
38 bytes::Bytes,
39 poll::{subscribe, DynPollable, Pollable},
40 streams::{DynInputStream, DynOutputStream, InputStream, OutputStream},
41 IoView,
42};
43
44#[unsafe(no_mangle)]
49pub unsafe extern "C" fn run_wasi(
50 out_buf: *mut u8,
51 out_size: *mut usize,
52 wasi_component: *const u8,
53 wasi_component_size: usize,
54) -> usize {
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
74fn run(wasi_component: &[u8]) -> Result<String> {
75 let mut config = Config::default();
80 config.async_support(true);
81 let engine = Engine::new(&config)?;
84
85 let component = match deserialize(&engine, wasi_component)? {
87 Some(c) => c,
88 None => return Ok("cannot load native code - requires virtual memory".to_string()),
89 };
90
91 let mut linker = Linker::new(&engine);
95 wasmtime_wasi_io::add_to_linker_async(&mut linker)?;
96 add_to_linker_async(&mut linker)?;
97
98 let instance_pre = linker.instantiate_pre(&component)?;
100 let command_pre = CommandPre::new(instance_pre)?;
102
103 let clock = Clock::new();
105
106 block_on(clock.clone(), async move {
108 let ctx = ExampleCtx {
109 table: ResourceTable::new(),
110 clock,
111 stdout: WriteLog::new(),
112 stderr: WriteLog::new(),
113 };
114 let mut store = Store::new(&engine, ctx);
115 let instance = command_pre.instantiate_async(&mut store).await?;
117 instance
118 .wasi_cli_run()
119 .call_run(&mut store)
120 .await?
121 .map_err(|()| anyhow::anyhow!("wasi cli run returned error"))?;
122
123 store.into_data().output()
124 })
125}
126
127fn deserialize(engine: &Engine, component: &[u8]) -> Result<Option<Component>> {
128 match unsafe { Component::deserialize(engine, component) } {
129 Ok(component) => Ok(Some(component)),
130 Err(e) => {
131 if !cfg!(feature = "custom")
137 && e.to_string()
138 .contains("requires virtual memory to be enabled")
139 {
140 Ok(None)
141 } else {
142 Err(e)
143 }
144 }
145 }
146}
147
148wasmtime::component::bindgen!({
151 path: "../../../crates/wasi/wit",
152 world: "wasi:cli/command",
153 async: { only_imports: [] },
154 trappable_imports: true,
155 with: {
161 "wasi:io": wasmtime_wasi_io::bindings::wasi::io,
162 }
163});
164
165pub struct ExampleCtx {
170 table: ResourceTable,
171 clock: Clock,
172 stdout: WriteLog,
173 stderr: WriteLog,
174}
175
176impl IoView for ExampleCtx {
179 fn table(&mut self) -> &mut ResourceTable {
180 &mut self.table
181 }
182}
183
184impl ExampleCtx {
185 fn output(&self) -> Result<String> {
189 let mut out = String::new();
190 let stdout = self.stdout.log.borrow();
191 if !stdout.is_empty() {
192 write!(&mut out, "stdout:\n")?;
193 for chunk in stdout.iter() {
194 write!(&mut out, "{}", String::from_utf8_lossy(chunk))?;
195 }
196 }
197 let stderr = self.stderr.log.borrow();
198 if !stderr.is_empty() {
199 write!(&mut out, "stderr:\n")?;
200 for chunk in stderr.iter() {
201 write!(&mut out, "{}", String::from_utf8_lossy(chunk))?;
202 }
203 }
204 Ok(out)
205 }
206}
207
208pub fn add_to_linker_async(linker: &mut Linker<ExampleCtx>) -> Result<()> {
213 wasi::clocks::monotonic_clock::add_to_linker(linker, |t| t)?;
214 wasi::clocks::wall_clock::add_to_linker(linker, |t| t)?;
215 wasi::cli::environment::add_to_linker(linker, |t| t)?;
216 wasi::cli::exit::add_to_linker(linker, &wasi::cli::exit::LinkOptions::default(), |t| t)?;
217 wasi::cli::stdin::add_to_linker(linker, |t| t)?;
218 wasi::cli::stdout::add_to_linker(linker, |t| t)?;
219 wasi::cli::stderr::add_to_linker(linker, |t| t)?;
220 wasi::random::random::add_to_linker(linker, |t| t)?;
221 wasi::filesystem::preopens::add_to_linker(linker, |t| t)?;
222 wasi::filesystem::types::add_to_linker(linker, |t| t)?;
223 Ok(())
224}
225
226#[derive(Clone)]
229struct Clock(Rc<Cell<u64>>);
230impl Clock {
231 fn new() -> Self {
232 Clock(Rc::new(Cell::new(0)))
233 }
234 fn get(&self) -> u64 {
235 self.0.get()
236 }
237 fn set(&self, to: u64) {
238 self.0.set(to)
239 }
240 fn timer(&self, due: u64) -> Deadline {
241 Deadline {
242 clock: self.clone(),
243 due,
244 }
245 }
246}
247unsafe impl Send for Clock {}
249unsafe impl Sync for Clock {}
250
251#[derive(Clone)]
254struct Deadline {
255 clock: Clock,
256 due: u64,
257}
258impl Future for Deadline {
259 type Output = ();
260 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
261 let now = self.clock.get();
262 if now < self.due {
263 Executor::current().push_deadline(self.due, cx.waker().clone());
264 Poll::Pending
265 } else {
266 Poll::Ready(())
267 }
268 }
269}
270#[wasmtime_wasi_io::async_trait]
271impl Pollable for Deadline {
272 async fn ready(&mut self) {
273 self.clone().await
274 }
275}
276
277struct NeverReadable;
280#[wasmtime_wasi_io::async_trait]
281impl Pollable for NeverReadable {
282 async fn ready(&mut self) {
283 struct Pending;
284 impl Future for Pending {
285 type Output = ();
286 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
287 Poll::Pending
288 }
289 }
290 Pending.await
291 }
292}
293impl InputStream for NeverReadable {
294 fn read(&mut self, _: usize) -> wasmtime_wasi_io::streams::StreamResult<Bytes> {
295 unreachable!("never ready for reading")
296 }
297}
298
299#[derive(Clone)]
304struct WriteLog {
305 log: Rc<RefCell<VecDeque<Bytes>>>,
306}
307impl WriteLog {
308 fn new() -> Self {
309 Self {
310 log: Rc::new(RefCell::new(VecDeque::new())),
311 }
312 }
313}
314unsafe impl Send for WriteLog {}
316unsafe impl Sync for WriteLog {}
317
318impl OutputStream for WriteLog {
319 fn check_write(&mut self) -> wasmtime_wasi_io::streams::StreamResult<usize> {
320 Ok(usize::MAX)
321 }
322 fn write(&mut self, contents: Bytes) -> wasmtime_wasi_io::streams::StreamResult<()> {
323 self.log.borrow_mut().push_back(contents);
324 Ok(())
325 }
326 fn flush(&mut self) -> wasmtime_wasi_io::streams::StreamResult<()> {
327 Ok(())
328 }
329}
330#[wasmtime_wasi_io::async_trait]
331impl Pollable for WriteLog {
332 async fn ready(&mut self) {
333 }
335}
336
337static EXECUTOR: ExecutorGlobal = ExecutorGlobal::new();
340
341struct ExecutorGlobal(RefCell<Option<Executor>>);
344impl ExecutorGlobal {
345 const fn new() -> Self {
346 ExecutorGlobal(RefCell::new(None))
347 }
348}
349unsafe impl Send for ExecutorGlobal {}
351unsafe impl Sync for ExecutorGlobal {}
352
353struct Executor(Rc<RefCell<ExecutorInner>>);
356
357impl Executor {
358 pub fn new() -> Self {
359 Executor(Rc::new(RefCell::new(ExecutorInner {
360 schedule: Vec::new(),
361 })))
362 }
363 pub fn current() -> Self {
364 Executor(
365 EXECUTOR
366 .0
367 .borrow_mut()
368 .as_ref()
369 .expect("Executor::current must be called within block_on")
370 .0
371 .clone(),
372 )
373 }
374 pub fn push_deadline(&mut self, due: u64, waker: Waker) {
375 self.0.borrow_mut().schedule.push((due, waker))
376 }
377}
378
379struct ExecutorInner {
382 schedule: Vec<(u64, Waker)>,
383}
384
385impl ExecutorInner {
386 fn earliest_deadline(&self) -> Option<u64> {
389 self.schedule.iter().map(|(due, _)| due).min().copied()
390 }
391 fn ready_deadlines(&mut self, now: u64) -> Vec<Waker> {
395 let mut i = 0;
396 let mut wakers = Vec::new();
397 while i < self.schedule.len() {
400 if let Some((due, _)) = self.schedule.get(i) {
401 if *due <= now {
402 let (_, waker) = self.schedule.remove(i);
403 wakers.push(waker);
404 } else {
405 i += 1;
406 }
407 } else {
408 break;
409 }
410 }
411 wakers
412 }
413}
414
415fn noop_waker() -> Waker {
417 use core::task::{RawWaker, RawWakerVTable};
418 const VTABLE: RawWakerVTable = RawWakerVTable::new(
419 |_| RAW,
421 |_| {},
423 |_| {},
425 |_| {},
427 );
428 const RAW: RawWaker = RawWaker::new(core::ptr::null(), &VTABLE);
429
430 unsafe { Waker::from_raw(RAW) }
431}
432
433fn block_on<R>(clock: Clock, f: impl Future<Output = Result<R>> + Send + 'static) -> Result<R> {
434 if EXECUTOR.0.borrow_mut().is_some() {
436 panic!("cannot block_on while executor is running!")
437 }
438 let executor = Executor::new();
439 *EXECUTOR.0.borrow_mut() = Some(Executor(executor.0.clone()));
440
441 let waker = noop_waker();
443 let mut cx = Context::from_waker(&waker);
444 let mut f = core::pin::pin!(f);
445
446 let r = 'outer: loop {
448 const POLLS_PER_CLOCK: usize = 200;
453 for _ in 0..POLLS_PER_CLOCK {
454 match f.as_mut().poll(&mut cx) {
455 Poll::Pending => {}
456 Poll::Ready(r) => break 'outer r,
457 }
458 }
459
460 if let Some(sleep_until) = executor.0.borrow().earliest_deadline() {
465 clock.set(sleep_until);
466 } else {
467 clock.set(clock.get() + 1);
468 }
469
470 for waker in executor.0.borrow_mut().ready_deadlines(clock.get()) {
472 waker.wake()
473 }
474 };
475
476 let _ = EXECUTOR
478 .0
479 .borrow_mut()
480 .take()
481 .expect("executor vacated global while running");
482 r
483}
484
485impl wasi::clocks::monotonic_clock::Host for ExampleCtx {
490 fn now(&mut self) -> Result<wasi::clocks::monotonic_clock::Instant> {
491 Ok(self.clock.get())
492 }
493 fn resolution(&mut self) -> Result<wasi::clocks::monotonic_clock::Duration> {
494 Ok(1)
495 }
496 fn subscribe_duration(
497 &mut self,
498 duration: wasi::clocks::monotonic_clock::Duration,
499 ) -> Result<Resource<DynPollable>> {
500 self.subscribe_instant(self.clock.get() + duration)
501 }
502 fn subscribe_instant(
503 &mut self,
504 deadline: wasi::clocks::monotonic_clock::Instant,
505 ) -> Result<Resource<DynPollable>> {
506 let timer = self.clock.timer(deadline);
507 let deadline = self.table().push(timer)?;
508 Ok(subscribe(self.table(), deadline)?)
509 }
510}
511
512impl wasi::clocks::wall_clock::Host for ExampleCtx {
513 fn now(&mut self) -> Result<wasi::clocks::wall_clock::Datetime> {
514 let now = self.clock.get();
517 let seconds = now / 1_000_000_000;
518 let nanoseconds = (now - (seconds * 1_000_000_000)) as u32;
519 Ok(wasi::clocks::wall_clock::Datetime {
520 seconds,
521 nanoseconds,
522 })
523 }
524 fn resolution(&mut self) -> Result<wasi::clocks::wall_clock::Datetime> {
525 Ok(wasi::clocks::wall_clock::Datetime {
526 seconds: 0,
527 nanoseconds: 1,
528 })
529 }
530}
531
532impl wasi::cli::environment::Host for ExampleCtx {
534 fn get_arguments(&mut self) -> Result<Vec<String>> {
535 Ok(Vec::new())
536 }
537 fn get_environment(&mut self) -> Result<Vec<(String, String)>> {
538 Ok(Vec::new())
539 }
540 fn initial_cwd(&mut self) -> Result<Option<String>> {
541 Ok(None)
542 }
543}
544
545impl wasi::cli::exit::Host for ExampleCtx {
551 fn exit(&mut self, code: Result<(), ()>) -> Result<()> {
552 if code.is_ok() {
553 bail!("wasi exit success")
554 } else {
555 bail!("wasi exit error")
556 }
557 }
558 fn exit_with_code(&mut self, _: u8) -> Result<()> {
562 unreachable!("this unstable func is not added to the linker");
563 }
564}
565
566impl wasi::cli::stdin::Host for ExampleCtx {
567 fn get_stdin(&mut self) -> Result<Resource<DynInputStream>> {
568 let stdin: DynInputStream = Box::new(NeverReadable);
569 Ok(self.table().push(stdin)?)
570 }
571}
572
573impl wasi::cli::stdout::Host for ExampleCtx {
574 fn get_stdout(&mut self) -> Result<Resource<DynOutputStream>> {
575 let stdout: DynOutputStream = Box::new(self.stdout.clone());
576 Ok(self.table().push(stdout)?)
577 }
578}
579
580impl wasi::cli::stderr::Host for ExampleCtx {
581 fn get_stderr(&mut self) -> Result<Resource<DynOutputStream>> {
582 let stderr: DynOutputStream = Box::new(self.stderr.clone());
583 Ok(self.table().push(stderr)?)
584 }
585}
586
587impl wasi::random::random::Host for ExampleCtx {
590 fn get_random_bytes(&mut self, len: u64) -> Result<Vec<u8>> {
591 let mut vec = Vec::new();
592 vec.resize(len as usize, 0u8);
593 Ok(vec)
594 }
595 fn get_random_u64(&mut self) -> Result<u64> {
596 Ok(0)
597 }
598}
599
600impl wasi::filesystem::preopens::Host for ExampleCtx {
605 fn get_directories(
606 &mut self,
607 ) -> Result<Vec<(Resource<wasi::filesystem::types::Descriptor>, String)>> {
608 Ok(Vec::new())
611 }
612}
613
614impl wasi::filesystem::types::HostDescriptor for ExampleCtx {
616 fn read_via_stream(
617 &mut self,
618 _: Resource<wasi::filesystem::types::Descriptor>,
619 _: u64,
620 ) -> Result<Result<Resource<DynInputStream>, wasi::filesystem::types::ErrorCode>> {
621 unreachable!("no filesystem")
622 }
623 fn write_via_stream(
624 &mut self,
625 _: Resource<wasi::filesystem::types::Descriptor>,
626 _: u64,
627 ) -> Result<Result<Resource<DynOutputStream>, wasi::filesystem::types::ErrorCode>> {
628 unreachable!("no filesystem")
629 }
630 fn append_via_stream(
631 &mut self,
632 _: Resource<wasi::filesystem::types::Descriptor>,
633 ) -> Result<Result<Resource<DynOutputStream>, wasi::filesystem::types::ErrorCode>> {
634 unreachable!("no filesystem")
635 }
636 fn advise(
637 &mut self,
638 _: Resource<wasi::filesystem::types::Descriptor>,
639 _: u64,
640 _: u64,
641 _: wasi::filesystem::types::Advice,
642 ) -> Result<Result<(), wasi::filesystem::types::ErrorCode>> {
643 unreachable!("no filesystem")
644 }
645 fn sync_data(
646 &mut self,
647 _: Resource<wasi::filesystem::types::Descriptor>,
648 ) -> Result<Result<(), wasi::filesystem::types::ErrorCode>> {
649 unreachable!("no filesystem")
650 }
651 fn get_flags(
652 &mut self,
653 _: Resource<wasi::filesystem::types::Descriptor>,
654 ) -> Result<Result<wasi::filesystem::types::DescriptorFlags, wasi::filesystem::types::ErrorCode>>
655 {
656 unreachable!("no filesystem")
657 }
658 fn get_type(
659 &mut self,
660 _: Resource<wasi::filesystem::types::Descriptor>,
661 ) -> Result<Result<wasi::filesystem::types::DescriptorType, wasi::filesystem::types::ErrorCode>>
662 {
663 unreachable!("no filesystem")
664 }
665 fn set_size(
666 &mut self,
667 _: Resource<wasi::filesystem::types::Descriptor>,
668 _: u64,
669 ) -> Result<Result<(), wasi::filesystem::types::ErrorCode>> {
670 unreachable!("no filesystem")
671 }
672 fn set_times(
673 &mut self,
674 _: Resource<wasi::filesystem::types::Descriptor>,
675 _: wasi::filesystem::types::NewTimestamp,
676 _: wasi::filesystem::types::NewTimestamp,
677 ) -> Result<Result<(), wasi::filesystem::types::ErrorCode>> {
678 unreachable!("no filesystem")
679 }
680 fn read(
681 &mut self,
682 _: Resource<wasi::filesystem::types::Descriptor>,
683 _: u64,
684 _: u64,
685 ) -> Result<Result<(Vec<u8>, bool), wasi::filesystem::types::ErrorCode>> {
686 unreachable!("no filesystem")
687 }
688 fn write(
689 &mut self,
690 _: Resource<wasi::filesystem::types::Descriptor>,
691 _: Vec<u8>,
692 _: u64,
693 ) -> Result<Result<u64, wasi::filesystem::types::ErrorCode>> {
694 unreachable!("no filesystem")
695 }
696
697 fn read_directory(
698 &mut self,
699 _: Resource<wasi::filesystem::types::Descriptor>,
700 ) -> Result<
701 Result<
702 Resource<wasi::filesystem::types::DirectoryEntryStream>,
703 wasi::filesystem::types::ErrorCode,
704 >,
705 > {
706 unreachable!("no filesystem")
707 }
708 fn sync(
709 &mut self,
710 _: Resource<wasi::filesystem::types::Descriptor>,
711 ) -> Result<Result<(), wasi::filesystem::types::ErrorCode>> {
712 unreachable!("no filesystem")
713 }
714 fn create_directory_at(
715 &mut self,
716 _: Resource<wasi::filesystem::types::Descriptor>,
717 _: String,
718 ) -> Result<Result<(), wasi::filesystem::types::ErrorCode>> {
719 unreachable!("no filesystem")
720 }
721 fn stat(
722 &mut self,
723 _: Resource<wasi::filesystem::types::Descriptor>,
724 ) -> Result<Result<wasi::filesystem::types::DescriptorStat, wasi::filesystem::types::ErrorCode>>
725 {
726 unreachable!("no filesystem")
727 }
728 fn stat_at(
729 &mut self,
730 _: Resource<wasi::filesystem::types::Descriptor>,
731 _: wasi::filesystem::types::PathFlags,
732 _: String,
733 ) -> Result<Result<wasi::filesystem::types::DescriptorStat, wasi::filesystem::types::ErrorCode>>
734 {
735 unreachable!("no filesystem")
736 }
737 fn set_times_at(
738 &mut self,
739 _: Resource<wasi::filesystem::types::Descriptor>,
740 _: wasi::filesystem::types::PathFlags,
741 _: String,
742 _: wasi::filesystem::types::NewTimestamp,
743 _: wasi::filesystem::types::NewTimestamp,
744 ) -> Result<Result<(), wasi::filesystem::types::ErrorCode>> {
745 unreachable!("no filesystem")
746 }
747 fn link_at(
748 &mut self,
749 _: Resource<wasi::filesystem::types::Descriptor>,
750 _: wasi::filesystem::types::PathFlags,
751 _: String,
752 _: Resource<wasi::filesystem::types::Descriptor>,
753 _: String,
754 ) -> Result<Result<(), wasi::filesystem::types::ErrorCode>> {
755 unreachable!("no filesystem")
756 }
757 fn open_at(
758 &mut self,
759 _: Resource<wasi::filesystem::types::Descriptor>,
760 _: wasi::filesystem::types::PathFlags,
761 _: String,
762 _: wasi::filesystem::types::OpenFlags,
763 _: wasi::filesystem::types::DescriptorFlags,
764 ) -> Result<
765 Result<Resource<wasi::filesystem::types::Descriptor>, wasi::filesystem::types::ErrorCode>,
766 > {
767 unreachable!("no filesystem")
768 }
769 fn readlink_at(
770 &mut self,
771 _: Resource<wasi::filesystem::types::Descriptor>,
772 _: String,
773 ) -> Result<Result<String, wasi::filesystem::types::ErrorCode>> {
774 unreachable!("no filesystem")
775 }
776 fn remove_directory_at(
777 &mut self,
778 _: Resource<wasi::filesystem::types::Descriptor>,
779 _: String,
780 ) -> Result<Result<(), wasi::filesystem::types::ErrorCode>> {
781 unreachable!("no filesystem")
782 }
783 fn rename_at(
784 &mut self,
785 _: Resource<wasi::filesystem::types::Descriptor>,
786 _: String,
787 _: Resource<wasi::filesystem::types::Descriptor>,
788 _: String,
789 ) -> Result<Result<(), wasi::filesystem::types::ErrorCode>> {
790 unreachable!("no filesystem")
791 }
792 fn symlink_at(
793 &mut self,
794 _: Resource<wasi::filesystem::types::Descriptor>,
795 _: String,
796 _: String,
797 ) -> Result<Result<(), wasi::filesystem::types::ErrorCode>> {
798 unreachable!("no filesystem")
799 }
800 fn unlink_file_at(
801 &mut self,
802 _: Resource<wasi::filesystem::types::Descriptor>,
803 _: String,
804 ) -> Result<Result<(), wasi::filesystem::types::ErrorCode>> {
805 unreachable!("no filesystem")
806 }
807 fn is_same_object(
808 &mut self,
809 _: Resource<wasi::filesystem::types::Descriptor>,
810 _: Resource<wasi::filesystem::types::Descriptor>,
811 ) -> Result<bool> {
812 unreachable!("no filesystem")
813 }
814 fn metadata_hash(
815 &mut self,
816 _: Resource<wasi::filesystem::types::Descriptor>,
817 ) -> Result<
818 Result<wasi::filesystem::types::MetadataHashValue, wasi::filesystem::types::ErrorCode>,
819 > {
820 unreachable!("no filesystem")
821 }
822 fn metadata_hash_at(
823 &mut self,
824 _: Resource<wasi::filesystem::types::Descriptor>,
825 _: wasi::filesystem::types::PathFlags,
826 _: String,
827 ) -> Result<
828 Result<wasi::filesystem::types::MetadataHashValue, wasi::filesystem::types::ErrorCode>,
829 > {
830 unreachable!("no filesystem")
831 }
832
833 fn drop(&mut self, _: Resource<wasi::filesystem::types::Descriptor>) -> Result<()> {
834 unreachable!("no filesystem")
835 }
836}
837impl wasi::filesystem::types::HostDirectoryEntryStream for ExampleCtx {
840 fn read_directory_entry(
841 &mut self,
842 _: Resource<wasi::filesystem::types::DirectoryEntryStream>,
843 ) -> Result<
844 Result<Option<wasi::filesystem::types::DirectoryEntry>, wasi::filesystem::types::ErrorCode>,
845 > {
846 unreachable!("no filesystem")
847 }
848 fn drop(&mut self, _: Resource<wasi::filesystem::types::DirectoryEntryStream>) -> Result<()> {
849 unreachable!("no filesystem")
850 }
851}
852
853impl wasi::filesystem::types::Host for ExampleCtx {
856 fn filesystem_error_code(
857 &mut self,
858 _: Resource<wasmtime_wasi_io::streams::Error>,
859 ) -> Result<Option<wasi::filesystem::types::ErrorCode>> {
860 Ok(None)
861 }
862}