1use super::arg_str;
34use crate::host::{with_host, IoTask, JsObj};
35use fusevm::Value;
36use indexmap::IndexMap;
37use std::process::{Child, Command, Stdio};
38use std::sync::atomic::{AtomicU64, Ordering};
39use std::sync::mpsc::Sender;
40use std::sync::{Arc, Mutex};
41
42pub const METHODS: &[&str] = &[
43 "execSync",
44 "spawnSync",
45 "execFileSync",
46 "exec",
47 "execFile",
48 "spawn",
49 "fork",
50];
51
52pub const CHILD_PROCESS_METHODS: &[&str] = &["kill", "send", "disconnect", "ref", "unref"];
55
56pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
57 Some(match method {
58 "execSync" => exec_sync(args),
59 "spawnSync" => spawn_sync(args),
60 "execFileSync" => exec_file_sync(args),
61 "exec" => exec(args),
62 "execFile" => exec_file(args),
63 "spawn" => spawn(args),
64 "fork" => fork(args),
65 _ => return None,
66 })
67}
68
69static NEXT_CHILD_ID: AtomicU64 = AtomicU64::new(1);
73
74struct ChildRec {
77 emitter: Value,
78 handle: Arc<Mutex<Option<Child>>>,
79}
80
81thread_local! {
82 static CHILDREN: std::cell::RefCell<std::collections::HashMap<u64, ChildRec>> =
83 std::cell::RefCell::new(std::collections::HashMap::new());
84}
85
86fn child_object(extra: IndexMap<String, Value>) -> Value {
89 super::net::new_emitter_object("ChildProcess", extra)
90}
91
92struct Run {
95 status: Option<i32>,
96 stdout: Vec<u8>,
97 stderr: Vec<u8>,
98 pid: u32,
99}
100
101fn run(program: &str, args: &[String], input: Option<&[u8]>) -> std::io::Result<Run> {
104 let mut cmd = Command::new(program);
105 cmd.args(args).stdout(Stdio::piped()).stderr(Stdio::piped());
106 cmd.stdin(if input.is_some() {
107 Stdio::piped()
108 } else {
109 Stdio::inherit()
110 });
111 let mut child = cmd.spawn()?;
112 let pid = child.id();
113 if let Some(bytes) = input {
114 if let Some(mut stdin) = child.stdin.take() {
115 use std::io::Write as _;
116 let _ = stdin.write_all(bytes);
117 }
119 }
120 let out = child.wait_with_output()?;
121 Ok(Run {
122 status: out.status.code(),
123 stdout: out.stdout,
124 stderr: out.stderr,
125 pid,
126 })
127}
128
129fn opts_input(args: &[Value], idx: usize) -> Option<Vec<u8>> {
131 let opts = args.get(idx)?;
132 match crate::builtins::get_property(opts, "input") {
133 Ok(Value::Undef) => None,
134 Ok(v) => Some(super::arg_str(&[v], 0).into_bytes()),
135 Err(_) => None,
136 }
137}
138
139fn exec_sync(args: &[Value]) -> Result<Value, String> {
142 let cmd = arg_str(args, 0);
143 let enc = opts_encoding(args, 1);
144 let r = run(
145 "sh",
146 &["-c".to_string(), cmd.clone()],
147 opts_input(args, 1).as_deref(),
148 )
149 .map_err(|e| format!("Error: {e}"))?;
150 if r.status != Some(0) {
151 let tail = String::from_utf8_lossy(&r.stderr);
152 return Err(format!("Error: Command failed: {cmd}\n{tail}"));
153 }
154 Ok(output_value(&r.stdout, enc.as_deref()))
155}
156
157fn spawn_sync(args: &[Value]) -> Result<Value, String> {
160 let cmd = arg_str(args, 0);
161 let cmd_args = arg_array(args, 1);
162 let enc = opts_encoding(args, 2);
163 match run(&cmd, &cmd_args, opts_input(args, 2).as_deref()) {
164 Ok(r) => {
165 let stdout = output_value(&r.stdout, enc.as_deref());
169 let stderr = output_value(&r.stderr, enc.as_deref());
170 Ok(with_host(|h| {
171 let mut m = IndexMap::new();
172 m.insert("pid".into(), Value::Float(r.pid as f64));
173 m.insert(
174 "status".into(),
175 r.status
176 .map(|c| Value::Float(c as f64))
177 .unwrap_or_else(|| h.null()),
178 );
179 m.insert("signal".into(), h.null());
182 m.insert("stdout".into(), stdout);
183 m.insert("stderr".into(), stderr);
184 h.new_object(m)
185 }))
186 }
187 Err(e) => Ok(with_host(|h| {
190 let mut m = IndexMap::new();
191 m.insert("pid".into(), Value::Float(0.0));
192 m.insert("status".into(), h.null());
193 m.insert("signal".into(), h.null());
194 m.insert("stdout".into(), h.null());
195 m.insert("stderr".into(), h.null());
196 m.insert("error".into(), h.new_str(format!("Error: spawn {cmd} {e}")));
197 h.new_object(m)
198 })),
199 }
200}
201
202fn exec_file_sync(args: &[Value]) -> Result<Value, String> {
205 let file = arg_str(args, 0);
206 let cmd_args = arg_array(args, 1);
207 let enc = opts_encoding(args, 2);
208 let r = run(&file, &cmd_args, opts_input(args, 2).as_deref())
209 .map_err(|e| format!("Error: spawn {file} {e}"))?;
210 if r.status != Some(0) {
211 let tail = String::from_utf8_lossy(&r.stderr);
212 return Err(format!("Error: Command failed: {file}\n{tail}"));
213 }
214 Ok(output_value(&r.stdout, enc.as_deref()))
215}
216
217fn exec(args: &[Value]) -> Result<Value, String> {
221 let cmd = arg_str(args, 0);
222 let Some(cb) = args.last().cloned() else {
224 return Ok(Value::Undef);
225 };
226 let (err, out, errout) = match run("sh", &["-c".to_string(), cmd.clone()], None) {
227 Ok(r) => {
228 let stdout = String::from_utf8_lossy(&r.stdout).into_owned();
229 let stderr = String::from_utf8_lossy(&r.stderr).into_owned();
230 let err = if r.status == Some(0) {
231 with_host(|h| h.null())
232 } else {
233 let code = r.status.unwrap_or(-1);
234 with_host(|h| h.new_str(format!("Error: Command failed: {cmd}\nexit code {code}")))
235 };
236 (err, stdout, stderr)
237 }
238 Err(e) => (
239 with_host(|h| h.new_str(format!("Error: {e}"))),
240 String::new(),
241 String::new(),
242 ),
243 };
244 with_host(|h| {
245 let so = h.new_str(out);
246 let se = h.new_str(errout);
247 h.queue_micro(cb, vec![err, so, se]);
248 });
249 Ok(Value::Undef)
250}
251
252fn spawn(args: &[Value]) -> Result<Value, String> {
256 let cmd = arg_str(args, 0);
257 let cmd_args = arg_array(args, 1);
258 match run(&cmd, &cmd_args, None) {
259 Ok(r) => {
260 let stdout = super::buffer::from_bytes(&r.stdout);
263 let stderr = super::buffer::from_bytes(&r.stderr);
264 let null = with_host(|h| h.null());
265 let mut m = IndexMap::new();
266 m.insert("pid".into(), Value::Float(r.pid as f64));
267 m.insert(
268 "exitCode".into(),
269 r.status
270 .map(|c| Value::Float(c as f64))
271 .unwrap_or_else(|| null.clone()),
272 );
273 m.insert("signalCode".into(), null);
274 m.insert("killed".into(), Value::Bool(false));
275 m.insert("connected".into(), Value::Bool(false));
276 m.insert("stdout".into(), stdout);
277 m.insert("stderr".into(), stderr);
278 Ok(child_object(m))
279 }
280 Err(e) => Err(format!("Error: spawn {cmd} {e}")),
281 }
282}
283
284fn exec_file(args: &[Value]) -> Result<Value, String> {
289 let file = arg_str(args, 0);
290 let cmd_args = arg_array(args, 1);
291 let cb = args
293 .iter()
294 .rev()
295 .find(|v| with_host(|h| crate::host::is_callable(h, v)))
296 .cloned();
297
298 match run(&file, &cmd_args, None) {
299 Ok(r) => {
300 let stdout_buf = super::buffer::from_bytes(&r.stdout);
301 let stderr_buf = super::buffer::from_bytes(&r.stderr);
302 let null = with_host(|h| h.null());
303 if let Some(cb) = cb {
304 let so = String::from_utf8_lossy(&r.stdout).into_owned();
305 let se = String::from_utf8_lossy(&r.stderr).into_owned();
306 let err = if r.status == Some(0) {
307 null.clone()
308 } else {
309 let code = r.status.unwrap_or(-1);
310 with_host(|h| {
311 h.new_str(format!("Error: Command failed: {file}\nexit code {code}"))
312 })
313 };
314 with_host(|h| {
315 let so = h.new_str(so);
316 let se = h.new_str(se);
317 h.queue_micro(cb, vec![err, so, se]);
318 });
319 }
320 let mut m = IndexMap::new();
321 m.insert("pid".into(), Value::Float(r.pid as f64));
322 m.insert(
323 "exitCode".into(),
324 r.status
325 .map(|c| Value::Float(c as f64))
326 .unwrap_or_else(|| null.clone()),
327 );
328 m.insert("signalCode".into(), null);
329 m.insert("killed".into(), Value::Bool(false));
330 m.insert("connected".into(), Value::Bool(false));
331 m.insert("stdout".into(), stdout_buf);
332 m.insert("stderr".into(), stderr_buf);
333 Ok(child_object(m))
334 }
335 Err(e) => {
336 if let Some(cb) = cb {
337 let msg = with_host(|h| h.new_str(format!("Error: spawn {file} {e}")));
338 let empty1 = with_host(|h| h.new_str(""));
339 let empty2 = with_host(|h| h.new_str(""));
340 with_host(|h| h.queue_micro(cb, vec![msg, empty1, empty2]));
341 }
342 Err(format!("Error: spawn {file} {e}"))
343 }
344 }
345}
346
347fn fork(args: &[Value]) -> Result<Value, String> {
358 let module = arg_str(args, 0);
359 let extra_args = arg_array(args, 1);
360 let exe = std::env::current_exe().map_err(|e| format!("Error: fork: {e}"))?;
361
362 let mut cmd = Command::new(exe);
363 cmd.arg(&module).args(&extra_args);
364 cmd.stdin(Stdio::inherit())
365 .stdout(Stdio::inherit())
366 .stderr(Stdio::inherit());
367 let child = cmd
368 .spawn()
369 .map_err(|e| format!("Error: fork {module} {e}"))?;
370 let pid = child.id();
371
372 let id = NEXT_CHILD_ID.fetch_add(1, Ordering::Relaxed);
373 let handle = Arc::new(Mutex::new(Some(child)));
374
375 let mut extra = IndexMap::new();
376 extra.insert("@@childid".into(), Value::Float(id as f64));
377 extra.insert("pid".into(), Value::Float(pid as f64));
378 extra.insert("connected".into(), Value::Bool(false));
379 extra.insert("killed".into(), Value::Bool(false));
380 extra.insert("exitCode".into(), with_host(|h| h.null()));
381 extra.insert("signalCode".into(), with_host(|h| h.null()));
382 let emitter = child_object(extra);
383 CHILDREN.with(|c| {
384 c.borrow_mut().insert(
385 id,
386 ChildRec {
387 emitter: emitter.clone(),
388 handle: handle.clone(),
389 },
390 );
391 });
392 with_host(|h| h.incr_handle());
393
394 let io_tx = with_host(|h| h.io_sender());
395 std::thread::spawn(move || wait_child(id, handle, io_tx));
396 Ok(emitter)
397}
398
399fn wait_child(id: u64, handle: Arc<Mutex<Option<Child>>>, io_tx: Sender<IoTask>) {
403 loop {
404 std::thread::sleep(std::time::Duration::from_millis(20));
405 let status = {
406 let mut g = match handle.lock() {
407 Ok(g) => g,
408 Err(_) => return,
409 };
410 match g.as_mut() {
411 Some(child) => match child.try_wait() {
412 Ok(Some(status)) => {
413 *g = None;
414 Some(status.code())
415 }
416 Ok(None) => None,
417 Err(_) => {
418 *g = None;
419 Some(None)
420 }
421 },
422 None => return,
424 }
425 };
426 if let Some(code) = status {
427 let _ = io_tx.send(Box::new(move || on_child_exit(id, code)));
428 return;
429 }
430 }
431}
432
433fn on_child_exit(id: u64, code: Option<i32>) -> Result<(), String> {
436 let emitter = CHILDREN.with(|c| c.borrow().get(&id).map(|r| r.emitter.clone()));
437 let Some(emitter) = emitter else {
438 return Ok(());
439 };
440 let (code_val, null1, null2) = with_host(|h| {
441 let cv = code
442 .map(|c| Value::Float(c as f64))
443 .unwrap_or_else(|| h.null());
444 (cv, h.null(), h.null())
445 });
446 set_prop(&emitter, "exitCode", code_val.clone());
447 set_prop(&emitter, "killed", Value::Bool(true));
448 let ev_exit = with_host(|h| h.new_str("exit"));
449 let ev_close = with_host(|h| h.new_str("close"));
450 super::events::instance_call(&emitter, "emit", vec![ev_exit, code_val.clone(), null1])?;
451 super::events::instance_call(&emitter, "emit", vec![ev_close, code_val, null2])?;
452 CHILDREN.with(|c| c.borrow_mut().remove(&id));
453 with_host(|h| h.decr_handle());
454 let _ = with_host(|h| h.io_sender()).send(Box::new(|| Ok(())));
455 Ok(())
456}
457
458fn set_prop(recv: &Value, key: &str, val: Value) {
459 with_host(|h| {
460 if let Some(JsObj::Object(p)) = h.get_mut(recv) {
461 p.insert(key.to_string(), val);
462 }
463 });
464}
465
466pub fn instance_call(recv: &Value, method: &str, args: Vec<Value>) -> Result<Value, String> {
473 if super::events::METHODS.contains(&method) {
474 return super::events::instance_call(recv, method, args);
475 }
476 match method {
477 "kill" => Ok(Value::Bool(kill_child(recv))),
478 "send" => Ok(Value::Bool(false)),
480 "disconnect" => {
481 set_prop(recv, "connected", Value::Bool(false));
482 Ok(Value::Undef)
483 }
484 "ref" | "unref" => Ok(recv.clone()),
485 _ => Err(crate::host::type_error(&format!(
486 "child.{method} is not a function"
487 ))),
488 }
489}
490
491fn kill_child(recv: &Value) -> bool {
495 let id = with_host(|h| match h.get(recv) {
496 Some(JsObj::Object(p)) => p.get("@@childid").map(|v| h.to_number(v) as u64),
497 _ => None,
498 });
499 let Some(id) = id else { return false };
500 let handle = CHILDREN.with(|c| c.borrow().get(&id).map(|r| r.handle.clone()));
501 let Some(handle) = handle else { return false };
502 if let Ok(mut g) = handle.lock() {
503 if let Some(child) = g.as_mut() {
504 let _ = child.kill();
505 return true;
506 }
507 }
508 false
509}
510
511fn output_value(bytes: &[u8], encoding: Option<&str>) -> Value {
515 match encoding {
516 Some(enc) if !enc.eq_ignore_ascii_case("buffer") => {
517 with_host(|h| h.new_str(String::from_utf8_lossy(bytes).into_owned()))
518 }
519 _ => super::buffer::from_bytes(bytes),
520 }
521}
522
523fn arg_array(args: &[Value], i: usize) -> Vec<String> {
526 with_host(|h| match args.get(i).and_then(|v| h.get(v)) {
527 Some(crate::host::JsObj::Array(items)) => items.iter().map(|v| h.str_of(v)).collect(),
528 _ => Vec::new(),
529 })
530}
531
532fn opts_encoding(args: &[Value], i: usize) -> Option<String> {
535 with_host(|h| match args.get(i).and_then(|v| h.get(v)) {
536 Some(crate::host::JsObj::Object(p)) => p
537 .get("encoding")
538 .map(|v| h.str_of(v))
539 .filter(|s| !s.is_empty() && s != "undefined" && s != "null"),
540 _ => None,
541 })
542}