#![allow(unused)]
use core::future::Future;
use core::pin::Pin;
use core::task::{Context, Poll};
use std::collections::hash_map;
use std::path::Path;
use std::sync::Arc;
use bitflags::bitflags;
use cap_fs_ext::{FileTypeExt as _, MetadataExt as _};
use cap_std::ambient_authority;
use tokio::task::{spawn_blocking, JoinHandle};
use tracing::debug;
use wasmtime::component::Linker;
use crate::engine::bindings::wasi::clocks::wall_clock;
use crate::engine::bindings::wasi::filesystem::types::{
Advice, DescriptorFlags, DescriptorStat, DescriptorType, ErrorCode, MetadataHashValue,
NewTimestamp, OpenFlags, PathFlags,
};
use crate::Ctx;
mod host;
pub struct AbortOnDropJoinHandle<T>(JoinHandle<T>);
impl<T> Future for AbortOnDropJoinHandle<T> {
type Output = T;
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
todo!()
}
}
impl<T> Drop for AbortOnDropJoinHandle<T> {
fn drop(&mut self) {
todo!()
}
}
pub struct Error;
pub struct DirectoryEntryStream;
#[derive(Clone, Default)]
pub struct WasiFilesystemCtx {
pub preopens: Vec<(Dir, String)>,
pub allow_blocking_current_thread: bool,
}
impl WasiFilesystemCtx {
pub fn preopened_dir(
&mut self,
host_path: impl AsRef<Path>,
guest_path: impl Into<String>,
dir_perms: DirPerms,
file_perms: FilePerms,
) -> wasmtime::Result<&mut Self> {
let dir = cap_std::fs::Dir::open_ambient_dir(host_path.as_ref(), ambient_authority())?;
let mut open_mode = OpenMode::empty();
if dir_perms.contains(DirPerms::READ) {
open_mode |= OpenMode::READ;
}
if dir_perms.contains(DirPerms::MUTATE) {
open_mode |= OpenMode::WRITE;
}
self.preopens.push((
Dir::new(
dir,
dir_perms,
file_perms,
open_mode,
self.allow_blocking_current_thread,
),
guest_path.into(),
));
Ok(self)
}
}
pub fn add_to_linker<T: Send>(
linker: &mut Linker<T>,
get: impl Fn(&mut T) -> &mut Ctx + Copy + Sync + Send + 'static,
) -> wasmtime::Result<()> {
crate::engine::bindings::wasi::filesystem::types::add_to_linker(linker, get)?;
crate::engine::bindings::wasi::filesystem::preopens::add_to_linker(linker, get)?;
Ok(())
}
fn datetime_from(_t: std::time::SystemTime) -> wall_clock::Datetime {
todo!()
}
fn systemtime_from(t: wall_clock::Datetime) -> Result<std::time::SystemTime, ErrorCode> {
std::time::SystemTime::UNIX_EPOCH
.checked_add(core::time::Duration::new(t.seconds, t.nanoseconds))
.ok_or(ErrorCode::Overflow)
}
fn systemtimespec_from(t: NewTimestamp) -> Result<Option<fs_set_times::SystemTimeSpec>, ErrorCode> {
use fs_set_times::SystemTimeSpec;
match t {
NewTimestamp::NoChange => Ok(None),
NewTimestamp::Now => Ok(Some(SystemTimeSpec::SymbolicNow)),
NewTimestamp::Timestamp(st) => {
let st = systemtime_from(st)?;
Ok(Some(SystemTimeSpec::Absolute(st)))
}
}
}
impl From<cap_std::fs::FileType> for DescriptorType {
fn from(ft: cap_std::fs::FileType) -> Self {
if ft.is_dir() {
DescriptorType::Directory
} else if ft.is_symlink() {
DescriptorType::SymbolicLink
} else if ft.is_block_device() {
DescriptorType::BlockDevice
} else if ft.is_char_device() {
DescriptorType::CharacterDevice
} else if ft.is_file() {
DescriptorType::RegularFile
} else {
DescriptorType::Unknown
}
}
}
impl From<cap_std::fs::Metadata> for DescriptorStat {
fn from(meta: cap_std::fs::Metadata) -> Self {
Self {
type_: meta.file_type().into(),
link_count: meta.nlink(),
size: meta.len(),
data_access_timestamp: meta.accessed().map(|t| datetime_from(t.into_std())).ok(),
data_modification_timestamp: meta.modified().map(|t| datetime_from(t.into_std())).ok(),
status_change_timestamp: meta.created().map(|t| datetime_from(t.into_std())).ok(),
}
}
}
impl From<&cap_std::fs::Metadata> for MetadataHashValue {
fn from(meta: &cap_std::fs::Metadata) -> Self {
use cap_fs_ext::MetadataExt;
use std::hash::Hasher;
let mut hasher = hash_map::DefaultHasher::new();
hasher.write_u64(meta.dev());
hasher.write_u64(meta.ino());
let lower = hasher.finish();
let upper = lower ^ 4614256656552045848u64;
Self { lower, upper }
}
}
#[cfg(unix)]
fn from_raw_os_error(err: Option<i32>) -> Option<ErrorCode> {
use rustix::io::Errno as RustixErrno;
err?;
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)]
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)
}
}
impl From<Advice> for system_interface::fs::Advice {
fn from(advice: Advice) -> Self {
match advice {
Advice::Normal => Self::Normal,
Advice::Sequential => Self::Sequential,
Advice::Random => Self::Random,
Advice::WillNeed => Self::WillNeed,
Advice::DontNeed => Self::DontNeed,
Advice::NoReuse => Self::NoReuse,
}
}
}
bitflags! {
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct FilePerms: usize {
const READ = 0b1;
const WRITE = 0b10;
}
}
bitflags! {
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct OpenMode: usize {
const READ = 0b1;
const WRITE = 0b10;
}
}
bitflags! {
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct DirPerms: usize {
const READ = 0b1;
const MUTATE = 0b10;
}
}
#[derive(Clone)]
pub enum Descriptor {
File(File),
Dir(Dir),
}
impl Descriptor {
pub fn file(&self) -> Result<&File, ErrorCode> {
match self {
Self::File(f) => Ok(f),
Self::Dir(_) => Err(ErrorCode::BadDescriptor),
}
}
pub fn dir(&self) -> Result<&Dir, ErrorCode> {
match self {
Self::Dir(d) => Ok(d),
Self::File(_) => Err(ErrorCode::NotDirectory),
}
}
pub fn is_file(&self) -> bool {
match self {
Self::File(_) => true,
Self::Dir(_) => false,
}
}
pub fn is_dir(&self) -> bool {
match self {
Self::File(_) => false,
Self::Dir(_) => true,
}
}
async fn get_metadata(&self) -> std::io::Result<cap_std::fs::Metadata> {
match self {
Self::File(f) => {
f.run_blocking(|f| f.metadata()).await
}
Self::Dir(d) => {
d.run_blocking(|d| d.dir_metadata()).await
}
}
}
async fn advise(self, offset: u64, len: u64, advice: Advice) -> Result<(), ErrorCode> {
use system_interface::fs::FileIoExt;
let f = self.file()?;
f.run_blocking(move |f| f.advise(offset, len, advice.into()))
.await?;
Ok(())
}
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 = d.open(std::path::Component::CurDir)?;
d.sync_data()?;
Ok(())
})
.await
}
}
}
async fn get_flags(self) -> Result<DescriptorFlags, ErrorCode> {
use system_interface::fs::{FdFlags, GetSetFdFlags};
fn get_from_fdflags(flags: FdFlags) -> DescriptorFlags {
let mut out = DescriptorFlags::empty();
if flags.contains(FdFlags::DSYNC) {
out |= DescriptorFlags::REQUESTED_WRITE_SYNC;
}
if flags.contains(FdFlags::RSYNC) {
out |= DescriptorFlags::DATA_INTEGRITY_SYNC;
}
if flags.contains(FdFlags::SYNC) {
out |= DescriptorFlags::FILE_INTEGRITY_SYNC;
}
out
}
match self {
Self::File(f) => {
let flags = f.run_blocking(|f| f.get_fd_flags()).await?;
let mut flags = get_from_fdflags(flags);
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 flags = d.run_blocking(|d| d.get_fd_flags()).await?;
let mut flags = get_from_fdflags(flags);
if d.open_mode.contains(OpenMode::READ) {
flags |= DescriptorFlags::READ;
}
if d.open_mode.contains(OpenMode::WRITE) {
flags |= DescriptorFlags::MUTATE_DIRECTORY;
}
Ok(flags)
}
}
}
async fn get_type(self) -> Result<DescriptorType, ErrorCode> {
match self {
Self::File(f) => {
let meta = f.run_blocking(|f| f.metadata()).await?;
Ok(meta.file_type().into())
}
Self::Dir(_) => Ok(DescriptorType::Directory),
}
}
async fn set_size(self, size: u64) -> Result<(), ErrorCode> {
let f = self.file()?;
if !f.perms.contains(FilePerms::WRITE) {
return Err(ErrorCode::NotPermitted);
}
f.run_blocking(move |f| f.set_len(size)).await?;
Ok(())
}
async fn set_times(self, atim: NewTimestamp, mtim: NewTimestamp) -> Result<(), ErrorCode> {
use fs_set_times::SetTimes as _;
match self {
Self::File(f) => {
if !f.perms.contains(FilePerms::WRITE) {
return Err(ErrorCode::NotPermitted);
}
let atim = systemtimespec_from(atim)?;
let mtim = systemtimespec_from(mtim)?;
f.run_blocking(|f| f.set_times(atim, mtim)).await?;
Ok(())
}
Self::Dir(d) => {
if !d.perms.contains(DirPerms::MUTATE) {
return Err(ErrorCode::NotPermitted);
}
let atim = systemtimespec_from(atim)?;
let mtim = systemtimespec_from(mtim)?;
d.run_blocking(|d| d.set_times(atim, mtim)).await?;
Ok(())
}
}
}
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 = d.open(std::path::Component::CurDir)?;
d.sync_all()?;
Ok(())
})
.await
}
}
}
async fn create_directory_at(self, path: String) -> Result<(), ErrorCode> {
let d = self.dir()?;
if !d.perms.contains(DirPerms::MUTATE) {
return Err(ErrorCode::NotPermitted);
}
d.run_blocking(move |d| d.create_dir(&path)).await?;
Ok(())
}
async fn stat(self) -> Result<DescriptorStat, ErrorCode> {
match self {
Self::File(f) => {
let meta = f.run_blocking(|f| f.metadata()).await?;
Ok(meta.into())
}
Self::Dir(d) => {
let meta = d.run_blocking(|d| d.dir_metadata()).await?;
Ok(meta.into())
}
}
}
async fn stat_at(
self,
path_flags: PathFlags,
path: String,
) -> Result<DescriptorStat, ErrorCode> {
let d = self.dir()?;
if !d.perms.contains(DirPerms::READ) {
return Err(ErrorCode::NotPermitted);
}
let meta = if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
d.run_blocking(move |d| d.metadata(&path)).await?
} else {
d.run_blocking(move |d| d.symlink_metadata(&path)).await?
};
Ok(meta.into())
}
async fn set_times_at(
self,
path_flags: PathFlags,
path: String,
atim: NewTimestamp,
mtim: NewTimestamp,
) -> Result<(), ErrorCode> {
use cap_fs_ext::DirExt as _;
let d = self.dir()?;
if !d.perms.contains(DirPerms::MUTATE) {
return Err(ErrorCode::NotPermitted);
}
let atim = systemtimespec_from(atim)?;
let mtim = systemtimespec_from(mtim)?;
if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
d.run_blocking(move |d| {
d.set_times(
&path,
atim.map(cap_fs_ext::SystemTimeSpec::from_std),
mtim.map(cap_fs_ext::SystemTimeSpec::from_std),
)
})
.await?;
} else {
d.run_blocking(move |d| {
d.set_symlink_times(
&path,
atim.map(cap_fs_ext::SystemTimeSpec::from_std),
mtim.map(cap_fs_ext::SystemTimeSpec::from_std),
)
})
.await?;
}
Ok(())
}
async fn link_at(
self,
old_path_flags: PathFlags,
old_path: String,
new_descriptor: Self,
new_path: String,
) -> Result<(), ErrorCode> {
let old_dir = self.dir()?;
if !old_dir.perms.contains(DirPerms::MUTATE) {
return Err(ErrorCode::NotPermitted);
}
let new_dir = new_descriptor.dir()?;
if !new_dir.perms.contains(DirPerms::MUTATE) {
return Err(ErrorCode::NotPermitted);
}
if old_path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
return Err(ErrorCode::Invalid);
}
let new_dir_handle = Arc::clone(&new_dir.dir);
old_dir
.run_blocking(move |d| d.hard_link(&old_path, &new_dir_handle, &new_path))
.await?;
Ok(())
}
async fn open_at(
self,
path_flags: PathFlags,
path: String,
oflags: OpenFlags,
flags: DescriptorFlags,
allow_blocking_current_thread: bool,
) -> Result<Self, ErrorCode> {
use cap_fs_ext::{FollowSymlinks, OpenOptionsFollowExt, OpenOptionsMaybeDirExt};
use system_interface::fs::{FdFlags, GetSetFdFlags};
let d = self.dir()?;
if !d.perms.contains(DirPerms::READ) {
return Err(ErrorCode::NotPermitted);
}
if !d.perms.contains(DirPerms::MUTATE) {
if oflags.contains(OpenFlags::CREATE) || oflags.contains(OpenFlags::TRUNCATE) {
return Err(ErrorCode::NotPermitted);
}
if flags.contains(DescriptorFlags::WRITE) {
return Err(ErrorCode::NotPermitted);
}
}
let mut create = false;
let mut open_mode = OpenMode::empty();
let mut opts = cap_std::fs::OpenOptions::new();
opts.maybe_dir(true);
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);
}
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)
&& (oflags.contains(OpenFlags::CREATE)
|| oflags.contains(OpenFlags::EXCLUSIVE)
|| oflags.contains(OpenFlags::TRUNCATE))
{
return Err(ErrorCode::Invalid);
}
if !d.perms.contains(DirPerms::MUTATE) && create {
return Err(ErrorCode::NotPermitted);
}
if !d.file_perms.contains(FilePerms::WRITE) && open_mode.contains(OpenMode::WRITE) {
return Err(ErrorCode::NotPermitted);
}
enum OpenResult {
Dir(cap_std::fs::Dir),
File(cap_std::fs::File),
NotDir,
}
let opened = d
.run_blocking::<_, std::io::Result<OpenResult>>(move |d| {
let mut opened = d.open_with(&path, &opts)?;
if opened.metadata()?.is_dir() {
Ok(OpenResult::Dir(cap_std::fs::Dir::from_std_file(
opened.into_std(),
)))
} else if oflags.contains(OpenFlags::DIRECTORY) {
Ok(OpenResult::NotDir)
} else {
let set_fd_flags = opened.new_set_fd_flags(FdFlags::NONBLOCK)?;
opened.set_fd_flags(set_fd_flags)?;
Ok(OpenResult::File(opened))
}
})
.await?;
match opened {
OpenResult::Dir(dir) => Ok(Self::Dir(Dir::new(
dir,
d.perms,
d.file_perms,
open_mode,
allow_blocking_current_thread,
))),
OpenResult::File(file) => Ok(Self::File(File::new(
file,
d.file_perms,
open_mode,
allow_blocking_current_thread,
))),
OpenResult::NotDir => Err(ErrorCode::NotDirectory),
}
}
async fn readlink_at(self, path: String) -> Result<String, ErrorCode> {
let d = self.dir()?;
if !d.perms.contains(DirPerms::READ) {
return Err(ErrorCode::NotPermitted);
}
let link = d.run_blocking(move |d| d.read_link(&path)).await?;
link.into_os_string()
.into_string()
.or(Err(ErrorCode::IllegalByteSequence))
}
async fn remove_directory_at(self, path: String) -> Result<(), ErrorCode> {
let d = self.dir()?;
if !d.perms.contains(DirPerms::MUTATE) {
return Err(ErrorCode::NotPermitted);
}
d.run_blocking(move |d| d.remove_dir(&path)).await?;
Ok(())
}
async fn rename_at(
self,
old_path: String,
new_fd: Self,
new_path: String,
) -> Result<(), ErrorCode> {
let old_dir = self.dir()?;
if !old_dir.perms.contains(DirPerms::MUTATE) {
return Err(ErrorCode::NotPermitted);
}
let new_dir = new_fd.dir()?;
if !new_dir.perms.contains(DirPerms::MUTATE) {
return Err(ErrorCode::NotPermitted);
}
let new_dir_handle = Arc::clone(&new_dir.dir);
old_dir
.run_blocking(move |d| d.rename(&old_path, &new_dir_handle, &new_path))
.await?;
Ok(())
}
async fn symlink_at(self, src_path: String, dest_path: String) -> Result<(), ErrorCode> {
#[cfg(windows)]
use cap_fs_ext::DirExt;
let d = self.dir()?;
if !d.perms.contains(DirPerms::MUTATE) {
return Err(ErrorCode::NotPermitted);
}
d.run_blocking(move |d| d.symlink(&src_path, &dest_path))
.await?;
Ok(())
}
async fn unlink_file_at(self, path: String) -> Result<(), ErrorCode> {
use cap_fs_ext::DirExt;
let d = self.dir()?;
if !d.perms.contains(DirPerms::MUTATE) {
return Err(ErrorCode::NotPermitted);
}
d.run_blocking(move |d| d.remove_file_or_symlink(&path))
.await?;
Ok(())
}
async fn is_same_object(self, other: Self) -> wasmtime::Result<bool> {
use cap_fs_ext::MetadataExt;
let meta_a = self.get_metadata().await?;
let meta_b = other.get_metadata().await?;
if meta_a.dev() == meta_b.dev() && meta_a.ino() == meta_b.ino() {
debug_assert_eq!(
MetadataHashValue::from(&meta_a).upper,
MetadataHashValue::from(&meta_b).upper,
);
debug_assert_eq!(
MetadataHashValue::from(&meta_a).lower,
MetadataHashValue::from(&meta_b).lower,
);
Ok(true)
} else {
Ok(false)
}
}
async fn metadata_hash(self) -> Result<MetadataHashValue, ErrorCode> {
let meta = self.get_metadata().await?;
Ok(MetadataHashValue::from(&meta))
}
async fn metadata_hash_at(
self,
path_flags: PathFlags,
path: String,
) -> Result<MetadataHashValue, ErrorCode> {
let d = self.dir()?;
let meta = d
.run_blocking(move |d| {
if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
d.metadata(path)
} else {
d.symlink_metadata(path)
}
})
.await?;
Ok(MetadataHashValue::from(&meta))
}
}
#[derive(Clone)]
pub struct File {
file: Arc<cap_std::fs::File>,
perms: FilePerms,
open_mode: OpenMode,
allow_blocking_current_thread: bool,
}
impl File {
pub fn new(
file: cap_std::fs::File,
perms: FilePerms,
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(&cap_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(&cap_std::fs::File) -> R + Send + 'static,
R: Send + 'static,
{
let f = self.file.clone();
AbortOnDropJoinHandle(spawn_blocking(move || body(&f)))
}
pub(crate) fn as_blocking_file(&self) -> Option<&cap_std::fs::File> {
if self.allow_blocking_current_thread {
Some(&self.file)
} else {
None
}
}
}
#[derive(Clone)]
pub struct Dir {
dir: Arc<cap_std::fs::Dir>,
perms: DirPerms,
file_perms: FilePerms,
open_mode: OpenMode,
allow_blocking_current_thread: bool,
}
impl Dir {
pub fn new(
dir: cap_std::fs::Dir,
perms: DirPerms,
file_perms: FilePerms,
open_mode: OpenMode,
allow_blocking_current_thread: bool,
) -> Self {
Dir {
dir: Arc::new(dir),
perms,
file_perms,
open_mode,
allow_blocking_current_thread,
}
}
pub(crate) async fn run_blocking<F, R>(&self, body: F) -> R
where
F: FnOnce(&cap_std::fs::Dir) -> 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.unwrap()
}
}
}