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
79#[derive(Copy, Clone, Debug, PartialEq, Eq)]
98pub enum FsPerms {
99 ReadOnly,
101 ReadWrite,
103}
104
105impl FsPerms {
106 pub fn write_not_permitted(&self) -> bool {
110 matches!(self, Self::ReadOnly)
111 }
112}
113
114bitflags::bitflags! {
115 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
116 pub struct OpenMode: usize {
117 const READ = 0b1;
118 const WRITE = 0b10;
119 }
120}
121
122bitflags::bitflags! {
123 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
125 pub(crate) struct PathFlags: usize {
126 const SYMLINK_FOLLOW = 0b1;
129 }
130}
131
132bitflags::bitflags! {
133 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
135 pub(crate) struct OpenFlags: usize {
136 const CREATE = 0b1;
138 const DIRECTORY = 0b10;
140 const EXCLUSIVE = 0b100;
142 const TRUNCATE = 0b1000;
144 }
145}
146
147bitflags::bitflags! {
148 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
152 pub(crate) struct DescriptorFlags: usize {
153 const READ = 0b1;
155 const WRITE = 0b10;
157 const FILE_INTEGRITY_SYNC = 0b100;
165 const DATA_INTEGRITY_SYNC = 0b1000;
173 const REQUESTED_WRITE_SYNC = 0b10000;
180 const MUTATE_DIRECTORY = 0b100000;
190 }
191}
192
193#[cfg_attr(
198 windows,
199 expect(dead_code, reason = "on Windows, some of these are not used")
200)]
201pub(crate) enum ErrorCode {
202 Access,
204 Already,
206 BadDescriptor,
208 Busy,
210 Exist,
212 FileTooLarge,
214 IllegalByteSequence,
216 InProgress,
218 Interrupted,
220 Invalid,
222 Io,
224 IsDirectory,
226 Loop,
228 TooManyLinks,
230 NameTooLong,
232 NoEntry,
234 InsufficientMemory,
236 InsufficientSpace,
238 NotDirectory,
240 NotEmpty,
242 Unsupported,
244 Overflow,
246 NotPermitted,
248 Pipe,
250 InvalidSeek,
252}
253
254pub(crate) enum DescriptorType {
258 Unknown,
261 #[cfg_attr(
263 windows,
264 expect(dead_code, reason = "windows has no notion of block devices")
265 )]
266 BlockDevice,
267 CharacterDevice,
269 Directory,
271 SymbolicLink,
273 RegularFile,
275}
276
277impl From<cap_primitives::fs::FileType> for DescriptorType {
278 fn from(ft: cap_primitives::fs::FileType) -> Self {
279 if ft.is_dir() {
280 DescriptorType::Directory
281 } else if ft.is_symlink() {
282 DescriptorType::SymbolicLink
283 } else if ft.is_file() {
284 DescriptorType::RegularFile
285 } else {
286 sys::descriptor_type(ft)
287 }
288 }
289}
290
291pub(crate) struct DescriptorStat {
295 pub type_: DescriptorType,
297 pub link_count: u64,
299 pub size: u64,
302 pub data_access_timestamp: Option<Datetime>,
307 pub data_modification_timestamp: Option<Datetime>,
312 pub status_change_timestamp: Option<Datetime>,
317}
318
319impl DescriptorStat {
320 fn new(meta: &Metadata, link_count: u64) -> Self {
323 Self {
324 type_: meta.file_type().into(),
325 link_count,
326 size: meta.len(),
327 data_access_timestamp: meta
328 .accessed()
329 .ok()
330 .and_then(|t| Datetime::try_from(t.into_std()).ok()),
331 data_modification_timestamp: meta
332 .modified()
333 .ok()
334 .and_then(|t| Datetime::try_from(t.into_std()).ok()),
335 status_change_timestamp: meta
336 .created()
337 .ok()
338 .and_then(|t| Datetime::try_from(t.into_std()).ok()),
339 }
340 }
341}
342
343pub(crate) struct MetadataHashValue {
346 pub lower: u64,
348 pub upper: u64,
350}
351
352impl MetadataHashValue {
353 fn new(identity: impl std::hash::Hash) -> Self {
356 use std::hash::Hasher as _;
359 let mut hasher = hash_map::DefaultHasher::new();
362 identity.hash(&mut hasher);
363 let lower = hasher.finish();
364 let upper = lower ^ 4614256656552045848u64;
374 Self { lower, upper }
375 }
376}
377
378#[derive(Copy, Clone, Debug)]
379pub(crate) enum Advice {
380 Normal,
381 Sequential,
382 Random,
383 WillNeed,
384 DontNeed,
385 NoReuse,
386}
387
388#[cfg(unix)]
389fn from_raw_os_error(err: Option<i32>) -> Option<ErrorCode> {
390 use rustix::io::Errno as RustixErrno;
391 if err.is_none() {
392 return None;
393 }
394 Some(match RustixErrno::from_raw_os_error(err.unwrap()) {
395 RustixErrno::PIPE => ErrorCode::Pipe,
396 RustixErrno::PERM => ErrorCode::NotPermitted,
397 RustixErrno::NOENT => ErrorCode::NoEntry,
398 RustixErrno::NOMEM => ErrorCode::InsufficientMemory,
399 RustixErrno::IO => ErrorCode::Io,
400 RustixErrno::BADF => ErrorCode::BadDescriptor,
401 RustixErrno::BUSY => ErrorCode::Busy,
402 RustixErrno::ACCESS => ErrorCode::Access,
403 RustixErrno::NOTDIR => ErrorCode::NotDirectory,
404 RustixErrno::ISDIR => ErrorCode::IsDirectory,
405 RustixErrno::INVAL => ErrorCode::Invalid,
406 RustixErrno::EXIST => ErrorCode::Exist,
407 RustixErrno::FBIG => ErrorCode::FileTooLarge,
408 RustixErrno::NOSPC => ErrorCode::InsufficientSpace,
409 RustixErrno::SPIPE => ErrorCode::InvalidSeek,
410 RustixErrno::MLINK => ErrorCode::TooManyLinks,
411 RustixErrno::NAMETOOLONG => ErrorCode::NameTooLong,
412 RustixErrno::NOTEMPTY => ErrorCode::NotEmpty,
413 RustixErrno::LOOP => ErrorCode::Loop,
414 RustixErrno::OVERFLOW => ErrorCode::Overflow,
415 RustixErrno::ILSEQ => ErrorCode::IllegalByteSequence,
416 RustixErrno::NOTSUP => ErrorCode::Unsupported,
417 RustixErrno::ALREADY => ErrorCode::Already,
418 RustixErrno::INPROGRESS => ErrorCode::InProgress,
419 RustixErrno::INTR => ErrorCode::Interrupted,
420
421 #[allow(unreachable_patterns, reason = "see comment")]
423 RustixErrno::OPNOTSUPP => ErrorCode::Unsupported,
424
425 _ => return None,
426 })
427}
428
429#[cfg(windows)]
430fn from_raw_os_error(raw_os_error: Option<i32>) -> Option<ErrorCode> {
431 use windows_sys::Win32::Foundation;
432 Some(match raw_os_error.map(|code| code as u32) {
433 Some(Foundation::ERROR_FILE_NOT_FOUND) => ErrorCode::NoEntry,
434 Some(Foundation::ERROR_PATH_NOT_FOUND) => ErrorCode::NoEntry,
435 Some(Foundation::ERROR_ACCESS_DENIED) => ErrorCode::Access,
436 Some(Foundation::ERROR_SHARING_VIOLATION) => ErrorCode::Access,
437 Some(Foundation::ERROR_PRIVILEGE_NOT_HELD) => ErrorCode::NotPermitted,
438 Some(Foundation::ERROR_INVALID_HANDLE) => ErrorCode::BadDescriptor,
439 Some(Foundation::ERROR_INVALID_NAME) => ErrorCode::NoEntry,
440 Some(Foundation::ERROR_NOT_ENOUGH_MEMORY) => ErrorCode::InsufficientMemory,
441 Some(Foundation::ERROR_OUTOFMEMORY) => ErrorCode::InsufficientMemory,
442 Some(Foundation::ERROR_DIR_NOT_EMPTY) => ErrorCode::NotEmpty,
443 Some(Foundation::ERROR_NOT_READY) => ErrorCode::Busy,
444 Some(Foundation::ERROR_BUSY) => ErrorCode::Busy,
445 Some(Foundation::ERROR_NOT_SUPPORTED) => ErrorCode::Unsupported,
446 Some(Foundation::ERROR_FILE_EXISTS) => ErrorCode::Exist,
447 Some(Foundation::ERROR_BROKEN_PIPE) => ErrorCode::Pipe,
448 Some(Foundation::ERROR_BUFFER_OVERFLOW) => ErrorCode::NameTooLong,
449 Some(Foundation::ERROR_NOT_A_REPARSE_POINT) => ErrorCode::Invalid,
450 Some(Foundation::ERROR_NEGATIVE_SEEK) => ErrorCode::Invalid,
451 Some(Foundation::ERROR_DIRECTORY) => ErrorCode::NotDirectory,
452 Some(Foundation::ERROR_ALREADY_EXISTS) => ErrorCode::Exist,
453 Some(Foundation::ERROR_STOPPED_ON_SYMLINK) => ErrorCode::Loop,
454 Some(Foundation::ERROR_DIRECTORY_NOT_SUPPORTED) => ErrorCode::IsDirectory,
455 _ => return None,
456 })
457}
458
459impl<'a> From<&'a std::io::Error> for ErrorCode {
460 fn from(err: &'a std::io::Error) -> ErrorCode {
461 match from_raw_os_error(err.raw_os_error()) {
462 Some(errno) => errno,
463 None => {
464 debug!("unknown raw os error: {err}");
465 match err.kind() {
466 std::io::ErrorKind::NotFound => ErrorCode::NoEntry,
467 std::io::ErrorKind::PermissionDenied => ErrorCode::NotPermitted,
468 std::io::ErrorKind::AlreadyExists => ErrorCode::Exist,
469 std::io::ErrorKind::InvalidInput => ErrorCode::Invalid,
470 _ => ErrorCode::Io,
471 }
472 }
473 }
474 }
475}
476
477impl From<std::io::Error> for ErrorCode {
478 fn from(err: std::io::Error) -> ErrorCode {
479 ErrorCode::from(&err)
480 }
481}
482
483#[derive(Clone)]
484pub enum Descriptor {
485 File(File),
486 Dir(Dir),
487}
488
489impl Descriptor {
490 pub(crate) fn file(&self) -> Result<&File, ErrorCode> {
491 match self {
492 Descriptor::File(f) => Ok(f),
493 Descriptor::Dir(_) => Err(ErrorCode::BadDescriptor),
497 }
498 }
499
500 pub(crate) fn dir(&self) -> Result<&Dir, ErrorCode> {
501 match self {
502 Descriptor::Dir(d) => Ok(d),
503 Descriptor::File(_) => Err(ErrorCode::NotDirectory),
504 }
505 }
506
507 pub(crate) async fn sync_data(&self) -> Result<(), ErrorCode> {
508 match self {
509 Self::File(f) => {
510 match f.run_blocking(|f| f.sync_data()).await {
511 Ok(()) => Ok(()),
512 #[cfg(windows)]
516 Err(err)
517 if err.raw_os_error()
518 == Some(windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED as _) =>
519 {
520 Ok(())
521 }
522 Err(err) => Err(err.into()),
523 }
524 }
525 Self::Dir(d) => {
526 d.run_blocking(|d| {
527 let d = cap_primitives::fs::open(
528 d,
529 std::path::Component::CurDir.as_ref(),
530 OpenOptions::new().read(true),
531 )?;
532 d.sync_data()?;
533 Ok(())
534 })
535 .await
536 }
537 }
538 }
539
540 pub(crate) async fn get_flags(&self) -> Result<DescriptorFlags, ErrorCode> {
541 match self {
542 Self::File(f) => {
543 let mut flags = f.run_blocking(|f| sys::get_flags(f)).await?;
544 if f.open_mode.contains(OpenMode::READ) {
545 flags |= DescriptorFlags::READ;
546 }
547 if f.open_mode.contains(OpenMode::WRITE) {
548 flags |= DescriptorFlags::WRITE;
549 }
550 Ok(flags)
551 }
552 Self::Dir(d) => {
553 let mut flags = d.run_blocking(|d| sys::get_flags(d)).await?;
554 if d.open_mode.contains(OpenMode::READ) {
555 flags |= DescriptorFlags::READ;
556 }
557 if d.open_mode.contains(OpenMode::WRITE) {
558 flags |= DescriptorFlags::MUTATE_DIRECTORY;
559 }
560 Ok(flags)
561 }
562 }
563 }
564
565 pub(crate) async fn get_type(&self) -> Result<DescriptorType, ErrorCode> {
566 match self {
567 Self::File(f) => {
568 let meta = f.run_blocking(|f| Metadata::from_file(f)).await?;
569 Ok(meta.file_type().into())
570 }
571 Self::Dir(_) => Ok(DescriptorType::Directory),
572 }
573 }
574
575 pub(crate) async fn set_times(
576 &self,
577 atim: Option<SystemTime>,
578 mtim: Option<SystemTime>,
579 ) -> Result<(), ErrorCode> {
580 let mut times = std::fs::FileTimes::new();
581 if let Some(atim) = atim {
582 times = times.set_accessed(atim);
583 }
584 if let Some(mtim) = mtim {
585 times = times.set_modified(mtim);
586 }
587 match self {
588 Self::File(f) => {
589 if f.perms.write_not_permitted() {
590 return Err(ErrorCode::NotPermitted);
591 }
592 f.run_blocking(move |f| f.set_times(times)).await?;
593 Ok(())
594 }
595 Self::Dir(d) => {
596 if d.perms.write_not_permitted() {
597 return Err(ErrorCode::NotPermitted);
598 }
599 d.run_blocking(move |d| d.set_times(times)).await?;
600 Ok(())
601 }
602 }
603 }
604
605 pub(crate) async fn sync(&self) -> Result<(), ErrorCode> {
606 match self {
607 Self::File(f) => {
608 match f.run_blocking(|f| f.sync_all()).await {
609 Ok(()) => Ok(()),
610 #[cfg(windows)]
614 Err(err)
615 if err.raw_os_error()
616 == Some(windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED as _) =>
617 {
618 Ok(())
619 }
620 Err(err) => Err(err.into()),
621 }
622 }
623 Self::Dir(d) => {
624 d.run_blocking(|d| {
625 let d = cap_primitives::fs::open(
626 d,
627 std::path::Component::CurDir.as_ref(),
628 OpenOptions::new().read(true),
629 )?;
630 d.sync_all()?;
631 Ok(())
632 })
633 .await
634 }
635 }
636 }
637
638 pub(crate) async fn stat(&self) -> Result<DescriptorStat, ErrorCode> {
639 match self {
640 Self::File(f) => Ok(f.run_blocking(|f| sys::stat(f)).await?),
641 Self::Dir(d) => Ok(d.run_blocking(|f| sys::stat(f)).await?),
642 }
643 }
644
645 pub(crate) async fn is_same_object(&self, other: &Self) -> wasmtime::Result<bool> {
646 let other = match other {
648 Self::File(f) => Arc::clone(&f.file),
649 Self::Dir(d) => Arc::clone(&d.dir),
650 };
651 Ok(match self {
652 Self::File(f) => {
653 f.run_blocking(move |f| sys::is_same_file(f, &other))
654 .await?
655 }
656 Self::Dir(d) => {
657 d.run_blocking(move |d| sys::is_same_file(d, &other))
658 .await?
659 }
660 })
661 }
662
663 pub(crate) async fn metadata_hash(&self) -> Result<MetadataHashValue, ErrorCode> {
664 match self {
665 Self::File(f) => Ok(f.run_blocking(|f| sys::metadata_hash(f)).await?),
666 Self::Dir(d) => Ok(d.run_blocking(|d| sys::metadata_hash(d)).await?),
667 }
668 }
669}
670
671#[derive(Clone)]
672pub struct File {
673 pub file: Arc<std::fs::File>,
679 pub perms: FsPerms,
684 pub open_mode: OpenMode,
689
690 allow_blocking_current_thread: bool,
691}
692
693impl File {
694 pub fn new(
695 file: std::fs::File,
696 perms: FsPerms,
697 open_mode: OpenMode,
698 allow_blocking_current_thread: bool,
699 ) -> Self {
700 Self {
701 file: Arc::new(file),
702 perms,
703 open_mode,
704 allow_blocking_current_thread,
705 }
706 }
707
708 pub(crate) async fn run_blocking<F, R>(&self, body: F) -> R
723 where
724 F: FnOnce(&std::fs::File) -> R + Send + 'static,
725 R: Send + 'static,
726 {
727 match self.as_blocking_file() {
728 Some(file) => body(file),
729 None => self.spawn_blocking(body).await,
730 }
731 }
732
733 pub(crate) fn spawn_blocking<F, R>(&self, body: F) -> AbortOnDropJoinHandle<R>
734 where
735 F: FnOnce(&std::fs::File) -> R + Send + 'static,
736 R: Send + 'static,
737 {
738 let f = self.file.clone();
739 spawn_blocking(move || body(&f))
740 }
741
742 pub(crate) fn as_blocking_file(&self) -> Option<&std::fs::File> {
746 if self.allow_blocking_current_thread {
747 Some(&self.file)
748 } else {
749 None
750 }
751 }
752
753 #[cfg(feature = "p3")]
755 pub(crate) fn as_file(&self) -> &Arc<std::fs::File> {
756 &self.file
757 }
758
759 pub(crate) async fn advise(
760 &self,
761 offset: u64,
762 len: u64,
763 advice: Advice,
764 ) -> Result<(), ErrorCode> {
765 self.run_blocking(move |f| sys::advise(f, offset, len, advice))
766 .await?;
767 Ok(())
768 }
769
770 pub(crate) async fn set_size(&self, size: u64) -> Result<(), ErrorCode> {
771 if self.perms.write_not_permitted() {
772 return Err(ErrorCode::NotPermitted);
773 }
774 self.run_blocking(move |f| f.set_len(size)).await?;
775 Ok(())
776 }
777}
778
779#[derive(Clone)]
780pub struct Dir {
781 pub dir: Arc<std::fs::File>,
789 pub perms: FsPerms,
796 pub open_mode: OpenMode,
801
802 pub(crate) allow_blocking_current_thread: bool,
803}
804
805impl Dir {
806 pub fn new(
807 dir: std::fs::File,
808 perms: FsPerms,
809 open_mode: OpenMode,
810 allow_blocking_current_thread: bool,
811 ) -> Self {
812 Dir {
813 dir: Arc::new(dir),
814 perms,
815 open_mode,
816 allow_blocking_current_thread,
817 }
818 }
819
820 pub(crate) async fn run_blocking<F, R>(&self, body: F) -> R
835 where
836 F: FnOnce(&std::fs::File) -> R + Send + 'static,
837 R: Send + 'static,
838 {
839 if self.allow_blocking_current_thread {
840 body(&self.dir)
841 } else {
842 let d = self.dir.clone();
843 spawn_blocking(move || body(&d)).await
844 }
845 }
846
847 #[cfg(feature = "p3")]
849 pub(crate) fn as_dir(&self) -> &Arc<std::fs::File> {
850 &self.dir
851 }
852
853 pub(crate) async fn create_directory_at(&self, path: String) -> Result<(), ErrorCode> {
854 if self.perms.write_not_permitted() {
855 return Err(ErrorCode::NotPermitted);
856 }
857 self.run_blocking(move |d| {
858 cap_primitives::fs::create_dir(d, path.as_ref(), &DirOptions::new())
859 })
860 .await?;
861 Ok(())
862 }
863
864 pub(crate) async fn stat_at(
865 &self,
866 path_flags: PathFlags,
867 path: String,
868 ) -> Result<DescriptorStat, ErrorCode> {
869 let follow = if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
870 FollowSymlinks::Yes
871 } else {
872 FollowSymlinks::No
873 };
874 let ret = self
875 .run_blocking(move |d| sys::stat_at(d, path.as_ref(), follow))
876 .await?;
877 Ok(ret)
878 }
879
880 pub(crate) async fn set_times_at(
881 &self,
882 path_flags: PathFlags,
883 path: String,
884 atim: Option<SystemTime>,
885 mtim: Option<SystemTime>,
886 ) -> Result<(), ErrorCode> {
887 if self.perms.write_not_permitted() {
888 return Err(ErrorCode::NotPermitted);
889 }
890 let atim =
891 atim.map(|t| SystemTimeSpec::Absolute(cap_primitives::time::SystemTime::from_std(t)));
892 let mtim =
893 mtim.map(|t| SystemTimeSpec::Absolute(cap_primitives::time::SystemTime::from_std(t)));
894 if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
895 self.run_blocking(move |d| cap_primitives::fs::set_times(d, path.as_ref(), atim, mtim))
896 .await?;
897 } else {
898 self.run_blocking(move |d| {
899 cap_primitives::fs::set_times_nofollow(d, path.as_ref(), atim, mtim)
900 })
901 .await?;
902 }
903 Ok(())
904 }
905
906 pub(crate) async fn link_at(
907 &self,
908 old_path_flags: PathFlags,
909 old_path: String,
910 new_dir: &Self,
911 new_path: String,
912 ) -> Result<(), ErrorCode> {
913 if self.perms.write_not_permitted() {
914 return Err(ErrorCode::NotPermitted);
915 }
916 if new_dir.perms.write_not_permitted() {
917 return Err(ErrorCode::NotPermitted);
918 }
919 if old_path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
920 return Err(ErrorCode::Invalid);
921 }
922 if self.perms != new_dir.perms {
923 return Err(ErrorCode::NotPermitted);
924 }
925 let new_dir_handle = Arc::clone(&new_dir.dir);
926 self.run_blocking(move |d| {
927 cap_primitives::fs::hard_link(d, old_path.as_ref(), &new_dir_handle, new_path.as_ref())
928 })
929 .await?;
930 Ok(())
931 }
932
933 pub(crate) async fn open_at(
934 &self,
935 path_flags: PathFlags,
936 path: String,
937 oflags: OpenFlags,
938 flags: DescriptorFlags,
939 allow_blocking_current_thread: bool,
940 ) -> Result<Descriptor, ErrorCode> {
941 let mut create = false;
943 let mut open_mode = OpenMode::empty();
945 let mut opts = OpenOptions::new();
947 sys::maybe_dir(&mut opts);
948
949 if oflags.contains(OpenFlags::CREATE) {
950 if oflags.contains(OpenFlags::EXCLUSIVE) {
951 opts.create_new(true);
952 } else {
953 opts.create(true);
954 }
955 create = true;
956 opts.write(true);
957 open_mode |= OpenMode::WRITE;
958 }
959
960 if oflags.contains(OpenFlags::TRUNCATE) {
961 opts.truncate(true).write(true);
962 open_mode |= OpenMode::WRITE;
963 }
964 if flags.contains(DescriptorFlags::READ) {
965 opts.read(true);
966 open_mode |= OpenMode::READ;
967 }
968 if flags.contains(DescriptorFlags::WRITE) {
969 opts.write(true);
970 open_mode |= OpenMode::WRITE;
971 } else {
972 opts.read(true);
975 open_mode |= OpenMode::READ;
976 }
977
978 {
983 use cap_fs_ext_avoid_using_this::OpenOptionsFollowExt;
984 if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
985 opts.follow(FollowSymlinks::Yes);
986 } else {
987 opts.follow(FollowSymlinks::No);
988 }
989 }
990
991 if flags.contains(DescriptorFlags::FILE_INTEGRITY_SYNC)
993 || flags.contains(DescriptorFlags::DATA_INTEGRITY_SYNC)
994 || flags.contains(DescriptorFlags::REQUESTED_WRITE_SYNC)
995 {
996 return Err(ErrorCode::Unsupported);
997 }
998
999 if oflags.contains(OpenFlags::DIRECTORY) {
1000 if oflags.contains(OpenFlags::CREATE)
1001 || oflags.contains(OpenFlags::EXCLUSIVE)
1002 || oflags.contains(OpenFlags::TRUNCATE)
1003 {
1004 return Err(ErrorCode::Invalid);
1005 }
1006 }
1007
1008 if self.perms.write_not_permitted() {
1011 if create || open_mode.contains(OpenMode::WRITE) {
1012 return Err(ErrorCode::NotPermitted);
1013 }
1014 }
1015
1016 enum OpenResult {
1020 Dir(std::fs::File),
1021 File(std::fs::File),
1022 NotDir,
1023 }
1024
1025 let opened = self
1026 .run_blocking::<_, std::io::Result<OpenResult>>(move |d| {
1027 let opened = cap_primitives::fs::open(d, path.as_ref(), &opts)?;
1028 if Metadata::from_file(&opened)?.is_dir() {
1029 Ok(OpenResult::Dir(opened))
1030 } else if oflags.contains(OpenFlags::DIRECTORY) {
1031 Ok(OpenResult::NotDir)
1032 } else {
1033 Ok(OpenResult::File(opened))
1034 }
1035 })
1036 .await?;
1037
1038 match opened {
1039 #[cfg(windows)]
1043 OpenResult::Dir(_) if flags.contains(DescriptorFlags::WRITE) => {
1044 Err(ErrorCode::IsDirectory)
1045 }
1046
1047 OpenResult::Dir(dir) => Ok(Descriptor::Dir(Dir::new(
1048 dir,
1049 self.perms,
1050 open_mode,
1051 allow_blocking_current_thread,
1052 ))),
1053
1054 OpenResult::File(file) => Ok(Descriptor::File(File::new(
1055 file,
1056 self.perms,
1057 open_mode,
1058 allow_blocking_current_thread,
1059 ))),
1060
1061 OpenResult::NotDir => Err(ErrorCode::NotDirectory),
1062 }
1063 }
1064
1065 pub(crate) async fn readlink_at(&self, path: String) -> Result<String, ErrorCode> {
1066 let link = self
1067 .run_blocking(move |d| cap_primitives::fs::read_link(d, path.as_ref()))
1068 .await?;
1069 link.into_os_string()
1070 .into_string()
1071 .or(Err(ErrorCode::IllegalByteSequence))
1072 }
1073
1074 pub(crate) async fn remove_directory_at(&self, path: String) -> Result<(), ErrorCode> {
1075 if self.perms.write_not_permitted() {
1076 return Err(ErrorCode::NotPermitted);
1077 }
1078 self.run_blocking(move |d| cap_primitives::fs::remove_dir(d, path.as_ref()))
1079 .await?;
1080 Ok(())
1081 }
1082
1083 pub(crate) async fn rename_at(
1084 &self,
1085 old_path: String,
1086 new_dir: &Self,
1087 new_path: String,
1088 ) -> Result<(), ErrorCode> {
1089 if self.perms.write_not_permitted() {
1090 return Err(ErrorCode::NotPermitted);
1091 }
1092 if new_dir.perms.write_not_permitted() {
1093 return Err(ErrorCode::NotPermitted);
1094 }
1095 if self.perms != new_dir.perms {
1096 return Err(ErrorCode::NotPermitted);
1097 }
1098 let new_dir_handle = Arc::clone(&new_dir.dir);
1099 self.run_blocking(move |d| {
1100 cap_primitives::fs::rename(d, old_path.as_ref(), &new_dir_handle, new_path.as_ref())
1101 })
1102 .await?;
1103 Ok(())
1104 }
1105
1106 pub(crate) async fn symlink_at(
1107 &self,
1108 src_path: String,
1109 dest_path: String,
1110 ) -> Result<(), ErrorCode> {
1111 if self.perms.write_not_permitted() {
1112 return Err(ErrorCode::NotPermitted);
1113 }
1114 self.run_blocking(move |d| sys::symlink(src_path.as_ref(), d, dest_path.as_ref()))
1115 .await?;
1116 Ok(())
1117 }
1118
1119 pub(crate) async fn unlink_file_at(&self, path: String) -> Result<(), ErrorCode> {
1120 if self.perms.write_not_permitted() {
1121 return Err(ErrorCode::NotPermitted);
1122 }
1123 self.run_blocking(move |d| sys::remove_file_or_symlink(d, path.as_ref()))
1124 .await?;
1125 Ok(())
1126 }
1127
1128 pub(crate) async fn metadata_hash_at(
1129 &self,
1130 path_flags: PathFlags,
1131 path: String,
1132 ) -> Result<MetadataHashValue, ErrorCode> {
1133 let follow = if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
1135 FollowSymlinks::Yes
1136 } else {
1137 FollowSymlinks::No
1138 };
1139 let hash = self
1140 .run_blocking(move |d| sys::metadata_hash_at(d, path.as_ref(), follow))
1141 .await?;
1142 Ok(hash)
1143 }
1144}
1145
1146impl WasiFilesystemCtxView<'_> {
1147 pub(crate) fn get_directories(
1148 &mut self,
1149 ) -> wasmtime::Result<Vec<(Resource<Descriptor>, String)>> {
1150 let preopens = self.ctx.preopens.clone();
1151 let mut results = Vec::with_capacity(preopens.len());
1152 for (dir, name) in preopens {
1153 let fd = self
1154 .table
1155 .push(Descriptor::Dir(dir))
1156 .with_context(|| format!("failed to push preopen {name}"))?;
1157 results.push((fd, name));
1158 }
1159 Ok(results)
1160 }
1161}