Skip to main content

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