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