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 if self.perms != new_dir.perms || self.file_perms != new_dir.file_perms {
919 return Err(ErrorCode::NotPermitted);
920 }
921 let new_dir_handle = Arc::clone(&new_dir.dir);
922 self.run_blocking(move |d| d.hard_link(&old_path, &new_dir_handle, &new_path))
923 .await?;
924 Ok(())
925 }
926
927 pub(crate) async fn open_at(
928 &self,
929 path_flags: PathFlags,
930 path: String,
931 oflags: OpenFlags,
932 flags: DescriptorFlags,
933 allow_blocking_current_thread: bool,
934 ) -> Result<Descriptor, ErrorCode> {
935 use cap_fs_ext::{FollowSymlinks, OpenOptionsFollowExt, OpenOptionsMaybeDirExt};
936
937 if !self.perms.contains(DirPerms::READ) {
938 return Err(ErrorCode::NotPermitted);
939 }
940
941 if !self.perms.contains(DirPerms::MUTATE) {
942 if oflags.contains(OpenFlags::CREATE) || oflags.contains(OpenFlags::TRUNCATE) {
943 return Err(ErrorCode::NotPermitted);
944 }
945 if flags.contains(DescriptorFlags::WRITE) {
946 return Err(ErrorCode::NotPermitted);
947 }
948 }
949
950 let mut create = false;
952 let mut open_mode = OpenMode::empty();
954 let mut opts = cap_std::fs::OpenOptions::new();
956 opts.maybe_dir(true);
957
958 if oflags.contains(OpenFlags::CREATE) {
959 if oflags.contains(OpenFlags::EXCLUSIVE) {
960 opts.create_new(true);
961 } else {
962 opts.create(true);
963 }
964 create = true;
965 opts.write(true);
966 open_mode |= OpenMode::WRITE;
967 }
968
969 if oflags.contains(OpenFlags::TRUNCATE) {
970 opts.truncate(true).write(true);
971 open_mode |= OpenMode::WRITE;
972 }
973 if flags.contains(DescriptorFlags::READ) {
974 opts.read(true);
975 open_mode |= OpenMode::READ;
976 }
977 if flags.contains(DescriptorFlags::WRITE) {
978 opts.write(true);
979 open_mode |= OpenMode::WRITE;
980 } else {
981 opts.read(true);
984 open_mode |= OpenMode::READ;
985 }
986 if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
987 opts.follow(FollowSymlinks::Yes);
988 } else {
989 opts.follow(FollowSymlinks::No);
990 }
991
992 if flags.contains(DescriptorFlags::FILE_INTEGRITY_SYNC)
994 || flags.contains(DescriptorFlags::DATA_INTEGRITY_SYNC)
995 || flags.contains(DescriptorFlags::REQUESTED_WRITE_SYNC)
996 {
997 return Err(ErrorCode::Unsupported);
998 }
999
1000 if oflags.contains(OpenFlags::DIRECTORY) {
1001 if oflags.contains(OpenFlags::CREATE)
1002 || oflags.contains(OpenFlags::EXCLUSIVE)
1003 || oflags.contains(OpenFlags::TRUNCATE)
1004 {
1005 return Err(ErrorCode::Invalid);
1006 }
1007 }
1008
1009 if !self.perms.contains(DirPerms::MUTATE) && create {
1012 return Err(ErrorCode::NotPermitted);
1013 }
1014 if !self.file_perms.contains(FilePerms::WRITE) && open_mode.contains(OpenMode::WRITE) {
1015 return Err(ErrorCode::NotPermitted);
1016 }
1017
1018 enum OpenResult {
1022 Dir(cap_std::fs::Dir),
1023 File(cap_std::fs::File),
1024 NotDir,
1025 }
1026
1027 let opened = self
1028 .run_blocking::<_, std::io::Result<OpenResult>>(move |d| {
1029 let opened = d.open_with(&path, &opts)?;
1030 if opened.metadata()?.is_dir() {
1031 Ok(OpenResult::Dir(cap_std::fs::Dir::from_std_file(
1032 opened.into_std(),
1033 )))
1034 } else if oflags.contains(OpenFlags::DIRECTORY) {
1035 Ok(OpenResult::NotDir)
1036 } else {
1037 Ok(OpenResult::File(opened))
1038 }
1039 })
1040 .await?;
1041
1042 match opened {
1043 #[cfg(windows)]
1047 OpenResult::Dir(_) if flags.contains(DescriptorFlags::WRITE) => {
1048 Err(ErrorCode::IsDirectory)
1049 }
1050
1051 OpenResult::Dir(dir) => Ok(Descriptor::Dir(Dir::new(
1052 dir,
1053 self.perms,
1054 self.file_perms,
1055 open_mode,
1056 allow_blocking_current_thread,
1057 ))),
1058
1059 OpenResult::File(file) => Ok(Descriptor::File(File::new(
1060 file,
1061 self.file_perms,
1062 open_mode,
1063 allow_blocking_current_thread,
1064 ))),
1065
1066 OpenResult::NotDir => Err(ErrorCode::NotDirectory),
1067 }
1068 }
1069
1070 pub(crate) async fn readlink_at(&self, path: String) -> Result<String, ErrorCode> {
1071 if !self.perms.contains(DirPerms::READ) {
1072 return Err(ErrorCode::NotPermitted);
1073 }
1074 let link = self.run_blocking(move |d| d.read_link(&path)).await?;
1075 link.into_os_string()
1076 .into_string()
1077 .or(Err(ErrorCode::IllegalByteSequence))
1078 }
1079
1080 pub(crate) async fn remove_directory_at(&self, path: String) -> Result<(), ErrorCode> {
1081 if !self.perms.contains(DirPerms::MUTATE) {
1082 return Err(ErrorCode::NotPermitted);
1083 }
1084 self.run_blocking(move |d| d.remove_dir(&path)).await?;
1085 Ok(())
1086 }
1087
1088 pub(crate) async fn rename_at(
1089 &self,
1090 old_path: String,
1091 new_dir: &Self,
1092 new_path: String,
1093 ) -> Result<(), ErrorCode> {
1094 if !self.perms.contains(DirPerms::MUTATE) {
1095 return Err(ErrorCode::NotPermitted);
1096 }
1097 if !new_dir.perms.contains(DirPerms::MUTATE) {
1098 return Err(ErrorCode::NotPermitted);
1099 }
1100 if self.perms != new_dir.perms || self.file_perms != new_dir.file_perms {
1101 return Err(ErrorCode::NotPermitted);
1102 }
1103 let new_dir_handle = Arc::clone(&new_dir.dir);
1104 self.run_blocking(move |d| d.rename(&old_path, &new_dir_handle, &new_path))
1105 .await?;
1106 Ok(())
1107 }
1108
1109 pub(crate) async fn symlink_at(
1110 &self,
1111 src_path: String,
1112 dest_path: String,
1113 ) -> Result<(), ErrorCode> {
1114 #[cfg(windows)]
1116 use cap_fs_ext::DirExt;
1117
1118 if !self.perms.contains(DirPerms::MUTATE) {
1119 return Err(ErrorCode::NotPermitted);
1120 }
1121 self.run_blocking(move |d| d.symlink(&src_path, &dest_path))
1122 .await?;
1123 Ok(())
1124 }
1125
1126 pub(crate) async fn unlink_file_at(&self, path: String) -> Result<(), ErrorCode> {
1127 use cap_fs_ext::DirExt;
1128
1129 if !self.perms.contains(DirPerms::MUTATE) {
1130 return Err(ErrorCode::NotPermitted);
1131 }
1132 self.run_blocking(move |d| d.remove_file_or_symlink(&path))
1133 .await?;
1134 Ok(())
1135 }
1136
1137 pub(crate) async fn metadata_hash_at(
1138 &self,
1139 path_flags: PathFlags,
1140 path: String,
1141 ) -> Result<MetadataHashValue, ErrorCode> {
1142 let meta = self
1144 .run_blocking(move |d| {
1145 if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
1146 d.metadata(path)
1147 } else {
1148 d.symlink_metadata(path)
1149 }
1150 })
1151 .await?;
1152 Ok(MetadataHashValue::from(&meta))
1153 }
1154}
1155
1156impl WasiFilesystemCtxView<'_> {
1157 pub(crate) fn get_directories(
1158 &mut self,
1159 ) -> wasmtime::Result<Vec<(Resource<Descriptor>, String)>> {
1160 let preopens = self.ctx.preopens.clone();
1161 let mut results = Vec::with_capacity(preopens.len());
1162 for (dir, name) in preopens {
1163 let fd = self
1164 .table
1165 .push(Descriptor::Dir(dir))
1166 .with_context(|| format!("failed to push preopen {name}"))?;
1167 results.push((fd, name));
1168 }
1169 Ok(results)
1170 }
1171}