wasmtime_wasi/p2/host/
filesystem.rs

1use crate::filesystem::{Descriptor, WasiFilesystemCtxView};
2use crate::p2::bindings::clocks::wall_clock;
3use crate::p2::bindings::filesystem::preopens;
4use crate::p2::bindings::filesystem::types::{
5    self, ErrorCode, HostDescriptor, HostDirectoryEntryStream,
6};
7use crate::p2::filesystem::{FileInputStream, FileOutputStream, ReaddirIterator};
8use crate::p2::{FsError, FsResult};
9use crate::{DirPerms, FilePerms};
10use wasmtime::component::Resource;
11use wasmtime_wasi_io::streams::{DynInputStream, DynOutputStream};
12
13mod sync;
14
15impl preopens::Host for WasiFilesystemCtxView<'_> {
16    fn get_directories(&mut self) -> wasmtime::Result<Vec<(Resource<Descriptor>, String)>> {
17        self.get_directories()
18    }
19}
20
21impl types::Host for WasiFilesystemCtxView<'_> {
22    fn convert_error_code(&mut self, err: FsError) -> anyhow::Result<ErrorCode> {
23        err.downcast()
24    }
25
26    fn filesystem_error_code(
27        &mut self,
28        err: Resource<anyhow::Error>,
29    ) -> anyhow::Result<Option<ErrorCode>> {
30        let err = self.table.get(&err)?;
31
32        // Currently `err` always comes from the stream implementation which
33        // uses standard reads/writes so only check for `std::io::Error` here.
34        if let Some(err) = err.downcast_ref::<std::io::Error>() {
35            return Ok(Some(ErrorCode::from(err)));
36        }
37
38        Ok(None)
39    }
40}
41
42impl HostDescriptor for WasiFilesystemCtxView<'_> {
43    async fn advise(
44        &mut self,
45        fd: Resource<types::Descriptor>,
46        offset: types::Filesize,
47        len: types::Filesize,
48        advice: types::Advice,
49    ) -> FsResult<()> {
50        let f = self.table.get(&fd)?.file()?;
51        f.advise(offset, len, advice.into()).await?;
52        Ok(())
53    }
54
55    async fn sync_data(&mut self, fd: Resource<types::Descriptor>) -> FsResult<()> {
56        let descriptor = self.table.get(&fd)?;
57        descriptor.sync_data().await?;
58        Ok(())
59    }
60
61    async fn get_flags(
62        &mut self,
63        fd: Resource<types::Descriptor>,
64    ) -> FsResult<types::DescriptorFlags> {
65        let descriptor = self.table.get(&fd)?;
66        let flags = descriptor.get_flags().await?;
67        Ok(flags.into())
68    }
69
70    async fn get_type(
71        &mut self,
72        fd: Resource<types::Descriptor>,
73    ) -> FsResult<types::DescriptorType> {
74        let descriptor = self.table.get(&fd)?;
75        let ty = descriptor.get_type().await?;
76        Ok(ty.into())
77    }
78
79    async fn set_size(
80        &mut self,
81        fd: Resource<types::Descriptor>,
82        size: types::Filesize,
83    ) -> FsResult<()> {
84        let f = self.table.get(&fd)?.file()?;
85        f.set_size(size).await?;
86        Ok(())
87    }
88
89    async fn set_times(
90        &mut self,
91        fd: Resource<types::Descriptor>,
92        atim: types::NewTimestamp,
93        mtim: types::NewTimestamp,
94    ) -> FsResult<()> {
95        let descriptor = self.table.get(&fd)?;
96        let atim = systemtimespec_from(atim)?;
97        let mtim = systemtimespec_from(mtim)?;
98        descriptor.set_times(atim, mtim).await?;
99        Ok(())
100    }
101
102    async fn read(
103        &mut self,
104        fd: Resource<types::Descriptor>,
105        len: types::Filesize,
106        offset: types::Filesize,
107    ) -> FsResult<(Vec<u8>, bool)> {
108        use std::io::IoSliceMut;
109        use system_interface::fs::FileIoExt;
110
111        let f = self.table.get(&fd)?.file()?;
112        if !f.perms.contains(FilePerms::READ) {
113            return Err(ErrorCode::NotPermitted.into());
114        }
115
116        let (mut buffer, r) = f
117            .run_blocking(move |f| {
118                let mut buffer = vec![0; len.try_into().unwrap_or(usize::MAX)];
119                let r = f.read_vectored_at(&mut [IoSliceMut::new(&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        use std::io::IoSlice;
141        use system_interface::fs::FileIoExt;
142
143        let f = self.table.get(&fd)?.file()?;
144        if !f.perms.contains(FilePerms::WRITE) {
145            return Err(ErrorCode::NotPermitted.into());
146        }
147
148        let bytes_written = f
149            .run_blocking(move |f| f.write_vectored_at(&[IoSlice::new(&buf)], offset))
150            .await?;
151
152        Ok(types::Filesize::try_from(bytes_written).expect("usize fits in Filesize"))
153    }
154
155    async fn read_directory(
156        &mut self,
157        fd: Resource<types::Descriptor>,
158    ) -> FsResult<Resource<types::DirectoryEntryStream>> {
159        let d = self.table.get(&fd)?.dir()?;
160        if !d.perms.contains(DirPerms::READ) {
161            return Err(ErrorCode::NotPermitted.into());
162        }
163
164        enum ReaddirError {
165            Io(std::io::Error),
166            IllegalSequence,
167        }
168        impl From<std::io::Error> for ReaddirError {
169            fn from(e: std::io::Error) -> ReaddirError {
170                ReaddirError::Io(e)
171            }
172        }
173
174        let entries = d
175            .run_blocking(|d| {
176                // Both `entries` and `metadata` perform syscalls, which is why they are done
177                // within this `block` call, rather than delay calculating the metadata
178                // for entries when they're demanded later in the iterator chain.
179                Ok::<_, std::io::Error>(
180                    d.entries()?
181                        .map(|entry| {
182                            let entry = entry?;
183                            let meta = entry.metadata()?;
184                            let type_ = descriptortype_from(meta.file_type());
185                            let name = entry
186                                .file_name()
187                                .into_string()
188                                .map_err(|_| ReaddirError::IllegalSequence)?;
189                            Ok(types::DirectoryEntry { type_, name })
190                        })
191                        .collect::<Vec<Result<types::DirectoryEntry, ReaddirError>>>(),
192                )
193            })
194            .await?
195            .into_iter();
196
197        // On windows, filter out files like `C:\DumpStack.log.tmp` which we
198        // can't get full metadata for.
199        #[cfg(windows)]
200        let entries = entries.filter(|entry| {
201            use windows_sys::Win32::Foundation::{ERROR_ACCESS_DENIED, ERROR_SHARING_VIOLATION};
202            if let Err(ReaddirError::Io(err)) = entry {
203                if err.raw_os_error() == Some(ERROR_SHARING_VIOLATION as i32)
204                    || err.raw_os_error() == Some(ERROR_ACCESS_DENIED as i32)
205                {
206                    return false;
207                }
208            }
209            true
210        });
211        let entries = entries.map(|r| match r {
212            Ok(r) => Ok(r),
213            Err(ReaddirError::Io(e)) => Err(e.into()),
214            Err(ReaddirError::IllegalSequence) => Err(ErrorCode::IllegalByteSequence.into()),
215        });
216        Ok(self.table.push(ReaddirIterator::new(entries))?)
217    }
218
219    async fn sync(&mut self, fd: Resource<types::Descriptor>) -> FsResult<()> {
220        let descriptor = self.table.get(&fd)?;
221        descriptor.sync().await?;
222        Ok(())
223    }
224
225    async fn create_directory_at(
226        &mut self,
227        fd: Resource<types::Descriptor>,
228        path: String,
229    ) -> FsResult<()> {
230        let d = self.table.get(&fd)?.dir()?;
231        d.create_directory_at(path).await?;
232        Ok(())
233    }
234
235    async fn stat(&mut self, fd: Resource<types::Descriptor>) -> FsResult<types::DescriptorStat> {
236        let descriptor = self.table.get(&fd)?;
237        let stat = descriptor.stat().await?;
238        Ok(stat.into())
239    }
240
241    async fn stat_at(
242        &mut self,
243        fd: Resource<types::Descriptor>,
244        path_flags: types::PathFlags,
245        path: String,
246    ) -> FsResult<types::DescriptorStat> {
247        let d = self.table.get(&fd)?.dir()?;
248        let stat = d.stat_at(path_flags.into(), path).await?;
249        Ok(stat.into())
250    }
251
252    async fn set_times_at(
253        &mut self,
254        fd: Resource<types::Descriptor>,
255        path_flags: types::PathFlags,
256        path: String,
257        atim: types::NewTimestamp,
258        mtim: types::NewTimestamp,
259    ) -> FsResult<()> {
260        let d = self.table.get(&fd)?.dir()?;
261        let atim = systemtimespec_from(atim)?;
262        let mtim = systemtimespec_from(mtim)?;
263        d.set_times_at(path_flags.into(), path, atim, mtim).await?;
264        Ok(())
265    }
266
267    async fn link_at(
268        &mut self,
269        fd: Resource<types::Descriptor>,
270        // TODO delete the path flags from this function
271        old_path_flags: types::PathFlags,
272        old_path: String,
273        new_descriptor: Resource<types::Descriptor>,
274        new_path: String,
275    ) -> FsResult<()> {
276        let old_dir = self.table.get(&fd)?.dir()?;
277        let new_dir = self.table.get(&new_descriptor)?.dir()?;
278        old_dir
279            .link_at(old_path_flags.into(), old_path, new_dir, new_path)
280            .await?;
281        Ok(())
282    }
283
284    async fn open_at(
285        &mut self,
286        fd: Resource<types::Descriptor>,
287        path_flags: types::PathFlags,
288        path: String,
289        oflags: types::OpenFlags,
290        flags: types::DescriptorFlags,
291    ) -> FsResult<Resource<types::Descriptor>> {
292        let d = self.table.get(&fd)?.dir()?;
293        let fd = d
294            .open_at(
295                path_flags.into(),
296                path,
297                oflags.into(),
298                flags.into(),
299                self.ctx.allow_blocking_current_thread,
300            )
301            .await?;
302        let fd = self.table.push(fd)?;
303        Ok(fd)
304    }
305
306    fn drop(&mut self, fd: Resource<types::Descriptor>) -> anyhow::Result<()> {
307        // The Drop will close the file/dir, but if the close syscall
308        // blocks the thread, I will face god and walk backwards into hell.
309        // tokio::fs::File just uses std::fs::File's Drop impl to close, so
310        // it doesn't appear anyone else has found this to be a problem.
311        // (Not that they could solve it without async drop...)
312        self.table.delete(fd)?;
313
314        Ok(())
315    }
316
317    async fn readlink_at(
318        &mut self,
319        fd: Resource<types::Descriptor>,
320        path: String,
321    ) -> FsResult<String> {
322        let d = self.table.get(&fd)?.dir()?;
323        let path = d.readlink_at(path).await?;
324        Ok(path)
325    }
326
327    async fn remove_directory_at(
328        &mut self,
329        fd: Resource<types::Descriptor>,
330        path: String,
331    ) -> FsResult<()> {
332        let d = self.table.get(&fd)?.dir()?;
333        d.remove_directory_at(path).await?;
334        Ok(())
335    }
336
337    async fn rename_at(
338        &mut self,
339        fd: Resource<types::Descriptor>,
340        old_path: String,
341        new_fd: Resource<types::Descriptor>,
342        new_path: String,
343    ) -> FsResult<()> {
344        let old_dir = self.table.get(&fd)?.dir()?;
345        let new_dir = self.table.get(&new_fd)?.dir()?;
346        old_dir.rename_at(old_path, new_dir, new_path).await?;
347        Ok(())
348    }
349
350    async fn symlink_at(
351        &mut self,
352        fd: Resource<types::Descriptor>,
353        src_path: String,
354        dest_path: String,
355    ) -> FsResult<()> {
356        let d = self.table.get(&fd)?.dir()?;
357        d.symlink_at(src_path, dest_path).await?;
358        Ok(())
359    }
360
361    async fn unlink_file_at(
362        &mut self,
363        fd: Resource<types::Descriptor>,
364        path: String,
365    ) -> FsResult<()> {
366        let d = self.table.get(&fd)?.dir()?;
367        d.unlink_file_at(path).await?;
368        Ok(())
369    }
370
371    fn read_via_stream(
372        &mut self,
373        fd: Resource<types::Descriptor>,
374        offset: types::Filesize,
375    ) -> FsResult<Resource<DynInputStream>> {
376        // Trap if fd lookup fails:
377        let f = self.table.get(&fd)?.file()?;
378
379        if !f.perms.contains(FilePerms::READ) {
380            Err(types::ErrorCode::BadDescriptor)?;
381        }
382
383        // Create a stream view for it.
384        let reader: DynInputStream = Box::new(FileInputStream::new(f, offset));
385
386        // Insert the stream view into the table. Trap if the table is full.
387        let index = self.table.push(reader)?;
388
389        Ok(index)
390    }
391
392    fn write_via_stream(
393        &mut self,
394        fd: Resource<types::Descriptor>,
395        offset: types::Filesize,
396    ) -> FsResult<Resource<DynOutputStream>> {
397        // Trap if fd lookup fails:
398        let f = self.table.get(&fd)?.file()?;
399
400        if !f.perms.contains(FilePerms::WRITE) {
401            Err(types::ErrorCode::BadDescriptor)?;
402        }
403
404        // Create a stream view for it.
405        let writer = FileOutputStream::write_at(f, offset);
406        let writer: DynOutputStream = Box::new(writer);
407
408        // Insert the stream view into the table. Trap if the table is full.
409        let index = self.table.push(writer)?;
410
411        Ok(index)
412    }
413
414    fn append_via_stream(
415        &mut self,
416        fd: Resource<types::Descriptor>,
417    ) -> FsResult<Resource<DynOutputStream>> {
418        // Trap if fd lookup fails:
419        let f = self.table.get(&fd)?.file()?;
420
421        if !f.perms.contains(FilePerms::WRITE) {
422            Err(types::ErrorCode::BadDescriptor)?;
423        }
424
425        // Create a stream view for it.
426        let appender = FileOutputStream::append(f);
427        let appender: DynOutputStream = Box::new(appender);
428
429        // Insert the stream view into the table. Trap if the table is full.
430        let index = self.table.push(appender)?;
431
432        Ok(index)
433    }
434
435    async fn is_same_object(
436        &mut self,
437        a: Resource<types::Descriptor>,
438        b: Resource<types::Descriptor>,
439    ) -> anyhow::Result<bool> {
440        let descriptor_a = self.table.get(&a)?;
441        let descriptor_b = self.table.get(&b)?;
442        descriptor_a.is_same_object(descriptor_b).await
443    }
444    async fn metadata_hash(
445        &mut self,
446        fd: Resource<types::Descriptor>,
447    ) -> FsResult<types::MetadataHashValue> {
448        let fd = self.table.get(&fd)?;
449        let meta = fd.metadata_hash().await?;
450        Ok(meta.into())
451    }
452    async fn metadata_hash_at(
453        &mut self,
454        fd: Resource<types::Descriptor>,
455        path_flags: types::PathFlags,
456        path: String,
457    ) -> FsResult<types::MetadataHashValue> {
458        let d = self.table.get(&fd)?.dir()?;
459        let meta = d.metadata_hash_at(path_flags.into(), path).await?;
460        Ok(meta.into())
461    }
462}
463
464impl HostDirectoryEntryStream for WasiFilesystemCtxView<'_> {
465    async fn read_directory_entry(
466        &mut self,
467        stream: Resource<types::DirectoryEntryStream>,
468    ) -> FsResult<Option<types::DirectoryEntry>> {
469        let readdir = self.table.get(&stream)?;
470        readdir.next()
471    }
472
473    fn drop(&mut self, stream: Resource<types::DirectoryEntryStream>) -> anyhow::Result<()> {
474        self.table.delete(stream)?;
475        Ok(())
476    }
477}
478
479impl From<types::Advice> for system_interface::fs::Advice {
480    fn from(advice: types::Advice) -> Self {
481        match advice {
482            types::Advice::Normal => Self::Normal,
483            types::Advice::Sequential => Self::Sequential,
484            types::Advice::Random => Self::Random,
485            types::Advice::WillNeed => Self::WillNeed,
486            types::Advice::DontNeed => Self::DontNeed,
487            types::Advice::NoReuse => Self::NoReuse,
488        }
489    }
490}
491
492impl From<types::OpenFlags> for crate::filesystem::OpenFlags {
493    fn from(flags: types::OpenFlags) -> Self {
494        let mut out = Self::empty();
495        if flags.contains(types::OpenFlags::CREATE) {
496            out |= Self::CREATE;
497        }
498        if flags.contains(types::OpenFlags::DIRECTORY) {
499            out |= Self::DIRECTORY;
500        }
501        if flags.contains(types::OpenFlags::EXCLUSIVE) {
502            out |= Self::EXCLUSIVE;
503        }
504        if flags.contains(types::OpenFlags::TRUNCATE) {
505            out |= Self::TRUNCATE;
506        }
507        out
508    }
509}
510
511impl From<types::PathFlags> for crate::filesystem::PathFlags {
512    fn from(flags: types::PathFlags) -> Self {
513        let mut out = Self::empty();
514        if flags.contains(types::PathFlags::SYMLINK_FOLLOW) {
515            out |= Self::SYMLINK_FOLLOW;
516        }
517        out
518    }
519}
520
521impl From<crate::filesystem::DescriptorFlags> for types::DescriptorFlags {
522    fn from(flags: crate::filesystem::DescriptorFlags) -> Self {
523        let mut out = Self::empty();
524        if flags.contains(crate::filesystem::DescriptorFlags::READ) {
525            out |= Self::READ;
526        }
527        if flags.contains(crate::filesystem::DescriptorFlags::WRITE) {
528            out |= Self::WRITE;
529        }
530        if flags.contains(crate::filesystem::DescriptorFlags::FILE_INTEGRITY_SYNC) {
531            out |= Self::FILE_INTEGRITY_SYNC;
532        }
533        if flags.contains(crate::filesystem::DescriptorFlags::DATA_INTEGRITY_SYNC) {
534            out |= Self::DATA_INTEGRITY_SYNC;
535        }
536        if flags.contains(crate::filesystem::DescriptorFlags::REQUESTED_WRITE_SYNC) {
537            out |= Self::REQUESTED_WRITE_SYNC;
538        }
539        if flags.contains(crate::filesystem::DescriptorFlags::MUTATE_DIRECTORY) {
540            out |= Self::MUTATE_DIRECTORY;
541        }
542        out
543    }
544}
545
546impl From<types::DescriptorFlags> for crate::filesystem::DescriptorFlags {
547    fn from(flags: types::DescriptorFlags) -> Self {
548        let mut out = Self::empty();
549        if flags.contains(types::DescriptorFlags::READ) {
550            out |= Self::READ;
551        }
552        if flags.contains(types::DescriptorFlags::WRITE) {
553            out |= Self::WRITE;
554        }
555        if flags.contains(types::DescriptorFlags::FILE_INTEGRITY_SYNC) {
556            out |= Self::FILE_INTEGRITY_SYNC;
557        }
558        if flags.contains(types::DescriptorFlags::DATA_INTEGRITY_SYNC) {
559            out |= Self::DATA_INTEGRITY_SYNC;
560        }
561        if flags.contains(types::DescriptorFlags::REQUESTED_WRITE_SYNC) {
562            out |= Self::REQUESTED_WRITE_SYNC;
563        }
564        if flags.contains(types::DescriptorFlags::MUTATE_DIRECTORY) {
565            out |= Self::MUTATE_DIRECTORY;
566        }
567        out
568    }
569}
570
571impl From<crate::filesystem::MetadataHashValue> for types::MetadataHashValue {
572    fn from(
573        crate::filesystem::MetadataHashValue { lower, upper }: crate::filesystem::MetadataHashValue,
574    ) -> Self {
575        Self { lower, upper }
576    }
577}
578
579impl From<crate::filesystem::DescriptorStat> for types::DescriptorStat {
580    fn from(
581        crate::filesystem::DescriptorStat {
582            type_,
583            link_count,
584            size,
585            data_access_timestamp,
586            data_modification_timestamp,
587            status_change_timestamp,
588        }: crate::filesystem::DescriptorStat,
589    ) -> Self {
590        Self {
591            type_: type_.into(),
592            link_count,
593            size,
594            data_access_timestamp: data_access_timestamp.map(Into::into),
595            data_modification_timestamp: data_modification_timestamp.map(Into::into),
596            status_change_timestamp: status_change_timestamp.map(Into::into),
597        }
598    }
599}
600
601impl From<crate::filesystem::DescriptorType> for types::DescriptorType {
602    fn from(ty: crate::filesystem::DescriptorType) -> Self {
603        match ty {
604            crate::filesystem::DescriptorType::Unknown => Self::Unknown,
605            crate::filesystem::DescriptorType::BlockDevice => Self::BlockDevice,
606            crate::filesystem::DescriptorType::CharacterDevice => Self::CharacterDevice,
607            crate::filesystem::DescriptorType::Directory => Self::Directory,
608            crate::filesystem::DescriptorType::SymbolicLink => Self::SymbolicLink,
609            crate::filesystem::DescriptorType::RegularFile => Self::RegularFile,
610        }
611    }
612}
613
614#[cfg(unix)]
615fn from_raw_os_error(err: Option<i32>) -> Option<ErrorCode> {
616    use rustix::io::Errno as RustixErrno;
617    if err.is_none() {
618        return None;
619    }
620    Some(match RustixErrno::from_raw_os_error(err.unwrap()) {
621        RustixErrno::PIPE => ErrorCode::Pipe,
622        RustixErrno::PERM => ErrorCode::NotPermitted,
623        RustixErrno::NOENT => ErrorCode::NoEntry,
624        RustixErrno::NOMEM => ErrorCode::InsufficientMemory,
625        RustixErrno::IO => ErrorCode::Io,
626        RustixErrno::BADF => ErrorCode::BadDescriptor,
627        RustixErrno::BUSY => ErrorCode::Busy,
628        RustixErrno::ACCESS => ErrorCode::Access,
629        RustixErrno::NOTDIR => ErrorCode::NotDirectory,
630        RustixErrno::ISDIR => ErrorCode::IsDirectory,
631        RustixErrno::INVAL => ErrorCode::Invalid,
632        RustixErrno::EXIST => ErrorCode::Exist,
633        RustixErrno::FBIG => ErrorCode::FileTooLarge,
634        RustixErrno::NOSPC => ErrorCode::InsufficientSpace,
635        RustixErrno::SPIPE => ErrorCode::InvalidSeek,
636        RustixErrno::MLINK => ErrorCode::TooManyLinks,
637        RustixErrno::NAMETOOLONG => ErrorCode::NameTooLong,
638        RustixErrno::NOTEMPTY => ErrorCode::NotEmpty,
639        RustixErrno::LOOP => ErrorCode::Loop,
640        RustixErrno::OVERFLOW => ErrorCode::Overflow,
641        RustixErrno::ILSEQ => ErrorCode::IllegalByteSequence,
642        RustixErrno::NOTSUP => ErrorCode::Unsupported,
643        RustixErrno::ALREADY => ErrorCode::Already,
644        RustixErrno::INPROGRESS => ErrorCode::InProgress,
645        RustixErrno::INTR => ErrorCode::Interrupted,
646
647        #[allow(
648            unreachable_patterns,
649            reason = "on some platforms, these have the same value as other errno values"
650        )]
651        RustixErrno::OPNOTSUPP => ErrorCode::Unsupported,
652
653        _ => return None,
654    })
655}
656#[cfg(windows)]
657fn from_raw_os_error(raw_os_error: Option<i32>) -> Option<ErrorCode> {
658    use windows_sys::Win32::Foundation;
659    Some(match raw_os_error.map(|code| code as u32) {
660        Some(Foundation::ERROR_FILE_NOT_FOUND) => ErrorCode::NoEntry,
661        Some(Foundation::ERROR_PATH_NOT_FOUND) => ErrorCode::NoEntry,
662        Some(Foundation::ERROR_ACCESS_DENIED) => ErrorCode::Access,
663        Some(Foundation::ERROR_SHARING_VIOLATION) => ErrorCode::Access,
664        Some(Foundation::ERROR_PRIVILEGE_NOT_HELD) => ErrorCode::NotPermitted,
665        Some(Foundation::ERROR_INVALID_HANDLE) => ErrorCode::BadDescriptor,
666        Some(Foundation::ERROR_INVALID_NAME) => ErrorCode::NoEntry,
667        Some(Foundation::ERROR_NOT_ENOUGH_MEMORY) => ErrorCode::InsufficientMemory,
668        Some(Foundation::ERROR_OUTOFMEMORY) => ErrorCode::InsufficientMemory,
669        Some(Foundation::ERROR_DIR_NOT_EMPTY) => ErrorCode::NotEmpty,
670        Some(Foundation::ERROR_NOT_READY) => ErrorCode::Busy,
671        Some(Foundation::ERROR_BUSY) => ErrorCode::Busy,
672        Some(Foundation::ERROR_NOT_SUPPORTED) => ErrorCode::Unsupported,
673        Some(Foundation::ERROR_FILE_EXISTS) => ErrorCode::Exist,
674        Some(Foundation::ERROR_BROKEN_PIPE) => ErrorCode::Pipe,
675        Some(Foundation::ERROR_BUFFER_OVERFLOW) => ErrorCode::NameTooLong,
676        Some(Foundation::ERROR_NOT_A_REPARSE_POINT) => ErrorCode::Invalid,
677        Some(Foundation::ERROR_NEGATIVE_SEEK) => ErrorCode::Invalid,
678        Some(Foundation::ERROR_DIRECTORY) => ErrorCode::NotDirectory,
679        Some(Foundation::ERROR_ALREADY_EXISTS) => ErrorCode::Exist,
680        Some(Foundation::ERROR_STOPPED_ON_SYMLINK) => ErrorCode::Loop,
681        Some(Foundation::ERROR_DIRECTORY_NOT_SUPPORTED) => ErrorCode::IsDirectory,
682        _ => return None,
683    })
684}
685
686impl From<std::io::Error> for ErrorCode {
687    fn from(err: std::io::Error) -> ErrorCode {
688        ErrorCode::from(&err)
689    }
690}
691
692impl<'a> From<&'a std::io::Error> for ErrorCode {
693    fn from(err: &'a std::io::Error) -> ErrorCode {
694        match from_raw_os_error(err.raw_os_error()) {
695            Some(errno) => errno,
696            None => {
697                tracing::debug!("unknown raw os error: {err}");
698                match err.kind() {
699                    std::io::ErrorKind::NotFound => ErrorCode::NoEntry,
700                    std::io::ErrorKind::PermissionDenied => ErrorCode::NotPermitted,
701                    std::io::ErrorKind::AlreadyExists => ErrorCode::Exist,
702                    std::io::ErrorKind::InvalidInput => ErrorCode::Invalid,
703                    _ => ErrorCode::Io,
704                }
705            }
706        }
707    }
708}
709
710impl From<cap_rand::Error> for ErrorCode {
711    fn from(err: cap_rand::Error) -> ErrorCode {
712        // I picked Error::Io as a 'reasonable default', FIXME dan is this ok?
713        from_raw_os_error(err.raw_os_error()).unwrap_or(ErrorCode::Io)
714    }
715}
716
717impl From<std::num::TryFromIntError> for ErrorCode {
718    fn from(_err: std::num::TryFromIntError) -> ErrorCode {
719        ErrorCode::Overflow
720    }
721}
722
723fn descriptortype_from(ft: cap_std::fs::FileType) -> types::DescriptorType {
724    use cap_fs_ext::FileTypeExt;
725    use types::DescriptorType;
726    if ft.is_dir() {
727        DescriptorType::Directory
728    } else if ft.is_symlink() {
729        DescriptorType::SymbolicLink
730    } else if ft.is_block_device() {
731        DescriptorType::BlockDevice
732    } else if ft.is_char_device() {
733        DescriptorType::CharacterDevice
734    } else if ft.is_file() {
735        DescriptorType::RegularFile
736    } else {
737        DescriptorType::Unknown
738    }
739}
740
741fn systemtime_from(t: wall_clock::Datetime) -> Result<std::time::SystemTime, ErrorCode> {
742    std::time::SystemTime::UNIX_EPOCH
743        .checked_add(core::time::Duration::new(t.seconds, t.nanoseconds))
744        .ok_or(ErrorCode::Overflow)
745}
746
747fn systemtimespec_from(
748    t: types::NewTimestamp,
749) -> Result<Option<fs_set_times::SystemTimeSpec>, ErrorCode> {
750    use fs_set_times::SystemTimeSpec;
751    match t {
752        types::NewTimestamp::NoChange => Ok(None),
753        types::NewTimestamp::Now => Ok(Some(SystemTimeSpec::SymbolicNow)),
754        types::NewTimestamp::Timestamp(st) => {
755            let st = systemtime_from(st)?;
756            Ok(Some(SystemTimeSpec::Absolute(st)))
757        }
758    }
759}
760
761#[cfg(test)]
762mod test {
763    use super::*;
764    use wasmtime::component::ResourceTable;
765
766    #[test]
767    fn table_readdir_works() {
768        let mut table = ResourceTable::new();
769        let ix = table
770            .push(ReaddirIterator::new(std::iter::empty()))
771            .unwrap();
772        let _ = table.get(&ix).unwrap();
773        table.delete(ix).unwrap();
774    }
775}