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