1use crate::clocks::Datetime;
2use crate::runtime::{AbortOnDropJoinHandle, spawn_blocking};
3use cap_fs_ext::{FileTypeExt as _, MetadataExt as _, SystemTimeSpec};
4use io_lifetimes::AsFilelike;
5use std::collections::hash_map;
6use std::sync::Arc;
7use std::time::SystemTime;
8use tracing::debug;
9use wasmtime::component::{HasData, Resource, ResourceTable};
10use wasmtime::error::Context as _;
11
12#[cfg(unix)]
13pub(crate) mod unix;
14#[cfg(unix)]
15pub(crate) use unix as sys;
16#[cfg(windows)]
17pub(crate) mod windows;
18#[cfg(windows)]
19pub(crate) use windows as sys;
20
21pub struct WasiFilesystem;
60
61impl HasData for WasiFilesystem {
62 type Data<'a> = WasiFilesystemCtxView<'a>;
63}
64
65#[derive(Clone, Default)]
66pub struct WasiFilesystemCtx {
67 pub(crate) allow_blocking_current_thread: bool,
68 pub(crate) preopens: Vec<(Dir, String)>,
69}
70
71pub struct WasiFilesystemCtxView<'a> {
72 pub ctx: &'a mut WasiFilesystemCtx,
73 pub table: &'a mut ResourceTable,
74}
75
76pub trait WasiFilesystemView: Send {
77 fn filesystem(&mut self) -> WasiFilesystemCtxView<'_>;
78}
79
80bitflags::bitflags! {
81 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
82 pub struct FilePerms: usize {
83 const READ = 0b1;
84 const WRITE = 0b10;
85 }
86}
87
88bitflags::bitflags! {
89 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
90 pub struct OpenMode: usize {
91 const READ = 0b1;
92 const WRITE = 0b10;
93 }
94}
95
96bitflags::bitflags! {
97 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
102 pub struct DirPerms: usize {
103 const READ = 0b1;
106
107 const MUTATE = 0b10;
110 }
111}
112
113bitflags::bitflags! {
114 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
116 pub(crate) struct PathFlags: usize {
117 const SYMLINK_FOLLOW = 0b1;
120 }
121}
122
123bitflags::bitflags! {
124 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
126 pub(crate) struct OpenFlags: usize {
127 const CREATE = 0b1;
129 const DIRECTORY = 0b10;
131 const EXCLUSIVE = 0b100;
133 const TRUNCATE = 0b1000;
135 }
136}
137
138bitflags::bitflags! {
139 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
143 pub(crate) struct DescriptorFlags: usize {
144 const READ = 0b1;
146 const WRITE = 0b10;
148 const FILE_INTEGRITY_SYNC = 0b100;
156 const DATA_INTEGRITY_SYNC = 0b1000;
164 const REQUESTED_WRITE_SYNC = 0b10000;
171 const MUTATE_DIRECTORY = 0b100000;
181 }
182}
183
184#[cfg_attr(
189 windows,
190 expect(dead_code, reason = "on Windows, some of these are not used")
191)]
192pub(crate) enum ErrorCode {
193 Access,
195 Already,
197 BadDescriptor,
199 Busy,
201 Exist,
203 FileTooLarge,
205 IllegalByteSequence,
207 InProgress,
209 Interrupted,
211 Invalid,
213 Io,
215 IsDirectory,
217 Loop,
219 TooManyLinks,
221 NameTooLong,
223 NoEntry,
225 InsufficientMemory,
227 InsufficientSpace,
229 NotDirectory,
231 NotEmpty,
233 Unsupported,
235 Overflow,
237 NotPermitted,
239 Pipe,
241 InvalidSeek,
243}
244
245fn datetime_from(t: std::time::SystemTime) -> Datetime {
246 Datetime::try_from(cap_std::time::SystemTime::from_std(t)).unwrap()
248}
249
250pub(crate) enum DescriptorType {
254 Unknown,
257 BlockDevice,
259 CharacterDevice,
261 Directory,
263 SymbolicLink,
265 RegularFile,
267}
268
269impl From<cap_std::fs::FileType> for DescriptorType {
270 fn from(ft: cap_std::fs::FileType) -> Self {
271 if ft.is_dir() {
272 DescriptorType::Directory
273 } else if ft.is_symlink() {
274 DescriptorType::SymbolicLink
275 } else if ft.is_block_device() {
276 DescriptorType::BlockDevice
277 } else if ft.is_char_device() {
278 DescriptorType::CharacterDevice
279 } else if ft.is_file() {
280 DescriptorType::RegularFile
281 } else {
282 DescriptorType::Unknown
283 }
284 }
285}
286
287pub(crate) struct DescriptorStat {
291 pub type_: DescriptorType,
293 pub link_count: u64,
295 pub size: u64,
298 pub data_access_timestamp: Option<Datetime>,
303 pub data_modification_timestamp: Option<Datetime>,
308 pub status_change_timestamp: Option<Datetime>,
313}
314
315impl From<cap_std::fs::Metadata> for DescriptorStat {
316 fn from(meta: cap_std::fs::Metadata) -> Self {
317 Self {
318 type_: meta.file_type().into(),
319 link_count: meta.nlink(),
320 size: meta.len(),
321 data_access_timestamp: meta.accessed().map(|t| datetime_from(t.into_std())).ok(),
322 data_modification_timestamp: meta.modified().map(|t| datetime_from(t.into_std())).ok(),
323 status_change_timestamp: meta.created().map(|t| datetime_from(t.into_std())).ok(),
324 }
325 }
326}
327
328pub(crate) struct MetadataHashValue {
331 pub lower: u64,
333 pub upper: u64,
335}
336
337impl From<&cap_std::fs::Metadata> for MetadataHashValue {
338 fn from(meta: &cap_std::fs::Metadata) -> Self {
339 use cap_fs_ext::MetadataExt;
340 use std::hash::Hasher;
343 let mut hasher = hash_map::DefaultHasher::new();
346 hasher.write_u64(meta.dev());
347 hasher.write_u64(meta.ino());
348 let lower = hasher.finish();
349 let upper = lower ^ 4614256656552045848u64;
359 Self { lower, upper }
360 }
361}
362
363#[derive(Copy, Clone, Debug)]
364pub(crate) enum Advice {
365 Normal,
366 Sequential,
367 Random,
368 WillNeed,
369 DontNeed,
370 NoReuse,
371}
372
373#[cfg(unix)]
374fn from_raw_os_error(err: Option<i32>) -> Option<ErrorCode> {
375 use rustix::io::Errno as RustixErrno;
376 if err.is_none() {
377 return None;
378 }
379 Some(match RustixErrno::from_raw_os_error(err.unwrap()) {
380 RustixErrno::PIPE => ErrorCode::Pipe,
381 RustixErrno::PERM => ErrorCode::NotPermitted,
382 RustixErrno::NOENT => ErrorCode::NoEntry,
383 RustixErrno::NOMEM => ErrorCode::InsufficientMemory,
384 RustixErrno::IO => ErrorCode::Io,
385 RustixErrno::BADF => ErrorCode::BadDescriptor,
386 RustixErrno::BUSY => ErrorCode::Busy,
387 RustixErrno::ACCESS => ErrorCode::Access,
388 RustixErrno::NOTDIR => ErrorCode::NotDirectory,
389 RustixErrno::ISDIR => ErrorCode::IsDirectory,
390 RustixErrno::INVAL => ErrorCode::Invalid,
391 RustixErrno::EXIST => ErrorCode::Exist,
392 RustixErrno::FBIG => ErrorCode::FileTooLarge,
393 RustixErrno::NOSPC => ErrorCode::InsufficientSpace,
394 RustixErrno::SPIPE => ErrorCode::InvalidSeek,
395 RustixErrno::MLINK => ErrorCode::TooManyLinks,
396 RustixErrno::NAMETOOLONG => ErrorCode::NameTooLong,
397 RustixErrno::NOTEMPTY => ErrorCode::NotEmpty,
398 RustixErrno::LOOP => ErrorCode::Loop,
399 RustixErrno::OVERFLOW => ErrorCode::Overflow,
400 RustixErrno::ILSEQ => ErrorCode::IllegalByteSequence,
401 RustixErrno::NOTSUP => ErrorCode::Unsupported,
402 RustixErrno::ALREADY => ErrorCode::Already,
403 RustixErrno::INPROGRESS => ErrorCode::InProgress,
404 RustixErrno::INTR => ErrorCode::Interrupted,
405
406 #[allow(unreachable_patterns, reason = "see comment")]
408 RustixErrno::OPNOTSUPP => ErrorCode::Unsupported,
409
410 _ => return None,
411 })
412}
413
414#[cfg(windows)]
415fn from_raw_os_error(raw_os_error: Option<i32>) -> Option<ErrorCode> {
416 use windows_sys::Win32::Foundation;
417 Some(match raw_os_error.map(|code| code as u32) {
418 Some(Foundation::ERROR_FILE_NOT_FOUND) => ErrorCode::NoEntry,
419 Some(Foundation::ERROR_PATH_NOT_FOUND) => ErrorCode::NoEntry,
420 Some(Foundation::ERROR_ACCESS_DENIED) => ErrorCode::Access,
421 Some(Foundation::ERROR_SHARING_VIOLATION) => ErrorCode::Access,
422 Some(Foundation::ERROR_PRIVILEGE_NOT_HELD) => ErrorCode::NotPermitted,
423 Some(Foundation::ERROR_INVALID_HANDLE) => ErrorCode::BadDescriptor,
424 Some(Foundation::ERROR_INVALID_NAME) => ErrorCode::NoEntry,
425 Some(Foundation::ERROR_NOT_ENOUGH_MEMORY) => ErrorCode::InsufficientMemory,
426 Some(Foundation::ERROR_OUTOFMEMORY) => ErrorCode::InsufficientMemory,
427 Some(Foundation::ERROR_DIR_NOT_EMPTY) => ErrorCode::NotEmpty,
428 Some(Foundation::ERROR_NOT_READY) => ErrorCode::Busy,
429 Some(Foundation::ERROR_BUSY) => ErrorCode::Busy,
430 Some(Foundation::ERROR_NOT_SUPPORTED) => ErrorCode::Unsupported,
431 Some(Foundation::ERROR_FILE_EXISTS) => ErrorCode::Exist,
432 Some(Foundation::ERROR_BROKEN_PIPE) => ErrorCode::Pipe,
433 Some(Foundation::ERROR_BUFFER_OVERFLOW) => ErrorCode::NameTooLong,
434 Some(Foundation::ERROR_NOT_A_REPARSE_POINT) => ErrorCode::Invalid,
435 Some(Foundation::ERROR_NEGATIVE_SEEK) => ErrorCode::Invalid,
436 Some(Foundation::ERROR_DIRECTORY) => ErrorCode::NotDirectory,
437 Some(Foundation::ERROR_ALREADY_EXISTS) => ErrorCode::Exist,
438 Some(Foundation::ERROR_STOPPED_ON_SYMLINK) => ErrorCode::Loop,
439 Some(Foundation::ERROR_DIRECTORY_NOT_SUPPORTED) => ErrorCode::IsDirectory,
440 _ => return None,
441 })
442}
443
444impl<'a> From<&'a std::io::Error> for ErrorCode {
445 fn from(err: &'a std::io::Error) -> ErrorCode {
446 match from_raw_os_error(err.raw_os_error()) {
447 Some(errno) => errno,
448 None => {
449 debug!("unknown raw os error: {err}");
450 match err.kind() {
451 std::io::ErrorKind::NotFound => ErrorCode::NoEntry,
452 std::io::ErrorKind::PermissionDenied => ErrorCode::NotPermitted,
453 std::io::ErrorKind::AlreadyExists => ErrorCode::Exist,
454 std::io::ErrorKind::InvalidInput => ErrorCode::Invalid,
455 _ => ErrorCode::Io,
456 }
457 }
458 }
459 }
460}
461
462impl From<std::io::Error> for ErrorCode {
463 fn from(err: std::io::Error) -> ErrorCode {
464 ErrorCode::from(&err)
465 }
466}
467
468#[derive(Clone)]
469pub enum Descriptor {
470 File(File),
471 Dir(Dir),
472}
473
474impl Descriptor {
475 pub(crate) fn file(&self) -> Result<&File, ErrorCode> {
476 match self {
477 Descriptor::File(f) => Ok(f),
478 Descriptor::Dir(_) => Err(ErrorCode::BadDescriptor),
479 }
480 }
481
482 pub(crate) fn dir(&self) -> Result<&Dir, ErrorCode> {
483 match self {
484 Descriptor::Dir(d) => Ok(d),
485 Descriptor::File(_) => Err(ErrorCode::NotDirectory),
486 }
487 }
488
489 async fn get_metadata(&self) -> std::io::Result<cap_std::fs::Metadata> {
490 match self {
491 Self::File(f) => {
492 f.run_blocking(|f| f.metadata()).await
494 }
495 Self::Dir(d) => {
496 d.run_blocking(|d| d.dir_metadata()).await
498 }
499 }
500 }
501
502 pub(crate) async fn sync_data(&self) -> Result<(), ErrorCode> {
503 match self {
504 Self::File(f) => {
505 match f.run_blocking(|f| f.sync_data()).await {
506 Ok(()) => Ok(()),
507 #[cfg(windows)]
511 Err(err)
512 if err.raw_os_error()
513 == Some(windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED as _) =>
514 {
515 Ok(())
516 }
517 Err(err) => Err(err.into()),
518 }
519 }
520 Self::Dir(d) => {
521 d.run_blocking(|d| {
522 let d = d.open(std::path::Component::CurDir)?;
523 d.sync_data()?;
524 Ok(())
525 })
526 .await
527 }
528 }
529 }
530
531 pub(crate) async fn get_flags(&self) -> Result<DescriptorFlags, ErrorCode> {
532 match self {
533 Self::File(f) => {
534 let mut flags = f.run_blocking(|f| sys::get_flags(f)).await?;
535 if f.open_mode.contains(OpenMode::READ) {
536 flags |= DescriptorFlags::READ;
537 }
538 if f.open_mode.contains(OpenMode::WRITE) {
539 flags |= DescriptorFlags::WRITE;
540 }
541 Ok(flags)
542 }
543 Self::Dir(d) => {
544 let mut flags = d.run_blocking(|d| sys::get_flags(d)).await?;
545 if d.open_mode.contains(OpenMode::READ) {
546 flags |= DescriptorFlags::READ;
547 }
548 if d.open_mode.contains(OpenMode::WRITE) {
549 flags |= DescriptorFlags::MUTATE_DIRECTORY;
550 }
551 Ok(flags)
552 }
553 }
554 }
555
556 pub(crate) async fn get_type(&self) -> Result<DescriptorType, ErrorCode> {
557 match self {
558 Self::File(f) => {
559 let meta = f.run_blocking(|f| f.metadata()).await?;
560 Ok(meta.file_type().into())
561 }
562 Self::Dir(_) => Ok(DescriptorType::Directory),
563 }
564 }
565
566 pub(crate) async fn set_times(
567 &self,
568 atim: Option<SystemTime>,
569 mtim: Option<SystemTime>,
570 ) -> Result<(), ErrorCode> {
571 let mut times = std::fs::FileTimes::new();
572 if let Some(atim) = atim {
573 times = times.set_accessed(atim);
574 }
575 if let Some(mtim) = mtim {
576 times = times.set_modified(mtim);
577 }
578 match self {
579 Self::File(f) => {
580 if !f.perms.contains(FilePerms::WRITE) {
581 return Err(ErrorCode::NotPermitted);
582 }
583 f.run_blocking(move |f| f.as_filelike_view::<std::fs::File>().set_times(times))
584 .await?;
585 Ok(())
586 }
587 Self::Dir(d) => {
588 if !d.perms.contains(DirPerms::MUTATE) {
589 return Err(ErrorCode::NotPermitted);
590 }
591 d.run_blocking(move |d| d.as_filelike_view::<std::fs::File>().set_times(times))
592 .await?;
593 Ok(())
594 }
595 }
596 }
597
598 pub(crate) async fn sync(&self) -> Result<(), ErrorCode> {
599 match self {
600 Self::File(f) => {
601 match f.run_blocking(|f| f.sync_all()).await {
602 Ok(()) => Ok(()),
603 #[cfg(windows)]
607 Err(err)
608 if err.raw_os_error()
609 == Some(windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED as _) =>
610 {
611 Ok(())
612 }
613 Err(err) => Err(err.into()),
614 }
615 }
616 Self::Dir(d) => {
617 d.run_blocking(|d| {
618 let d = d.open(std::path::Component::CurDir)?;
619 d.sync_all()?;
620 Ok(())
621 })
622 .await
623 }
624 }
625 }
626
627 pub(crate) async fn stat(&self) -> Result<DescriptorStat, ErrorCode> {
628 match self {
629 Self::File(f) => {
630 let meta = f.run_blocking(|f| f.metadata()).await?;
632 Ok(meta.into())
633 }
634 Self::Dir(d) => {
635 let meta = d.run_blocking(|d| d.dir_metadata()).await?;
637 Ok(meta.into())
638 }
639 }
640 }
641
642 pub(crate) async fn is_same_object(&self, other: &Self) -> wasmtime::Result<bool> {
643 use cap_fs_ext::MetadataExt;
644 let meta_a = self.get_metadata().await?;
645 let meta_b = other.get_metadata().await?;
646 if meta_a.dev() == meta_b.dev() && meta_a.ino() == meta_b.ino() {
647 debug_assert_eq!(
650 MetadataHashValue::from(&meta_a).upper,
651 MetadataHashValue::from(&meta_b).upper,
652 );
653 debug_assert_eq!(
654 MetadataHashValue::from(&meta_a).lower,
655 MetadataHashValue::from(&meta_b).lower,
656 );
657 Ok(true)
658 } else {
659 Ok(false)
661 }
662 }
663
664 pub(crate) async fn metadata_hash(&self) -> Result<MetadataHashValue, ErrorCode> {
665 let meta = self.get_metadata().await?;
666 Ok(MetadataHashValue::from(&meta))
667 }
668}
669
670#[derive(Clone)]
671pub struct File {
672 pub file: Arc<cap_std::fs::File>,
678 pub perms: FilePerms,
682 pub open_mode: OpenMode,
687
688 allow_blocking_current_thread: bool,
689}
690
691impl File {
692 pub fn new(
693 file: cap_std::fs::File,
694 perms: FilePerms,
695 open_mode: OpenMode,
696 allow_blocking_current_thread: bool,
697 ) -> Self {
698 Self {
699 file: Arc::new(file),
700 perms,
701 open_mode,
702 allow_blocking_current_thread,
703 }
704 }
705
706 pub(crate) async fn run_blocking<F, R>(&self, body: F) -> R
721 where
722 F: FnOnce(&cap_std::fs::File) -> R + Send + 'static,
723 R: Send + 'static,
724 {
725 match self.as_blocking_file() {
726 Some(file) => body(file),
727 None => self.spawn_blocking(body).await,
728 }
729 }
730
731 pub(crate) fn spawn_blocking<F, R>(&self, body: F) -> AbortOnDropJoinHandle<R>
732 where
733 F: FnOnce(&cap_std::fs::File) -> R + Send + 'static,
734 R: Send + 'static,
735 {
736 let f = self.file.clone();
737 spawn_blocking(move || body(&f))
738 }
739
740 pub(crate) fn as_blocking_file(&self) -> Option<&cap_std::fs::File> {
744 if self.allow_blocking_current_thread {
745 Some(&self.file)
746 } else {
747 None
748 }
749 }
750
751 #[cfg(feature = "p3")]
753 pub(crate) fn as_file(&self) -> &Arc<cap_std::fs::File> {
754 &self.file
755 }
756
757 pub(crate) async fn advise(
758 &self,
759 offset: u64,
760 len: u64,
761 advice: Advice,
762 ) -> Result<(), ErrorCode> {
763 self.run_blocking(move |f| sys::advise(f, offset, len, advice))
764 .await?;
765 Ok(())
766 }
767
768 pub(crate) async fn set_size(&self, size: u64) -> Result<(), ErrorCode> {
769 if !self.perms.contains(FilePerms::WRITE) {
770 return Err(ErrorCode::NotPermitted);
771 }
772 self.run_blocking(move |f| f.set_len(size)).await?;
773 Ok(())
774 }
775}
776
777#[derive(Clone)]
778pub struct Dir {
779 pub dir: Arc<cap_std::fs::Dir>,
784 pub perms: DirPerms,
791 pub file_perms: FilePerms,
793 pub open_mode: OpenMode,
798
799 pub(crate) allow_blocking_current_thread: bool,
800}
801
802impl Dir {
803 pub fn new(
804 dir: cap_std::fs::Dir,
805 perms: DirPerms,
806 file_perms: FilePerms,
807 open_mode: OpenMode,
808 allow_blocking_current_thread: bool,
809 ) -> Self {
810 Dir {
811 dir: Arc::new(dir),
812 perms,
813 file_perms,
814 open_mode,
815 allow_blocking_current_thread,
816 }
817 }
818
819 pub(crate) async fn run_blocking<F, R>(&self, body: F) -> R
834 where
835 F: FnOnce(&cap_std::fs::Dir) -> R + Send + 'static,
836 R: Send + 'static,
837 {
838 if self.allow_blocking_current_thread {
839 body(&self.dir)
840 } else {
841 let d = self.dir.clone();
842 spawn_blocking(move || body(&d)).await
843 }
844 }
845
846 #[cfg(feature = "p3")]
848 pub(crate) fn as_dir(&self) -> &Arc<cap_std::fs::Dir> {
849 &self.dir
850 }
851
852 pub(crate) async fn create_directory_at(&self, path: String) -> Result<(), ErrorCode> {
853 if !self.perms.contains(DirPerms::MUTATE) {
854 return Err(ErrorCode::NotPermitted);
855 }
856 self.run_blocking(move |d| d.create_dir(&path)).await?;
857 Ok(())
858 }
859
860 pub(crate) async fn stat_at(
861 &self,
862 path_flags: PathFlags,
863 path: String,
864 ) -> Result<DescriptorStat, ErrorCode> {
865 if !self.perms.contains(DirPerms::READ) {
866 return Err(ErrorCode::NotPermitted);
867 }
868
869 let meta = if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
870 self.run_blocking(move |d| d.metadata(&path)).await?
871 } else {
872 self.run_blocking(move |d| d.symlink_metadata(&path))
873 .await?
874 };
875 Ok(meta.into())
876 }
877
878 pub(crate) async fn set_times_at(
879 &self,
880 path_flags: PathFlags,
881 path: String,
882 atim: Option<SystemTime>,
883 mtim: Option<SystemTime>,
884 ) -> Result<(), ErrorCode> {
885 use cap_fs_ext::DirExt as _;
886
887 if !self.perms.contains(DirPerms::MUTATE) {
888 return Err(ErrorCode::NotPermitted);
889 }
890 let atim = atim.map(|t| SystemTimeSpec::Absolute(cap_std::time::SystemTime::from_std(t)));
891 let mtim = mtim.map(|t| SystemTimeSpec::Absolute(cap_std::time::SystemTime::from_std(t)));
892 if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
893 self.run_blocking(move |d| d.set_times(&path, atim, mtim))
894 .await?;
895 } else {
896 self.run_blocking(move |d| d.set_symlink_times(&path, atim, mtim))
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
934 if !self.perms.contains(DirPerms::READ) {
935 return Err(ErrorCode::NotPermitted);
936 }
937
938 if !self.perms.contains(DirPerms::MUTATE) {
939 if oflags.contains(OpenFlags::CREATE) || oflags.contains(OpenFlags::TRUNCATE) {
940 return Err(ErrorCode::NotPermitted);
941 }
942 if flags.contains(DescriptorFlags::WRITE) {
943 return Err(ErrorCode::NotPermitted);
944 }
945 }
946
947 let mut create = false;
949 let mut open_mode = OpenMode::empty();
951 let mut opts = cap_std::fs::OpenOptions::new();
953 opts.maybe_dir(true);
954
955 if oflags.contains(OpenFlags::CREATE) {
956 if oflags.contains(OpenFlags::EXCLUSIVE) {
957 opts.create_new(true);
958 } else {
959 opts.create(true);
960 }
961 create = true;
962 opts.write(true);
963 open_mode |= OpenMode::WRITE;
964 }
965
966 if oflags.contains(OpenFlags::TRUNCATE) {
967 opts.truncate(true).write(true);
968 open_mode |= OpenMode::WRITE;
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}