1use crate::clocks::Datetime;
2use crate::runtime::{AbortOnDropJoinHandle, spawn_blocking};
3use anyhow::Context as _;
4use cap_fs_ext::{FileTypeExt as _, MetadataExt as _};
5use fs_set_times::SystemTimeSpec;
6use std::collections::hash_map;
7use std::sync::Arc;
8use tracing::debug;
9use wasmtime::component::{HasData, Resource, ResourceTable};
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>,
669 pub perms: FilePerms,
673 pub open_mode: OpenMode,
678
679 allow_blocking_current_thread: bool,
680}
681
682impl File {
683 pub fn new(
684 file: cap_std::fs::File,
685 perms: FilePerms,
686 open_mode: OpenMode,
687 allow_blocking_current_thread: bool,
688 ) -> Self {
689 Self {
690 file: Arc::new(file),
691 perms,
692 open_mode,
693 allow_blocking_current_thread,
694 }
695 }
696
697 pub(crate) async fn run_blocking<F, R>(&self, body: F) -> R
712 where
713 F: FnOnce(&cap_std::fs::File) -> R + Send + 'static,
714 R: Send + 'static,
715 {
716 match self.as_blocking_file() {
717 Some(file) => body(file),
718 None => self.spawn_blocking(body).await,
719 }
720 }
721
722 pub(crate) fn spawn_blocking<F, R>(&self, body: F) -> AbortOnDropJoinHandle<R>
723 where
724 F: FnOnce(&cap_std::fs::File) -> R + Send + 'static,
725 R: Send + 'static,
726 {
727 let f = self.file.clone();
728 spawn_blocking(move || body(&f))
729 }
730
731 pub(crate) fn as_blocking_file(&self) -> Option<&cap_std::fs::File> {
735 if self.allow_blocking_current_thread {
736 Some(&self.file)
737 } else {
738 None
739 }
740 }
741
742 #[cfg(feature = "p3")]
744 pub(crate) fn as_file(&self) -> &Arc<cap_std::fs::File> {
745 &self.file
746 }
747
748 pub(crate) async fn advise(
749 &self,
750 offset: u64,
751 len: u64,
752 advice: system_interface::fs::Advice,
753 ) -> Result<(), ErrorCode> {
754 use system_interface::fs::FileIoExt as _;
755 self.run_blocking(move |f| f.advise(offset, len, advice))
756 .await?;
757 Ok(())
758 }
759
760 pub(crate) async fn set_size(&self, size: u64) -> Result<(), ErrorCode> {
761 if !self.perms.contains(FilePerms::WRITE) {
762 return Err(ErrorCode::NotPermitted);
763 }
764 self.run_blocking(move |f| f.set_len(size)).await?;
765 Ok(())
766 }
767}
768
769#[derive(Clone)]
770pub struct Dir {
771 pub dir: Arc<cap_std::fs::Dir>,
778 pub perms: DirPerms,
785 pub file_perms: FilePerms,
787 pub open_mode: OpenMode,
792
793 pub(crate) allow_blocking_current_thread: bool,
794}
795
796impl Dir {
797 pub fn new(
798 dir: cap_std::fs::Dir,
799 perms: DirPerms,
800 file_perms: FilePerms,
801 open_mode: OpenMode,
802 allow_blocking_current_thread: bool,
803 ) -> Self {
804 Dir {
805 dir: Arc::new(dir),
806 perms,
807 file_perms,
808 open_mode,
809 allow_blocking_current_thread,
810 }
811 }
812
813 pub(crate) async fn run_blocking<F, R>(&self, body: F) -> R
828 where
829 F: FnOnce(&cap_std::fs::Dir) -> R + Send + 'static,
830 R: Send + 'static,
831 {
832 if self.allow_blocking_current_thread {
833 body(&self.dir)
834 } else {
835 let d = self.dir.clone();
836 spawn_blocking(move || body(&d)).await
837 }
838 }
839
840 #[cfg(feature = "p3")]
842 pub(crate) fn as_dir(&self) -> &Arc<cap_std::fs::Dir> {
843 &self.dir
844 }
845
846 pub(crate) async fn create_directory_at(&self, path: String) -> Result<(), ErrorCode> {
847 if !self.perms.contains(DirPerms::MUTATE) {
848 return Err(ErrorCode::NotPermitted);
849 }
850 self.run_blocking(move |d| d.create_dir(&path)).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 meta = if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
864 self.run_blocking(move |d| d.metadata(&path)).await?
865 } else {
866 self.run_blocking(move |d| d.symlink_metadata(&path))
867 .await?
868 };
869 Ok(meta.into())
870 }
871
872 pub(crate) async fn set_times_at(
873 &self,
874 path_flags: PathFlags,
875 path: String,
876 atim: Option<SystemTimeSpec>,
877 mtim: Option<SystemTimeSpec>,
878 ) -> Result<(), ErrorCode> {
879 use cap_fs_ext::DirExt as _;
880
881 if !self.perms.contains(DirPerms::MUTATE) {
882 return Err(ErrorCode::NotPermitted);
883 }
884 if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
885 self.run_blocking(move |d| {
886 d.set_times(
887 &path,
888 atim.map(cap_fs_ext::SystemTimeSpec::from_std),
889 mtim.map(cap_fs_ext::SystemTimeSpec::from_std),
890 )
891 })
892 .await?;
893 } else {
894 self.run_blocking(move |d| {
895 d.set_symlink_times(
896 &path,
897 atim.map(cap_fs_ext::SystemTimeSpec::from_std),
898 mtim.map(cap_fs_ext::SystemTimeSpec::from_std),
899 )
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.contains(DirPerms::MUTATE) {
914 return Err(ErrorCode::NotPermitted);
915 }
916 if !new_dir.perms.contains(DirPerms::MUTATE) {
917 return Err(ErrorCode::NotPermitted);
918 }
919 if old_path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
920 return Err(ErrorCode::Invalid);
921 }
922 let new_dir_handle = Arc::clone(&new_dir.dir);
923 self.run_blocking(move |d| d.hard_link(&old_path, &new_dir_handle, &new_path))
924 .await?;
925 Ok(())
926 }
927
928 pub(crate) async fn open_at(
929 &self,
930 path_flags: PathFlags,
931 path: String,
932 oflags: OpenFlags,
933 flags: DescriptorFlags,
934 allow_blocking_current_thread: bool,
935 ) -> Result<Descriptor, ErrorCode> {
936 use cap_fs_ext::{FollowSymlinks, OpenOptionsFollowExt, OpenOptionsMaybeDirExt};
937 use system_interface::fs::{FdFlags, GetSetFdFlags};
938
939 if !self.perms.contains(DirPerms::READ) {
940 return Err(ErrorCode::NotPermitted);
941 }
942
943 if !self.perms.contains(DirPerms::MUTATE) {
944 if oflags.contains(OpenFlags::CREATE) || oflags.contains(OpenFlags::TRUNCATE) {
945 return Err(ErrorCode::NotPermitted);
946 }
947 if flags.contains(DescriptorFlags::WRITE) {
948 return Err(ErrorCode::NotPermitted);
949 }
950 }
951
952 let mut create = false;
954 let mut open_mode = OpenMode::empty();
956 let mut opts = cap_std::fs::OpenOptions::new();
958 opts.maybe_dir(true);
959
960 if oflags.contains(OpenFlags::CREATE) {
961 if oflags.contains(OpenFlags::EXCLUSIVE) {
962 opts.create_new(true);
963 } else {
964 opts.create(true);
965 }
966 create = true;
967 opts.write(true);
968 open_mode |= OpenMode::WRITE;
969 }
970
971 if oflags.contains(OpenFlags::TRUNCATE) {
972 opts.truncate(true).write(true);
973 }
974 if flags.contains(DescriptorFlags::READ) {
975 opts.read(true);
976 open_mode |= OpenMode::READ;
977 }
978 if flags.contains(DescriptorFlags::WRITE) {
979 opts.write(true);
980 open_mode |= OpenMode::WRITE;
981 } else {
982 opts.read(true);
985 open_mode |= OpenMode::READ;
986 }
987 if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
988 opts.follow(FollowSymlinks::Yes);
989 } else {
990 opts.follow(FollowSymlinks::No);
991 }
992
993 if flags.contains(DescriptorFlags::FILE_INTEGRITY_SYNC)
995 || flags.contains(DescriptorFlags::DATA_INTEGRITY_SYNC)
996 || flags.contains(DescriptorFlags::REQUESTED_WRITE_SYNC)
997 {
998 return Err(ErrorCode::Unsupported);
999 }
1000
1001 if oflags.contains(OpenFlags::DIRECTORY) {
1002 if oflags.contains(OpenFlags::CREATE)
1003 || oflags.contains(OpenFlags::EXCLUSIVE)
1004 || oflags.contains(OpenFlags::TRUNCATE)
1005 {
1006 return Err(ErrorCode::Invalid);
1007 }
1008 }
1009
1010 if !self.perms.contains(DirPerms::MUTATE) && create {
1013 return Err(ErrorCode::NotPermitted);
1014 }
1015 if !self.file_perms.contains(FilePerms::WRITE) && open_mode.contains(OpenMode::WRITE) {
1016 return Err(ErrorCode::NotPermitted);
1017 }
1018
1019 enum OpenResult {
1023 Dir(cap_std::fs::Dir),
1024 File(cap_std::fs::File),
1025 NotDir,
1026 }
1027
1028 let opened = self
1029 .run_blocking::<_, std::io::Result<OpenResult>>(move |d| {
1030 let mut opened = d.open_with(&path, &opts)?;
1031 if opened.metadata()?.is_dir() {
1032 Ok(OpenResult::Dir(cap_std::fs::Dir::from_std_file(
1033 opened.into_std(),
1034 )))
1035 } else if oflags.contains(OpenFlags::DIRECTORY) {
1036 Ok(OpenResult::NotDir)
1037 } else {
1038 let set_fd_flags = opened.new_set_fd_flags(FdFlags::NONBLOCK)?;
1041 opened.set_fd_flags(set_fd_flags)?;
1042 Ok(OpenResult::File(opened))
1043 }
1044 })
1045 .await?;
1046
1047 match opened {
1048 #[cfg(windows)]
1052 OpenResult::Dir(_) if flags.contains(DescriptorFlags::WRITE) => {
1053 Err(ErrorCode::IsDirectory)
1054 }
1055
1056 OpenResult::Dir(dir) => Ok(Descriptor::Dir(Dir::new(
1057 dir,
1058 self.perms,
1059 self.file_perms,
1060 open_mode,
1061 allow_blocking_current_thread,
1062 ))),
1063
1064 OpenResult::File(file) => Ok(Descriptor::File(File::new(
1065 file,
1066 self.file_perms,
1067 open_mode,
1068 allow_blocking_current_thread,
1069 ))),
1070
1071 OpenResult::NotDir => Err(ErrorCode::NotDirectory),
1072 }
1073 }
1074
1075 pub(crate) async fn readlink_at(&self, path: String) -> Result<String, ErrorCode> {
1076 if !self.perms.contains(DirPerms::READ) {
1077 return Err(ErrorCode::NotPermitted);
1078 }
1079 let link = self.run_blocking(move |d| d.read_link(&path)).await?;
1080 link.into_os_string()
1081 .into_string()
1082 .or(Err(ErrorCode::IllegalByteSequence))
1083 }
1084
1085 pub(crate) async fn remove_directory_at(&self, path: String) -> Result<(), ErrorCode> {
1086 if !self.perms.contains(DirPerms::MUTATE) {
1087 return Err(ErrorCode::NotPermitted);
1088 }
1089 self.run_blocking(move |d| d.remove_dir(&path)).await?;
1090 Ok(())
1091 }
1092
1093 pub(crate) async fn rename_at(
1094 &self,
1095 old_path: String,
1096 new_dir: &Self,
1097 new_path: String,
1098 ) -> Result<(), ErrorCode> {
1099 if !self.perms.contains(DirPerms::MUTATE) {
1100 return Err(ErrorCode::NotPermitted);
1101 }
1102 if !new_dir.perms.contains(DirPerms::MUTATE) {
1103 return Err(ErrorCode::NotPermitted);
1104 }
1105 let new_dir_handle = Arc::clone(&new_dir.dir);
1106 self.run_blocking(move |d| d.rename(&old_path, &new_dir_handle, &new_path))
1107 .await?;
1108 Ok(())
1109 }
1110
1111 pub(crate) async fn symlink_at(
1112 &self,
1113 src_path: String,
1114 dest_path: String,
1115 ) -> Result<(), ErrorCode> {
1116 #[cfg(windows)]
1118 use cap_fs_ext::DirExt;
1119
1120 if !self.perms.contains(DirPerms::MUTATE) {
1121 return Err(ErrorCode::NotPermitted);
1122 }
1123 self.run_blocking(move |d| d.symlink(&src_path, &dest_path))
1124 .await?;
1125 Ok(())
1126 }
1127
1128 pub(crate) async fn unlink_file_at(&self, path: String) -> Result<(), ErrorCode> {
1129 use cap_fs_ext::DirExt;
1130
1131 if !self.perms.contains(DirPerms::MUTATE) {
1132 return Err(ErrorCode::NotPermitted);
1133 }
1134 self.run_blocking(move |d| d.remove_file_or_symlink(&path))
1135 .await?;
1136 Ok(())
1137 }
1138
1139 pub(crate) async fn metadata_hash_at(
1140 &self,
1141 path_flags: PathFlags,
1142 path: String,
1143 ) -> Result<MetadataHashValue, ErrorCode> {
1144 let meta = self
1146 .run_blocking(move |d| {
1147 if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
1148 d.metadata(path)
1149 } else {
1150 d.symlink_metadata(path)
1151 }
1152 })
1153 .await?;
1154 Ok(MetadataHashValue::from(&meta))
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}