Skip to main content

wasmtime_wasi/p3/filesystem/
host.rs

1use crate::filesystem::sys;
2use crate::filesystem::{Descriptor, Dir, File, WasiFilesystem, WasiFilesystemCtxView};
3use crate::p3::bindings::clocks::system_clock;
4use crate::p3::bindings::filesystem::types::{
5    self, Advice, DescriptorFlags, DescriptorStat, DescriptorType, DirectoryEntry, ErrorCode,
6    Filesize, MetadataHashValue, NewTimestamp, OpenFlags, PathFlags,
7};
8use crate::p3::filesystem::{FilesystemError, FilesystemResult, preopens};
9use crate::p3::{DEFAULT_BUFFER_CAPACITY, FallibleIteratorProducer};
10use bytes::BytesMut;
11use core::pin::Pin;
12use core::task::{Context, Poll, ready};
13use core::{iter, mem};
14use std::ffi::OsString;
15use std::io;
16use std::sync::Arc;
17use std::time::SystemTime;
18use tokio::sync::{mpsc, oneshot};
19use tokio::task::{JoinHandle, spawn_blocking};
20use wasmtime::component::{
21    Access, Accessor, Destination, FutureReader, Resource, ResourceTable, Source, StreamConsumer,
22    StreamProducer, StreamReader, StreamResult,
23};
24use wasmtime::error::Context as _;
25use wasmtime::{AsContextMut, StoreContextMut};
26
27fn get_descriptor<'a>(
28    table: &'a ResourceTable,
29    fd: &'a Resource<Descriptor>,
30) -> FilesystemResult<&'a Descriptor> {
31    table
32        .get(fd)
33        .context("failed to get descriptor resource from table")
34        .map_err(FilesystemError::trap)
35}
36
37fn get_file<'a>(
38    table: &'a ResourceTable,
39    fd: &'a Resource<Descriptor>,
40) -> FilesystemResult<&'a File> {
41    let file = get_descriptor(table, fd).map(Descriptor::file)??;
42    Ok(file)
43}
44
45fn get_dir<'a>(
46    table: &'a ResourceTable,
47    fd: &'a Resource<Descriptor>,
48) -> FilesystemResult<&'a Dir> {
49    let dir = get_descriptor(table, fd).map(Descriptor::dir)??;
50    Ok(dir)
51}
52
53fn get_writable_file(table: &ResourceTable, fd: &Resource<Descriptor>) -> FilesystemResult<File> {
54    let file = get_file(table, fd)?;
55    if file.perms.write_not_permitted() {
56        return Err(ErrorCode::NotPermitted.into());
57    }
58    Ok(file.clone())
59}
60
61fn systemtime_from(t: system_clock::Instant) -> Result<std::time::SystemTime, ErrorCode> {
62    if let Ok(seconds) = <i64 as TryInto<u64>>::try_into(t.seconds) {
63        // Catch nanoseconds-into-seconds Overflow error explicitly:
64        // unfortunately, Duration::new panics when input overflows
65        let duration = core::time::Duration::new(
66            seconds
67                .checked_add(u64::from(t.nanoseconds / 1_000_000_000))
68                .ok_or(ErrorCode::Overflow)?,
69            t.nanoseconds % 1_000_000_000,
70        );
71        std::time::SystemTime::UNIX_EPOCH
72            .checked_add(duration)
73            .ok_or(ErrorCode::Overflow)
74    } else {
75        let duration = core::time::Duration::new(
76            t.seconds
77                .unsigned_abs()
78                .checked_add(u64::from(t.nanoseconds / 1_000_000_000))
79                .ok_or(ErrorCode::Overflow)?,
80            t.nanoseconds % 1_000_000_000,
81        );
82        std::time::SystemTime::UNIX_EPOCH
83            .checked_sub(duration)
84            .ok_or(ErrorCode::Overflow)
85    }
86}
87
88fn systemtimespec_from(t: NewTimestamp) -> Result<Option<SystemTime>, ErrorCode> {
89    match t {
90        NewTimestamp::NoChange => Ok(None),
91        NewTimestamp::Now => Ok(Some(SystemTime::now())),
92        NewTimestamp::Timestamp(st) => Ok(Some(systemtime_from(st)?)),
93    }
94}
95
96struct ReadStreamProducer {
97    file: File,
98    offset: u64,
99    result: Option<oneshot::Sender<Result<(), ErrorCode>>>,
100    task: Option<JoinHandle<std::io::Result<BytesMut>>>,
101}
102
103impl Drop for ReadStreamProducer {
104    fn drop(&mut self) {
105        self.close(Ok(()))
106    }
107}
108
109impl ReadStreamProducer {
110    fn close(&mut self, res: Result<(), ErrorCode>) {
111        if let Some(tx) = self.result.take() {
112            _ = tx.send(res);
113        }
114    }
115
116    /// Update the internal `offset` field after reading `amt` bytes from the file.
117    fn complete_read(&mut self, amt: usize) -> StreamResult {
118        let Ok(amt) = amt.try_into() else {
119            self.close(Err(ErrorCode::Overflow));
120            return StreamResult::Dropped;
121        };
122        let Some(amt) = self.offset.checked_add(amt) else {
123            self.close(Err(ErrorCode::Overflow));
124            return StreamResult::Dropped;
125        };
126        self.offset = amt;
127        StreamResult::Completed
128    }
129}
130
131impl<D> StreamProducer<D> for ReadStreamProducer {
132    type Item = u8;
133    type Buffer = BytesMut;
134
135    fn poll_produce<'a>(
136        mut self: Pin<&mut Self>,
137        cx: &mut Context<'_>,
138        store: StoreContextMut<'a, D>,
139        mut dst: Destination<'a, Self::Item, Self::Buffer>,
140        finish: bool,
141    ) -> Poll<wasmtime::Result<StreamResult>> {
142        if let Some(file) = self.file.as_blocking_file() {
143            // Once a blocking file, always a blocking file, so assert as such.
144            assert!(self.task.is_none());
145            let mut dst = dst.as_direct(store, DEFAULT_BUFFER_CAPACITY);
146            let buf = dst.remaining();
147            if buf.is_empty() {
148                return Poll::Ready(Ok(StreamResult::Completed));
149            }
150            return match sys::read_at_cursor_unspecified(file, buf, self.offset) {
151                Ok(0) => {
152                    self.close(Ok(()));
153                    Poll::Ready(Ok(StreamResult::Dropped))
154                }
155                Ok(n) => {
156                    dst.mark_written(n);
157                    Poll::Ready(Ok(self.complete_read(n)))
158                }
159                Err(err) => {
160                    self.close(Err(err.into()));
161                    Poll::Ready(Ok(StreamResult::Dropped))
162                }
163            };
164        }
165
166        // Lazily spawn a read task if one hasn't already been spawned yet.
167        let me = &mut *self;
168        let task = me.task.get_or_insert_with(|| {
169            let mut buf = dst.take_buffer();
170            buf.resize(DEFAULT_BUFFER_CAPACITY, 0);
171            let file = Arc::clone(me.file.as_file());
172            let offset = me.offset;
173            spawn_blocking(move || {
174                sys::read_at_cursor_unspecified(&file, &mut buf, offset).map(|n| {
175                    buf.truncate(n);
176                    buf
177                })
178            })
179        });
180
181        // Await the completion of the read task. Note that this is not a
182        // cancellable await point because we can't cancel the other task, so
183        // the `finish` parameter is ignored.
184        let result = match Pin::new(&mut *task).poll(cx) {
185            // If cancellation is requested, then flag that to Tokio. Note that
186            // this still waits for the actual completion of the spawned task,
187            // which won't actually happen if it's already executing.
188            Poll::Pending if finish => {
189                task.abort();
190                ready!(Pin::new(task).poll(cx))
191            }
192            other => ready!(other),
193        };
194        self.task = None;
195        match result {
196            Ok(Ok(buf)) if buf.is_empty() => {
197                self.close(Ok(()));
198                Poll::Ready(Ok(StreamResult::Dropped))
199            }
200            Ok(Ok(buf)) => {
201                let n = buf.len();
202                dst.set_buffer(buf);
203                Poll::Ready(Ok(self.complete_read(n)))
204            }
205            Ok(Err(err)) => {
206                self.close(Err(err.into()));
207                Poll::Ready(Ok(StreamResult::Dropped))
208            }
209            Err(err) => {
210                if err.is_cancelled() {
211                    return Poll::Ready(Ok(StreamResult::Cancelled));
212                }
213                panic!("I/O task should not panic: {err}")
214            }
215        }
216    }
217}
218
219fn map_dir_entry(
220    entry: std::io::Result<(OsString, crate::filesystem::primitives::FileType)>,
221) -> Result<Option<DirectoryEntry>, ErrorCode> {
222    match entry {
223        Ok((filename, ty)) => {
224            let Ok(name) = filename.into_string() else {
225                return Err(ErrorCode::IllegalByteSequence);
226            };
227            Ok(Some(DirectoryEntry {
228                type_: ty.into(),
229                name,
230            }))
231        }
232        Err(err) => {
233            // On windows, filter out files like `C:\DumpStack.log.tmp` which we
234            // can't get full metadata for.
235            #[cfg(windows)]
236            {
237                use windows_sys::Win32::Foundation::{
238                    ERROR_ACCESS_DENIED, ERROR_SHARING_VIOLATION,
239                };
240                if err.raw_os_error() == Some(ERROR_SHARING_VIOLATION as i32)
241                    || err.raw_os_error() == Some(ERROR_ACCESS_DENIED as i32)
242                {
243                    return Ok(None);
244                }
245            }
246            Err(err.into())
247        }
248    }
249}
250
251struct ReadDirStream {
252    rx: mpsc::Receiver<DirectoryEntry>,
253    task: JoinHandle<Result<(), ErrorCode>>,
254    result: Option<oneshot::Sender<Result<(), ErrorCode>>>,
255}
256
257impl ReadDirStream {
258    fn new(
259        dir: Arc<std::fs::File>,
260        result: oneshot::Sender<Result<(), ErrorCode>>,
261    ) -> ReadDirStream {
262        let (tx, rx) = mpsc::channel(1);
263        ReadDirStream {
264            task: spawn_blocking(move || {
265                let entries = crate::filesystem::primitives::read_dir(&dir)?;
266                for entry in entries {
267                    if let Some(entry) = map_dir_entry(entry)? {
268                        if let Err(_) = tx.blocking_send(entry) {
269                            break;
270                        }
271                    }
272                }
273                Ok(())
274            }),
275            rx,
276            result: Some(result),
277        }
278    }
279
280    fn close(&mut self, res: Result<(), ErrorCode>) {
281        self.rx.close();
282        self.task.abort();
283        let _ = self.result.take().unwrap().send(res);
284    }
285}
286
287impl<D> StreamProducer<D> for ReadDirStream {
288    type Item = DirectoryEntry;
289    type Buffer = Option<DirectoryEntry>;
290
291    fn poll_produce<'a>(
292        mut self: Pin<&mut Self>,
293        cx: &mut Context<'_>,
294        mut store: StoreContextMut<'a, D>,
295        mut dst: Destination<'a, Self::Item, Self::Buffer>,
296        finish: bool,
297    ) -> Poll<wasmtime::Result<StreamResult>> {
298        // If this is a 0-length read then `mpsc::Receiver` does not expose an
299        // API to wait for an item to be available without taking it out of the
300        // channel. In lieu of that just say that we're complete and ready for a
301        // read.
302        if dst.remaining(&mut store) == Some(0) {
303            return Poll::Ready(Ok(StreamResult::Completed));
304        }
305
306        match self.rx.poll_recv(cx) {
307            // If an item is on the channel then send that along and say that
308            // the read is now complete with one item being yielded.
309            Poll::Ready(Some(item)) => {
310                dst.set_buffer(Some(item));
311                Poll::Ready(Ok(StreamResult::Completed))
312            }
313
314            // If there's nothing left on the channel then that means that an
315            // error occurred or the iterator is done. In both cases an
316            // un-cancellable wait for the spawned task is entered and we await
317            // its completion. Upon completion there our own stream is closed
318            // with the result (sending an error code on our oneshot) and then
319            // the stream is reported as dropped.
320            Poll::Ready(None) => {
321                let result = ready!(Pin::new(&mut self.task).poll(cx))
322                    .expect("spawned task should not panic");
323                self.close(result);
324                Poll::Ready(Ok(StreamResult::Dropped))
325            }
326
327            // If an item isn't ready yet then cancel this outstanding request
328            // if `finish` is set, otherwise propagate the `Pending` status.
329            Poll::Pending if finish => Poll::Ready(Ok(StreamResult::Cancelled)),
330            Poll::Pending => Poll::Pending,
331        }
332    }
333}
334
335impl Drop for ReadDirStream {
336    fn drop(&mut self) {
337        if self.result.is_some() {
338            self.close(Ok(()));
339        }
340    }
341}
342
343struct WriteStreamConsumer {
344    file: File,
345    location: WriteLocation,
346    result: Option<oneshot::Sender<Result<(), ErrorCode>>>,
347    buffer: BytesMut,
348    task: Option<JoinHandle<std::io::Result<(BytesMut, usize)>>>,
349}
350
351#[derive(Copy, Clone)]
352enum WriteLocation {
353    End,
354    Offset(u64),
355}
356
357impl WriteStreamConsumer {
358    fn new(
359        file: File,
360        location: WriteLocation,
361        result: oneshot::Sender<Result<(), ErrorCode>>,
362    ) -> Self {
363        Self {
364            file,
365            location,
366            result: Some(result),
367            buffer: BytesMut::default(),
368            task: None,
369        }
370    }
371
372    fn close(&mut self, res: Result<(), ErrorCode>) {
373        _ = self.result.take().unwrap().send(res);
374    }
375
376    /// Update the internal `offset` field after writing `amt` bytes from the file.
377    fn complete_write(&mut self, amt: usize) -> StreamResult {
378        match &mut self.location {
379            WriteLocation::End => StreamResult::Completed,
380            WriteLocation::Offset(offset) => {
381                let Ok(amt) = amt.try_into() else {
382                    self.close(Err(ErrorCode::Overflow));
383                    return StreamResult::Dropped;
384                };
385                let Some(amt) = offset.checked_add(amt) else {
386                    self.close(Err(ErrorCode::Overflow));
387                    return StreamResult::Dropped;
388                };
389                *offset = amt;
390                StreamResult::Completed
391            }
392        }
393    }
394}
395
396impl WriteLocation {
397    fn write(&self, file: &std::fs::File, bytes: &[u8]) -> io::Result<usize> {
398        match *self {
399            WriteLocation::End => sys::append_cursor_unspecified(file, bytes),
400            WriteLocation::Offset(at) => sys::write_at_cursor_unspecified(file, bytes, at),
401        }
402    }
403}
404
405impl<D> StreamConsumer<D> for WriteStreamConsumer {
406    type Item = u8;
407
408    fn poll_consume(
409        mut self: Pin<&mut Self>,
410        cx: &mut Context<'_>,
411        store: StoreContextMut<D>,
412        src: Source<Self::Item>,
413        finish: bool,
414    ) -> Poll<wasmtime::Result<StreamResult>> {
415        let mut src = src.as_direct(store);
416        if let Some(file) = self.file.as_blocking_file() {
417            // Once a blocking file, always a blocking file, so assert as such.
418            assert!(self.task.is_none());
419            return match self.location.write(file, src.remaining()) {
420                Ok(n) => {
421                    src.mark_read(n);
422                    Poll::Ready(Ok(self.complete_write(n)))
423                }
424                Err(err) => {
425                    self.close(Err(err.into()));
426                    Poll::Ready(Ok(StreamResult::Dropped))
427                }
428            };
429        }
430        let me = &mut *self;
431        let task = me.task.get_or_insert_with(|| {
432            debug_assert!(me.buffer.is_empty());
433            let remaining = src.remaining();
434            let n = remaining.len().min(DEFAULT_BUFFER_CAPACITY);
435            me.buffer.extend_from_slice(&remaining[..n]);
436            let buf = mem::take(&mut me.buffer);
437            let file = Arc::clone(me.file.as_file());
438            let location = me.location;
439            spawn_blocking(move || location.write(&file, &buf).map(|n| (buf, n)))
440        });
441        let result = match Pin::new(&mut *task).poll(cx) {
442            // If cancellation is requested, then flag that to Tokio. Note that
443            // this still waits for the actual completion of the spawned task,
444            // which won't actually happen if it's already executing.
445            Poll::Pending if finish => {
446                task.abort();
447                ready!(Pin::new(task).poll(cx))
448            }
449            other => ready!(other),
450        };
451        self.task = None;
452        match result {
453            Ok(Ok((buf, n))) => {
454                src.mark_read(n);
455                self.buffer = buf;
456                self.buffer.clear();
457                Poll::Ready(Ok(self.complete_write(n)))
458            }
459            Ok(Err(err)) => {
460                self.close(Err(err.into()));
461                Poll::Ready(Ok(StreamResult::Dropped))
462            }
463            Err(err) => {
464                if err.is_cancelled() {
465                    return Poll::Ready(Ok(StreamResult::Cancelled));
466                }
467                panic!("I/O task should not panic: {err}")
468            }
469        }
470    }
471}
472
473impl Drop for WriteStreamConsumer {
474    fn drop(&mut self) {
475        if self.result.is_some() {
476            self.close(Ok(()))
477        }
478    }
479}
480
481impl types::Host for WasiFilesystemCtxView<'_> {
482    fn convert_error_code(&mut self, error: FilesystemError) -> wasmtime::Result<ErrorCode> {
483        error.downcast()
484    }
485}
486
487fn read_via_stream(
488    mut store: impl AsContextMut,
489    fd: Descriptor,
490    offset: Filesize,
491) -> wasmtime::Result<(StreamReader<u8>, FutureReader<Result<(), ErrorCode>>)> {
492    let mut store = store.as_context_mut();
493    let file = match fd {
494        Descriptor::File(file) => file,
495        Descriptor::Dir(_) => {
496            return Ok((
497                StreamReader::new(&mut store, iter::empty())?,
498                FutureReader::new(&mut store, async move {
499                    wasmtime::error::Ok(Err(ErrorCode::IsDirectory))
500                })?,
501            ));
502        }
503    };
504    let (result_tx, result_rx) = oneshot::channel();
505    Ok((
506        StreamReader::new(
507            &mut store,
508            ReadStreamProducer {
509                file,
510                offset,
511                result: Some(result_tx),
512                task: None,
513            },
514        )?,
515        FutureReader::new(&mut store, result_rx)?,
516    ))
517}
518
519fn write_via_stream(
520    mut store: impl AsContextMut,
521    file: FilesystemResult<File>,
522    mut data: StreamReader<u8>,
523    location: WriteLocation,
524) -> wasmtime::Result<FutureReader<Result<(), ErrorCode>>> {
525    let mut store = store.as_context_mut();
526    let (result_tx, result_rx) = oneshot::channel();
527    match file {
528        Ok(file) => {
529            data.pipe(
530                &mut store,
531                WriteStreamConsumer::new(file, location, result_tx),
532            )?;
533        }
534        Err(err) => {
535            data.close(&mut store)?;
536            let _ = result_tx.send(Err(err.downcast().unwrap_or(ErrorCode::Io)));
537        }
538    }
539    FutureReader::new(&mut store, result_rx)
540}
541
542fn read_directory(
543    mut store: impl AsContextMut,
544    dir: FilesystemResult<Dir>,
545) -> wasmtime::Result<(
546    StreamReader<DirectoryEntry>,
547    FutureReader<Result<(), ErrorCode>>,
548)> {
549    let mut store = store.as_context_mut();
550    let (result_tx, result_rx) = oneshot::channel();
551    let stream = match dir {
552        Ok(dir) => {
553            let allow_blocking_current_thread = dir.allow_blocking_current_thread;
554            let dir = Arc::clone(dir.as_dir());
555            if allow_blocking_current_thread {
556                match crate::filesystem::primitives::read_dir(&dir) {
557                    Ok(readdir) => StreamReader::new(
558                        &mut store,
559                        FallibleIteratorProducer::new(
560                            readdir.filter_map(|e| map_dir_entry(e).transpose()),
561                            result_tx,
562                        ),
563                    )?,
564                    Err(e) => {
565                        let _ = result_tx.send(Err(e.into()));
566                        StreamReader::new(&mut store, iter::empty())?
567                    }
568                }
569            } else {
570                StreamReader::new(&mut store, ReadDirStream::new(dir, result_tx))?
571            }
572        }
573        Err(err) => {
574            let _ = result_tx.send(Err(err.downcast().unwrap_or(ErrorCode::Io)));
575            StreamReader::new(&mut store, iter::empty())?
576        }
577    };
578    Ok((stream, FutureReader::new(&mut store, result_rx)?))
579}
580
581impl WasiFilesystemCtxView<'_> {
582    fn advise(
583        &self,
584        fd: &Resource<Descriptor>,
585        offset: Filesize,
586        length: Filesize,
587        advice: Advice,
588    ) -> impl Future<Output = FilesystemResult<()>> + use<> {
589        let file = get_file(self.table, fd).cloned();
590        async move {
591            file?.advise(offset, length, advice.into()).await?;
592            Ok(())
593        }
594    }
595
596    fn get_flags(
597        &self,
598        fd: &Resource<Descriptor>,
599    ) -> impl Future<Output = FilesystemResult<DescriptorFlags>> + use<> {
600        let fd = get_descriptor(self.table, fd).cloned();
601        async move {
602            let flags = fd?.get_flags().await?;
603            Ok(flags.into())
604        }
605    }
606
607    fn get_type(
608        &self,
609        fd: &Resource<Descriptor>,
610    ) -> impl Future<Output = FilesystemResult<DescriptorType>> + use<> {
611        let fd = get_descriptor(self.table, fd).cloned();
612        async move {
613            let ty = fd?.get_type().await?;
614            Ok(ty.into())
615        }
616    }
617
618    fn set_size(
619        &self,
620        fd: &Resource<Descriptor>,
621        size: Filesize,
622    ) -> impl Future<Output = FilesystemResult<()>> + use<> {
623        let file = get_file(self.table, fd).cloned();
624        async move {
625            file?.set_size(size).await?;
626            Ok(())
627        }
628    }
629
630    fn set_times(
631        &self,
632        fd: &Resource<Descriptor>,
633        data_access_timestamp: NewTimestamp,
634        data_modification_timestamp: NewTimestamp,
635    ) -> impl Future<Output = FilesystemResult<()>> + use<> {
636        let fd = get_descriptor(self.table, &fd).cloned();
637        async move {
638            let atim = systemtimespec_from(data_access_timestamp)?;
639            let mtim = systemtimespec_from(data_modification_timestamp)?;
640            fd?.set_times(atim, mtim).await?;
641            Ok(())
642        }
643    }
644
645    fn sync(
646        &self,
647        fd: &Resource<Descriptor>,
648    ) -> impl Future<Output = FilesystemResult<()>> + use<> {
649        let fd = get_descriptor(self.table, &fd).cloned();
650        async move {
651            fd?.sync().await?;
652            Ok(())
653        }
654    }
655
656    fn sync_data(
657        &self,
658        fd: &Resource<Descriptor>,
659    ) -> impl Future<Output = FilesystemResult<()>> + use<> {
660        let fd = get_descriptor(self.table, &fd).cloned();
661        async move {
662            fd?.sync_data().await?;
663            Ok(())
664        }
665    }
666
667    fn create_directory_at(
668        &self,
669        fd: &Resource<Descriptor>,
670        path: String,
671    ) -> impl Future<Output = FilesystemResult<()>> + use<> {
672        let dir = get_dir(self.table, &fd).cloned();
673        async move {
674            dir?.create_directory_at(path).await?;
675            Ok(())
676        }
677    }
678
679    fn stat(
680        &self,
681        fd: &Resource<Descriptor>,
682    ) -> impl Future<Output = FilesystemResult<DescriptorStat>> + use<> {
683        let fd = get_descriptor(self.table, &fd).cloned();
684        async move {
685            let stat = fd?.stat().await?;
686            Ok(stat.into())
687        }
688    }
689
690    fn stat_at(
691        &self,
692        fd: &Resource<Descriptor>,
693        path_flags: PathFlags,
694        path: String,
695    ) -> impl Future<Output = FilesystemResult<DescriptorStat>> + use<> {
696        let dir = get_dir(self.table, &fd).cloned();
697        async move {
698            let stat = dir?.stat_at(path_flags.into(), path).await?;
699            Ok(stat.into())
700        }
701    }
702
703    fn set_times_at(
704        &self,
705        fd: &Resource<Descriptor>,
706        path_flags: PathFlags,
707        path: String,
708        data_access_timestamp: NewTimestamp,
709        data_modification_timestamp: NewTimestamp,
710    ) -> impl Future<Output = FilesystemResult<()>> + use<> {
711        let dir = get_dir(self.table, &fd).cloned();
712        async move {
713            let atim = systemtimespec_from(data_access_timestamp)?;
714            let mtim = systemtimespec_from(data_modification_timestamp)?;
715            dir?.set_times_at(path_flags.into(), path, atim, mtim)
716                .await?;
717            Ok(())
718        }
719    }
720
721    fn link_at(
722        &self,
723        old_fd: &Resource<Descriptor>,
724        old_path_flags: PathFlags,
725        old_path: String,
726        new_fd: &Resource<Descriptor>,
727        new_path: String,
728    ) -> impl Future<Output = FilesystemResult<()>> + use<> {
729        let old_dir = get_dir(self.table, old_fd).cloned();
730        let new_dir = get_dir(self.table, new_fd).cloned();
731
732        async move {
733            old_dir?
734                .link_at(old_path_flags.into(), old_path, &new_dir?, new_path)
735                .await?;
736            Ok(())
737        }
738    }
739
740    fn open_at(
741        &self,
742        fd: &Resource<Descriptor>,
743        path_flags: PathFlags,
744        path: String,
745        open_flags: OpenFlags,
746        flags: DescriptorFlags,
747    ) -> impl Future<Output = FilesystemResult<Descriptor>> + use<> {
748        let dir = get_dir(self.table, fd).cloned();
749        let allow_blocking_current_thread = self.ctx.allow_blocking_current_thread;
750        async move {
751            let fd = dir?
752                .open_at(
753                    path_flags.into(),
754                    path,
755                    open_flags.into(),
756                    flags.into(),
757                    allow_blocking_current_thread,
758                )
759                .await?;
760            Ok(fd)
761        }
762    }
763
764    fn readlink_at(
765        &self,
766        fd: &Resource<Descriptor>,
767        path: String,
768    ) -> impl Future<Output = FilesystemResult<String>> + use<> {
769        let dir = get_dir(self.table, fd).cloned();
770        async move { Ok(dir?.readlink_at(path).await?) }
771    }
772
773    fn remove_directory_at(
774        &self,
775        fd: &Resource<Descriptor>,
776        path: String,
777    ) -> impl Future<Output = FilesystemResult<()>> + use<> {
778        let dir = get_dir(self.table, fd).cloned();
779        async move {
780            dir?.remove_directory_at(path).await?;
781            Ok(())
782        }
783    }
784
785    fn rename_at(
786        &self,
787        fd: &Resource<Descriptor>,
788        old_path: String,
789        new_fd: &Resource<Descriptor>,
790        new_path: String,
791    ) -> impl Future<Output = FilesystemResult<()>> + use<> {
792        let old_dir = get_dir(self.table, fd).cloned();
793        let new_dir = get_dir(self.table, new_fd).cloned();
794        async move {
795            old_dir?.rename_at(old_path, &new_dir?, new_path).await?;
796            Ok(())
797        }
798    }
799
800    fn symlink_at(
801        &self,
802        fd: &Resource<Descriptor>,
803        old_path: String,
804        new_path: String,
805    ) -> impl Future<Output = FilesystemResult<()>> + use<> {
806        let dir = get_dir(self.table, fd).cloned();
807        async move {
808            dir?.symlink_at(old_path, new_path).await?;
809            Ok(())
810        }
811    }
812
813    fn unlink_file_at(
814        &self,
815        fd: &Resource<Descriptor>,
816        path: String,
817    ) -> impl Future<Output = FilesystemResult<()>> + use<> {
818        let dir = get_dir(self.table, fd).cloned();
819        async move {
820            dir?.unlink_file_at(path).await?;
821            Ok(())
822        }
823    }
824
825    fn is_same_object(
826        &self,
827        fd: &Resource<Descriptor>,
828        other: &Resource<Descriptor>,
829    ) -> impl Future<Output = wasmtime::Result<bool>> + use<> {
830        let fd = get_descriptor(self.table, fd).cloned();
831        let other = get_descriptor(self.table, other).cloned();
832        async move { fd?.is_same_object(&other?).await }
833    }
834
835    fn metadata_hash(
836        &self,
837        fd: &Resource<Descriptor>,
838    ) -> impl Future<Output = FilesystemResult<MetadataHashValue>> + use<> {
839        let fd = get_descriptor(self.table, fd).cloned();
840        async move {
841            let meta = fd?.metadata_hash().await?;
842            Ok(meta.into())
843        }
844    }
845
846    fn metadata_hash_at(
847        &self,
848        fd: &Resource<Descriptor>,
849        path_flags: PathFlags,
850        path: String,
851    ) -> impl Future<Output = FilesystemResult<MetadataHashValue>> + use<> {
852        let dir = get_dir(self.table, fd).cloned();
853        async move {
854            let meta = dir?.metadata_hash_at(path_flags.into(), path).await?;
855            Ok(meta.into())
856        }
857    }
858}
859
860impl<U> types::HostDescriptorWithStore<U> for WasiFilesystem {
861    fn read_via_stream(
862        mut store: Access<U, Self>,
863        fd: Resource<Descriptor>,
864        offset: Filesize,
865    ) -> wasmtime::Result<(StreamReader<u8>, FutureReader<Result<(), ErrorCode>>)> {
866        let fd = get_descriptor(store.get().table, &fd)?.clone();
867        read_via_stream(&mut store, fd, offset)
868    }
869
870    fn write_via_stream(
871        mut store: Access<'_, U, Self>,
872        fd: Resource<Descriptor>,
873        data: StreamReader<u8>,
874        offset: Filesize,
875    ) -> wasmtime::Result<FutureReader<Result<(), ErrorCode>>> {
876        let file = get_writable_file(store.get().table, &fd);
877        write_via_stream(&mut store, file, data, WriteLocation::Offset(offset))
878    }
879
880    fn append_via_stream(
881        mut store: Access<'_, U, Self>,
882        fd: Resource<Descriptor>,
883        data: StreamReader<u8>,
884    ) -> wasmtime::Result<FutureReader<Result<(), ErrorCode>>> {
885        let file = get_writable_file(store.get().table, &fd);
886        write_via_stream(&mut store, file, data, WriteLocation::End)
887    }
888
889    async fn advise(
890        store: &Accessor<U, Self>,
891        fd: Resource<Descriptor>,
892        offset: Filesize,
893        length: Filesize,
894        advice: Advice,
895    ) -> FilesystemResult<()> {
896        store
897            .with(|mut s| s.get().advise(&fd, offset, length, advice))
898            .await
899    }
900
901    async fn sync_data(
902        store: &Accessor<U, Self>,
903        fd: Resource<Descriptor>,
904    ) -> FilesystemResult<()> {
905        store.with(|mut s| s.get().sync_data(&fd)).await
906    }
907
908    async fn get_flags(
909        store: &Accessor<U, Self>,
910        fd: Resource<Descriptor>,
911    ) -> FilesystemResult<DescriptorFlags> {
912        store.with(|mut s| s.get().get_flags(&fd)).await
913    }
914
915    async fn get_type(
916        store: &Accessor<U, Self>,
917        fd: Resource<Descriptor>,
918    ) -> FilesystemResult<DescriptorType> {
919        store.with(|mut s| s.get().get_type(&fd)).await
920    }
921
922    async fn set_size(
923        store: &Accessor<U, Self>,
924        fd: Resource<Descriptor>,
925        size: Filesize,
926    ) -> FilesystemResult<()> {
927        store.with(|mut s| s.get().set_size(&fd, size)).await
928    }
929
930    async fn set_times(
931        store: &Accessor<U, Self>,
932        fd: Resource<Descriptor>,
933        data_access_timestamp: NewTimestamp,
934        data_modification_timestamp: NewTimestamp,
935    ) -> FilesystemResult<()> {
936        store
937            .with(|mut s| {
938                s.get()
939                    .set_times(&fd, data_access_timestamp, data_modification_timestamp)
940            })
941            .await
942    }
943
944    fn read_directory(
945        mut store: Access<'_, U, Self>,
946        fd: Resource<Descriptor>,
947    ) -> wasmtime::Result<(
948        StreamReader<DirectoryEntry>,
949        FutureReader<Result<(), ErrorCode>>,
950    )> {
951        let dir = get_dir(store.get().table, &fd).cloned();
952        read_directory(&mut store, dir)
953    }
954
955    async fn sync(store: &Accessor<U, Self>, fd: Resource<Descriptor>) -> FilesystemResult<()> {
956        store.with(|mut s| s.get().sync(&fd)).await
957    }
958
959    async fn create_directory_at(
960        store: &Accessor<U, Self>,
961        fd: Resource<Descriptor>,
962        path: String,
963    ) -> FilesystemResult<()> {
964        store
965            .with(|mut s| s.get().create_directory_at(&fd, path))
966            .await
967    }
968
969    async fn stat(
970        store: &Accessor<U, Self>,
971        fd: Resource<Descriptor>,
972    ) -> FilesystemResult<DescriptorStat> {
973        store.with(|mut s| s.get().stat(&fd)).await
974    }
975
976    async fn stat_at(
977        store: &Accessor<U, Self>,
978        fd: Resource<Descriptor>,
979        path_flags: PathFlags,
980        path: String,
981    ) -> FilesystemResult<DescriptorStat> {
982        store
983            .with(|mut s| s.get().stat_at(&fd, path_flags, path))
984            .await
985    }
986
987    async fn set_times_at(
988        store: &Accessor<U, Self>,
989        fd: Resource<Descriptor>,
990        path_flags: PathFlags,
991        path: String,
992        data_access_timestamp: NewTimestamp,
993        data_modification_timestamp: NewTimestamp,
994    ) -> FilesystemResult<()> {
995        store
996            .with(|mut s| {
997                s.get().set_times_at(
998                    &fd,
999                    path_flags,
1000                    path,
1001                    data_access_timestamp,
1002                    data_modification_timestamp,
1003                )
1004            })
1005            .await
1006    }
1007
1008    async fn link_at(
1009        store: &Accessor<U, Self>,
1010        fd: Resource<Descriptor>,
1011        old_path_flags: PathFlags,
1012        old_path: String,
1013        new_fd: Resource<Descriptor>,
1014        new_path: String,
1015    ) -> FilesystemResult<()> {
1016        store
1017            .with(|mut s| {
1018                s.get()
1019                    .link_at(&fd, old_path_flags, old_path, &new_fd, new_path)
1020            })
1021            .await
1022    }
1023
1024    async fn open_at(
1025        store: &Accessor<U, Self>,
1026        fd: Resource<Descriptor>,
1027        path_flags: PathFlags,
1028        path: String,
1029        open_flags: OpenFlags,
1030        flags: DescriptorFlags,
1031    ) -> FilesystemResult<Resource<Descriptor>> {
1032        let fd = store
1033            .with(|mut s| s.get().open_at(&fd, path_flags, path, open_flags, flags))
1034            .await?;
1035        let fd = store.with(|mut store| store.get().table.push(fd))?;
1036        Ok(fd)
1037    }
1038
1039    async fn readlink_at(
1040        store: &Accessor<U, Self>,
1041        fd: Resource<Descriptor>,
1042        path: String,
1043    ) -> FilesystemResult<String> {
1044        store.with(|mut s| s.get().readlink_at(&fd, path)).await
1045    }
1046
1047    async fn remove_directory_at(
1048        store: &Accessor<U, Self>,
1049        fd: Resource<Descriptor>,
1050        path: String,
1051    ) -> FilesystemResult<()> {
1052        store
1053            .with(|mut s| s.get().remove_directory_at(&fd, path))
1054            .await
1055    }
1056
1057    async fn rename_at(
1058        store: &Accessor<U, Self>,
1059        fd: Resource<Descriptor>,
1060        old_path: String,
1061        new_fd: Resource<Descriptor>,
1062        new_path: String,
1063    ) -> FilesystemResult<()> {
1064        store
1065            .with(|mut s| s.get().rename_at(&fd, old_path, &new_fd, new_path))
1066            .await
1067    }
1068
1069    async fn symlink_at(
1070        store: &Accessor<U, Self>,
1071        fd: Resource<Descriptor>,
1072        old_path: String,
1073        new_path: String,
1074    ) -> FilesystemResult<()> {
1075        store
1076            .with(|mut s| s.get().symlink_at(&fd, old_path, new_path))
1077            .await
1078    }
1079
1080    async fn unlink_file_at(
1081        store: &Accessor<U, Self>,
1082        fd: Resource<Descriptor>,
1083        path: String,
1084    ) -> FilesystemResult<()> {
1085        store.with(|mut s| s.get().unlink_file_at(&fd, path)).await
1086    }
1087
1088    async fn is_same_object(
1089        store: &Accessor<U, Self>,
1090        fd: Resource<Descriptor>,
1091        other: Resource<Descriptor>,
1092    ) -> wasmtime::Result<bool> {
1093        store
1094            .with(|mut s| s.get().is_same_object(&fd, &other))
1095            .await
1096    }
1097
1098    async fn metadata_hash(
1099        store: &Accessor<U, Self>,
1100        fd: Resource<Descriptor>,
1101    ) -> FilesystemResult<MetadataHashValue> {
1102        store.with(|mut s| s.get().metadata_hash(&fd)).await
1103    }
1104
1105    async fn metadata_hash_at(
1106        store: &Accessor<U, Self>,
1107        fd: Resource<Descriptor>,
1108        path_flags: PathFlags,
1109        path: String,
1110    ) -> FilesystemResult<MetadataHashValue> {
1111        store
1112            .with(|mut s| s.get().metadata_hash_at(&fd, path_flags, path))
1113            .await
1114    }
1115}
1116
1117impl types::HostDescriptor for WasiFilesystemCtxView<'_> {
1118    fn drop(&mut self, fd: Resource<Descriptor>) -> wasmtime::Result<()> {
1119        self.table
1120            .delete(fd)
1121            .context("failed to delete descriptor resource from table")?;
1122        Ok(())
1123    }
1124}
1125
1126impl preopens::Host for WasiFilesystemCtxView<'_> {
1127    fn get_directories(&mut self) -> wasmtime::Result<Vec<(Resource<Descriptor>, String)>> {
1128        self.get_directories()
1129    }
1130}
1131
1132mod named {
1133    use crate::filesystem::{Descriptor, WasiFilesystemNamed, WasiFilesystemNamedView};
1134    use crate::p3::bindings::filesystem::types::{
1135        Advice, DescriptorFlags, DescriptorStat, DescriptorType, DirectoryEntry, ErrorCode,
1136        Filesize, MetadataHashValue, NewTimestamp, OpenFlags, PathFlags,
1137    };
1138    use crate::p3::bindings::named_imports::wasi::filesystem::{preopens, types};
1139    use crate::p3::filesystem::{FilesystemError, FilesystemResult};
1140    use crate::{NamedId, WasiCtxNamedView};
1141    use wasmtime::component::{Access, Accessor, FutureReader, Resource, StreamReader};
1142
1143    impl<T> types::Host for WasiCtxNamedView<'_, T>
1144    where
1145        T: WasiFilesystemNamedView,
1146    {
1147        fn convert_error_code(&mut self, error: FilesystemError) -> wasmtime::Result<ErrorCode> {
1148            error.downcast()
1149        }
1150    }
1151
1152    impl<T, U> types::HostDescriptorWithStore<U> for WasiFilesystemNamed<T>
1153    where
1154        T: WasiFilesystemNamedView,
1155    {
1156        fn read_via_stream(
1157            mut store: Access<U, Self>,
1158            id: NamedId,
1159            fd: Resource<Descriptor>,
1160            offset: Filesize,
1161        ) -> wasmtime::Result<(StreamReader<u8>, FutureReader<Result<(), ErrorCode>>)> {
1162            let fd = super::get_descriptor(store.get().0.filesystem(id).table, &fd)?.clone();
1163            super::read_via_stream(&mut store, fd, offset)
1164        }
1165
1166        fn write_via_stream(
1167            mut store: Access<'_, U, Self>,
1168            id: NamedId,
1169            fd: Resource<Descriptor>,
1170            data: StreamReader<u8>,
1171            offset: Filesize,
1172        ) -> wasmtime::Result<FutureReader<Result<(), ErrorCode>>> {
1173            let file = super::get_writable_file(store.get().0.filesystem(id).table, &fd);
1174            super::write_via_stream(&mut store, file, data, super::WriteLocation::Offset(offset))
1175        }
1176
1177        fn append_via_stream(
1178            mut store: Access<'_, U, Self>,
1179            id: NamedId,
1180            fd: Resource<Descriptor>,
1181            data: StreamReader<u8>,
1182        ) -> wasmtime::Result<FutureReader<Result<(), ErrorCode>>> {
1183            let file = super::get_writable_file(store.get().0.filesystem(id).table, &fd);
1184            super::write_via_stream(&mut store, file, data, super::WriteLocation::End)
1185        }
1186
1187        async fn advise(
1188            store: &Accessor<U, Self>,
1189            id: NamedId,
1190            fd: Resource<Descriptor>,
1191            offset: Filesize,
1192            length: Filesize,
1193            advice: Advice,
1194        ) -> FilesystemResult<()> {
1195            let result = store.with(|mut s| {
1196                let ctx = s.get().0.filesystem(id);
1197                ctx.advise(&fd, offset, length, advice)
1198            });
1199            result.await
1200        }
1201
1202        async fn sync_data(
1203            store: &Accessor<U, Self>,
1204            id: NamedId,
1205            fd: Resource<Descriptor>,
1206        ) -> FilesystemResult<()> {
1207            let result = store.with(|mut s| {
1208                let ctx = s.get().0.filesystem(id);
1209                ctx.sync_data(&fd)
1210            });
1211            result.await
1212        }
1213
1214        async fn get_flags(
1215            store: &Accessor<U, Self>,
1216            id: NamedId,
1217            fd: Resource<Descriptor>,
1218        ) -> FilesystemResult<DescriptorFlags> {
1219            let result = store.with(|mut s| {
1220                let ctx = s.get().0.filesystem(id);
1221                ctx.get_flags(&fd)
1222            });
1223            result.await
1224        }
1225
1226        async fn get_type(
1227            store: &Accessor<U, Self>,
1228            id: NamedId,
1229            fd: Resource<Descriptor>,
1230        ) -> FilesystemResult<DescriptorType> {
1231            let result = store.with(|mut s| {
1232                let ctx = s.get().0.filesystem(id);
1233                ctx.get_type(&fd)
1234            });
1235            result.await
1236        }
1237
1238        async fn set_size(
1239            store: &Accessor<U, Self>,
1240            id: NamedId,
1241            fd: Resource<Descriptor>,
1242            size: Filesize,
1243        ) -> FilesystemResult<()> {
1244            let result = store.with(|mut s| {
1245                let ctx = s.get().0.filesystem(id);
1246                ctx.set_size(&fd, size)
1247            });
1248            result.await
1249        }
1250
1251        async fn set_times(
1252            store: &Accessor<U, Self>,
1253            id: NamedId,
1254            fd: Resource<Descriptor>,
1255            data_access_timestamp: NewTimestamp,
1256            data_modification_timestamp: NewTimestamp,
1257        ) -> FilesystemResult<()> {
1258            let result = store.with(|mut s| {
1259                let ctx = s.get().0.filesystem(id);
1260                ctx.set_times(&fd, data_access_timestamp, data_modification_timestamp)
1261            });
1262            result.await
1263        }
1264
1265        fn read_directory(
1266            mut store: Access<'_, U, Self>,
1267            id: NamedId,
1268            fd: Resource<Descriptor>,
1269        ) -> wasmtime::Result<(
1270            StreamReader<DirectoryEntry>,
1271            FutureReader<Result<(), ErrorCode>>,
1272        )> {
1273            let dir = super::get_dir(store.get().0.filesystem(id).table, &fd).cloned();
1274            super::read_directory(&mut store, dir)
1275        }
1276
1277        async fn sync(
1278            store: &Accessor<U, Self>,
1279            id: NamedId,
1280            fd: Resource<Descriptor>,
1281        ) -> FilesystemResult<()> {
1282            let result = store.with(|mut s| {
1283                let ctx = s.get().0.filesystem(id);
1284                ctx.sync(&fd)
1285            });
1286            result.await
1287        }
1288
1289        async fn create_directory_at(
1290            store: &Accessor<U, Self>,
1291            id: NamedId,
1292            fd: Resource<Descriptor>,
1293            path: String,
1294        ) -> FilesystemResult<()> {
1295            let result = store.with(|mut s| {
1296                let ctx = s.get().0.filesystem(id);
1297                ctx.create_directory_at(&fd, path)
1298            });
1299            result.await
1300        }
1301
1302        async fn stat(
1303            store: &Accessor<U, Self>,
1304            id: NamedId,
1305            fd: Resource<Descriptor>,
1306        ) -> FilesystemResult<DescriptorStat> {
1307            let result = store.with(|mut s| {
1308                let ctx = s.get().0.filesystem(id);
1309                ctx.stat(&fd)
1310            });
1311            result.await
1312        }
1313
1314        async fn stat_at(
1315            store: &Accessor<U, Self>,
1316            id: NamedId,
1317            fd: Resource<Descriptor>,
1318            path_flags: PathFlags,
1319            path: String,
1320        ) -> FilesystemResult<DescriptorStat> {
1321            let result = store.with(|mut s| {
1322                let ctx = s.get().0.filesystem(id);
1323                ctx.stat_at(&fd, path_flags, path)
1324            });
1325            result.await
1326        }
1327
1328        async fn set_times_at(
1329            store: &Accessor<U, Self>,
1330            id: NamedId,
1331            fd: Resource<Descriptor>,
1332            path_flags: PathFlags,
1333            path: String,
1334            data_access_timestamp: NewTimestamp,
1335            data_modification_timestamp: NewTimestamp,
1336        ) -> FilesystemResult<()> {
1337            let result = store.with(|mut s| {
1338                let ctx = s.get().0.filesystem(id);
1339                ctx.set_times_at(
1340                    &fd,
1341                    path_flags,
1342                    path,
1343                    data_access_timestamp,
1344                    data_modification_timestamp,
1345                )
1346            });
1347            result.await
1348        }
1349
1350        async fn link_at(
1351            store: &Accessor<U, Self>,
1352            id: NamedId,
1353            fd: Resource<Descriptor>,
1354            old_path_flags: PathFlags,
1355            old_path: String,
1356            new_fd: Resource<Descriptor>,
1357            new_path: String,
1358        ) -> FilesystemResult<()> {
1359            let result = store.with(|mut s| {
1360                let ctx = s.get().0.filesystem(id);
1361                ctx.link_at(&fd, old_path_flags, old_path, &new_fd, new_path)
1362            });
1363            result.await
1364        }
1365
1366        async fn open_at(
1367            store: &Accessor<U, Self>,
1368            id: NamedId,
1369            fd: Resource<Descriptor>,
1370            path_flags: PathFlags,
1371            path: String,
1372            open_flags: OpenFlags,
1373            flags: DescriptorFlags,
1374        ) -> FilesystemResult<Resource<Descriptor>> {
1375            let result = store.with(|mut s| {
1376                let ctx = s.get().0.filesystem(id);
1377                ctx.open_at(&fd, path_flags, path, open_flags, flags)
1378            });
1379            let fd = result.await?;
1380            let fd = store.with(|mut store| store.get().0.filesystem(id).table.push(fd))?;
1381            Ok(fd)
1382        }
1383
1384        async fn readlink_at(
1385            store: &Accessor<U, Self>,
1386            id: NamedId,
1387            fd: Resource<Descriptor>,
1388            path: String,
1389        ) -> FilesystemResult<String> {
1390            let result = store.with(|mut s| {
1391                let ctx = s.get().0.filesystem(id);
1392                ctx.readlink_at(&fd, path)
1393            });
1394            result.await
1395        }
1396
1397        async fn remove_directory_at(
1398            store: &Accessor<U, Self>,
1399            id: NamedId,
1400            fd: Resource<Descriptor>,
1401            path: String,
1402        ) -> FilesystemResult<()> {
1403            let result = store.with(|mut s| {
1404                let ctx = s.get().0.filesystem(id);
1405                ctx.remove_directory_at(&fd, path)
1406            });
1407            result.await
1408        }
1409
1410        async fn rename_at(
1411            store: &Accessor<U, Self>,
1412            id: NamedId,
1413            fd: Resource<Descriptor>,
1414            old_path: String,
1415            new_fd: Resource<Descriptor>,
1416            new_path: String,
1417        ) -> FilesystemResult<()> {
1418            let result = store.with(|mut s| {
1419                let ctx = s.get().0.filesystem(id);
1420                ctx.rename_at(&fd, old_path, &new_fd, new_path)
1421            });
1422            result.await
1423        }
1424
1425        async fn symlink_at(
1426            store: &Accessor<U, Self>,
1427            id: NamedId,
1428            fd: Resource<Descriptor>,
1429            old_path: String,
1430            new_path: String,
1431        ) -> FilesystemResult<()> {
1432            let result = store.with(|mut s| {
1433                let ctx = s.get().0.filesystem(id);
1434                ctx.symlink_at(&fd, old_path, new_path)
1435            });
1436            result.await
1437        }
1438
1439        async fn unlink_file_at(
1440            store: &Accessor<U, Self>,
1441            id: NamedId,
1442            fd: Resource<Descriptor>,
1443            path: String,
1444        ) -> FilesystemResult<()> {
1445            let result = store.with(|mut s| {
1446                let ctx = s.get().0.filesystem(id);
1447                ctx.unlink_file_at(&fd, path)
1448            });
1449            result.await
1450        }
1451
1452        async fn is_same_object(
1453            store: &Accessor<U, Self>,
1454            id: NamedId,
1455            fd: Resource<Descriptor>,
1456            other: Resource<Descriptor>,
1457        ) -> wasmtime::Result<bool> {
1458            let result = store.with(|mut s| {
1459                let ctx = s.get().0.filesystem(id);
1460                ctx.is_same_object(&fd, &other)
1461            });
1462            result.await
1463        }
1464
1465        async fn metadata_hash(
1466            store: &Accessor<U, Self>,
1467            id: NamedId,
1468            fd: Resource<Descriptor>,
1469        ) -> FilesystemResult<MetadataHashValue> {
1470            let result = store.with(|mut s| {
1471                let ctx = s.get().0.filesystem(id);
1472                ctx.metadata_hash(&fd)
1473            });
1474            result.await
1475        }
1476
1477        async fn metadata_hash_at(
1478            store: &Accessor<U, Self>,
1479            id: NamedId,
1480            fd: Resource<Descriptor>,
1481            path_flags: PathFlags,
1482            path: String,
1483        ) -> FilesystemResult<MetadataHashValue> {
1484            let result = store.with(|mut s| {
1485                let ctx = s.get().0.filesystem(id);
1486                ctx.metadata_hash_at(&fd, path_flags, path)
1487            });
1488            result.await
1489        }
1490    }
1491
1492    impl<T> types::HostDescriptor for WasiCtxNamedView<'_, T>
1493    where
1494        T: WasiFilesystemNamedView,
1495    {
1496        fn drop(&mut self, id: NamedId, fd: Resource<Descriptor>) -> wasmtime::Result<()> {
1497            super::types::HostDescriptor::drop(&mut self.0.filesystem(id), fd)
1498        }
1499    }
1500
1501    impl<T> preopens::Host for WasiCtxNamedView<'_, T>
1502    where
1503        T: WasiFilesystemNamedView,
1504    {
1505        fn get_directories(
1506            &mut self,
1507            id: NamedId,
1508        ) -> wasmtime::Result<Vec<(Resource<Descriptor>, String)>> {
1509            super::preopens::Host::get_directories(&mut self.0.filesystem(id))
1510        }
1511    }
1512}