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