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 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 #[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 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 #[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 #[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 let meta = f.run_blocking(|f| f.metadata()).await?;
394 Ok(descriptorstat_from(meta))
395 }
396 Descriptor::Dir(d) => {
397 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 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 let mut create = false;
521 let mut open_mode = OpenMode::empty();
523 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 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 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 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 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 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 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 #[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 let f = self.table().get(&fd)?.file()?;
741
742 if !f.perms.contains(FilePerms::READ) {
743 Err(types::ErrorCode::BadDescriptor)?;
744 }
745
746 let reader: DynInputStream = Box::new(FileInputStream::new(f, offset));
748
749 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 let f = self.table().get(&fd)?.file()?;
762
763 if !f.perms.contains(FilePerms::WRITE) {
764 Err(types::ErrorCode::BadDescriptor)?;
765 }
766
767 let writer = FileOutputStream::write_at(f, offset);
769 let writer: DynOutputStream = Box::new(writer);
770
771 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 let f = self.table().get(&fd)?.file()?;
783
784 if !f.perms.contains(FilePerms::WRITE) {
785 Err(types::ErrorCode::BadDescriptor)?;
786 }
787
788 let appender = FileOutputStream::append(f);
790 let appender: DynOutputStream = Box::new(appender);
791
792 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 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 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 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 Ok(f.run_blocking(|f| f.metadata()).await?)
879 }
880 Descriptor::Dir(d) => {
881 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 use std::hash::Hasher;
892 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 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 #[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 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 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}