mirror of
https://github.com/denoland/deno.git
synced 2024-11-01 09:24:20 -04:00
6984b63f2f
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.
294 lines
6.9 KiB
Rust
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!({}))
|
|
}
|