use cap_std::fs::Dir;
use monty_types::{FileMode, MontyObject};
use super::{
common::{
MemoryBudget, MountContext, check_write_limit, commit_write_bytes, host_append_bytes, host_append_text,
host_is_dir, host_is_file, host_iterdir, host_mkdir, host_read_bytes, host_read_text, host_rmdir, host_stat,
host_unlink, host_write_bytes, host_write_text, map_io, reject_non_regular,
},
dispatch::{FsRequest, file_handle_result},
error::MountError,
path_security::{MountRelativePath, normalize_virtual_path, resolve_virtual_path},
};
pub(super) fn execute(request: FsRequest, ctx: &mut MountContext<'_>) -> Result<MontyObject, MountError> {
match request {
FsRequest::Exists { path } => bool_query(&path, ctx, |dir, rel| dir.exists(rel)),
FsRequest::IsFile { path } => bool_query(&path, ctx, host_is_file),
FsRequest::IsDir { path } => bool_query(&path, ctx, host_is_dir),
FsRequest::IsSymlink { path } => bool_query(&path, ctx, |dir, rel| {
dir.symlink_metadata(rel).is_ok_and(|meta| meta.is_symlink())
}),
FsRequest::ReadText { path } => {
let target = resolve_virtual_path(&path, ctx.mount_virtual)?;
host_read_text(
ctx.mount_dir,
target.for_dir_op(),
&path,
MemoryBudget::full(ctx.memory_usage_limit),
)
}
FsRequest::ReadBytes { path } => {
let target = resolve_virtual_path(&path, ctx.mount_virtual)?;
host_read_bytes(
ctx.mount_dir,
target.for_dir_op(),
&path,
MemoryBudget::full(ctx.memory_usage_limit),
)
}
FsRequest::WriteText { path, data } => write_text(&path, &data, ctx),
FsRequest::WriteBytes { path, data } => write_bytes(&path, &data, ctx),
FsRequest::AppendText { path, data } => append_text(&path, &data, ctx),
FsRequest::AppendBytes { path, data } => append_bytes(&path, &data, ctx),
FsRequest::Mkdir {
path,
parents,
exist_ok,
} => mkdir(&path, parents, exist_ok, ctx),
FsRequest::Unlink { path } => {
let target = resolve_virtual_path(&path, ctx.mount_virtual)?;
host_unlink(ctx.mount_dir, target.for_dir_op(), &path)
}
FsRequest::Rmdir { path } => {
let target = resolve_virtual_path(&path, ctx.mount_virtual)?;
reject_mount_root(&target, &path)?;
host_rmdir(ctx.mount_dir, target.for_dir_op(), &path)
}
FsRequest::Iterdir { path } => {
let target = resolve_virtual_path(&path, ctx.mount_virtual)?;
host_iterdir(
ctx.mount_dir,
target.for_dir_op(),
&path,
MemoryBudget::full(ctx.memory_usage_limit),
)
}
FsRequest::Stat { path } => {
let target = resolve_virtual_path(&path, ctx.mount_virtual)?;
host_stat(ctx.mount_dir, target.for_dir_op(), &path)
}
FsRequest::Rename { src, dst } => rename(&src, &dst, ctx),
FsRequest::Resolve { path } | FsRequest::Absolute { path } => {
Ok(MontyObject::Path(normalize_virtual_path(&path)))
}
FsRequest::Open { path, mode } => open(&path, mode, ctx),
}
}
fn open(path: &str, mode: FileMode, ctx: &mut MountContext<'_>) -> Result<MontyObject, MountError> {
let target = resolve_virtual_path(path, ctx.mount_virtual)?;
let rel = target.for_dir_op();
match mode {
FileMode::Read(_) | FileMode::ReadUpdate(_) => {
ctx.mount_dir.metadata(rel).map_err(|err| map_io(err, path))?;
reject_non_regular(ctx.mount_dir, rel, path)?;
}
FileMode::Write(_) | FileMode::WriteUpdate(_) => {
check_write_limit(0, ctx)?;
host_write_text(ctx.mount_dir, rel, "", path)?;
commit_write_bytes(0, ctx);
}
FileMode::Append(_) | FileMode::AppendUpdate(_) => {
host_append_bytes(ctx.mount_dir, rel, &[], path)?;
}
}
Ok(file_handle_result(path, mode))
}
fn bool_query(
path: &str,
ctx: &MountContext<'_>,
query: impl Fn(&Dir, &str) -> bool,
) -> Result<MontyObject, MountError> {
let target = resolve_virtual_path(path, ctx.mount_virtual)?;
Ok(MontyObject::Bool(query(ctx.mount_dir, target.for_dir_op())))
}
fn write_text(path: &str, data: &str, ctx: &mut MountContext<'_>) -> Result<MontyObject, MountError> {
check_write_limit(data.len(), ctx)?;
let target = resolve_virtual_path(path, ctx.mount_virtual)?;
let result = host_write_text(ctx.mount_dir, target.for_dir_op(), data, path)?;
commit_write_bytes(data.len(), ctx);
Ok(result)
}
fn write_bytes(path: &str, data: &[u8], ctx: &mut MountContext<'_>) -> Result<MontyObject, MountError> {
check_write_limit(data.len(), ctx)?;
let target = resolve_virtual_path(path, ctx.mount_virtual)?;
let result = host_write_bytes(ctx.mount_dir, target.for_dir_op(), data, path)?;
commit_write_bytes(data.len(), ctx);
Ok(result)
}
fn append_text(path: &str, data: &str, ctx: &mut MountContext<'_>) -> Result<MontyObject, MountError> {
check_write_limit(data.len(), ctx)?;
let target = resolve_virtual_path(path, ctx.mount_virtual)?;
let result = host_append_text(ctx.mount_dir, target.for_dir_op(), data, path)?;
commit_write_bytes(data.len(), ctx);
Ok(result)
}
fn append_bytes(path: &str, data: &[u8], ctx: &mut MountContext<'_>) -> Result<MontyObject, MountError> {
check_write_limit(data.len(), ctx)?;
let target = resolve_virtual_path(path, ctx.mount_virtual)?;
let result = host_append_bytes(ctx.mount_dir, target.for_dir_op(), data, path)?;
commit_write_bytes(data.len(), ctx);
Ok(result)
}
fn mkdir(path: &str, parents: bool, exist_ok: bool, ctx: &MountContext<'_>) -> Result<MontyObject, MountError> {
let target = resolve_virtual_path(path, ctx.mount_virtual)?;
host_mkdir(ctx.mount_dir, target.for_dir_op(), parents, exist_ok, path)
}
fn rename(src: &str, dst: &str, ctx: &MountContext<'_>) -> Result<MontyObject, MountError> {
let src_target = resolve_virtual_path(src, ctx.mount_virtual)?;
let dst_target = resolve_virtual_path(dst, ctx.mount_virtual)?;
reject_mount_root(&src_target, src)?;
reject_mount_root(&dst_target, dst)?;
ctx.mount_dir
.rename(src_target.for_dir_op(), ctx.mount_dir, dst_target.for_dir_op())
.map_err(|err| map_io(err, src))?;
Ok(MontyObject::None)
}
fn reject_mount_root(target: &MountRelativePath, vpath: &str) -> Result<(), MountError> {
if target.is_mount_root() {
Err(MountError::PathEscape {
virtual_path: vpath.to_owned(),
})
} else {
Ok(())
}
}