wasmtime_wasi/host/
filesystem.rs

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