use crate::grant_file_access;
use rong::*;
use std::time::SystemTime;
use tokio::fs;
async fn symlink(old_path: String, new_path: String) -> JSResult<()> {
let resolved_old = grant_file_access(&old_path)?;
let resolved_new = grant_file_access(&new_path)?;
#[cfg(unix)]
{
fs::symlink(&resolved_old, &resolved_new)
.await
.map_err(|e| HostError::new("FS_IO", format!("Failed to create symlink: {}", e)).into())
}
#[cfg(windows)]
{
match fs::metadata(&resolved_old).await {
Ok(metadata) => {
if metadata.is_dir() {
tokio::fs::symlink_dir(&resolved_old, &resolved_new).await
} else {
tokio::fs::symlink_file(&resolved_old, &resolved_new).await
}
}
Err(e) => Err(e),
}
.map_err(|e| HostError::new("FS_IO", format!("Failed to create symlink: {}", e)).into())
}
}
async fn readlink(path: String) -> JSResult<String> {
let resolved = grant_file_access(&path)?;
fs::read_link(&resolved)
.await
.map(|p| p.to_string_lossy().into_owned())
.map_err(|e| HostError::new("FS_IO", format!("Failed to read symlink: {}", e)).into())
}
#[cfg(unix)]
async fn chmod(path: String, mode: u32) -> JSResult<()> {
let resolved = grant_file_access(&path)?;
use std::os::unix::fs::PermissionsExt;
let permissions = std::fs::Permissions::from_mode(mode);
fs::set_permissions(&resolved, permissions)
.await
.map_err(|e| HostError::new("FS_IO", format!("Failed to change permissions: {}", e)).into())
}
#[cfg(unix)]
async fn chown(path: String, uid: u32, gid: u32) -> JSResult<()> {
let resolved = grant_file_access(&path)?;
use nix::unistd::{Gid, Uid, chown as nix_chown};
nix_chown(
&resolved,
Some(Uid::from_raw(uid)),
Some(Gid::from_raw(gid)),
)
.map_err(|e| HostError::new("FS_IO", format!("Failed to change ownership: {}", e)).into())
}
#[derive(FromJSObj)]
pub(crate) struct UTimeOptions {
accessed: Option<f64>,
modified: Option<f64>,
}
async fn utime(path: String, options: UTimeOptions) -> JSResult<()> {
let resolved = grant_file_access(&path)?;
use filetime::FileTime;
let atime = options
.accessed
.map(|t| FileTime::from_unix_time((t / 1000.0) as i64, 0));
let mtime = options
.modified
.map(|t| FileTime::from_unix_time((t / 1000.0) as i64, 0));
filetime::set_file_times(
&resolved,
atime.unwrap_or_else(|| FileTime::from_system_time(SystemTime::now())),
mtime.unwrap_or_else(|| FileTime::from_system_time(SystemTime::now())),
)
.map_err(|e| HostError::new("FS_IO", format!("Failed to set file times: {}", e)).into())
}
async fn rename(from: String, to: String) -> JSResult<()> {
let resolved_from = grant_file_access(&from)?;
let resolved_to = grant_file_access(&to)?;
fs::rename(&resolved_from, &resolved_to)
.await
.map_err(|e| HostError::new("FS_IO", format!("Failed to rename file: {}", e)).into())
}
async fn real_path(path: String) -> JSResult<String> {
let resolved = grant_file_access(&path)?;
fs::canonicalize(&resolved)
.await
.map(|p| p.to_string_lossy().into_owned())
.map_err(|e| HostError::new("FS_IO", format!("Failed to resolve real path: {}", e)).into())
}
pub(crate) fn init(ctx: &JSContext) -> JSResult<()> {
let rong = ctx.host_namespace();
let symlink_fn = JSFunc::new(ctx, symlink)?.name("symlink")?;
rong.set("symlink", symlink_fn)?;
let readlink_fn = JSFunc::new(ctx, readlink)?.name("readlink")?;
rong.set("readlink", readlink_fn)?;
#[cfg(unix)]
{
let chmod_fn = JSFunc::new(ctx, chmod)?.name("chmod")?;
rong.set("chmod", chmod_fn)?;
let chown_fn = JSFunc::new(ctx, chown)?.name("chown")?;
rong.set("chown", chown_fn)?;
}
let utime_fn = JSFunc::new(ctx, utime)?.name("utime")?;
rong.set("utime", utime_fn)?;
let rename_fn = JSFunc::new(ctx, rename)?.name("rename")?;
rong.set("rename", rename_fn)?;
let real_path_fn = JSFunc::new(ctx, real_path)?.name("realPath")?;
rong.set("realPath", real_path_fn)?;
Ok(())
}