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 }
970 if flags.contains(DescriptorFlags::READ) {
971 opts.read(true);
972 open_mode |= OpenMode::READ;
973 }
974 if flags.contains(DescriptorFlags::WRITE) {
975 opts.write(true);
976 open_mode |= OpenMode::WRITE;
977 } else {
978 opts.read(true);
981 open_mode |= OpenMode::READ;
982 }
983 if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
984 opts.follow(FollowSymlinks::Yes);
985 } else {
986 opts.follow(FollowSymlinks::No);
987 }
988
989 if flags.contains(DescriptorFlags::FILE_INTEGRITY_SYNC)
991 || flags.contains(DescriptorFlags::DATA_INTEGRITY_SYNC)
992 || flags.contains(DescriptorFlags::REQUESTED_WRITE_SYNC)
993 {
994 return Err(ErrorCode::Unsupported);
995 }
996
997 if oflags.contains(OpenFlags::DIRECTORY) {
998 if oflags.contains(OpenFlags::CREATE)
999 || oflags.contains(OpenFlags::EXCLUSIVE)
1000 || oflags.contains(OpenFlags::TRUNCATE)
1001 {
1002 return Err(ErrorCode::Invalid);
1003 }
1004 }
1005
1006 if !self.perms.contains(DirPerms::MUTATE) && create {
1009 return Err(ErrorCode::NotPermitted);
1010 }
1011 if !self.file_perms.contains(FilePerms::WRITE) && open_mode.contains(OpenMode::WRITE) {
1012 return Err(ErrorCode::NotPermitted);
1013 }
1014
1015 enum OpenResult {
1019 Dir(cap_std::fs::Dir),
1020 File(cap_std::fs::File),
1021 NotDir,
1022 }
1023
1024 let opened = self
1025 .run_blocking::<_, std::io::Result<OpenResult>>(move |d| {
1026 let opened = d.open_with(&path, &opts)?;
1027 if opened.metadata()?.is_dir() {
1028 Ok(OpenResult::Dir(cap_std::fs::Dir::from_std_file(
1029 opened.into_std(),
1030 )))
1031 } else if oflags.contains(OpenFlags::DIRECTORY) {
1032 Ok(OpenResult::NotDir)
1033 } else {
1034 Ok(OpenResult::File(opened))
1035 }
1036 })
1037 .await?;
1038
1039 match opened {
1040 #[cfg(windows)]
1044 OpenResult::Dir(_) if flags.contains(DescriptorFlags::WRITE) => {
1045 Err(ErrorCode::IsDirectory)
1046 }
1047
1048 OpenResult::Dir(dir) => Ok(Descriptor::Dir(Dir::new(
1049 dir,
1050 self.perms,
1051 self.file_perms,
1052 open_mode,
1053 allow_blocking_current_thread,
1054 ))),
1055
1056 OpenResult::File(file) => Ok(Descriptor::File(File::new(
1057 file,
1058 self.file_perms,
1059 open_mode,
1060 allow_blocking_current_thread,
1061 ))),
1062
1063 OpenResult::NotDir => Err(ErrorCode::NotDirectory),
1064 }
1065 }
1066
1067 pub(crate) async fn readlink_at(&self, path: String) -> Result<String, ErrorCode> {
1068 if !self.perms.contains(DirPerms::READ) {
1069 return Err(ErrorCode::NotPermitted);
1070 }
1071 let link = self.run_blocking(move |d| d.read_link(&path)).await?;
1072 link.into_os_string()
1073 .into_string()
1074 .or(Err(ErrorCode::IllegalByteSequence))
1075 }
1076
1077 pub(crate) async fn remove_directory_at(&self, path: String) -> Result<(), ErrorCode> {
1078 if !self.perms.contains(DirPerms::MUTATE) {
1079 return Err(ErrorCode::NotPermitted);
1080 }
1081 self.run_blocking(move |d| d.remove_dir(&path)).await?;
1082 Ok(())
1083 }
1084
1085 pub(crate) async fn rename_at(
1086 &self,
1087 old_path: String,
1088 new_dir: &Self,
1089 new_path: String,
1090 ) -> Result<(), ErrorCode> {
1091 if !self.perms.contains(DirPerms::MUTATE) {
1092 return Err(ErrorCode::NotPermitted);
1093 }
1094 if !new_dir.perms.contains(DirPerms::MUTATE) {
1095 return Err(ErrorCode::NotPermitted);
1096 }
1097 let new_dir_handle = Arc::clone(&new_dir.dir);
1098 self.run_blocking(move |d| d.rename(&old_path, &new_dir_handle, &new_path))
1099 .await?;
1100 Ok(())
1101 }
1102
1103 pub(crate) async fn symlink_at(
1104 &self,
1105 src_path: String,
1106 dest_path: String,
1107 ) -> Result<(), ErrorCode> {
1108 #[cfg(windows)]
1110 use cap_fs_ext::DirExt;
1111
1112 if !self.perms.contains(DirPerms::MUTATE) {
1113 return Err(ErrorCode::NotPermitted);
1114 }
1115 self.run_blocking(move |d| d.symlink(&src_path, &dest_path))
1116 .await?;
1117 Ok(())
1118 }
1119
1120 pub(crate) async fn unlink_file_at(&self, path: String) -> Result<(), ErrorCode> {
1121 use cap_fs_ext::DirExt;
1122
1123 if !self.perms.contains(DirPerms::MUTATE) {
1124 return Err(ErrorCode::NotPermitted);
1125 }
1126 self.run_blocking(move |d| d.remove_file_or_symlink(&path))
1127 .await?;
1128 Ok(())
1129 }
1130
1131 pub(crate) async fn metadata_hash_at(
1132 &self,
1133 path_flags: PathFlags,
1134 path: String,
1135 ) -> Result<MetadataHashValue, ErrorCode> {
1136 let meta = self
1138 .run_blocking(move |d| {
1139 if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
1140 d.metadata(path)
1141 } else {
1142 d.symlink_metadata(path)
1143 }
1144 })
1145 .await?;
1146 Ok(MetadataHashValue::from(&meta))
1147 }
1148}
1149
1150impl WasiFilesystemCtxView<'_> {
1151 pub(crate) fn get_directories(
1152 &mut self,
1153 ) -> wasmtime::Result<Vec<(Resource<Descriptor>, String)>> {
1154 let preopens = self.ctx.preopens.clone();
1155 let mut results = Vec::with_capacity(preopens.len());
1156 for (dir, name) in preopens {
1157 let fd = self
1158 .table
1159 .push(Descriptor::Dir(dir))
1160 .with_context(|| format!("failed to push preopen {name}"))?;
1161 results.push((fd, name));
1162 }
1163 Ok(results)
1164 }
1165}