1use crate::host::{with_host, JsObj};
10use fusevm::Value;
11use indexmap::IndexMap;
12
13pub const METHODS: &[&str] = &[
15 "cwd",
16 "chdir",
17 "exit",
18 "hrtime",
19 "hrtime.bigint",
24 "uptime",
25 "memoryUsage",
26 "cpuUsage",
27 "umask",
28 "binding",
29 "emit",
30 "on",
31 "once",
32 "off",
33 "addListener",
34 "removeListener",
35 "removeAllListeners",
36 "listeners",
37 "emitWarning",
38 "kill",
39 "getuid",
40 "getgid",
41 "geteuid",
42 "getegid",
43 "getgroups",
44 "setuid",
45 "setgid",
46 "seteuid",
47 "setegid",
48 "setgroups",
49 "initgroups",
50 "ref",
51 "unref",
52 "abort",
53 "getActiveResourcesInfo",
54 "resourceUsage",
55 "threadCpuUsage",
56 "availableMemory",
57 "constrainedMemory",
58 "getBuiltinModule",
59 "openStdin",
60 "hasUncaughtExceptionCaptureCallback",
61 "setUncaughtExceptionCaptureCallback",
62 "addUncaughtExceptionCaptureCallback",
63 "execve",
64 "reallyExit",
65 "loadEnvFile",
66 "setSourceMapsEnabled",
67];
68
69thread_local! {
70 static UNCAUGHT_CAPTURE: std::cell::RefCell<Option<Value>> =
74 const { std::cell::RefCell::new(None) };
75}
76
77static TRACE_HINT_SHOWN: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
80
81pub fn emit_warning(name: &str, code: Option<&str>, message: &str, detail: Option<&str>) {
86 let argv: Vec<String> = std::env::args().collect();
87 let flag = |f: &str| argv.iter().any(|a| a == f);
88 let is_deprecation = name == "DeprecationWarning";
89 if flag("--no-warnings") || (is_deprecation && flag("--no-deprecation")) {
90 return;
91 }
92 let trace = flag("--trace-warnings") || (is_deprecation && flag("--trace-deprecation"));
93
94 let mut msg = std::format!("(node:{}) ", std::process::id());
95 if let Some(c) = code {
96 msg.push_str(&std::format!("[{c}] "));
97 }
98 msg.push_str(&std::format!("{name}: {message}"));
99 if let Some(d) = detail {
100 msg.push_str(&std::format!("\n{d}"));
101 }
102 if !trace && !TRACE_HINT_SHOWN.swap(true, std::sync::atomic::Ordering::Relaxed) {
103 let trace_flag = if is_deprecation {
104 "--trace-deprecation"
105 } else {
106 "--trace-warnings"
107 };
108 msg.push_str(&std::format!(
109 "\n(Use `node {trace_flag} ...` to show where the warning was created)"
110 ));
111 }
112 eprintln!("{msg}");
113}
114
115pub fn emit_deprecation_warning(code: &str, message: &str) {
118 use std::cell::RefCell;
119 thread_local! {
120 static SEEN: RefCell<std::collections::HashSet<String>> =
121 RefCell::new(std::collections::HashSet::new());
122 }
123 let first = SEEN.with(|s| s.borrow_mut().insert(code.to_string()));
124 if first {
125 emit_warning("DeprecationWarning", Some(code), message, None);
126 }
127}
128
129pub fn signal_number(name: &str) -> Option<libc::c_int> {
137 Some(match name.to_uppercase().as_str() {
138 "SIGHUP" => libc::SIGHUP,
139 "SIGINT" => libc::SIGINT,
140 "SIGQUIT" => libc::SIGQUIT,
141 "SIGILL" => libc::SIGILL,
142 "SIGTRAP" => libc::SIGTRAP,
143 "SIGABRT" => libc::SIGABRT,
144 "SIGBUS" => libc::SIGBUS,
145 "SIGFPE" => libc::SIGFPE,
146 "SIGKILL" => libc::SIGKILL,
147 "SIGUSR1" => libc::SIGUSR1,
148 "SIGSEGV" => libc::SIGSEGV,
149 "SIGUSR2" => libc::SIGUSR2,
150 "SIGPIPE" => libc::SIGPIPE,
151 "SIGALRM" => libc::SIGALRM,
152 "SIGTERM" => libc::SIGTERM,
153 "SIGCHLD" => libc::SIGCHLD,
154 "SIGCONT" => libc::SIGCONT,
155 "SIGSTOP" => libc::SIGSTOP,
156 "SIGTSTP" => libc::SIGTSTP,
157 "SIGWINCH" => libc::SIGWINCH,
158 _ => return None,
159 })
160}
161
162fn emit_warning_args(args: &[Value]) {
164 let message = super::arg_str(args, 0);
165 let mut name = "Warning".to_string();
166 let mut code: Option<String> = None;
167 let mut detail: Option<String> = None;
168 match args.get(1) {
169 Some(v) if with_host(|h| matches!(h.get(v), Some(JsObj::Object(_)))) => {
170 let field = |k: &str| {
171 with_host(|h| match h.get(v) {
172 Some(JsObj::Object(p)) => {
173 p.get(k).filter(|x| !h.is_nullish(x)).map(|x| h.str_of(x))
174 }
175 _ => None,
176 })
177 };
178 if let Some(t) = field("type") {
179 name = t;
180 }
181 code = field("code");
182 detail = field("detail");
183 }
184 Some(_) => {
185 name = super::arg_str(args, 1);
186 code = args.get(2).map(|_| super::arg_str(args, 2));
187 }
188 None => {}
189 }
190 emit_warning(&name, code.as_deref(), &message, detail.as_deref());
191}
192
193fn memo(name: &str, make: impl FnOnce() -> Value) -> Value {
206 if let Some(v) = with_host(|h| h.builtin_static("process", name)) {
207 return v;
208 }
209 let v = make();
210 with_host(|h| h.set_builtin_static("process", name, v.clone()));
211 v
212}
213
214fn features() -> Value {
216 with_host(|h| {
217 let mut m = IndexMap::new();
218 for (k, v) in [
219 ("inspector", false),
220 ("debug", false),
221 ("uv", false),
222 ("ipv6", true),
223 ("tls_alpn", false),
224 ("tls_sni", false),
225 ("tls_ocsp", false),
226 ("tls", true),
227 ("openssl_is_boringssl", false),
228 ("cached_builtins", true),
229 ("require_module", true),
230 ("quic", false),
231 ] {
232 m.insert(k.to_string(), Value::Bool(v));
233 }
234 let ts = h.new_str("none");
236 m.insert("typescript".into(), ts);
237 h.new_object(m)
238 })
239}
240
241fn config() -> Value {
243 with_host(|h| {
244 let mut vars = IndexMap::new();
245 let arch = h.new_str(super::os::arch());
246 let plat = h.new_str(super::os::platform());
247 vars.insert("host_arch".to_string(), arch.clone());
248 vars.insert("target_arch".to_string(), arch);
249 vars.insert("node_shared".to_string(), Value::Bool(false));
250 vars.insert("node_use_openssl".to_string(), Value::Bool(false));
251 vars.insert("v8_enable_i18n_support".to_string(), Value::Bool(false));
252 vars.insert("node_platform".to_string(), plat);
253 let variables = h.new_object(vars);
254 let defaults = h.new_object(IndexMap::new());
255 let mut m = IndexMap::new();
256 m.insert("target_defaults".to_string(), defaults);
257 m.insert("variables".to_string(), variables);
258 h.new_object(m)
259 })
260}
261
262fn allowed_flags() -> Value {
264 let flags = [
265 "--enable-source-maps",
266 "--max-old-space-size",
267 "--no-warnings",
268 "--preserve-symlinks",
269 "--stack-trace-limit",
270 "--throw-deprecation",
271 "--trace-warnings",
272 "--unhandled-rejections",
273 "--zero-fill-buffers",
274 ];
275 let vals: Vec<Value> = flags.iter().map(|f| with_host(|h| h.new_str(*f))).collect();
276 let set = with_host(|h| {
277 h.alloc(crate::host::JsObj::Set {
278 entries: indexmap::IndexMap::new(),
279 weak: false,
280 })
281 });
282 for v in vals {
283 let _ = crate::host::call_method(&set, "add", vec![v]);
284 }
285 set
286}
287
288pub fn constant(name: &str) -> Option<Value> {
289 Some(match name {
290 "env" => memo("env", env_object),
291 "release" => memo("release", || {
298 with_host(|h| {
299 let mut m = IndexMap::new();
300 let name = h.new_str("node");
301 m.insert("name".into(), name);
302 h.new_object(m)
303 })
304 }),
305 "argv" => memo("argv", argv),
306 "argv0" => with_host(|h| h.new_str(exec_path())),
307 "execPath" => with_host(|h| h.new_str(exec_path())),
308 "execArgv" => memo("execArgv", exec_argv),
309 "platform" => with_host(|h| h.new_str(super::os::platform())),
310 "arch" => with_host(|h| h.new_str(super::os::arch())),
311 "pid" => Value::Float(std::process::id() as f64),
312 "ppid" => Value::Float(0.0),
313 "title" => with_host(|h| h.new_str("node")),
314 "version" => with_host(|h| h.new_str("v26.5.0")),
317 "versions" => memo("versions", versions),
318 "features" => memo("features", features),
324 "config" => memo("config", config),
328 "allowedNodeEnvironmentFlags" => memo("allowedNodeEnvironmentFlags", allowed_flags),
332 "stdout" => memo("stdout", || std_stream(1)),
333 "stderr" => memo("stderr", || std_stream(2)),
334 "stdin" => memo("stdin", || std_stream(0)),
335 "exitCode" => match with_host(|h| h.exit_code) {
338 Some(c) => Value::Float(c as f64),
339 None => Value::Undef,
340 },
341 _ => return None,
342 })
343}
344
345pub fn set_exit_code(val: &Value) -> Result<(), String> {
369 if matches!(val, Value::Undef) || with_host(|h| h.is_null(val)) {
370 with_host(|h| h.exit_code = None);
371 return Ok(());
372 }
373 let numeric = match with_host(|h| h.as_str(val)) {
375 Some(s) if !s.is_empty() => {
376 let n = with_host(|h| h.to_number(val));
377 if n.is_nan() {
378 None
379 } else {
380 Some(n)
381 }
382 }
383 Some(_) => None,
384 None => match val {
385 Value::Float(_) | Value::Int(_) => Some(with_host(|h| h.to_number(val))),
386 _ => None,
387 },
388 };
389 match numeric {
390 Some(n) if n.fract() == 0.0 && n.is_finite() => {
391 with_host(|h| h.exit_code = Some(n as i32));
392 Ok(())
393 }
394 Some(n) => Err(crate::host::coded_error(
395 "RangeError",
396 "ERR_OUT_OF_RANGE",
397 &format!(
398 "The value of \"code\" is out of range. It must be an integer. Received {}",
399 crate::host::fmt_number(n)
400 ),
401 )),
402 None => Err(crate::host::invalid_arg_type(
403 "code", "argument", "number", val,
404 )),
405 }
406}
407
408pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
409 Some(match method {
410 "cwd" => {
411 let d = std::env::current_dir()
412 .map(|p| p.to_string_lossy().into_owned())
413 .unwrap_or_default();
414 Ok(with_host(|h| h.new_str(d)))
415 }
416 "hrtime" => Ok(hrtime(args)),
420 "hrtime.bigint" => Ok(with_host(|h| {
422 let now = std::time::SystemTime::now()
423 .duration_since(std::time::UNIX_EPOCH)
424 .unwrap_or_default();
425 h.new_bigint(num_bigint::BigInt::from(now.as_nanos()))
426 })),
427 "uptime" => Ok(Value::Float(0.0)),
428 "memoryUsage" => Ok(memory_usage()),
429 "cpuUsage" => Ok(with_host(|h| {
430 let mut m = IndexMap::new();
431 m.insert("user".into(), Value::Float(0.0));
432 m.insert("system".into(), Value::Float(0.0));
433 h.new_object(m)
434 })),
435 "umask" => Ok(Value::Float(0.0)),
436 "binding" => Err(crate::host::type_error("process.binding is not supported")),
437 "on" | "once" | "addListener" => {
441 let (event, f) = (event_name(args), args.get(1).cloned());
442 if let Some(f) = f {
443 let once = method == "once";
444 with_host(|h| {
445 h.process_listeners
446 .entry(event)
447 .or_default()
448 .push(crate::host::ProcListener { f, once })
449 });
450 }
451 Ok(with_host(|h| h.alloc(JsObj::Builtin("process".into()))))
452 }
453 "off" | "removeListener" => {
454 let (event, f) = (event_name(args), args.get(1).cloned());
455 if let Some(f) = f {
456 with_host(|h| {
457 if let Some(l) = h.process_listeners.get_mut(&event) {
458 if let Some(i) = l.iter().position(|x| x.f == f) {
459 l.remove(i);
460 }
461 }
462 });
463 }
464 Ok(with_host(|h| h.alloc(JsObj::Builtin("process".into()))))
465 }
466 "removeAllListeners" => {
467 let event = event_name(args);
468 with_host(|h| {
469 if event.is_empty() {
470 h.process_listeners.clear();
471 } else {
472 h.process_listeners.shift_remove(&event);
473 }
474 });
475 Ok(with_host(|h| h.alloc(JsObj::Builtin("process".into()))))
476 }
477 "listeners" => {
478 let event = event_name(args);
479 Ok(with_host(|h| {
480 let l = h
481 .process_listeners
482 .get(&event)
483 .map(|v| v.iter().map(|x| x.f.clone()).collect())
484 .unwrap_or_default();
485 h.new_array(l)
486 }))
487 }
488 "emit" => {
489 let event = event_name(args);
490 let rest: Vec<Value> = args.iter().skip(1).cloned().collect();
491 let listeners = with_host(|h| h.take_process_listeners(&event));
492 let any = !listeners.is_empty();
493 let mut r = Ok(Value::Bool(any));
494 for f in listeners {
495 if let Err(e) = crate::host::invoke(&f, rest.clone(), None) {
496 r = Err(e);
497 break;
498 }
499 }
500 r
501 }
502 "emitWarning" => {
503 emit_warning_args(args);
504 Ok(Value::Undef)
505 }
506 "exit" | "reallyExit" => {
528 if !args.is_empty() {
529 if let Err(e) = set_exit_code(&args[0]) {
530 return Some(Err(e));
531 }
532 }
533 let code = with_host(|h| h.exit_code).unwrap_or(0);
534 if let Err(e) = emit_exit_event(code) {
535 return Some(Err(e));
536 }
537 let code = with_host(|h| h.exit_code).unwrap_or(0);
539 use std::io::Write;
540 let _ = std::io::stdout().flush();
541 let _ = std::io::stderr().flush();
542 crate::cache::flush();
547 std::process::exit(code);
548 }
549 "chdir" => {
553 let dir = super::arg_str(args, 0);
554 std::env::set_current_dir(&dir)
555 .map(|()| Value::Undef)
556 .map_err(|e| {
561 let from = std::env::current_dir()
562 .map(|p| p.display().to_string())
563 .unwrap_or_default();
564 format!(
565 "Error: {}, chdir '{from}' -> '{dir}'",
566 crate::stdlib::fs::libuv_message(&e)
567 )
568 })
569 }
570 "kill" => {
574 let pid = with_host(|h| args.first().map(|v| h.to_number(v)).unwrap_or(0.0)) as i32;
575 let sig: Result<libc::c_int, String> = match args.get(1) {
576 Some(v) if !matches!(v, Value::Undef) => match with_host(|h| h.as_str(v)) {
577 Some(name) => signal_number(&name).ok_or(crate::host::coded_error(
578 "TypeError",
579 "ERR_UNKNOWN_SIGNAL",
580 &format!("Unknown signal: {name}"),
581 )),
582 None => Ok(with_host(|h| h.to_number(v)) as libc::c_int),
583 },
584 _ => Ok(libc::SIGTERM),
585 };
586 sig.and_then(|sig| {
587 if unsafe { libc::kill(pid, sig) } != 0 {
590 Err(format!("Error: {}", std::io::Error::last_os_error()))
591 } else {
592 Ok(Value::Undef)
593 }
594 })
595 }
596 "setSourceMapsEnabled" => Ok(Value::Undef),
600
601 "getuid" => Ok(Value::Float(unsafe { libc::getuid() } as f64)),
603 "geteuid" => Ok(Value::Float(unsafe { libc::geteuid() } as f64)),
604 "getgid" => Ok(Value::Float(unsafe { libc::getgid() } as f64)),
605 "getegid" => Ok(Value::Float(unsafe { libc::getegid() } as f64)),
606 "getgroups" => {
607 let groups = supplementary_groups();
608 Ok(with_host(|h| {
609 h.new_array(groups.into_iter().map(Value::Float).collect())
610 }))
611 }
612
613 "setuid" | "seteuid" | "setgid" | "setegid" => {
616 let id = super::arg_num(args, 0);
617 if id.is_finite() {
618 let id = id as u32;
619 unsafe {
621 match method {
622 "setuid" => libc::setuid(id),
623 "seteuid" => libc::seteuid(id),
624 "setgid" => libc::setgid(id),
625 _ => libc::setegid(id),
626 };
627 }
628 }
629 Ok(Value::Undef)
630 }
631 "setgroups" => {
632 let groups = gid_array(args.first());
633 unsafe {
635 libc::setgroups(groups.len() as _, groups.as_ptr());
636 }
637 Ok(Value::Undef)
638 }
639 "initgroups" => {
640 let user = super::arg_str(args, 0);
641 let extra = super::arg_num(args, 1);
642 if let Ok(c) = std::ffi::CString::new(user) {
643 let gid = if extra.is_finite() { extra as u32 } else { 0 };
644 unsafe {
646 libc::initgroups(c.as_ptr(), gid as _);
647 }
648 }
649 Ok(Value::Undef)
650 }
651
652 "ref" | "unref" => Ok(with_host(|h| h.alloc(JsObj::Builtin("process".into())))),
655 "abort" => std::process::abort(),
656 "getActiveResourcesInfo" => Ok(with_host(|h| h.new_array(Vec::new()))),
657 "resourceUsage" => Ok(resource_usage()),
658 "threadCpuUsage" => Ok(thread_cpu_usage()),
659 "availableMemory" | "constrainedMemory" => Ok(Value::Float(0.0)),
660 "getBuiltinModule" => {
661 let id = super::arg_str(args, 0);
662 let id = id.strip_prefix("node:").unwrap_or(&id);
663 match crate::stdlib::resolve(id) {
664 Some(ns) => Ok(with_host(|h| h.alloc(JsObj::Builtin(ns.to_string())))),
665 None => Ok(Value::Undef),
666 }
667 }
668 "openStdin" => Ok(std_stream(0)),
669
670 "hasUncaughtExceptionCaptureCallback" => {
671 Ok(Value::Bool(UNCAUGHT_CAPTURE.with(|c| c.borrow().is_some())))
672 }
673 "setUncaughtExceptionCaptureCallback" => {
674 let cb = args.first().cloned().unwrap_or(Value::Undef);
675 let clear = matches!(cb, Value::Undef) || with_host(|h| h.is_null(&cb));
676 if clear {
677 UNCAUGHT_CAPTURE.with(|c| *c.borrow_mut() = None);
678 } else if UNCAUGHT_CAPTURE.with(|c| c.borrow().is_some()) {
679 return Some(Err(crate::host::type_error(
680 "`process.setUncaughtExceptionCaptureCallback()` was called \
681 while a capture callback was already active",
682 )));
683 } else {
684 UNCAUGHT_CAPTURE.with(|c| *c.borrow_mut() = Some(cb));
685 }
686 Ok(Value::Undef)
687 }
688 "addUncaughtExceptionCaptureCallback" => {
689 let cb = args.first().cloned().unwrap_or(Value::Undef);
690 if !matches!(cb, Value::Undef) {
691 UNCAUGHT_CAPTURE.with(|c| *c.borrow_mut() = Some(cb));
692 }
693 Ok(Value::Undef)
694 }
695 "execve" => exec_ve(args),
696 "loadEnvFile" => load_env_file(&super::arg_str(args, 0)),
697 _ => return None,
698 })
699}
700
701fn env_object() -> Value {
703 with_host(|h| {
704 let mut m = IndexMap::new();
705 m.insert("@@envObject".into(), Value::Bool(true));
710 for (k, v) in std::env::vars() {
711 m.insert(k, h.new_str(v));
712 }
713 h.new_object(m)
714 })
715}
716
717static ARGV: std::sync::OnceLock<(Vec<String>, Vec<String>)> = std::sync::OnceLock::new();
723
724pub fn install_argv() {
732 let split = crate::cli::split_argv(std::env::args());
733 let mut argv = vec![exec_path()];
734 if let Some(s) = &split.script {
735 argv.push(if s == "-" {
737 s.clone()
738 } else {
739 super::path::resolve_one(s)
740 });
741 }
742 argv.extend(split.user);
743 let _ = ARGV.set((split.exec, argv));
744}
745
746fn argv() -> Value {
753 with_host(|h| {
754 let items: Vec<Value> = match ARGV.get() {
755 Some((_, argv)) => argv.iter().map(|a| h.new_str(a.clone())).collect(),
756 None => std::env::args().map(|a| h.new_str(a)).collect(),
757 };
758 h.new_array(items)
759 })
760}
761
762fn exec_argv() -> Value {
764 with_host(|h| {
765 let items: Vec<Value> = ARGV
766 .get()
767 .map(|(e, _)| e.iter().map(|a| h.new_str(a.clone())).collect())
768 .unwrap_or_default();
769 h.new_array(items)
770 })
771}
772
773fn exec_path() -> String {
774 std::env::current_exe()
775 .map(|p| p.to_string_lossy().into_owned())
776 .unwrap_or_else(|_| "node".into())
777}
778
779fn versions() -> Value {
781 with_host(|h| {
782 let mut m = IndexMap::new();
783 m.insert("node".into(), h.new_str("26.5.0"));
784 m.insert("v8".into(), h.new_str("0.0.0"));
785 h.new_object(m)
786 })
787}
788
789fn std_stream(fd: i32) -> Value {
793 with_host(|h| {
794 let mut m = IndexMap::new();
795 m.insert("@@native".into(), h.new_str("WriteStream"));
796 m.insert("fd".into(), Value::Float(fd as f64));
797 let is_tty = unsafe { libc::isatty(fd) == 1 };
799 if is_tty {
805 m.insert("isTTY".into(), Value::Bool(true));
806 }
807 m.insert("writable".into(), Value::Bool(fd != 0));
808 m.insert("readable".into(), Value::Bool(fd == 0));
809 if is_tty {
811 if let Some((cols, rows)) = super::tty::window_size(fd) {
812 m.insert("columns".into(), Value::Float(cols as f64));
813 m.insert("rows".into(), Value::Float(rows as f64));
814 }
815 }
816 h.new_object(m)
817 })
818}
819
820pub fn stream_instance_call(recv: &Value, method: &str, args: &[Value]) -> Result<Value, String> {
824 match method {
825 "write" | "end" => {
826 let fd = with_host(|h| match h.get(recv) {
827 Some(JsObj::Object(p)) => p.get("fd").map(|v| h.to_number(v)).unwrap_or(1.0),
828 _ => 1.0,
829 });
830 if method == "end" && args.first().map(|v| matches!(v, Value::Undef)) != Some(false) {
833 return Ok(Value::Bool(true));
834 }
835 let bytes = chunk_bytes(args)?;
836 with_host(|h| h.write_out_bytes(&bytes, fd == 2.0));
837 Ok(Value::Bool(true))
838 }
839 "on" | "once" | "removeListener" | "cork" | "uncork" | "setEncoding" => Ok(recv.clone()),
841 "cursorTo" | "moveCursor" | "clearLine" | "clearScreenDown" => {
844 let seq = tty_control(method, args);
845 write_fd(stream_fd(recv), seq.as_bytes());
846 Ok(Value::Bool(true))
847 }
848 "getWindowSize" => {
849 let (c, r) = super::tty::window_size(stream_fd(recv) as i32).unwrap_or((80, 24));
850 Ok(with_host(|h| {
851 h.new_array(vec![Value::Float(c as f64), Value::Float(r as f64)])
852 }))
853 }
854 "getColorDepth" => Ok(Value::Float(24.0)),
857 "hasColors" => Ok(Value::Bool(true)),
858 _ => Err(crate::host::type_error(&format!(
859 "{method} is not a function"
860 ))),
861 }
862}
863
864fn hrtime(args: &[Value]) -> Value {
865 let now = std::time::SystemTime::now()
866 .duration_since(std::time::UNIX_EPOCH)
867 .unwrap_or_default();
868 let (mut secs, mut nanos) = (now.as_secs() as f64, now.subsec_nanos() as f64);
869 if let Some(Value::Obj(_)) = args.first() {
871 if let Some(prev) = with_host(|h| match h.get(&args[0]) {
872 Some(JsObj::Array(a)) if a.len() == 2 => Some((h.to_number(&a[0]), h.to_number(&a[1]))),
873 _ => None,
874 }) {
875 secs -= prev.0;
876 nanos -= prev.1;
877 }
878 }
879 with_host(|h| h.new_array(vec![Value::Float(secs), Value::Float(nanos)]))
880}
881
882#[cfg(target_os = "macos")]
890fn resident_bytes() -> Option<u64> {
891 let mut info: libc::proc_taskinfo = unsafe { std::mem::zeroed() };
892 let size = std::mem::size_of::<libc::proc_taskinfo>() as libc::c_int;
893 let got = unsafe {
894 libc::proc_pidinfo(
895 std::process::id() as libc::c_int,
896 libc::PROC_PIDTASKINFO,
897 0,
898 (&mut info as *mut libc::proc_taskinfo).cast(),
899 size,
900 )
901 };
902 (got == size).then_some(info.pti_resident_size)
903}
904
905#[cfg(target_os = "linux")]
906fn resident_bytes() -> Option<u64> {
907 let statm = std::fs::read_to_string("/proc/self/statm").ok()?;
909 let pages: u64 = statm.split_whitespace().nth(1)?.parse().ok()?;
910 let page = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
911 (page > 0).then(|| pages * page as u64)
912}
913
914#[cfg(not(any(target_os = "macos", target_os = "linux")))]
915fn resident_bytes() -> Option<u64> {
916 None
917}
918
919fn memory_usage() -> Value {
920 let rss = resident_bytes().unwrap_or(0) as f64;
921 with_host(|h| {
922 let mut m = IndexMap::new();
923 m.insert("rss".into(), Value::Float(rss));
924 for k in ["heapTotal", "heapUsed", "external", "arrayBuffers"] {
927 m.insert(k.into(), Value::Float(0.0));
928 }
929 h.new_object(m)
930 })
931}
932
933pub fn memory_usage_rss() -> Value {
935 Value::Float(resident_bytes().unwrap_or(0) as f64)
936}
937
938pub fn emit_exit_event(code: i32) -> Result<(), String> {
948 if with_host(|h| std::mem::replace(&mut h.exiting, true)) {
949 return Ok(());
950 }
951 let listeners = with_host(|h| h.take_process_listeners("exit"));
952 for f in listeners {
953 crate::host::invoke(&f, vec![Value::Float(code as f64)], None)?;
954 }
955 Ok(())
956}
957
958pub fn emit_before_exit(code: i32) -> Result<bool, String> {
966 let listeners = with_host(|h| h.take_process_listeners("beforeExit"));
967 let any = !listeners.is_empty();
968 for f in listeners {
969 crate::host::invoke(&f, vec![Value::Float(code as f64)], None)?;
970 }
971 Ok(any)
972}
973
974fn chunk_bytes(args: &[Value]) -> Result<Vec<u8>, String> {
990 let chunk = args.first().cloned().unwrap_or(Value::Undef);
991 if with_host(|h| h.is_null(&chunk)) {
992 return Err(crate::host::type_error(
993 "May not write null values to stream",
994 ));
995 }
996 if let Some(s) = with_host(|h| h.as_str(&chunk)) {
997 let enc = match args.get(1) {
998 Some(v) if !matches!(v, Value::Undef) => with_host(|h| h.str_of(v)),
999 _ => "utf8".to_string(),
1000 };
1001 return Ok(super::buffer::decode_str(&s, &enc));
1002 }
1003 match super::native_tag(&chunk).as_deref() {
1004 Some("Buffer") | Some("TypedArray") | Some("DataView") => {
1005 Ok(super::buffer::bytes_like(&chunk).unwrap_or_default())
1006 }
1007 _ => Err(crate::host::type_error(&format!(
1008 "The \"chunk\" argument must be of type string or an instance of \
1009 Buffer, TypedArray, or DataView. Received {}",
1010 super::received_desc(&chunk)
1011 ))),
1012 }
1013}
1014
1015fn stream_fd(recv: &Value) -> f64 {
1017 with_host(|h| match h.get(recv) {
1018 Some(JsObj::Object(p)) => p.get("fd").map(|v| h.to_number(v)).unwrap_or(1.0),
1019 _ => 1.0,
1020 })
1021}
1022
1023fn write_fd(fd: f64, bytes: &[u8]) {
1026 let text = String::from_utf8_lossy(bytes).into_owned();
1027 with_host(|h| h.write_out(&text, fd == 2.0));
1028}
1029
1030fn tty_control(method: &str, args: &[Value]) -> String {
1032 match method {
1033 "cursorTo" => {
1035 let x = super::arg_num(args, 0);
1036 let y = super::arg_num(args, 1);
1037 let x = if x.is_finite() { x as i64 } else { 0 };
1038 if y.is_finite() {
1039 format!("\x1b[{};{}H", y as i64 + 1, x + 1)
1040 } else {
1041 format!("\x1b[{}G", x + 1)
1042 }
1043 }
1044 "moveCursor" => {
1046 let dx = super::arg_num(args, 0);
1047 let dy = super::arg_num(args, 1);
1048 let mut s = String::new();
1049 let dx = if dx.is_finite() { dx as i64 } else { 0 };
1050 let dy = if dy.is_finite() { dy as i64 } else { 0 };
1051 if dx > 0 {
1052 s.push_str(&format!("\x1b[{dx}C"));
1053 } else if dx < 0 {
1054 s.push_str(&format!("\x1b[{}D", -dx));
1055 }
1056 if dy > 0 {
1057 s.push_str(&format!("\x1b[{dy}B"));
1058 } else if dy < 0 {
1059 s.push_str(&format!("\x1b[{}A", -dy));
1060 }
1061 s
1062 }
1063 "clearLine" => match super::arg_num(args, 0) {
1065 d if d < 0.0 => "\x1b[1K".into(),
1066 d if d > 0.0 => "\x1b[0K".into(),
1067 _ => "\x1b[2K".into(),
1068 },
1069 _ => "\x1b[0J".into(),
1071 }
1072}
1073
1074fn supplementary_groups() -> Vec<f64> {
1076 unsafe {
1078 let n = libc::getgroups(0, std::ptr::null_mut());
1079 if n <= 0 {
1080 return Vec::new();
1081 }
1082 let mut buf = vec![0 as libc::gid_t; n as usize];
1083 let filled = libc::getgroups(n, buf.as_mut_ptr());
1084 if filled < 0 {
1085 return Vec::new();
1086 }
1087 buf.truncate(filled as usize);
1088 buf.into_iter().map(|g| g as f64).collect()
1089 }
1090}
1091
1092fn gid_array(v: Option<&Value>) -> Vec<libc::gid_t> {
1094 let Some(v) = v else { return Vec::new() };
1095 with_host(|h| match h.get(v) {
1096 Some(JsObj::Array(a)) => a.iter().map(|x| h.to_number(x) as libc::gid_t).collect(),
1097 _ => Vec::new(),
1098 })
1099}
1100
1101fn get_rusage() -> Option<libc::rusage> {
1103 unsafe {
1105 let mut ru: libc::rusage = std::mem::zeroed();
1106 (libc::getrusage(libc::RUSAGE_SELF, &mut ru) == 0).then_some(ru)
1107 }
1108}
1109
1110fn tv_micros(t: &libc::timeval) -> f64 {
1112 t.tv_sec as f64 * 1e6 + t.tv_usec as f64
1113}
1114
1115fn resource_usage() -> Value {
1117 let ru = get_rusage();
1118 with_host(|h| {
1119 let mut m = IndexMap::new();
1120 let (utime, stime) = ru
1121 .as_ref()
1122 .map(|r| (tv_micros(&r.ru_utime), tv_micros(&r.ru_stime)))
1123 .unwrap_or((0.0, 0.0));
1124 m.insert("userCPUTime".into(), Value::Float(utime));
1125 m.insert("systemCPUTime".into(), Value::Float(stime));
1126 let fields = [
1127 ("maxRSS", ru.as_ref().map(|r| r.ru_maxrss)),
1128 ("sharedMemorySize", ru.as_ref().map(|r| r.ru_ixrss)),
1129 ("unsharedDataSize", ru.as_ref().map(|r| r.ru_idrss)),
1130 ("unsharedStackSize", ru.as_ref().map(|r| r.ru_isrss)),
1131 ("minorPageFault", ru.as_ref().map(|r| r.ru_minflt)),
1132 ("majorPageFault", ru.as_ref().map(|r| r.ru_majflt)),
1133 ("swappedOut", ru.as_ref().map(|r| r.ru_nswap)),
1134 ("fsRead", ru.as_ref().map(|r| r.ru_inblock)),
1135 ("fsWrite", ru.as_ref().map(|r| r.ru_oublock)),
1136 ("ipcSent", ru.as_ref().map(|r| r.ru_msgsnd)),
1137 ("ipcReceived", ru.as_ref().map(|r| r.ru_msgrcv)),
1138 ("signalsCount", ru.as_ref().map(|r| r.ru_nsignals)),
1139 ("voluntaryContextSwitches", ru.as_ref().map(|r| r.ru_nvcsw)),
1140 (
1141 "involuntaryContextSwitches",
1142 ru.as_ref().map(|r| r.ru_nivcsw),
1143 ),
1144 ];
1145 for (k, v) in fields {
1146 m.insert(k.into(), Value::Float(v.unwrap_or(0) as f64));
1147 }
1148 h.new_object(m)
1149 })
1150}
1151
1152fn thread_cpu_usage() -> Value {
1155 let (u, s) = get_rusage()
1156 .map(|r| (tv_micros(&r.ru_utime), tv_micros(&r.ru_stime)))
1157 .unwrap_or((0.0, 0.0));
1158 with_host(|h| {
1159 let mut m = IndexMap::new();
1160 m.insert("user".into(), Value::Float(u));
1161 m.insert("system".into(), Value::Float(s));
1162 h.new_object(m)
1163 })
1164}
1165
1166fn exec_ve(args: &[Value]) -> Result<Value, String> {
1169 use std::ffi::CString;
1170 let prog = CString::new(super::arg_str(args, 0))
1171 .map_err(|_| crate::host::type_error("process.execve: invalid file path"))?;
1172
1173 let argv_strs: Vec<String> = with_host(|h| match args.get(1).and_then(|v| h.get(v)) {
1174 Some(JsObj::Array(a)) => a.iter().map(|x| h.str_of(x)).collect(),
1175 _ => Vec::new(),
1176 });
1177 let env_strs: Vec<String> = {
1178 let from_arg = with_host(|h| match args.get(2).and_then(|v| h.get(v)) {
1179 Some(JsObj::Object(p)) => Some(
1180 p.iter()
1181 .map(|(k, v)| format!("{k}={}", h.str_of(v)))
1182 .collect::<Vec<_>>(),
1183 ),
1184 _ => None,
1185 });
1186 from_arg.unwrap_or_else(|| std::env::vars().map(|(k, v)| format!("{k}={v}")).collect())
1187 };
1188
1189 let to_c = |s: String| {
1190 CString::new(s).map_err(|_| crate::host::type_error("process.execve: NUL in argument"))
1191 };
1192 let argv_c: Vec<CString> = argv_strs.into_iter().map(to_c).collect::<Result<_, _>>()?;
1193 let env_c: Vec<CString> = env_strs.into_iter().map(to_c).collect::<Result<_, _>>()?;
1194
1195 let mut argv_p: Vec<*const libc::c_char> = argv_c.iter().map(|c| c.as_ptr()).collect();
1196 argv_p.push(std::ptr::null());
1197 let mut envp_p: Vec<*const libc::c_char> = env_c.iter().map(|c| c.as_ptr()).collect();
1198 envp_p.push(std::ptr::null());
1199
1200 unsafe {
1203 libc::execve(prog.as_ptr(), argv_p.as_ptr(), envp_p.as_ptr());
1204 }
1205 Err(crate::host::type_error(&format!(
1206 "process.execve failed: {}",
1207 std::io::Error::last_os_error()
1208 )))
1209}
1210
1211fn load_env_file(path: &str) -> Result<Value, String> {
1214 let path = if path.is_empty() { ".env" } else { path };
1215 let text =
1216 std::fs::read_to_string(path).map_err(|e| format!("Error: ENOENT: {e}, open '{path}'"))?;
1217 for line in text.lines() {
1218 let line = line.trim();
1219 if line.is_empty() || line.starts_with('#') {
1220 continue;
1221 }
1222 let line = line.strip_prefix("export ").unwrap_or(line);
1223 let Some((key, val)) = line.split_once('=') else {
1224 continue;
1225 };
1226 let key = key.trim();
1227 if key.is_empty() {
1228 continue;
1229 }
1230 let mut val = val.trim();
1231 if val.len() >= 2
1232 && ((val.starts_with('"') && val.ends_with('"'))
1233 || (val.starts_with('\'') && val.ends_with('\'')))
1234 {
1235 val = &val[1..val.len() - 1];
1236 }
1237 std::env::set_var(key, val);
1238 }
1239 Ok(Value::Undef)
1240}
1241
1242fn event_name(args: &[Value]) -> String {
1244 args.first()
1245 .map(|v| with_host(|h| h.str_of(v)))
1246 .unwrap_or_default()
1247}