1use super::arg_str;
36use crate::host::{with_host, IoTask, JsObj};
37use fusevm::Value;
38use indexmap::IndexMap;
39use std::process::{Child, Command, Stdio};
40use std::sync::atomic::{AtomicU64, Ordering};
41use std::sync::mpsc::Sender;
42use std::sync::{Arc, Mutex};
43
44pub const METHODS: &[&str] = &[
45 "execSync",
46 "spawnSync",
47 "execFileSync",
48 "exec",
49 "execFile",
50 "spawn",
51 "fork",
52];
53
54pub const CHILD_PROCESS_METHODS: &[&str] = &["kill", "send", "disconnect", "ref", "unref"];
57
58pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
59 Some(match method {
60 "execSync" => exec_sync(args),
61 "spawnSync" => spawn_sync(args),
62 "execFileSync" => exec_file_sync(args),
63 "exec" => exec(args),
64 "execFile" => exec_file(args),
65 "spawn" => spawn(args),
66 "@@childEvents" => {
67 let child = args.first().cloned().unwrap_or(Value::Undef);
68 child_events(&child).map(|_| Value::Undef)
69 }
70 "fork" => fork(args),
71 _ => return None,
72 })
73}
74
75static NEXT_CHILD_ID: AtomicU64 = AtomicU64::new(1);
79
80struct ChildRec {
83 emitter: Value,
84 handle: Arc<Mutex<Option<Child>>>,
85}
86
87thread_local! {
88 static CHILDREN: std::cell::RefCell<std::collections::HashMap<u64, ChildRec>> =
89 std::cell::RefCell::new(std::collections::HashMap::new());
90}
91
92fn child_object(extra: IndexMap<String, Value>) -> Value {
95 super::net::new_emitter_object("ChildProcess", extra)
96}
97
98struct Run {
101 status: Option<i32>,
102 signal: Option<i32>,
104 stdout: Vec<u8>,
105 stderr: Vec<u8>,
106 pid: u32,
107}
108
109#[derive(Default)]
111struct SpawnOpts {
112 input: Option<Vec<u8>>,
113 env: Option<Vec<(String, String)>>,
115 cwd: Option<String>,
116}
117
118fn spawn_opts(args: &[Value], idx: usize) -> SpawnOpts {
125 let Some(opts) = args.get(idx) else {
126 return SpawnOpts::default();
127 };
128 let read = |k: &str| crate::builtins::get_property(opts, k).ok();
129 let input = match read("input") {
130 Some(Value::Undef) | None => None,
131 Some(v) => Some(super::arg_str(&[v], 0).into_bytes()),
132 };
133 let cwd = match read("cwd") {
134 Some(Value::Undef) | None => None,
135 Some(v) => Some(with_host(|h| h.str_of(&v))),
136 };
137 let env = match read("env") {
138 Some(v) if with_host(|h| matches!(h.get(&v), Some(JsObj::Object(_)))) => {
139 let keys = with_host(|h| match h.get(&v) {
140 Some(JsObj::Object(m)) => m
141 .keys()
142 .filter(|k| !k.starts_with("@@"))
143 .cloned()
144 .collect::<Vec<_>>(),
145 _ => Vec::new(),
146 });
147 Some(
148 keys.into_iter()
149 .filter_map(|k| {
150 let val = crate::builtins::get_property(&v, &k).ok()?;
151 Some((k, with_host(|h| h.str_of(&val))))
152 })
153 .collect(),
154 )
155 }
156 _ => None,
157 };
158 SpawnOpts { input, env, cwd }
159}
160
161fn run(program: &str, args: &[String], opts: &SpawnOpts) -> std::io::Result<Run> {
164 let input = opts.input.as_deref();
165 let mut cmd = Command::new(program);
166 cmd.args(args).stdout(Stdio::piped()).stderr(Stdio::piped());
167 if let Some(dir) = &opts.cwd {
168 cmd.current_dir(dir);
169 }
170 if let Some(vars) = &opts.env {
171 cmd.env_clear();
172 for (k, v) in vars {
173 cmd.env(k, v);
174 }
175 }
176 cmd.stdin(if input.is_some() {
177 Stdio::piped()
178 } else {
179 Stdio::inherit()
180 });
181 let mut child = cmd.spawn()?;
182 let pid = child.id();
183 if let Some(bytes) = input {
184 if let Some(mut stdin) = child.stdin.take() {
185 use std::io::Write as _;
186 let _ = stdin.write_all(bytes);
187 }
189 }
190 let out = child.wait_with_output()?;
191 #[cfg(unix)]
192 let signal = std::os::unix::process::ExitStatusExt::signal(&out.status);
193 #[cfg(not(unix))]
194 let signal = None;
195 Ok(Run {
196 status: out.status.code(),
197 signal,
198 stdout: out.stdout,
199 stderr: out.stderr,
200 pid,
201 })
202}
203
204fn echo_stderr(args: &[Value], opts_idx: usize, bytes: &[u8]) {
213 if bytes.is_empty() {
214 return;
215 }
216 let explicit_stdio = args
217 .get(opts_idx)
218 .and_then(|o| crate::builtins::get_property(o, "stdio").ok())
219 .is_some_and(|v| !matches!(v, Value::Undef));
220 if explicit_stdio {
221 return;
222 }
223 let text = String::from_utf8_lossy(bytes).into_owned();
224 with_host(|h| h.write_out(&text, true));
225}
226
227fn command_failed(cmd: &str, r: &Run, enc: Option<&str>) -> String {
234 let tail = String::from_utf8_lossy(&r.stderr).into_owned();
235 let msg = format!("Command failed: {cmd}\n{tail}");
236 let stdout = output_value(&r.stdout, enc);
239 let stderr = output_value(&r.stderr, enc);
240 let e = crate::builtins::make_error_pub("Error", &msg);
243 let null = with_host(|h| h.null());
244 let status = r
245 .status
246 .map(|c| Value::Float(c as f64))
247 .unwrap_or_else(|| null.clone());
248 for (k, v) in [
249 ("status", status),
250 ("signal", null),
251 ("pid", Value::Float(r.pid as f64)),
252 ("stdout", stdout),
253 ("stderr", stderr),
254 ] {
255 let _ = crate::builtins::set_property_pub(&e, k, v);
256 }
257 with_host(|h| h.exc = Some(e));
258 format!("Error: {msg}")
259}
260
261fn exec_error(cmd: &str, r: &Run) -> Value {
265 let tail = String::from_utf8_lossy(&r.stderr).into_owned();
266 let e = crate::builtins::make_error_pub("Error", &format!("Command failed: {cmd}\n{tail}"));
267 let null = with_host(|h| h.null());
268 let cmd_v = with_host(|h| h.new_str(cmd.to_string()));
269 for (k, v) in [
270 ("killed", Value::Bool(false)),
271 ("code", Value::Float(r.status.unwrap_or(-1) as f64)),
272 ("signal", null),
273 ("cmd", cmd_v),
274 ] {
275 let _ = crate::builtins::set_property_pub(&e, k, v);
276 }
277 e
278}
279
280fn spawn_error(file: &str, argv: &[String], e: &std::io::Error) -> Value {
285 let code = super::fs::libuv_code(e);
286 let err = crate::builtins::make_error_pub("Error", &format!("spawn {file} {code}"));
287 let errno = -f64::from(e.raw_os_error().unwrap_or(5));
288 let (code_v, syscall, path, spawnargs) = with_host(|h| {
289 let items = argv.iter().map(|a| h.new_str(a.clone())).collect();
290 (
291 h.new_str(code.to_string()),
292 h.new_str(format!("spawn {file}")),
293 h.new_str(file.to_string()),
294 h.new_array(items),
295 )
296 });
297 for (k, v) in [
298 ("errno", Value::Float(errno)),
299 ("code", code_v),
300 ("syscall", syscall),
301 ("path", path),
302 ("spawnargs", spawnargs),
303 ] {
304 let _ = crate::builtins::set_property_pub(&err, k, v);
305 }
306 err
307}
308
309fn exec_sync(args: &[Value]) -> Result<Value, String> {
312 let cmd = arg_str(args, 0);
313 let enc = opts_encoding(args, 1);
314 let r = run("sh", &["-c".to_string(), cmd.clone()], &spawn_opts(args, 1))
315 .map_err(|e| format!("Error: {e}"))?;
316 echo_stderr(args, 1, &r.stderr);
317 if r.status != Some(0) {
318 return Err(command_failed(&cmd, &r, enc.as_deref()));
319 }
320 Ok(output_value(&r.stdout, enc.as_deref()))
321}
322
323fn spawn_sync(args: &[Value]) -> Result<Value, String> {
326 let cmd = arg_str(args, 0);
327 let cmd_args = arg_array(args, 1);
328 let enc = opts_encoding(args, 2);
329 match run(&cmd, &cmd_args, &spawn_opts(args, 2)) {
330 Ok(r) => {
331 let stdout = output_value(&r.stdout, enc.as_deref());
335 let stderr = output_value(&r.stderr, enc.as_deref());
336 Ok(with_host(|h| {
337 let mut m = IndexMap::new();
338 m.insert("pid".into(), Value::Float(r.pid as f64));
339 m.insert(
340 "status".into(),
341 r.status
342 .map(|c| Value::Float(c as f64))
343 .unwrap_or_else(|| h.null()),
344 );
345 m.insert("signal".into(), h.null());
348 m.insert("stdout".into(), stdout);
349 m.insert("stderr".into(), stderr);
350 h.new_object(m)
351 }))
352 }
353 Err(e) => Ok(with_host(|h| {
356 let mut m = IndexMap::new();
357 m.insert("pid".into(), Value::Float(0.0));
358 m.insert("status".into(), h.null());
359 m.insert("signal".into(), h.null());
360 m.insert("stdout".into(), h.null());
361 m.insert("stderr".into(), h.null());
362 m.insert("error".into(), h.new_str(format!("Error: spawn {cmd} {e}")));
363 h.new_object(m)
364 })),
365 }
366}
367
368fn exec_file_sync(args: &[Value]) -> Result<Value, String> {
371 let file = arg_str(args, 0);
372 let cmd_args = arg_array(args, 1);
373 let enc = opts_encoding(args, 2);
374 let r = run(&file, &cmd_args, &spawn_opts(args, 2))
375 .map_err(|e| format!("Error: spawn {file} {e}"))?;
376 echo_stderr(args, 2, &r.stderr);
377 if r.status != Some(0) {
378 return Err(command_failed(&file, &r, enc.as_deref()));
382 }
383 Ok(output_value(&r.stdout, enc.as_deref()))
384}
385
386fn exec(args: &[Value]) -> Result<Value, String> {
390 let cmd = arg_str(args, 0);
391 let Some(cb) = args.last().cloned() else {
393 return Ok(Value::Undef);
394 };
395 let (err, out, errout) = match run("sh", &["-c".to_string(), cmd.clone()], &spawn_opts(args, 1))
396 {
397 Ok(r) => {
398 let stdout = String::from_utf8_lossy(&r.stdout).into_owned();
399 let stderr = String::from_utf8_lossy(&r.stderr).into_owned();
400 let err = if r.status == Some(0) {
407 with_host(|h| h.null())
408 } else {
409 exec_error(&cmd, &r)
410 };
411 (err, stdout, stderr)
412 }
413 Err(e) => (
414 with_host(|h| crate::builtins::synth_error(h, &format!("Error: {e}"))),
415 String::new(),
416 String::new(),
417 ),
418 };
419 with_host(|h| {
420 let so = h.new_str(out);
421 let se = h.new_str(errout);
422 h.queue_micro(cb, vec![err, so, se]);
423 });
424 Ok(Value::Undef)
425}
426
427fn spawn(args: &[Value]) -> Result<Value, String> {
431 let cmd = arg_str(args, 0);
432 let cmd_args = arg_array(args, 1);
433 let disp = stdio_dispositions(args.get(2));
434 let null = with_host(|h| h.null());
435 let pipe = |d: Disposition| match d {
436 Disposition::Pipe => child_pipe(),
437 _ => null.clone(),
438 };
439 let (stdout, stderr) = (pipe(disp[1]), pipe(disp[2]));
440 let mut m = IndexMap::new();
441 let (status, signal, out, err, spawn_err) = match run(&cmd, &cmd_args, &spawn_opts(args, 2)) {
442 Ok(r) => {
443 m.insert("pid".into(), Value::Float(r.pid as f64));
444 (r.status, r.signal, r.stdout, r.stderr, None)
445 }
446 Err(e) => {
449 m.insert("pid".into(), Value::Undef);
450 (
451 None,
452 None,
453 Vec::new(),
454 Vec::new(),
455 Some(spawn_error(&cmd, &cmd_args, &e)),
456 )
457 }
458 };
459 m.insert("exitCode".into(), null.clone());
462 m.insert("signalCode".into(), null.clone());
463 m.insert("killed".into(), Value::Bool(false));
464 m.insert("connected".into(), Value::Bool(false));
465 m.insert("stdout".into(), stdout);
466 m.insert("stderr".into(), stderr);
467 let child = child_object(m);
468 PENDING.with(|p| {
469 p.borrow_mut().push(Finished {
470 child: child.clone(),
471 status,
472 signal,
473 out,
474 err,
475 disp,
476 spawn_err,
477 })
478 });
479 with_host(|h| {
480 let cb = h.alloc(JsObj::Builtin("child_process.@@childEvents".into()));
481 h.add_timer(-1.0, cb, vec![child.clone()], None);
482 });
483 Ok(child)
484}
485
486#[derive(Clone, Copy, PartialEq)]
488enum Disposition {
489 Pipe,
491 Inherit,
493 Ignore,
494}
495
496fn stdio_dispositions(opts: Option<&Value>) -> [Disposition; 3] {
500 let Some(stdio) = opts.and_then(|o| crate::builtins::get_property(o, "stdio").ok()) else {
501 return [Disposition::Pipe; 3];
502 };
503 let one = |v: &Value| -> Disposition {
504 match v {
505 Value::Float(_) | Value::Int(_) => Disposition::Inherit,
506 Value::Undef => Disposition::Pipe,
507 _ => with_host(|h| {
508 if h.is_null(v) {
509 return Disposition::Pipe;
510 }
511 if matches!(h.get(v), Some(JsObj::Object(_))) {
512 return Disposition::Inherit;
513 }
514 match h.str_of(v).as_str() {
515 "inherit" => Disposition::Inherit,
516 "ignore" => Disposition::Ignore,
517 _ => Disposition::Pipe,
518 }
519 }),
520 }
521 };
522 let items = with_host(|h| match h.get(&stdio) {
523 Some(JsObj::Array(items)) => Some(items.clone()),
524 _ => None,
525 });
526 match items {
527 Some(items) => {
528 let at = |i: usize| one(items.get(i).unwrap_or(&Value::Undef));
529 [at(0), at(1), at(2)]
530 }
531 None => [one(&stdio); 3],
532 }
533}
534
535struct Finished {
537 child: Value,
538 status: Option<i32>,
539 signal: Option<i32>,
540 out: Vec<u8>,
541 err: Vec<u8>,
542 disp: [Disposition; 3],
543 spawn_err: Option<Value>,
544}
545
546thread_local! {
547 static PENDING: std::cell::RefCell<Vec<Finished>> = const { std::cell::RefCell::new(Vec::new()) };
548}
549
550fn child_pipe() -> Value {
553 super::net::new_emitter_object("ChildPipe", IndexMap::new())
554}
555
556pub const CHILD_PIPE_METHODS: &[&str] = &["setEncoding", "pipe", "resume", "pause", "destroy"];
558
559pub fn pipe_call(recv: &Value, method: &str, args: Vec<Value>) -> Result<Value, String> {
560 if !CHILD_PIPE_METHODS.contains(&method) && super::events::METHODS.contains(&method) {
561 return super::events::instance_call(recv, method, args);
562 }
563 match method {
564 "setEncoding" => {
565 let enc = match args.first() {
566 Some(v) if !matches!(v, Value::Undef) => arg_str(&args, 0),
567 _ => "utf8".to_string(),
568 };
569 set_prop(recv, "@@encoding", with_host(|h| h.new_str(enc)));
570 Ok(recv.clone())
571 }
572 "pipe" => {
573 let dest = args.first().cloned().unwrap_or(Value::Undef);
574 set_prop(recv, "@@pipeDest", dest.clone());
575 Ok(dest)
576 }
577 "resume" | "pause" | "destroy" => Ok(recv.clone()),
578 _ => Err(crate::host::type_error(&format!(
579 "{method} is not a function"
580 ))),
581 }
582}
583
584fn prop(v: &Value, key: &str) -> Option<Value> {
585 with_host(|h| match h.get(v) {
586 Some(JsObj::Object(p)) => p.get(key).cloned(),
587 _ => None,
588 })
589}
590
591fn emit(target: &Value, event: &str, mut args: Vec<Value>) -> Result<(), String> {
592 args.insert(0, with_host(|h| h.new_str(event)));
593 super::events::instance_call(target, "emit", args).map(|_| ())
594}
595
596fn pipe_output(pipe: &Value, bytes: &[u8]) -> Result<(), String> {
599 let dest = prop(pipe, "@@pipeDest");
600 if !bytes.is_empty() {
601 let enc = prop(pipe, "@@encoding").map(|v| with_host(|h| h.str_of(&v)));
602 let chunk = output_value(bytes, enc.as_deref());
603 if let Some(dest) = &dest {
604 crate::host::call_method(dest, "write", vec![chunk.clone()])?;
605 }
606 emit(pipe, "data", vec![chunk])?;
607 }
608 if let Some(dest) = &dest {
609 let is_std = super::native_tag(dest).as_deref() == Some("WriteStream");
610 if !is_std {
611 crate::host::call_method(dest, "end", Vec::new())?;
612 }
613 }
614 emit(pipe, "end", Vec::new())
615}
616
617fn child_events(child: &Value) -> Result<(), String> {
622 let found = PENDING.with(|p| {
623 let mut p = p.borrow_mut();
624 let i = p
625 .iter()
626 .position(|f| with_host(|h| h.strict_eq(&f.child, child)))?;
627 Some(p.remove(i))
628 });
629 let Some(f) = found else { return Ok(()) };
630 let null = with_host(|h| h.null());
631 if let Some(err) = f.spawn_err {
632 emit(child, "error", vec![err])?;
633 return emit(child, "close", vec![Value::Float(-2.0), null]);
634 }
635 emit(child, "spawn", Vec::new())?;
636 let pipes = [prop(child, "stdout"), prop(child, "stderr")];
637 for (i, bytes) in [(1, &f.out), (2, &f.err)] {
638 match f.disp[i] {
639 Disposition::Pipe => {
640 if let Some(p) = &pipes[i - 1] {
641 pipe_output(p, bytes)?;
642 }
643 }
644 Disposition::Inherit => with_host(|h| h.write_out_bytes(bytes, i == 2)),
645 Disposition::Ignore => {}
646 }
647 }
648 let code = f
649 .status
650 .map_or_else(|| null.clone(), |c| Value::Float(c as f64));
651 let signal = match f.signal.and_then(signal_name) {
652 Some(name) => with_host(|h| h.new_str(name)),
653 None => null.clone(),
654 };
655 set_prop(child, "exitCode", code.clone());
656 set_prop(child, "signalCode", signal.clone());
657 emit(child, "exit", vec![code.clone(), signal.clone()])?;
658 emit(child, "close", vec![code, signal])?;
659 for (i, p) in pipes.iter().enumerate() {
660 if let (Some(p), Disposition::Pipe) = (p, f.disp[i + 1]) {
661 emit(p, "close", Vec::new())?;
662 }
663 }
664 Ok(())
665}
666
667fn signal_name(n: i32) -> Option<&'static str> {
670 const NAMES: &[&str] = &[
671 "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", "SIGTRAP", "SIGABRT", "SIGBUS", "SIGFPE",
672 "SIGKILL", "SIGUSR1", "SIGSEGV", "SIGUSR2", "SIGPIPE", "SIGALRM", "SIGTERM", "SIGCHLD",
673 "SIGCONT", "SIGSTOP", "SIGTSTP", "SIGWINCH",
674 ];
675 NAMES
676 .iter()
677 .copied()
678 .find(|name| super::process::signal_number(name) == Some(n))
679}
680
681fn exec_file(args: &[Value]) -> Result<Value, String> {
686 let file = arg_str(args, 0);
687 let cmd_args = arg_array(args, 1);
688 let cb = args
690 .iter()
691 .rev()
692 .find(|v| with_host(|h| crate::host::is_callable(h, v)))
693 .cloned();
694
695 let full_cmd = std::iter::once(file.clone())
696 .chain(cmd_args.iter().cloned())
697 .collect::<Vec<_>>()
698 .join(" ");
699
700 match run(&file, &cmd_args, &spawn_opts(args, 2)) {
701 Ok(r) => {
702 let stdout_buf = super::buffer::from_bytes(&r.stdout);
703 let stderr_buf = super::buffer::from_bytes(&r.stderr);
704 let null = with_host(|h| h.null());
705 if let Some(cb) = cb {
706 let so = String::from_utf8_lossy(&r.stdout).into_owned();
707 let se = String::from_utf8_lossy(&r.stderr).into_owned();
708 let err = if r.status == Some(0) {
715 null.clone()
716 } else {
717 exec_error(&full_cmd, &r)
718 };
719 with_host(|h| {
720 let so = h.new_str(so);
721 let se = h.new_str(se);
722 h.queue_micro(cb, vec![err, so, se]);
723 });
724 }
725 let mut m = IndexMap::new();
726 m.insert("pid".into(), Value::Float(r.pid as f64));
727 m.insert(
728 "exitCode".into(),
729 r.status
730 .map(|c| Value::Float(c as f64))
731 .unwrap_or_else(|| null.clone()),
732 );
733 m.insert("signalCode".into(), null);
734 m.insert("killed".into(), Value::Bool(false));
735 m.insert("connected".into(), Value::Bool(false));
736 m.insert("stdout".into(), stdout_buf);
737 m.insert("stderr".into(), stderr_buf);
738 Ok(child_object(m))
739 }
740 Err(e) => {
744 let err = spawn_error(&file, &cmd_args, &e);
745 if let Some(cb) = cb {
746 let (empty1, empty2) = with_host(|h| (h.new_str(""), h.new_str("")));
747 with_host(|h| h.queue_micro(cb, vec![err, empty1, empty2]));
748 let null = with_host(|h| h.null());
749 let mut m = IndexMap::new();
750 m.insert("pid".into(), Value::Undef);
751 m.insert("exitCode".into(), null.clone());
752 m.insert("signalCode".into(), null.clone());
753 m.insert("killed".into(), Value::Bool(false));
754 m.insert("connected".into(), Value::Bool(false));
755 m.insert("stdout".into(), null.clone());
756 m.insert("stderr".into(), null);
757 return Ok(child_object(m));
758 }
759 Err(format!("Error: spawn {file} {e}"))
760 }
761 }
762}
763
764fn fork(args: &[Value]) -> Result<Value, String> {
775 let module = arg_str(args, 0);
776 let extra_args = arg_array(args, 1);
777 let exe = std::env::current_exe().map_err(|e| format!("Error: fork: {e}"))?;
778
779 let mut cmd = Command::new(exe);
780 cmd.arg(&module).args(&extra_args);
781 cmd.stdin(Stdio::inherit())
782 .stdout(Stdio::inherit())
783 .stderr(Stdio::inherit());
784 let child = cmd
785 .spawn()
786 .map_err(|e| format!("Error: fork {module} {e}"))?;
787 let pid = child.id();
788
789 let id = NEXT_CHILD_ID.fetch_add(1, Ordering::Relaxed);
790 let handle = Arc::new(Mutex::new(Some(child)));
791
792 let mut extra = IndexMap::new();
793 extra.insert("@@childid".into(), Value::Float(id as f64));
794 extra.insert("pid".into(), Value::Float(pid as f64));
795 extra.insert("connected".into(), Value::Bool(false));
796 extra.insert("killed".into(), Value::Bool(false));
797 extra.insert("exitCode".into(), with_host(|h| h.null()));
798 extra.insert("signalCode".into(), with_host(|h| h.null()));
799 let emitter = child_object(extra);
800 CHILDREN.with(|c| {
801 c.borrow_mut().insert(
802 id,
803 ChildRec {
804 emitter: emitter.clone(),
805 handle: handle.clone(),
806 },
807 );
808 });
809 with_host(|h| h.incr_handle());
810
811 let io_tx = with_host(|h| h.io_sender());
812 std::thread::spawn(move || wait_child(id, handle, io_tx));
813 Ok(emitter)
814}
815
816fn wait_child(id: u64, handle: Arc<Mutex<Option<Child>>>, io_tx: Sender<IoTask>) {
820 loop {
821 std::thread::sleep(std::time::Duration::from_millis(20));
822 let status = {
823 let mut g = match handle.lock() {
824 Ok(g) => g,
825 Err(_) => return,
826 };
827 match g.as_mut() {
828 Some(child) => match child.try_wait() {
829 Ok(Some(status)) => {
830 *g = None;
831 Some(status.code())
832 }
833 Ok(None) => None,
834 Err(_) => {
835 *g = None;
836 Some(None)
837 }
838 },
839 None => return,
841 }
842 };
843 if let Some(code) = status {
844 let _ = io_tx.send(Box::new(move || on_child_exit(id, code)));
845 return;
846 }
847 }
848}
849
850fn on_child_exit(id: u64, code: Option<i32>) -> Result<(), String> {
853 let emitter = CHILDREN.with(|c| c.borrow().get(&id).map(|r| r.emitter.clone()));
854 let Some(emitter) = emitter else {
855 return Ok(());
856 };
857 let (code_val, null1, null2) = with_host(|h| {
858 let cv = code
859 .map(|c| Value::Float(c as f64))
860 .unwrap_or_else(|| h.null());
861 (cv, h.null(), h.null())
862 });
863 set_prop(&emitter, "exitCode", code_val.clone());
864 set_prop(&emitter, "killed", Value::Bool(true));
865 let ev_exit = with_host(|h| h.new_str("exit"));
866 let ev_close = with_host(|h| h.new_str("close"));
867 super::events::instance_call(&emitter, "emit", vec![ev_exit, code_val.clone(), null1])?;
868 super::events::instance_call(&emitter, "emit", vec![ev_close, code_val, null2])?;
869 CHILDREN.with(|c| c.borrow_mut().remove(&id));
870 with_host(|h| h.decr_handle());
871 let _ = with_host(|h| h.io_sender()).send(Box::new(|| Ok(())));
872 Ok(())
873}
874
875fn set_prop(recv: &Value, key: &str, val: Value) {
876 with_host(|h| {
877 if let Some(JsObj::Object(p)) = h.get_mut(recv) {
878 p.insert(key.to_string(), val);
879 }
880 });
881}
882
883pub fn instance_call(recv: &Value, method: &str, args: Vec<Value>) -> Result<Value, String> {
890 if super::events::METHODS.contains(&method) {
891 return super::events::instance_call(recv, method, args);
892 }
893 match method {
894 "kill" => Ok(Value::Bool(kill_child(recv))),
895 "send" => Ok(Value::Bool(false)),
897 "disconnect" => {
898 set_prop(recv, "connected", Value::Bool(false));
899 Ok(Value::Undef)
900 }
901 "ref" | "unref" => Ok(recv.clone()),
902 _ => Err(crate::host::type_error(&format!(
903 "child.{method} is not a function"
904 ))),
905 }
906}
907
908fn kill_child(recv: &Value) -> bool {
912 let id = with_host(|h| match h.get(recv) {
913 Some(JsObj::Object(p)) => p.get("@@childid").map(|v| h.to_number(v) as u64),
914 _ => None,
915 });
916 let Some(id) = id else { return false };
917 let handle = CHILDREN.with(|c| c.borrow().get(&id).map(|r| r.handle.clone()));
918 let Some(handle) = handle else { return false };
919 if let Ok(mut g) = handle.lock() {
920 if let Some(child) = g.as_mut() {
921 let _ = child.kill();
922 return true;
923 }
924 }
925 false
926}
927
928fn output_value(bytes: &[u8], encoding: Option<&str>) -> Value {
932 match encoding {
933 Some(enc) if !enc.eq_ignore_ascii_case("buffer") => {
934 with_host(|h| h.new_str(String::from_utf8_lossy(bytes).into_owned()))
935 }
936 _ => super::buffer::from_bytes(bytes),
937 }
938}
939
940fn arg_array(args: &[Value], i: usize) -> Vec<String> {
943 with_host(|h| match args.get(i).and_then(|v| h.get(v)) {
944 Some(crate::host::JsObj::Array(items)) => items.iter().map(|v| h.str_of(v)).collect(),
945 _ => Vec::new(),
946 })
947}
948
949fn opts_encoding(args: &[Value], i: usize) -> Option<String> {
952 with_host(|h| match args.get(i).and_then(|v| h.get(v)) {
953 Some(crate::host::JsObj::Object(p)) => p
954 .get("encoding")
955 .map(|v| h.str_of(v))
956 .filter(|s| !s.is_empty() && s != "undefined" && s != "null"),
957 _ => None,
958 })
959}