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
214pub fn constant(name: &str) -> Option<Value> {
215 Some(match name {
216 "env" => memo("env", env_object),
217 "argv" => memo("argv", argv),
218 "argv0" => with_host(|h| h.new_str(exec_path())),
219 "execPath" => with_host(|h| h.new_str(exec_path())),
220 "execArgv" => memo("execArgv", exec_argv),
221 "platform" => with_host(|h| h.new_str(super::os::platform())),
222 "arch" => with_host(|h| h.new_str(super::os::arch())),
223 "pid" => Value::Float(std::process::id() as f64),
224 "ppid" => Value::Float(0.0),
225 "title" => with_host(|h| h.new_str("node")),
226 "version" => with_host(|h| h.new_str("v26.5.0")),
229 "versions" => memo("versions", versions),
230 "stdout" => memo("stdout", || std_stream(1)),
231 "stderr" => memo("stderr", || std_stream(2)),
232 "stdin" => memo("stdin", || std_stream(0)),
233 "exitCode" => match with_host(|h| h.exit_code) {
236 Some(c) => Value::Float(c as f64),
237 None => Value::Undef,
238 },
239 _ => return None,
240 })
241}
242
243pub fn set_exit_code(val: &Value) -> Result<(), String> {
267 if matches!(val, Value::Undef) || with_host(|h| h.is_null(val)) {
268 with_host(|h| h.exit_code = None);
269 return Ok(());
270 }
271 let numeric = match with_host(|h| h.as_str(val)) {
273 Some(s) if !s.is_empty() => {
274 let n = with_host(|h| h.to_number(val));
275 if n.is_nan() {
276 None
277 } else {
278 Some(n)
279 }
280 }
281 Some(_) => None,
282 None => match val {
283 Value::Float(_) | Value::Int(_) => Some(with_host(|h| h.to_number(val))),
284 _ => None,
285 },
286 };
287 match numeric {
288 Some(n) if n.fract() == 0.0 && n.is_finite() => {
289 with_host(|h| h.exit_code = Some(n as i32));
290 Ok(())
291 }
292 Some(n) => Err(crate::host::coded_error(
293 "RangeError",
294 "ERR_OUT_OF_RANGE",
295 &format!(
296 "The value of \"code\" is out of range. It must be an integer. Received {}",
297 crate::host::fmt_number(n)
298 ),
299 )),
300 None => Err(crate::host::invalid_arg_type(
301 "code", "argument", "number", val,
302 )),
303 }
304}
305
306pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
307 Some(match method {
308 "cwd" => {
309 let d = std::env::current_dir()
310 .map(|p| p.to_string_lossy().into_owned())
311 .unwrap_or_default();
312 Ok(with_host(|h| h.new_str(d)))
313 }
314 "hrtime" => Ok(hrtime(args)),
318 "hrtime.bigint" => Ok(with_host(|h| {
320 let now = std::time::SystemTime::now()
321 .duration_since(std::time::UNIX_EPOCH)
322 .unwrap_or_default();
323 h.new_bigint(num_bigint::BigInt::from(now.as_nanos()))
324 })),
325 "uptime" => Ok(Value::Float(0.0)),
326 "memoryUsage" => Ok(memory_usage()),
327 "cpuUsage" => Ok(with_host(|h| {
328 let mut m = IndexMap::new();
329 m.insert("user".into(), Value::Float(0.0));
330 m.insert("system".into(), Value::Float(0.0));
331 h.new_object(m)
332 })),
333 "umask" => Ok(Value::Float(0.0)),
334 "binding" => Err(crate::host::type_error("process.binding is not supported")),
335 "on" | "once" | "addListener" => {
339 let (event, f) = (event_name(args), args.get(1).cloned());
340 if let Some(f) = f {
341 let once = method == "once";
342 with_host(|h| {
343 h.process_listeners
344 .entry(event)
345 .or_default()
346 .push(crate::host::ProcListener { f, once })
347 });
348 }
349 Ok(with_host(|h| h.alloc(JsObj::Builtin("process".into()))))
350 }
351 "off" | "removeListener" => {
352 let (event, f) = (event_name(args), args.get(1).cloned());
353 if let Some(f) = f {
354 with_host(|h| {
355 if let Some(l) = h.process_listeners.get_mut(&event) {
356 if let Some(i) = l.iter().position(|x| x.f == f) {
357 l.remove(i);
358 }
359 }
360 });
361 }
362 Ok(with_host(|h| h.alloc(JsObj::Builtin("process".into()))))
363 }
364 "removeAllListeners" => {
365 let event = event_name(args);
366 with_host(|h| {
367 if event.is_empty() {
368 h.process_listeners.clear();
369 } else {
370 h.process_listeners.shift_remove(&event);
371 }
372 });
373 Ok(with_host(|h| h.alloc(JsObj::Builtin("process".into()))))
374 }
375 "listeners" => {
376 let event = event_name(args);
377 Ok(with_host(|h| {
378 let l = h
379 .process_listeners
380 .get(&event)
381 .map(|v| v.iter().map(|x| x.f.clone()).collect())
382 .unwrap_or_default();
383 h.new_array(l)
384 }))
385 }
386 "emit" => {
387 let event = event_name(args);
388 let rest: Vec<Value> = args.iter().skip(1).cloned().collect();
389 let listeners = with_host(|h| h.take_process_listeners(&event));
390 let any = !listeners.is_empty();
391 let mut r = Ok(Value::Bool(any));
392 for f in listeners {
393 if let Err(e) = crate::host::invoke(&f, rest.clone(), None) {
394 r = Err(e);
395 break;
396 }
397 }
398 r
399 }
400 "emitWarning" => {
401 emit_warning_args(args);
402 Ok(Value::Undef)
403 }
404 "exit" | "reallyExit" => {
426 if !args.is_empty() {
427 if let Err(e) = set_exit_code(&args[0]) {
428 return Some(Err(e));
429 }
430 }
431 let code = with_host(|h| h.exit_code).unwrap_or(0);
432 if let Err(e) = emit_exit_event(code) {
433 return Some(Err(e));
434 }
435 let code = with_host(|h| h.exit_code).unwrap_or(0);
437 use std::io::Write;
438 let _ = std::io::stdout().flush();
439 let _ = std::io::stderr().flush();
440 crate::cache::flush();
445 std::process::exit(code);
446 }
447 "chdir" => {
451 let dir = super::arg_str(args, 0);
452 std::env::set_current_dir(&dir)
453 .map(|()| Value::Undef)
454 .map_err(|e| {
459 let from = std::env::current_dir()
460 .map(|p| p.display().to_string())
461 .unwrap_or_default();
462 format!(
463 "Error: {}, chdir '{from}' -> '{dir}'",
464 crate::stdlib::fs::libuv_message(&e)
465 )
466 })
467 }
468 "kill" => {
472 let pid = with_host(|h| args.first().map(|v| h.to_number(v)).unwrap_or(0.0)) as i32;
473 let sig: Result<libc::c_int, String> = match args.get(1) {
474 Some(v) if !matches!(v, Value::Undef) => match with_host(|h| h.as_str(v)) {
475 Some(name) => signal_number(&name).ok_or(crate::host::coded_error(
476 "TypeError",
477 "ERR_UNKNOWN_SIGNAL",
478 &format!("Unknown signal: {name}"),
479 )),
480 None => Ok(with_host(|h| h.to_number(v)) as libc::c_int),
481 },
482 _ => Ok(libc::SIGTERM),
483 };
484 sig.and_then(|sig| {
485 if unsafe { libc::kill(pid, sig) } != 0 {
488 Err(format!("Error: {}", std::io::Error::last_os_error()))
489 } else {
490 Ok(Value::Undef)
491 }
492 })
493 }
494 "setSourceMapsEnabled" => Ok(Value::Undef),
498
499 "getuid" => Ok(Value::Float(unsafe { libc::getuid() } as f64)),
501 "geteuid" => Ok(Value::Float(unsafe { libc::geteuid() } as f64)),
502 "getgid" => Ok(Value::Float(unsafe { libc::getgid() } as f64)),
503 "getegid" => Ok(Value::Float(unsafe { libc::getegid() } as f64)),
504 "getgroups" => {
505 let groups = supplementary_groups();
506 Ok(with_host(|h| {
507 h.new_array(groups.into_iter().map(Value::Float).collect())
508 }))
509 }
510
511 "setuid" | "seteuid" | "setgid" | "setegid" => {
514 let id = super::arg_num(args, 0);
515 if id.is_finite() {
516 let id = id as u32;
517 unsafe {
519 match method {
520 "setuid" => libc::setuid(id),
521 "seteuid" => libc::seteuid(id),
522 "setgid" => libc::setgid(id),
523 _ => libc::setegid(id),
524 };
525 }
526 }
527 Ok(Value::Undef)
528 }
529 "setgroups" => {
530 let groups = gid_array(args.first());
531 unsafe {
533 libc::setgroups(groups.len() as _, groups.as_ptr());
534 }
535 Ok(Value::Undef)
536 }
537 "initgroups" => {
538 let user = super::arg_str(args, 0);
539 let extra = super::arg_num(args, 1);
540 if let Ok(c) = std::ffi::CString::new(user) {
541 let gid = if extra.is_finite() { extra as u32 } else { 0 };
542 unsafe {
544 libc::initgroups(c.as_ptr(), gid as _);
545 }
546 }
547 Ok(Value::Undef)
548 }
549
550 "ref" | "unref" => Ok(with_host(|h| h.alloc(JsObj::Builtin("process".into())))),
553 "abort" => std::process::abort(),
554 "getActiveResourcesInfo" => Ok(with_host(|h| h.new_array(Vec::new()))),
555 "resourceUsage" => Ok(resource_usage()),
556 "threadCpuUsage" => Ok(thread_cpu_usage()),
557 "availableMemory" | "constrainedMemory" => Ok(Value::Float(0.0)),
558 "getBuiltinModule" => {
559 let id = super::arg_str(args, 0);
560 let id = id.strip_prefix("node:").unwrap_or(&id);
561 match crate::stdlib::resolve(id) {
562 Some(ns) => Ok(with_host(|h| h.alloc(JsObj::Builtin(ns.to_string())))),
563 None => Ok(Value::Undef),
564 }
565 }
566 "openStdin" => Ok(std_stream(0)),
567
568 "hasUncaughtExceptionCaptureCallback" => {
569 Ok(Value::Bool(UNCAUGHT_CAPTURE.with(|c| c.borrow().is_some())))
570 }
571 "setUncaughtExceptionCaptureCallback" => {
572 let cb = args.first().cloned().unwrap_or(Value::Undef);
573 let clear = matches!(cb, Value::Undef) || with_host(|h| h.is_null(&cb));
574 if clear {
575 UNCAUGHT_CAPTURE.with(|c| *c.borrow_mut() = None);
576 } else if UNCAUGHT_CAPTURE.with(|c| c.borrow().is_some()) {
577 return Some(Err(crate::host::type_error(
578 "`process.setUncaughtExceptionCaptureCallback()` was called \
579 while a capture callback was already active",
580 )));
581 } else {
582 UNCAUGHT_CAPTURE.with(|c| *c.borrow_mut() = Some(cb));
583 }
584 Ok(Value::Undef)
585 }
586 "addUncaughtExceptionCaptureCallback" => {
587 let cb = args.first().cloned().unwrap_or(Value::Undef);
588 if !matches!(cb, Value::Undef) {
589 UNCAUGHT_CAPTURE.with(|c| *c.borrow_mut() = Some(cb));
590 }
591 Ok(Value::Undef)
592 }
593 "execve" => exec_ve(args),
594 "loadEnvFile" => load_env_file(&super::arg_str(args, 0)),
595 _ => return None,
596 })
597}
598
599fn env_object() -> Value {
601 with_host(|h| {
602 let mut m = IndexMap::new();
603 for (k, v) in std::env::vars() {
604 m.insert(k, h.new_str(v));
605 }
606 h.new_object(m)
607 })
608}
609
610static ARGV: std::sync::OnceLock<(Vec<String>, Vec<String>)> = std::sync::OnceLock::new();
616
617pub fn install_argv() {
625 let split = crate::cli::split_argv(std::env::args());
626 let mut argv = vec![exec_path()];
627 if let Some(s) = &split.script {
628 argv.push(if s == "-" {
630 s.clone()
631 } else {
632 super::path::resolve_one(s)
633 });
634 }
635 argv.extend(split.user);
636 let _ = ARGV.set((split.exec, argv));
637}
638
639fn argv() -> Value {
646 with_host(|h| {
647 let items: Vec<Value> = match ARGV.get() {
648 Some((_, argv)) => argv.iter().map(|a| h.new_str(a.clone())).collect(),
649 None => std::env::args().map(|a| h.new_str(a)).collect(),
650 };
651 h.new_array(items)
652 })
653}
654
655fn exec_argv() -> Value {
657 with_host(|h| {
658 let items: Vec<Value> = ARGV
659 .get()
660 .map(|(e, _)| e.iter().map(|a| h.new_str(a.clone())).collect())
661 .unwrap_or_default();
662 h.new_array(items)
663 })
664}
665
666fn exec_path() -> String {
667 std::env::current_exe()
668 .map(|p| p.to_string_lossy().into_owned())
669 .unwrap_or_else(|_| "node".into())
670}
671
672fn versions() -> Value {
674 with_host(|h| {
675 let mut m = IndexMap::new();
676 m.insert("node".into(), h.new_str("26.5.0"));
677 m.insert("v8".into(), h.new_str("0.0.0"));
678 h.new_object(m)
679 })
680}
681
682fn std_stream(fd: i32) -> Value {
686 with_host(|h| {
687 let mut m = IndexMap::new();
688 m.insert("@@native".into(), h.new_str("WriteStream"));
689 m.insert("fd".into(), Value::Float(fd as f64));
690 let is_tty = unsafe { libc::isatty(fd) == 1 };
692 m.insert("isTTY".into(), Value::Bool(is_tty));
693 m.insert("writable".into(), Value::Bool(fd != 0));
694 m.insert("readable".into(), Value::Bool(fd == 0));
695 if is_tty {
697 if let Some((cols, rows)) = super::tty::window_size(fd) {
698 m.insert("columns".into(), Value::Float(cols as f64));
699 m.insert("rows".into(), Value::Float(rows as f64));
700 }
701 }
702 h.new_object(m)
703 })
704}
705
706pub fn stream_instance_call(recv: &Value, method: &str, args: &[Value]) -> Result<Value, String> {
710 match method {
711 "write" | "end" => {
712 let fd = with_host(|h| match h.get(recv) {
713 Some(JsObj::Object(p)) => p.get("fd").map(|v| h.to_number(v)).unwrap_or(1.0),
714 _ => 1.0,
715 });
716 if method == "end" && args.first().map(|v| matches!(v, Value::Undef)) != Some(false) {
719 return Ok(Value::Bool(true));
720 }
721 let bytes = chunk_bytes(args)?;
722 with_host(|h| h.write_out_bytes(&bytes, fd == 2.0));
723 Ok(Value::Bool(true))
724 }
725 "on" | "once" | "removeListener" | "cork" | "uncork" | "setEncoding" => Ok(recv.clone()),
727 "cursorTo" | "moveCursor" | "clearLine" | "clearScreenDown" => {
730 let seq = tty_control(method, args);
731 write_fd(stream_fd(recv), seq.as_bytes());
732 Ok(Value::Bool(true))
733 }
734 "getWindowSize" => {
735 let (c, r) = super::tty::window_size(stream_fd(recv) as i32).unwrap_or((80, 24));
736 Ok(with_host(|h| {
737 h.new_array(vec![Value::Float(c as f64), Value::Float(r as f64)])
738 }))
739 }
740 "getColorDepth" => Ok(Value::Float(24.0)),
743 "hasColors" => Ok(Value::Bool(true)),
744 _ => Err(crate::host::type_error(&format!(
745 "{method} is not a function"
746 ))),
747 }
748}
749
750fn hrtime(args: &[Value]) -> Value {
751 let now = std::time::SystemTime::now()
752 .duration_since(std::time::UNIX_EPOCH)
753 .unwrap_or_default();
754 let (mut secs, mut nanos) = (now.as_secs() as f64, now.subsec_nanos() as f64);
755 if let Some(Value::Obj(_)) = args.first() {
757 if let Some(prev) = with_host(|h| match h.get(&args[0]) {
758 Some(JsObj::Array(a)) if a.len() == 2 => Some((h.to_number(&a[0]), h.to_number(&a[1]))),
759 _ => None,
760 }) {
761 secs -= prev.0;
762 nanos -= prev.1;
763 }
764 }
765 with_host(|h| h.new_array(vec![Value::Float(secs), Value::Float(nanos)]))
766}
767
768fn memory_usage() -> Value {
769 with_host(|h| {
770 let mut m = IndexMap::new();
771 for k in ["rss", "heapTotal", "heapUsed", "external", "arrayBuffers"] {
772 m.insert(k.into(), Value::Float(0.0));
773 }
774 h.new_object(m)
775 })
776}
777
778pub fn emit_exit_event(code: i32) -> Result<(), String> {
788 if with_host(|h| std::mem::replace(&mut h.exiting, true)) {
789 return Ok(());
790 }
791 let listeners = with_host(|h| h.take_process_listeners("exit"));
792 for f in listeners {
793 crate::host::invoke(&f, vec![Value::Float(code as f64)], None)?;
794 }
795 Ok(())
796}
797
798pub fn emit_before_exit(code: i32) -> Result<bool, String> {
806 let listeners = with_host(|h| h.take_process_listeners("beforeExit"));
807 let any = !listeners.is_empty();
808 for f in listeners {
809 crate::host::invoke(&f, vec![Value::Float(code as f64)], None)?;
810 }
811 Ok(any)
812}
813
814fn chunk_bytes(args: &[Value]) -> Result<Vec<u8>, String> {
830 let chunk = args.first().cloned().unwrap_or(Value::Undef);
831 if with_host(|h| h.is_null(&chunk)) {
832 return Err(crate::host::type_error(
833 "May not write null values to stream",
834 ));
835 }
836 if let Some(s) = with_host(|h| h.as_str(&chunk)) {
837 let enc = match args.get(1) {
838 Some(v) if !matches!(v, Value::Undef) => with_host(|h| h.str_of(v)),
839 _ => "utf8".to_string(),
840 };
841 return Ok(super::buffer::decode_str(&s, &enc));
842 }
843 match super::native_tag(&chunk).as_deref() {
844 Some("Buffer") | Some("TypedArray") | Some("DataView") => {
845 Ok(super::buffer::bytes_like(&chunk).unwrap_or_default())
846 }
847 _ => Err(crate::host::type_error(&format!(
848 "The \"chunk\" argument must be of type string or an instance of \
849 Buffer, TypedArray, or DataView. Received {}",
850 super::received_desc(&chunk)
851 ))),
852 }
853}
854
855fn stream_fd(recv: &Value) -> f64 {
857 with_host(|h| match h.get(recv) {
858 Some(JsObj::Object(p)) => p.get("fd").map(|v| h.to_number(v)).unwrap_or(1.0),
859 _ => 1.0,
860 })
861}
862
863fn write_fd(fd: f64, bytes: &[u8]) {
866 let text = String::from_utf8_lossy(bytes).into_owned();
867 with_host(|h| h.write_out(&text, fd == 2.0));
868}
869
870fn tty_control(method: &str, args: &[Value]) -> String {
872 match method {
873 "cursorTo" => {
875 let x = super::arg_num(args, 0);
876 let y = super::arg_num(args, 1);
877 let x = if x.is_finite() { x as i64 } else { 0 };
878 if y.is_finite() {
879 format!("\x1b[{};{}H", y as i64 + 1, x + 1)
880 } else {
881 format!("\x1b[{}G", x + 1)
882 }
883 }
884 "moveCursor" => {
886 let dx = super::arg_num(args, 0);
887 let dy = super::arg_num(args, 1);
888 let mut s = String::new();
889 let dx = if dx.is_finite() { dx as i64 } else { 0 };
890 let dy = if dy.is_finite() { dy as i64 } else { 0 };
891 if dx > 0 {
892 s.push_str(&format!("\x1b[{dx}C"));
893 } else if dx < 0 {
894 s.push_str(&format!("\x1b[{}D", -dx));
895 }
896 if dy > 0 {
897 s.push_str(&format!("\x1b[{dy}B"));
898 } else if dy < 0 {
899 s.push_str(&format!("\x1b[{}A", -dy));
900 }
901 s
902 }
903 "clearLine" => match super::arg_num(args, 0) {
905 d if d < 0.0 => "\x1b[1K".into(),
906 d if d > 0.0 => "\x1b[0K".into(),
907 _ => "\x1b[2K".into(),
908 },
909 _ => "\x1b[0J".into(),
911 }
912}
913
914fn supplementary_groups() -> Vec<f64> {
916 unsafe {
918 let n = libc::getgroups(0, std::ptr::null_mut());
919 if n <= 0 {
920 return Vec::new();
921 }
922 let mut buf = vec![0 as libc::gid_t; n as usize];
923 let filled = libc::getgroups(n, buf.as_mut_ptr());
924 if filled < 0 {
925 return Vec::new();
926 }
927 buf.truncate(filled as usize);
928 buf.into_iter().map(|g| g as f64).collect()
929 }
930}
931
932fn gid_array(v: Option<&Value>) -> Vec<libc::gid_t> {
934 let Some(v) = v else { return Vec::new() };
935 with_host(|h| match h.get(v) {
936 Some(JsObj::Array(a)) => a.iter().map(|x| h.to_number(x) as libc::gid_t).collect(),
937 _ => Vec::new(),
938 })
939}
940
941fn get_rusage() -> Option<libc::rusage> {
943 unsafe {
945 let mut ru: libc::rusage = std::mem::zeroed();
946 (libc::getrusage(libc::RUSAGE_SELF, &mut ru) == 0).then_some(ru)
947 }
948}
949
950fn tv_micros(t: &libc::timeval) -> f64 {
952 t.tv_sec as f64 * 1e6 + t.tv_usec as f64
953}
954
955fn resource_usage() -> Value {
957 let ru = get_rusage();
958 with_host(|h| {
959 let mut m = IndexMap::new();
960 let (utime, stime) = ru
961 .as_ref()
962 .map(|r| (tv_micros(&r.ru_utime), tv_micros(&r.ru_stime)))
963 .unwrap_or((0.0, 0.0));
964 m.insert("userCPUTime".into(), Value::Float(utime));
965 m.insert("systemCPUTime".into(), Value::Float(stime));
966 let fields = [
967 ("maxRSS", ru.as_ref().map(|r| r.ru_maxrss)),
968 ("sharedMemorySize", ru.as_ref().map(|r| r.ru_ixrss)),
969 ("unsharedDataSize", ru.as_ref().map(|r| r.ru_idrss)),
970 ("unsharedStackSize", ru.as_ref().map(|r| r.ru_isrss)),
971 ("minorPageFault", ru.as_ref().map(|r| r.ru_minflt)),
972 ("majorPageFault", ru.as_ref().map(|r| r.ru_majflt)),
973 ("swappedOut", ru.as_ref().map(|r| r.ru_nswap)),
974 ("fsRead", ru.as_ref().map(|r| r.ru_inblock)),
975 ("fsWrite", ru.as_ref().map(|r| r.ru_oublock)),
976 ("ipcSent", ru.as_ref().map(|r| r.ru_msgsnd)),
977 ("ipcReceived", ru.as_ref().map(|r| r.ru_msgrcv)),
978 ("signalsCount", ru.as_ref().map(|r| r.ru_nsignals)),
979 ("voluntaryContextSwitches", ru.as_ref().map(|r| r.ru_nvcsw)),
980 (
981 "involuntaryContextSwitches",
982 ru.as_ref().map(|r| r.ru_nivcsw),
983 ),
984 ];
985 for (k, v) in fields {
986 m.insert(k.into(), Value::Float(v.unwrap_or(0) as f64));
987 }
988 h.new_object(m)
989 })
990}
991
992fn thread_cpu_usage() -> Value {
995 let (u, s) = get_rusage()
996 .map(|r| (tv_micros(&r.ru_utime), tv_micros(&r.ru_stime)))
997 .unwrap_or((0.0, 0.0));
998 with_host(|h| {
999 let mut m = IndexMap::new();
1000 m.insert("user".into(), Value::Float(u));
1001 m.insert("system".into(), Value::Float(s));
1002 h.new_object(m)
1003 })
1004}
1005
1006fn exec_ve(args: &[Value]) -> Result<Value, String> {
1009 use std::ffi::CString;
1010 let prog = CString::new(super::arg_str(args, 0))
1011 .map_err(|_| crate::host::type_error("process.execve: invalid file path"))?;
1012
1013 let argv_strs: Vec<String> = with_host(|h| match args.get(1).and_then(|v| h.get(v)) {
1014 Some(JsObj::Array(a)) => a.iter().map(|x| h.str_of(x)).collect(),
1015 _ => Vec::new(),
1016 });
1017 let env_strs: Vec<String> = {
1018 let from_arg = with_host(|h| match args.get(2).and_then(|v| h.get(v)) {
1019 Some(JsObj::Object(p)) => Some(
1020 p.iter()
1021 .map(|(k, v)| format!("{k}={}", h.str_of(v)))
1022 .collect::<Vec<_>>(),
1023 ),
1024 _ => None,
1025 });
1026 from_arg.unwrap_or_else(|| std::env::vars().map(|(k, v)| format!("{k}={v}")).collect())
1027 };
1028
1029 let to_c = |s: String| {
1030 CString::new(s).map_err(|_| crate::host::type_error("process.execve: NUL in argument"))
1031 };
1032 let argv_c: Vec<CString> = argv_strs.into_iter().map(to_c).collect::<Result<_, _>>()?;
1033 let env_c: Vec<CString> = env_strs.into_iter().map(to_c).collect::<Result<_, _>>()?;
1034
1035 let mut argv_p: Vec<*const libc::c_char> = argv_c.iter().map(|c| c.as_ptr()).collect();
1036 argv_p.push(std::ptr::null());
1037 let mut envp_p: Vec<*const libc::c_char> = env_c.iter().map(|c| c.as_ptr()).collect();
1038 envp_p.push(std::ptr::null());
1039
1040 unsafe {
1043 libc::execve(prog.as_ptr(), argv_p.as_ptr(), envp_p.as_ptr());
1044 }
1045 Err(crate::host::type_error(&format!(
1046 "process.execve failed: {}",
1047 std::io::Error::last_os_error()
1048 )))
1049}
1050
1051fn load_env_file(path: &str) -> Result<Value, String> {
1054 let path = if path.is_empty() { ".env" } else { path };
1055 let text =
1056 std::fs::read_to_string(path).map_err(|e| format!("Error: ENOENT: {e}, open '{path}'"))?;
1057 for line in text.lines() {
1058 let line = line.trim();
1059 if line.is_empty() || line.starts_with('#') {
1060 continue;
1061 }
1062 let line = line.strip_prefix("export ").unwrap_or(line);
1063 let Some((key, val)) = line.split_once('=') else {
1064 continue;
1065 };
1066 let key = key.trim();
1067 if key.is_empty() {
1068 continue;
1069 }
1070 let mut val = val.trim();
1071 if val.len() >= 2
1072 && ((val.starts_with('"') && val.ends_with('"'))
1073 || (val.starts_with('\'') && val.ends_with('\'')))
1074 {
1075 val = &val[1..val.len() - 1];
1076 }
1077 std::env::set_var(key, val);
1078 }
1079 Ok(Value::Undef)
1080}
1081
1082fn event_name(args: &[Value]) -> String {
1084 args.first()
1085 .map(|v| with_host(|h| h.str_of(v)))
1086 .unwrap_or_default()
1087}