wasmtime_wasi/p2/host/
filesystem.rs

1use crate::p2::bindings::clocks::wall_clock;
2use crate::p2::bindings::filesystem::preopens;
3use crate::p2::bindings::filesystem::types::{
4    self, ErrorCode, HostDescriptor, HostDirectoryEntryStream,
5};
6use crate::p2::filesystem::{
7    Descriptor, Dir, File, FileInputStream, FileOutputStream, ReaddirIterator,
8};
9use crate::p2::{FsError, FsResult, IoView, WasiImpl, WasiView};
10use crate::{DirPerms, FilePerms, OpenMode};
11use anyhow::Context;
12use wasmtime::component::Resource;
13use wasmtime_wasi_io::streams::{DynInputStream, DynOutputStream};
14
15mod sync;
16
17impl<T> preopens::Host for WasiImpl<T>
18where
19    T: WasiView,
20{
21    fn get_directories(
22        &mut self,
23    ) -> Result<Vec<(Resource<types::Descriptor>, String)>, anyhow::Error> {
24        let mut results = Vec::new();
25        for (dir, name) in self.ctx().preopens.clone() {
26            let fd = self
27                .table()
28                .push(Descriptor::Dir(dir))
29                .with_context(|| format!("failed to push preopen {name}"))?;
30            results.push((fd, name));
31        }
32        Ok(results)
33    }
34}
35
36impl<T> types::Host for WasiImpl<T>
37where
38    T: WasiView,
39{
40    fn convert_error_code(&mut self, err: FsError) -> anyhow::Result<ErrorCode> {
41        err.downcast()
42    }
43
44    fn filesystem_error_code(
45        &mut self,
46        err: Resource<anyhow::Error>,
47    ) -> anyhow::Result<Option<ErrorCode>> {
48        let err = self.table().get(&err)?;
49
50        // Currently `err` always comes from the stream implementation which
51        // uses standard reads/writes so only check for `std::io::Error` here.
52        if let Some(err) = err.downcast_ref::<std::io::Error>() {
53            return Ok(Some(ErrorCode::from(err)));
54        }
55
56        Ok(None)
57    }
58}
59
60impl<T> HostDescriptor for WasiImpl<T>
61where
62    T: WasiView,
63{
64    async fn advise(
65        &mut self,
66        fd: Resource<types::Descriptor>,
67        offset: types::Filesize,
68        len: types::Filesize,
69        advice: types::Advice,
70    ) -> FsResult<()> {
71        use system_interface::fs::{Advice as A, FileIoExt};
72        use types::Advice;
73
74        let advice = match advice {
75            Advice::Normal => A::Normal,
76            Advice::Sequential => A::Sequential,
77            Advice::Random => A::Random,
78            Advice::WillNeed => A::WillNeed,
79            Advice::DontNeed => A::DontNeed,
80            Advice::NoReuse => A::NoReuse,
81        };
82
83        let f = self.table().get(&fd)?.file()?;
84        f.run_blocking(move |f| f.advise(offset, len, advice))
85            .await?;
86        Ok(())
87    }
88
89    async fn sync_data(&mut self, fd: Resource<types::Descriptor>) -> FsResult<()> {
90        let descriptor = self.table().get(&fd)?;
91
92        match descriptor {
93            Descriptor::File(f) => {
94                match f.run_blocking(|f| f.sync_data()).await {
95                    Ok(()) => Ok(()),
96                    // On windows, `sync_data` uses `FileFlushBuffers` which fails with
97                    // `ERROR_ACCESS_DENIED` if the file is not upen for writing. Ignore
98                    // this error, for POSIX compatibility.
99                    #[cfg(windows)]
100                    Err(e)
101                        if e.raw_os_error()
102                            == Some(windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED as _) =>
103                    {
104                        Ok(())
105                    }
106                    Err(e) => Err(e.into()),
107                }
108            }
109            Descriptor::Dir(d) => {
110                d.run_blocking(|d| Ok(d.open(std::path::Component::CurDir)?.sync_data()?))
111                    .await
112            }
113        }
114    }
115
116    async fn get_flags(
117        &mut self,
118        fd: Resource<types::Descriptor>,
119    ) -> FsResult<types::DescriptorFlags> {
120        use system_interface::fs::{FdFlags, GetSetFdFlags};
121        use types::DescriptorFlags;
122
123        fn get_from_fdflags(flags: FdFlags) -> DescriptorFlags {
124            let mut out = DescriptorFlags::empty();
125            if flags.contains(FdFlags::DSYNC) {
126                out |= DescriptorFlags::REQUESTED_WRITE_SYNC;
127            }
128            if flags.contains(FdFlags::RSYNC) {
129                out |= DescriptorFlags::DATA_INTEGRITY_SYNC;
130            }
131            if flags.contains(FdFlags::SYNC) {
132                out |= DescriptorFlags::FILE_INTEGRITY_SYNC;
133            }
134            out
135        }
136
137        let descriptor = self.table().get(&fd)?;
138        match descriptor {
139            Descriptor::File(f) => {
140                let flags = f.run_blocking(|f| f.get_fd_flags()).await?;
141                let mut flags = get_from_fdflags(flags);
142                if f.open_mode.contains(OpenMode::READ) {
143                    flags |= DescriptorFlags::READ;
144                }
145                if f.open_mode.contains(OpenMode::WRITE) {
146                    flags |= DescriptorFlags::WRITE;
147                }
148                Ok(flags)
149            }
150            Descriptor::Dir(d) => {
151                let flags = d.run_blocking(|d| d.get_fd_flags()).await?;
152                let mut flags = get_from_fdflags(flags);
153                if d.open_mode.contains(OpenMode::READ) {
154                    flags |= DescriptorFlags::READ;
155                }
156                if d.open_mode.contains(OpenMode::WRITE) {
157                    flags |= DescriptorFlags::MUTATE_DIRECTORY;
158                }
159                Ok(flags)
160            }
161        }
162    }
163
164    async fn get_type(
165        &mut self,
166        fd: Resource<types::Descriptor>,
167    ) -> FsResult<types::DescriptorType> {
168        let descriptor = self.table().get(&fd)?;
169
170        match descriptor {
171            Descriptor::File(f) => {
172                let meta = f.run_blocking(|f| f.metadata()).await?;
173                Ok(descriptortype_from(meta.file_type()))
174            }
175            Descriptor::Dir(_) => Ok(types::DescriptorType::Directory),
176        }
177    }
178
179    async fn set_size(
180        &mut self,
181        fd: Resource<types::Descriptor>,
182        size: types::Filesize,
183    ) -> FsResult<()> {
184        let f = self.table().get(&fd)?.file()?;
185        if !f.perms.contains(FilePerms::WRITE) {
186            Err(ErrorCode::NotPermitted)?;
187        }
188        f.run_blocking(move |f| f.set_len(size)).await?;
189        Ok(())
190    }
191
192    async fn set_times(
193        &mut self,
194        fd: Resource<types::Descriptor>,
195        atim: types::NewTimestamp,
196        mtim: types::NewTimestamp,
197    ) -> FsResult<()> {
198        use fs_set_times::SetTimes;
199
200        let descriptor = self.table().get(&fd)?;
201        match descriptor {
202            Descriptor::File(f) => {
203                if !f.perms.contains(FilePerms::WRITE) {
204                    return Err(ErrorCode::NotPermitted.into());
205                }
206                let atim = systemtimespec_from(atim)?;
207                let mtim = systemtimespec_from(mtim)?;
208                f.run_blocking(|f| f.set_times(atim, mtim)).await?;
209                Ok(())
210            }
211            Descriptor::Dir(d) => {
212                if !d.perms.contains(DirPerms::MUTATE) {
213                    return Err(ErrorCode::NotPermitted.into());
214                }
215                let atim = systemtimespec_from(atim)?;
216                let mtim = systemtimespec_from(mtim)?;
217                d.run_blocking(|d| d.set_times(atim, mtim)).await?;
218                Ok(())
219            }
220        }
221    }
222
223    async fn read(
224        &mut self,
225        fd: Resource<types::Descriptor>,
226        len: types::Filesize,
227        offset: types::Filesize,
228    ) -> FsResult<(Vec<u8>, bool)> {
229        use std::io::IoSliceMut;
230        use system_interface::fs::FileIoExt;
231
232        let table = self.table();
233
234        let f = table.get(&fd)?.file()?;
235        if !f.perms.contains(FilePerms::READ) {
236            return Err(ErrorCode::NotPermitted.into());
237        }
238
239        let (mut buffer, r) = f
240            .run_blocking(move |f| {
241                let mut buffer = vec![0; len.try_into().unwrap_or(usize::MAX)];
242                let r = f.read_vectored_at(&mut [IoSliceMut::new(&mut buffer)], offset);
243                (buffer, r)
244            })
245            .await;
246
247        let (bytes_read, state) = match r? {
248            0 => (0, true),
249            n => (n, false),
250        };
251
252        buffer.truncate(
253            bytes_read
254                .try_into()
255                .expect("bytes read into memory as u64 fits in usize"),
256        );
257
258        Ok((buffer, state))
259    }
260
261    async fn write(
262        &mut self,
263        fd: Resource<types::Descriptor>,
264        buf: Vec<u8>,
265        offset: types::Filesize,
266    ) -> FsResult<types::Filesize> {
267        use std::io::IoSlice;
268        use system_interface::fs::FileIoExt;
269
270        let table = self.table();
271        let f = table.get(&fd)?.file()?;
272        if !f.perms.contains(FilePerms::WRITE) {
273            return Err(ErrorCode::NotPermitted.into());
274        }
275
276        let bytes_written = f
277            .run_blocking(move |f| f.write_vectored_at(&[IoSlice::new(&buf)], offset))
278            .await?;
279
280        Ok(types::Filesize::try_from(bytes_written).expect("usize fits in Filesize"))
281    }
282
283    async fn read_directory(
284        &mut self,
285        fd: Resource<types::Descriptor>,
286    ) -> FsResult<Resource<types::DirectoryEntryStream>> {
287        let table = self.table();
288        let d = table.get(&fd)?.dir()?;
289        if !d.perms.contains(DirPerms::READ) {
290            return Err(ErrorCode::NotPermitted.into());
291        }
292
293        enum ReaddirError {
294            Io(std::io::Error),
295            IllegalSequence,
296        }
297        impl From<std::io::Error> for ReaddirError {
298            fn from(e: std::io::Error) -> ReaddirError {
299                ReaddirError::Io(e)
300            }
301        }
302
303        let entries = d
304            .run_blocking(|d| {
305                // Both `entries` and `metadata` perform syscalls, which is why they are done
306                // within this `block` call, rather than delay calculating the metadata
307                // for entries when they're demanded later in the iterator chain.
308                Ok::<_, std::io::Error>(
309                    d.entries()?
310                        .map(|entry| {
311                            let entry = entry?;
312                            let meta = entry.metadata()?;
313                            let type_ = descriptortype_from(meta.file_type());
314                            let name = entry
315                                .file_name()
316                                .into_string()
317                                .map_err(|_| ReaddirError::IllegalSequence)?;
318                            Ok(types::DirectoryEntry { type_, name })
319                        })
320                        .collect::<Vec<Result<types::DirectoryEntry, ReaddirError>>>(),
321                )
322            })
323            .await?
324            .into_iter();
325
326        // On windows, filter out files like `C:\DumpStack.log.tmp` which we
327        // can't get full metadata for.
328        #[cfg(windows)]
329        let entries = entries.filter(|entry| {
330            use windows_sys::Win32::Foundation::{ERROR_ACCESS_DENIED, ERROR_SHARING_VIOLATION};
331            if let Err(ReaddirError::Io(err)) = entry {
332                if err.raw_os_error() == Some(ERROR_SHARING_VIOLATION as i32)
333                    || err.raw_os_error() == Some(ERROR_ACCESS_DENIED as i32)
334                {
335                    return false;
336                }
337            }
338            true
339        });
340        let entries = entries.map(|r| match r {
341            Ok(r) => Ok(r),
342            Err(ReaddirError::Io(e)) => Err(e.into()),
343            Err(ReaddirError::IllegalSequence) => Err(ErrorCode::IllegalByteSequence.into()),
344        });
345        Ok(table.push(ReaddirIterator::new(entries))?)
346    }
347
348    async fn sync(&mut self, fd: Resource<types::Descriptor>) -> FsResult<()> {
349        let descriptor = self.table().get(&fd)?;
350
351        match descriptor {
352            Descriptor::File(f) => {
353                match f.run_blocking(|f| f.sync_all()).await {
354                    Ok(()) => Ok(()),
355                    // On windows, `sync_data` uses `FileFlushBuffers` which fails with
356                    // `ERROR_ACCESS_DENIED` if the file is not upen for writing. Ignore
357                    // this error, for POSIX compatibility.
358                    #[cfg(windows)]
359                    Err(e)
360                        if e.raw_os_error()
361                            == Some(windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED as _) =>
362                    {
363                        Ok(())
364                    }
365                    Err(e) => Err(e.into()),
366                }
367            }
368            Descriptor::Dir(d) => {
369                d.run_blocking(|d| Ok(d.open(std::path::Component::CurDir)?.sync_all()?))
370                    .await
371            }
372        }
373    }
374
375    async fn create_directory_at(
376        &mut self,
377        fd: Resource<types::Descriptor>,
378        path: String,
379    ) -> FsResult<()> {
380        let table = self.table();
381        let d = table.get(&fd)?.dir()?;
382        if !d.perms.contains(DirPerms::MUTATE) {
383            return Err(ErrorCode::NotPermitted.into());
384        }
385        d.run_blocking(move |d| d.create_dir(&path)).await?;
386        Ok(())
387    }
388
389    async fn stat(&mut self, fd: Resource<types::Descriptor>) -> FsResult<types::DescriptorStat> {
390        let descriptor = self.table().get(&fd)?;
391        match descriptor {
392            Descriptor::File(f) => {
393                // No permissions check on stat: if opened, allowed to stat it
394                let meta = f.run_blocking(|f| f.metadata()).await?;
395                Ok(descriptorstat_from(meta))
396            }
397            Descriptor::Dir(d) => {
398                // No permissions check on stat: if opened, allowed to stat it
399                let meta = d.run_blocking(|d| d.dir_metadata()).await?;
400                Ok(descriptorstat_from(meta))
401            }
402        }
403    }
404
405    async fn stat_at(
406        &mut self,
407        fd: Resource<types::Descriptor>,
408        path_flags: types::PathFlags,
409        path: String,
410    ) -> FsResult<types::DescriptorStat> {
411        let table = self.table();
412        let d = table.get(&fd)?.dir()?;
413        if !d.perms.contains(DirPerms::READ) {
414            return Err(ErrorCode::NotPermitted.into());
415        }
416
417        let meta = if symlink_follow(path_flags) {
418            d.run_blocking(move |d| d.metadata(&path)).await?
419        } else {
420            d.run_blocking(move |d| d.symlink_metadata(&path)).await?
421        };
422        Ok(descriptorstat_from(meta))
423    }
424
425    async fn set_times_at(
426        &mut self,
427        fd: Resource<types::Descriptor>,
428        path_flags: types::PathFlags,
429        path: String,
430        atim: types::NewTimestamp,
431        mtim: types::NewTimestamp,
432    ) -> FsResult<()> {
433        use cap_fs_ext::DirExt;
434
435        let table = self.table();
436        let d = table.get(&fd)?.dir()?;
437        if !d.perms.contains(DirPerms::MUTATE) {
438            return Err(ErrorCode::NotPermitted.into());
439        }
440        let atim = systemtimespec_from(atim)?;
441        let mtim = systemtimespec_from(mtim)?;
442        if symlink_follow(path_flags) {
443            d.run_blocking(move |d| {
444                d.set_times(
445                    &path,
446                    atim.map(cap_fs_ext::SystemTimeSpec::from_std),
447                    mtim.map(cap_fs_ext::SystemTimeSpec::from_std),
448                )
449            })
450            .await?;
451        } else {
452            d.run_blocking(move |d| {
453                d.set_symlink_times(
454                    &path,
455                    atim.map(cap_fs_ext::SystemTimeSpec::from_std),
456                    mtim.map(cap_fs_ext::SystemTimeSpec::from_std),
457                )
458            })
459            .await?;
460        }
461        Ok(())
462    }
463
464    async fn link_at(
465        &mut self,
466        fd: Resource<types::Descriptor>,
467        // TODO delete the path flags from this function
468        old_path_flags: types::PathFlags,
469        old_path: String,
470        new_descriptor: Resource<types::Descriptor>,
471        new_path: String,
472    ) -> FsResult<()> {
473        let table = self.table();
474        let old_dir = table.get(&fd)?.dir()?;
475        if !old_dir.perms.contains(DirPerms::MUTATE) {
476            return Err(ErrorCode::NotPermitted.into());
477        }
478        let new_dir = table.get(&new_descriptor)?.dir()?;
479        if !new_dir.perms.contains(DirPerms::MUTATE) {
480            return Err(ErrorCode::NotPermitted.into());
481        }
482        if symlink_follow(old_path_flags) {
483            return Err(ErrorCode::Invalid.into());
484        }
485        let new_dir_handle = std::sync::Arc::clone(&new_dir.dir);
486        old_dir
487            .run_blocking(move |d| d.hard_link(&old_path, &new_dir_handle, &new_path))
488            .await?;
489        Ok(())
490    }
491
492    async fn open_at(
493        &mut self,
494        fd: Resource<types::Descriptor>,
495        path_flags: types::PathFlags,
496        path: String,
497        oflags: types::OpenFlags,
498        flags: types::DescriptorFlags,
499    ) -> FsResult<Resource<types::Descriptor>> {
500        use cap_fs_ext::{FollowSymlinks, OpenOptionsFollowExt, OpenOptionsMaybeDirExt};
501        use system_interface::fs::{FdFlags, GetSetFdFlags};
502        use types::{DescriptorFlags, OpenFlags};
503
504        let allow_blocking_current_thread = self.ctx().allow_blocking_current_thread;
505        let table = self.table();
506        let d = table.get(&fd)?.dir()?;
507        if !d.perms.contains(DirPerms::READ) {
508            Err(ErrorCode::NotPermitted)?;
509        }
510
511        if !d.perms.contains(DirPerms::MUTATE) {
512            if oflags.contains(OpenFlags::CREATE) || oflags.contains(OpenFlags::TRUNCATE) {
513                Err(ErrorCode::NotPermitted)?;
514            }
515            if flags.contains(DescriptorFlags::WRITE) {
516                Err(ErrorCode::NotPermitted)?;
517            }
518        }
519
520        // Track whether we are creating file, for permission check:
521        let mut create = false;
522        // Track open mode, for permission check and recording in created descriptor:
523        let mut open_mode = OpenMode::empty();
524        // Construct the OpenOptions to give the OS:
525        let mut opts = cap_std::fs::OpenOptions::new();
526        opts.maybe_dir(true);
527
528        if oflags.contains(OpenFlags::CREATE) {
529            if oflags.contains(OpenFlags::EXCLUSIVE) {
530                opts.create_new(true);
531            } else {
532                opts.create(true);
533            }
534            create = true;
535            opts.write(true);
536            open_mode |= OpenMode::WRITE;
537        }
538
539        if oflags.contains(OpenFlags::TRUNCATE) {
540            opts.truncate(true).write(true);
541        }
542        if flags.contains(DescriptorFlags::READ) {
543            opts.read(true);
544            open_mode |= OpenMode::READ;
545        }
546        if flags.contains(DescriptorFlags::WRITE) {
547            opts.write(true);
548            open_mode |= OpenMode::WRITE;
549        } else {
550            // If not opened write, open read. This way the OS lets us open
551            // the file, but we can use perms to reject use of the file later.
552            opts.read(true);
553            open_mode |= OpenMode::READ;
554        }
555        if symlink_follow(path_flags) {
556            opts.follow(FollowSymlinks::Yes);
557        } else {
558            opts.follow(FollowSymlinks::No);
559        }
560
561        // These flags are not yet supported in cap-std:
562        if flags.contains(DescriptorFlags::FILE_INTEGRITY_SYNC)
563            || flags.contains(DescriptorFlags::DATA_INTEGRITY_SYNC)
564            || flags.contains(DescriptorFlags::REQUESTED_WRITE_SYNC)
565        {
566            Err(ErrorCode::Unsupported)?;
567        }
568
569        if oflags.contains(OpenFlags::DIRECTORY) {
570            if oflags.contains(OpenFlags::CREATE)
571                || oflags.contains(OpenFlags::EXCLUSIVE)
572                || oflags.contains(OpenFlags::TRUNCATE)
573            {
574                Err(ErrorCode::Invalid)?;
575            }
576        }
577
578        // Now enforce this WasiCtx's permissions before letting the OS have
579        // its shot:
580        if !d.perms.contains(DirPerms::MUTATE) && create {
581            Err(ErrorCode::NotPermitted)?;
582        }
583        if !d.file_perms.contains(FilePerms::WRITE) && open_mode.contains(OpenMode::WRITE) {
584            Err(ErrorCode::NotPermitted)?;
585        }
586
587        // Represents each possible outcome from the spawn_blocking operation.
588        // This makes sure we don't have to give spawn_blocking any way to
589        // manipulate the table.
590        enum OpenResult {
591            Dir(cap_std::fs::Dir),
592            File(cap_std::fs::File),
593            NotDir,
594        }
595
596        let opened = d
597            .run_blocking::<_, std::io::Result<OpenResult>>(move |d| {
598                let mut opened = d.open_with(&path, &opts)?;
599                if opened.metadata()?.is_dir() {
600                    Ok(OpenResult::Dir(cap_std::fs::Dir::from_std_file(
601                        opened.into_std(),
602                    )))
603                } else if oflags.contains(OpenFlags::DIRECTORY) {
604                    Ok(OpenResult::NotDir)
605                } else {
606                    // FIXME cap-std needs a nonblocking open option so that files reads and writes
607                    // are nonblocking. Instead we set it after opening here:
608                    let set_fd_flags = opened.new_set_fd_flags(FdFlags::NONBLOCK)?;
609                    opened.set_fd_flags(set_fd_flags)?;
610                    Ok(OpenResult::File(opened))
611                }
612            })
613            .await?;
614
615        match opened {
616            OpenResult::Dir(dir) => Ok(table.push(Descriptor::Dir(Dir::new(
617                dir,
618                d.perms,
619                d.file_perms,
620                open_mode,
621                allow_blocking_current_thread,
622            )))?),
623
624            OpenResult::File(file) => Ok(table.push(Descriptor::File(File::new(
625                file,
626                d.file_perms,
627                open_mode,
628                allow_blocking_current_thread,
629            )))?),
630
631            OpenResult::NotDir => Err(ErrorCode::NotDirectory.into()),
632        }
633    }
634
635    fn drop(&mut self, fd: Resource<types::Descriptor>) -> anyhow::Result<()> {
636        let table = self.table();
637
638        // The Drop will close the file/dir, but if the close syscall
639        // blocks the thread, I will face god and walk backwards into hell.
640        // tokio::fs::File just uses std::fs::File's Drop impl to close, so
641        // it doesn't appear anyone else has found this to be a problem.
642        // (Not that they could solve it without async drop...)
643        table.delete(fd)?;
644
645        Ok(())
646    }
647
648    async fn readlink_at(
649        &mut self,
650        fd: Resource<types::Descriptor>,
651        path: String,
652    ) -> FsResult<String> {
653        let table = self.table();
654        let d = table.get(&fd)?.dir()?;
655        if !d.perms.contains(DirPerms::READ) {
656            return Err(ErrorCode::NotPermitted.into());
657        }
658        let link = d.run_blocking(move |d| d.read_link(&path)).await?;
659        Ok(link
660            .into_os_string()
661            .into_string()
662            .map_err(|_| ErrorCode::IllegalByteSequence)?)
663    }
664
665    async fn remove_directory_at(
666        &mut self,
667        fd: Resource<types::Descriptor>,
668        path: String,
669    ) -> FsResult<()> {
670        let table = self.table();
671        let d = table.get(&fd)?.dir()?;
672        if !d.perms.contains(DirPerms::MUTATE) {
673            return Err(ErrorCode::NotPermitted.into());
674        }
675        Ok(d.run_blocking(move |d| d.remove_dir(&path)).await?)
676    }
677
678    async fn rename_at(
679        &mut self,
680        fd: Resource<types::Descriptor>,
681        old_path: String,
682        new_fd: Resource<types::Descriptor>,
683        new_path: String,
684    ) -> FsResult<()> {
685        let table = self.table();
686        let old_dir = table.get(&fd)?.dir()?;
687        if !old_dir.perms.contains(DirPerms::MUTATE) {
688            return Err(ErrorCode::NotPermitted.into());
689        }
690        let new_dir = table.get(&new_fd)?.dir()?;
691        if !new_dir.perms.contains(DirPerms::MUTATE) {
692            return Err(ErrorCode::NotPermitted.into());
693        }
694        let new_dir_handle = std::sync::Arc::clone(&new_dir.dir);
695        Ok(old_dir
696            .run_blocking(move |d| d.rename(&old_path, &new_dir_handle, &new_path))
697            .await?)
698    }
699
700    async fn symlink_at(
701        &mut self,
702        fd: Resource<types::Descriptor>,
703        src_path: String,
704        dest_path: String,
705    ) -> FsResult<()> {
706        // On windows, Dir.symlink is provided by DirExt
707        #[cfg(windows)]
708        use cap_fs_ext::DirExt;
709
710        let table = self.table();
711        let d = table.get(&fd)?.dir()?;
712        if !d.perms.contains(DirPerms::MUTATE) {
713            return Err(ErrorCode::NotPermitted.into());
714        }
715        Ok(d.run_blocking(move |d| d.symlink(&src_path, &dest_path))
716            .await?)
717    }
718
719    async fn unlink_file_at(
720        &mut self,
721        fd: Resource<types::Descriptor>,
722        path: String,
723    ) -> FsResult<()> {
724        use cap_fs_ext::DirExt;
725
726        let table = self.table();
727        let d = table.get(&fd)?.dir()?;
728        if !d.perms.contains(DirPerms::MUTATE) {
729            return Err(ErrorCode::NotPermitted.into());
730        }
731        Ok(d.run_blocking(move |d| d.remove_file_or_symlink(&path))
732            .await?)
733    }
734
735    fn read_via_stream(
736        &mut self,
737        fd: Resource<types::Descriptor>,
738        offset: types::Filesize,
739    ) -> FsResult<Resource<DynInputStream>> {
740        // Trap if fd lookup fails:
741        let f = self.table().get(&fd)?.file()?;
742
743        if !f.perms.contains(FilePerms::READ) {
744            Err(types::ErrorCode::BadDescriptor)?;
745        }
746
747        // Create a stream view for it.
748        let reader: DynInputStream = Box::new(FileInputStream::new(f, offset));
749
750        // Insert the stream view into the table. Trap if the table is full.
751        let index = self.table().push(reader)?;
752
753        Ok(index)
754    }
755
756    fn write_via_stream(
757        &mut self,
758        fd: Resource<types::Descriptor>,
759        offset: types::Filesize,
760    ) -> FsResult<Resource<DynOutputStream>> {
761        // Trap if fd lookup fails:
762        let f = self.table().get(&fd)?.file()?;
763
764        if !f.perms.contains(FilePerms::WRITE) {
765            Err(types::ErrorCode::BadDescriptor)?;
766        }
767
768        // Create a stream view for it.
769        let writer = FileOutputStream::write_at(f, offset);
770        let writer: DynOutputStream = Box::new(writer);
771
772        // Insert the stream view into the table. Trap if the table is full.
773        let index = self.table().push(writer)?;
774
775        Ok(index)
776    }
777
778    fn append_via_stream(
779        &mut self,
780        fd: Resource<types::Descriptor>,
781    ) -> FsResult<Resource<DynOutputStream>> {
782        // Trap if fd lookup fails:
783        let f = self.table().get(&fd)?.file()?;
784
785        if !f.perms.contains(FilePerms::WRITE) {
786            Err(types::ErrorCode::BadDescriptor)?;
787        }
788
789        // Create a stream view for it.
790        let appender = FileOutputStream::append(f);
791        let appender: DynOutputStream = Box::new(appender);
792
793        // Insert the stream view into the table. Trap if the table is full.
794        let index = self.table().push(appender)?;
795
796        Ok(index)
797    }
798
799    async fn is_same_object(
800        &mut self,
801        a: Resource<types::Descriptor>,
802        b: Resource<types::Descriptor>,
803    ) -> anyhow::Result<bool> {
804        use cap_fs_ext::MetadataExt;
805        let descriptor_a = self.table().get(&a)?;
806        let meta_a = get_descriptor_metadata(descriptor_a).await?;
807        let descriptor_b = self.table().get(&b)?;
808        let meta_b = get_descriptor_metadata(descriptor_b).await?;
809        if meta_a.dev() == meta_b.dev() && meta_a.ino() == meta_b.ino() {
810            // MetadataHashValue does not derive eq, so use a pair of
811            // comparisons to check equality:
812            debug_assert_eq!(
813                calculate_metadata_hash(&meta_a).upper,
814                calculate_metadata_hash(&meta_b).upper
815            );
816            debug_assert_eq!(
817                calculate_metadata_hash(&meta_a).lower,
818                calculate_metadata_hash(&meta_b).lower
819            );
820            Ok(true)
821        } else {
822            // Hash collisions are possible, so don't assert the negative here
823            Ok(false)
824        }
825    }
826    async fn metadata_hash(
827        &mut self,
828        fd: Resource<types::Descriptor>,
829    ) -> FsResult<types::MetadataHashValue> {
830        let descriptor_a = self.table().get(&fd)?;
831        let meta = get_descriptor_metadata(descriptor_a).await?;
832        Ok(calculate_metadata_hash(&meta))
833    }
834    async fn metadata_hash_at(
835        &mut self,
836        fd: Resource<types::Descriptor>,
837        path_flags: types::PathFlags,
838        path: String,
839    ) -> FsResult<types::MetadataHashValue> {
840        let table = self.table();
841        let d = table.get(&fd)?.dir()?;
842        // No permissions check on metadata: if dir opened, allowed to stat it
843        let meta = d
844            .run_blocking(move |d| {
845                if symlink_follow(path_flags) {
846                    d.metadata(path)
847                } else {
848                    d.symlink_metadata(path)
849                }
850            })
851            .await?;
852        Ok(calculate_metadata_hash(&meta))
853    }
854}
855
856impl<T> HostDirectoryEntryStream for WasiImpl<T>
857where
858    T: WasiView,
859{
860    async fn read_directory_entry(
861        &mut self,
862        stream: Resource<types::DirectoryEntryStream>,
863    ) -> FsResult<Option<types::DirectoryEntry>> {
864        let table = self.table();
865        let readdir = table.get(&stream)?;
866        readdir.next()
867    }
868
869    fn drop(&mut self, stream: Resource<types::DirectoryEntryStream>) -> anyhow::Result<()> {
870        self.table().delete(stream)?;
871        Ok(())
872    }
873}
874
875async fn get_descriptor_metadata(fd: &types::Descriptor) -> FsResult<cap_std::fs::Metadata> {
876    match fd {
877        Descriptor::File(f) => {
878            // No permissions check on metadata: if opened, allowed to stat it
879            Ok(f.run_blocking(|f| f.metadata()).await?)
880        }
881        Descriptor::Dir(d) => {
882            // No permissions check on metadata: if opened, allowed to stat it
883            Ok(d.run_blocking(|d| d.dir_metadata()).await?)
884        }
885    }
886}
887
888fn calculate_metadata_hash(meta: &cap_std::fs::Metadata) -> types::MetadataHashValue {
889    use cap_fs_ext::MetadataExt;
890    // Without incurring any deps, std provides us with a 64 bit hash
891    // function:
892    use std::hash::Hasher;
893    // Note that this means that the metadata hash (which becomes a preview1 ino) may
894    // change when a different rustc release is used to build this host implementation:
895    let mut hasher = std::collections::hash_map::DefaultHasher::new();
896    hasher.write_u64(meta.dev());
897    hasher.write_u64(meta.ino());
898    let lower = hasher.finish();
899    // MetadataHashValue has a pair of 64-bit members for representing a
900    // single 128-bit number. However, we only have 64 bits of entropy. To
901    // synthesize the upper 64 bits, lets xor the lower half with an arbitrary
902    // constant, in this case the 64 bit integer corresponding to the IEEE
903    // double representation of (a number as close as possible to) pi.
904    // This seems better than just repeating the same bits in the upper and
905    // lower parts outright, which could make folks wonder if the struct was
906    // mangled in the ABI, or worse yet, lead to consumers of this interface
907    // expecting them to be equal.
908    let upper = lower ^ 4614256656552045848u64;
909    types::MetadataHashValue { lower, upper }
910}
911
912#[cfg(unix)]
913fn from_raw_os_error(err: Option<i32>) -> Option<ErrorCode> {
914    use rustix::io::Errno as RustixErrno;
915    if err.is_none() {
916        return None;
917    }
918    Some(match RustixErrno::from_raw_os_error(err.unwrap()) {
919        RustixErrno::PIPE => ErrorCode::Pipe,
920        RustixErrno::PERM => ErrorCode::NotPermitted,
921        RustixErrno::NOENT => ErrorCode::NoEntry,
922        RustixErrno::NOMEM => ErrorCode::InsufficientMemory,
923        RustixErrno::IO => ErrorCode::Io,
924        RustixErrno::BADF => ErrorCode::BadDescriptor,
925        RustixErrno::BUSY => ErrorCode::Busy,
926        RustixErrno::ACCESS => ErrorCode::Access,
927        RustixErrno::NOTDIR => ErrorCode::NotDirectory,
928        RustixErrno::ISDIR => ErrorCode::IsDirectory,
929        RustixErrno::INVAL => ErrorCode::Invalid,
930        RustixErrno::EXIST => ErrorCode::Exist,
931        RustixErrno::FBIG => ErrorCode::FileTooLarge,
932        RustixErrno::NOSPC => ErrorCode::InsufficientSpace,
933        RustixErrno::SPIPE => ErrorCode::InvalidSeek,
934        RustixErrno::MLINK => ErrorCode::TooManyLinks,
935        RustixErrno::NAMETOOLONG => ErrorCode::NameTooLong,
936        RustixErrno::NOTEMPTY => ErrorCode::NotEmpty,
937        RustixErrno::LOOP => ErrorCode::Loop,
938        RustixErrno::OVERFLOW => ErrorCode::Overflow,
939        RustixErrno::ILSEQ => ErrorCode::IllegalByteSequence,
940        RustixErrno::NOTSUP => ErrorCode::Unsupported,
941        RustixErrno::ALREADY => ErrorCode::Already,
942        RustixErrno::INPROGRESS => ErrorCode::InProgress,
943        RustixErrno::INTR => ErrorCode::Interrupted,
944
945        // On some platforms, these have the same value as other errno values.
946        #[allow(unreachable_patterns)]
947        RustixErrno::OPNOTSUPP => ErrorCode::Unsupported,
948
949        _ => return None,
950    })
951}
952#[cfg(windows)]
953fn from_raw_os_error(raw_os_error: Option<i32>) -> Option<ErrorCode> {
954    use windows_sys::Win32::Foundation;
955    Some(match raw_os_error.map(|code| code as u32) {
956        Some(Foundation::ERROR_FILE_NOT_FOUND) => ErrorCode::NoEntry,
957        Some(Foundation::ERROR_PATH_NOT_FOUND) => ErrorCode::NoEntry,
958        Some(Foundation::ERROR_ACCESS_DENIED) => ErrorCode::Access,
959        Some(Foundation::ERROR_SHARING_VIOLATION) => ErrorCode::Access,
960        Some(Foundation::ERROR_PRIVILEGE_NOT_HELD) => ErrorCode::NotPermitted,
961        Some(Foundation::ERROR_INVALID_HANDLE) => ErrorCode::BadDescriptor,
962        Some(Foundation::ERROR_INVALID_NAME) => ErrorCode::NoEntry,
963        Some(Foundation::ERROR_NOT_ENOUGH_MEMORY) => ErrorCode::InsufficientMemory,
964        Some(Foundation::ERROR_OUTOFMEMORY) => ErrorCode::InsufficientMemory,
965        Some(Foundation::ERROR_DIR_NOT_EMPTY) => ErrorCode::NotEmpty,
966        Some(Foundation::ERROR_NOT_READY) => ErrorCode::Busy,
967        Some(Foundation::ERROR_BUSY) => ErrorCode::Busy,
968        Some(Foundation::ERROR_NOT_SUPPORTED) => ErrorCode::Unsupported,
969        Some(Foundation::ERROR_FILE_EXISTS) => ErrorCode::Exist,
970        Some(Foundation::ERROR_BROKEN_PIPE) => ErrorCode::Pipe,
971        Some(Foundation::ERROR_BUFFER_OVERFLOW) => ErrorCode::NameTooLong,
972        Some(Foundation::ERROR_NOT_A_REPARSE_POINT) => ErrorCode::Invalid,
973        Some(Foundation::ERROR_NEGATIVE_SEEK) => ErrorCode::Invalid,
974        Some(Foundation::ERROR_DIRECTORY) => ErrorCode::NotDirectory,
975        Some(Foundation::ERROR_ALREADY_EXISTS) => ErrorCode::Exist,
976        Some(Foundation::ERROR_STOPPED_ON_SYMLINK) => ErrorCode::Loop,
977        Some(Foundation::ERROR_DIRECTORY_NOT_SUPPORTED) => ErrorCode::IsDirectory,
978        _ => return None,
979    })
980}
981
982impl From<std::io::Error> for ErrorCode {
983    fn from(err: std::io::Error) -> ErrorCode {
984        ErrorCode::from(&err)
985    }
986}
987
988impl<'a> From<&'a std::io::Error> for ErrorCode {
989    fn from(err: &'a std::io::Error) -> ErrorCode {
990        match from_raw_os_error(err.raw_os_error()) {
991            Some(errno) => errno,
992            None => {
993                tracing::debug!("unknown raw os error: {err}");
994                match err.kind() {
995                    std::io::ErrorKind::NotFound => ErrorCode::NoEntry,
996                    std::io::ErrorKind::PermissionDenied => ErrorCode::NotPermitted,
997                    std::io::ErrorKind::AlreadyExists => ErrorCode::Exist,
998                    std::io::ErrorKind::InvalidInput => ErrorCode::Invalid,
999                    _ => ErrorCode::Io,
1000                }
1001            }
1002        }
1003    }
1004}
1005
1006impl From<cap_rand::Error> for ErrorCode {
1007    fn from(err: cap_rand::Error) -> ErrorCode {
1008        // I picked Error::Io as a 'reasonable default', FIXME dan is this ok?
1009        from_raw_os_error(err.raw_os_error()).unwrap_or(ErrorCode::Io)
1010    }
1011}
1012
1013impl From<std::num::TryFromIntError> for ErrorCode {
1014    fn from(_err: std::num::TryFromIntError) -> ErrorCode {
1015        ErrorCode::Overflow
1016    }
1017}
1018
1019fn descriptortype_from(ft: cap_std::fs::FileType) -> types::DescriptorType {
1020    use cap_fs_ext::FileTypeExt;
1021    use types::DescriptorType;
1022    if ft.is_dir() {
1023        DescriptorType::Directory
1024    } else if ft.is_symlink() {
1025        DescriptorType::SymbolicLink
1026    } else if ft.is_block_device() {
1027        DescriptorType::BlockDevice
1028    } else if ft.is_char_device() {
1029        DescriptorType::CharacterDevice
1030    } else if ft.is_file() {
1031        DescriptorType::RegularFile
1032    } else {
1033        DescriptorType::Unknown
1034    }
1035}
1036
1037fn systemtimespec_from(t: types::NewTimestamp) -> FsResult<Option<fs_set_times::SystemTimeSpec>> {
1038    use fs_set_times::SystemTimeSpec;
1039    use types::NewTimestamp;
1040    match t {
1041        NewTimestamp::NoChange => Ok(None),
1042        NewTimestamp::Now => Ok(Some(SystemTimeSpec::SymbolicNow)),
1043        NewTimestamp::Timestamp(st) => Ok(Some(SystemTimeSpec::Absolute(systemtime_from(st)?))),
1044    }
1045}
1046
1047fn systemtime_from(t: wall_clock::Datetime) -> FsResult<std::time::SystemTime> {
1048    use std::time::{Duration, SystemTime};
1049    SystemTime::UNIX_EPOCH
1050        .checked_add(Duration::new(t.seconds, t.nanoseconds))
1051        .ok_or_else(|| ErrorCode::Overflow.into())
1052}
1053
1054fn datetime_from(t: std::time::SystemTime) -> wall_clock::Datetime {
1055    // FIXME make this infallible or handle errors properly
1056    wall_clock::Datetime::try_from(cap_std::time::SystemTime::from_std(t)).unwrap()
1057}
1058
1059fn descriptorstat_from(meta: cap_std::fs::Metadata) -> types::DescriptorStat {
1060    use cap_fs_ext::MetadataExt;
1061    types::DescriptorStat {
1062        type_: descriptortype_from(meta.file_type()),
1063        link_count: meta.nlink(),
1064        size: meta.len(),
1065        data_access_timestamp: meta.accessed().map(|t| datetime_from(t.into_std())).ok(),
1066        data_modification_timestamp: meta.modified().map(|t| datetime_from(t.into_std())).ok(),
1067        status_change_timestamp: meta.created().map(|t| datetime_from(t.into_std())).ok(),
1068    }
1069}
1070
1071fn symlink_follow(path_flags: types::PathFlags) -> bool {
1072    path_flags.contains(types::PathFlags::SYMLINK_FOLLOW)
1073}
1074
1075#[cfg(test)]
1076mod test {
1077    use super::*;
1078    use wasmtime::component::ResourceTable;
1079
1080    #[test]
1081    fn table_readdir_works() {
1082        let mut table = ResourceTable::new();
1083        let ix = table
1084            .push(ReaddirIterator::new(std::iter::empty()))
1085            .unwrap();
1086        let _ = table.get(&ix).unwrap();
1087        table.delete(ix).unwrap();
1088    }
1089}