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
11#[cfg(unix)]
12pub(crate) mod unix;
13#[cfg(unix)]
14pub(crate) use unix as sys;
15#[cfg(windows)]
16pub(crate) mod windows;
17#[cfg(windows)]
18pub(crate) use windows as sys;
19
20pub struct WasiFilesystem;
59
60impl HasData for WasiFilesystem {
61 type Data<'a> = WasiFilesystemCtxView<'a>;
62}
63
64#[derive(Clone, Default)]
65pub struct WasiFilesystemCtx {
66 pub(crate) allow_blocking_current_thread: bool,
67 pub(crate) preopens: Vec<(Dir, String)>,
68}
69
70pub struct WasiFilesystemCtxView<'a> {
71 pub ctx: &'a mut WasiFilesystemCtx,
72 pub table: &'a mut ResourceTable,
73}
74
75pub trait WasiFilesystemView: Send {
76 fn filesystem(&mut self) -> WasiFilesystemCtxView<'_>;
77}
78
79bitflags::bitflags! {
80 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
81 pub struct FilePerms: usize {
82 const READ = 0b1;
83 const WRITE = 0b10;
84 }
85}
86
87bitflags::bitflags! {
88 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
89 pub struct OpenMode: usize {
90 const READ = 0b1;
91 const WRITE = 0b10;
92 }
93}
94
95bitflags::bitflags! {
96 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
101 pub struct DirPerms: usize {
102 const READ = 0b1;
105
106 const MUTATE = 0b10;
109 }
110}
111
112bitflags::bitflags! {
113 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
115 pub(crate) struct PathFlags: usize {
116 const SYMLINK_FOLLOW = 0b1;
119 }
120}
121
122bitflags::bitflags! {
123 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
125 pub(crate) struct OpenFlags: usize {
126 const CREATE = 0b1;
128 const DIRECTORY = 0b10;
130 const EXCLUSIVE = 0b100;
132 const TRUNCATE = 0b1000;
134 }
135}
136
137bitflags::bitflags! {
138 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
142 pub(crate) struct DescriptorFlags: usize {
143 const READ = 0b1;
145 const WRITE = 0b10;
147 const FILE_INTEGRITY_SYNC = 0b100;
155 const DATA_INTEGRITY_SYNC = 0b1000;
163 const REQUESTED_WRITE_SYNC = 0b10000;
170 const MUTATE_DIRECTORY = 0b100000;
180 }
181}
182
183#[cfg_attr(
188 windows,
189 expect(dead_code, reason = "on Windows, some of these are not used")
190)]
191pub(crate) enum ErrorCode {
192 Access,
194 Already,
196 BadDescriptor,
198 Busy,
200 Exist,
202 FileTooLarge,
204 IllegalByteSequence,
206 InProgress,
208 Interrupted,
210 Invalid,
212 Io,
214 IsDirectory,
216 Loop,
218 TooManyLinks,
220 NameTooLong,
222 NoEntry,
224 InsufficientMemory,
226 InsufficientSpace,
228 NotDirectory,
230 NotEmpty,
232 Unsupported,
234 Overflow,
236 NotPermitted,
238 Pipe,
240 InvalidSeek,
242}
243
244fn datetime_from(t: std::time::SystemTime) -> Datetime {
245 Datetime::try_from(cap_std::time::SystemTime::from_std(t)).unwrap()
247}
248
249pub(crate) enum DescriptorType {
253 Unknown,
256 BlockDevice,
258 CharacterDevice,
260 Directory,
262 SymbolicLink,
264 RegularFile,
266}
267
268impl From<cap_std::fs::FileType> for DescriptorType {
269 fn from(ft: cap_std::fs::FileType) -> Self {
270 if ft.is_dir() {
271 DescriptorType::Directory
272 } else if ft.is_symlink() {
273 DescriptorType::SymbolicLink
274 } else if ft.is_block_device() {
275 DescriptorType::BlockDevice
276 } else if ft.is_char_device() {
277 DescriptorType::CharacterDevice
278 } else if ft.is_file() {
279 DescriptorType::RegularFile
280 } else {
281 DescriptorType::Unknown
282 }
283 }
284}
285
286pub(crate) struct DescriptorStat {
290 pub type_: DescriptorType,
292 pub link_count: u64,
294 pub size: u64,
297 pub data_access_timestamp: Option<Datetime>,
302 pub data_modification_timestamp: Option<Datetime>,
307 pub status_change_timestamp: Option<Datetime>,
312}
313
314impl From<cap_std::fs::Metadata> for DescriptorStat {
315 fn from(meta: cap_std::fs::Metadata) -> Self {
316 Self {
317 type_: meta.file_type().into(),
318 link_count: meta.nlink(),
319 size: meta.len(),
320 data_access_timestamp: meta.accessed().map(|t| datetime_from(t.into_std())).ok(),
321 data_modification_timestamp: meta.modified().map(|t| datetime_from(t.into_std())).ok(),
322 status_change_timestamp: meta.created().map(|t| datetime_from(t.into_std())).ok(),
323 }
324 }
325}
326
327pub(crate) struct MetadataHashValue {
330 pub lower: u64,
332 pub upper: u64,
334}
335
336impl From<&cap_std::fs::Metadata> for MetadataHashValue {
337 fn from(meta: &cap_std::fs::Metadata) -> Self {
338 use cap_fs_ext::MetadataExt;
339 use std::hash::Hasher;
342 let mut hasher = hash_map::DefaultHasher::new();
345 hasher.write_u64(meta.dev());
346 hasher.write_u64(meta.ino());
347 let lower = hasher.finish();
348 let upper = lower ^ 4614256656552045848u64;
358 Self { lower, upper }
359 }
360}
361
362#[derive(Copy, Clone, Debug)]
363pub(crate) enum Advice {
364 Normal,
365 Sequential,
366 Random,
367 WillNeed,
368 DontNeed,
369 NoReuse,
370}
371
372#[cfg(unix)]
373fn from_raw_os_error(err: Option<i32>) -> Option<ErrorCode> {
374 use rustix::io::Errno as RustixErrno;
375 if err.is_none() {
376 return None;
377 }
378 Some(match RustixErrno::from_raw_os_error(err.unwrap()) {
379 RustixErrno::PIPE => ErrorCode::Pipe,
380 RustixErrno::PERM => ErrorCode::NotPermitted,
381 RustixErrno::NOENT => ErrorCode::NoEntry,
382 RustixErrno::NOMEM => ErrorCode::InsufficientMemory,
383 RustixErrno::IO => ErrorCode::Io,
384 RustixErrno::BADF => ErrorCode::BadDescriptor,
385 RustixErrno::BUSY => ErrorCode::Busy,
386 RustixErrno::ACCESS => ErrorCode::Access,
387 RustixErrno::NOTDIR => ErrorCode::NotDirectory,
388 RustixErrno::ISDIR => ErrorCode::IsDirectory,
389 RustixErrno::INVAL => ErrorCode::Invalid,
390 RustixErrno::EXIST => ErrorCode::Exist,
391 RustixErrno::FBIG => ErrorCode::FileTooLarge,
392 RustixErrno::NOSPC => ErrorCode::InsufficientSpace,
393 RustixErrno::SPIPE => ErrorCode::InvalidSeek,
394 RustixErrno::MLINK => ErrorCode::TooManyLinks,
395 RustixErrno::NAMETOOLONG => ErrorCode::NameTooLong,
396 RustixErrno::NOTEMPTY => ErrorCode::NotEmpty,
397 RustixErrno::LOOP => ErrorCode::Loop,
398 RustixErrno::OVERFLOW => ErrorCode::Overflow,
399 RustixErrno::ILSEQ => ErrorCode::IllegalByteSequence,
400 RustixErrno::NOTSUP => ErrorCode::Unsupported,
401 RustixErrno::ALREADY => ErrorCode::Already,
402 RustixErrno::INPROGRESS => ErrorCode::InProgress,
403 RustixErrno::INTR => ErrorCode::Interrupted,
404
405 #[allow(unreachable_patterns, reason = "see comment")]
407 RustixErrno::OPNOTSUPP => ErrorCode::Unsupported,
408
409 _ => return None,
410 })
411}
412
413#[cfg(windows)]
414fn from_raw_os_error(raw_os_error: Option<i32>) -> Option<ErrorCode> {
415 use windows_sys::Win32::Foundation;
416 Some(match raw_os_error.map(|code| code as u32) {
417 Some(Foundation::ERROR_FILE_NOT_FOUND) => ErrorCode::NoEntry,
418 Some(Foundation::ERROR_PATH_NOT_FOUND) => ErrorCode::NoEntry,
419 Some(Foundation::ERROR_ACCESS_DENIED) => ErrorCode::Access,
420 Some(Foundation::ERROR_SHARING_VIOLATION) => ErrorCode::Access,
421 Some(Foundation::ERROR_PRIVILEGE_NOT_HELD) => ErrorCode::NotPermitted,
422 Some(Foundation::ERROR_INVALID_HANDLE) => ErrorCode::BadDescriptor,
423 Some(Foundation::ERROR_INVALID_NAME) => ErrorCode::NoEntry,
424 Some(Foundation::ERROR_NOT_ENOUGH_MEMORY) => ErrorCode::InsufficientMemory,
425 Some(Foundation::ERROR_OUTOFMEMORY) => ErrorCode::InsufficientMemory,
426 Some(Foundation::ERROR_DIR_NOT_EMPTY) => ErrorCode::NotEmpty,
427 Some(Foundation::ERROR_NOT_READY) => ErrorCode::Busy,
428 Some(Foundation::ERROR_BUSY) => ErrorCode::Busy,
429 Some(Foundation::ERROR_NOT_SUPPORTED) => ErrorCode::Unsupported,
430 Some(Foundation::ERROR_FILE_EXISTS) => ErrorCode::Exist,
431 Some(Foundation::ERROR_BROKEN_PIPE) => ErrorCode::Pipe,
432 Some(Foundation::ERROR_BUFFER_OVERFLOW) => ErrorCode::NameTooLong,
433 Some(Foundation::ERROR_NOT_A_REPARSE_POINT) => ErrorCode::Invalid,
434 Some(Foundation::ERROR_NEGATIVE_SEEK) => ErrorCode::Invalid,
435 Some(Foundation::ERROR_DIRECTORY) => ErrorCode::NotDirectory,
436 Some(Foundation::ERROR_ALREADY_EXISTS) => ErrorCode::Exist,
437 Some(Foundation::ERROR_STOPPED_ON_SYMLINK) => ErrorCode::Loop,
438 Some(Foundation::ERROR_DIRECTORY_NOT_SUPPORTED) => ErrorCode::IsDirectory,
439 _ => return None,
440 })
441}
442
443impl<'a> From<&'a std::io::Error> for ErrorCode {
444 fn from(err: &'a std::io::Error) -> ErrorCode {
445 match from_raw_os_error(err.raw_os_error()) {
446 Some(errno) => errno,
447 None => {
448 debug!("unknown raw os error: {err}");
449 match err.kind() {
450 std::io::ErrorKind::NotFound => ErrorCode::NoEntry,
451 std::io::ErrorKind::PermissionDenied => ErrorCode::NotPermitted,
452 std::io::ErrorKind::AlreadyExists => ErrorCode::Exist,
453 std::io::ErrorKind::InvalidInput => ErrorCode::Invalid,
454 _ => ErrorCode::Io,
455 }
456 }
457 }
458 }
459}
460
461impl From<std::io::Error> for ErrorCode {
462 fn from(err: std::io::Error) -> ErrorCode {
463 ErrorCode::from(&err)
464 }
465}
466
467#[derive(Clone)]
468pub enum Descriptor {
469 File(File),
470 Dir(Dir),
471}
472
473impl Descriptor {
474 pub(crate) fn file(&self) -> Result<&File, ErrorCode> {
475 match self {
476 Descriptor::File(f) => Ok(f),
477 Descriptor::Dir(_) => Err(ErrorCode::BadDescriptor),
478 }
479 }
480
481 pub(crate) fn dir(&self) -> Result<&Dir, ErrorCode> {
482 match self {
483 Descriptor::Dir(d) => Ok(d),
484 Descriptor::File(_) => Err(ErrorCode::NotDirectory),
485 }
486 }
487
488 async fn get_metadata(&self) -> std::io::Result<cap_std::fs::Metadata> {
489 match self {
490 Self::File(f) => {
491 f.run_blocking(|f| f.metadata()).await
493 }
494 Self::Dir(d) => {
495 d.run_blocking(|d| d.dir_metadata()).await
497 }
498 }
499 }
500
501 pub(crate) async fn sync_data(&self) -> Result<(), ErrorCode> {
502 match self {
503 Self::File(f) => {
504 match f.run_blocking(|f| f.sync_data()).await {
505 Ok(()) => Ok(()),
506 #[cfg(windows)]
510 Err(err)
511 if err.raw_os_error()
512 == Some(windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED as _) =>
513 {
514 Ok(())
515 }
516 Err(err) => Err(err.into()),
517 }
518 }
519 Self::Dir(d) => {
520 d.run_blocking(|d| {
521 let d = d.open(std::path::Component::CurDir)?;
522 d.sync_data()?;
523 Ok(())
524 })
525 .await
526 }
527 }
528 }
529
530 pub(crate) async fn get_flags(&self) -> Result<DescriptorFlags, ErrorCode> {
531 match self {
532 Self::File(f) => {
533 let mut flags = f.run_blocking(|f| sys::get_flags(f)).await?;
534 if f.open_mode.contains(OpenMode::READ) {
535 flags |= DescriptorFlags::READ;
536 }
537 if f.open_mode.contains(OpenMode::WRITE) {
538 flags |= DescriptorFlags::WRITE;
539 }
540 Ok(flags)
541 }
542 Self::Dir(d) => {
543 let mut flags = d.run_blocking(|d| sys::get_flags(d)).await?;
544 if d.open_mode.contains(OpenMode::READ) {
545 flags |= DescriptorFlags::READ;
546 }
547 if d.open_mode.contains(OpenMode::WRITE) {
548 flags |= DescriptorFlags::MUTATE_DIRECTORY;
549 }
550 Ok(flags)
551 }
552 }
553 }
554
555 pub(crate) async fn get_type(&self) -> Result<DescriptorType, ErrorCode> {
556 match self {
557 Self::File(f) => {
558 let meta = f.run_blocking(|f| f.metadata()).await?;
559 Ok(meta.file_type().into())
560 }
561 Self::Dir(_) => Ok(DescriptorType::Directory),
562 }
563 }
564
565 pub(crate) async fn set_times(
566 &self,
567 atim: Option<SystemTimeSpec>,
568 mtim: Option<SystemTimeSpec>,
569 ) -> Result<(), ErrorCode> {
570 use fs_set_times::SetTimes as _;
571 match self {
572 Self::File(f) => {
573 if !f.perms.contains(FilePerms::WRITE) {
574 return Err(ErrorCode::NotPermitted);
575 }
576 f.run_blocking(|f| f.set_times(atim, mtim)).await?;
577 Ok(())
578 }
579 Self::Dir(d) => {
580 if !d.perms.contains(DirPerms::MUTATE) {
581 return Err(ErrorCode::NotPermitted);
582 }
583 d.run_blocking(|d| d.set_times(atim, mtim)).await?;
584 Ok(())
585 }
586 }
587 }
588
589 pub(crate) async fn sync(&self) -> Result<(), ErrorCode> {
590 match self {
591 Self::File(f) => {
592 match f.run_blocking(|f| f.sync_all()).await {
593 Ok(()) => Ok(()),
594 #[cfg(windows)]
598 Err(err)
599 if err.raw_os_error()
600 == Some(windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED as _) =>
601 {
602 Ok(())
603 }
604 Err(err) => Err(err.into()),
605 }
606 }
607 Self::Dir(d) => {
608 d.run_blocking(|d| {
609 let d = d.open(std::path::Component::CurDir)?;
610 d.sync_all()?;
611 Ok(())
612 })
613 .await
614 }
615 }
616 }
617
618 pub(crate) async fn stat(&self) -> Result<DescriptorStat, ErrorCode> {
619 match self {
620 Self::File(f) => {
621 let meta = f.run_blocking(|f| f.metadata()).await?;
623 Ok(meta.into())
624 }
625 Self::Dir(d) => {
626 let meta = d.run_blocking(|d| d.dir_metadata()).await?;
628 Ok(meta.into())
629 }
630 }
631 }
632
633 pub(crate) async fn is_same_object(&self, other: &Self) -> wasmtime::Result<bool> {
634 use cap_fs_ext::MetadataExt;
635 let meta_a = self.get_metadata().await?;
636 let meta_b = other.get_metadata().await?;
637 if meta_a.dev() == meta_b.dev() && meta_a.ino() == meta_b.ino() {
638 debug_assert_eq!(
641 MetadataHashValue::from(&meta_a).upper,
642 MetadataHashValue::from(&meta_b).upper,
643 );
644 debug_assert_eq!(
645 MetadataHashValue::from(&meta_a).lower,
646 MetadataHashValue::from(&meta_b).lower,
647 );
648 Ok(true)
649 } else {
650 Ok(false)
652 }
653 }
654
655 pub(crate) async fn metadata_hash(&self) -> Result<MetadataHashValue, ErrorCode> {
656 let meta = self.get_metadata().await?;
657 Ok(MetadataHashValue::from(&meta))
658 }
659}
660
661#[derive(Clone)]
662pub struct File {
663 pub file: Arc<cap_std::fs::File>,
669 pub perms: FilePerms,
673 pub open_mode: OpenMode,
678
679 allow_blocking_current_thread: bool,
680}
681
682impl File {
683 pub fn new(
684 file: cap_std::fs::File,
685 perms: FilePerms,
686 open_mode: OpenMode,
687 allow_blocking_current_thread: bool,
688 ) -> Self {
689 Self {
690 file: Arc::new(file),
691 perms,
692 open_mode,
693 allow_blocking_current_thread,
694 }
695 }
696
697 pub(crate) async fn run_blocking<F, R>(&self, body: F) -> R
712 where
713 F: FnOnce(&cap_std::fs::File) -> R + Send + 'static,
714 R: Send + 'static,
715 {
716 match self.as_blocking_file() {
717 Some(file) => body(file),
718 None => self.spawn_blocking(body).await,
719 }
720 }
721
722 pub(crate) fn spawn_blocking<F, R>(&self, body: F) -> AbortOnDropJoinHandle<R>
723 where
724 F: FnOnce(&cap_std::fs::File) -> R + Send + 'static,
725 R: Send + 'static,
726 {
727 let f = self.file.clone();
728 spawn_blocking(move || body(&f))
729 }
730
731 pub(crate) fn as_blocking_file(&self) -> Option<&cap_std::fs::File> {
735 if self.allow_blocking_current_thread {
736 Some(&self.file)
737 } else {
738 None
739 }
740 }
741
742 #[cfg(feature = "p3")]
744 pub(crate) fn as_file(&self) -> &Arc<cap_std::fs::File> {
745 &self.file
746 }
747
748 pub(crate) async fn advise(
749 &self,
750 offset: u64,
751 len: u64,
752 advice: Advice,
753 ) -> Result<(), ErrorCode> {
754 self.run_blocking(move |f| sys::advise(f, offset, len, advice))
755 .await?;
756 Ok(())
757 }
758
759 pub(crate) async fn set_size(&self, size: u64) -> Result<(), ErrorCode> {
760 if !self.perms.contains(FilePerms::WRITE) {
761 return Err(ErrorCode::NotPermitted);
762 }
763 self.run_blocking(move |f| f.set_len(size)).await?;
764 Ok(())
765 }
766}
767
768#[derive(Clone)]
769pub struct Dir {
770 pub dir: Arc<cap_std::fs::Dir>,
775 pub perms: DirPerms,
782 pub file_perms: FilePerms,
784 pub open_mode: OpenMode,
789
790 pub(crate) allow_blocking_current_thread: bool,
791}
792
793impl Dir {
794 pub fn new(
795 dir: cap_std::fs::Dir,
796 perms: DirPerms,
797 file_perms: FilePerms,
798 open_mode: OpenMode,
799 allow_blocking_current_thread: bool,
800 ) -> Self {
801 Dir {
802 dir: Arc::new(dir),
803 perms,
804 file_perms,
805 open_mode,
806 allow_blocking_current_thread,
807 }
808 }
809
810 pub(crate) async fn run_blocking<F, R>(&self, body: F) -> R
825 where
826 F: FnOnce(&cap_std::fs::Dir) -> R + Send + 'static,
827 R: Send + 'static,
828 {
829 if self.allow_blocking_current_thread {
830 body(&self.dir)
831 } else {
832 let d = self.dir.clone();
833 spawn_blocking(move || body(&d)).await
834 }
835 }
836
837 #[cfg(feature = "p3")]
839 pub(crate) fn as_dir(&self) -> &Arc<cap_std::fs::Dir> {
840 &self.dir
841 }
842
843 pub(crate) async fn create_directory_at(&self, path: String) -> Result<(), ErrorCode> {
844 if !self.perms.contains(DirPerms::MUTATE) {
845 return Err(ErrorCode::NotPermitted);
846 }
847 self.run_blocking(move |d| d.create_dir(&path)).await?;
848 Ok(())
849 }
850
851 pub(crate) async fn stat_at(
852 &self,
853 path_flags: PathFlags,
854 path: String,
855 ) -> Result<DescriptorStat, ErrorCode> {
856 if !self.perms.contains(DirPerms::READ) {
857 return Err(ErrorCode::NotPermitted);
858 }
859
860 let meta = if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
861 self.run_blocking(move |d| d.metadata(&path)).await?
862 } else {
863 self.run_blocking(move |d| d.symlink_metadata(&path))
864 .await?
865 };
866 Ok(meta.into())
867 }
868
869 pub(crate) async fn set_times_at(
870 &self,
871 path_flags: PathFlags,
872 path: String,
873 atim: Option<SystemTimeSpec>,
874 mtim: Option<SystemTimeSpec>,
875 ) -> Result<(), ErrorCode> {
876 use cap_fs_ext::DirExt as _;
877
878 if !self.perms.contains(DirPerms::MUTATE) {
879 return Err(ErrorCode::NotPermitted);
880 }
881 if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
882 self.run_blocking(move |d| {
883 d.set_times(
884 &path,
885 atim.map(cap_fs_ext::SystemTimeSpec::from_std),
886 mtim.map(cap_fs_ext::SystemTimeSpec::from_std),
887 )
888 })
889 .await?;
890 } else {
891 self.run_blocking(move |d| {
892 d.set_symlink_times(
893 &path,
894 atim.map(cap_fs_ext::SystemTimeSpec::from_std),
895 mtim.map(cap_fs_ext::SystemTimeSpec::from_std),
896 )
897 })
898 .await?;
899 }
900 Ok(())
901 }
902
903 pub(crate) async fn link_at(
904 &self,
905 old_path_flags: PathFlags,
906 old_path: String,
907 new_dir: &Self,
908 new_path: String,
909 ) -> Result<(), ErrorCode> {
910 if !self.perms.contains(DirPerms::MUTATE) {
911 return Err(ErrorCode::NotPermitted);
912 }
913 if !new_dir.perms.contains(DirPerms::MUTATE) {
914 return Err(ErrorCode::NotPermitted);
915 }
916 if old_path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
917 return Err(ErrorCode::Invalid);
918 }
919 let new_dir_handle = Arc::clone(&new_dir.dir);
920 self.run_blocking(move |d| d.hard_link(&old_path, &new_dir_handle, &new_path))
921 .await?;
922 Ok(())
923 }
924
925 pub(crate) async fn open_at(
926 &self,
927 path_flags: PathFlags,
928 path: String,
929 oflags: OpenFlags,
930 flags: DescriptorFlags,
931 allow_blocking_current_thread: bool,
932 ) -> Result<Descriptor, ErrorCode> {
933 use cap_fs_ext::{FollowSymlinks, OpenOptionsFollowExt, OpenOptionsMaybeDirExt};
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 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 Ok(OpenResult::File(opened))
1036 }
1037 })
1038 .await?;
1039
1040 match opened {
1041 #[cfg(windows)]
1045 OpenResult::Dir(_) if flags.contains(DescriptorFlags::WRITE) => {
1046 Err(ErrorCode::IsDirectory)
1047 }
1048
1049 OpenResult::Dir(dir) => Ok(Descriptor::Dir(Dir::new(
1050 dir,
1051 self.perms,
1052 self.file_perms,
1053 open_mode,
1054 allow_blocking_current_thread,
1055 ))),
1056
1057 OpenResult::File(file) => Ok(Descriptor::File(File::new(
1058 file,
1059 self.file_perms,
1060 open_mode,
1061 allow_blocking_current_thread,
1062 ))),
1063
1064 OpenResult::NotDir => Err(ErrorCode::NotDirectory),
1065 }
1066 }
1067
1068 pub(crate) async fn readlink_at(&self, path: String) -> Result<String, ErrorCode> {
1069 if !self.perms.contains(DirPerms::READ) {
1070 return Err(ErrorCode::NotPermitted);
1071 }
1072 let link = self.run_blocking(move |d| d.read_link(&path)).await?;
1073 link.into_os_string()
1074 .into_string()
1075 .or(Err(ErrorCode::IllegalByteSequence))
1076 }
1077
1078 pub(crate) async fn remove_directory_at(&self, path: String) -> Result<(), ErrorCode> {
1079 if !self.perms.contains(DirPerms::MUTATE) {
1080 return Err(ErrorCode::NotPermitted);
1081 }
1082 self.run_blocking(move |d| d.remove_dir(&path)).await?;
1083 Ok(())
1084 }
1085
1086 pub(crate) async fn rename_at(
1087 &self,
1088 old_path: String,
1089 new_dir: &Self,
1090 new_path: String,
1091 ) -> Result<(), ErrorCode> {
1092 if !self.perms.contains(DirPerms::MUTATE) {
1093 return Err(ErrorCode::NotPermitted);
1094 }
1095 if !new_dir.perms.contains(DirPerms::MUTATE) {
1096 return Err(ErrorCode::NotPermitted);
1097 }
1098 let new_dir_handle = Arc::clone(&new_dir.dir);
1099 self.run_blocking(move |d| d.rename(&old_path, &new_dir_handle, &new_path))
1100 .await?;
1101 Ok(())
1102 }
1103
1104 pub(crate) async fn symlink_at(
1105 &self,
1106 src_path: String,
1107 dest_path: String,
1108 ) -> Result<(), ErrorCode> {
1109 #[cfg(windows)]
1111 use cap_fs_ext::DirExt;
1112
1113 if !self.perms.contains(DirPerms::MUTATE) {
1114 return Err(ErrorCode::NotPermitted);
1115 }
1116 self.run_blocking(move |d| d.symlink(&src_path, &dest_path))
1117 .await?;
1118 Ok(())
1119 }
1120
1121 pub(crate) async fn unlink_file_at(&self, path: String) -> Result<(), ErrorCode> {
1122 use cap_fs_ext::DirExt;
1123
1124 if !self.perms.contains(DirPerms::MUTATE) {
1125 return Err(ErrorCode::NotPermitted);
1126 }
1127 self.run_blocking(move |d| d.remove_file_or_symlink(&path))
1128 .await?;
1129 Ok(())
1130 }
1131
1132 pub(crate) async fn metadata_hash_at(
1133 &self,
1134 path_flags: PathFlags,
1135 path: String,
1136 ) -> Result<MetadataHashValue, ErrorCode> {
1137 let meta = self
1139 .run_blocking(move |d| {
1140 if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
1141 d.metadata(path)
1142 } else {
1143 d.symlink_metadata(path)
1144 }
1145 })
1146 .await?;
1147 Ok(MetadataHashValue::from(&meta))
1148 }
1149}
1150
1151impl WasiFilesystemCtxView<'_> {
1152 pub(crate) fn get_directories(
1153 &mut self,
1154 ) -> wasmtime::Result<Vec<(Resource<Descriptor>, String)>> {
1155 let preopens = self.ctx.preopens.clone();
1156 let mut results = Vec::with_capacity(preopens.len());
1157 for (dir, name) in preopens {
1158 let fd = self
1159 .table
1160 .push(Descriptor::Dir(dir))
1161 .with_context(|| format!("failed to push preopen {name}"))?;
1162 results.push((fd, name));
1163 }
1164 Ok(results)
1165 }
1166}