1use crate::clocks::Datetime;
2use crate::runtime::{AbortOnDropJoinHandle, spawn_blocking};
3use cap_primitives::fs::{DirOptions, FollowSymlinks, Metadata, OpenOptions, SystemTimeSpec};
4use std::collections::hash_map;
5use std::sync::Arc;
6use std::time::SystemTime;
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
244pub(crate) enum DescriptorType {
248 Unknown,
251 #[cfg_attr(
253 windows,
254 expect(dead_code, reason = "windows has no notion of block devices")
255 )]
256 BlockDevice,
257 CharacterDevice,
259 Directory,
261 SymbolicLink,
263 RegularFile,
265}
266
267impl From<cap_primitives::fs::FileType> for DescriptorType {
268 fn from(ft: cap_primitives::fs::FileType) -> Self {
269 if ft.is_dir() {
270 DescriptorType::Directory
271 } else if ft.is_symlink() {
272 DescriptorType::SymbolicLink
273 } else if ft.is_file() {
274 DescriptorType::RegularFile
275 } else {
276 sys::descriptor_type(ft)
277 }
278 }
279}
280
281pub(crate) struct DescriptorStat {
285 pub type_: DescriptorType,
287 pub link_count: u64,
289 pub size: u64,
292 pub data_access_timestamp: Option<Datetime>,
297 pub data_modification_timestamp: Option<Datetime>,
302 pub status_change_timestamp: Option<Datetime>,
307}
308
309impl DescriptorStat {
310 fn new(meta: &Metadata, link_count: u64) -> Self {
313 Self {
314 type_: meta.file_type().into(),
315 link_count,
316 size: meta.len(),
317 data_access_timestamp: meta
318 .accessed()
319 .ok()
320 .and_then(|t| Datetime::try_from(t.into_std()).ok()),
321 data_modification_timestamp: meta
322 .modified()
323 .ok()
324 .and_then(|t| Datetime::try_from(t.into_std()).ok()),
325 status_change_timestamp: meta
326 .created()
327 .ok()
328 .and_then(|t| Datetime::try_from(t.into_std()).ok()),
329 }
330 }
331}
332
333pub(crate) struct MetadataHashValue {
336 pub lower: u64,
338 pub upper: u64,
340}
341
342impl MetadataHashValue {
343 fn new(identity: impl std::hash::Hash) -> Self {
346 use std::hash::Hasher as _;
349 let mut hasher = hash_map::DefaultHasher::new();
352 identity.hash(&mut hasher);
353 let lower = hasher.finish();
354 let upper = lower ^ 4614256656552045848u64;
364 Self { lower, upper }
365 }
366}
367
368#[derive(Copy, Clone, Debug)]
369pub(crate) enum Advice {
370 Normal,
371 Sequential,
372 Random,
373 WillNeed,
374 DontNeed,
375 NoReuse,
376}
377
378#[cfg(unix)]
379fn from_raw_os_error(err: Option<i32>) -> Option<ErrorCode> {
380 use rustix::io::Errno as RustixErrno;
381 if err.is_none() {
382 return None;
383 }
384 Some(match RustixErrno::from_raw_os_error(err.unwrap()) {
385 RustixErrno::PIPE => ErrorCode::Pipe,
386 RustixErrno::PERM => ErrorCode::NotPermitted,
387 RustixErrno::NOENT => ErrorCode::NoEntry,
388 RustixErrno::NOMEM => ErrorCode::InsufficientMemory,
389 RustixErrno::IO => ErrorCode::Io,
390 RustixErrno::BADF => ErrorCode::BadDescriptor,
391 RustixErrno::BUSY => ErrorCode::Busy,
392 RustixErrno::ACCESS => ErrorCode::Access,
393 RustixErrno::NOTDIR => ErrorCode::NotDirectory,
394 RustixErrno::ISDIR => ErrorCode::IsDirectory,
395 RustixErrno::INVAL => ErrorCode::Invalid,
396 RustixErrno::EXIST => ErrorCode::Exist,
397 RustixErrno::FBIG => ErrorCode::FileTooLarge,
398 RustixErrno::NOSPC => ErrorCode::InsufficientSpace,
399 RustixErrno::SPIPE => ErrorCode::InvalidSeek,
400 RustixErrno::MLINK => ErrorCode::TooManyLinks,
401 RustixErrno::NAMETOOLONG => ErrorCode::NameTooLong,
402 RustixErrno::NOTEMPTY => ErrorCode::NotEmpty,
403 RustixErrno::LOOP => ErrorCode::Loop,
404 RustixErrno::OVERFLOW => ErrorCode::Overflow,
405 RustixErrno::ILSEQ => ErrorCode::IllegalByteSequence,
406 RustixErrno::NOTSUP => ErrorCode::Unsupported,
407 RustixErrno::ALREADY => ErrorCode::Already,
408 RustixErrno::INPROGRESS => ErrorCode::InProgress,
409 RustixErrno::INTR => ErrorCode::Interrupted,
410
411 #[allow(unreachable_patterns, reason = "see comment")]
413 RustixErrno::OPNOTSUPP => ErrorCode::Unsupported,
414
415 _ => return None,
416 })
417}
418
419#[cfg(windows)]
420fn from_raw_os_error(raw_os_error: Option<i32>) -> Option<ErrorCode> {
421 use windows_sys::Win32::Foundation;
422 Some(match raw_os_error.map(|code| code as u32) {
423 Some(Foundation::ERROR_FILE_NOT_FOUND) => ErrorCode::NoEntry,
424 Some(Foundation::ERROR_PATH_NOT_FOUND) => ErrorCode::NoEntry,
425 Some(Foundation::ERROR_ACCESS_DENIED) => ErrorCode::Access,
426 Some(Foundation::ERROR_SHARING_VIOLATION) => ErrorCode::Access,
427 Some(Foundation::ERROR_PRIVILEGE_NOT_HELD) => ErrorCode::NotPermitted,
428 Some(Foundation::ERROR_INVALID_HANDLE) => ErrorCode::BadDescriptor,
429 Some(Foundation::ERROR_INVALID_NAME) => ErrorCode::NoEntry,
430 Some(Foundation::ERROR_NOT_ENOUGH_MEMORY) => ErrorCode::InsufficientMemory,
431 Some(Foundation::ERROR_OUTOFMEMORY) => ErrorCode::InsufficientMemory,
432 Some(Foundation::ERROR_DIR_NOT_EMPTY) => ErrorCode::NotEmpty,
433 Some(Foundation::ERROR_NOT_READY) => ErrorCode::Busy,
434 Some(Foundation::ERROR_BUSY) => ErrorCode::Busy,
435 Some(Foundation::ERROR_NOT_SUPPORTED) => ErrorCode::Unsupported,
436 Some(Foundation::ERROR_FILE_EXISTS) => ErrorCode::Exist,
437 Some(Foundation::ERROR_BROKEN_PIPE) => ErrorCode::Pipe,
438 Some(Foundation::ERROR_BUFFER_OVERFLOW) => ErrorCode::NameTooLong,
439 Some(Foundation::ERROR_NOT_A_REPARSE_POINT) => ErrorCode::Invalid,
440 Some(Foundation::ERROR_NEGATIVE_SEEK) => ErrorCode::Invalid,
441 Some(Foundation::ERROR_DIRECTORY) => ErrorCode::NotDirectory,
442 Some(Foundation::ERROR_ALREADY_EXISTS) => ErrorCode::Exist,
443 Some(Foundation::ERROR_STOPPED_ON_SYMLINK) => ErrorCode::Loop,
444 Some(Foundation::ERROR_DIRECTORY_NOT_SUPPORTED) => ErrorCode::IsDirectory,
445 _ => return None,
446 })
447}
448
449impl<'a> From<&'a std::io::Error> for ErrorCode {
450 fn from(err: &'a std::io::Error) -> ErrorCode {
451 match from_raw_os_error(err.raw_os_error()) {
452 Some(errno) => errno,
453 None => {
454 debug!("unknown raw os error: {err}");
455 match err.kind() {
456 std::io::ErrorKind::NotFound => ErrorCode::NoEntry,
457 std::io::ErrorKind::PermissionDenied => ErrorCode::NotPermitted,
458 std::io::ErrorKind::AlreadyExists => ErrorCode::Exist,
459 std::io::ErrorKind::InvalidInput => ErrorCode::Invalid,
460 _ => ErrorCode::Io,
461 }
462 }
463 }
464 }
465}
466
467impl From<std::io::Error> for ErrorCode {
468 fn from(err: std::io::Error) -> ErrorCode {
469 ErrorCode::from(&err)
470 }
471}
472
473#[derive(Clone)]
474pub enum Descriptor {
475 File(File),
476 Dir(Dir),
477}
478
479impl Descriptor {
480 pub(crate) fn file(&self) -> Result<&File, ErrorCode> {
481 match self {
482 Descriptor::File(f) => Ok(f),
483 Descriptor::Dir(_) => Err(ErrorCode::BadDescriptor),
484 }
485 }
486
487 pub(crate) fn dir(&self) -> Result<&Dir, ErrorCode> {
488 match self {
489 Descriptor::Dir(d) => Ok(d),
490 Descriptor::File(_) => Err(ErrorCode::NotDirectory),
491 }
492 }
493
494 pub(crate) async fn sync_data(&self) -> Result<(), ErrorCode> {
495 match self {
496 Self::File(f) => {
497 match f.run_blocking(|f| f.sync_data()).await {
498 Ok(()) => Ok(()),
499 #[cfg(windows)]
503 Err(err)
504 if err.raw_os_error()
505 == Some(windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED as _) =>
506 {
507 Ok(())
508 }
509 Err(err) => Err(err.into()),
510 }
511 }
512 Self::Dir(d) => {
513 d.run_blocking(|d| {
514 let d = cap_primitives::fs::open(
515 d,
516 std::path::Component::CurDir.as_ref(),
517 OpenOptions::new().read(true),
518 )?;
519 d.sync_data()?;
520 Ok(())
521 })
522 .await
523 }
524 }
525 }
526
527 pub(crate) async fn get_flags(&self) -> Result<DescriptorFlags, ErrorCode> {
528 match self {
529 Self::File(f) => {
530 let mut flags = f.run_blocking(|f| sys::get_flags(f)).await?;
531 if f.open_mode.contains(OpenMode::READ) {
532 flags |= DescriptorFlags::READ;
533 }
534 if f.open_mode.contains(OpenMode::WRITE) {
535 flags |= DescriptorFlags::WRITE;
536 }
537 Ok(flags)
538 }
539 Self::Dir(d) => {
540 let mut flags = d.run_blocking(|d| sys::get_flags(d)).await?;
541 if d.open_mode.contains(OpenMode::READ) {
542 flags |= DescriptorFlags::READ;
543 }
544 if d.open_mode.contains(OpenMode::WRITE) {
545 flags |= DescriptorFlags::MUTATE_DIRECTORY;
546 }
547 Ok(flags)
548 }
549 }
550 }
551
552 pub(crate) async fn get_type(&self) -> Result<DescriptorType, ErrorCode> {
553 match self {
554 Self::File(f) => {
555 let meta = f.run_blocking(|f| Metadata::from_file(f)).await?;
556 Ok(meta.file_type().into())
557 }
558 Self::Dir(_) => Ok(DescriptorType::Directory),
559 }
560 }
561
562 pub(crate) async fn set_times(
563 &self,
564 atim: Option<SystemTime>,
565 mtim: Option<SystemTime>,
566 ) -> Result<(), ErrorCode> {
567 let mut times = std::fs::FileTimes::new();
568 if let Some(atim) = atim {
569 times = times.set_accessed(atim);
570 }
571 if let Some(mtim) = mtim {
572 times = times.set_modified(mtim);
573 }
574 match self {
575 Self::File(f) => {
576 if !f.perms.contains(FilePerms::WRITE) {
577 return Err(ErrorCode::NotPermitted);
578 }
579 f.run_blocking(move |f| f.set_times(times)).await?;
580 Ok(())
581 }
582 Self::Dir(d) => {
583 if !d.perms.contains(DirPerms::MUTATE) {
584 return Err(ErrorCode::NotPermitted);
585 }
586 d.run_blocking(move |d| d.set_times(times)).await?;
587 Ok(())
588 }
589 }
590 }
591
592 pub(crate) async fn sync(&self) -> Result<(), ErrorCode> {
593 match self {
594 Self::File(f) => {
595 match f.run_blocking(|f| f.sync_all()).await {
596 Ok(()) => Ok(()),
597 #[cfg(windows)]
601 Err(err)
602 if err.raw_os_error()
603 == Some(windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED as _) =>
604 {
605 Ok(())
606 }
607 Err(err) => Err(err.into()),
608 }
609 }
610 Self::Dir(d) => {
611 d.run_blocking(|d| {
612 let d = cap_primitives::fs::open(
613 d,
614 std::path::Component::CurDir.as_ref(),
615 OpenOptions::new().read(true),
616 )?;
617 d.sync_all()?;
618 Ok(())
619 })
620 .await
621 }
622 }
623 }
624
625 pub(crate) async fn stat(&self) -> Result<DescriptorStat, ErrorCode> {
626 match self {
627 Self::File(f) => Ok(f.run_blocking(|f| sys::stat(f)).await?),
628 Self::Dir(d) => Ok(d.run_blocking(|f| sys::stat(f)).await?),
629 }
630 }
631
632 pub(crate) async fn is_same_object(&self, other: &Self) -> wasmtime::Result<bool> {
633 let other = match other {
635 Self::File(f) => Arc::clone(&f.file),
636 Self::Dir(d) => Arc::clone(&d.dir),
637 };
638 Ok(match self {
639 Self::File(f) => {
640 f.run_blocking(move |f| sys::is_same_file(f, &other))
641 .await?
642 }
643 Self::Dir(d) => {
644 d.run_blocking(move |d| sys::is_same_file(d, &other))
645 .await?
646 }
647 })
648 }
649
650 pub(crate) async fn metadata_hash(&self) -> Result<MetadataHashValue, ErrorCode> {
651 match self {
652 Self::File(f) => Ok(f.run_blocking(|f| sys::metadata_hash(f)).await?),
653 Self::Dir(d) => Ok(d.run_blocking(|d| sys::metadata_hash(d)).await?),
654 }
655 }
656}
657
658#[derive(Clone)]
659pub struct File {
660 pub file: Arc<std::fs::File>,
666 pub perms: FilePerms,
670 pub open_mode: OpenMode,
675
676 allow_blocking_current_thread: bool,
677}
678
679impl File {
680 pub fn new(
681 file: std::fs::File,
682 perms: FilePerms,
683 open_mode: OpenMode,
684 allow_blocking_current_thread: bool,
685 ) -> Self {
686 Self {
687 file: Arc::new(file),
688 perms,
689 open_mode,
690 allow_blocking_current_thread,
691 }
692 }
693
694 pub(crate) async fn run_blocking<F, R>(&self, body: F) -> R
709 where
710 F: FnOnce(&std::fs::File) -> R + Send + 'static,
711 R: Send + 'static,
712 {
713 match self.as_blocking_file() {
714 Some(file) => body(file),
715 None => self.spawn_blocking(body).await,
716 }
717 }
718
719 pub(crate) fn spawn_blocking<F, R>(&self, body: F) -> AbortOnDropJoinHandle<R>
720 where
721 F: FnOnce(&std::fs::File) -> R + Send + 'static,
722 R: Send + 'static,
723 {
724 let f = self.file.clone();
725 spawn_blocking(move || body(&f))
726 }
727
728 pub(crate) fn as_blocking_file(&self) -> Option<&std::fs::File> {
732 if self.allow_blocking_current_thread {
733 Some(&self.file)
734 } else {
735 None
736 }
737 }
738
739 #[cfg(feature = "p3")]
741 pub(crate) fn as_file(&self) -> &Arc<std::fs::File> {
742 &self.file
743 }
744
745 pub(crate) async fn advise(
746 &self,
747 offset: u64,
748 len: u64,
749 advice: Advice,
750 ) -> Result<(), ErrorCode> {
751 self.run_blocking(move |f| sys::advise(f, offset, len, advice))
752 .await?;
753 Ok(())
754 }
755
756 pub(crate) async fn set_size(&self, size: u64) -> Result<(), ErrorCode> {
757 if !self.perms.contains(FilePerms::WRITE) {
758 return Err(ErrorCode::NotPermitted);
759 }
760 self.run_blocking(move |f| f.set_len(size)).await?;
761 Ok(())
762 }
763}
764
765#[derive(Clone)]
766pub struct Dir {
767 pub dir: Arc<std::fs::File>,
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: std::fs::File,
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(&std::fs::File) -> 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<std::fs::File> {
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| {
848 cap_primitives::fs::create_dir(d, path.as_ref(), &DirOptions::new())
849 })
850 .await?;
851 Ok(())
852 }
853
854 pub(crate) async fn stat_at(
855 &self,
856 path_flags: PathFlags,
857 path: String,
858 ) -> Result<DescriptorStat, ErrorCode> {
859 if !self.perms.contains(DirPerms::READ) {
860 return Err(ErrorCode::NotPermitted);
861 }
862
863 let follow = if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
864 FollowSymlinks::Yes
865 } else {
866 FollowSymlinks::No
867 };
868 let ret = self
869 .run_blocking(move |d| sys::stat_at(d, path.as_ref(), follow))
870 .await?;
871 Ok(ret)
872 }
873
874 pub(crate) async fn set_times_at(
875 &self,
876 path_flags: PathFlags,
877 path: String,
878 atim: Option<SystemTime>,
879 mtim: Option<SystemTime>,
880 ) -> Result<(), ErrorCode> {
881 if !self.perms.contains(DirPerms::MUTATE) {
882 return Err(ErrorCode::NotPermitted);
883 }
884 let atim =
885 atim.map(|t| SystemTimeSpec::Absolute(cap_primitives::time::SystemTime::from_std(t)));
886 let mtim =
887 mtim.map(|t| SystemTimeSpec::Absolute(cap_primitives::time::SystemTime::from_std(t)));
888 if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
889 self.run_blocking(move |d| cap_primitives::fs::set_times(d, path.as_ref(), atim, mtim))
890 .await?;
891 } else {
892 self.run_blocking(move |d| {
893 cap_primitives::fs::set_times_nofollow(d, path.as_ref(), atim, mtim)
894 })
895 .await?;
896 }
897 Ok(())
898 }
899
900 pub(crate) async fn link_at(
901 &self,
902 old_path_flags: PathFlags,
903 old_path: String,
904 new_dir: &Self,
905 new_path: String,
906 ) -> Result<(), ErrorCode> {
907 if !self.perms.contains(DirPerms::MUTATE) {
908 return Err(ErrorCode::NotPermitted);
909 }
910 if !new_dir.perms.contains(DirPerms::MUTATE) {
911 return Err(ErrorCode::NotPermitted);
912 }
913 if old_path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
914 return Err(ErrorCode::Invalid);
915 }
916 if self.perms != new_dir.perms || self.file_perms != new_dir.file_perms {
917 return Err(ErrorCode::NotPermitted);
918 }
919 let new_dir_handle = Arc::clone(&new_dir.dir);
920 self.run_blocking(move |d| {
921 cap_primitives::fs::hard_link(d, old_path.as_ref(), &new_dir_handle, new_path.as_ref())
922 })
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 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 = OpenOptions::new();
954 sys::maybe_dir(&mut opts);
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
985 {
990 use cap_fs_ext_avoid_using_this::OpenOptionsFollowExt;
991 if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
992 opts.follow(FollowSymlinks::Yes);
993 } else {
994 opts.follow(FollowSymlinks::No);
995 }
996 }
997
998 if flags.contains(DescriptorFlags::FILE_INTEGRITY_SYNC)
1000 || flags.contains(DescriptorFlags::DATA_INTEGRITY_SYNC)
1001 || flags.contains(DescriptorFlags::REQUESTED_WRITE_SYNC)
1002 {
1003 return Err(ErrorCode::Unsupported);
1004 }
1005
1006 if oflags.contains(OpenFlags::DIRECTORY) {
1007 if oflags.contains(OpenFlags::CREATE)
1008 || oflags.contains(OpenFlags::EXCLUSIVE)
1009 || oflags.contains(OpenFlags::TRUNCATE)
1010 {
1011 return Err(ErrorCode::Invalid);
1012 }
1013 }
1014
1015 if !self.perms.contains(DirPerms::MUTATE) && create {
1018 return Err(ErrorCode::NotPermitted);
1019 }
1020 if !self.file_perms.contains(FilePerms::WRITE) && open_mode.contains(OpenMode::WRITE) {
1021 return Err(ErrorCode::NotPermitted);
1022 }
1023
1024 enum OpenResult {
1028 Dir(std::fs::File),
1029 File(std::fs::File),
1030 NotDir,
1031 }
1032
1033 let opened = self
1034 .run_blocking::<_, std::io::Result<OpenResult>>(move |d| {
1035 let opened = cap_primitives::fs::open(d, path.as_ref(), &opts)?;
1036 if Metadata::from_file(&opened)?.is_dir() {
1037 Ok(OpenResult::Dir(opened))
1038 } else if oflags.contains(OpenFlags::DIRECTORY) {
1039 Ok(OpenResult::NotDir)
1040 } else {
1041 Ok(OpenResult::File(opened))
1042 }
1043 })
1044 .await?;
1045
1046 match opened {
1047 #[cfg(windows)]
1051 OpenResult::Dir(_) if flags.contains(DescriptorFlags::WRITE) => {
1052 Err(ErrorCode::IsDirectory)
1053 }
1054
1055 OpenResult::Dir(dir) => Ok(Descriptor::Dir(Dir::new(
1056 dir,
1057 self.perms,
1058 self.file_perms,
1059 open_mode,
1060 allow_blocking_current_thread,
1061 ))),
1062
1063 OpenResult::File(file) => Ok(Descriptor::File(File::new(
1064 file,
1065 self.file_perms,
1066 open_mode,
1067 allow_blocking_current_thread,
1068 ))),
1069
1070 OpenResult::NotDir => Err(ErrorCode::NotDirectory),
1071 }
1072 }
1073
1074 pub(crate) async fn readlink_at(&self, path: String) -> Result<String, ErrorCode> {
1075 if !self.perms.contains(DirPerms::READ) {
1076 return Err(ErrorCode::NotPermitted);
1077 }
1078 let link = self
1079 .run_blocking(move |d| cap_primitives::fs::read_link(d, path.as_ref()))
1080 .await?;
1081 link.into_os_string()
1082 .into_string()
1083 .or(Err(ErrorCode::IllegalByteSequence))
1084 }
1085
1086 pub(crate) async fn remove_directory_at(&self, path: String) -> Result<(), ErrorCode> {
1087 if !self.perms.contains(DirPerms::MUTATE) {
1088 return Err(ErrorCode::NotPermitted);
1089 }
1090 self.run_blocking(move |d| cap_primitives::fs::remove_dir(d, path.as_ref()))
1091 .await?;
1092 Ok(())
1093 }
1094
1095 pub(crate) async fn rename_at(
1096 &self,
1097 old_path: String,
1098 new_dir: &Self,
1099 new_path: String,
1100 ) -> Result<(), ErrorCode> {
1101 if !self.perms.contains(DirPerms::MUTATE) {
1102 return Err(ErrorCode::NotPermitted);
1103 }
1104 if !new_dir.perms.contains(DirPerms::MUTATE) {
1105 return Err(ErrorCode::NotPermitted);
1106 }
1107 if self.perms != new_dir.perms || self.file_perms != new_dir.file_perms {
1108 return Err(ErrorCode::NotPermitted);
1109 }
1110 let new_dir_handle = Arc::clone(&new_dir.dir);
1111 self.run_blocking(move |d| {
1112 cap_primitives::fs::rename(d, old_path.as_ref(), &new_dir_handle, new_path.as_ref())
1113 })
1114 .await?;
1115 Ok(())
1116 }
1117
1118 pub(crate) async fn symlink_at(
1119 &self,
1120 src_path: String,
1121 dest_path: String,
1122 ) -> Result<(), ErrorCode> {
1123 if !self.perms.contains(DirPerms::MUTATE) {
1124 return Err(ErrorCode::NotPermitted);
1125 }
1126 self.run_blocking(move |d| sys::symlink(src_path.as_ref(), d, dest_path.as_ref()))
1127 .await?;
1128 Ok(())
1129 }
1130
1131 pub(crate) async fn unlink_file_at(&self, path: String) -> Result<(), ErrorCode> {
1132 if !self.perms.contains(DirPerms::MUTATE) {
1133 return Err(ErrorCode::NotPermitted);
1134 }
1135 self.run_blocking(move |d| sys::remove_file_or_symlink(d, path.as_ref()))
1136 .await?;
1137 Ok(())
1138 }
1139
1140 pub(crate) async fn metadata_hash_at(
1141 &self,
1142 path_flags: PathFlags,
1143 path: String,
1144 ) -> Result<MetadataHashValue, ErrorCode> {
1145 let follow = if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
1147 FollowSymlinks::Yes
1148 } else {
1149 FollowSymlinks::No
1150 };
1151 let hash = self
1152 .run_blocking(move |d| sys::metadata_hash_at(d, path.as_ref(), follow))
1153 .await?;
1154 Ok(hash)
1155 }
1156}
1157
1158impl WasiFilesystemCtxView<'_> {
1159 pub(crate) fn get_directories(
1160 &mut self,
1161 ) -> wasmtime::Result<Vec<(Resource<Descriptor>, String)>> {
1162 let preopens = self.ctx.preopens.clone();
1163 let mut results = Vec::with_capacity(preopens.len());
1164 for (dir, name) in preopens {
1165 let fd = self
1166 .table
1167 .push(Descriptor::Dir(dir))
1168 .with_context(|| format!("failed to push preopen {name}"))?;
1169 results.push((fd, name));
1170 }
1171 Ok(results)
1172 }
1173}