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 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 #[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 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 #[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 #[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 let meta = f.run_blocking(|f| f.metadata()).await?;
391 Ok(descriptorstat_from(meta))
392 }
393 Descriptor::Dir(d) => {
394 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 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 let mut create = false;
518 let mut open_mode = OpenMode::empty();
520 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 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 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 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 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 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 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 #[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 let f = self.table().get(&fd)?.file()?;
738
739 if !f.perms.contains(FilePerms::READ) {
740 Err(types::ErrorCode::BadDescriptor)?;
741 }
742
743 let reader: DynInputStream = Box::new(FileInputStream::new(f, offset));
745
746 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 let f = self.table().get(&fd)?.file()?;
759
760 if !f.perms.contains(FilePerms::WRITE) {
761 Err(types::ErrorCode::BadDescriptor)?;
762 }
763
764 let writer = FileOutputStream::write_at(f, offset);
766 let writer: DynOutputStream = Box::new(writer);
767
768 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 let f = self.table().get(&fd)?.file()?;
780
781 if !f.perms.contains(FilePerms::WRITE) {
782 Err(types::ErrorCode::BadDescriptor)?;
783 }
784
785 let appender = FileOutputStream::append(f);
787 let appender: DynOutputStream = Box::new(appender);
788
789 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 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 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 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 Ok(f.run_blocking(|f| f.metadata()).await?)
876 }
877 Descriptor::Dir(d) => {
878 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 use std::hash::Hasher;
889 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 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 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 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}