use crate::host::{with_host, JsObj};
use fusevm::Value;
use indexmap::IndexMap;
pub const METHODS: &[&str] = &[
"cwd",
"chdir",
"exit",
"hrtime",
"hrtime.bigint",
"uptime",
"memoryUsage",
"cpuUsage",
"umask",
"binding",
"emit",
"on",
"once",
"off",
"addListener",
"removeListener",
"removeAllListeners",
"listeners",
"emitWarning",
"kill",
"getuid",
"getgid",
"geteuid",
"getegid",
"getgroups",
"setuid",
"setgid",
"seteuid",
"setegid",
"setgroups",
"initgroups",
"ref",
"unref",
"abort",
"getActiveResourcesInfo",
"resourceUsage",
"threadCpuUsage",
"availableMemory",
"constrainedMemory",
"getBuiltinModule",
"openStdin",
"hasUncaughtExceptionCaptureCallback",
"setUncaughtExceptionCaptureCallback",
"addUncaughtExceptionCaptureCallback",
"execve",
"reallyExit",
"loadEnvFile",
"setSourceMapsEnabled",
];
thread_local! {
static UNCAUGHT_CAPTURE: std::cell::RefCell<Option<Value>> =
const { std::cell::RefCell::new(None) };
}
static TRACE_HINT_SHOWN: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
pub fn emit_warning(name: &str, code: Option<&str>, message: &str, detail: Option<&str>) {
let argv: Vec<String> = std::env::args().collect();
let flag = |f: &str| argv.iter().any(|a| a == f);
let is_deprecation = name == "DeprecationWarning";
if flag("--no-warnings") || (is_deprecation && flag("--no-deprecation")) {
return;
}
let trace = flag("--trace-warnings") || (is_deprecation && flag("--trace-deprecation"));
let mut msg = std::format!("(node:{}) ", std::process::id());
if let Some(c) = code {
msg.push_str(&std::format!("[{c}] "));
}
msg.push_str(&std::format!("{name}: {message}"));
if let Some(d) = detail {
msg.push_str(&std::format!("\n{d}"));
}
if !trace && !TRACE_HINT_SHOWN.swap(true, std::sync::atomic::Ordering::Relaxed) {
let trace_flag = if is_deprecation {
"--trace-deprecation"
} else {
"--trace-warnings"
};
msg.push_str(&std::format!(
"\n(Use `node {trace_flag} ...` to show where the warning was created)"
));
}
eprintln!("{msg}");
}
pub fn emit_deprecation_warning(code: &str, message: &str) {
use std::cell::RefCell;
thread_local! {
static SEEN: RefCell<std::collections::HashSet<String>> =
RefCell::new(std::collections::HashSet::new());
}
let first = SEEN.with(|s| s.borrow_mut().insert(code.to_string()));
if first {
emit_warning("DeprecationWarning", Some(code), message, None);
}
}
pub fn signal_number(name: &str) -> Option<libc::c_int> {
Some(match name.to_uppercase().as_str() {
"SIGHUP" => libc::SIGHUP,
"SIGINT" => libc::SIGINT,
"SIGQUIT" => libc::SIGQUIT,
"SIGILL" => libc::SIGILL,
"SIGTRAP" => libc::SIGTRAP,
"SIGABRT" => libc::SIGABRT,
"SIGBUS" => libc::SIGBUS,
"SIGFPE" => libc::SIGFPE,
"SIGKILL" => libc::SIGKILL,
"SIGUSR1" => libc::SIGUSR1,
"SIGSEGV" => libc::SIGSEGV,
"SIGUSR2" => libc::SIGUSR2,
"SIGPIPE" => libc::SIGPIPE,
"SIGALRM" => libc::SIGALRM,
"SIGTERM" => libc::SIGTERM,
"SIGCHLD" => libc::SIGCHLD,
"SIGCONT" => libc::SIGCONT,
"SIGSTOP" => libc::SIGSTOP,
"SIGTSTP" => libc::SIGTSTP,
"SIGWINCH" => libc::SIGWINCH,
_ => return None,
})
}
fn emit_warning_args(args: &[Value]) {
let message = super::arg_str(args, 0);
let mut name = "Warning".to_string();
let mut code: Option<String> = None;
let mut detail: Option<String> = None;
match args.get(1) {
Some(v) if with_host(|h| matches!(h.get(v), Some(JsObj::Object(_)))) => {
let field = |k: &str| {
with_host(|h| match h.get(v) {
Some(JsObj::Object(p)) => {
p.get(k).filter(|x| !h.is_nullish(x)).map(|x| h.str_of(x))
}
_ => None,
})
};
if let Some(t) = field("type") {
name = t;
}
code = field("code");
detail = field("detail");
}
Some(_) => {
name = super::arg_str(args, 1);
code = args.get(2).map(|_| super::arg_str(args, 2));
}
None => {}
}
emit_warning(&name, code.as_deref(), &message, detail.as_deref());
}
fn memo(name: &str, make: impl FnOnce() -> Value) -> Value {
if let Some(v) = with_host(|h| h.builtin_static("process", name)) {
return v;
}
let v = make();
with_host(|h| h.set_builtin_static("process", name, v.clone()));
v
}
fn features() -> Value {
with_host(|h| {
let mut m = IndexMap::new();
for (k, v) in [
("inspector", false),
("debug", false),
("uv", false),
("ipv6", true),
("tls_alpn", false),
("tls_sni", false),
("tls_ocsp", false),
("tls", true),
("openssl_is_boringssl", false),
("cached_builtins", true),
("require_module", true),
("quic", false),
] {
m.insert(k.to_string(), Value::Bool(v));
}
let ts = h.new_str("none");
m.insert("typescript".into(), ts);
h.new_object(m)
})
}
fn config() -> Value {
with_host(|h| {
let mut vars = IndexMap::new();
let arch = h.new_str(super::os::arch());
let plat = h.new_str(super::os::platform());
vars.insert("host_arch".to_string(), arch.clone());
vars.insert("target_arch".to_string(), arch);
vars.insert("node_shared".to_string(), Value::Bool(false));
vars.insert("node_use_openssl".to_string(), Value::Bool(false));
vars.insert("v8_enable_i18n_support".to_string(), Value::Bool(false));
vars.insert("node_platform".to_string(), plat);
let variables = h.new_object(vars);
let defaults = h.new_object(IndexMap::new());
let mut m = IndexMap::new();
m.insert("target_defaults".to_string(), defaults);
m.insert("variables".to_string(), variables);
h.new_object(m)
})
}
fn allowed_flags() -> Value {
let flags = [
"--enable-source-maps",
"--max-old-space-size",
"--no-warnings",
"--preserve-symlinks",
"--stack-trace-limit",
"--throw-deprecation",
"--trace-warnings",
"--unhandled-rejections",
"--zero-fill-buffers",
];
let vals: Vec<Value> = flags.iter().map(|f| with_host(|h| h.new_str(*f))).collect();
let set = with_host(|h| {
h.alloc(crate::host::JsObj::Set {
entries: indexmap::IndexMap::new(),
weak: false,
})
});
for v in vals {
let _ = crate::host::call_method(&set, "add", vec![v]);
}
set
}
pub fn constant(name: &str) -> Option<Value> {
Some(match name {
"env" => memo("env", env_object),
"release" => memo("release", || {
with_host(|h| {
let mut m = IndexMap::new();
let name = h.new_str("node");
m.insert("name".into(), name);
h.new_object(m)
})
}),
"argv" => memo("argv", argv),
"argv0" => with_host(|h| h.new_str(exec_path())),
"execPath" => with_host(|h| h.new_str(exec_path())),
"execArgv" => memo("execArgv", exec_argv),
"platform" => with_host(|h| h.new_str(super::os::platform())),
"arch" => with_host(|h| h.new_str(super::os::arch())),
"pid" => Value::Float(std::process::id() as f64),
"ppid" => Value::Float(0.0),
"title" => with_host(|h| h.new_str("node")),
"version" => with_host(|h| h.new_str("v26.5.0")),
"versions" => memo("versions", versions),
"features" => memo("features", features),
"config" => memo("config", config),
"allowedNodeEnvironmentFlags" => memo("allowedNodeEnvironmentFlags", allowed_flags),
"stdout" => memo("stdout", || std_stream(1)),
"stderr" => memo("stderr", || std_stream(2)),
"stdin" => memo("stdin", || std_stream(0)),
"exitCode" => match with_host(|h| h.exit_code) {
Some(c) => Value::Float(c as f64),
None => Value::Undef,
},
_ => return None,
})
}
pub fn set_exit_code(val: &Value) -> Result<(), String> {
if matches!(val, Value::Undef) || with_host(|h| h.is_null(val)) {
with_host(|h| h.exit_code = None);
return Ok(());
}
let numeric = match with_host(|h| h.as_str(val)) {
Some(s) if !s.is_empty() => {
let n = with_host(|h| h.to_number(val));
if n.is_nan() {
None
} else {
Some(n)
}
}
Some(_) => None,
None => match val {
Value::Float(_) | Value::Int(_) => Some(with_host(|h| h.to_number(val))),
_ => None,
},
};
match numeric {
Some(n) if n.fract() == 0.0 && n.is_finite() => {
with_host(|h| h.exit_code = Some(n as i32));
Ok(())
}
Some(n) => Err(crate::host::coded_error(
"RangeError",
"ERR_OUT_OF_RANGE",
&format!(
"The value of \"code\" is out of range. It must be an integer. Received {}",
crate::host::fmt_number(n)
),
)),
None => Err(crate::host::invalid_arg_type(
"code", "argument", "number", val,
)),
}
}
pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
Some(match method {
"cwd" => {
let d = std::env::current_dir()
.map(|p| p.to_string_lossy().into_owned())
.unwrap_or_default();
Ok(with_host(|h| h.new_str(d)))
}
"hrtime" => Ok(hrtime(args)),
"hrtime.bigint" => Ok(with_host(|h| {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default();
h.new_bigint(num_bigint::BigInt::from(now.as_nanos()))
})),
"uptime" => Ok(Value::Float(0.0)),
"memoryUsage" => Ok(memory_usage()),
"cpuUsage" => Ok(with_host(|h| {
let mut m = IndexMap::new();
m.insert("user".into(), Value::Float(0.0));
m.insert("system".into(), Value::Float(0.0));
h.new_object(m)
})),
"umask" => Ok(Value::Float(0.0)),
"binding" => Err(crate::host::type_error("process.binding is not supported")),
"on" | "once" | "addListener" => {
let (event, f) = (event_name(args), args.get(1).cloned());
if let Some(f) = f {
let once = method == "once";
with_host(|h| {
h.process_listeners
.entry(event)
.or_default()
.push(crate::host::ProcListener { f, once })
});
}
Ok(with_host(|h| h.alloc(JsObj::Builtin("process".into()))))
}
"off" | "removeListener" => {
let (event, f) = (event_name(args), args.get(1).cloned());
if let Some(f) = f {
with_host(|h| {
if let Some(l) = h.process_listeners.get_mut(&event) {
if let Some(i) = l.iter().position(|x| x.f == f) {
l.remove(i);
}
}
});
}
Ok(with_host(|h| h.alloc(JsObj::Builtin("process".into()))))
}
"removeAllListeners" => {
let event = event_name(args);
with_host(|h| {
if event.is_empty() {
h.process_listeners.clear();
} else {
h.process_listeners.shift_remove(&event);
}
});
Ok(with_host(|h| h.alloc(JsObj::Builtin("process".into()))))
}
"listeners" => {
let event = event_name(args);
Ok(with_host(|h| {
let l = h
.process_listeners
.get(&event)
.map(|v| v.iter().map(|x| x.f.clone()).collect())
.unwrap_or_default();
h.new_array(l)
}))
}
"emit" => {
let event = event_name(args);
let rest: Vec<Value> = args.iter().skip(1).cloned().collect();
let listeners = with_host(|h| h.take_process_listeners(&event));
let any = !listeners.is_empty();
let mut r = Ok(Value::Bool(any));
for f in listeners {
if let Err(e) = crate::host::invoke(&f, rest.clone(), None) {
r = Err(e);
break;
}
}
r
}
"emitWarning" => {
emit_warning_args(args);
Ok(Value::Undef)
}
"exit" | "reallyExit" => {
if !args.is_empty() {
if let Err(e) = set_exit_code(&args[0]) {
return Some(Err(e));
}
}
let code = with_host(|h| h.exit_code).unwrap_or(0);
if let Err(e) = emit_exit_event(code) {
return Some(Err(e));
}
let code = with_host(|h| h.exit_code).unwrap_or(0);
use std::io::Write;
let _ = std::io::stdout().flush();
let _ = std::io::stderr().flush();
crate::cache::flush();
std::process::exit(code);
}
"chdir" => {
let dir = super::arg_str(args, 0);
std::env::set_current_dir(&dir)
.map(|()| Value::Undef)
.map_err(|e| {
let from = std::env::current_dir()
.map(|p| p.display().to_string())
.unwrap_or_default();
format!(
"Error: {}, chdir '{from}' -> '{dir}'",
crate::stdlib::fs::libuv_message(&e)
)
})
}
"kill" => {
let pid = with_host(|h| args.first().map(|v| h.to_number(v)).unwrap_or(0.0)) as i32;
let sig: Result<libc::c_int, String> = match args.get(1) {
Some(v) if !matches!(v, Value::Undef) => match with_host(|h| h.as_str(v)) {
Some(name) => signal_number(&name).ok_or(crate::host::coded_error(
"TypeError",
"ERR_UNKNOWN_SIGNAL",
&format!("Unknown signal: {name}"),
)),
None => Ok(with_host(|h| h.to_number(v)) as libc::c_int),
},
_ => Ok(libc::SIGTERM),
};
sig.and_then(|sig| {
if unsafe { libc::kill(pid, sig) } != 0 {
Err(format!("Error: {}", std::io::Error::last_os_error()))
} else {
Ok(Value::Undef)
}
})
}
"setSourceMapsEnabled" => Ok(Value::Undef),
"getuid" => Ok(Value::Float(unsafe { libc::getuid() } as f64)),
"geteuid" => Ok(Value::Float(unsafe { libc::geteuid() } as f64)),
"getgid" => Ok(Value::Float(unsafe { libc::getgid() } as f64)),
"getegid" => Ok(Value::Float(unsafe { libc::getegid() } as f64)),
"getgroups" => {
let groups = supplementary_groups();
Ok(with_host(|h| {
h.new_array(groups.into_iter().map(Value::Float).collect())
}))
}
"setuid" | "seteuid" | "setgid" | "setegid" => {
let id = super::arg_num(args, 0);
if id.is_finite() {
let id = id as u32;
unsafe {
match method {
"setuid" => libc::setuid(id),
"seteuid" => libc::seteuid(id),
"setgid" => libc::setgid(id),
_ => libc::setegid(id),
};
}
}
Ok(Value::Undef)
}
"setgroups" => {
let groups = gid_array(args.first());
unsafe {
libc::setgroups(groups.len() as _, groups.as_ptr());
}
Ok(Value::Undef)
}
"initgroups" => {
let user = super::arg_str(args, 0);
let extra = super::arg_num(args, 1);
if let Ok(c) = std::ffi::CString::new(user) {
let gid = if extra.is_finite() { extra as u32 } else { 0 };
unsafe {
libc::initgroups(c.as_ptr(), gid as _);
}
}
Ok(Value::Undef)
}
"ref" | "unref" => Ok(with_host(|h| h.alloc(JsObj::Builtin("process".into())))),
"abort" => std::process::abort(),
"getActiveResourcesInfo" => Ok(with_host(|h| h.new_array(Vec::new()))),
"resourceUsage" => Ok(resource_usage()),
"threadCpuUsage" => Ok(thread_cpu_usage()),
"availableMemory" | "constrainedMemory" => Ok(Value::Float(0.0)),
"getBuiltinModule" => {
let id = super::arg_str(args, 0);
let id = id.strip_prefix("node:").unwrap_or(&id);
match crate::stdlib::resolve(id) {
Some(ns) => Ok(with_host(|h| h.alloc(JsObj::Builtin(ns.to_string())))),
None => Ok(Value::Undef),
}
}
"openStdin" => Ok(std_stream(0)),
"hasUncaughtExceptionCaptureCallback" => {
Ok(Value::Bool(UNCAUGHT_CAPTURE.with(|c| c.borrow().is_some())))
}
"setUncaughtExceptionCaptureCallback" => {
let cb = args.first().cloned().unwrap_or(Value::Undef);
let clear = matches!(cb, Value::Undef) || with_host(|h| h.is_null(&cb));
if clear {
UNCAUGHT_CAPTURE.with(|c| *c.borrow_mut() = None);
} else if UNCAUGHT_CAPTURE.with(|c| c.borrow().is_some()) {
return Some(Err(crate::host::type_error(
"`process.setUncaughtExceptionCaptureCallback()` was called \
while a capture callback was already active",
)));
} else {
UNCAUGHT_CAPTURE.with(|c| *c.borrow_mut() = Some(cb));
}
Ok(Value::Undef)
}
"addUncaughtExceptionCaptureCallback" => {
let cb = args.first().cloned().unwrap_or(Value::Undef);
if !matches!(cb, Value::Undef) {
UNCAUGHT_CAPTURE.with(|c| *c.borrow_mut() = Some(cb));
}
Ok(Value::Undef)
}
"execve" => exec_ve(args),
"loadEnvFile" => load_env_file(&super::arg_str(args, 0)),
_ => return None,
})
}
fn env_object() -> Value {
with_host(|h| {
let mut m = IndexMap::new();
m.insert("@@envObject".into(), Value::Bool(true));
for (k, v) in std::env::vars() {
m.insert(k, h.new_str(v));
}
h.new_object(m)
})
}
static ARGV: std::sync::OnceLock<(Vec<String>, Vec<String>)> = std::sync::OnceLock::new();
pub fn install_argv() {
let split = crate::cli::split_argv(std::env::args());
let mut argv = vec![exec_path()];
if let Some(s) = &split.script {
argv.push(if s == "-" {
s.clone()
} else {
super::path::resolve_one(s)
});
}
argv.extend(split.user);
let _ = ARGV.set((split.exec, argv));
}
fn argv() -> Value {
with_host(|h| {
let items: Vec<Value> = match ARGV.get() {
Some((_, argv)) => argv.iter().map(|a| h.new_str(a.clone())).collect(),
None => std::env::args().map(|a| h.new_str(a)).collect(),
};
h.new_array(items)
})
}
fn exec_argv() -> Value {
with_host(|h| {
let items: Vec<Value> = ARGV
.get()
.map(|(e, _)| e.iter().map(|a| h.new_str(a.clone())).collect())
.unwrap_or_default();
h.new_array(items)
})
}
fn exec_path() -> String {
std::env::current_exe()
.map(|p| p.to_string_lossy().into_owned())
.unwrap_or_else(|_| "node".into())
}
fn versions() -> Value {
with_host(|h| {
let mut m = IndexMap::new();
m.insert("node".into(), h.new_str("26.5.0"));
m.insert("v8".into(), h.new_str("0.0.0"));
h.new_object(m)
})
}
fn std_stream(fd: i32) -> Value {
with_host(|h| {
let mut m = IndexMap::new();
m.insert("@@native".into(), h.new_str("WriteStream"));
m.insert("fd".into(), Value::Float(fd as f64));
let is_tty = unsafe { libc::isatty(fd) == 1 };
if is_tty {
m.insert("isTTY".into(), Value::Bool(true));
}
m.insert("writable".into(), Value::Bool(fd != 0));
m.insert("readable".into(), Value::Bool(fd == 0));
if is_tty {
if let Some((cols, rows)) = super::tty::window_size(fd) {
m.insert("columns".into(), Value::Float(cols as f64));
m.insert("rows".into(), Value::Float(rows as f64));
}
}
h.new_object(m)
})
}
pub fn stream_instance_call(recv: &Value, method: &str, args: &[Value]) -> Result<Value, String> {
match method {
"write" | "end" => {
let fd = with_host(|h| match h.get(recv) {
Some(JsObj::Object(p)) => p.get("fd").map(|v| h.to_number(v)).unwrap_or(1.0),
_ => 1.0,
});
if method == "end" && args.first().map(|v| matches!(v, Value::Undef)) != Some(false) {
return Ok(Value::Bool(true));
}
let bytes = chunk_bytes(args)?;
with_host(|h| h.write_out_bytes(&bytes, fd == 2.0));
Ok(Value::Bool(true))
}
"on" | "once" | "removeListener" | "cork" | "uncork" | "setEncoding" => Ok(recv.clone()),
"cursorTo" | "moveCursor" | "clearLine" | "clearScreenDown" => {
let seq = tty_control(method, args);
write_fd(stream_fd(recv), seq.as_bytes());
Ok(Value::Bool(true))
}
"getWindowSize" => {
let (c, r) = super::tty::window_size(stream_fd(recv) as i32).unwrap_or((80, 24));
Ok(with_host(|h| {
h.new_array(vec![Value::Float(c as f64), Value::Float(r as f64)])
}))
}
"getColorDepth" => Ok(Value::Float(24.0)),
"hasColors" => Ok(Value::Bool(true)),
_ => Err(crate::host::type_error(&format!(
"{method} is not a function"
))),
}
}
fn hrtime(args: &[Value]) -> Value {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default();
let (mut secs, mut nanos) = (now.as_secs() as f64, now.subsec_nanos() as f64);
if let Some(Value::Obj(_)) = args.first() {
if let Some(prev) = with_host(|h| match h.get(&args[0]) {
Some(JsObj::Array(a)) if a.len() == 2 => Some((h.to_number(&a[0]), h.to_number(&a[1]))),
_ => None,
}) {
secs -= prev.0;
nanos -= prev.1;
}
}
with_host(|h| h.new_array(vec![Value::Float(secs), Value::Float(nanos)]))
}
#[cfg(target_os = "macos")]
fn resident_bytes() -> Option<u64> {
let mut info: libc::proc_taskinfo = unsafe { std::mem::zeroed() };
let size = std::mem::size_of::<libc::proc_taskinfo>() as libc::c_int;
let got = unsafe {
libc::proc_pidinfo(
std::process::id() as libc::c_int,
libc::PROC_PIDTASKINFO,
0,
(&mut info as *mut libc::proc_taskinfo).cast(),
size,
)
};
(got == size).then_some(info.pti_resident_size)
}
#[cfg(target_os = "linux")]
fn resident_bytes() -> Option<u64> {
let statm = std::fs::read_to_string("/proc/self/statm").ok()?;
let pages: u64 = statm.split_whitespace().nth(1)?.parse().ok()?;
let page = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
(page > 0).then(|| pages * page as u64)
}
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
fn resident_bytes() -> Option<u64> {
None
}
fn memory_usage() -> Value {
let rss = resident_bytes().unwrap_or(0) as f64;
with_host(|h| {
let mut m = IndexMap::new();
m.insert("rss".into(), Value::Float(rss));
for k in ["heapTotal", "heapUsed", "external", "arrayBuffers"] {
m.insert(k.into(), Value::Float(0.0));
}
h.new_object(m)
})
}
pub fn memory_usage_rss() -> Value {
Value::Float(resident_bytes().unwrap_or(0) as f64)
}
pub fn emit_exit_event(code: i32) -> Result<(), String> {
if with_host(|h| std::mem::replace(&mut h.exiting, true)) {
return Ok(());
}
let listeners = with_host(|h| h.take_process_listeners("exit"));
for f in listeners {
crate::host::invoke(&f, vec![Value::Float(code as f64)], None)?;
}
Ok(())
}
pub fn emit_before_exit(code: i32) -> Result<bool, String> {
let listeners = with_host(|h| h.take_process_listeners("beforeExit"));
let any = !listeners.is_empty();
for f in listeners {
crate::host::invoke(&f, vec![Value::Float(code as f64)], None)?;
}
Ok(any)
}
fn chunk_bytes(args: &[Value]) -> Result<Vec<u8>, String> {
let chunk = args.first().cloned().unwrap_or(Value::Undef);
if with_host(|h| h.is_null(&chunk)) {
return Err(crate::host::type_error(
"May not write null values to stream",
));
}
if let Some(s) = with_host(|h| h.as_str(&chunk)) {
let enc = match args.get(1) {
Some(v) if !matches!(v, Value::Undef) => with_host(|h| h.str_of(v)),
_ => "utf8".to_string(),
};
return Ok(super::buffer::decode_str(&s, &enc));
}
match super::native_tag(&chunk).as_deref() {
Some("Buffer") | Some("TypedArray") | Some("DataView") => {
Ok(super::buffer::bytes_like(&chunk).unwrap_or_default())
}
_ => Err(crate::host::type_error(&format!(
"The \"chunk\" argument must be of type string or an instance of \
Buffer, TypedArray, or DataView. Received {}",
super::received_desc(&chunk)
))),
}
}
fn stream_fd(recv: &Value) -> f64 {
with_host(|h| match h.get(recv) {
Some(JsObj::Object(p)) => p.get("fd").map(|v| h.to_number(v)).unwrap_or(1.0),
_ => 1.0,
})
}
fn write_fd(fd: f64, bytes: &[u8]) {
let text = String::from_utf8_lossy(bytes).into_owned();
with_host(|h| h.write_out(&text, fd == 2.0));
}
fn tty_control(method: &str, args: &[Value]) -> String {
match method {
"cursorTo" => {
let x = super::arg_num(args, 0);
let y = super::arg_num(args, 1);
let x = if x.is_finite() { x as i64 } else { 0 };
if y.is_finite() {
format!("\x1b[{};{}H", y as i64 + 1, x + 1)
} else {
format!("\x1b[{}G", x + 1)
}
}
"moveCursor" => {
let dx = super::arg_num(args, 0);
let dy = super::arg_num(args, 1);
let mut s = String::new();
let dx = if dx.is_finite() { dx as i64 } else { 0 };
let dy = if dy.is_finite() { dy as i64 } else { 0 };
if dx > 0 {
s.push_str(&format!("\x1b[{dx}C"));
} else if dx < 0 {
s.push_str(&format!("\x1b[{}D", -dx));
}
if dy > 0 {
s.push_str(&format!("\x1b[{dy}B"));
} else if dy < 0 {
s.push_str(&format!("\x1b[{}A", -dy));
}
s
}
"clearLine" => match super::arg_num(args, 0) {
d if d < 0.0 => "\x1b[1K".into(),
d if d > 0.0 => "\x1b[0K".into(),
_ => "\x1b[2K".into(),
},
_ => "\x1b[0J".into(),
}
}
fn supplementary_groups() -> Vec<f64> {
unsafe {
let n = libc::getgroups(0, std::ptr::null_mut());
if n <= 0 {
return Vec::new();
}
let mut buf = vec![0 as libc::gid_t; n as usize];
let filled = libc::getgroups(n, buf.as_mut_ptr());
if filled < 0 {
return Vec::new();
}
buf.truncate(filled as usize);
buf.into_iter().map(|g| g as f64).collect()
}
}
fn gid_array(v: Option<&Value>) -> Vec<libc::gid_t> {
let Some(v) = v else { return Vec::new() };
with_host(|h| match h.get(v) {
Some(JsObj::Array(a)) => a.iter().map(|x| h.to_number(x) as libc::gid_t).collect(),
_ => Vec::new(),
})
}
fn get_rusage() -> Option<libc::rusage> {
unsafe {
let mut ru: libc::rusage = std::mem::zeroed();
(libc::getrusage(libc::RUSAGE_SELF, &mut ru) == 0).then_some(ru)
}
}
fn tv_micros(t: &libc::timeval) -> f64 {
t.tv_sec as f64 * 1e6 + t.tv_usec as f64
}
fn resource_usage() -> Value {
let ru = get_rusage();
with_host(|h| {
let mut m = IndexMap::new();
let (utime, stime) = ru
.as_ref()
.map(|r| (tv_micros(&r.ru_utime), tv_micros(&r.ru_stime)))
.unwrap_or((0.0, 0.0));
m.insert("userCPUTime".into(), Value::Float(utime));
m.insert("systemCPUTime".into(), Value::Float(stime));
let fields = [
("maxRSS", ru.as_ref().map(|r| r.ru_maxrss)),
("sharedMemorySize", ru.as_ref().map(|r| r.ru_ixrss)),
("unsharedDataSize", ru.as_ref().map(|r| r.ru_idrss)),
("unsharedStackSize", ru.as_ref().map(|r| r.ru_isrss)),
("minorPageFault", ru.as_ref().map(|r| r.ru_minflt)),
("majorPageFault", ru.as_ref().map(|r| r.ru_majflt)),
("swappedOut", ru.as_ref().map(|r| r.ru_nswap)),
("fsRead", ru.as_ref().map(|r| r.ru_inblock)),
("fsWrite", ru.as_ref().map(|r| r.ru_oublock)),
("ipcSent", ru.as_ref().map(|r| r.ru_msgsnd)),
("ipcReceived", ru.as_ref().map(|r| r.ru_msgrcv)),
("signalsCount", ru.as_ref().map(|r| r.ru_nsignals)),
("voluntaryContextSwitches", ru.as_ref().map(|r| r.ru_nvcsw)),
(
"involuntaryContextSwitches",
ru.as_ref().map(|r| r.ru_nivcsw),
),
];
for (k, v) in fields {
m.insert(k.into(), Value::Float(v.unwrap_or(0) as f64));
}
h.new_object(m)
})
}
fn thread_cpu_usage() -> Value {
let (u, s) = get_rusage()
.map(|r| (tv_micros(&r.ru_utime), tv_micros(&r.ru_stime)))
.unwrap_or((0.0, 0.0));
with_host(|h| {
let mut m = IndexMap::new();
m.insert("user".into(), Value::Float(u));
m.insert("system".into(), Value::Float(s));
h.new_object(m)
})
}
fn exec_ve(args: &[Value]) -> Result<Value, String> {
use std::ffi::CString;
let prog = CString::new(super::arg_str(args, 0))
.map_err(|_| crate::host::type_error("process.execve: invalid file path"))?;
let argv_strs: Vec<String> = with_host(|h| match args.get(1).and_then(|v| h.get(v)) {
Some(JsObj::Array(a)) => a.iter().map(|x| h.str_of(x)).collect(),
_ => Vec::new(),
});
let env_strs: Vec<String> = {
let from_arg = with_host(|h| match args.get(2).and_then(|v| h.get(v)) {
Some(JsObj::Object(p)) => Some(
p.iter()
.map(|(k, v)| format!("{k}={}", h.str_of(v)))
.collect::<Vec<_>>(),
),
_ => None,
});
from_arg.unwrap_or_else(|| std::env::vars().map(|(k, v)| format!("{k}={v}")).collect())
};
let to_c = |s: String| {
CString::new(s).map_err(|_| crate::host::type_error("process.execve: NUL in argument"))
};
let argv_c: Vec<CString> = argv_strs.into_iter().map(to_c).collect::<Result<_, _>>()?;
let env_c: Vec<CString> = env_strs.into_iter().map(to_c).collect::<Result<_, _>>()?;
let mut argv_p: Vec<*const libc::c_char> = argv_c.iter().map(|c| c.as_ptr()).collect();
argv_p.push(std::ptr::null());
let mut envp_p: Vec<*const libc::c_char> = env_c.iter().map(|c| c.as_ptr()).collect();
envp_p.push(std::ptr::null());
unsafe {
libc::execve(prog.as_ptr(), argv_p.as_ptr(), envp_p.as_ptr());
}
Err(crate::host::type_error(&format!(
"process.execve failed: {}",
std::io::Error::last_os_error()
)))
}
fn load_env_file(path: &str) -> Result<Value, String> {
let path = if path.is_empty() { ".env" } else { path };
let text =
std::fs::read_to_string(path).map_err(|e| format!("Error: ENOENT: {e}, open '{path}'"))?;
for line in text.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
let line = line.strip_prefix("export ").unwrap_or(line);
let Some((key, val)) = line.split_once('=') else {
continue;
};
let key = key.trim();
if key.is_empty() {
continue;
}
let mut val = val.trim();
if val.len() >= 2
&& ((val.starts_with('"') && val.ends_with('"'))
|| (val.starts_with('\'') && val.ends_with('\'')))
{
val = &val[1..val.len() - 1];
}
std::env::set_var(key, val);
}
Ok(Value::Undef)
}
fn event_name(args: &[Value]) -> String {
args.first()
.map(|v| with_host(|h| h.str_of(v)))
.unwrap_or_default()
}