summaryrefslogtreecommitdiff
path: root/runtime/ops/process.rs
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/ops/process.rs')
-rw-r--r--runtime/ops/process.rs290
1 files changed, 290 insertions, 0 deletions
diff --git a/runtime/ops/process.rs b/runtime/ops/process.rs
new file mode 100644
index 000000000..67b3d0761
--- /dev/null
+++ b/runtime/ops/process.rs
@@ -0,0 +1,290 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+use super::io::{std_file_resource, StreamResource, StreamResourceHolder};
+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::futures::future::poll_fn;
+use deno_core::futures::future::FutureExt;
+use deno_core::serde_json;
+use deno_core::serde_json::json;
+use deno_core::serde_json::Value;
+use deno_core::BufVec;
+use deno_core::OpState;
+use deno_core::ZeroCopyBuf;
+use serde::Deserialize;
+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::fs::File, AnyError> {
+ 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<std::process::Stdio, AnyError> {
+ 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<String>,
+ cwd: Option<String>,
+ env: Vec<(String, String)>,
+ stdin: String,
+ stdout: String,
+ stderr: String,
+ stdin_rid: u32,
+ stdout_rid: u32,
+ stderr_rid: u32,
+}
+
+struct ChildResource {
+ child: tokio::process::Child,
+}
+
+fn op_run(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: &mut [ZeroCopyBuf],
+) -> Result<Value, AnyError> {
+ let run_args: RunArgs = serde_json::from_value(args)?;
+ state.borrow::<Permissions>().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(
+ "childStdin",
+ Box::new(StreamResourceHolder::new(StreamResource::ChildStdin(
+ child_stdin,
+ ))),
+ );
+ Some(rid)
+ }
+ None => None,
+ };
+
+ let stdout_rid = match child.stdout.take() {
+ Some(child_stdout) => {
+ let rid = state.resource_table.add(
+ "childStdout",
+ Box::new(StreamResourceHolder::new(StreamResource::ChildStdout(
+ child_stdout,
+ ))),
+ );
+ Some(rid)
+ }
+ None => None,
+ };
+
+ let stderr_rid = match child.stderr.take() {
+ Some(child_stderr) => {
+ let rid = state.resource_table.add(
+ "childStderr",
+ Box::new(StreamResourceHolder::new(StreamResource::ChildStderr(
+ child_stderr,
+ ))),
+ );
+ Some(rid)
+ }
+ None => None,
+ };
+
+ let child_resource = ChildResource { child };
+ let child_rid = state.resource_table.add("child", Box::new(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<RefCell<OpState>>,
+ args: Value,
+ _zero_copy: BufVec,
+) -> Result<Value, AnyError> {
+ let args: RunStatusArgs = serde_json::from_value(args)?;
+ let rid = args.rid as u32;
+
+ {
+ let s = state.borrow();
+ s.borrow::<Permissions>().check_run()?;
+ }
+
+ let run_status = poll_fn(|cx| {
+ let mut state = state.borrow_mut();
+ let child_resource = state
+ .resource_table
+ .get_mut::<ChildResource>(rid)
+ .ok_or_else(bad_resource_id)?;
+ let child = &mut child_resource.child;
+ child.poll_unpin(cx).map_err(AnyError::from)
+ })
+ .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<Value, AnyError> {
+ super::check_unstable(state, "Deno.kill");
+ state.borrow::<Permissions>().check_run()?;
+
+ let args: KillArgs = serde_json::from_value(args)?;
+ kill(args.pid, args.signo)?;
+ Ok(json!({}))
+}