harn-vm 0.10.180

Async bytecode virtual machine for the Harn programming language
use std::collections::BTreeMap;

use crate::stdlib::args::Args;
use crate::stdlib::macros::harn_builtin;
use crate::value::{VmDictExt, VmError, VmValue};

use super::{io_error_thrown, resolve_fs_path};

#[cfg(unix)]
pub(super) fn unix_file_identity(metadata: &std::fs::Metadata) -> String {
    use std::os::unix::fs::MetadataExt;
    format!("unix:{}:{}", metadata.dev(), metadata.ino())
}

#[harn_builtin(
    exposure = "runtime_internal",
    sig = "stat(path: string, options?: {follow_symlinks?: bool}) -> {size: int, is_file: bool, is_dir: bool, is_symlink: bool, readonly: bool, modified?: float, mode?: int, identity?: string}",
    category = "fs",
    doc = "Read file metadata. Set follow_symlinks=false to inspect the link itself. Unix hosts also report permission mode bits and file identity."
)]
fn stat_builtin(args: &[VmValue], _out: &mut String) -> Result<VmValue, VmError> {
    let reader = Args::thrown("stat", args);
    reader.arity(1, 2)?;
    let path = reader.string(0, "path")?;
    let mut options = reader.options(1, "options")?;
    let follow_symlinks = options.bool_or("follow_symlinks", true)?;
    options.finish(&[])?;
    let resolved = resolve_fs_path(path);
    let authorize = if follow_symlinks {
        crate::stdlib::sandbox::enforce_fs_path
    } else {
        crate::stdlib::sandbox::enforce_fs_entry_path
    };
    authorize("stat", &resolved, crate::stdlib::sandbox::FsAccess::Read)?;
    let metadata = if follow_symlinks {
        std::fs::metadata(&resolved)
    } else {
        std::fs::symlink_metadata(&resolved)
    }
    .map_err(|e| io_error_thrown(format!("Failed to stat {}: {e}", resolved.display()), &e))?;
    let mut info = BTreeMap::new();
    info.put_int("size", metadata.len() as i64);
    info.put_bool("is_file", metadata.is_file());
    info.put_bool("is_dir", metadata.is_dir());
    info.put_bool("is_symlink", metadata.is_symlink());
    info.put_bool("readonly", metadata.permissions().readonly());
    #[cfg(unix)]
    {
        use std::os::unix::fs::PermissionsExt;
        info.put_int("mode", i64::from(metadata.permissions().mode()));
        info.put_str("identity", unix_file_identity(&metadata));
    }
    if let Ok(modified) = metadata.modified() {
        if let Ok(dur) = modified.duration_since(std::time::UNIX_EPOCH) {
            info.put("modified", VmValue::Float(dur.as_secs_f64()));
        }
    }
    Ok(VmValue::dict(info))
}