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