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 OpenResult::Dir(dir) => Ok(Descriptor::Dir(Dir::new(
1049 dir,
1050 self.perms,
1051 self.file_perms,
1052 open_mode,
1053 allow_blocking_current_thread,
1054 ))),
1055
1056 OpenResult::File(file) => Ok(Descriptor::File(File::new(
1057 file,
1058 self.file_perms,
1059 open_mode,
1060 allow_blocking_current_thread,
1061 ))),
1062
1063 OpenResult::NotDir => Err(ErrorCode::NotDirectory),
1064 }
1065 }
1066
1067 pub(crate) async fn readlink_at(&self, path: String) -> Result<String, ErrorCode> {
1068 if !self.perms.contains(DirPerms::READ) {
1069 return Err(ErrorCode::NotPermitted);
1070 }
1071 let link = self.run_blocking(move |d| d.read_link(&path)).await?;
1072 link.into_os_string()
1073 .into_string()
1074 .or(Err(ErrorCode::IllegalByteSequence))
1075 }
1076
1077 pub(crate) async fn remove_directory_at(&self, path: String) -> Result<(), ErrorCode> {
1078 if !self.perms.contains(DirPerms::MUTATE) {
1079 return Err(ErrorCode::NotPermitted);
1080 }
1081 self.run_blocking(move |d| d.remove_dir(&path)).await?;
1082 Ok(())
1083 }
1084
1085 pub(crate) async fn rename_at(
1086 &self,
1087 old_path: String,
1088 new_dir: &Self,
1089 new_path: String,
1090 ) -> Result<(), ErrorCode> {
1091 if !self.perms.contains(DirPerms::MUTATE) {
1092 return Err(ErrorCode::NotPermitted);
1093 }
1094 if !new_dir.perms.contains(DirPerms::MUTATE) {
1095 return Err(ErrorCode::NotPermitted);
1096 }
1097 let new_dir_handle = Arc::clone(&new_dir.dir);
1098 self.run_blocking(move |d| d.rename(&old_path, &new_dir_handle, &new_path))
1099 .await?;
1100 Ok(())
1101 }
1102
1103 pub(crate) async fn symlink_at(
1104 &self,
1105 src_path: String,
1106 dest_path: String,
1107 ) -> Result<(), ErrorCode> {
1108 #[cfg(windows)]
1110 use cap_fs_ext::DirExt;
1111
1112 if !self.perms.contains(DirPerms::MUTATE) {
1113 return Err(ErrorCode::NotPermitted);
1114 }
1115 self.run_blocking(move |d| d.symlink(&src_path, &dest_path))
1116 .await?;
1117 Ok(())
1118 }
1119
1120 pub(crate) async fn unlink_file_at(&self, path: String) -> Result<(), ErrorCode> {
1121 use cap_fs_ext::DirExt;
1122
1123 if !self.perms.contains(DirPerms::MUTATE) {
1124 return Err(ErrorCode::NotPermitted);
1125 }
1126 self.run_blocking(move |d| d.remove_file_or_symlink(&path))
1127 .await?;
1128 Ok(())
1129 }
1130
1131 pub(crate) async fn metadata_hash_at(
1132 &self,
1133 path_flags: PathFlags,
1134 path: String,
1135 ) -> Result<MetadataHashValue, ErrorCode> {
1136 let meta = self
1138 .run_blocking(move |d| {
1139 if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
1140 d.metadata(path)
1141 } else {
1142 d.symlink_metadata(path)
1143 }
1144 })
1145 .await?;
1146 Ok(MetadataHashValue::from(&meta))
1147 }
1148}
1149
1150impl WasiFilesystemCtxView<'_> {
1151 pub(crate) fn get_directories(
1152 &mut self,
1153 ) -> wasmtime::Result<Vec<(Resource<Descriptor>, String)>> {
1154 let preopens = self.ctx.preopens.clone();
1155 let mut results = Vec::with_capacity(preopens.len());
1156 for (dir, name) in preopens {
1157 let fd = self
1158 .table
1159 .push(Descriptor::Dir(dir))
1160 .with_context(|| format!("failed to push preopen {name}"))?;
1161 results.push((fd, name));
1162 }
1163 Ok(results)
1164 }
1165}