1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-01 09:24:20 -04:00
denoland-deno/runtime/ops/process.rs
Bartek Iwańczuk 6984b63f2f
refactor: rewrite ops to use ResourceTable2 (#8512)
This commit migrates all ops to use new resource table
and "AsyncRefCell".

Old implementation of resource table was completely 
removed and all code referencing it was updated to use
new system.
2020-12-16 17:14:12 +01:00

294 lines
6.9 KiB
Rust

// Copyright 2018-2020 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::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: AsyncRefCell<tokio::process::Child>,
}
impl Resource for ChildResource {
fn name(&self) -> Cow<str> {
"child".into()
}
}
impl ChildResource {
fn borrow_mut(self: Rc<Self>) -> AsyncMutFuture<tokio::process::Child> {
RcRef::map(self, |r| &r.child).borrow_mut()
}
}
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(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<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 resource = state
.borrow_mut()
.resource_table
.get::<ChildResource>(rid)
.ok_or_else(bad_resource_id)?;
let mut child = resource.borrow_mut().await;
let run_status = (&mut *child).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!({}))
}