diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/flags.rs | 9 | ||||
-rw-r--r-- | src/isolate.rs | 5 | ||||
-rw-r--r-- | src/msg.fbs | 37 | ||||
-rw-r--r-- | src/ops.rs | 141 | ||||
-rw-r--r-- | src/permissions.rs | 14 | ||||
-rw-r--r-- | src/resources.rs | 88 |
6 files changed, 288 insertions, 6 deletions
diff --git a/src/flags.rs b/src/flags.rs index dbbe51684..fcc0d0461 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -25,6 +25,7 @@ pub struct DenoFlags { pub allow_write: bool, pub allow_net: bool, pub allow_env: bool, + pub allow_run: bool, pub types: bool, } @@ -93,10 +94,9 @@ fn set_recognized_flags( if matches.opt_present("allow-env") { flags.allow_env = true; } - // TODO: uncomment once https://github.com/denoland/deno/pull/1156 lands on master - // if matches.opt_present("allow-run") { - // flags.allow_run = true; - // } + if matches.opt_present("allow-run") { + flags.allow_run = true; + } if matches.opt_present("types") { flags.types = true; } @@ -126,6 +126,7 @@ pub fn set_flags( opts.optflag("", "allow-write", "Allow file system write access."); opts.optflag("", "allow-net", "Allow network access."); opts.optflag("", "allow-env", "Allow environment access."); + opts.optflag("", "allow-run", "Allow running subprocesses."); opts.optflag("", "recompile", "Force recompilation of TypeScript code."); opts.optflag("h", "help", "Print this message."); opts.optflag("D", "log-debug", "Log debug output."); diff --git a/src/isolate.rs b/src/isolate.rs index ccfb3453c..c02f4d6ce 100644 --- a/src/isolate.rs +++ b/src/isolate.rs @@ -89,6 +89,11 @@ impl IsolateState { perm.check_net(filename) } + pub fn check_run(&self) -> DenoResult<()> { + let mut perm = self.permissions.lock().unwrap(); + perm.check_run() + } + fn metrics_op_dispatched( &self, bytes_sent_control: u64, diff --git a/src/msg.fbs b/src/msg.fbs index 2a9c013a3..d6bbc220e 100644 --- a/src/msg.fbs +++ b/src/msg.fbs @@ -53,6 +53,10 @@ union Any { CwdRes, Metrics, MetricsRes, + Run, + RunRes, + RunStatus, + RunStatusRes } enum ErrorKind: byte { @@ -78,8 +82,8 @@ enum ErrorKind: byte { WriteZero, Other, UnexpectedEof, - BadResource, + CommandFailed, // url errors @@ -413,4 +417,35 @@ table MetricsRes { bytes_received: uint64; } +enum ProcessStdio: byte { Inherit, Piped, Null } + +table Run { + args: [string]; + cwd: string; + stdin: ProcessStdio; + stdout: ProcessStdio; + stderr: ProcessStdio; +} + +table RunRes { + rid: uint32; + pid: uint32; + // The following stdio rids are only valid if "Piped" was specified for the + // corresponding stdio stream. The caller MUST issue a close op for all valid + // stdio streams. + stdin_rid: uint32; + stdout_rid: uint32; + stderr_rid: uint32; +} + +table RunStatus { + rid: uint32; +} + +table RunStatusRes { + got_signal: bool; + exit_code: int; + exit_signal: int; +} + root_type Base; diff --git a/src/ops.rs b/src/ops.rs index 0e2c7e119..e08e67705 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -25,10 +25,13 @@ use resources::table_entries; use std; use std::fs; use std::net::{Shutdown, SocketAddr}; -#[cfg(any(unix))] +#[cfg(unix)] use std::os::unix::fs::PermissionsExt; +#[cfg(unix)] +use std::os::unix::process::ExitStatusExt; use std::path::Path; use std::path::PathBuf; +use std::process::Command; use std::str::FromStr; use std::sync::Arc; use std::time::UNIX_EPOCH; @@ -36,6 +39,7 @@ use std::time::{Duration, Instant}; use tokio; use tokio::net::TcpListener; use tokio::net::TcpStream; +use tokio_process::CommandExt; use tokio_threadpool; type OpResult = DenoResult<Buf>; @@ -100,6 +104,8 @@ pub fn dispatch( msg::Any::ReplReadline => op_repl_readline, msg::Any::ReplStart => op_repl_start, msg::Any::Resources => op_resources, + msg::Any::Run => op_run, + msg::Any::RunStatus => op_run_status, msg::Any::SetEnv => op_set_env, msg::Any::Shutdown => op_shutdown, msg::Any::Start => op_start, @@ -1352,3 +1358,136 @@ fn op_resources( }, )) } + +fn subprocess_stdio_map(v: msg::ProcessStdio) -> std::process::Stdio { + match v { + msg::ProcessStdio::Inherit => std::process::Stdio::inherit(), + msg::ProcessStdio::Piped => std::process::Stdio::piped(), + msg::ProcessStdio::Null => std::process::Stdio::null(), + } +} + +fn op_run( + state: &Arc<IsolateState>, + base: &msg::Base, + data: &'static mut [u8], +) -> Box<Op> { + assert!(base.sync()); + let cmd_id = base.cmd_id(); + + if let Err(e) = state.check_run() { + return odd_future(e); + } + + assert_eq!(data.len(), 0); + let inner = base.inner_as_run().unwrap(); + let args = inner.args().unwrap(); + let cwd = inner.cwd(); + + let mut cmd = Command::new(args.get(0)); + (1..args.len()).for_each(|i| { + let arg = args.get(i); + cmd.arg(arg); + }); + cwd.map(|d| cmd.current_dir(d)); + + cmd.stdin(subprocess_stdio_map(inner.stdin())); + cmd.stdout(subprocess_stdio_map(inner.stdout())); + cmd.stderr(subprocess_stdio_map(inner.stderr())); + + // Spawn the command. + let child = match cmd.spawn_async() { + Ok(v) => v, + Err(err) => { + return odd_future(err.into()); + } + }; + + let pid = child.id(); + let resources = resources::add_child(child); + + let mut res_args = msg::RunResArgs { + rid: resources.child_rid, + pid, + ..Default::default() + }; + + if let Some(stdin_rid) = resources.stdin_rid { + res_args.stdin_rid = stdin_rid; + } + if let Some(stdout_rid) = resources.stdout_rid { + res_args.stdout_rid = stdout_rid; + } + if let Some(stderr_rid) = resources.stderr_rid { + res_args.stderr_rid = stderr_rid; + } + + let builder = &mut FlatBufferBuilder::new(); + let inner = msg::RunRes::create(builder, &res_args); + ok_future(serialize_response( + cmd_id, + builder, + msg::BaseArgs { + inner: Some(inner.as_union_value()), + inner_type: msg::Any::RunRes, + ..Default::default() + }, + )) +} + +fn op_run_status( + state: &Arc<IsolateState>, + base: &msg::Base, + data: &'static mut [u8], +) -> Box<Op> { + assert_eq!(data.len(), 0); + let cmd_id = base.cmd_id(); + let inner = base.inner_as_run_status().unwrap(); + let rid = inner.rid(); + + if let Err(e) = state.check_run() { + return odd_future(e); + } + + let future = match resources::child_status(rid) { + Err(e) => { + return odd_future(e); + } + Ok(f) => f, + }; + + let future = future.and_then(move |run_status| { + 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(); + + let builder = &mut FlatBufferBuilder::new(); + let inner = msg::RunStatusRes::create( + builder, + &msg::RunStatusResArgs { + got_signal, + exit_code: code.unwrap_or(-1), + exit_signal: signal.unwrap_or(-1), + ..Default::default() + }, + ); + Ok(serialize_response( + cmd_id, + builder, + msg::BaseArgs { + inner: Some(inner.as_union_value()), + inner_type: msg::Any::RunStatusRes, + ..Default::default() + }, + )) + }); + Box::new(future) +} diff --git a/src/permissions.rs b/src/permissions.rs index 263c936fe..7ca13d44a 100644 --- a/src/permissions.rs +++ b/src/permissions.rs @@ -12,6 +12,7 @@ pub struct DenoPermissions { pub allow_write: bool, pub allow_net: bool, pub allow_env: bool, + pub allow_run: bool, } impl DenoPermissions { @@ -20,9 +21,22 @@ impl DenoPermissions { allow_write: flags.allow_write, allow_env: flags.allow_env, allow_net: flags.allow_net, + allow_run: flags.allow_run, } } + pub fn check_run(&mut self) -> DenoResult<()> { + if self.allow_run { + return Ok(()); + }; + // TODO get location (where access occurred) + let r = permission_prompt("Deno requests access to run a subprocess."); + if r.is_ok() { + self.allow_run = true; + } + r + } + pub fn check_write(&mut self, filename: &str) -> DenoResult<()> { if self.allow_write { return Ok(()); diff --git a/src/resources.rs b/src/resources.rs index 90b7ce772..36e0d9486 100644 --- a/src/resources.rs +++ b/src/resources.rs @@ -20,12 +20,14 @@ use tokio_write; use futures; use futures::future::{Either, FutureResult}; +use futures::Future; use futures::Poll; use hyper; use std; use std::collections::HashMap; use std::io::{Error, Read, Write}; use std::net::{Shutdown, SocketAddr}; +use std::process::ExitStatus; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; use std::sync::Mutex; @@ -33,6 +35,7 @@ use tokio; use tokio::io::{AsyncRead, AsyncWrite}; use tokio::net::TcpStream; use tokio_io; +use tokio_process; pub type ResourceId = u32; // Sometimes referred to RID. @@ -63,6 +66,10 @@ enum Repr { TcpStream(tokio::net::TcpStream), HttpBody(HttpBody), Repl(Repl), + Child(tokio_process::Child), + ChildStdin(tokio_process::ChildStdin), + ChildStdout(tokio_process::ChildStdout), + ChildStderr(tokio_process::ChildStderr), } pub fn table_entries() -> Vec<(u32, String)> { @@ -94,6 +101,10 @@ fn inspect_repr(repr: &Repr) -> String { Repr::TcpStream(_) => "tcpStream", Repr::HttpBody(_) => "httpBody", Repr::Repl(_) => "repl", + Repr::Child(_) => "child", + Repr::ChildStdin(_) => "childStdin", + Repr::ChildStdout(_) => "childStdout", + Repr::ChildStderr(_) => "childStderr", }; String::from(h_repr) @@ -160,6 +171,8 @@ impl AsyncRead for Resource { Repr::Stdin(ref mut f) => f.poll_read(buf), Repr::TcpStream(ref mut f) => f.poll_read(buf), Repr::HttpBody(ref mut f) => f.poll_read(buf), + Repr::ChildStdout(ref mut f) => f.poll_read(buf), + Repr::ChildStderr(ref mut f) => f.poll_read(buf), _ => panic!("Cannot read"), }, } @@ -187,6 +200,7 @@ impl AsyncWrite for Resource { Repr::Stdout(ref mut f) => f.poll_write(buf), Repr::Stderr(ref mut f) => f.poll_write(buf), Repr::TcpStream(ref mut f) => f.poll_write(buf), + Repr::ChildStdin(ref mut f) => f.poll_write(buf), _ => panic!("Cannot write"), }, } @@ -244,6 +258,80 @@ pub fn add_repl(repl: Repl) -> Resource { Resource { rid } } +pub struct ChildResources { + pub child_rid: ResourceId, + pub stdin_rid: Option<ResourceId>, + pub stdout_rid: Option<ResourceId>, + pub stderr_rid: Option<ResourceId>, +} + +pub fn add_child(mut c: tokio_process::Child) -> ChildResources { + let child_rid = new_rid(); + let mut tg = RESOURCE_TABLE.lock().unwrap(); + + let mut resources = ChildResources { + child_rid, + stdin_rid: None, + stdout_rid: None, + stderr_rid: None, + }; + + if c.stdin().is_some() { + let stdin = c.stdin().take().unwrap(); + let rid = new_rid(); + let r = tg.insert(rid, Repr::ChildStdin(stdin)); + assert!(r.is_none()); + resources.stdin_rid = Some(rid); + } + if c.stdout().is_some() { + let stdout = c.stdout().take().unwrap(); + let rid = new_rid(); + let r = tg.insert(rid, Repr::ChildStdout(stdout)); + assert!(r.is_none()); + resources.stdout_rid = Some(rid); + } + if c.stderr().is_some() { + let stderr = c.stderr().take().unwrap(); + let rid = new_rid(); + let r = tg.insert(rid, Repr::ChildStderr(stderr)); + assert!(r.is_none()); + resources.stderr_rid = Some(rid); + } + + let r = tg.insert(child_rid, Repr::Child(c)); + assert!(r.is_none()); + + return resources; +} + +pub struct ChildStatus { + rid: ResourceId, +} + +// Invert the dumbness that tokio_process causes by making Child itself a future. +impl Future for ChildStatus { + type Item = ExitStatus; + type Error = DenoError; + + fn poll(&mut self) -> Poll<ExitStatus, DenoError> { + let mut table = RESOURCE_TABLE.lock().unwrap(); + let maybe_repr = table.get_mut(&self.rid); + match maybe_repr { + Some(Repr::Child(ref mut child)) => child.poll().map_err(DenoError::from), + _ => Err(bad_resource()), + } + } +} + +pub fn child_status(rid: ResourceId) -> DenoResult<ChildStatus> { + let mut table = RESOURCE_TABLE.lock().unwrap(); + let maybe_repr = table.get_mut(&rid); + match maybe_repr { + Some(Repr::Child(ref mut _child)) => Ok(ChildStatus { rid }), + _ => Err(bad_resource()), + } +} + pub fn readline(rid: ResourceId, prompt: &str) -> DenoResult<String> { let mut table = RESOURCE_TABLE.lock().unwrap(); let maybe_repr = table.get_mut(&rid); |