use crate::clocks::Datetime;
use crate::filesystem::primitives::{DirOptions, FollowSymlinks, Metadata, OpenOptions};
use crate::runtime::{AbortOnDropJoinHandle, spawn_blocking};
use std::collections::hash_map;
use std::sync::Arc;
use std::time::SystemTime;
use tracing::debug;
use wasmtime::component::{HasData, Resource, ResourceTable};
use wasmtime::error::Context as _;
#[cfg(unix)]
pub(crate) mod unix;
#[cfg(unix)]
pub(crate) use unix as sys;
#[cfg(windows)]
pub(crate) mod windows;
#[cfg(windows)]
pub(crate) use windows as sys;
pub(crate) mod primitives;
pub struct WasiFilesystem;
impl HasData for WasiFilesystem {
type Data<'a> = WasiFilesystemCtxView<'a>;
}
#[derive(Clone, Default)]
pub struct WasiFilesystemCtx {
pub(crate) allow_blocking_current_thread: bool,
pub(crate) preopens: Vec<(Dir, String)>,
}
pub struct WasiFilesystemCtxView<'a> {
pub ctx: &'a mut WasiFilesystemCtx,
pub table: &'a mut ResourceTable,
}
pub trait WasiFilesystemView: Send {
fn filesystem(&mut self) -> WasiFilesystemCtxView<'_>;
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum FsPerms {
ReadOnly,
ReadWrite,
}
impl FsPerms {
pub fn write_not_permitted(&self) -> bool {
matches!(self, Self::ReadOnly)
}
}
bitflags::bitflags! {
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct OpenMode: usize {
const READ = 0b1;
const WRITE = 0b10;
}
}
bitflags::bitflags! {
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) struct PathFlags: usize {
const SYMLINK_FOLLOW = 0b1;
}
}
bitflags::bitflags! {
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) struct OpenFlags: usize {
const CREATE = 0b1;
const DIRECTORY = 0b10;
const EXCLUSIVE = 0b100;
const TRUNCATE = 0b1000;
}
}
bitflags::bitflags! {
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) struct DescriptorFlags: usize {
const READ = 0b1;
const WRITE = 0b10;
const FILE_INTEGRITY_SYNC = 0b100;
const DATA_INTEGRITY_SYNC = 0b1000;
const REQUESTED_WRITE_SYNC = 0b10000;
const MUTATE_DIRECTORY = 0b100000;
}
}
#[cfg_attr(
windows,
expect(dead_code, reason = "on Windows, some of these are not used")
)]
pub(crate) enum ErrorCode {
Access,
Already,
BadDescriptor,
Busy,
Exist,
FileTooLarge,
IllegalByteSequence,
InProgress,
Interrupted,
Invalid,
Io,
IsDirectory,
Loop,
TooManyLinks,
NameTooLong,
NoEntry,
InsufficientMemory,
InsufficientSpace,
NotDirectory,
NotEmpty,
Unsupported,
Overflow,
NotPermitted,
Pipe,
InvalidSeek,
}
pub(crate) enum DescriptorType {
Unknown,
#[cfg_attr(
windows,
expect(dead_code, reason = "windows has no notion of block devices")
)]
BlockDevice,
CharacterDevice,
Directory,
SymbolicLink,
RegularFile,
}
impl From<crate::filesystem::primitives::FileType> for DescriptorType {
fn from(ft: crate::filesystem::primitives::FileType) -> Self {
if ft.is_dir() {
DescriptorType::Directory
} else if ft.is_symlink() {
DescriptorType::SymbolicLink
} else if ft.is_file() {
DescriptorType::RegularFile
} else {
sys::descriptor_type(ft)
}
}
}
pub(crate) struct DescriptorStat {
pub type_: DescriptorType,
pub link_count: u64,
pub size: u64,
pub data_access_timestamp: Option<Datetime>,
pub data_modification_timestamp: Option<Datetime>,
pub status_change_timestamp: Option<Datetime>,
}
impl DescriptorStat {
fn new(meta: &Metadata, link_count: u64) -> Self {
Self {
type_: meta.file_type().into(),
link_count,
size: meta.len(),
data_access_timestamp: meta
.accessed()
.ok()
.and_then(|t| Datetime::try_from(t).ok()),
data_modification_timestamp: meta
.modified()
.ok()
.and_then(|t| Datetime::try_from(t).ok()),
status_change_timestamp: meta.created().ok().and_then(|t| Datetime::try_from(t).ok()),
}
}
}
pub(crate) struct MetadataHashValue {
pub lower: u64,
pub upper: u64,
}
impl MetadataHashValue {
fn new(identity: impl std::hash::Hash) -> Self {
use std::hash::Hasher as _;
let mut hasher = hash_map::DefaultHasher::new();
identity.hash(&mut hasher);
let lower = hasher.finish();
let upper = lower ^ 4614256656552045848u64;
Self { lower, upper }
}
}
#[derive(Copy, Clone, Debug)]
pub(crate) enum Advice {
Normal,
Sequential,
Random,
WillNeed,
DontNeed,
NoReuse,
}
#[cfg(unix)]
fn from_raw_os_error(err: Option<i32>) -> Option<ErrorCode> {
use rustix::io::Errno as RustixErrno;
if err.is_none() {
return None;
}
Some(match RustixErrno::from_raw_os_error(err.unwrap()) {
RustixErrno::PIPE => ErrorCode::Pipe,
RustixErrno::PERM => ErrorCode::NotPermitted,
RustixErrno::NOENT => ErrorCode::NoEntry,
RustixErrno::NOMEM => ErrorCode::InsufficientMemory,
RustixErrno::IO => ErrorCode::Io,
RustixErrno::BADF => ErrorCode::BadDescriptor,
RustixErrno::BUSY => ErrorCode::Busy,
RustixErrno::ACCESS => ErrorCode::Access,
RustixErrno::NOTDIR => ErrorCode::NotDirectory,
RustixErrno::ISDIR => ErrorCode::IsDirectory,
RustixErrno::INVAL => ErrorCode::Invalid,
RustixErrno::EXIST => ErrorCode::Exist,
RustixErrno::FBIG => ErrorCode::FileTooLarge,
RustixErrno::NOSPC => ErrorCode::InsufficientSpace,
RustixErrno::SPIPE => ErrorCode::InvalidSeek,
RustixErrno::MLINK => ErrorCode::TooManyLinks,
RustixErrno::NAMETOOLONG => ErrorCode::NameTooLong,
RustixErrno::NOTEMPTY => ErrorCode::NotEmpty,
RustixErrno::LOOP => ErrorCode::Loop,
RustixErrno::OVERFLOW => ErrorCode::Overflow,
RustixErrno::ILSEQ => ErrorCode::IllegalByteSequence,
RustixErrno::NOTSUP => ErrorCode::Unsupported,
RustixErrno::ALREADY => ErrorCode::Already,
RustixErrno::INPROGRESS => ErrorCode::InProgress,
RustixErrno::INTR => ErrorCode::Interrupted,
#[allow(unreachable_patterns, reason = "see comment")]
RustixErrno::OPNOTSUPP => ErrorCode::Unsupported,
_ => return None,
})
}
#[cfg(windows)]
fn from_raw_os_error(raw_os_error: Option<i32>) -> Option<ErrorCode> {
use windows_sys::Win32::Foundation;
Some(match raw_os_error.map(|code| code as u32) {
Some(Foundation::ERROR_FILE_NOT_FOUND) => ErrorCode::NoEntry,
Some(Foundation::ERROR_PATH_NOT_FOUND) => ErrorCode::NoEntry,
Some(Foundation::ERROR_ACCESS_DENIED) => ErrorCode::Access,
Some(Foundation::ERROR_SHARING_VIOLATION) => ErrorCode::Access,
Some(Foundation::ERROR_PRIVILEGE_NOT_HELD) => ErrorCode::NotPermitted,
Some(Foundation::ERROR_INVALID_HANDLE) => ErrorCode::BadDescriptor,
Some(Foundation::ERROR_INVALID_NAME) => ErrorCode::NoEntry,
Some(Foundation::ERROR_NOT_ENOUGH_MEMORY) => ErrorCode::InsufficientMemory,
Some(Foundation::ERROR_OUTOFMEMORY) => ErrorCode::InsufficientMemory,
Some(Foundation::ERROR_DIR_NOT_EMPTY) => ErrorCode::NotEmpty,
Some(Foundation::ERROR_NOT_READY) => ErrorCode::Busy,
Some(Foundation::ERROR_BUSY) => ErrorCode::Busy,
Some(Foundation::ERROR_NOT_SUPPORTED) => ErrorCode::Unsupported,
Some(Foundation::ERROR_FILE_EXISTS) => ErrorCode::Exist,
Some(Foundation::ERROR_BROKEN_PIPE) => ErrorCode::Pipe,
Some(Foundation::ERROR_BUFFER_OVERFLOW) => ErrorCode::NameTooLong,
Some(Foundation::ERROR_NOT_A_REPARSE_POINT) => ErrorCode::Invalid,
Some(Foundation::ERROR_NEGATIVE_SEEK) => ErrorCode::Invalid,
Some(Foundation::ERROR_DIRECTORY) => ErrorCode::NotDirectory,
Some(Foundation::ERROR_ALREADY_EXISTS) => ErrorCode::Exist,
Some(Foundation::ERROR_STOPPED_ON_SYMLINK) => ErrorCode::Loop,
Some(Foundation::ERROR_DIRECTORY_NOT_SUPPORTED) => ErrorCode::IsDirectory,
_ => return None,
})
}
impl<'a> From<&'a std::io::Error> for ErrorCode {
fn from(err: &'a std::io::Error) -> ErrorCode {
match from_raw_os_error(err.raw_os_error()) {
Some(errno) => errno,
None => {
debug!("unknown raw os error: {err}");
match err.kind() {
std::io::ErrorKind::NotFound => ErrorCode::NoEntry,
std::io::ErrorKind::PermissionDenied => ErrorCode::NotPermitted,
std::io::ErrorKind::AlreadyExists => ErrorCode::Exist,
std::io::ErrorKind::InvalidInput => ErrorCode::Invalid,
_ => ErrorCode::Io,
}
}
}
}
}
impl From<std::io::Error> for ErrorCode {
fn from(err: std::io::Error) -> ErrorCode {
ErrorCode::from(&err)
}
}
#[derive(Clone)]
pub enum Descriptor {
File(File),
Dir(Dir),
}
impl Descriptor {
pub(crate) fn file(&self) -> Result<&File, ErrorCode> {
match self {
Descriptor::File(f) => Ok(f),
Descriptor::Dir(_) => Err(ErrorCode::BadDescriptor),
}
}
pub(crate) fn dir(&self) -> Result<&Dir, ErrorCode> {
match self {
Descriptor::Dir(d) => Ok(d),
Descriptor::File(_) => Err(ErrorCode::NotDirectory),
}
}
pub(crate) async fn sync_data(&self) -> Result<(), ErrorCode> {
match self {
Self::File(f) => {
match f.run_blocking(|f| f.sync_data()).await {
Ok(()) => Ok(()),
#[cfg(windows)]
Err(err)
if err.raw_os_error()
== Some(windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED as _) =>
{
Ok(())
}
Err(err) => Err(err.into()),
}
}
Self::Dir(d) => {
d.run_blocking(|d| {
let d = crate::filesystem::primitives::open(
d,
std::path::Component::CurDir.as_ref(),
OpenOptions::new().read(true),
)?;
d.sync_data()?;
Ok(())
})
.await
}
}
}
pub(crate) async fn get_flags(&self) -> Result<DescriptorFlags, ErrorCode> {
match self {
Self::File(f) => {
let mut flags = f.run_blocking(|f| sys::get_flags(f)).await?;
if f.open_mode.contains(OpenMode::READ) {
flags |= DescriptorFlags::READ;
}
if f.open_mode.contains(OpenMode::WRITE) {
flags |= DescriptorFlags::WRITE;
}
Ok(flags)
}
Self::Dir(d) => {
let mut flags = d.run_blocking(|d| sys::get_flags(d)).await?;
if d.open_mode.contains(OpenMode::READ) {
flags |= DescriptorFlags::READ;
}
if d.open_mode.contains(OpenMode::WRITE) {
flags |= DescriptorFlags::MUTATE_DIRECTORY;
}
Ok(flags)
}
}
}
pub(crate) async fn get_type(&self) -> Result<DescriptorType, ErrorCode> {
match self {
Self::File(f) => {
let meta = f.run_blocking(|f| Metadata::from_file(f)).await?;
Ok(meta.file_type().into())
}
Self::Dir(_) => Ok(DescriptorType::Directory),
}
}
pub(crate) async fn set_times(
&self,
atim: Option<SystemTime>,
mtim: Option<SystemTime>,
) -> Result<(), ErrorCode> {
let mut times = std::fs::FileTimes::new();
if let Some(atim) = atim {
times = times.set_accessed(atim);
}
if let Some(mtim) = mtim {
times = times.set_modified(mtim);
}
match self {
Self::File(f) => {
if f.perms.write_not_permitted() {
return Err(ErrorCode::NotPermitted);
}
f.run_blocking(move |f| f.set_times(times)).await?;
Ok(())
}
Self::Dir(d) => {
if d.perms.write_not_permitted() {
return Err(ErrorCode::NotPermitted);
}
d.run_blocking(move |d| d.set_times(times)).await?;
Ok(())
}
}
}
pub(crate) async fn sync(&self) -> Result<(), ErrorCode> {
match self {
Self::File(f) => {
match f.run_blocking(|f| f.sync_all()).await {
Ok(()) => Ok(()),
#[cfg(windows)]
Err(err)
if err.raw_os_error()
== Some(windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED as _) =>
{
Ok(())
}
Err(err) => Err(err.into()),
}
}
Self::Dir(d) => {
d.run_blocking(|d| {
let d = crate::filesystem::primitives::open(
d,
std::path::Component::CurDir.as_ref(),
OpenOptions::new().read(true),
)?;
d.sync_all()?;
Ok(())
})
.await
}
}
}
pub(crate) async fn stat(&self) -> Result<DescriptorStat, ErrorCode> {
match self {
Self::File(f) => Ok(f.run_blocking(|f| sys::stat(f)).await?),
Self::Dir(d) => Ok(d.run_blocking(|f| sys::stat(f)).await?),
}
}
pub(crate) async fn is_same_object(&self, other: &Self) -> wasmtime::Result<bool> {
let other = match other {
Self::File(f) => Arc::clone(&f.file),
Self::Dir(d) => Arc::clone(&d.dir),
};
Ok(match self {
Self::File(f) => {
f.run_blocking(move |f| sys::is_same_file(f, &other))
.await?
}
Self::Dir(d) => {
d.run_blocking(move |d| sys::is_same_file(d, &other))
.await?
}
})
}
pub(crate) async fn metadata_hash(&self) -> Result<MetadataHashValue, ErrorCode> {
match self {
Self::File(f) => Ok(f.run_blocking(|f| sys::metadata_hash(f)).await?),
Self::Dir(d) => Ok(d.run_blocking(|d| sys::metadata_hash(d)).await?),
}
}
}
#[derive(Clone)]
pub struct File {
pub file: Arc<std::fs::File>,
pub perms: FsPerms,
pub open_mode: OpenMode,
allow_blocking_current_thread: bool,
}
impl File {
pub fn new(
file: std::fs::File,
perms: FsPerms,
open_mode: OpenMode,
allow_blocking_current_thread: bool,
) -> Self {
Self {
file: Arc::new(file),
perms,
open_mode,
allow_blocking_current_thread,
}
}
pub(crate) async fn run_blocking<F, R>(&self, body: F) -> R
where
F: FnOnce(&std::fs::File) -> R + Send + 'static,
R: Send + 'static,
{
match self.as_blocking_file() {
Some(file) => body(file),
None => self.spawn_blocking(body).await,
}
}
pub(crate) fn spawn_blocking<F, R>(&self, body: F) -> AbortOnDropJoinHandle<R>
where
F: FnOnce(&std::fs::File) -> R + Send + 'static,
R: Send + 'static,
{
let f = self.file.clone();
spawn_blocking(move || body(&f))
}
pub(crate) fn as_blocking_file(&self) -> Option<&std::fs::File> {
if self.allow_blocking_current_thread {
Some(&self.file)
} else {
None
}
}
#[cfg(feature = "p3")]
pub(crate) fn as_file(&self) -> &Arc<std::fs::File> {
&self.file
}
pub(crate) async fn advise(
&self,
offset: u64,
len: u64,
advice: Advice,
) -> Result<(), ErrorCode> {
self.run_blocking(move |f| sys::advise(f, offset, len, advice))
.await?;
Ok(())
}
pub(crate) async fn set_size(&self, size: u64) -> Result<(), ErrorCode> {
if self.perms.write_not_permitted() {
return Err(ErrorCode::NotPermitted);
}
self.run_blocking(move |f| f.set_len(size)).await?;
Ok(())
}
}
#[derive(Clone)]
pub struct Dir {
pub dir: Arc<std::fs::File>,
pub perms: FsPerms,
pub open_mode: OpenMode,
pub(crate) allow_blocking_current_thread: bool,
}
impl Dir {
pub fn new(
dir: std::fs::File,
perms: FsPerms,
open_mode: OpenMode,
allow_blocking_current_thread: bool,
) -> Self {
Dir {
dir: Arc::new(dir),
perms,
open_mode,
allow_blocking_current_thread,
}
}
pub(crate) async fn run_blocking<F, R>(&self, body: F) -> R
where
F: FnOnce(&std::fs::File) -> R + Send + 'static,
R: Send + 'static,
{
if self.allow_blocking_current_thread {
body(&self.dir)
} else {
let d = self.dir.clone();
spawn_blocking(move || body(&d)).await
}
}
#[cfg(feature = "p3")]
pub(crate) fn as_dir(&self) -> &Arc<std::fs::File> {
&self.dir
}
pub(crate) async fn create_directory_at(&self, path: String) -> Result<(), ErrorCode> {
if self.perms.write_not_permitted() {
return Err(ErrorCode::NotPermitted);
}
self.run_blocking(move |d| {
crate::filesystem::primitives::create_dir(d, path.as_ref(), &DirOptions::new())
})
.await?;
Ok(())
}
pub(crate) async fn stat_at(
&self,
path_flags: PathFlags,
path: String,
) -> Result<DescriptorStat, ErrorCode> {
let follow = if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
FollowSymlinks::Yes
} else {
FollowSymlinks::No
};
let ret = self
.run_blocking(move |d| sys::stat_at(d, path.as_ref(), follow))
.await?;
Ok(ret)
}
pub(crate) async fn set_times_at(
&self,
path_flags: PathFlags,
path: String,
atim: Option<SystemTime>,
mtim: Option<SystemTime>,
) -> Result<(), ErrorCode> {
if self.perms.write_not_permitted() {
return Err(ErrorCode::NotPermitted);
}
if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
self.run_blocking(move |d| {
crate::filesystem::primitives::set_times(d, path.as_ref(), atim, mtim)
})
.await?;
} else {
self.run_blocking(move |d| {
crate::filesystem::primitives::set_times_nofollow(d, path.as_ref(), atim, mtim)
})
.await?;
}
Ok(())
}
pub(crate) async fn link_at(
&self,
old_path_flags: PathFlags,
old_path: String,
new_dir: &Self,
new_path: String,
) -> Result<(), ErrorCode> {
if self.perms.write_not_permitted() {
return Err(ErrorCode::NotPermitted);
}
if new_dir.perms.write_not_permitted() {
return Err(ErrorCode::NotPermitted);
}
if old_path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
return Err(ErrorCode::Invalid);
}
if self.perms != new_dir.perms {
return Err(ErrorCode::NotPermitted);
}
let new_dir_handle = Arc::clone(&new_dir.dir);
self.run_blocking(move |d| {
crate::filesystem::primitives::hard_link(
d,
old_path.as_ref(),
&new_dir_handle,
new_path.as_ref(),
)
})
.await?;
Ok(())
}
pub(crate) async fn open_at(
&self,
path_flags: PathFlags,
path: String,
oflags: OpenFlags,
flags: DescriptorFlags,
allow_blocking_current_thread: bool,
) -> Result<Descriptor, ErrorCode> {
let mut create = false;
let mut open_mode = OpenMode::empty();
let mut opts = OpenOptions::new();
sys::maybe_dir(&mut opts);
if oflags.contains(OpenFlags::CREATE) {
if oflags.contains(OpenFlags::EXCLUSIVE) {
opts.create_new(true);
} else {
opts.create(true);
}
create = true;
opts.write(true);
open_mode |= OpenMode::WRITE;
}
if oflags.contains(OpenFlags::TRUNCATE) {
opts.truncate(true).write(true);
open_mode |= OpenMode::WRITE;
}
if flags.contains(DescriptorFlags::READ) {
opts.read(true);
open_mode |= OpenMode::READ;
}
if flags.contains(DescriptorFlags::WRITE) {
opts.write(true);
open_mode |= OpenMode::WRITE;
} else {
opts.read(true);
open_mode |= OpenMode::READ;
}
if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
opts.follow(FollowSymlinks::Yes);
} else {
opts.follow(FollowSymlinks::No);
}
if flags.contains(DescriptorFlags::FILE_INTEGRITY_SYNC)
|| flags.contains(DescriptorFlags::DATA_INTEGRITY_SYNC)
|| flags.contains(DescriptorFlags::REQUESTED_WRITE_SYNC)
{
return Err(ErrorCode::Unsupported);
}
if oflags.contains(OpenFlags::DIRECTORY) {
if oflags.contains(OpenFlags::CREATE)
|| oflags.contains(OpenFlags::EXCLUSIVE)
|| oflags.contains(OpenFlags::TRUNCATE)
{
return Err(ErrorCode::Invalid);
}
}
if self.perms.write_not_permitted() {
if create || open_mode.contains(OpenMode::WRITE) {
return Err(ErrorCode::NotPermitted);
}
}
enum OpenResult {
Dir(std::fs::File),
File(std::fs::File),
NotDir,
}
let opened = self
.run_blocking::<_, std::io::Result<OpenResult>>(move |d| {
let opened = crate::filesystem::primitives::open(d, path.as_ref(), &opts)?;
if Metadata::from_file(&opened)?.is_dir() {
Ok(OpenResult::Dir(opened))
} else if oflags.contains(OpenFlags::DIRECTORY) {
Ok(OpenResult::NotDir)
} else {
Ok(OpenResult::File(opened))
}
})
.await?;
match opened {
#[cfg(windows)]
OpenResult::Dir(_) if flags.contains(DescriptorFlags::WRITE) => {
Err(ErrorCode::IsDirectory)
}
OpenResult::Dir(dir) => Ok(Descriptor::Dir(Dir::new(
dir,
self.perms,
open_mode,
allow_blocking_current_thread,
))),
OpenResult::File(file) => Ok(Descriptor::File(File::new(
file,
self.perms,
open_mode,
allow_blocking_current_thread,
))),
OpenResult::NotDir => Err(ErrorCode::NotDirectory),
}
}
pub(crate) async fn readlink_at(&self, path: String) -> Result<String, ErrorCode> {
let link = self
.run_blocking(move |d| crate::filesystem::primitives::read_link(d, path.as_ref()))
.await?;
link.into_os_string()
.into_string()
.or(Err(ErrorCode::IllegalByteSequence))
}
pub(crate) async fn remove_directory_at(&self, path: String) -> Result<(), ErrorCode> {
if self.perms.write_not_permitted() {
return Err(ErrorCode::NotPermitted);
}
self.run_blocking(move |d| crate::filesystem::primitives::remove_dir(d, path.as_ref()))
.await?;
Ok(())
}
pub(crate) async fn rename_at(
&self,
old_path: String,
new_dir: &Self,
new_path: String,
) -> Result<(), ErrorCode> {
if self.perms.write_not_permitted() {
return Err(ErrorCode::NotPermitted);
}
if new_dir.perms.write_not_permitted() {
return Err(ErrorCode::NotPermitted);
}
if self.perms != new_dir.perms {
return Err(ErrorCode::NotPermitted);
}
let new_dir_handle = Arc::clone(&new_dir.dir);
self.run_blocking(move |d| {
crate::filesystem::primitives::rename(
d,
old_path.as_ref(),
&new_dir_handle,
new_path.as_ref(),
)
})
.await?;
Ok(())
}
pub(crate) async fn symlink_at(
&self,
src_path: String,
dest_path: String,
) -> Result<(), ErrorCode> {
if self.perms.write_not_permitted() {
return Err(ErrorCode::NotPermitted);
}
self.run_blocking(move |d| sys::symlink(src_path.as_ref(), d, dest_path.as_ref()))
.await?;
Ok(())
}
pub(crate) async fn unlink_file_at(&self, path: String) -> Result<(), ErrorCode> {
if self.perms.write_not_permitted() {
return Err(ErrorCode::NotPermitted);
}
self.run_blocking(move |d| sys::remove_file_or_symlink(d, path.as_ref()))
.await?;
Ok(())
}
pub(crate) async fn metadata_hash_at(
&self,
path_flags: PathFlags,
path: String,
) -> Result<MetadataHashValue, ErrorCode> {
let follow = if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
FollowSymlinks::Yes
} else {
FollowSymlinks::No
};
let hash = self
.run_blocking(move |d| sys::metadata_hash_at(d, path.as_ref(), follow))
.await?;
Ok(hash)
}
}
impl WasiFilesystemCtxView<'_> {
pub(crate) fn get_directories(
&mut self,
) -> wasmtime::Result<Vec<(Resource<Descriptor>, String)>> {
let preopens = self.ctx.preopens.clone();
let mut results = Vec::with_capacity(preopens.len());
for (dir, name) in preopens {
let fd = self
.table
.push(Descriptor::Dir(dir))
.with_context(|| format!("failed to push preopen {name}"))?;
results.push((fd, name));
}
Ok(results)
}
}