Skip to main content

wasmtime_wasi/p2/host/
filesystem.rs

1use crate::filesystem::sys;
2use crate::filesystem::{Descriptor, WasiFilesystemCtxView};
3use crate::p2::bindings::clocks::wall_clock;
4use crate::p2::bindings::filesystem::preopens;
5use crate::p2::bindings::filesystem::types::{
6    self, ErrorCode, HostDescriptor, HostDirectoryEntryStream,
7};
8use crate::p2::filesystem::{FileInputStream, FileOutputStream, ReaddirIterator};
9use crate::p2::{FsError, FsResult};
10use std::time::SystemTime;
11use wasmtime::component::Resource;
12use wasmtime_wasi_io::streams::{DynInputStream, DynOutputStream};
13
14mod sync;
15
16impl preopens::Host for WasiFilesystemCtxView<'_> {
17    fn get_directories(&mut self) -> wasmtime::Result<Vec<(Resource<Descriptor>, String)>> {
18        self.get_directories()
19    }
20}
21
22impl types::Host for WasiFilesystemCtxView<'_> {
23    fn convert_error_code(&mut self, err: FsError) -> wasmtime::Result<ErrorCode> {
24        err.downcast()
25    }
26
27    fn filesystem_error_code(
28        &mut self,
29        err: Resource<wasmtime::Error>,
30    ) -> wasmtime::Result<Option<ErrorCode>> {
31        let err = self.table.get(&err)?;
32
33        // Currently `err` always comes from the stream implementation which
34        // uses standard reads/writes so only check for `std::io::Error` here.
35        if let Some(err) = err.downcast_ref::<std::io::Error>() {
36            return Ok(Some(ErrorCode::from(err)));
37        }
38
39        Ok(None)
40    }
41}
42
43impl HostDescriptor for WasiFilesystemCtxView<'_> {
44    async fn advise(
45        &mut self,
46        fd: Resource<types::Descriptor>,
47        offset: types::Filesize,
48        len: types::Filesize,
49        advice: types::Advice,
50    ) -> FsResult<()> {
51        let f = self.table.get(&fd)?.file()?;
52        f.advise(offset, len, advice.into()).await?;
53        Ok(())
54    }
55
56    async fn sync_data(&mut self, fd: Resource<types::Descriptor>) -> FsResult<()> {
57        let descriptor = self.table.get(&fd)?;
58        descriptor.sync_data().await?;
59        Ok(())
60    }
61
62    async fn get_flags(
63        &mut self,
64        fd: Resource<types::Descriptor>,
65    ) -> FsResult<types::DescriptorFlags> {
66        let descriptor = self.table.get(&fd)?;
67        let flags = descriptor.get_flags().await?;
68        Ok(flags.into())
69    }
70
71    async fn get_type(
72        &mut self,
73        fd: Resource<types::Descriptor>,
74    ) -> FsResult<types::DescriptorType> {
75        let descriptor = self.table.get(&fd)?;
76        let ty = descriptor.get_type().await?;
77        Ok(ty.into())
78    }
79
80    async fn set_size(
81        &mut self,
82        fd: Resource<types::Descriptor>,
83        size: types::Filesize,
84    ) -> FsResult<()> {
85        let f = self.table.get(&fd)?.file()?;
86        f.set_size(size).await?;
87        Ok(())
88    }
89
90    async fn set_times(
91        &mut self,
92        fd: Resource<types::Descriptor>,
93        atim: types::NewTimestamp,
94        mtim: types::NewTimestamp,
95    ) -> FsResult<()> {
96        let descriptor = self.table.get(&fd)?;
97        let atim = systemtimespec_from(atim)?;
98        let mtim = systemtimespec_from(mtim)?;
99        descriptor.set_times(atim, mtim).await?;
100        Ok(())
101    }
102
103    async fn read(
104        &mut self,
105        fd: Resource<types::Descriptor>,
106        len: types::Filesize,
107        offset: types::Filesize,
108    ) -> FsResult<(Vec<u8>, bool)> {
109        let f = self.table.get(&fd)?.file()?;
110
111        let (mut buffer, r) = f
112            .run_blocking(move |f| {
113                let mut buffer = vec![
114                    0;
115                    len.try_into()
116                        .unwrap_or(usize::MAX)
117                        .min(crate::MAX_READ_SIZE_ALLOC)
118                ];
119                let r = sys::read_at_cursor_unspecified(f, &mut buffer, offset);
120                (buffer, r)
121            })
122            .await;
123
124        let (bytes_read, state) = match r? {
125            0 => (0, true),
126            n => (n, false),
127        };
128
129        buffer.truncate(bytes_read);
130
131        Ok((buffer, state))
132    }
133
134    async fn write(
135        &mut self,
136        fd: Resource<types::Descriptor>,
137        buf: Vec<u8>,
138        offset: types::Filesize,
139    ) -> FsResult<types::Filesize> {
140        let f = self.table.get(&fd)?.file()?;
141        if f.perms.write_not_permitted() {
142            return Err(ErrorCode::NotPermitted.into());
143        }
144
145        let bytes_written = f
146            .run_blocking(move |f| sys::write_at_cursor_unspecified(f, &buf, offset))
147            .await?;
148
149        Ok(types::Filesize::try_from(bytes_written).expect("usize fits in Filesize"))
150    }
151
152    async fn read_directory(
153        &mut self,
154        fd: Resource<types::Descriptor>,
155    ) -> FsResult<Resource<types::DirectoryEntryStream>> {
156        let d = self.table.get(&fd)?.dir()?;
157
158        enum ReaddirError {
159            Io(std::io::Error),
160            IllegalSequence,
161        }
162        impl From<std::io::Error> for ReaddirError {
163            fn from(e: std::io::Error) -> ReaddirError {
164                ReaddirError::Io(e)
165            }
166        }
167
168        let entries = d
169            .run_blocking(|d| {
170                // Both `entries` and `metadata` perform syscalls, which is why they are done
171                // within this `block` call, rather than delay calculating the metadata
172                // for entries when they're demanded later in the iterator chain.
173                Ok::<_, std::io::Error>(
174                    crate::filesystem::primitives::read_dir(d)?
175                        .map(|entry| {
176                            let (filename, ty) = entry?;
177                            let type_ = descriptortype_from(ty);
178                            let name = filename
179                                .into_string()
180                                .map_err(|_| ReaddirError::IllegalSequence)?;
181                            Ok(types::DirectoryEntry { type_, name })
182                        })
183                        .collect::<Vec<Result<types::DirectoryEntry, ReaddirError>>>(),
184                )
185            })
186            .await?
187            .into_iter();
188
189        // On windows, filter out files like `C:\DumpStack.log.tmp` which we
190        // can't get full metadata for.
191        #[cfg(windows)]
192        let entries = entries.filter(|entry| {
193            use windows_sys::Win32::Foundation::{ERROR_ACCESS_DENIED, ERROR_SHARING_VIOLATION};
194            if let Err(ReaddirError::Io(err)) = entry {
195                if err.raw_os_error() == Some(ERROR_SHARING_VIOLATION as i32)
196                    || err.raw_os_error() == Some(ERROR_ACCESS_DENIED as i32)
197                {
198                    return false;
199                }
200            }
201            true
202        });
203        let entries = entries.map(|r| match r {
204            Ok(r) => Ok(r),
205            Err(ReaddirError::Io(e)) => Err(e.into()),
206            Err(ReaddirError::IllegalSequence) => Err(ErrorCode::IllegalByteSequence.into()),
207        });
208        Ok(self.table.push(ReaddirIterator::new(entries))?)
209    }
210
211    async fn sync(&mut self, fd: Resource<types::Descriptor>) -> FsResult<()> {
212        let descriptor = self.table.get(&fd)?;
213        descriptor.sync().await?;
214        Ok(())
215    }
216
217    async fn create_directory_at(
218        &mut self,
219        fd: Resource<types::Descriptor>,
220        path: String,
221    ) -> FsResult<()> {
222        let d = self.table.get(&fd)?.dir()?;
223        d.create_directory_at(path).await?;
224        Ok(())
225    }
226
227    async fn stat(&mut self, fd: Resource<types::Descriptor>) -> FsResult<types::DescriptorStat> {
228        let descriptor = self.table.get(&fd)?;
229        let stat = descriptor.stat().await?;
230        Ok(stat.try_into()?)
231    }
232
233    async fn stat_at(
234        &mut self,
235        fd: Resource<types::Descriptor>,
236        path_flags: types::PathFlags,
237        path: String,
238    ) -> FsResult<types::DescriptorStat> {
239        let d = self.table.get(&fd)?.dir()?;
240        let stat = d.stat_at(path_flags.into(), path).await?;
241        Ok(stat.try_into()?)
242    }
243
244    async fn set_times_at(
245        &mut self,
246        fd: Resource<types::Descriptor>,
247        path_flags: types::PathFlags,
248        path: String,
249        atim: types::NewTimestamp,
250        mtim: types::NewTimestamp,
251    ) -> FsResult<()> {
252        let d = self.table.get(&fd)?.dir()?;
253        let atim = systemtimespec_from(atim)?;
254        let mtim = systemtimespec_from(mtim)?;
255        d.set_times_at(path_flags.into(), path, atim, mtim).await?;
256        Ok(())
257    }
258
259    async fn link_at(
260        &mut self,
261        fd: Resource<types::Descriptor>,
262        old_path_flags: types::PathFlags,
263        old_path: String,
264        new_descriptor: Resource<types::Descriptor>,
265        new_path: String,
266    ) -> FsResult<()> {
267        let old_dir = self.table.get(&fd)?.dir()?;
268        let new_dir = self.table.get(&new_descriptor)?.dir()?;
269        old_dir
270            .link_at(old_path_flags.into(), old_path, new_dir, new_path)
271            .await?;
272        Ok(())
273    }
274
275    async fn open_at(
276        &mut self,
277        fd: Resource<types::Descriptor>,
278        path_flags: types::PathFlags,
279        path: String,
280        oflags: types::OpenFlags,
281        flags: types::DescriptorFlags,
282    ) -> FsResult<Resource<types::Descriptor>> {
283        let d = self.table.get(&fd)?.dir()?;
284        let fd = d
285            .open_at(
286                path_flags.into(),
287                path,
288                oflags.into(),
289                flags.into(),
290                self.ctx.allow_blocking_current_thread,
291            )
292            .await?;
293        let fd = self.table.push(fd)?;
294        Ok(fd)
295    }
296
297    fn drop(&mut self, fd: Resource<types::Descriptor>) -> wasmtime::Result<()> {
298        // The Drop will close the file/dir, but if the close syscall
299        // blocks the thread, I will face god and walk backwards into hell.
300        // tokio::fs::File just uses std::fs::File's Drop impl to close, so
301        // it doesn't appear anyone else has found this to be a problem.
302        // (Not that they could solve it without async drop...)
303        self.table.delete(fd)?;
304
305        Ok(())
306    }
307
308    async fn readlink_at(
309        &mut self,
310        fd: Resource<types::Descriptor>,
311        path: String,
312    ) -> FsResult<String> {
313        let d = self.table.get(&fd)?.dir()?;
314        let path = d.readlink_at(path).await?;
315        Ok(path)
316    }
317
318    async fn remove_directory_at(
319        &mut self,
320        fd: Resource<types::Descriptor>,
321        path: String,
322    ) -> FsResult<()> {
323        let d = self.table.get(&fd)?.dir()?;
324        d.remove_directory_at(path).await?;
325        Ok(())
326    }
327
328    async fn rename_at(
329        &mut self,
330        fd: Resource<types::Descriptor>,
331        old_path: String,
332        new_fd: Resource<types::Descriptor>,
333        new_path: String,
334    ) -> FsResult<()> {
335        let old_dir = self.table.get(&fd)?.dir()?;
336        let new_dir = self.table.get(&new_fd)?.dir()?;
337        old_dir.rename_at(old_path, new_dir, new_path).await?;
338        Ok(())
339    }
340
341    async fn symlink_at(
342        &mut self,
343        fd: Resource<types::Descriptor>,
344        src_path: String,
345        dest_path: String,
346    ) -> FsResult<()> {
347        let d = self.table.get(&fd)?.dir()?;
348        d.symlink_at(src_path, dest_path).await?;
349        Ok(())
350    }
351
352    async fn unlink_file_at(
353        &mut self,
354        fd: Resource<types::Descriptor>,
355        path: String,
356    ) -> FsResult<()> {
357        let d = self.table.get(&fd)?.dir()?;
358        d.unlink_file_at(path).await?;
359        Ok(())
360    }
361
362    fn read_via_stream(
363        &mut self,
364        fd: Resource<types::Descriptor>,
365        offset: types::Filesize,
366    ) -> FsResult<Resource<DynInputStream>> {
367        // Trap if fd lookup fails. A directory is is-directory, not
368        // bad-descriptor (POSIX EISDIR on read).
369        let f = match self.table.get(&fd)? {
370            Descriptor::File(f) => f,
371            Descriptor::Dir(_) => return Err(ErrorCode::IsDirectory.into()),
372        };
373
374        // Create a stream view for it.
375        let reader: DynInputStream = Box::new(FileInputStream::new(f, offset));
376
377        // Insert the stream view into the table. Trap if the table is full.
378        let index = self.table.push(reader)?;
379
380        Ok(index)
381    }
382
383    fn write_via_stream(
384        &mut self,
385        fd: Resource<types::Descriptor>,
386        offset: types::Filesize,
387    ) -> FsResult<Resource<DynOutputStream>> {
388        // Trap if fd lookup fails:
389        let f = self.table.get(&fd)?.file()?;
390
391        if f.perms.write_not_permitted() {
392            Err(types::ErrorCode::NotPermitted)?;
393        }
394
395        // Create a stream view for it.
396        let writer = FileOutputStream::write_at(f, offset);
397        let writer: DynOutputStream = Box::new(writer);
398
399        // Insert the stream view into the table. Trap if the table is full.
400        let index = self.table.push(writer)?;
401
402        Ok(index)
403    }
404
405    fn append_via_stream(
406        &mut self,
407        fd: Resource<types::Descriptor>,
408    ) -> FsResult<Resource<DynOutputStream>> {
409        // Trap if fd lookup fails:
410        let f = self.table.get(&fd)?.file()?;
411
412        if f.perms.write_not_permitted() {
413            Err(types::ErrorCode::NotPermitted)?;
414        }
415
416        // Create a stream view for it.
417        let appender = FileOutputStream::append(f);
418        let appender: DynOutputStream = Box::new(appender);
419
420        // Insert the stream view into the table. Trap if the table is full.
421        let index = self.table.push(appender)?;
422
423        Ok(index)
424    }
425
426    async fn is_same_object(
427        &mut self,
428        a: Resource<types::Descriptor>,
429        b: Resource<types::Descriptor>,
430    ) -> wasmtime::Result<bool> {
431        let descriptor_a = self.table.get(&a)?;
432        let descriptor_b = self.table.get(&b)?;
433        descriptor_a.is_same_object(descriptor_b).await
434    }
435    async fn metadata_hash(
436        &mut self,
437        fd: Resource<types::Descriptor>,
438    ) -> FsResult<types::MetadataHashValue> {
439        let fd = self.table.get(&fd)?;
440        let meta = fd.metadata_hash().await?;
441        Ok(meta.into())
442    }
443    async fn metadata_hash_at(
444        &mut self,
445        fd: Resource<types::Descriptor>,
446        path_flags: types::PathFlags,
447        path: String,
448    ) -> FsResult<types::MetadataHashValue> {
449        let d = self.table.get(&fd)?.dir()?;
450        let meta = d.metadata_hash_at(path_flags.into(), path).await?;
451        Ok(meta.into())
452    }
453}
454
455impl HostDirectoryEntryStream for WasiFilesystemCtxView<'_> {
456    async fn read_directory_entry(
457        &mut self,
458        stream: Resource<types::DirectoryEntryStream>,
459    ) -> FsResult<Option<types::DirectoryEntry>> {
460        let readdir = self.table.get(&stream)?;
461        readdir.next()
462    }
463
464    fn drop(&mut self, stream: Resource<types::DirectoryEntryStream>) -> wasmtime::Result<()> {
465        self.table.delete(stream)?;
466        Ok(())
467    }
468}
469
470impl From<types::Advice> for crate::filesystem::Advice {
471    fn from(advice: types::Advice) -> Self {
472        match advice {
473            types::Advice::Normal => Self::Normal,
474            types::Advice::Sequential => Self::Sequential,
475            types::Advice::Random => Self::Random,
476            types::Advice::WillNeed => Self::WillNeed,
477            types::Advice::DontNeed => Self::DontNeed,
478            types::Advice::NoReuse => Self::NoReuse,
479        }
480    }
481}
482
483impl From<types::OpenFlags> for crate::filesystem::OpenFlags {
484    fn from(flags: types::OpenFlags) -> Self {
485        let mut out = Self::empty();
486        if flags.contains(types::OpenFlags::CREATE) {
487            out |= Self::CREATE;
488        }
489        if flags.contains(types::OpenFlags::DIRECTORY) {
490            out |= Self::DIRECTORY;
491        }
492        if flags.contains(types::OpenFlags::EXCLUSIVE) {
493            out |= Self::EXCLUSIVE;
494        }
495        if flags.contains(types::OpenFlags::TRUNCATE) {
496            out |= Self::TRUNCATE;
497        }
498        out
499    }
500}
501
502impl From<types::PathFlags> for crate::filesystem::PathFlags {
503    fn from(flags: types::PathFlags) -> Self {
504        let mut out = Self::empty();
505        if flags.contains(types::PathFlags::SYMLINK_FOLLOW) {
506            out |= Self::SYMLINK_FOLLOW;
507        }
508        out
509    }
510}
511
512impl From<crate::filesystem::DescriptorFlags> for types::DescriptorFlags {
513    fn from(flags: crate::filesystem::DescriptorFlags) -> Self {
514        let mut out = Self::empty();
515        if flags.contains(crate::filesystem::DescriptorFlags::READ) {
516            out |= Self::READ;
517        }
518        if flags.contains(crate::filesystem::DescriptorFlags::WRITE) {
519            out |= Self::WRITE;
520        }
521        if flags.contains(crate::filesystem::DescriptorFlags::FILE_INTEGRITY_SYNC) {
522            out |= Self::FILE_INTEGRITY_SYNC;
523        }
524        if flags.contains(crate::filesystem::DescriptorFlags::DATA_INTEGRITY_SYNC) {
525            out |= Self::DATA_INTEGRITY_SYNC;
526        }
527        if flags.contains(crate::filesystem::DescriptorFlags::REQUESTED_WRITE_SYNC) {
528            out |= Self::REQUESTED_WRITE_SYNC;
529        }
530        if flags.contains(crate::filesystem::DescriptorFlags::MUTATE_DIRECTORY) {
531            out |= Self::MUTATE_DIRECTORY;
532        }
533        out
534    }
535}
536
537impl From<types::DescriptorFlags> for crate::filesystem::DescriptorFlags {
538    fn from(flags: types::DescriptorFlags) -> Self {
539        let mut out = Self::empty();
540        if flags.contains(types::DescriptorFlags::READ) {
541            out |= Self::READ;
542        }
543        if flags.contains(types::DescriptorFlags::WRITE) {
544            out |= Self::WRITE;
545        }
546        if flags.contains(types::DescriptorFlags::FILE_INTEGRITY_SYNC) {
547            out |= Self::FILE_INTEGRITY_SYNC;
548        }
549        if flags.contains(types::DescriptorFlags::DATA_INTEGRITY_SYNC) {
550            out |= Self::DATA_INTEGRITY_SYNC;
551        }
552        if flags.contains(types::DescriptorFlags::REQUESTED_WRITE_SYNC) {
553            out |= Self::REQUESTED_WRITE_SYNC;
554        }
555        if flags.contains(types::DescriptorFlags::MUTATE_DIRECTORY) {
556            out |= Self::MUTATE_DIRECTORY;
557        }
558        out
559    }
560}
561
562impl From<crate::filesystem::MetadataHashValue> for types::MetadataHashValue {
563    fn from(
564        crate::filesystem::MetadataHashValue { lower, upper }: crate::filesystem::MetadataHashValue,
565    ) -> Self {
566        Self { lower, upper }
567    }
568}
569
570impl TryFrom<crate::filesystem::DescriptorStat> for types::DescriptorStat {
571    type Error = ErrorCode;
572
573    fn try_from(
574        crate::filesystem::DescriptorStat {
575            type_,
576            link_count,
577            size,
578            data_access_timestamp,
579            data_modification_timestamp,
580            status_change_timestamp,
581        }: crate::filesystem::DescriptorStat,
582    ) -> Result<Self, ErrorCode> {
583        // Internal timestamps use i64 seconds; wasi:clocks/wall-clock uses u64
584        // (non-negative). Times outside that range become missing rather than
585        // failing the whole stat (e.g. host-clamped far-past mtimes on macOS).
586        Ok(Self {
587            type_: type_.into(),
588            link_count,
589            size,
590            data_access_timestamp: data_access_timestamp.and_then(|t| t.try_into().ok()),
591            data_modification_timestamp: data_modification_timestamp
592                .and_then(|t| t.try_into().ok()),
593            status_change_timestamp: status_change_timestamp.and_then(|t| t.try_into().ok()),
594        })
595    }
596}
597
598impl From<crate::filesystem::DescriptorType> for types::DescriptorType {
599    fn from(ty: crate::filesystem::DescriptorType) -> Self {
600        match ty {
601            crate::filesystem::DescriptorType::Unknown => Self::Unknown,
602            crate::filesystem::DescriptorType::BlockDevice => Self::BlockDevice,
603            crate::filesystem::DescriptorType::CharacterDevice => Self::CharacterDevice,
604            crate::filesystem::DescriptorType::Directory => Self::Directory,
605            crate::filesystem::DescriptorType::SymbolicLink => Self::SymbolicLink,
606            crate::filesystem::DescriptorType::RegularFile => Self::RegularFile,
607        }
608    }
609}
610
611#[cfg(unix)]
612fn from_raw_os_error(err: Option<i32>) -> Option<ErrorCode> {
613    use rustix::io::Errno as RustixErrno;
614    if err.is_none() {
615        return None;
616    }
617    Some(match RustixErrno::from_raw_os_error(err.unwrap()) {
618        RustixErrno::PIPE => ErrorCode::Pipe,
619        RustixErrno::PERM => ErrorCode::NotPermitted,
620        RustixErrno::NOENT => ErrorCode::NoEntry,
621        RustixErrno::NOMEM => ErrorCode::InsufficientMemory,
622        RustixErrno::IO => ErrorCode::Io,
623        RustixErrno::BADF => ErrorCode::BadDescriptor,
624        RustixErrno::BUSY => ErrorCode::Busy,
625        RustixErrno::ACCESS => ErrorCode::Access,
626        RustixErrno::NOTDIR => ErrorCode::NotDirectory,
627        RustixErrno::ISDIR => ErrorCode::IsDirectory,
628        RustixErrno::INVAL => ErrorCode::Invalid,
629        RustixErrno::EXIST => ErrorCode::Exist,
630        RustixErrno::FBIG => ErrorCode::FileTooLarge,
631        RustixErrno::NOSPC => ErrorCode::InsufficientSpace,
632        RustixErrno::SPIPE => ErrorCode::InvalidSeek,
633        RustixErrno::MLINK => ErrorCode::TooManyLinks,
634        RustixErrno::NAMETOOLONG => ErrorCode::NameTooLong,
635        RustixErrno::NOTEMPTY => ErrorCode::NotEmpty,
636        RustixErrno::LOOP => ErrorCode::Loop,
637        RustixErrno::OVERFLOW => ErrorCode::Overflow,
638        RustixErrno::ILSEQ => ErrorCode::IllegalByteSequence,
639        RustixErrno::NOTSUP => ErrorCode::Unsupported,
640        RustixErrno::ALREADY => ErrorCode::Already,
641        RustixErrno::INPROGRESS => ErrorCode::InProgress,
642        RustixErrno::INTR => ErrorCode::Interrupted,
643
644        #[allow(
645            unreachable_patterns,
646            reason = "on some platforms, these have the same value as other errno values"
647        )]
648        RustixErrno::OPNOTSUPP => ErrorCode::Unsupported,
649
650        _ => return None,
651    })
652}
653#[cfg(windows)]
654fn from_raw_os_error(raw_os_error: Option<i32>) -> Option<ErrorCode> {
655    use windows_sys::Win32::Foundation;
656    Some(match raw_os_error.map(|code| code as u32) {
657        Some(Foundation::ERROR_FILE_NOT_FOUND) => ErrorCode::NoEntry,
658        Some(Foundation::ERROR_PATH_NOT_FOUND) => ErrorCode::NoEntry,
659        Some(Foundation::ERROR_ACCESS_DENIED) => ErrorCode::Access,
660        Some(Foundation::ERROR_SHARING_VIOLATION) => ErrorCode::Access,
661        Some(Foundation::ERROR_PRIVILEGE_NOT_HELD) => ErrorCode::NotPermitted,
662        Some(Foundation::ERROR_INVALID_HANDLE) => ErrorCode::BadDescriptor,
663        Some(Foundation::ERROR_INVALID_NAME) => ErrorCode::NoEntry,
664        Some(Foundation::ERROR_NOT_ENOUGH_MEMORY) => ErrorCode::InsufficientMemory,
665        Some(Foundation::ERROR_OUTOFMEMORY) => ErrorCode::InsufficientMemory,
666        Some(Foundation::ERROR_DIR_NOT_EMPTY) => ErrorCode::NotEmpty,
667        Some(Foundation::ERROR_NOT_READY) => ErrorCode::Busy,
668        Some(Foundation::ERROR_BUSY) => ErrorCode::Busy,
669        Some(Foundation::ERROR_NOT_SUPPORTED) => ErrorCode::Unsupported,
670        Some(Foundation::ERROR_FILE_EXISTS) => ErrorCode::Exist,
671        Some(Foundation::ERROR_BROKEN_PIPE) => ErrorCode::Pipe,
672        Some(Foundation::ERROR_BUFFER_OVERFLOW) => ErrorCode::NameTooLong,
673        Some(Foundation::ERROR_NOT_A_REPARSE_POINT) => ErrorCode::Invalid,
674        Some(Foundation::ERROR_NEGATIVE_SEEK) => ErrorCode::Invalid,
675        Some(Foundation::ERROR_DIRECTORY) => ErrorCode::NotDirectory,
676        Some(Foundation::ERROR_ALREADY_EXISTS) => ErrorCode::Exist,
677        Some(Foundation::ERROR_STOPPED_ON_SYMLINK) => ErrorCode::Loop,
678        Some(Foundation::ERROR_DIRECTORY_NOT_SUPPORTED) => ErrorCode::IsDirectory,
679        _ => return None,
680    })
681}
682
683impl From<std::io::Error> for ErrorCode {
684    fn from(err: std::io::Error) -> ErrorCode {
685        ErrorCode::from(&err)
686    }
687}
688
689impl<'a> From<&'a std::io::Error> for ErrorCode {
690    fn from(err: &'a std::io::Error) -> ErrorCode {
691        match from_raw_os_error(err.raw_os_error()) {
692            Some(errno) => errno,
693            None => {
694                tracing::debug!("unknown raw os error: {err}");
695                match err.kind() {
696                    std::io::ErrorKind::NotFound => ErrorCode::NoEntry,
697                    std::io::ErrorKind::PermissionDenied => ErrorCode::NotPermitted,
698                    std::io::ErrorKind::AlreadyExists => ErrorCode::Exist,
699                    std::io::ErrorKind::InvalidInput => ErrorCode::Invalid,
700                    _ => ErrorCode::Io,
701                }
702            }
703        }
704    }
705}
706
707impl From<std::num::TryFromIntError> for ErrorCode {
708    fn from(_err: std::num::TryFromIntError) -> ErrorCode {
709        ErrorCode::Overflow
710    }
711}
712
713fn descriptortype_from(ft: crate::filesystem::primitives::FileType) -> types::DescriptorType {
714    crate::filesystem::DescriptorType::from(ft).into()
715}
716
717fn systemtime_from(t: wall_clock::Datetime) -> Result<std::time::SystemTime, ErrorCode> {
718    // Catch nanoseconds-into-seconds Overflow error explicitly:
719    // unfortunately, Duration::new panics when input overflows
720    let duration = core::time::Duration::new(
721        t.seconds
722            .checked_add(u64::from(t.nanoseconds / 1_000_000_000))
723            .ok_or(ErrorCode::Overflow)?,
724        t.nanoseconds % 1_000_000_000,
725    );
726    std::time::SystemTime::UNIX_EPOCH
727        .checked_add(duration)
728        .ok_or(ErrorCode::Overflow)
729}
730
731fn systemtimespec_from(t: types::NewTimestamp) -> Result<Option<SystemTime>, ErrorCode> {
732    match t {
733        types::NewTimestamp::NoChange => Ok(None),
734        types::NewTimestamp::Now => Ok(Some(SystemTime::now())),
735        types::NewTimestamp::Timestamp(st) => {
736            let st = systemtime_from(st)?;
737            Ok(Some(st))
738        }
739    }
740}
741
742impl From<crate::clocks::DatetimeError> for ErrorCode {
743    fn from(_: crate::clocks::DatetimeError) -> ErrorCode {
744        ErrorCode::Overflow
745    }
746}
747
748#[cfg(test)]
749mod test {
750    use super::*;
751    use wasmtime::component::ResourceTable;
752
753    #[test]
754    fn table_readdir_works() {
755        let mut table = ResourceTable::new();
756        let ix = table
757            .push(ReaddirIterator::new(std::iter::empty()))
758            .unwrap();
759        let _ = table.get(&ix).unwrap();
760        table.delete(ix).unwrap();
761    }
762}
763
764mod named {
765    use crate::filesystem::WasiFilesystemNamedView;
766    use crate::p2::bindings::filesystem::types::{
767        Advice, Descriptor, DescriptorFlags, DescriptorStat, DescriptorType, DirectoryEntry,
768        DirectoryEntryStream, ErrorCode, Filesize, MetadataHashValue, NewTimestamp, OpenFlags,
769        PathFlags,
770    };
771    use crate::p2::bindings::named_imports::wasi::filesystem::{preopens, types};
772    use crate::p2::{FsError, FsResult};
773    use crate::{NamedId, WasiCtxNamedView};
774    use wasmtime::component::Resource;
775    use wasmtime_wasi_io::streams::{DynInputStream, DynOutputStream};
776
777    impl<T> preopens::Host for WasiCtxNamedView<'_, T>
778    where
779        T: WasiFilesystemNamedView,
780    {
781        fn get_directories(
782            &mut self,
783            id: NamedId,
784        ) -> wasmtime::Result<Vec<(Resource<Descriptor>, String)>> {
785            super::preopens::Host::get_directories(&mut self.0.filesystem(id))
786        }
787    }
788
789    impl<T> types::Host for WasiCtxNamedView<'_, T>
790    where
791        T: WasiFilesystemNamedView,
792    {
793        fn convert_error_code(&mut self, err: FsError) -> wasmtime::Result<ErrorCode> {
794            err.downcast()
795        }
796
797        fn filesystem_error_code(
798            &mut self,
799            id: NamedId,
800            err: Resource<wasmtime::Error>,
801        ) -> wasmtime::Result<Option<ErrorCode>> {
802            super::types::Host::filesystem_error_code(&mut self.0.filesystem(id), err)
803        }
804    }
805
806    impl<T> types::HostDescriptor for WasiCtxNamedView<'_, T>
807    where
808        T: WasiFilesystemNamedView,
809    {
810        async fn advise(
811            &mut self,
812            id: NamedId,
813            fd: Resource<Descriptor>,
814            offset: Filesize,
815            len: Filesize,
816            advice: Advice,
817        ) -> FsResult<()> {
818            super::HostDescriptor::advise(&mut self.0.filesystem(id), fd, offset, len, advice).await
819        }
820
821        async fn sync_data(&mut self, id: NamedId, fd: Resource<Descriptor>) -> FsResult<()> {
822            super::HostDescriptor::sync_data(&mut self.0.filesystem(id), fd).await
823        }
824
825        async fn get_flags(
826            &mut self,
827            id: NamedId,
828            fd: Resource<Descriptor>,
829        ) -> FsResult<DescriptorFlags> {
830            super::HostDescriptor::get_flags(&mut self.0.filesystem(id), fd).await
831        }
832
833        async fn get_type(
834            &mut self,
835            id: NamedId,
836            fd: Resource<Descriptor>,
837        ) -> FsResult<DescriptorType> {
838            super::HostDescriptor::get_type(&mut self.0.filesystem(id), fd).await
839        }
840
841        async fn set_size(
842            &mut self,
843            id: NamedId,
844            fd: Resource<Descriptor>,
845            size: Filesize,
846        ) -> FsResult<()> {
847            super::HostDescriptor::set_size(&mut self.0.filesystem(id), fd, size).await
848        }
849
850        async fn set_times(
851            &mut self,
852            id: NamedId,
853            fd: Resource<Descriptor>,
854            atim: NewTimestamp,
855            mtim: NewTimestamp,
856        ) -> FsResult<()> {
857            super::HostDescriptor::set_times(&mut self.0.filesystem(id), fd, atim, mtim).await
858        }
859
860        async fn read(
861            &mut self,
862            id: NamedId,
863            fd: Resource<Descriptor>,
864            len: Filesize,
865            offset: Filesize,
866        ) -> FsResult<(Vec<u8>, bool)> {
867            super::HostDescriptor::read(&mut self.0.filesystem(id), fd, len, offset).await
868        }
869
870        async fn write(
871            &mut self,
872            id: NamedId,
873            fd: Resource<Descriptor>,
874            buf: Vec<u8>,
875            offset: Filesize,
876        ) -> FsResult<Filesize> {
877            super::HostDescriptor::write(&mut self.0.filesystem(id), fd, buf, offset).await
878        }
879
880        async fn read_directory(
881            &mut self,
882            id: NamedId,
883            fd: Resource<Descriptor>,
884        ) -> FsResult<Resource<DirectoryEntryStream>> {
885            super::HostDescriptor::read_directory(&mut self.0.filesystem(id), fd).await
886        }
887
888        async fn sync(&mut self, id: NamedId, fd: Resource<Descriptor>) -> FsResult<()> {
889            super::HostDescriptor::sync(&mut self.0.filesystem(id), fd).await
890        }
891
892        async fn create_directory_at(
893            &mut self,
894            id: NamedId,
895            fd: Resource<Descriptor>,
896            path: String,
897        ) -> FsResult<()> {
898            super::HostDescriptor::create_directory_at(&mut self.0.filesystem(id), fd, path).await
899        }
900
901        async fn stat(
902            &mut self,
903            id: NamedId,
904            fd: Resource<Descriptor>,
905        ) -> FsResult<DescriptorStat> {
906            super::HostDescriptor::stat(&mut self.0.filesystem(id), fd).await
907        }
908
909        async fn stat_at(
910            &mut self,
911            id: NamedId,
912            fd: Resource<Descriptor>,
913            path_flags: PathFlags,
914            path: String,
915        ) -> FsResult<DescriptorStat> {
916            super::HostDescriptor::stat_at(&mut self.0.filesystem(id), fd, path_flags, path).await
917        }
918
919        async fn set_times_at(
920            &mut self,
921            id: NamedId,
922            fd: Resource<Descriptor>,
923            path_flags: PathFlags,
924            path: String,
925            atim: NewTimestamp,
926            mtim: NewTimestamp,
927        ) -> FsResult<()> {
928            super::HostDescriptor::set_times_at(
929                &mut self.0.filesystem(id),
930                fd,
931                path_flags,
932                path,
933                atim,
934                mtim,
935            )
936            .await
937        }
938
939        async fn link_at(
940            &mut self,
941            id: NamedId,
942            fd: Resource<Descriptor>,
943            old_path_flags: PathFlags,
944            old_path: String,
945            new_descriptor: Resource<Descriptor>,
946            new_path: String,
947        ) -> FsResult<()> {
948            super::HostDescriptor::link_at(
949                &mut self.0.filesystem(id),
950                fd,
951                old_path_flags,
952                old_path,
953                new_descriptor,
954                new_path,
955            )
956            .await
957        }
958
959        async fn open_at(
960            &mut self,
961            id: NamedId,
962            fd: Resource<Descriptor>,
963            path_flags: PathFlags,
964            path: String,
965            oflags: OpenFlags,
966            flags: DescriptorFlags,
967        ) -> FsResult<Resource<Descriptor>> {
968            super::HostDescriptor::open_at(
969                &mut self.0.filesystem(id),
970                fd,
971                path_flags,
972                path,
973                oflags,
974                flags,
975            )
976            .await
977        }
978
979        fn drop(&mut self, id: NamedId, fd: Resource<Descriptor>) -> wasmtime::Result<()> {
980            super::HostDescriptor::drop(&mut self.0.filesystem(id), fd)
981        }
982
983        async fn readlink_at(
984            &mut self,
985            id: NamedId,
986            fd: Resource<Descriptor>,
987            path: String,
988        ) -> FsResult<String> {
989            super::HostDescriptor::readlink_at(&mut self.0.filesystem(id), fd, path).await
990        }
991
992        async fn remove_directory_at(
993            &mut self,
994            id: NamedId,
995            fd: Resource<Descriptor>,
996            path: String,
997        ) -> FsResult<()> {
998            super::HostDescriptor::remove_directory_at(&mut self.0.filesystem(id), fd, path).await
999        }
1000
1001        async fn rename_at(
1002            &mut self,
1003            id: NamedId,
1004            fd: Resource<Descriptor>,
1005            old_path: String,
1006            new_fd: Resource<Descriptor>,
1007            new_path: String,
1008        ) -> FsResult<()> {
1009            super::HostDescriptor::rename_at(
1010                &mut self.0.filesystem(id),
1011                fd,
1012                old_path,
1013                new_fd,
1014                new_path,
1015            )
1016            .await
1017        }
1018
1019        async fn symlink_at(
1020            &mut self,
1021            id: NamedId,
1022            fd: Resource<Descriptor>,
1023            src_path: String,
1024            dest_path: String,
1025        ) -> FsResult<()> {
1026            super::HostDescriptor::symlink_at(&mut self.0.filesystem(id), fd, src_path, dest_path)
1027                .await
1028        }
1029
1030        async fn unlink_file_at(
1031            &mut self,
1032            id: NamedId,
1033            fd: Resource<Descriptor>,
1034            path: String,
1035        ) -> FsResult<()> {
1036            super::HostDescriptor::unlink_file_at(&mut self.0.filesystem(id), fd, path).await
1037        }
1038
1039        fn read_via_stream(
1040            &mut self,
1041            id: NamedId,
1042            fd: Resource<Descriptor>,
1043            offset: Filesize,
1044        ) -> FsResult<Resource<DynInputStream>> {
1045            super::HostDescriptor::read_via_stream(&mut self.0.filesystem(id), fd, offset)
1046        }
1047
1048        fn write_via_stream(
1049            &mut self,
1050            id: NamedId,
1051            fd: Resource<Descriptor>,
1052            offset: Filesize,
1053        ) -> FsResult<Resource<DynOutputStream>> {
1054            super::HostDescriptor::write_via_stream(&mut self.0.filesystem(id), fd, offset)
1055        }
1056
1057        fn append_via_stream(
1058            &mut self,
1059            id: NamedId,
1060            fd: Resource<Descriptor>,
1061        ) -> FsResult<Resource<DynOutputStream>> {
1062            super::HostDescriptor::append_via_stream(&mut self.0.filesystem(id), fd)
1063        }
1064
1065        async fn is_same_object(
1066            &mut self,
1067            id: NamedId,
1068            a: Resource<Descriptor>,
1069            b: Resource<Descriptor>,
1070        ) -> wasmtime::Result<bool> {
1071            super::HostDescriptor::is_same_object(&mut self.0.filesystem(id), a, b).await
1072        }
1073
1074        async fn metadata_hash(
1075            &mut self,
1076            id: NamedId,
1077            fd: Resource<Descriptor>,
1078        ) -> FsResult<MetadataHashValue> {
1079            super::HostDescriptor::metadata_hash(&mut self.0.filesystem(id), fd).await
1080        }
1081
1082        async fn metadata_hash_at(
1083            &mut self,
1084            id: NamedId,
1085            fd: Resource<Descriptor>,
1086            path_flags: PathFlags,
1087            path: String,
1088        ) -> FsResult<MetadataHashValue> {
1089            super::HostDescriptor::metadata_hash_at(
1090                &mut self.0.filesystem(id),
1091                fd,
1092                path_flags,
1093                path,
1094            )
1095            .await
1096        }
1097    }
1098
1099    impl<T> types::HostDirectoryEntryStream for WasiCtxNamedView<'_, T>
1100    where
1101        T: WasiFilesystemNamedView,
1102    {
1103        async fn read_directory_entry(
1104            &mut self,
1105            id: NamedId,
1106            stream: Resource<DirectoryEntryStream>,
1107        ) -> FsResult<Option<DirectoryEntry>> {
1108            super::HostDirectoryEntryStream::read_directory_entry(
1109                &mut self.0.filesystem(id),
1110                stream,
1111            )
1112            .await
1113        }
1114
1115        fn drop(
1116            &mut self,
1117            id: NamedId,
1118            stream: Resource<DirectoryEntryStream>,
1119        ) -> wasmtime::Result<()> {
1120            super::HostDirectoryEntryStream::drop(&mut self.0.filesystem(id), stream)
1121        }
1122    }
1123}