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                    cap_primitives::fs::read_base_dir(d)?
175                        .map(|entry| {
176                            let entry = entry?;
177                            let meta = entry.metadata()?;
178                            let type_ = descriptortype_from(meta.file_type());
179                            let name = entry
180                                .file_name()
181                                .into_string()
182                                .map_err(|_| ReaddirError::IllegalSequence)?;
183                            Ok(types::DirectoryEntry { type_, name })
184                        })
185                        .collect::<Vec<Result<types::DirectoryEntry, ReaddirError>>>(),
186                )
187            })
188            .await?
189            .into_iter();
190
191        // On windows, filter out files like `C:\DumpStack.log.tmp` which we
192        // can't get full metadata for.
193        #[cfg(windows)]
194        let entries = entries.filter(|entry| {
195            use windows_sys::Win32::Foundation::{ERROR_ACCESS_DENIED, ERROR_SHARING_VIOLATION};
196            if let Err(ReaddirError::Io(err)) = entry {
197                if err.raw_os_error() == Some(ERROR_SHARING_VIOLATION as i32)
198                    || err.raw_os_error() == Some(ERROR_ACCESS_DENIED as i32)
199                {
200                    return false;
201                }
202            }
203            true
204        });
205        let entries = entries.map(|r| match r {
206            Ok(r) => Ok(r),
207            Err(ReaddirError::Io(e)) => Err(e.into()),
208            Err(ReaddirError::IllegalSequence) => Err(ErrorCode::IllegalByteSequence.into()),
209        });
210        Ok(self.table.push(ReaddirIterator::new(entries))?)
211    }
212
213    async fn sync(&mut self, fd: Resource<types::Descriptor>) -> FsResult<()> {
214        let descriptor = self.table.get(&fd)?;
215        descriptor.sync().await?;
216        Ok(())
217    }
218
219    async fn create_directory_at(
220        &mut self,
221        fd: Resource<types::Descriptor>,
222        path: String,
223    ) -> FsResult<()> {
224        let d = self.table.get(&fd)?.dir()?;
225        d.create_directory_at(path).await?;
226        Ok(())
227    }
228
229    async fn stat(&mut self, fd: Resource<types::Descriptor>) -> FsResult<types::DescriptorStat> {
230        let descriptor = self.table.get(&fd)?;
231        let stat = descriptor.stat().await?;
232        Ok(stat.try_into()?)
233    }
234
235    async fn stat_at(
236        &mut self,
237        fd: Resource<types::Descriptor>,
238        path_flags: types::PathFlags,
239        path: String,
240    ) -> FsResult<types::DescriptorStat> {
241        let d = self.table.get(&fd)?.dir()?;
242        let stat = d.stat_at(path_flags.into(), path).await?;
243        Ok(stat.try_into()?)
244    }
245
246    async fn set_times_at(
247        &mut self,
248        fd: Resource<types::Descriptor>,
249        path_flags: types::PathFlags,
250        path: String,
251        atim: types::NewTimestamp,
252        mtim: types::NewTimestamp,
253    ) -> FsResult<()> {
254        let d = self.table.get(&fd)?.dir()?;
255        let atim = systemtimespec_from(atim)?;
256        let mtim = systemtimespec_from(mtim)?;
257        d.set_times_at(path_flags.into(), path, atim, mtim).await?;
258        Ok(())
259    }
260
261    async fn link_at(
262        &mut self,
263        fd: Resource<types::Descriptor>,
264        // TODO delete the path flags from this function
265        old_path_flags: types::PathFlags,
266        old_path: String,
267        new_descriptor: Resource<types::Descriptor>,
268        new_path: String,
269    ) -> FsResult<()> {
270        let old_dir = self.table.get(&fd)?.dir()?;
271        let new_dir = self.table.get(&new_descriptor)?.dir()?;
272        old_dir
273            .link_at(old_path_flags.into(), old_path, new_dir, new_path)
274            .await?;
275        Ok(())
276    }
277
278    async fn open_at(
279        &mut self,
280        fd: Resource<types::Descriptor>,
281        path_flags: types::PathFlags,
282        path: String,
283        oflags: types::OpenFlags,
284        flags: types::DescriptorFlags,
285    ) -> FsResult<Resource<types::Descriptor>> {
286        let d = self.table.get(&fd)?.dir()?;
287        let fd = d
288            .open_at(
289                path_flags.into(),
290                path,
291                oflags.into(),
292                flags.into(),
293                self.ctx.allow_blocking_current_thread,
294            )
295            .await?;
296        let fd = self.table.push(fd)?;
297        Ok(fd)
298    }
299
300    fn drop(&mut self, fd: Resource<types::Descriptor>) -> wasmtime::Result<()> {
301        // The Drop will close the file/dir, but if the close syscall
302        // blocks the thread, I will face god and walk backwards into hell.
303        // tokio::fs::File just uses std::fs::File's Drop impl to close, so
304        // it doesn't appear anyone else has found this to be a problem.
305        // (Not that they could solve it without async drop...)
306        self.table.delete(fd)?;
307
308        Ok(())
309    }
310
311    async fn readlink_at(
312        &mut self,
313        fd: Resource<types::Descriptor>,
314        path: String,
315    ) -> FsResult<String> {
316        let d = self.table.get(&fd)?.dir()?;
317        let path = d.readlink_at(path).await?;
318        Ok(path)
319    }
320
321    async fn remove_directory_at(
322        &mut self,
323        fd: Resource<types::Descriptor>,
324        path: String,
325    ) -> FsResult<()> {
326        let d = self.table.get(&fd)?.dir()?;
327        d.remove_directory_at(path).await?;
328        Ok(())
329    }
330
331    async fn rename_at(
332        &mut self,
333        fd: Resource<types::Descriptor>,
334        old_path: String,
335        new_fd: Resource<types::Descriptor>,
336        new_path: String,
337    ) -> FsResult<()> {
338        let old_dir = self.table.get(&fd)?.dir()?;
339        let new_dir = self.table.get(&new_fd)?.dir()?;
340        old_dir.rename_at(old_path, new_dir, new_path).await?;
341        Ok(())
342    }
343
344    async fn symlink_at(
345        &mut self,
346        fd: Resource<types::Descriptor>,
347        src_path: String,
348        dest_path: String,
349    ) -> FsResult<()> {
350        let d = self.table.get(&fd)?.dir()?;
351        d.symlink_at(src_path, dest_path).await?;
352        Ok(())
353    }
354
355    async fn unlink_file_at(
356        &mut self,
357        fd: Resource<types::Descriptor>,
358        path: String,
359    ) -> FsResult<()> {
360        let d = self.table.get(&fd)?.dir()?;
361        d.unlink_file_at(path).await?;
362        Ok(())
363    }
364
365    fn read_via_stream(
366        &mut self,
367        fd: Resource<types::Descriptor>,
368        offset: types::Filesize,
369    ) -> FsResult<Resource<DynInputStream>> {
370        // Trap if fd lookup fails. A directory is is-directory, not
371        // bad-descriptor (POSIX EISDIR on read).
372        let f = match self.table.get(&fd)? {
373            Descriptor::File(f) => f,
374            Descriptor::Dir(_) => return Err(ErrorCode::IsDirectory.into()),
375        };
376
377        // Create a stream view for it.
378        let reader: DynInputStream = Box::new(FileInputStream::new(f, offset));
379
380        // Insert the stream view into the table. Trap if the table is full.
381        let index = self.table.push(reader)?;
382
383        Ok(index)
384    }
385
386    fn write_via_stream(
387        &mut self,
388        fd: Resource<types::Descriptor>,
389        offset: types::Filesize,
390    ) -> FsResult<Resource<DynOutputStream>> {
391        // Trap if fd lookup fails:
392        let f = self.table.get(&fd)?.file()?;
393
394        if f.perms.write_not_permitted() {
395            Err(types::ErrorCode::NotPermitted)?;
396        }
397
398        // Create a stream view for it.
399        let writer = FileOutputStream::write_at(f, offset);
400        let writer: DynOutputStream = Box::new(writer);
401
402        // Insert the stream view into the table. Trap if the table is full.
403        let index = self.table.push(writer)?;
404
405        Ok(index)
406    }
407
408    fn append_via_stream(
409        &mut self,
410        fd: Resource<types::Descriptor>,
411    ) -> FsResult<Resource<DynOutputStream>> {
412        // Trap if fd lookup fails:
413        let f = self.table.get(&fd)?.file()?;
414
415        if f.perms.write_not_permitted() {
416            Err(types::ErrorCode::NotPermitted)?;
417        }
418
419        // Create a stream view for it.
420        let appender = FileOutputStream::append(f);
421        let appender: DynOutputStream = Box::new(appender);
422
423        // Insert the stream view into the table. Trap if the table is full.
424        let index = self.table.push(appender)?;
425
426        Ok(index)
427    }
428
429    async fn is_same_object(
430        &mut self,
431        a: Resource<types::Descriptor>,
432        b: Resource<types::Descriptor>,
433    ) -> wasmtime::Result<bool> {
434        let descriptor_a = self.table.get(&a)?;
435        let descriptor_b = self.table.get(&b)?;
436        descriptor_a.is_same_object(descriptor_b).await
437    }
438    async fn metadata_hash(
439        &mut self,
440        fd: Resource<types::Descriptor>,
441    ) -> FsResult<types::MetadataHashValue> {
442        let fd = self.table.get(&fd)?;
443        let meta = fd.metadata_hash().await?;
444        Ok(meta.into())
445    }
446    async fn metadata_hash_at(
447        &mut self,
448        fd: Resource<types::Descriptor>,
449        path_flags: types::PathFlags,
450        path: String,
451    ) -> FsResult<types::MetadataHashValue> {
452        let d = self.table.get(&fd)?.dir()?;
453        let meta = d.metadata_hash_at(path_flags.into(), path).await?;
454        Ok(meta.into())
455    }
456}
457
458impl HostDirectoryEntryStream for WasiFilesystemCtxView<'_> {
459    async fn read_directory_entry(
460        &mut self,
461        stream: Resource<types::DirectoryEntryStream>,
462    ) -> FsResult<Option<types::DirectoryEntry>> {
463        let readdir = self.table.get(&stream)?;
464        readdir.next()
465    }
466
467    fn drop(&mut self, stream: Resource<types::DirectoryEntryStream>) -> wasmtime::Result<()> {
468        self.table.delete(stream)?;
469        Ok(())
470    }
471}
472
473impl From<types::Advice> for crate::filesystem::Advice {
474    fn from(advice: types::Advice) -> Self {
475        match advice {
476            types::Advice::Normal => Self::Normal,
477            types::Advice::Sequential => Self::Sequential,
478            types::Advice::Random => Self::Random,
479            types::Advice::WillNeed => Self::WillNeed,
480            types::Advice::DontNeed => Self::DontNeed,
481            types::Advice::NoReuse => Self::NoReuse,
482        }
483    }
484}
485
486impl From<types::OpenFlags> for crate::filesystem::OpenFlags {
487    fn from(flags: types::OpenFlags) -> Self {
488        let mut out = Self::empty();
489        if flags.contains(types::OpenFlags::CREATE) {
490            out |= Self::CREATE;
491        }
492        if flags.contains(types::OpenFlags::DIRECTORY) {
493            out |= Self::DIRECTORY;
494        }
495        if flags.contains(types::OpenFlags::EXCLUSIVE) {
496            out |= Self::EXCLUSIVE;
497        }
498        if flags.contains(types::OpenFlags::TRUNCATE) {
499            out |= Self::TRUNCATE;
500        }
501        out
502    }
503}
504
505impl From<types::PathFlags> for crate::filesystem::PathFlags {
506    fn from(flags: types::PathFlags) -> Self {
507        let mut out = Self::empty();
508        if flags.contains(types::PathFlags::SYMLINK_FOLLOW) {
509            out |= Self::SYMLINK_FOLLOW;
510        }
511        out
512    }
513}
514
515impl From<crate::filesystem::DescriptorFlags> for types::DescriptorFlags {
516    fn from(flags: crate::filesystem::DescriptorFlags) -> Self {
517        let mut out = Self::empty();
518        if flags.contains(crate::filesystem::DescriptorFlags::READ) {
519            out |= Self::READ;
520        }
521        if flags.contains(crate::filesystem::DescriptorFlags::WRITE) {
522            out |= Self::WRITE;
523        }
524        if flags.contains(crate::filesystem::DescriptorFlags::FILE_INTEGRITY_SYNC) {
525            out |= Self::FILE_INTEGRITY_SYNC;
526        }
527        if flags.contains(crate::filesystem::DescriptorFlags::DATA_INTEGRITY_SYNC) {
528            out |= Self::DATA_INTEGRITY_SYNC;
529        }
530        if flags.contains(crate::filesystem::DescriptorFlags::REQUESTED_WRITE_SYNC) {
531            out |= Self::REQUESTED_WRITE_SYNC;
532        }
533        if flags.contains(crate::filesystem::DescriptorFlags::MUTATE_DIRECTORY) {
534            out |= Self::MUTATE_DIRECTORY;
535        }
536        out
537    }
538}
539
540impl From<types::DescriptorFlags> for crate::filesystem::DescriptorFlags {
541    fn from(flags: types::DescriptorFlags) -> Self {
542        let mut out = Self::empty();
543        if flags.contains(types::DescriptorFlags::READ) {
544            out |= Self::READ;
545        }
546        if flags.contains(types::DescriptorFlags::WRITE) {
547            out |= Self::WRITE;
548        }
549        if flags.contains(types::DescriptorFlags::FILE_INTEGRITY_SYNC) {
550            out |= Self::FILE_INTEGRITY_SYNC;
551        }
552        if flags.contains(types::DescriptorFlags::DATA_INTEGRITY_SYNC) {
553            out |= Self::DATA_INTEGRITY_SYNC;
554        }
555        if flags.contains(types::DescriptorFlags::REQUESTED_WRITE_SYNC) {
556            out |= Self::REQUESTED_WRITE_SYNC;
557        }
558        if flags.contains(types::DescriptorFlags::MUTATE_DIRECTORY) {
559            out |= Self::MUTATE_DIRECTORY;
560        }
561        out
562    }
563}
564
565impl From<crate::filesystem::MetadataHashValue> for types::MetadataHashValue {
566    fn from(
567        crate::filesystem::MetadataHashValue { lower, upper }: crate::filesystem::MetadataHashValue,
568    ) -> Self {
569        Self { lower, upper }
570    }
571}
572
573impl TryFrom<crate::filesystem::DescriptorStat> for types::DescriptorStat {
574    type Error = ErrorCode;
575
576    fn try_from(
577        crate::filesystem::DescriptorStat {
578            type_,
579            link_count,
580            size,
581            data_access_timestamp,
582            data_modification_timestamp,
583            status_change_timestamp,
584        }: crate::filesystem::DescriptorStat,
585    ) -> Result<Self, ErrorCode> {
586        // Internal timestamps use i64 seconds; wasi:clocks/wall-clock uses u64
587        // (non-negative). Times outside that range become missing rather than
588        // failing the whole stat (e.g. host-clamped far-past mtimes on macOS).
589        Ok(Self {
590            type_: type_.into(),
591            link_count,
592            size,
593            data_access_timestamp: data_access_timestamp.and_then(|t| t.try_into().ok()),
594            data_modification_timestamp: data_modification_timestamp
595                .and_then(|t| t.try_into().ok()),
596            status_change_timestamp: status_change_timestamp.and_then(|t| t.try_into().ok()),
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<std::num::TryFromIntError> for ErrorCode {
711    fn from(_err: std::num::TryFromIntError) -> ErrorCode {
712        ErrorCode::Overflow
713    }
714}
715
716fn descriptortype_from(ft: cap_primitives::fs::FileType) -> types::DescriptorType {
717    crate::filesystem::DescriptorType::from(ft).into()
718}
719
720fn systemtime_from(t: wall_clock::Datetime) -> Result<std::time::SystemTime, ErrorCode> {
721    std::time::SystemTime::UNIX_EPOCH
722        .checked_add(core::time::Duration::new(t.seconds, t.nanoseconds))
723        .ok_or(ErrorCode::Overflow)
724}
725
726fn systemtimespec_from(t: types::NewTimestamp) -> Result<Option<SystemTime>, ErrorCode> {
727    match t {
728        types::NewTimestamp::NoChange => Ok(None),
729        types::NewTimestamp::Now => Ok(Some(SystemTime::now())),
730        types::NewTimestamp::Timestamp(st) => {
731            let st = systemtime_from(st)?;
732            Ok(Some(st))
733        }
734    }
735}
736
737impl From<crate::clocks::DatetimeError> for ErrorCode {
738    fn from(_: crate::clocks::DatetimeError) -> ErrorCode {
739        ErrorCode::Overflow
740    }
741}
742
743#[cfg(test)]
744mod test {
745    use super::*;
746    use wasmtime::component::ResourceTable;
747
748    #[test]
749    fn table_readdir_works() {
750        let mut table = ResourceTable::new();
751        let ix = table
752            .push(ReaddirIterator::new(std::iter::empty()))
753            .unwrap();
754        let _ = table.get(&ix).unwrap();
755        table.delete(ix).unwrap();
756    }
757}