// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. use super::io::{std_file_resource, StreamResource}; use crate::permissions::Permissions; use deno_core::error::bad_resource_id; use deno_core::error::type_error; use deno_core::error::AnyError; use deno_core::serde_json; use deno_core::serde_json::json; use deno_core::serde_json::Value; use deno_core::AsyncMutFuture; use deno_core::AsyncRefCell; use deno_core::BufVec; use deno_core::OpState; use deno_core::RcRef; use deno_core::Resource; use deno_core::ZeroCopyBuf; use serde::Deserialize; use std::borrow::Cow; use std::cell::RefCell; use std::rc::Rc; use tokio::process::Command; #[cfg(unix)] use std::os::unix::process::ExitStatusExt; pub fn init(rt: &mut deno_core::JsRuntime) { super::reg_json_sync(rt, "op_run", op_run); super::reg_json_async(rt, "op_run_status", op_run_status); super::reg_json_sync(rt, "op_kill", op_kill); } fn clone_file( state: &mut OpState, rid: u32, ) -> Result { std_file_resource(state, rid, move |r| match r { Ok(std_file) => std_file.try_clone().map_err(AnyError::from), Err(_) => Err(bad_resource_id()), }) } fn subprocess_stdio_map(s: &str) -> Result { match s { "inherit" => Ok(std::process::Stdio::inherit()), "piped" => Ok(std::process::Stdio::piped()), "null" => Ok(std::process::Stdio::null()), _ => Err(type_error("Invalid resource for stdio")), } } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct RunArgs { cmd: Vec, cwd: Option, env: Vec<(String, String)>, stdin: String, stdout: String, stderr: String, stdin_rid: u32, stdout_rid: u32, stderr_rid: u32, } struct ChildResource { child: AsyncRefCell, } impl Resource for ChildResource { fn name(&self) -> Cow { "child".into() } } impl ChildResource { fn borrow_mut(self: Rc) -> AsyncMutFuture { RcRef::map(self, |r| &r.child).borrow_mut() } } fn op_run( state: &mut OpState, args: Value, _zero_copy: &mut [ZeroCopyBuf], ) -> Result { let run_args: RunArgs = serde_json::from_value(args)?; state.borrow::().check_run()?; let args = run_args.cmd; let env = run_args.env; let cwd = run_args.cwd; let mut c = Command::new(args.get(0).unwrap()); (1..args.len()).for_each(|i| { let arg = args.get(i).unwrap(); c.arg(arg); }); cwd.map(|d| c.current_dir(d)); for (key, value) in &env { c.env(key, value); } // TODO: make this work with other resources, eg. sockets if !run_args.stdin.is_empty() { c.stdin(subprocess_stdio_map(run_args.stdin.as_ref())?); } else { let file = clone_file(state, run_args.stdin_rid)?; c.stdin(file); } if !run_args.stdout.is_empty() { c.stdout(subprocess_stdio_map(run_args.stdout.as_ref())?); } else { let file = clone_file(state, run_args.stdout_rid)?; c.stdout(file); } if !run_args.stderr.is_empty() { c.stderr(subprocess_stdio_map(run_args.stderr.as_ref())?); } else { let file = clone_file(state, run_args.stderr_rid)?; c.stderr(file); } // We want to kill child when it's closed c.kill_on_drop(true); // Spawn the command. let mut child = c.spawn()?; let pid = child.id(); let stdin_rid = match child.stdin.take() { Some(child_stdin) => { let rid = state .resource_table .add(StreamResource::child_stdin(child_stdin)); Some(rid) } None => None, }; let stdout_rid = match child.stdout.take() { Some(child_stdout) => { let rid = state .resource_table .add(StreamResource::child_stdout(child_stdout)); Some(rid) } None => None, }; let stderr_rid = match child.stderr.take() { Some(child_stderr) => { let rid = state .resource_table .add(StreamResource::child_stderr(child_stderr)); Some(rid) } None => None, }; let child_resource = ChildResource { child: AsyncRefCell::new(child), }; let child_rid = state.resource_table.add(child_resource); Ok(json!({ "rid": child_rid, "pid": pid, "stdinRid": stdin_rid, "stdoutRid": stdout_rid, "stderrRid": stderr_rid, })) } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct RunStatusArgs { rid: i32, } async fn op_run_status( state: Rc>, args: Value, _zero_copy: BufVec, ) -> Result { let args: RunStatusArgs = serde_json::from_value(args)?; let rid = args.rid as u32; { let s = state.borrow(); s.borrow::().check_run()?; } let resource = state .borrow_mut() .resource_table .get::(rid) .ok_or_else(bad_resource_id)?; let mut child = resource.borrow_mut().await; let run_status = child.wait().await?; let code = run_status.code(); #[cfg(unix)] let signal = run_status.signal(); #[cfg(not(unix))] let signal = None; code .or(signal) .expect("Should have either an exit code or a signal."); let got_signal = signal.is_some(); Ok(json!({ "gotSignal": got_signal, "exitCode": code.unwrap_or(-1), "exitSignal": signal.unwrap_or(-1), })) } #[cfg(not(unix))] const SIGINT: i32 = 2; #[cfg(not(unix))] const SIGKILL: i32 = 9; #[cfg(not(unix))] const SIGTERM: i32 = 15; #[cfg(not(unix))] use winapi::{ shared::minwindef::DWORD, um::{ handleapi::CloseHandle, processthreadsapi::{OpenProcess, TerminateProcess}, winnt::PROCESS_TERMINATE, }, }; #[cfg(unix)] pub fn kill(pid: i32, signo: i32) -> Result<(), AnyError> { use nix::sys::signal::{kill as unix_kill, Signal}; use nix::unistd::Pid; use std::convert::TryFrom; let sig = Signal::try_from(signo)?; unix_kill(Pid::from_raw(pid), Option::Some(sig)).map_err(AnyError::from) } #[cfg(not(unix))] pub fn kill(pid: i32, signal: i32) -> Result<(), AnyError> { use std::io::Error; match signal { SIGINT | SIGKILL | SIGTERM => { if pid <= 0 { return Err(type_error("unsupported pid")); } unsafe { let handle = OpenProcess(PROCESS_TERMINATE, 0, pid as DWORD); if handle.is_null() { return Err(Error::last_os_error().into()); } if TerminateProcess(handle, 1) == 0 { CloseHandle(handle); return Err(Error::last_os_error().into()); } if CloseHandle(handle) == 0 { return Err(Error::last_os_error().into()); } } } _ => { return Err(type_error("unsupported signal")); } } Ok(()) } #[derive(Deserialize)] struct KillArgs { pid: i32, signo: i32, } fn op_kill( state: &mut OpState, args: Value, _zero_copy: &mut [ZeroCopyBuf], ) -> Result { super::check_unstable(state, "Deno.kill"); state.borrow::().check_run()?; let args: KillArgs = serde_json::from_value(args)?; kill(args.pid, args.signo)?; Ok(json!({})) }