1use crate::clocks::Datetime;
2use crate::runtime::{AbortOnDropJoinHandle, spawn_blocking};
3use cap_fs_ext::{FileTypeExt as _, MetadataExt as _};
4use fs_set_times::SystemTimeSpec;
5use std::collections::hash_map;
6use std::sync::Arc;
7use tracing::debug;
8use wasmtime::component::{HasData, Resource, ResourceTable};
9use wasmtime::error::Context as _;
10
11pub struct WasiFilesystem;
50
51impl HasData for WasiFilesystem {
52 type Data<'a> = WasiFilesystemCtxView<'a>;
53}
54
55#[derive(Clone, Default)]
56pub struct WasiFilesystemCtx {
57 pub(crate) allow_blocking_current_thread: bool,
58 pub(crate) preopens: Vec<(Dir, String)>,
59}
60
61pub struct WasiFilesystemCtxView<'a> {
62 pub ctx: &'a mut WasiFilesystemCtx,
63 pub table: &'a mut ResourceTable,
64}
65
66pub trait WasiFilesystemView: Send {
67 fn filesystem(&mut self) -> WasiFilesystemCtxView<'_>;
68}
69
70bitflags::bitflags! {
71 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
72 pub struct FilePerms: usize {
73 const READ = 0b1;
74 const WRITE = 0b10;
75 }
76}
77
78bitflags::bitflags! {
79 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
80 pub struct OpenMode: usize {
81 const READ = 0b1;
82 const WRITE = 0b10;
83 }
84}
85
86bitflags::bitflags! {
87 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
92 pub struct DirPerms: usize {
93 const READ = 0b1;
96
97 const MUTATE = 0b10;
100 }
101}
102
103bitflags::bitflags! {
104 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
106 pub(crate) struct PathFlags: usize {
107 const SYMLINK_FOLLOW = 0b1;
110 }
111}
112
113bitflags::bitflags! {
114 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
116 pub(crate) struct OpenFlags: usize {
117 const CREATE = 0b1;
119 const DIRECTORY = 0b10;
121 const EXCLUSIVE = 0b100;
123 const TRUNCATE = 0b1000;
125 }
126}
127
128bitflags::bitflags! {
129 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
133 pub(crate) struct DescriptorFlags: usize {
134 const READ = 0b1;
136 const WRITE = 0b10;
138 const FILE_INTEGRITY_SYNC = 0b100;
146 const DATA_INTEGRITY_SYNC = 0b1000;
154 const REQUESTED_WRITE_SYNC = 0b10000;
161 const MUTATE_DIRECTORY = 0b100000;
171 }
172}
173
174#[cfg_attr(
179 windows,
180 expect(dead_code, reason = "on Windows, some of these are not used")
181)]
182pub(crate) enum ErrorCode {
183 Access,
185 Already,
187 BadDescriptor,
189 Busy,
191 Exist,
193 FileTooLarge,
195 IllegalByteSequence,
197 InProgress,
199 Interrupted,
201 Invalid,
203 Io,
205 IsDirectory,
207 Loop,
209 TooManyLinks,
211 NameTooLong,
213 NoEntry,
215 InsufficientMemory,
217 InsufficientSpace,
219 NotDirectory,
221 NotEmpty,
223 Unsupported,
225 Overflow,
227 NotPermitted,
229 Pipe,
231 InvalidSeek,
233}
234
235fn datetime_from(t: std::time::SystemTime) -> Datetime {
236 Datetime::try_from(cap_std::time::SystemTime::from_std(t)).unwrap()
238}
239
240pub(crate) enum DescriptorType {
244 Unknown,
247 BlockDevice,
249 CharacterDevice,
251 Directory,
253 SymbolicLink,
255 RegularFile,
257}
258
259impl From<cap_std::fs::FileType> for DescriptorType {
260 fn from(ft: cap_std::fs::FileType) -> Self {
261 if ft.is_dir() {
262 DescriptorType::Directory
263 } else if ft.is_symlink() {
264 DescriptorType::SymbolicLink
265 } else if ft.is_block_device() {
266 DescriptorType::BlockDevice
267 } else if ft.is_char_device() {
268 DescriptorType::CharacterDevice
269 } else if ft.is_file() {
270 DescriptorType::RegularFile
271 } else {
272 DescriptorType::Unknown
273 }
274 }
275}
276
277pub(crate) struct DescriptorStat {
281 pub type_: DescriptorType,
283 pub link_count: u64,
285 pub size: u64,
288 pub data_access_timestamp: Option<Datetime>,
293 pub data_modification_timestamp: Option<Datetime>,
298 pub status_change_timestamp: Option<Datetime>,
303}
304
305impl From<cap_std::fs::Metadata> for DescriptorStat {
306 fn from(meta: cap_std::fs::Metadata) -> Self {
307 Self {
308 type_: meta.file_type().into(),
309 link_count: meta.nlink(),
310 size: meta.len(),
311 data_access_timestamp: meta.accessed().map(|t| datetime_from(t.into_std())).ok(),
312 data_modification_timestamp: meta.modified().map(|t| datetime_from(t.into_std())).ok(),
313 status_change_timestamp: meta.created().map(|t| datetime_from(t.into_std())).ok(),
314 }
315 }
316}
317
318pub(crate) struct MetadataHashValue {
321 pub lower: u64,
323 pub upper: u64,
325}
326
327impl From<&cap_std::fs::Metadata> for MetadataHashValue {
328 fn from(meta: &cap_std::fs::Metadata) -> Self {
329 use cap_fs_ext::MetadataExt;
330 use std::hash::Hasher;
333 let mut hasher = hash_map::DefaultHasher::new();
336 hasher.write_u64(meta.dev());
337 hasher.write_u64(meta.ino());
338 let lower = hasher.finish();
339 let upper = lower ^ 4614256656552045848u64;
349 Self { lower, upper }
350 }
351}
352
353#[cfg(unix)]
354fn from_raw_os_error(err: Option<i32>) -> Option<ErrorCode> {
355 use rustix::io::Errno as RustixErrno;
356 if err.is_none() {
357 return None;
358 }
359 Some(match RustixErrno::from_raw_os_error(err.unwrap()) {
360 RustixErrno::PIPE => ErrorCode::Pipe,
361 RustixErrno::PERM => ErrorCode::NotPermitted,
362 RustixErrno::NOENT => ErrorCode::NoEntry,
363 RustixErrno::NOMEM => ErrorCode::InsufficientMemory,
364 RustixErrno::IO => ErrorCode::Io,
365 RustixErrno::BADF => ErrorCode::BadDescriptor,
366 RustixErrno::BUSY => ErrorCode::Busy,
367 RustixErrno::ACCESS => ErrorCode::Access,
368 RustixErrno::NOTDIR => ErrorCode::NotDirectory,
369 RustixErrno::ISDIR => ErrorCode::IsDirectory,
370 RustixErrno::INVAL => ErrorCode::Invalid,
371 RustixErrno::EXIST => ErrorCode::Exist,
372 RustixErrno::FBIG => ErrorCode::FileTooLarge,
373 RustixErrno::NOSPC => ErrorCode::InsufficientSpace,
374 RustixErrno::SPIPE => ErrorCode::InvalidSeek,
375 RustixErrno::MLINK => ErrorCode::TooManyLinks,
376 RustixErrno::NAMETOOLONG => ErrorCode::NameTooLong,
377 RustixErrno::NOTEMPTY => ErrorCode::NotEmpty,
378 RustixErrno::LOOP => ErrorCode::Loop,
379 RustixErrno::OVERFLOW => ErrorCode::Overflow,
380 RustixErrno::ILSEQ => ErrorCode::IllegalByteSequence,
381 RustixErrno::NOTSUP => ErrorCode::Unsupported,
382 RustixErrno::ALREADY => ErrorCode::Already,
383 RustixErrno::INPROGRESS => ErrorCode::InProgress,
384 RustixErrno::INTR => ErrorCode::Interrupted,
385
386 #[allow(unreachable_patterns, reason = "see comment")]
388 RustixErrno::OPNOTSUPP => ErrorCode::Unsupported,
389
390 _ => return None,
391 })
392}
393
394#[cfg(windows)]
395fn from_raw_os_error(raw_os_error: Option<i32>) -> Option<ErrorCode> {
396 use windows_sys::Win32::Foundation;
397 Some(match raw_os_error.map(|code| code as u32) {
398 Some(Foundation::ERROR_FILE_NOT_FOUND) => ErrorCode::NoEntry,
399 Some(Foundation::ERROR_PATH_NOT_FOUND) => ErrorCode::NoEntry,
400 Some(Foundation::ERROR_ACCESS_DENIED) => ErrorCode::Access,
401 Some(Foundation::ERROR_SHARING_VIOLATION) => ErrorCode::Access,
402 Some(Foundation::ERROR_PRIVILEGE_NOT_HELD) => ErrorCode::NotPermitted,
403 Some(Foundation::ERROR_INVALID_HANDLE) => ErrorCode::BadDescriptor,
404 Some(Foundation::ERROR_INVALID_NAME) => ErrorCode::NoEntry,
405 Some(Foundation::ERROR_NOT_ENOUGH_MEMORY) => ErrorCode::InsufficientMemory,
406 Some(Foundation::ERROR_OUTOFMEMORY) => ErrorCode::InsufficientMemory,
407 Some(Foundation::ERROR_DIR_NOT_EMPTY) => ErrorCode::NotEmpty,
408 Some(Foundation::ERROR_NOT_READY) => ErrorCode::Busy,
409 Some(Foundation::ERROR_BUSY) => ErrorCode::Busy,
410 Some(Foundation::ERROR_NOT_SUPPORTED) => ErrorCode::Unsupported,
411 Some(Foundation::ERROR_FILE_EXISTS) => ErrorCode::Exist,
412 Some(Foundation::ERROR_BROKEN_PIPE) => ErrorCode::Pipe,
413 Some(Foundation::ERROR_BUFFER_OVERFLOW) => ErrorCode::NameTooLong,
414 Some(Foundation::ERROR_NOT_A_REPARSE_POINT) => ErrorCode::Invalid,
415 Some(Foundation::ERROR_NEGATIVE_SEEK) => ErrorCode::Invalid,
416 Some(Foundation::ERROR_DIRECTORY) => ErrorCode::NotDirectory,
417 Some(Foundation::ERROR_ALREADY_EXISTS) => ErrorCode::Exist,
418 Some(Foundation::ERROR_STOPPED_ON_SYMLINK) => ErrorCode::Loop,
419 Some(Foundation::ERROR_DIRECTORY_NOT_SUPPORTED) => ErrorCode::IsDirectory,
420 _ => return None,
421 })
422}
423
424impl<'a> From<&'a std::io::Error> for ErrorCode {
425 fn from(err: &'a std::io::Error) -> ErrorCode {
426 match from_raw_os_error(err.raw_os_error()) {
427 Some(errno) => errno,
428 None => {
429 debug!("unknown raw os error: {err}");
430 match err.kind() {
431 std::io::ErrorKind::NotFound => ErrorCode::NoEntry,
432 std::io::ErrorKind::PermissionDenied => ErrorCode::NotPermitted,
433 std::io::ErrorKind::AlreadyExists => ErrorCode::Exist,
434 std::io::ErrorKind::InvalidInput => ErrorCode::Invalid,
435 _ => ErrorCode::Io,
436 }
437 }
438 }
439 }
440}
441
442impl From<std::io::Error> for ErrorCode {
443 fn from(err: std::io::Error) -> ErrorCode {
444 ErrorCode::from(&err)
445 }
446}
447
448#[derive(Clone)]
449pub enum Descriptor {
450 File(File),
451 Dir(Dir),
452}
453
454impl Descriptor {
455 pub(crate) fn file(&self) -> Result<&File, ErrorCode> {
456 match self {
457 Descriptor::File(f) => Ok(f),
458 Descriptor::Dir(_) => Err(ErrorCode::BadDescriptor),
459 }
460 }
461
462 pub(crate) fn dir(&self) -> Result<&Dir, ErrorCode> {
463 match self {
464 Descriptor::Dir(d) => Ok(d),
465 Descriptor::File(_) => Err(ErrorCode::NotDirectory),
466 }
467 }
468
469 async fn get_metadata(&self) -> std::io::Result<cap_std::fs::Metadata> {
470 match self {
471 Self::File(f) => {
472 f.run_blocking(|f| f.metadata()).await
474 }
475 Self::Dir(d) => {
476 d.run_blocking(|d| d.dir_metadata()).await
478 }
479 }
480 }
481
482 pub(crate) async fn sync_data(&self) -> Result<(), ErrorCode> {
483 match self {
484 Self::File(f) => {
485 match f.run_blocking(|f| f.sync_data()).await {
486 Ok(()) => Ok(()),
487 #[cfg(windows)]
491 Err(err)
492 if err.raw_os_error()
493 == Some(windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED as _) =>
494 {
495 Ok(())
496 }
497 Err(err) => Err(err.into()),
498 }
499 }
500 Self::Dir(d) => {
501 d.run_blocking(|d| {
502 let d = d.open(std::path::Component::CurDir)?;
503 d.sync_data()?;
504 Ok(())
505 })
506 .await
507 }
508 }
509 }
510
511 pub(crate) async fn get_flags(&self) -> Result<DescriptorFlags, ErrorCode> {
512 use system_interface::fs::{FdFlags, GetSetFdFlags};
513
514 fn get_from_fdflags(flags: FdFlags) -> DescriptorFlags {
515 let mut out = DescriptorFlags::empty();
516 if flags.contains(FdFlags::DSYNC) {
517 out |= DescriptorFlags::REQUESTED_WRITE_SYNC;
518 }
519 if flags.contains(FdFlags::RSYNC) {
520 out |= DescriptorFlags::DATA_INTEGRITY_SYNC;
521 }
522 if flags.contains(FdFlags::SYNC) {
523 out |= DescriptorFlags::FILE_INTEGRITY_SYNC;
524 }
525 out
526 }
527 match self {
528 Self::File(f) => {
529 let flags = f.run_blocking(|f| f.get_fd_flags()).await?;
530 let mut flags = get_from_fdflags(flags);
531 if f.open_mode.contains(OpenMode::READ) {
532 flags |= DescriptorFlags::READ;
533 }
534 if f.open_mode.contains(OpenMode::WRITE) {
535 flags |= DescriptorFlags::WRITE;
536 }
537 Ok(flags)
538 }
539 Self::Dir(d) => {
540 let flags = d.run_blocking(|d| d.get_fd_flags()).await?;
541 let mut flags = get_from_fdflags(flags);
542 if d.open_mode.contains(OpenMode::READ) {
543 flags |= DescriptorFlags::READ;
544 }
545 if d.open_mode.contains(OpenMode::WRITE) {
546 flags |= DescriptorFlags::MUTATE_DIRECTORY;
547 }
548 Ok(flags)
549 }
550 }
551 }
552
553 pub(crate) async fn get_type(&self) -> Result<DescriptorType, ErrorCode> {
554 match self {
555 Self::File(f) => {
556 let meta = f.run_blocking(|f| f.metadata()).await?;
557 Ok(meta.file_type().into())
558 }
559 Self::Dir(_) => Ok(DescriptorType::Directory),
560 }
561 }
562
563 pub(crate) async fn set_times(
564 &self,
565 atim: Option<SystemTimeSpec>,
566 mtim: Option<SystemTimeSpec>,
567 ) -> Result<(), ErrorCode> {
568 use fs_set_times::SetTimes as _;
569 match self {
570 Self::File(f) => {
571 if !f.perms.contains(FilePerms::WRITE) {
572 return Err(ErrorCode::NotPermitted);
573 }
574 f.run_blocking(|f| f.set_times(atim, mtim)).await?;
575 Ok(())
576 }
577 Self::Dir(d) => {
578 if !d.perms.contains(DirPerms::MUTATE) {
579 return Err(ErrorCode::NotPermitted);
580 }
581 d.run_blocking(|d| d.set_times(atim, mtim)).await?;
582 Ok(())
583 }
584 }
585 }
586
587 pub(crate) async fn sync(&self) -> Result<(), ErrorCode> {
588 match self {
589 Self::File(f) => {
590 match f.run_blocking(|f| f.sync_all()).await {
591 Ok(()) => Ok(()),
592 #[cfg(windows)]
596 Err(err)
597 if err.raw_os_error()
598 == Some(windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED as _) =>
599 {
600 Ok(())
601 }
602 Err(err) => Err(err.into()),
603 }
604 }
605 Self::Dir(d) => {
606 d.run_blocking(|d| {
607 let d = d.open(std::path::Component::CurDir)?;
608 d.sync_all()?;
609 Ok(())
610 })
611 .await
612 }
613 }
614 }
615
616 pub(crate) async fn stat(&self) -> Result<DescriptorStat, ErrorCode> {
617 match self {
618 Self::File(f) => {
619 let meta = f.run_blocking(|f| f.metadata()).await?;
621 Ok(meta.into())
622 }
623 Self::Dir(d) => {
624 let meta = d.run_blocking(|d| d.dir_metadata()).await?;
626 Ok(meta.into())
627 }
628 }
629 }
630
631 pub(crate) async fn is_same_object(&self, other: &Self) -> wasmtime::Result<bool> {
632 use cap_fs_ext::MetadataExt;
633 let meta_a = self.get_metadata().await?;
634 let meta_b = other.get_metadata().await?;
635 if meta_a.dev() == meta_b.dev() && meta_a.ino() == meta_b.ino() {
636 debug_assert_eq!(
639 MetadataHashValue::from(&meta_a).upper,
640 MetadataHashValue::from(&meta_b).upper,
641 );
642 debug_assert_eq!(
643 MetadataHashValue::from(&meta_a).lower,
644 MetadataHashValue::from(&meta_b).lower,
645 );
646 Ok(true)
647 } else {
648 Ok(false)
650 }
651 }
652
653 pub(crate) async fn metadata_hash(&self) -> Result<MetadataHashValue, ErrorCode> {
654 let meta = self.get_metadata().await?;
655 Ok(MetadataHashValue::from(&meta))
656 }
657}
658
659#[derive(Clone)]
660pub struct File {
661 pub file: Arc<cap_std::fs::File>,
667 pub perms: FilePerms,
671 pub open_mode: OpenMode,
676
677 allow_blocking_current_thread: bool,
678}
679
680impl File {
681 pub fn new(
682 file: cap_std::fs::File,
683 perms: FilePerms,
684 open_mode: OpenMode,
685 allow_blocking_current_thread: bool,
686 ) -> Self {
687 Self {
688 file: Arc::new(file),
689 perms,
690 open_mode,
691 allow_blocking_current_thread,
692 }
693 }
694
695 pub(crate) async fn run_blocking<F, R>(&self, body: F) -> R
710 where
711 F: FnOnce(&cap_std::fs::File) -> R + Send + 'static,
712 R: Send + 'static,
713 {
714 match self.as_blocking_file() {
715 Some(file) => body(file),
716 None => self.spawn_blocking(body).await,
717 }
718 }
719
720 pub(crate) fn spawn_blocking<F, R>(&self, body: F) -> AbortOnDropJoinHandle<R>
721 where
722 F: FnOnce(&cap_std::fs::File) -> R + Send + 'static,
723 R: Send + 'static,
724 {
725 let f = self.file.clone();
726 spawn_blocking(move || body(&f))
727 }
728
729 pub(crate) fn as_blocking_file(&self) -> Option<&cap_std::fs::File> {
733 if self.allow_blocking_current_thread {
734 Some(&self.file)
735 } else {
736 None
737 }
738 }
739
740 #[cfg(feature = "p3")]
742 pub(crate) fn as_file(&self) -> &Arc<cap_std::fs::File> {
743 &self.file
744 }
745
746 pub(crate) async fn advise(
747 &self,
748 offset: u64,
749 len: u64,
750 advice: system_interface::fs::Advice,
751 ) -> Result<(), ErrorCode> {
752 use system_interface::fs::FileIoExt as _;
753 self.run_blocking(move |f| f.advise(offset, len, advice))
754 .await?;
755 Ok(())
756 }
757
758 pub(crate) async fn set_size(&self, size: u64) -> Result<(), ErrorCode> {
759 if !self.perms.contains(FilePerms::WRITE) {
760 return Err(ErrorCode::NotPermitted);
761 }
762 self.run_blocking(move |f| f.set_len(size)).await?;
763 Ok(())
764 }
765}
766
767#[derive(Clone)]
768pub struct Dir {
769 pub dir: Arc<cap_std::fs::Dir>,
774 pub perms: DirPerms,
781 pub file_perms: FilePerms,
783 pub open_mode: OpenMode,
788
789 pub(crate) allow_blocking_current_thread: bool,
790}
791
792impl Dir {
793 pub fn new(
794 dir: cap_std::fs::Dir,
795 perms: DirPerms,
796 file_perms: FilePerms,
797 open_mode: OpenMode,
798 allow_blocking_current_thread: bool,
799 ) -> Self {
800 Dir {
801 dir: Arc::new(dir),
802 perms,
803 file_perms,
804 open_mode,
805 allow_blocking_current_thread,
806 }
807 }
808
809 pub(crate) async fn run_blocking<F, R>(&self, body: F) -> R
824 where
825 F: FnOnce(&cap_std::fs::Dir) -> R + Send + 'static,
826 R: Send + 'static,
827 {
828 if self.allow_blocking_current_thread {
829 body(&self.dir)
830 } else {
831 let d = self.dir.clone();
832 spawn_blocking(move || body(&d)).await
833 }
834 }
835
836 #[cfg(feature = "p3")]
838 pub(crate) fn as_dir(&self) -> &Arc<cap_std::fs::Dir> {
839 &self.dir
840 }
841
842 pub(crate) async fn create_directory_at(&self, path: String) -> Result<(), ErrorCode> {
843 if !self.perms.contains(DirPerms::MUTATE) {
844 return Err(ErrorCode::NotPermitted);
845 }
846 self.run_blocking(move |d| d.create_dir(&path)).await?;
847 Ok(())
848 }
849
850 pub(crate) async fn stat_at(
851 &self,
852 path_flags: PathFlags,
853 path: String,
854 ) -> Result<DescriptorStat, ErrorCode> {
855 if !self.perms.contains(DirPerms::READ) {
856 return Err(ErrorCode::NotPermitted);
857 }
858
859 let meta = if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
860 self.run_blocking(move |d| d.metadata(&path)).await?
861 } else {
862 self.run_blocking(move |d| d.symlink_metadata(&path))
863 .await?
864 };
865 Ok(meta.into())
866 }
867
868 pub(crate) async fn set_times_at(
869 &self,
870 path_flags: PathFlags,
871 path: String,
872 atim: Option<SystemTimeSpec>,
873 mtim: Option<SystemTimeSpec>,
874 ) -> Result<(), ErrorCode> {
875 use cap_fs_ext::DirExt as _;
876
877 if !self.perms.contains(DirPerms::MUTATE) {
878 return Err(ErrorCode::NotPermitted);
879 }
880 if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
881 self.run_blocking(move |d| {
882 d.set_times(
883 &path,
884 atim.map(cap_fs_ext::SystemTimeSpec::from_std),
885 mtim.map(cap_fs_ext::SystemTimeSpec::from_std),
886 )
887 })
888 .await?;
889 } else {
890 self.run_blocking(move |d| {
891 d.set_symlink_times(
892 &path,
893 atim.map(cap_fs_ext::SystemTimeSpec::from_std),
894 mtim.map(cap_fs_ext::SystemTimeSpec::from_std),
895 )
896 })
897 .await?;
898 }
899 Ok(())
900 }
901
902 pub(crate) async fn link_at(
903 &self,
904 old_path_flags: PathFlags,
905 old_path: String,
906 new_dir: &Self,
907 new_path: String,
908 ) -> Result<(), ErrorCode> {
909 if !self.perms.contains(DirPerms::MUTATE) {
910 return Err(ErrorCode::NotPermitted);
911 }
912 if !new_dir.perms.contains(DirPerms::MUTATE) {
913 return Err(ErrorCode::NotPermitted);
914 }
915 if old_path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
916 return Err(ErrorCode::Invalid);
917 }
918 let new_dir_handle = Arc::clone(&new_dir.dir);
919 self.run_blocking(move |d| d.hard_link(&old_path, &new_dir_handle, &new_path))
920 .await?;
921 Ok(())
922 }
923
924 pub(crate) async fn open_at(
925 &self,
926 path_flags: PathFlags,
927 path: String,
928 oflags: OpenFlags,
929 flags: DescriptorFlags,
930 allow_blocking_current_thread: bool,
931 ) -> Result<Descriptor, ErrorCode> {
932 use cap_fs_ext::{FollowSymlinks, OpenOptionsFollowExt, OpenOptionsMaybeDirExt};
933 use system_interface::fs::{FdFlags, GetSetFdFlags};
934
935 if !self.perms.contains(DirPerms::READ) {
936 return Err(ErrorCode::NotPermitted);
937 }
938
939 if !self.perms.contains(DirPerms::MUTATE) {
940 if oflags.contains(OpenFlags::CREATE) || oflags.contains(OpenFlags::TRUNCATE) {
941 return Err(ErrorCode::NotPermitted);
942 }
943 if flags.contains(DescriptorFlags::WRITE) {
944 return Err(ErrorCode::NotPermitted);
945 }
946 }
947
948 let mut create = false;
950 let mut open_mode = OpenMode::empty();
952 let mut opts = cap_std::fs::OpenOptions::new();
954 opts.maybe_dir(true);
955
956 if oflags.contains(OpenFlags::CREATE) {
957 if oflags.contains(OpenFlags::EXCLUSIVE) {
958 opts.create_new(true);
959 } else {
960 opts.create(true);
961 }
962 create = true;
963 opts.write(true);
964 open_mode |= OpenMode::WRITE;
965 }
966
967 if oflags.contains(OpenFlags::TRUNCATE) {
968 opts.truncate(true).write(true);
969 open_mode |= OpenMode::WRITE;
970 }
971 if flags.contains(DescriptorFlags::READ) {
972 opts.read(true);
973 open_mode |= OpenMode::READ;
974 }
975 if flags.contains(DescriptorFlags::WRITE) {
976 opts.write(true);
977 open_mode |= OpenMode::WRITE;
978 } else {
979 opts.read(true);
982 open_mode |= OpenMode::READ;
983 }
984 if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
985 opts.follow(FollowSymlinks::Yes);
986 } else {
987 opts.follow(FollowSymlinks::No);
988 }
989
990 if flags.contains(DescriptorFlags::FILE_INTEGRITY_SYNC)
992 || flags.contains(DescriptorFlags::DATA_INTEGRITY_SYNC)
993 || flags.contains(DescriptorFlags::REQUESTED_WRITE_SYNC)
994 {
995 return Err(ErrorCode::Unsupported);
996 }
997
998 if oflags.contains(OpenFlags::DIRECTORY) {
999 if oflags.contains(OpenFlags::CREATE)
1000 || oflags.contains(OpenFlags::EXCLUSIVE)
1001 || oflags.contains(OpenFlags::TRUNCATE)
1002 {
1003 return Err(ErrorCode::Invalid);
1004 }
1005 }
1006
1007 if !self.perms.contains(DirPerms::MUTATE) && create {
1010 return Err(ErrorCode::NotPermitted);
1011 }
1012 if !self.file_perms.contains(FilePerms::WRITE) && open_mode.contains(OpenMode::WRITE) {
1013 return Err(ErrorCode::NotPermitted);
1014 }
1015
1016 enum OpenResult {
1020 Dir(cap_std::fs::Dir),
1021 File(cap_std::fs::File),
1022 NotDir,
1023 }
1024
1025 let opened = self
1026 .run_blocking::<_, std::io::Result<OpenResult>>(move |d| {
1027 let mut opened = d.open_with(&path, &opts)?;
1028 if opened.metadata()?.is_dir() {
1029 Ok(OpenResult::Dir(cap_std::fs::Dir::from_std_file(
1030 opened.into_std(),
1031 )))
1032 } else if oflags.contains(OpenFlags::DIRECTORY) {
1033 Ok(OpenResult::NotDir)
1034 } else {
1035 let set_fd_flags = opened.new_set_fd_flags(FdFlags::NONBLOCK)?;
1038 opened.set_fd_flags(set_fd_flags)?;
1039 Ok(OpenResult::File(opened))
1040 }
1041 })
1042 .await?;
1043
1044 match opened {
1045 #[cfg(windows)]
1049 OpenResult::Dir(_) if flags.contains(DescriptorFlags::WRITE) => {
1050 Err(ErrorCode::IsDirectory)
1051 }
1052
1053 OpenResult::Dir(dir) => Ok(Descriptor::Dir(Dir::new(
1054 dir,
1055 self.perms,
1056 self.file_perms,
1057 open_mode,
1058 allow_blocking_current_thread,
1059 ))),
1060
1061 OpenResult::File(file) => Ok(Descriptor::File(File::new(
1062 file,
1063 self.file_perms,
1064 open_mode,
1065 allow_blocking_current_thread,
1066 ))),
1067
1068 OpenResult::NotDir => Err(ErrorCode::NotDirectory),
1069 }
1070 }
1071
1072 pub(crate) async fn readlink_at(&self, path: String) -> Result<String, ErrorCode> {
1073 if !self.perms.contains(DirPerms::READ) {
1074 return Err(ErrorCode::NotPermitted);
1075 }
1076 let link = self.run_blocking(move |d| d.read_link(&path)).await?;
1077 link.into_os_string()
1078 .into_string()
1079 .or(Err(ErrorCode::IllegalByteSequence))
1080 }
1081
1082 pub(crate) async fn remove_directory_at(&self, path: String) -> Result<(), ErrorCode> {
1083 if !self.perms.contains(DirPerms::MUTATE) {
1084 return Err(ErrorCode::NotPermitted);
1085 }
1086 self.run_blocking(move |d| d.remove_dir(&path)).await?;
1087 Ok(())
1088 }
1089
1090 pub(crate) async fn rename_at(
1091 &self,
1092 old_path: String,
1093 new_dir: &Self,
1094 new_path: String,
1095 ) -> Result<(), ErrorCode> {
1096 if !self.perms.contains(DirPerms::MUTATE) {
1097 return Err(ErrorCode::NotPermitted);
1098 }
1099 if !new_dir.perms.contains(DirPerms::MUTATE) {
1100 return Err(ErrorCode::NotPermitted);
1101 }
1102 let new_dir_handle = Arc::clone(&new_dir.dir);
1103 self.run_blocking(move |d| d.rename(&old_path, &new_dir_handle, &new_path))
1104 .await?;
1105 Ok(())
1106 }
1107
1108 pub(crate) async fn symlink_at(
1109 &self,
1110 src_path: String,
1111 dest_path: String,
1112 ) -> Result<(), ErrorCode> {
1113 #[cfg(windows)]
1115 use cap_fs_ext::DirExt;
1116
1117 if !self.perms.contains(DirPerms::MUTATE) {
1118 return Err(ErrorCode::NotPermitted);
1119 }
1120 self.run_blocking(move |d| d.symlink(&src_path, &dest_path))
1121 .await?;
1122 Ok(())
1123 }
1124
1125 pub(crate) async fn unlink_file_at(&self, path: String) -> Result<(), ErrorCode> {
1126 use cap_fs_ext::DirExt;
1127
1128 if !self.perms.contains(DirPerms::MUTATE) {
1129 return Err(ErrorCode::NotPermitted);
1130 }
1131 self.run_blocking(move |d| d.remove_file_or_symlink(&path))
1132 .await?;
1133 Ok(())
1134 }
1135
1136 pub(crate) async fn metadata_hash_at(
1137 &self,
1138 path_flags: PathFlags,
1139 path: String,
1140 ) -> Result<MetadataHashValue, ErrorCode> {
1141 let meta = self
1143 .run_blocking(move |d| {
1144 if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
1145 d.metadata(path)
1146 } else {
1147 d.symlink_metadata(path)
1148 }
1149 })
1150 .await?;
1151 Ok(MetadataHashValue::from(&meta))
1152 }
1153}
1154
1155impl WasiFilesystemCtxView<'_> {
1156 pub(crate) fn get_directories(
1157 &mut self,
1158 ) -> wasmtime::Result<Vec<(Resource<Descriptor>, String)>> {
1159 let preopens = self.ctx.preopens.clone();
1160 let mut results = Vec::with_capacity(preopens.len());
1161 for (dir, name) in preopens {
1162 let fd = self
1163 .table
1164 .push(Descriptor::Dir(dir))
1165 .with_context(|| format!("failed to push preopen {name}"))?;
1166 results.push((fd, name));
1167 }
1168 Ok(results)
1169 }
1170}