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