1use crate::clocks::Datetime;
2use crate::runtime::{AbortOnDropJoinHandle, spawn_blocking};
3use cap_fs_ext::{FileTypeExt as _, MetadataExt as _};
4use fs_set_times::SystemTimeSpec;
5use std::collections::hash_map;
6use std::sync::Arc;
7use tracing::debug;
8use wasmtime::component::{HasData, Resource, ResourceTable};
9use wasmtime::error::Context as _;
10
11pub struct WasiFilesystem;
50
51impl HasData for WasiFilesystem {
52 type Data<'a> = WasiFilesystemCtxView<'a>;
53}
54
55#[derive(Clone, Default)]
56pub struct WasiFilesystemCtx {
57 pub(crate) allow_blocking_current_thread: bool,
58 pub(crate) preopens: Vec<(Dir, String)>,
59}
60
61pub struct WasiFilesystemCtxView<'a> {
62 pub ctx: &'a mut WasiFilesystemCtx,
63 pub table: &'a mut ResourceTable,
64}
65
66pub trait WasiFilesystemView: Send {
67 fn filesystem(&mut self) -> WasiFilesystemCtxView<'_>;
68}
69
70bitflags::bitflags! {
71 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
72 pub struct FilePerms: usize {
73 const READ = 0b1;
74 const WRITE = 0b10;
75 }
76}
77
78bitflags::bitflags! {
79 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
80 pub struct OpenMode: usize {
81 const READ = 0b1;
82 const WRITE = 0b10;
83 }
84}
85
86bitflags::bitflags! {
87 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
92 pub struct DirPerms: usize {
93 const READ = 0b1;
96
97 const MUTATE = 0b10;
100 }
101}
102
103bitflags::bitflags! {
104 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
106 pub(crate) struct PathFlags: usize {
107 const SYMLINK_FOLLOW = 0b1;
110 }
111}
112
113bitflags::bitflags! {
114 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
116 pub(crate) struct OpenFlags: usize {
117 const CREATE = 0b1;
119 const DIRECTORY = 0b10;
121 const EXCLUSIVE = 0b100;
123 const TRUNCATE = 0b1000;
125 }
126}
127
128bitflags::bitflags! {
129 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
133 pub(crate) struct DescriptorFlags: usize {
134 const READ = 0b1;
136 const WRITE = 0b10;
138 const FILE_INTEGRITY_SYNC = 0b100;
146 const DATA_INTEGRITY_SYNC = 0b1000;
154 const REQUESTED_WRITE_SYNC = 0b10000;
161 const MUTATE_DIRECTORY = 0b100000;
171 }
172}
173
174#[cfg_attr(
179 windows,
180 expect(dead_code, reason = "on Windows, some of these are not used")
181)]
182pub(crate) enum ErrorCode {
183 Access,
185 Already,
187 BadDescriptor,
189 Busy,
191 Exist,
193 FileTooLarge,
195 IllegalByteSequence,
197 InProgress,
199 Interrupted,
201 Invalid,
203 Io,
205 IsDirectory,
207 Loop,
209 TooManyLinks,
211 NameTooLong,
213 NoEntry,
215 InsufficientMemory,
217 InsufficientSpace,
219 NotDirectory,
221 NotEmpty,
223 Unsupported,
225 Overflow,
227 NotPermitted,
229 Pipe,
231 InvalidSeek,
233}
234
235fn datetime_from(t: std::time::SystemTime) -> Datetime {
236 Datetime::try_from(cap_std::time::SystemTime::from_std(t)).unwrap()
238}
239
240pub(crate) enum DescriptorType {
244 Unknown,
247 BlockDevice,
249 CharacterDevice,
251 Directory,
253 SymbolicLink,
255 RegularFile,
257}
258
259impl From<cap_std::fs::FileType> for DescriptorType {
260 fn from(ft: cap_std::fs::FileType) -> Self {
261 if ft.is_dir() {
262 DescriptorType::Directory
263 } else if ft.is_symlink() {
264 DescriptorType::SymbolicLink
265 } else if ft.is_block_device() {
266 DescriptorType::BlockDevice
267 } else if ft.is_char_device() {
268 DescriptorType::CharacterDevice
269 } else if ft.is_file() {
270 DescriptorType::RegularFile
271 } else {
272 DescriptorType::Unknown
273 }
274 }
275}
276
277pub(crate) struct DescriptorStat {
281 pub type_: DescriptorType,
283 pub link_count: u64,
285 pub size: u64,
288 pub data_access_timestamp: Option<Datetime>,
293 pub data_modification_timestamp: Option<Datetime>,
298 pub status_change_timestamp: Option<Datetime>,
303}
304
305impl From<cap_std::fs::Metadata> for DescriptorStat {
306 fn from(meta: cap_std::fs::Metadata) -> Self {
307 Self {
308 type_: meta.file_type().into(),
309 link_count: meta.nlink(),
310 size: meta.len(),
311 data_access_timestamp: meta.accessed().map(|t| datetime_from(t.into_std())).ok(),
312 data_modification_timestamp: meta.modified().map(|t| datetime_from(t.into_std())).ok(),
313 status_change_timestamp: meta.created().map(|t| datetime_from(t.into_std())).ok(),
314 }
315 }
316}
317
318pub(crate) struct MetadataHashValue {
321 pub lower: u64,
323 pub upper: u64,
325}
326
327impl From<&cap_std::fs::Metadata> for MetadataHashValue {
328 fn from(meta: &cap_std::fs::Metadata) -> Self {
329 use cap_fs_ext::MetadataExt;
330 use std::hash::Hasher;
333 let mut hasher = hash_map::DefaultHasher::new();
336 hasher.write_u64(meta.dev());
337 hasher.write_u64(meta.ino());
338 let lower = hasher.finish();
339 let upper = lower ^ 4614256656552045848u64;
349 Self { lower, upper }
350 }
351}
352
353#[cfg(unix)]
354fn from_raw_os_error(err: Option<i32>) -> Option<ErrorCode> {
355 use rustix::io::Errno as RustixErrno;
356 if err.is_none() {
357 return None;
358 }
359 Some(match RustixErrno::from_raw_os_error(err.unwrap()) {
360 RustixErrno::PIPE => ErrorCode::Pipe,
361 RustixErrno::PERM => ErrorCode::NotPermitted,
362 RustixErrno::NOENT => ErrorCode::NoEntry,
363 RustixErrno::NOMEM => ErrorCode::InsufficientMemory,
364 RustixErrno::IO => ErrorCode::Io,
365 RustixErrno::BADF => ErrorCode::BadDescriptor,
366 RustixErrno::BUSY => ErrorCode::Busy,
367 RustixErrno::ACCESS => ErrorCode::Access,
368 RustixErrno::NOTDIR => ErrorCode::NotDirectory,
369 RustixErrno::ISDIR => ErrorCode::IsDirectory,
370 RustixErrno::INVAL => ErrorCode::Invalid,
371 RustixErrno::EXIST => ErrorCode::Exist,
372 RustixErrno::FBIG => ErrorCode::FileTooLarge,
373 RustixErrno::NOSPC => ErrorCode::InsufficientSpace,
374 RustixErrno::SPIPE => ErrorCode::InvalidSeek,
375 RustixErrno::MLINK => ErrorCode::TooManyLinks,
376 RustixErrno::NAMETOOLONG => ErrorCode::NameTooLong,
377 RustixErrno::NOTEMPTY => ErrorCode::NotEmpty,
378 RustixErrno::LOOP => ErrorCode::Loop,
379 RustixErrno::OVERFLOW => ErrorCode::Overflow,
380 RustixErrno::ILSEQ => ErrorCode::IllegalByteSequence,
381 RustixErrno::NOTSUP => ErrorCode::Unsupported,
382 RustixErrno::ALREADY => ErrorCode::Already,
383 RustixErrno::INPROGRESS => ErrorCode::InProgress,
384 RustixErrno::INTR => ErrorCode::Interrupted,
385
386 #[allow(unreachable_patterns, reason = "see comment")]
388 RustixErrno::OPNOTSUPP => ErrorCode::Unsupported,
389
390 _ => return None,
391 })
392}
393
394#[cfg(windows)]
395fn from_raw_os_error(raw_os_error: Option<i32>) -> Option<ErrorCode> {
396 use windows_sys::Win32::Foundation;
397 Some(match raw_os_error.map(|code| code as u32) {
398 Some(Foundation::ERROR_FILE_NOT_FOUND) => ErrorCode::NoEntry,
399 Some(Foundation::ERROR_PATH_NOT_FOUND) => ErrorCode::NoEntry,
400 Some(Foundation::ERROR_ACCESS_DENIED) => ErrorCode::Access,
401 Some(Foundation::ERROR_SHARING_VIOLATION) => ErrorCode::Access,
402 Some(Foundation::ERROR_PRIVILEGE_NOT_HELD) => ErrorCode::NotPermitted,
403 Some(Foundation::ERROR_INVALID_HANDLE) => ErrorCode::BadDescriptor,
404 Some(Foundation::ERROR_INVALID_NAME) => ErrorCode::NoEntry,
405 Some(Foundation::ERROR_NOT_ENOUGH_MEMORY) => ErrorCode::InsufficientMemory,
406 Some(Foundation::ERROR_OUTOFMEMORY) => ErrorCode::InsufficientMemory,
407 Some(Foundation::ERROR_DIR_NOT_EMPTY) => ErrorCode::NotEmpty,
408 Some(Foundation::ERROR_NOT_READY) => ErrorCode::Busy,
409 Some(Foundation::ERROR_BUSY) => ErrorCode::Busy,
410 Some(Foundation::ERROR_NOT_SUPPORTED) => ErrorCode::Unsupported,
411 Some(Foundation::ERROR_FILE_EXISTS) => ErrorCode::Exist,
412 Some(Foundation::ERROR_BROKEN_PIPE) => ErrorCode::Pipe,
413 Some(Foundation::ERROR_BUFFER_OVERFLOW) => ErrorCode::NameTooLong,
414 Some(Foundation::ERROR_NOT_A_REPARSE_POINT) => ErrorCode::Invalid,
415 Some(Foundation::ERROR_NEGATIVE_SEEK) => ErrorCode::Invalid,
416 Some(Foundation::ERROR_DIRECTORY) => ErrorCode::NotDirectory,
417 Some(Foundation::ERROR_ALREADY_EXISTS) => ErrorCode::Exist,
418 Some(Foundation::ERROR_STOPPED_ON_SYMLINK) => ErrorCode::Loop,
419 Some(Foundation::ERROR_DIRECTORY_NOT_SUPPORTED) => ErrorCode::IsDirectory,
420 _ => return None,
421 })
422}
423
424impl<'a> From<&'a std::io::Error> for ErrorCode {
425 fn from(err: &'a std::io::Error) -> ErrorCode {
426 match from_raw_os_error(err.raw_os_error()) {
427 Some(errno) => errno,
428 None => {
429 debug!("unknown raw os error: {err}");
430 match err.kind() {
431 std::io::ErrorKind::NotFound => ErrorCode::NoEntry,
432 std::io::ErrorKind::PermissionDenied => ErrorCode::NotPermitted,
433 std::io::ErrorKind::AlreadyExists => ErrorCode::Exist,
434 std::io::ErrorKind::InvalidInput => ErrorCode::Invalid,
435 _ => ErrorCode::Io,
436 }
437 }
438 }
439 }
440}
441
442impl From<std::io::Error> for ErrorCode {
443 fn from(err: std::io::Error) -> ErrorCode {
444 ErrorCode::from(&err)
445 }
446}
447
448#[derive(Clone)]
449pub enum Descriptor {
450 File(File),
451 Dir(Dir),
452}
453
454impl Descriptor {
455 pub(crate) fn file(&self) -> Result<&File, ErrorCode> {
456 match self {
457 Descriptor::File(f) => Ok(f),
458 Descriptor::Dir(_) => Err(ErrorCode::BadDescriptor),
459 }
460 }
461
462 pub(crate) fn dir(&self) -> Result<&Dir, ErrorCode> {
463 match self {
464 Descriptor::Dir(d) => Ok(d),
465 Descriptor::File(_) => Err(ErrorCode::NotDirectory),
466 }
467 }
468
469 async fn get_metadata(&self) -> std::io::Result<cap_std::fs::Metadata> {
470 match self {
471 Self::File(f) => {
472 f.run_blocking(|f| f.metadata()).await
474 }
475 Self::Dir(d) => {
476 d.run_blocking(|d| d.dir_metadata()).await
478 }
479 }
480 }
481
482 pub(crate) async fn sync_data(&self) -> Result<(), ErrorCode> {
483 match self {
484 Self::File(f) => {
485 match f.run_blocking(|f| f.sync_data()).await {
486 Ok(()) => Ok(()),
487 #[cfg(windows)]
491 Err(err)
492 if err.raw_os_error()
493 == Some(windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED as _) =>
494 {
495 Ok(())
496 }
497 Err(err) => Err(err.into()),
498 }
499 }
500 Self::Dir(d) => {
501 d.run_blocking(|d| {
502 let d = d.open(std::path::Component::CurDir)?;
503 d.sync_data()?;
504 Ok(())
505 })
506 .await
507 }
508 }
509 }
510
511 pub(crate) async fn get_flags(&self) -> Result<DescriptorFlags, ErrorCode> {
512 use system_interface::fs::{FdFlags, GetSetFdFlags};
513
514 fn get_from_fdflags(flags: FdFlags) -> DescriptorFlags {
515 let mut out = DescriptorFlags::empty();
516 if flags.contains(FdFlags::DSYNC) {
517 out |= DescriptorFlags::REQUESTED_WRITE_SYNC;
518 }
519 if flags.contains(FdFlags::RSYNC) {
520 out |= DescriptorFlags::DATA_INTEGRITY_SYNC;
521 }
522 if flags.contains(FdFlags::SYNC) {
523 out |= DescriptorFlags::FILE_INTEGRITY_SYNC;
524 }
525 out
526 }
527 match self {
528 Self::File(f) => {
529 let flags = f.run_blocking(|f| f.get_fd_flags()).await?;
530 let mut flags = get_from_fdflags(flags);
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 flags = d.run_blocking(|d| d.get_fd_flags()).await?;
541 let mut flags = get_from_fdflags(flags);
542 if d.open_mode.contains(OpenMode::READ) {
543 flags |= DescriptorFlags::READ;
544 }
545 if d.open_mode.contains(OpenMode::WRITE) {
546 flags |= DescriptorFlags::MUTATE_DIRECTORY;
547 }
548 Ok(flags)
549 }
550 }
551 }
552
553 pub(crate) async fn get_type(&self) -> Result<DescriptorType, ErrorCode> {
554 match self {
555 Self::File(f) => {
556 let meta = f.run_blocking(|f| f.metadata()).await?;
557 Ok(meta.file_type().into())
558 }
559 Self::Dir(_) => Ok(DescriptorType::Directory),
560 }
561 }
562
563 pub(crate) async fn set_times(
564 &self,
565 atim: Option<SystemTimeSpec>,
566 mtim: Option<SystemTimeSpec>,
567 ) -> Result<(), ErrorCode> {
568 use fs_set_times::SetTimes as _;
569 match self {
570 Self::File(f) => {
571 if !f.perms.contains(FilePerms::WRITE) {
572 return Err(ErrorCode::NotPermitted);
573 }
574 f.run_blocking(|f| f.set_times(atim, mtim)).await?;
575 Ok(())
576 }
577 Self::Dir(d) => {
578 if !d.perms.contains(DirPerms::MUTATE) {
579 return Err(ErrorCode::NotPermitted);
580 }
581 d.run_blocking(|d| d.set_times(atim, mtim)).await?;
582 Ok(())
583 }
584 }
585 }
586
587 pub(crate) async fn sync(&self) -> Result<(), ErrorCode> {
588 match self {
589 Self::File(f) => {
590 match f.run_blocking(|f| f.sync_all()).await {
591 Ok(()) => Ok(()),
592 #[cfg(windows)]
596 Err(err)
597 if err.raw_os_error()
598 == Some(windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED as _) =>
599 {
600 Ok(())
601 }
602 Err(err) => Err(err.into()),
603 }
604 }
605 Self::Dir(d) => {
606 d.run_blocking(|d| {
607 let d = d.open(std::path::Component::CurDir)?;
608 d.sync_all()?;
609 Ok(())
610 })
611 .await
612 }
613 }
614 }
615
616 pub(crate) async fn stat(&self) -> Result<DescriptorStat, ErrorCode> {
617 match self {
618 Self::File(f) => {
619 let meta = f.run_blocking(|f| f.metadata()).await?;
621 Ok(meta.into())
622 }
623 Self::Dir(d) => {
624 let meta = d.run_blocking(|d| d.dir_metadata()).await?;
626 Ok(meta.into())
627 }
628 }
629 }
630
631 pub(crate) async fn is_same_object(&self, other: &Self) -> wasmtime::Result<bool> {
632 use cap_fs_ext::MetadataExt;
633 let meta_a = self.get_metadata().await?;
634 let meta_b = other.get_metadata().await?;
635 if meta_a.dev() == meta_b.dev() && meta_a.ino() == meta_b.ino() {
636 debug_assert_eq!(
639 MetadataHashValue::from(&meta_a).upper,
640 MetadataHashValue::from(&meta_b).upper,
641 );
642 debug_assert_eq!(
643 MetadataHashValue::from(&meta_a).lower,
644 MetadataHashValue::from(&meta_b).lower,
645 );
646 Ok(true)
647 } else {
648 Ok(false)
650 }
651 }
652
653 pub(crate) async fn metadata_hash(&self) -> Result<MetadataHashValue, ErrorCode> {
654 let meta = self.get_metadata().await?;
655 Ok(MetadataHashValue::from(&meta))
656 }
657}
658
659#[derive(Clone)]
660pub struct File {
661 pub file: Arc<cap_std::fs::File>,
667 pub perms: FilePerms,
671 pub open_mode: OpenMode,
676
677 allow_blocking_current_thread: bool,
678}
679
680impl File {
681 pub fn new(
682 file: cap_std::fs::File,
683 perms: FilePerms,
684 open_mode: OpenMode,
685 allow_blocking_current_thread: bool,
686 ) -> Self {
687 Self {
688 file: Arc::new(file),
689 perms,
690 open_mode,
691 allow_blocking_current_thread,
692 }
693 }
694
695 pub(crate) async fn run_blocking<F, R>(&self, body: F) -> R
710 where
711 F: FnOnce(&cap_std::fs::File) -> R + Send + 'static,
712 R: Send + 'static,
713 {
714 match self.as_blocking_file() {
715 Some(file) => body(file),
716 None => self.spawn_blocking(body).await,
717 }
718 }
719
720 pub(crate) fn spawn_blocking<F, R>(&self, body: F) -> AbortOnDropJoinHandle<R>
721 where
722 F: FnOnce(&cap_std::fs::File) -> R + Send + 'static,
723 R: Send + 'static,
724 {
725 let f = self.file.clone();
726 spawn_blocking(move || body(&f))
727 }
728
729 pub(crate) fn as_blocking_file(&self) -> Option<&cap_std::fs::File> {
733 if self.allow_blocking_current_thread {
734 Some(&self.file)
735 } else {
736 None
737 }
738 }
739
740 #[cfg(feature = "p3")]
742 pub(crate) fn as_file(&self) -> &Arc<cap_std::fs::File> {
743 &self.file
744 }
745
746 pub(crate) async fn advise(
747 &self,
748 offset: u64,
749 len: u64,
750 advice: system_interface::fs::Advice,
751 ) -> Result<(), ErrorCode> {
752 use system_interface::fs::FileIoExt as _;
753 self.run_blocking(move |f| f.advise(offset, len, advice))
754 .await?;
755 Ok(())
756 }
757
758 pub(crate) async fn set_size(&self, size: u64) -> Result<(), ErrorCode> {
759 if !self.perms.contains(FilePerms::WRITE) {
760 return Err(ErrorCode::NotPermitted);
761 }
762 self.run_blocking(move |f| f.set_len(size)).await?;
763 Ok(())
764 }
765}
766
767#[derive(Clone)]
768pub struct Dir {
769 pub dir: Arc<cap_std::fs::Dir>,
774 pub perms: DirPerms,
781 pub file_perms: FilePerms,
783 pub open_mode: OpenMode,
788
789 pub(crate) allow_blocking_current_thread: bool,
790}
791
792impl Dir {
793 pub fn new(
794 dir: cap_std::fs::Dir,
795 perms: DirPerms,
796 file_perms: FilePerms,
797 open_mode: OpenMode,
798 allow_blocking_current_thread: bool,
799 ) -> Self {
800 Dir {
801 dir: Arc::new(dir),
802 perms,
803 file_perms,
804 open_mode,
805 allow_blocking_current_thread,
806 }
807 }
808
809 pub(crate) async fn run_blocking<F, R>(&self, body: F) -> R
824 where
825 F: FnOnce(&cap_std::fs::Dir) -> R + Send + 'static,
826 R: Send + 'static,
827 {
828 if self.allow_blocking_current_thread {
829 body(&self.dir)
830 } else {
831 let d = self.dir.clone();
832 spawn_blocking(move || body(&d)).await
833 }
834 }
835
836 #[cfg(feature = "p3")]
838 pub(crate) fn as_dir(&self) -> &Arc<cap_std::fs::Dir> {
839 &self.dir
840 }
841
842 pub(crate) async fn create_directory_at(&self, path: String) -> Result<(), ErrorCode> {
843 if !self.perms.contains(DirPerms::MUTATE) {
844 return Err(ErrorCode::NotPermitted);
845 }
846 self.run_blocking(move |d| d.create_dir(&path)).await?;
847 Ok(())
848 }
849
850 pub(crate) async fn stat_at(
851 &self,
852 path_flags: PathFlags,
853 path: String,
854 ) -> Result<DescriptorStat, ErrorCode> {
855 if !self.perms.contains(DirPerms::READ) {
856 return Err(ErrorCode::NotPermitted);
857 }
858
859 let meta = if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
860 self.run_blocking(move |d| d.metadata(&path)).await?
861 } else {
862 self.run_blocking(move |d| d.symlink_metadata(&path))
863 .await?
864 };
865 Ok(meta.into())
866 }
867
868 pub(crate) async fn set_times_at(
869 &self,
870 path_flags: PathFlags,
871 path: String,
872 atim: Option<SystemTimeSpec>,
873 mtim: Option<SystemTimeSpec>,
874 ) -> Result<(), ErrorCode> {
875 use cap_fs_ext::DirExt as _;
876
877 if !self.perms.contains(DirPerms::MUTATE) {
878 return Err(ErrorCode::NotPermitted);
879 }
880 if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
881 self.run_blocking(move |d| {
882 d.set_times(
883 &path,
884 atim.map(cap_fs_ext::SystemTimeSpec::from_std),
885 mtim.map(cap_fs_ext::SystemTimeSpec::from_std),
886 )
887 })
888 .await?;
889 } else {
890 self.run_blocking(move |d| {
891 d.set_symlink_times(
892 &path,
893 atim.map(cap_fs_ext::SystemTimeSpec::from_std),
894 mtim.map(cap_fs_ext::SystemTimeSpec::from_std),
895 )
896 })
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 use system_interface::fs::{FdFlags, GetSetFdFlags};
934
935 if !self.perms.contains(DirPerms::READ) {
936 return Err(ErrorCode::NotPermitted);
937 }
938
939 if !self.perms.contains(DirPerms::MUTATE) {
940 if oflags.contains(OpenFlags::CREATE) || oflags.contains(OpenFlags::TRUNCATE) {
941 return Err(ErrorCode::NotPermitted);
942 }
943 if flags.contains(DescriptorFlags::WRITE) {
944 return Err(ErrorCode::NotPermitted);
945 }
946 }
947
948 let mut create = false;
950 let mut open_mode = OpenMode::empty();
952 let mut opts = cap_std::fs::OpenOptions::new();
954 opts.maybe_dir(true);
955
956 if oflags.contains(OpenFlags::CREATE) {
957 if oflags.contains(OpenFlags::EXCLUSIVE) {
958 opts.create_new(true);
959 } else {
960 opts.create(true);
961 }
962 create = true;
963 opts.write(true);
964 open_mode |= OpenMode::WRITE;
965 }
966
967 if oflags.contains(OpenFlags::TRUNCATE) {
968 opts.truncate(true).write(true);
969 }
970 if flags.contains(DescriptorFlags::READ) {
971 opts.read(true);
972 open_mode |= OpenMode::READ;
973 }
974 if flags.contains(DescriptorFlags::WRITE) {
975 opts.write(true);
976 open_mode |= OpenMode::WRITE;
977 } else {
978 opts.read(true);
981 open_mode |= OpenMode::READ;
982 }
983 if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
984 opts.follow(FollowSymlinks::Yes);
985 } else {
986 opts.follow(FollowSymlinks::No);
987 }
988
989 if flags.contains(DescriptorFlags::FILE_INTEGRITY_SYNC)
991 || flags.contains(DescriptorFlags::DATA_INTEGRITY_SYNC)
992 || flags.contains(DescriptorFlags::REQUESTED_WRITE_SYNC)
993 {
994 return Err(ErrorCode::Unsupported);
995 }
996
997 if oflags.contains(OpenFlags::DIRECTORY) {
998 if oflags.contains(OpenFlags::CREATE)
999 || oflags.contains(OpenFlags::EXCLUSIVE)
1000 || oflags.contains(OpenFlags::TRUNCATE)
1001 {
1002 return Err(ErrorCode::Invalid);
1003 }
1004 }
1005
1006 if !self.perms.contains(DirPerms::MUTATE) && create {
1009 return Err(ErrorCode::NotPermitted);
1010 }
1011 if !self.file_perms.contains(FilePerms::WRITE) && open_mode.contains(OpenMode::WRITE) {
1012 return Err(ErrorCode::NotPermitted);
1013 }
1014
1015 enum OpenResult {
1019 Dir(cap_std::fs::Dir),
1020 File(cap_std::fs::File),
1021 NotDir,
1022 }
1023
1024 let opened = self
1025 .run_blocking::<_, std::io::Result<OpenResult>>(move |d| {
1026 let mut 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 let set_fd_flags = opened.new_set_fd_flags(FdFlags::NONBLOCK)?;
1037 opened.set_fd_flags(set_fd_flags)?;
1038 Ok(OpenResult::File(opened))
1039 }
1040 })
1041 .await?;
1042
1043 match opened {
1044 #[cfg(windows)]
1048 OpenResult::Dir(_) if flags.contains(DescriptorFlags::WRITE) => {
1049 Err(ErrorCode::IsDirectory)
1050 }
1051
1052 OpenResult::Dir(dir) => Ok(Descriptor::Dir(Dir::new(
1053 dir,
1054 self.perms,
1055 self.file_perms,
1056 open_mode,
1057 allow_blocking_current_thread,
1058 ))),
1059
1060 OpenResult::File(file) => Ok(Descriptor::File(File::new(
1061 file,
1062 self.file_perms,
1063 open_mode,
1064 allow_blocking_current_thread,
1065 ))),
1066
1067 OpenResult::NotDir => Err(ErrorCode::NotDirectory),
1068 }
1069 }
1070
1071 pub(crate) async fn readlink_at(&self, path: String) -> Result<String, ErrorCode> {
1072 if !self.perms.contains(DirPerms::READ) {
1073 return Err(ErrorCode::NotPermitted);
1074 }
1075 let link = self.run_blocking(move |d| d.read_link(&path)).await?;
1076 link.into_os_string()
1077 .into_string()
1078 .or(Err(ErrorCode::IllegalByteSequence))
1079 }
1080
1081 pub(crate) async fn remove_directory_at(&self, path: String) -> Result<(), ErrorCode> {
1082 if !self.perms.contains(DirPerms::MUTATE) {
1083 return Err(ErrorCode::NotPermitted);
1084 }
1085 self.run_blocking(move |d| d.remove_dir(&path)).await?;
1086 Ok(())
1087 }
1088
1089 pub(crate) async fn rename_at(
1090 &self,
1091 old_path: String,
1092 new_dir: &Self,
1093 new_path: String,
1094 ) -> Result<(), ErrorCode> {
1095 if !self.perms.contains(DirPerms::MUTATE) {
1096 return Err(ErrorCode::NotPermitted);
1097 }
1098 if !new_dir.perms.contains(DirPerms::MUTATE) {
1099 return Err(ErrorCode::NotPermitted);
1100 }
1101 let new_dir_handle = Arc::clone(&new_dir.dir);
1102 self.run_blocking(move |d| d.rename(&old_path, &new_dir_handle, &new_path))
1103 .await?;
1104 Ok(())
1105 }
1106
1107 pub(crate) async fn symlink_at(
1108 &self,
1109 src_path: String,
1110 dest_path: String,
1111 ) -> Result<(), ErrorCode> {
1112 #[cfg(windows)]
1114 use cap_fs_ext::DirExt;
1115
1116 if !self.perms.contains(DirPerms::MUTATE) {
1117 return Err(ErrorCode::NotPermitted);
1118 }
1119 self.run_blocking(move |d| d.symlink(&src_path, &dest_path))
1120 .await?;
1121 Ok(())
1122 }
1123
1124 pub(crate) async fn unlink_file_at(&self, path: String) -> Result<(), ErrorCode> {
1125 use cap_fs_ext::DirExt;
1126
1127 if !self.perms.contains(DirPerms::MUTATE) {
1128 return Err(ErrorCode::NotPermitted);
1129 }
1130 self.run_blocking(move |d| d.remove_file_or_symlink(&path))
1131 .await?;
1132 Ok(())
1133 }
1134
1135 pub(crate) async fn metadata_hash_at(
1136 &self,
1137 path_flags: PathFlags,
1138 path: String,
1139 ) -> Result<MetadataHashValue, ErrorCode> {
1140 let meta = self
1142 .run_blocking(move |d| {
1143 if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
1144 d.metadata(path)
1145 } else {
1146 d.symlink_metadata(path)
1147 }
1148 })
1149 .await?;
1150 Ok(MetadataHashValue::from(&meta))
1151 }
1152}
1153
1154impl WasiFilesystemCtxView<'_> {
1155 pub(crate) fn get_directories(
1156 &mut self,
1157 ) -> wasmtime::Result<Vec<(Resource<Descriptor>, String)>> {
1158 let preopens = self.ctx.preopens.clone();
1159 let mut results = Vec::with_capacity(preopens.len());
1160 for (dir, name) in preopens {
1161 let fd = self
1162 .table
1163 .push(Descriptor::Dir(dir))
1164 .with_context(|| format!("failed to push preopen {name}"))?;
1165 results.push((fd, name));
1166 }
1167 Ok(results)
1168 }
1169}