use anyhow::{anyhow, Result};
use easy_error::Error as BundError;
use rust_dynamic::value::Value;
use rust_multistackvm::multistackvm::VM;
use super::helpers::{pull, push, require_depth, resolve_fs_path, value_to_string};
pub fn register(vm: &mut VM) -> Result<()> {
vm.register_inline("ink.fs.read".to_string(), ink_fs_read)
.map_err(|e| anyhow!("register ink.fs.read: {e}"))?;
vm.register_inline("ink.fs.write".to_string(), ink_fs_write)
.map_err(|e| anyhow!("register ink.fs.write: {e}"))?;
Ok(())
}
fn to_bund_err(e: anyhow::Error) -> BundError {
easy_error::err_msg(e.to_string())
}
fn ink_fs_read(vm: &mut VM) -> std::result::Result<&mut VM, BundError> {
do_ink_fs_read(vm).map_err(to_bund_err)
}
fn do_ink_fs_read(vm: &mut VM) -> Result<&mut VM> {
let tag = "ink.fs.read";
require_depth(vm, 1, tag)?;
let path = value_to_string(pull(vm, tag)?, "path", tag)?;
let resolved = resolve_fs_path(tag, &path)?;
reject_symlink_escape(tag, &path, &resolved)?;
let bytes = std::fs::read(&resolved)
.map_err(|e| anyhow!("{tag} `{path}`: {e}"))?;
let s = String::from_utf8(bytes)
.map_err(|e| anyhow!("{tag} `{path}`: not UTF-8: {e}"))?;
push(vm, Value::from_string(s));
Ok(vm)
}
fn reject_symlink_escape(tag: &str, raw: &str, resolved: &std::path::Path) -> Result<()> {
if crate::scripting::active_policy().map(|p| p.fs_unsandboxed).unwrap_or(false) {
return Ok(());
}
let Some(store) = crate::scripting::active_store() else {
return Ok(());
};
let (Ok(real), Ok(root)) = (resolved.canonicalize(), store.project_root().canonicalize())
else {
return Ok(());
};
if !real.starts_with(&root) {
return Err(anyhow!(
"{tag} `{raw}`: rejected — resolves outside the project via a symlink"
));
}
Ok(())
}
fn ink_fs_write(vm: &mut VM) -> std::result::Result<&mut VM, BundError> {
do_ink_fs_write(vm).map_err(to_bund_err)
}
fn do_ink_fs_write(vm: &mut VM) -> Result<&mut VM> {
let tag = "ink.fs.write";
require_depth(vm, 2, tag)?;
let content = value_to_string(pull(vm, tag)?, "content", tag)?;
let path = value_to_string(pull(vm, tag)?, "path", tag)?;
let resolved = resolve_fs_path(tag, &path)?;
crate::io_atomic::write(&resolved, content.as_bytes())
.map_err(|e| anyhow!("{tag} `{path}`: {e}"))?;
Ok(vm)
}