diff options
author | Bartek IwaĆczuk <biwanczuk@gmail.com> | 2020-12-13 19:45:53 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-12-13 19:45:53 +0100 |
commit | 2e74f164b6dcf0ecbf8dd38fba9fae550d784bd0 (patch) | |
tree | 61abe8e09d5331ace5d9de529f0e2737a8e05dbb /runtime/ops/process.rs | |
parent | 84ef9bd21fb48fb6b5fbc8dafc3de9f361bade3b (diff) |
refactor: deno_runtime crate (#8640)
This commit moves Deno JS runtime, ops, permissions and
inspector implementation to new "deno_runtime" crate located
in "runtime/" directory.
Details in "runtime/README.md".
Co-authored-by: Ryan Dahl <ry@tinyclouds.org>
Diffstat (limited to 'runtime/ops/process.rs')
-rw-r--r-- | runtime/ops/process.rs | 290 |
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!({})) +} |