0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-02-02 04:38:21 -05:00
denoland-deno/cli/ops/process.rs
Bartek Iwańczuk 4e1abb4f3a
refactor: use OpError instead of ErrBox for errors in ops (#4058)
To better reflect changes in error types in JS from #3662 this PR changes 
default error type used in ops from "ErrBox" to "OpError".

"OpError" is a type that can be sent over to JSON; it has all 
information needed to construct error in JavaScript. That
made "GetErrorKind" trait useless and so it was removed altogether.

To provide compatibility with previous use of "ErrBox" an implementation of
"From<ErrBox> for OpError" was added, however, it is an escape hatch and
ops implementors should strive to use "OpError" directly.
2020-02-23 14:51:29 -05:00

260 lines
6 KiB
Rust

// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
use super::dispatch_json::{Deserialize, JsonOp, Value};
use super::io::StreamResource;
use crate::op_error::OpError;
use crate::ops::json_op;
use crate::signal::kill;
use crate::state::State;
use deno_core::*;
use futures;
use futures::future::FutureExt;
use futures::future::TryFutureExt;
use std;
use std::convert::From;
use std::future::Future;
use std::pin::Pin;
use std::process::ExitStatus;
use std::task::Context;
use std::task::Poll;
use tokio::process::Command;
#[cfg(unix)]
use std::os::unix::process::ExitStatusExt;
pub fn init(i: &mut Isolate, s: &State) {
i.register_op("run", s.core_op(json_op(s.stateful_op(op_run))));
i.register_op(
"run_status",
s.core_op(json_op(s.stateful_op(op_run_status))),
);
i.register_op("kill", s.core_op(json_op(s.stateful_op(op_kill))));
}
fn clone_file(rid: u32, state: &State) -> Result<std::fs::File, OpError> {
let mut state = state.borrow_mut();
let repr = state
.resource_table
.get_mut::<StreamResource>(rid)
.ok_or_else(OpError::bad_resource)?;
let file = match repr {
StreamResource::FsFile(ref mut file) => file,
_ => return Err(OpError::bad_resource()),
};
let tokio_file = futures::executor::block_on(file.try_clone())?;
let std_file = futures::executor::block_on(tokio_file.into_std());
Ok(std_file)
}
fn subprocess_stdio_map(s: &str) -> std::process::Stdio {
match s {
"inherit" => std::process::Stdio::inherit(),
"piped" => std::process::Stdio::piped(),
"null" => std::process::Stdio::null(),
_ => unreachable!(),
}
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct RunArgs {
args: 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: &State,
args: Value,
_zero_copy: Option<ZeroCopyBuf>,
) -> Result<JsonOp, OpError> {
let run_args: RunArgs = serde_json::from_value(args)?;
state.check_run()?;
let state_ = state.clone();
let args = run_args.args;
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
let stdin_rid = run_args.stdin_rid;
if stdin_rid > 0 {
let file = clone_file(stdin_rid, &state_)?;
c.stdin(file);
} else {
c.stdin(subprocess_stdio_map(run_args.stdin.as_ref()));
}
let stdout_rid = run_args.stdout_rid;
if stdout_rid > 0 {
let file = clone_file(stdout_rid, &state_)?;
c.stdout(file);
} else {
c.stdout(subprocess_stdio_map(run_args.stdout.as_ref()));
}
let stderr_rid = run_args.stderr_rid;
if stderr_rid > 0 {
let file = clone_file(stderr_rid, &state_)?;
c.stderr(file);
} else {
c.stderr(subprocess_stdio_map(run_args.stderr.as_ref()));
}
// 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 mut state = state_.borrow_mut();
let table = &mut state.resource_table;
let stdin_rid = match child.stdin.take() {
Some(child_stdin) => {
let rid = table.add(
"childStdin",
Box::new(StreamResource::ChildStdin(child_stdin)),
);
Some(rid)
}
None => None,
};
let stdout_rid = match child.stdout.take() {
Some(child_stdout) => {
let rid = table.add(
"childStdout",
Box::new(StreamResource::ChildStdout(child_stdout)),
);
Some(rid)
}
None => None,
};
let stderr_rid = match child.stderr.take() {
Some(child_stderr) => {
let rid = table.add(
"childStderr",
Box::new(StreamResource::ChildStderr(child_stderr)),
);
Some(rid)
}
None => None,
};
let child_resource = ChildResource { child };
let child_rid = table.add("child", Box::new(child_resource));
Ok(JsonOp::Sync(json!({
"rid": child_rid,
"pid": pid,
"stdinRid": stdin_rid,
"stdoutRid": stdout_rid,
"stderrRid": stderr_rid,
})))
}
pub struct ChildStatus {
rid: ResourceId,
state: State,
}
impl Future for ChildStatus {
type Output = Result<ExitStatus, OpError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
let inner = self.get_mut();
let mut state = inner.state.borrow_mut();
let child_resource = state
.resource_table
.get_mut::<ChildResource>(inner.rid)
.ok_or_else(OpError::bad_resource)?;
let child = &mut child_resource.child;
child.map_err(OpError::from).poll_unpin(cx)
}
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct RunStatusArgs {
rid: i32,
}
fn op_run_status(
state: &State,
args: Value,
_zero_copy: Option<ZeroCopyBuf>,
) -> Result<JsonOp, OpError> {
let args: RunStatusArgs = serde_json::from_value(args)?;
let rid = args.rid as u32;
state.check_run()?;
let future = ChildStatus {
rid,
state: state.clone(),
};
let future = async move {
let run_status = future.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),
}))
};
Ok(JsonOp::Async(future.boxed_local()))
}
#[derive(Deserialize)]
struct KillArgs {
pid: i32,
signo: i32,
}
fn op_kill(
state: &State,
args: Value,
_zero_copy: Option<ZeroCopyBuf>,
) -> Result<JsonOp, OpError> {
state.check_run()?;
let args: KillArgs = serde_json::from_value(args)?;
kill(args.pid, args.signo)?;
Ok(JsonOp::Sync(json!({})))
}