diff options
Diffstat (limited to 'cli/ops')
-rw-r--r-- | cli/ops/compiler.rs | 99 | ||||
-rw-r--r-- | cli/ops/dispatch_json.rs | 111 | ||||
-rw-r--r-- | cli/ops/dispatch_minimal.rs | 110 | ||||
-rw-r--r-- | cli/ops/errors.rs | 56 | ||||
-rw-r--r-- | cli/ops/fetch.rs | 73 | ||||
-rw-r--r-- | cli/ops/files.rs | 136 | ||||
-rw-r--r-- | cli/ops/fs.rs | 525 | ||||
-rw-r--r-- | cli/ops/io.rs | 46 | ||||
-rw-r--r-- | cli/ops/metrics.rs | 21 | ||||
-rw-r--r-- | cli/ops/mod.rs | 25 | ||||
-rw-r--r-- | cli/ops/net.rs | 151 | ||||
-rw-r--r-- | cli/ops/os.rs | 157 | ||||
-rw-r--r-- | cli/ops/performance.rs | 30 | ||||
-rw-r--r-- | cli/ops/permissions.rs | 44 | ||||
-rw-r--r-- | cli/ops/process.rs | 157 | ||||
-rw-r--r-- | cli/ops/random.rs | 24 | ||||
-rw-r--r-- | cli/ops/repl.rs | 50 | ||||
-rw-r--r-- | cli/ops/resources.rs | 14 | ||||
-rw-r--r-- | cli/ops/timers.rs | 42 | ||||
-rw-r--r-- | cli/ops/tls.rs | 76 | ||||
-rw-r--r-- | cli/ops/workers.rs | 227 |
21 files changed, 2174 insertions, 0 deletions
diff --git a/cli/ops/compiler.rs b/cli/ops/compiler.rs new file mode 100644 index 000000000..4228842dd --- /dev/null +++ b/cli/ops/compiler.rs @@ -0,0 +1,99 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +use super::dispatch_json::{Deserialize, JsonOp, Value}; +use crate::futures::future::join_all; +use crate::futures::Future; +use crate::state::ThreadSafeState; +use deno::*; + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct CacheArgs { + module_id: String, + contents: String, + extension: String, +} + +pub fn op_cache( + state: &ThreadSafeState, + args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: CacheArgs = serde_json::from_value(args)?; + + let module_specifier = ModuleSpecifier::resolve_url(&args.module_id) + .expect("Should be valid module specifier"); + + state.ts_compiler.cache_compiler_output( + &module_specifier, + &args.extension, + &args.contents, + )?; + + Ok(JsonOp::Sync(json!({}))) +} + +#[derive(Deserialize)] +struct FetchSourceFilesArgs { + specifiers: Vec<String>, + referrer: String, +} + +pub fn op_fetch_source_files( + state: &ThreadSafeState, + args: Value, + _data: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: FetchSourceFilesArgs = serde_json::from_value(args)?; + + // TODO(ry) Maybe a security hole. Only the compiler worker should have access + // to this. Need a test to demonstrate the hole. + let is_dyn_import = false; + + let mut futures = vec![]; + for specifier in &args.specifiers { + let resolved_specifier = + state.resolve(specifier, &args.referrer, false, is_dyn_import)?; + let fut = state + .file_fetcher + .fetch_source_file_async(&resolved_specifier); + futures.push(fut); + } + + let future = join_all(futures) + .map_err(ErrBox::from) + .and_then(move |files| { + let res = files + .into_iter() + .map(|file| { + json!({ + "url": file.url.to_string(), + "filename": file.filename.to_str().unwrap(), + "mediaType": file.media_type as i32, + "sourceCode": String::from_utf8(file.source_code).unwrap(), + }) + }) + .collect(); + + futures::future::ok(res) + }); + + Ok(JsonOp::Async(Box::new(future))) +} + +#[derive(Deserialize)] +struct FetchAssetArgs { + name: String, +} + +pub fn op_fetch_asset( + _state: &ThreadSafeState, + args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: FetchAssetArgs = serde_json::from_value(args)?; + if let Some(source_code) = crate::js::get_asset(&args.name) { + Ok(JsonOp::Sync(json!(source_code))) + } else { + panic!("op_fetch_asset bad asset {}", args.name) + } +} diff --git a/cli/ops/dispatch_json.rs b/cli/ops/dispatch_json.rs new file mode 100644 index 000000000..3a8faf2a8 --- /dev/null +++ b/cli/ops/dispatch_json.rs @@ -0,0 +1,111 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +use crate::tokio_util; +use deno::*; +use futures::Future; +use futures::Poll; +pub use serde_derive::Deserialize; +use serde_json::json; +pub use serde_json::Value; + +pub type AsyncJsonOp = Box<dyn Future<Item = Value, Error = ErrBox> + Send>; + +pub enum JsonOp { + Sync(Value), + Async(AsyncJsonOp), +} + +fn json_err(err: ErrBox) -> Value { + use crate::deno_error::GetErrorKind; + json!({ + "message": err.to_string(), + "kind": err.kind() as u32, + }) +} + +fn serialize_result( + promise_id: Option<u64>, + result: Result<Value, ErrBox>, +) -> Buf { + let value = match result { + Ok(v) => json!({ "ok": v, "promiseId": promise_id }), + Err(err) => json!({ "err": json_err(err), "promiseId": promise_id }), + }; + let mut vec = serde_json::to_vec(&value).unwrap(); + debug!("JSON response pre-align, len={}", vec.len()); + // Align to 32bit word, padding with the space character. + vec.resize((vec.len() + 3usize) & !3usize, b' '); + vec.into_boxed_slice() +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct AsyncArgs { + promise_id: Option<u64>, +} + +pub fn json_op<D>(d: D) -> impl Fn(&[u8], Option<PinnedBuf>) -> CoreOp +where + D: Fn(Value, Option<PinnedBuf>) -> Result<JsonOp, ErrBox>, +{ + move |control: &[u8], zero_copy: Option<PinnedBuf>| { + let async_args: AsyncArgs = serde_json::from_slice(control).unwrap(); + let promise_id = async_args.promise_id; + let is_sync = promise_id.is_none(); + + let result = serde_json::from_slice(control) + .map_err(ErrBox::from) + .and_then(|args| d(args, zero_copy)); + + // Convert to CoreOp + match result { + Ok(JsonOp::Sync(sync_value)) => { + assert!(promise_id.is_none()); + CoreOp::Sync(serialize_result(promise_id, Ok(sync_value))) + } + Ok(JsonOp::Async(fut)) => { + assert!(promise_id.is_some()); + let fut2 = Box::new(fut.then(move |result| -> Result<Buf, ()> { + Ok(serialize_result(promise_id, result)) + })); + CoreOp::Async(fut2) + } + Err(sync_err) => { + let buf = serialize_result(promise_id, Err(sync_err)); + if is_sync { + CoreOp::Sync(buf) + } else { + CoreOp::Async(Box::new(futures::future::ok(buf))) + } + } + } + } +} + +// This is just type conversion. Implement From trait? +// See https://github.com/tokio-rs/tokio/blob/ffd73a64e7ec497622b7f939e38017afe7124dc4/tokio-fs/src/lib.rs#L76-L85 +fn convert_blocking_json<F>(f: F) -> Poll<Value, ErrBox> +where + F: FnOnce() -> Result<Value, ErrBox>, +{ + use futures::Async::*; + match tokio_threadpool::blocking(f) { + Ok(Ready(Ok(v))) => Ok(Ready(v)), + Ok(Ready(Err(err))) => Err(err), + Ok(NotReady) => Ok(NotReady), + Err(err) => panic!("blocking error {}", err), + } +} + +pub fn blocking_json<F>(is_sync: bool, f: F) -> Result<JsonOp, ErrBox> +where + F: 'static + Send + FnOnce() -> Result<Value, ErrBox>, +{ + if is_sync { + Ok(JsonOp::Sync(f()?)) + } else { + Ok(JsonOp::Async(Box::new(futures::sync::oneshot::spawn( + tokio_util::poll_fn(move || convert_blocking_json(f)), + &tokio_executor::DefaultExecutor::current(), + )))) + } +} diff --git a/cli/ops/dispatch_minimal.rs b/cli/ops/dispatch_minimal.rs new file mode 100644 index 000000000..618a040bf --- /dev/null +++ b/cli/ops/dispatch_minimal.rs @@ -0,0 +1,110 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +// Do not add flatbuffer dependencies to this module. +//! Connects to js/dispatch_minimal.ts sendAsyncMinimal This acts as a faster +//! alternative to flatbuffers using a very simple list of int32s to lay out +//! messages. The first i32 is used to determine if a message a flatbuffer +//! message or a "minimal" message. +use deno::Buf; +use deno::CoreOp; +use deno::ErrBox; +use deno::Op; +use deno::PinnedBuf; +use futures::Future; + +pub type MinimalOp = dyn Future<Item = i32, Error = ErrBox> + Send; +pub type Dispatcher = fn(i32, Option<PinnedBuf>) -> Box<MinimalOp>; + +#[derive(Copy, Clone, Debug, PartialEq)] +// This corresponds to RecordMinimal on the TS side. +pub struct Record { + pub promise_id: i32, + pub arg: i32, + pub result: i32, +} + +impl Into<Buf> for Record { + fn into(self) -> Buf { + let vec = vec![self.promise_id, self.arg, self.result]; + let buf32 = vec.into_boxed_slice(); + let ptr = Box::into_raw(buf32) as *mut [u8; 3 * 4]; + unsafe { Box::from_raw(ptr) } + } +} + +pub fn parse_min_record(bytes: &[u8]) -> Option<Record> { + if bytes.len() % std::mem::size_of::<i32>() != 0 { + return None; + } + let p = bytes.as_ptr(); + #[allow(clippy::cast_ptr_alignment)] + let p32 = p as *const i32; + let s = unsafe { std::slice::from_raw_parts(p32, bytes.len() / 4) }; + + if s.len() != 3 { + return None; + } + let ptr = s.as_ptr(); + let ints = unsafe { std::slice::from_raw_parts(ptr, 3) }; + Some(Record { + promise_id: ints[0], + arg: ints[1], + result: ints[2], + }) +} + +#[test] +fn test_parse_min_record() { + let buf = vec![1, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0]; + assert_eq!( + parse_min_record(&buf), + Some(Record { + promise_id: 1, + arg: 3, + result: 4, + }) + ); + + let buf = vec![]; + assert_eq!(parse_min_record(&buf), None); + + let buf = vec![5]; + assert_eq!(parse_min_record(&buf), None); +} + +pub fn minimal_op( + d: Dispatcher, +) -> impl Fn(&[u8], Option<PinnedBuf>) -> CoreOp { + move |control: &[u8], zero_copy: Option<PinnedBuf>| { + let mut record = parse_min_record(control).unwrap(); + let is_sync = record.promise_id == 0; + let rid = record.arg; + let min_op = d(rid, zero_copy); + + // Convert to CoreOp + let fut = Box::new(min_op.then(move |result| -> Result<Buf, ()> { + match result { + Ok(r) => { + record.result = r; + } + Err(err) => { + // TODO(ry) The dispatch_minimal doesn't properly pipe errors back to + // the caller. + debug!("swallowed err {}", err); + record.result = -1; + } + } + Ok(record.into()) + })); + + if is_sync { + // Warning! Possible deadlocks can occur if we try to wait for a future + // while in a future. The safe but expensive alternative is to use + // tokio_util::block_on. + // This block is only exercised for readSync and writeSync, which I think + // works since they're simple polling futures. + Op::Sync(fut.wait().unwrap()) + } else { + Op::Async(fut) + } + } +} diff --git a/cli/ops/errors.rs b/cli/ops/errors.rs new file mode 100644 index 000000000..cd21a3880 --- /dev/null +++ b/cli/ops/errors.rs @@ -0,0 +1,56 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +use super::dispatch_json::{Deserialize, JsonOp, Value}; +use crate::fmt_errors::JSError; +use crate::source_maps::get_orig_position; +use crate::source_maps::CachedMaps; +use crate::state::ThreadSafeState; +use deno::*; +use std::collections::HashMap; + +#[derive(Deserialize)] +struct FormatErrorArgs { + error: String, +} + +pub fn op_format_error( + state: &ThreadSafeState, + args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: FormatErrorArgs = serde_json::from_value(args)?; + let error = JSError::from_json(&args.error, &state.ts_compiler); + + Ok(JsonOp::Sync(json!({ + "error": error.to_string(), + }))) +} + +#[derive(Deserialize)] +struct ApplySourceMap { + filename: String, + line: i32, + column: i32, +} + +pub fn op_apply_source_map( + state: &ThreadSafeState, + args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: ApplySourceMap = serde_json::from_value(args)?; + + let mut mappings_map: CachedMaps = HashMap::new(); + let (orig_filename, orig_line, orig_column) = get_orig_position( + args.filename, + args.line.into(), + args.column.into(), + &mut mappings_map, + &state.ts_compiler, + ); + + Ok(JsonOp::Sync(json!({ + "filename": orig_filename.to_string(), + "line": orig_line as u32, + "column": orig_column as u32, + }))) +} diff --git a/cli/ops/fetch.rs b/cli/ops/fetch.rs new file mode 100644 index 000000000..f69065f1d --- /dev/null +++ b/cli/ops/fetch.rs @@ -0,0 +1,73 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +use super::dispatch_json::{Deserialize, JsonOp, Value}; +use crate::http_util::get_client; +use crate::resources; +use crate::state::ThreadSafeState; +use deno::*; +use http::header::HeaderName; +use http::header::HeaderValue; +use http::Method; +use hyper; +use hyper::rt::Future; +use std; +use std::convert::From; + +#[derive(Deserialize)] +struct FetchArgs { + method: Option<String>, + url: String, + headers: Vec<(String, String)>, +} + +pub fn op_fetch( + state: &ThreadSafeState, + args: Value, + data: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: FetchArgs = serde_json::from_value(args)?; + let url = args.url; + + let client = get_client(); + + let method = match args.method { + Some(method_str) => Method::from_bytes(method_str.as_bytes())?, + None => Method::GET, + }; + + let url_ = url::Url::parse(&url).map_err(ErrBox::from)?; + state.check_net_url(&url_)?; + + let mut request = client.request(method, url_); + + if let Some(buf) = data { + request = request.body(Vec::from(&*buf)); + } + + for (key, value) in args.headers { + let name = HeaderName::from_bytes(key.as_bytes()).unwrap(); + let v = HeaderValue::from_str(&value).unwrap(); + request = request.header(name, v); + } + debug!("Before fetch {}", url); + let future = request.send().map_err(ErrBox::from).and_then(move |res| { + let status = res.status(); + let mut res_headers = Vec::new(); + for (key, val) in res.headers().iter() { + res_headers.push((key.to_string(), val.to_str().unwrap().to_owned())); + } + + let body = res.into_body(); + let body_resource = resources::add_reqwest_body(body); + + let json_res = json!({ + "bodyRid": body_resource.rid, + "status": status.as_u16(), + "statusText": status.canonical_reason().unwrap_or(""), + "headers": res_headers + }); + + futures::future::ok(json_res) + }); + + Ok(JsonOp::Async(Box::new(future))) +} diff --git a/cli/ops/files.rs b/cli/ops/files.rs new file mode 100644 index 000000000..01abff3a9 --- /dev/null +++ b/cli/ops/files.rs @@ -0,0 +1,136 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +use super::dispatch_json::{Deserialize, JsonOp, Value}; +use crate::fs as deno_fs; +use crate::resources; +use crate::state::ThreadSafeState; +use deno::*; +use futures::Future; +use std; +use std::convert::From; +use tokio; + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct OpenArgs { + promise_id: Option<u64>, + filename: String, + mode: String, +} + +pub fn op_open( + state: &ThreadSafeState, + args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: OpenArgs = serde_json::from_value(args)?; + let (filename, filename_) = deno_fs::resolve_from_cwd(&args.filename)?; + let mode = args.mode.as_ref(); + + let mut open_options = tokio::fs::OpenOptions::new(); + + match mode { + "r" => { + open_options.read(true); + } + "r+" => { + open_options.read(true).write(true); + } + "w" => { + open_options.create(true).write(true).truncate(true); + } + "w+" => { + open_options + .read(true) + .create(true) + .write(true) + .truncate(true); + } + "a" => { + open_options.create(true).append(true); + } + "a+" => { + open_options.read(true).create(true).append(true); + } + "x" => { + open_options.create_new(true).write(true); + } + "x+" => { + open_options.create_new(true).read(true).write(true); + } + &_ => { + panic!("Unknown file open mode."); + } + } + + match mode { + "r" => { + state.check_read(&filename_)?; + } + "w" | "a" | "x" => { + state.check_write(&filename_)?; + } + &_ => { + state.check_read(&filename_)?; + state.check_write(&filename_)?; + } + } + + let is_sync = args.promise_id.is_none(); + let op = open_options.open(filename).map_err(ErrBox::from).and_then( + move |fs_file| { + let resource = resources::add_fs_file(fs_file); + futures::future::ok(json!(resource.rid)) + }, + ); + + if is_sync { + let buf = op.wait()?; + Ok(JsonOp::Sync(buf)) + } else { + Ok(JsonOp::Async(Box::new(op))) + } +} + +#[derive(Deserialize)] +struct CloseArgs { + rid: i32, +} + +pub fn op_close( + _state: &ThreadSafeState, + args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: CloseArgs = serde_json::from_value(args)?; + + let resource = resources::lookup(args.rid as u32)?; + resource.close(); + Ok(JsonOp::Sync(json!({}))) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct SeekArgs { + promise_id: Option<u64>, + rid: i32, + offset: i32, + whence: i32, +} + +pub fn op_seek( + _state: &ThreadSafeState, + args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: SeekArgs = serde_json::from_value(args)?; + + let resource = resources::lookup(args.rid as u32)?; + let op = resources::seek(resource, args.offset, args.whence as u32) + .and_then(move |_| futures::future::ok(json!({}))); + if args.promise_id.is_none() { + let buf = op.wait()?; + Ok(JsonOp::Sync(buf)) + } else { + Ok(JsonOp::Async(Box::new(op))) + } +} diff --git a/cli/ops/fs.rs b/cli/ops/fs.rs new file mode 100644 index 000000000..c549bef32 --- /dev/null +++ b/cli/ops/fs.rs @@ -0,0 +1,525 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +// Some deserializer fields are only used on Unix and Windows build fails without it +#![allow(dead_code)] +use super::dispatch_json::{blocking_json, Deserialize, JsonOp, Value}; +use crate::deno_error::DenoError; +use crate::deno_error::ErrorKind; +use crate::fs as deno_fs; +use crate::state::ThreadSafeState; +use deno::*; +use remove_dir_all::remove_dir_all; +use std::convert::From; +use std::fs; +use std::path::PathBuf; +use std::time::UNIX_EPOCH; + +#[cfg(unix)] +use std::os::unix::fs::PermissionsExt; + +#[derive(Deserialize)] +struct ChdirArgs { + directory: String, +} + +pub fn op_chdir( + _state: &ThreadSafeState, + args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: ChdirArgs = serde_json::from_value(args)?; + std::env::set_current_dir(&args.directory)?; + Ok(JsonOp::Sync(json!({}))) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct MkdirArgs { + promise_id: Option<u64>, + path: String, + recursive: bool, + mode: u32, +} + +pub fn op_mkdir( + state: &ThreadSafeState, + args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: MkdirArgs = serde_json::from_value(args)?; + let (path, path_) = deno_fs::resolve_from_cwd(args.path.as_ref())?; + + state.check_write(&path_)?; + + let is_sync = args.promise_id.is_none(); + blocking_json(is_sync, move || { + debug!("op_mkdir {}", path_); + deno_fs::mkdir(&path, args.mode, args.recursive)?; + Ok(json!({})) + }) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct ChmodArgs { + promise_id: Option<u64>, + path: String, + mode: u32, +} + +pub fn op_chmod( + state: &ThreadSafeState, + args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: ChmodArgs = serde_json::from_value(args)?; + let (path, path_) = deno_fs::resolve_from_cwd(args.path.as_ref())?; + + state.check_write(&path_)?; + + let is_sync = args.promise_id.is_none(); + blocking_json(is_sync, move || { + debug!("op_chmod {}", &path_); + // Still check file/dir exists on windows + let _metadata = fs::metadata(&path)?; + #[cfg(any(unix))] + { + let mut permissions = _metadata.permissions(); + permissions.set_mode(args.mode); + fs::set_permissions(&path, permissions)?; + } + Ok(json!({})) + }) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct ChownArgs { + promise_id: Option<u64>, + path: String, + uid: u32, + gid: u32, +} + +pub fn op_chown( + state: &ThreadSafeState, + args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: ChownArgs = serde_json::from_value(args)?; + + state.check_write(&args.path)?; + + let is_sync = args.promise_id.is_none(); + blocking_json(is_sync, move || { + debug!("op_chown {}", &args.path); + match deno_fs::chown(args.path.as_ref(), args.uid, args.gid) { + Ok(_) => Ok(json!({})), + Err(e) => Err(e), + } + }) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct RemoveArgs { + promise_id: Option<u64>, + path: String, + recursive: bool, +} + +pub fn op_remove( + state: &ThreadSafeState, + args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: RemoveArgs = serde_json::from_value(args)?; + let (path, path_) = deno_fs::resolve_from_cwd(args.path.as_ref())?; + let recursive = args.recursive; + + state.check_write(&path_)?; + + let is_sync = args.promise_id.is_none(); + blocking_json(is_sync, move || { + debug!("op_remove {}", path.display()); + let metadata = fs::metadata(&path)?; + if metadata.is_file() { + fs::remove_file(&path)?; + } else if recursive { + remove_dir_all(&path)?; + } else { + fs::remove_dir(&path)?; + } + Ok(json!({})) + }) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct CopyFileArgs { + promise_id: Option<u64>, + from: String, + to: String, +} + +pub fn op_copy_file( + state: &ThreadSafeState, + args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: CopyFileArgs = serde_json::from_value(args)?; + + let (from, from_) = deno_fs::resolve_from_cwd(args.from.as_ref())?; + let (to, to_) = deno_fs::resolve_from_cwd(args.to.as_ref())?; + + state.check_read(&from_)?; + state.check_write(&to_)?; + + debug!("op_copy_file {} {}", from.display(), to.display()); + let is_sync = args.promise_id.is_none(); + blocking_json(is_sync, move || { + // On *nix, Rust deem non-existent path as invalid input + // See https://github.com/rust-lang/rust/issues/54800 + // Once the issue is reolved, we should remove this workaround. + if cfg!(unix) && !from.is_file() { + return Err( + DenoError::new(ErrorKind::NotFound, "File not found".to_string()) + .into(), + ); + } + + fs::copy(&from, &to)?; + Ok(json!({})) + }) +} + +macro_rules! to_seconds { + ($time:expr) => {{ + // Unwrap is safe here as if the file is before the unix epoch + // something is very wrong. + $time + .and_then(|t| Ok(t.duration_since(UNIX_EPOCH).unwrap().as_secs())) + .unwrap_or(0) + }}; +} + +#[cfg(any(unix))] +fn get_mode(perm: &fs::Permissions) -> u32 { + perm.mode() +} + +#[cfg(not(any(unix)))] +fn get_mode(_perm: &fs::Permissions) -> u32 { + 0 +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct StatArgs { + promise_id: Option<u64>, + filename: String, + lstat: bool, +} + +pub fn op_stat( + state: &ThreadSafeState, + args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: StatArgs = serde_json::from_value(args)?; + + let (filename, filename_) = + deno_fs::resolve_from_cwd(args.filename.as_ref())?; + let lstat = args.lstat; + + state.check_read(&filename_)?; + + let is_sync = args.promise_id.is_none(); + blocking_json(is_sync, move || { + debug!("op_stat {} {}", filename.display(), lstat); + let metadata = if lstat { + fs::symlink_metadata(&filename)? + } else { + fs::metadata(&filename)? + }; + + Ok(json!({ + "isFile": metadata.is_file(), + "isSymlink": metadata.file_type().is_symlink(), + "len": metadata.len(), + "modified":to_seconds!(metadata.modified()), + "accessed":to_seconds!(metadata.accessed()), + "created":to_seconds!(metadata.created()), + "mode": get_mode(&metadata.permissions()), + "hasMode": cfg!(target_family = "unix"), // false on windows, + })) + }) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct ReadDirArgs { + promise_id: Option<u64>, + path: String, +} + +pub fn op_read_dir( + state: &ThreadSafeState, + args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: ReadDirArgs = serde_json::from_value(args)?; + let (path, path_) = deno_fs::resolve_from_cwd(args.path.as_ref())?; + + state.check_read(&path_)?; + + let is_sync = args.promise_id.is_none(); + blocking_json(is_sync, move || { + debug!("op_read_dir {}", path.display()); + + let entries: Vec<_> = fs::read_dir(path)? + .map(|entry| { + let entry = entry.unwrap(); + let metadata = entry.metadata().unwrap(); + let file_type = metadata.file_type(); + + json!({ + "isFile": file_type.is_file(), + "isSymlink": file_type.is_symlink(), + "len": metadata.len(), + "modified": to_seconds!(metadata.modified()), + "accessed": to_seconds!(metadata.accessed()), + "created": to_seconds!(metadata.created()), + "mode": get_mode(&metadata.permissions()), + "name": entry.file_name().to_str().unwrap(), + "hasMode": cfg!(target_family = "unix"), // false on windows, + }) + }) + .collect(); + + Ok(json!({ "entries": entries })) + }) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct RenameArgs { + promise_id: Option<u64>, + oldpath: String, + newpath: String, +} + +pub fn op_rename( + state: &ThreadSafeState, + args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: RenameArgs = serde_json::from_value(args)?; + + let (oldpath, oldpath_) = deno_fs::resolve_from_cwd(args.oldpath.as_ref())?; + let (newpath, newpath_) = deno_fs::resolve_from_cwd(args.newpath.as_ref())?; + + state.check_read(&oldpath_)?; + state.check_write(&oldpath_)?; + state.check_write(&newpath_)?; + + let is_sync = args.promise_id.is_none(); + blocking_json(is_sync, move || { + debug!("op_rename {} {}", oldpath.display(), newpath.display()); + fs::rename(&oldpath, &newpath)?; + Ok(json!({})) + }) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct LinkArgs { + promise_id: Option<u64>, + oldname: String, + newname: String, +} + +pub fn op_link( + state: &ThreadSafeState, + args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: LinkArgs = serde_json::from_value(args)?; + + let (oldname, oldname_) = deno_fs::resolve_from_cwd(args.oldname.as_ref())?; + let (newname, newname_) = deno_fs::resolve_from_cwd(args.newname.as_ref())?; + + state.check_read(&oldname_)?; + state.check_write(&newname_)?; + + let is_sync = args.promise_id.is_none(); + blocking_json(is_sync, move || { + debug!("op_link {} {}", oldname.display(), newname.display()); + std::fs::hard_link(&oldname, &newname)?; + Ok(json!({})) + }) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct SymlinkArgs { + promise_id: Option<u64>, + oldname: String, + newname: String, +} + +pub fn op_symlink( + state: &ThreadSafeState, + args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: SymlinkArgs = serde_json::from_value(args)?; + + let (oldname, _oldname_) = deno_fs::resolve_from_cwd(args.oldname.as_ref())?; + let (newname, newname_) = deno_fs::resolve_from_cwd(args.newname.as_ref())?; + + state.check_write(&newname_)?; + // TODO Use type for Windows. + if cfg!(windows) { + return Err( + DenoError::new(ErrorKind::Other, "Not implemented".to_string()).into(), + ); + } + let is_sync = args.promise_id.is_none(); + blocking_json(is_sync, move || { + debug!("op_symlink {} {}", oldname.display(), newname.display()); + #[cfg(any(unix))] + std::os::unix::fs::symlink(&oldname, &newname)?; + Ok(json!({})) + }) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct ReadLinkArgs { + promise_id: Option<u64>, + name: String, +} + +pub fn op_read_link( + state: &ThreadSafeState, + args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: ReadLinkArgs = serde_json::from_value(args)?; + + let (name, name_) = deno_fs::resolve_from_cwd(args.name.as_ref())?; + + state.check_read(&name_)?; + + let is_sync = args.promise_id.is_none(); + blocking_json(is_sync, move || { + debug!("op_read_link {}", name.display()); + let path = fs::read_link(&name)?; + let path_str = path.to_str().unwrap(); + + Ok(json!(path_str)) + }) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct TruncateArgs { + promise_id: Option<u64>, + name: String, + len: u64, +} + +pub fn op_truncate( + state: &ThreadSafeState, + args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: TruncateArgs = serde_json::from_value(args)?; + + let (filename, filename_) = deno_fs::resolve_from_cwd(args.name.as_ref())?; + let len = args.len; + + state.check_write(&filename_)?; + + let is_sync = args.promise_id.is_none(); + blocking_json(is_sync, move || { + debug!("op_truncate {} {}", filename_, len); + let f = fs::OpenOptions::new().write(true).open(&filename)?; + f.set_len(len)?; + Ok(json!({})) + }) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct MakeTempDirArgs { + promise_id: Option<u64>, + dir: Option<String>, + prefix: Option<String>, + suffix: Option<String>, +} + +pub fn op_make_temp_dir( + state: &ThreadSafeState, + args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: MakeTempDirArgs = serde_json::from_value(args)?; + + // FIXME + state.check_write("make_temp")?; + + let dir = args.dir.map(PathBuf::from); + let prefix = args.prefix.map(String::from); + let suffix = args.suffix.map(String::from); + + let is_sync = args.promise_id.is_none(); + blocking_json(is_sync, move || { + // TODO(piscisaureus): use byte vector for paths, not a string. + // See https://github.com/denoland/deno/issues/627. + // We can't assume that paths are always valid utf8 strings. + let path = deno_fs::make_temp_dir( + // Converting Option<String> to Option<&str> + dir.as_ref().map(|x| &**x), + prefix.as_ref().map(|x| &**x), + suffix.as_ref().map(|x| &**x), + )?; + let path_str = path.to_str().unwrap(); + + Ok(json!(path_str)) + }) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct Utime { + promise_id: Option<u64>, + filename: String, + atime: u64, + mtime: u64, +} + +pub fn op_utime( + state: &ThreadSafeState, + args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: Utime = serde_json::from_value(args)?; + state.check_write(&args.filename)?; + let is_sync = args.promise_id.is_none(); + blocking_json(is_sync, move || { + debug!("op_utimes {} {} {}", args.filename, args.atime, args.mtime); + utime::set_file_times(args.filename, args.atime, args.mtime)?; + Ok(json!({})) + }) +} + +pub fn op_cwd( + _state: &ThreadSafeState, + _args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let path = std::env::current_dir()?; + let path_str = path.into_os_string().into_string().unwrap(); + Ok(JsonOp::Sync(json!(path_str))) +} diff --git a/cli/ops/io.rs b/cli/ops/io.rs new file mode 100644 index 000000000..8b8520c35 --- /dev/null +++ b/cli/ops/io.rs @@ -0,0 +1,46 @@ +use super::dispatch_minimal::MinimalOp; +use crate::deno_error; +use crate::resources; +use crate::tokio_read; +use crate::tokio_write; +use deno::ErrBox; +use deno::PinnedBuf; +use futures::Future; + +pub fn op_read(rid: i32, zero_copy: Option<PinnedBuf>) -> Box<MinimalOp> { + debug!("read rid={}", rid); + let zero_copy = match zero_copy { + None => { + return Box::new(futures::future::err(deno_error::no_buffer_specified())) + } + Some(buf) => buf, + }; + + match resources::lookup(rid as u32) { + Err(e) => Box::new(futures::future::err(e)), + Ok(resource) => Box::new( + tokio_read::read(resource, zero_copy) + .map_err(ErrBox::from) + .and_then(move |(_resource, _buf, nread)| Ok(nread as i32)), + ), + } +} + +pub fn op_write(rid: i32, zero_copy: Option<PinnedBuf>) -> Box<MinimalOp> { + debug!("write rid={}", rid); + let zero_copy = match zero_copy { + None => { + return Box::new(futures::future::err(deno_error::no_buffer_specified())) + } + Some(buf) => buf, + }; + + match resources::lookup(rid as u32) { + Err(e) => Box::new(futures::future::err(e)), + Ok(resource) => Box::new( + tokio_write::write(resource, zero_copy) + .map_err(ErrBox::from) + .and_then(move |(_resource, _buf, nwritten)| Ok(nwritten as i32)), + ), + } +} diff --git a/cli/ops/metrics.rs b/cli/ops/metrics.rs new file mode 100644 index 000000000..e1a23f6c8 --- /dev/null +++ b/cli/ops/metrics.rs @@ -0,0 +1,21 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +use super::dispatch_json::{JsonOp, Value}; +use crate::state::ThreadSafeState; +use deno::*; +use std::sync::atomic::Ordering; + +pub fn op_metrics( + state: &ThreadSafeState, + _args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let m = &state.metrics; + + Ok(JsonOp::Sync(json!({ + "opsDispatched": m.ops_dispatched.load(Ordering::SeqCst) as u64, + "opsCompleted": m.ops_completed.load(Ordering::SeqCst) as u64, + "bytesSentControl": m.bytes_sent_control.load(Ordering::SeqCst) as u64, + "bytesSentData": m.bytes_sent_data.load(Ordering::SeqCst) as u64, + "bytesReceived": m.bytes_received.load(Ordering::SeqCst) as u64 + }))) +} diff --git a/cli/ops/mod.rs b/cli/ops/mod.rs new file mode 100644 index 000000000..0c07adcc9 --- /dev/null +++ b/cli/ops/mod.rs @@ -0,0 +1,25 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +mod dispatch_json; +mod dispatch_minimal; + +pub use dispatch_json::json_op; +pub use dispatch_json::JsonOp; +pub use dispatch_minimal::minimal_op; + +pub mod compiler; +pub mod errors; +pub mod fetch; +pub mod files; +pub mod fs; +pub mod io; +pub mod metrics; +pub mod net; +pub mod os; +pub mod performance; +pub mod permissions; +pub mod process; +pub mod random; +pub mod repl; +pub mod resources; +pub mod timers; +pub mod workers; diff --git a/cli/ops/net.rs b/cli/ops/net.rs new file mode 100644 index 000000000..507eff504 --- /dev/null +++ b/cli/ops/net.rs @@ -0,0 +1,151 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +use super::dispatch_json::{Deserialize, JsonOp, Value}; +use crate::resolve_addr::resolve_addr; +use crate::resources; +use crate::resources::Resource; +use crate::state::ThreadSafeState; +use crate::tokio_util; +use deno::*; +use futures::Future; +use std; +use std::convert::From; +use std::net::Shutdown; +use tokio; +use tokio::net::TcpListener; +use tokio::net::TcpStream; + +#[derive(Deserialize)] +struct AcceptArgs { + rid: i32, +} + +pub fn op_accept( + _state: &ThreadSafeState, + args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: AcceptArgs = serde_json::from_value(args)?; + let server_rid = args.rid as u32; + + let server_resource = resources::lookup(server_rid)?; + let op = tokio_util::accept(server_resource) + .and_then(move |(tcp_stream, _socket_addr)| { + let local_addr = tcp_stream.local_addr()?; + let remote_addr = tcp_stream.peer_addr()?; + let tcp_stream_resource = resources::add_tcp_stream(tcp_stream); + Ok((tcp_stream_resource, local_addr, remote_addr)) + }) + .map_err(ErrBox::from) + .and_then(move |(tcp_stream_resource, local_addr, remote_addr)| { + futures::future::ok(json!({ + "rid": tcp_stream_resource.rid, + "localAddr": local_addr.to_string(), + "remoteAddr": remote_addr.to_string(), + })) + }); + + Ok(JsonOp::Async(Box::new(op))) +} + +#[derive(Deserialize)] +struct DialArgs { + transport: String, + hostname: String, + port: u16, +} + +pub fn op_dial( + state: &ThreadSafeState, + args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: DialArgs = serde_json::from_value(args)?; + assert_eq!(args.transport, "tcp"); // TODO Support others. + + // TODO(ry) Using format! is suboptimal here. Better would be if + // state.check_net and resolve_addr() took hostname and port directly. + let address = format!("{}:{}", args.hostname, args.port); + + state.check_net(&address)?; + + let op = resolve_addr(&address).and_then(move |addr| { + TcpStream::connect(&addr) + .map_err(ErrBox::from) + .and_then(move |tcp_stream| { + let local_addr = tcp_stream.local_addr()?; + let remote_addr = tcp_stream.peer_addr()?; + let tcp_stream_resource = resources::add_tcp_stream(tcp_stream); + Ok((tcp_stream_resource, local_addr, remote_addr)) + }) + .map_err(ErrBox::from) + .and_then(move |(tcp_stream_resource, local_addr, remote_addr)| { + futures::future::ok(json!({ + "rid": tcp_stream_resource.rid, + "localAddr": local_addr.to_string(), + "remoteAddr": remote_addr.to_string(), + })) + }) + }); + + Ok(JsonOp::Async(Box::new(op))) +} + +#[derive(Deserialize)] +struct ShutdownArgs { + rid: i32, + how: i32, +} + +pub fn op_shutdown( + _state: &ThreadSafeState, + args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: ShutdownArgs = serde_json::from_value(args)?; + + let rid = args.rid as u32; + let how = args.how; + let mut resource = resources::lookup(rid)?; + + let shutdown_mode = match how { + 0 => Shutdown::Read, + 1 => Shutdown::Write, + _ => unimplemented!(), + }; + + // Use UFCS for disambiguation + Resource::shutdown(&mut resource, shutdown_mode)?; + Ok(JsonOp::Sync(json!({}))) +} + +#[derive(Deserialize)] +struct ListenArgs { + transport: String, + hostname: String, + port: u16, +} + +pub fn op_listen( + state: &ThreadSafeState, + args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: ListenArgs = serde_json::from_value(args)?; + assert_eq!(args.transport, "tcp"); + + // TODO(ry) Using format! is suboptimal here. Better would be if + // state.check_net and resolve_addr() took hostname and port directly. + let address = format!("{}:{}", args.hostname, args.port); + + state.check_net(&address)?; + + let addr = resolve_addr(&address).wait()?; + let listener = TcpListener::bind(&addr)?; + let local_addr = listener.local_addr()?; + let resource = resources::add_tcp_listener(listener); + + Ok(JsonOp::Sync(json!({ + "rid": resource.rid, + "localAddr": local_addr.to_string() + }))) +} diff --git a/cli/ops/os.rs b/cli/ops/os.rs new file mode 100644 index 000000000..92f640afd --- /dev/null +++ b/cli/ops/os.rs @@ -0,0 +1,157 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +use super::dispatch_json::{Deserialize, JsonOp, Value}; +use crate::colors; +use crate::fs as deno_fs; +use crate::state::ThreadSafeState; +use crate::version; +use atty; +use deno::*; +use log; +use std::collections::HashMap; +use std::env; +use sys_info; +use url::Url; + +/// BUILD_OS and BUILD_ARCH match the values in Deno.build. See js/build.ts. +#[cfg(target_os = "macos")] +static BUILD_OS: &str = "mac"; +#[cfg(target_os = "linux")] +static BUILD_OS: &str = "linux"; +#[cfg(target_os = "windows")] +static BUILD_OS: &str = "win"; +#[cfg(target_arch = "x86_64")] +static BUILD_ARCH: &str = "x64"; + +pub fn op_start( + state: &ThreadSafeState, + _args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + Ok(JsonOp::Sync(json!({ + "cwd": deno_fs::normalize_path(&env::current_dir().unwrap()), + "pid": std::process::id(), + "argv": state.argv, + "mainModule": state.main_module().map(|x| x.as_str().to_string()), + "debugFlag": state + .flags + .log_level + .map_or(false, |l| l == log::Level::Debug), + "versionFlag": state.flags.version, + "v8Version": version::v8(), + "denoVersion": version::DENO, + "tsVersion": version::TYPESCRIPT, + "noColor": !colors::use_color(), + "os": BUILD_OS, + "arch": BUILD_ARCH, + }))) +} + +pub fn op_home_dir( + state: &ThreadSafeState, + _args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + state.check_env()?; + let path = dirs::home_dir() + .unwrap_or_default() + .into_os_string() + .into_string() + .unwrap_or_default(); + Ok(JsonOp::Sync(json!(path))) +} + +pub fn op_exec_path( + state: &ThreadSafeState, + _args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + state.check_env()?; + let current_exe = env::current_exe().unwrap(); + // Now apply URL parser to current exe to get fully resolved path, otherwise + // we might get `./` and `../` bits in `exec_path` + let exe_url = Url::from_file_path(current_exe).unwrap(); + let path = exe_url.to_file_path().unwrap(); + Ok(JsonOp::Sync(json!(path))) +} + +#[derive(Deserialize)] +struct SetEnv { + key: String, + value: String, +} + +pub fn op_set_env( + state: &ThreadSafeState, + args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: SetEnv = serde_json::from_value(args)?; + state.check_env()?; + env::set_var(args.key, args.value); + Ok(JsonOp::Sync(json!({}))) +} + +pub fn op_env( + state: &ThreadSafeState, + _args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + state.check_env()?; + let v = env::vars().collect::<HashMap<String, String>>(); + Ok(JsonOp::Sync(json!(v))) +} + +#[derive(Deserialize)] +struct GetEnv { + key: String, +} + +pub fn op_get_env( + state: &ThreadSafeState, + args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: GetEnv = serde_json::from_value(args)?; + state.check_env()?; + let r = match env::var(args.key) { + Err(env::VarError::NotPresent) => json!([]), + v => json!([v?]), + }; + Ok(JsonOp::Sync(r)) +} + +#[derive(Deserialize)] +struct Exit { + code: i32, +} + +pub fn op_exit( + _s: &ThreadSafeState, + args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: Exit = serde_json::from_value(args)?; + std::process::exit(args.code) +} + +pub fn op_is_tty( + _s: &ThreadSafeState, + _args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + Ok(JsonOp::Sync(json!({ + "stdin": atty::is(atty::Stream::Stdin), + "stdout": atty::is(atty::Stream::Stdout), + "stderr": atty::is(atty::Stream::Stderr), + }))) +} + +pub fn op_hostname( + state: &ThreadSafeState, + _args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + state.check_env()?; + let hostname = sys_info::hostname().unwrap_or_else(|_| "".to_owned()); + Ok(JsonOp::Sync(json!(hostname))) +} diff --git a/cli/ops/performance.rs b/cli/ops/performance.rs new file mode 100644 index 000000000..090fc3323 --- /dev/null +++ b/cli/ops/performance.rs @@ -0,0 +1,30 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +use super::dispatch_json::{JsonOp, Value}; +use crate::state::ThreadSafeState; +use deno::*; + +// Returns a milliseconds and nanoseconds subsec +// since the start time of the deno runtime. +// If the High precision flag is not set, the +// nanoseconds are rounded on 2ms. +pub fn op_now( + state: &ThreadSafeState, + _args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let seconds = state.start_time.elapsed().as_secs(); + let mut subsec_nanos = state.start_time.elapsed().subsec_nanos(); + let reduced_time_precision = 2_000_000; // 2ms in nanoseconds + + // If the permission is not enabled + // Round the nano result on 2 milliseconds + // see: https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp#Reduced_time_precision + if !state.permissions.allows_hrtime() { + subsec_nanos -= subsec_nanos % reduced_time_precision + } + + Ok(JsonOp::Sync(json!({ + "seconds": seconds, + "subsecNanos": subsec_nanos, + }))) +} diff --git a/cli/ops/permissions.rs b/cli/ops/permissions.rs new file mode 100644 index 000000000..5d14f39be --- /dev/null +++ b/cli/ops/permissions.rs @@ -0,0 +1,44 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +use super::dispatch_json::{Deserialize, JsonOp, Value}; +use crate::state::ThreadSafeState; +use deno::*; + +pub fn op_permissions( + state: &ThreadSafeState, + _args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + Ok(JsonOp::Sync(json!({ + "run": state.permissions.allows_run(), + "read": state.permissions.allows_read(), + "write": state.permissions.allows_write(), + "net": state.permissions.allows_net(), + "env": state.permissions.allows_env(), + "hrtime": state.permissions.allows_hrtime(), + }))) +} + +#[derive(Deserialize)] +struct RevokePermissionArgs { + permission: String, +} + +pub fn op_revoke_permission( + state: &ThreadSafeState, + args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: RevokePermissionArgs = serde_json::from_value(args)?; + let permission = args.permission.as_ref(); + match permission { + "run" => state.permissions.revoke_run(), + "read" => state.permissions.revoke_read(), + "write" => state.permissions.revoke_write(), + "net" => state.permissions.revoke_net(), + "env" => state.permissions.revoke_env(), + "hrtime" => state.permissions.revoke_hrtime(), + _ => Ok(()), + }?; + + Ok(JsonOp::Sync(json!({}))) +} diff --git a/cli/ops/process.rs b/cli/ops/process.rs new file mode 100644 index 000000000..8dff53c6e --- /dev/null +++ b/cli/ops/process.rs @@ -0,0 +1,157 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +use super::dispatch_json::{Deserialize, JsonOp, Value}; +use crate::resources; +use crate::signal::kill; +use crate::state::ThreadSafeState; +use deno::*; +use futures; +use futures::Future; +use std; +use std::convert::From; +use std::process::Command; +use tokio_process::CommandExt; + +#[cfg(unix)] +use std::os::unix::process::ExitStatusExt; + +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, +} + +pub fn op_run( + state: &ThreadSafeState, + args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let run_args: RunArgs = serde_json::from_value(args)?; + + state.check_run()?; + + 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 { + c.stdin(resources::get_file(stdin_rid)?); + } else { + c.stdin(subprocess_stdio_map(run_args.stdin.as_ref())); + } + + let stdout_rid = run_args.stdout_rid; + if stdout_rid > 0 { + c.stdout(resources::get_file(stdout_rid)?); + } else { + c.stdout(subprocess_stdio_map(run_args.stdout.as_ref())); + } + + let stderr_rid = run_args.stderr_rid; + if stderr_rid > 0 { + c.stderr(resources::get_file(stderr_rid)?); + } else { + c.stderr(subprocess_stdio_map(run_args.stderr.as_ref())); + } + + // Spawn the command. + let child = c.spawn_async().map_err(ErrBox::from)?; + + let pid = child.id(); + let resources = resources::add_child(child); + + Ok(JsonOp::Sync(json!({ + "rid": resources.child_rid, + "pid": pid, + "stdinRid": resources.stdin_rid, + "stdoutRid": resources.stdout_rid, + "stderrRid": resources.stderr_rid, + }))) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct RunStatusArgs { + rid: i32, +} + +pub fn op_run_status( + state: &ThreadSafeState, + args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: RunStatusArgs = serde_json::from_value(args)?; + let rid = args.rid as u32; + + state.check_run()?; + + let future = resources::child_status(rid)?; + + 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(); + + futures::future::ok(json!({ + "gotSignal": got_signal, + "exitCode": code.unwrap_or(-1), + "exitSignal": signal.unwrap_or(-1), + })) + }); + + Ok(JsonOp::Async(Box::new(future))) +} + +#[derive(Deserialize)] +struct KillArgs { + pid: i32, + signo: i32, +} + +pub fn op_kill( + state: &ThreadSafeState, + args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + state.check_run()?; + + let args: KillArgs = serde_json::from_value(args)?; + kill(args.pid, args.signo)?; + Ok(JsonOp::Sync(json!({}))) +} diff --git a/cli/ops/random.rs b/cli/ops/random.rs new file mode 100644 index 000000000..7470eab40 --- /dev/null +++ b/cli/ops/random.rs @@ -0,0 +1,24 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +use super::dispatch_json::{JsonOp, Value}; +use crate::state::ThreadSafeState; +use deno::*; +use rand::thread_rng; +use rand::Rng; + +pub fn op_get_random_values( + state: &ThreadSafeState, + _args: Value, + zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + assert!(zero_copy.is_some()); + + if let Some(ref seeded_rng) = state.seeded_rng { + let mut rng = seeded_rng.lock().unwrap(); + rng.fill(&mut zero_copy.unwrap()[..]); + } else { + let mut rng = thread_rng(); + rng.fill(&mut zero_copy.unwrap()[..]); + } + + Ok(JsonOp::Sync(json!({}))) +} diff --git a/cli/ops/repl.rs b/cli/ops/repl.rs new file mode 100644 index 000000000..7ab7509de --- /dev/null +++ b/cli/ops/repl.rs @@ -0,0 +1,50 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +use super::dispatch_json::{blocking_json, Deserialize, JsonOp, Value}; +use crate::repl; +use crate::resources; +use crate::state::ThreadSafeState; +use deno::*; + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct ReplStartArgs { + history_file: String, +} + +pub fn op_repl_start( + state: &ThreadSafeState, + args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: ReplStartArgs = serde_json::from_value(args)?; + + debug!("op_repl_start {}", args.history_file); + let history_path = repl::history_path(&state.dir, &args.history_file); + let repl = repl::Repl::new(history_path); + let resource = resources::add_repl(repl); + + Ok(JsonOp::Sync(json!(resource.rid))) +} + +#[derive(Deserialize)] +struct ReplReadlineArgs { + rid: i32, + prompt: String, +} + +pub fn op_repl_readline( + _state: &ThreadSafeState, + args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: ReplReadlineArgs = serde_json::from_value(args)?; + let rid = args.rid; + let prompt = args.prompt; + debug!("op_repl_readline {} {}", rid, prompt); + + blocking_json(false, move || { + let repl = resources::get_repl(rid as u32)?; + let line = repl.lock().unwrap().readline(&prompt)?; + Ok(json!(line)) + }) +} diff --git a/cli/ops/resources.rs b/cli/ops/resources.rs new file mode 100644 index 000000000..dafd01d08 --- /dev/null +++ b/cli/ops/resources.rs @@ -0,0 +1,14 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +use super::dispatch_json::{JsonOp, Value}; +use crate::resources::table_entries; +use crate::state::ThreadSafeState; +use deno::*; + +pub fn op_resources( + _state: &ThreadSafeState, + _args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let serialized_resources = table_entries(); + Ok(JsonOp::Sync(json!(serialized_resources))) +} diff --git a/cli/ops/timers.rs b/cli/ops/timers.rs new file mode 100644 index 000000000..abcd5c1b3 --- /dev/null +++ b/cli/ops/timers.rs @@ -0,0 +1,42 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +use super::dispatch_json::{Deserialize, JsonOp, Value}; +use crate::state::ThreadSafeState; +use deno::*; +use futures::Future; +use std; +use std::time::Duration; +use std::time::Instant; + +pub fn op_global_timer_stop( + state: &ThreadSafeState, + _args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let state = state; + let mut t = state.global_timer.lock().unwrap(); + t.cancel(); + Ok(JsonOp::Sync(json!({}))) +} + +#[derive(Deserialize)] +struct GlobalTimerArgs { + timeout: u64, +} + +pub fn op_global_timer( + state: &ThreadSafeState, + args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: GlobalTimerArgs = serde_json::from_value(args)?; + let val = args.timeout; + + let state = state; + let mut t = state.global_timer.lock().unwrap(); + let deadline = Instant::now() + Duration::from_millis(val); + let f = t + .new_timeout(deadline) + .then(move |_| futures::future::ok(json!({}))); + + Ok(JsonOp::Async(Box::new(f))) +} diff --git a/cli/ops/tls.rs b/cli/ops/tls.rs new file mode 100644 index 000000000..2b1d94f2b --- /dev/null +++ b/cli/ops/tls.rs @@ -0,0 +1,76 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +use super::dispatch_json::{Deserialize, JsonOp, Value}; +use crate::resolve_addr::resolve_addr; +use crate::resources; +use crate::state::ThreadSafeState; +use deno::*; +use futures::Future; +use std; +use std::convert::From; +use std::sync::Arc; +use tokio; +use tokio::net::TcpStream; +use tokio_rustls::{rustls::ClientConfig, TlsConnector}; +use webpki; +use webpki::DNSNameRef; +use webpki_roots; + +#[derive(Deserialize)] +struct DialTLSArgs { + hostname: String, + port: u16, +} + +pub fn op_dial_tls( + state: &ThreadSafeState, + args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: DialTLSArgs = serde_json::from_value(args)?; + + // TODO(ry) Using format! is suboptimal here. Better would be if + // state.check_net and resolve_addr() took hostname and port directly. + let address = format!("{}:{}", args.hostname, args.port); + + state.check_net(&address)?; + + let mut domain = args.hostname; + if domain.is_empty() { + domain.push_str("localhost"); + } + + let op = resolve_addr(&address).and_then(move |addr| { + TcpStream::connect(&addr) + .and_then(move |tcp_stream| { + let local_addr = tcp_stream.local_addr()?; + let remote_addr = tcp_stream.peer_addr()?; + let mut config = ClientConfig::new(); + config + .root_store + .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); + + let tls_connector = TlsConnector::from(Arc::new(config)); + Ok((tls_connector, tcp_stream, local_addr, remote_addr)) + }) + .map_err(ErrBox::from) + .and_then( + move |(tls_connector, tcp_stream, local_addr, remote_addr)| { + let dnsname = DNSNameRef::try_from_ascii_str(&domain) + .expect("Invalid DNS lookup"); + tls_connector + .connect(dnsname, tcp_stream) + .map_err(ErrBox::from) + .and_then(move |tls_stream| { + let tls_stream_resource = resources::add_tls_stream(tls_stream); + futures::future::ok(json!({ + "rid": tls_stream_resource.rid, + "localAddr": local_addr.to_string(), + "remoteAddr": remote_addr.to_string(), + })) + }) + }, + ) + }); + + Ok(JsonOp::Async(Box::new(op))) +} diff --git a/cli/ops/workers.rs b/cli/ops/workers.rs new file mode 100644 index 000000000..6950f25d6 --- /dev/null +++ b/cli/ops/workers.rs @@ -0,0 +1,227 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +use super::dispatch_json::{Deserialize, JsonOp, Value}; +use crate::deno_error::js_check; +use crate::deno_error::DenoError; +use crate::deno_error::ErrorKind; +use crate::resources; +use crate::startup_data; +use crate::state::ThreadSafeState; +use crate::worker::Worker; +use deno::*; +use futures; +use futures::Async; +use futures::Future; +use futures::Sink; +use futures::Stream; +use std; +use std::convert::From; + +struct GetMessageFuture { + pub state: ThreadSafeState, +} + +impl Future for GetMessageFuture { + type Item = Option<Buf>; + type Error = (); + + fn poll(&mut self) -> Result<Async<Self::Item>, Self::Error> { + let mut wc = self.state.worker_channels.lock().unwrap(); + wc.1 + .poll() + .map_err(|err| panic!("worker_channel recv err {:?}", err)) + } +} + +/// Get message from host as guest worker +pub fn op_worker_get_message( + state: &ThreadSafeState, + _args: Value, + _data: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let op = GetMessageFuture { + state: state.clone(), + }; + + let op = op + .map_err(move |_| -> ErrBox { unimplemented!() }) + .and_then(move |maybe_buf| { + debug!("op_worker_get_message"); + + futures::future::ok(json!({ + "data": maybe_buf.map(|buf| buf.to_owned()) + })) + }); + + Ok(JsonOp::Async(Box::new(op))) +} + +/// Post message to host as guest worker +pub fn op_worker_post_message( + state: &ThreadSafeState, + _args: Value, + data: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let d = Vec::from(data.unwrap().as_ref()).into_boxed_slice(); + + let tx = { + let wc = state.worker_channels.lock().unwrap(); + wc.0.clone() + }; + tx.send(d) + .wait() + .map_err(|e| DenoError::new(ErrorKind::Other, e.to_string()))?; + + Ok(JsonOp::Sync(json!({}))) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct CreateWorkerArgs { + specifier: String, + include_deno_namespace: bool, + has_source_code: bool, + source_code: String, +} + +/// Create worker as the host +pub fn op_create_worker( + state: &ThreadSafeState, + args: Value, + _data: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: CreateWorkerArgs = serde_json::from_value(args)?; + + let specifier = args.specifier.as_ref(); + // Only include deno namespace if requested AND current worker + // has included namespace (to avoid escalation). + let include_deno_namespace = + args.include_deno_namespace && state.include_deno_namespace; + let has_source_code = args.has_source_code; + let source_code = args.source_code; + + let parent_state = state.clone(); + + let mut module_specifier = ModuleSpecifier::resolve_url_or_path(specifier)?; + + let mut child_argv = parent_state.argv.clone(); + + if !has_source_code { + if let Some(module) = state.main_module() { + module_specifier = + ModuleSpecifier::resolve_import(specifier, &module.to_string())?; + child_argv[1] = module_specifier.to_string(); + } + } + + let child_state = ThreadSafeState::new( + parent_state.flags.clone(), + child_argv, + parent_state.progress.clone(), + include_deno_namespace, + )?; + let rid = child_state.resource.rid; + let name = format!("USER-WORKER-{}", specifier); + let deno_main_call = format!("denoMain({})", include_deno_namespace); + + let mut worker = + Worker::new(name, startup_data::deno_isolate_init(), child_state); + js_check(worker.execute(&deno_main_call)); + js_check(worker.execute("workerMain()")); + + let exec_cb = move |worker: Worker| { + let mut workers_tl = parent_state.workers.lock().unwrap(); + workers_tl.insert(rid, worker.shared()); + json!(rid) + }; + + // Has provided source code, execute immediately. + if has_source_code { + js_check(worker.execute(&source_code)); + return Ok(JsonOp::Sync(exec_cb(worker))); + } + + let op = worker + .execute_mod_async(&module_specifier, false) + .and_then(move |()| Ok(exec_cb(worker))); + + let result = op.wait()?; + Ok(JsonOp::Sync(result)) +} + +#[derive(Deserialize)] +struct HostGetWorkerClosedArgs { + rid: i32, +} + +/// Return when the worker closes +pub fn op_host_get_worker_closed( + state: &ThreadSafeState, + args: Value, + _data: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: HostGetWorkerClosedArgs = serde_json::from_value(args)?; + + let rid = args.rid as u32; + let state = state.clone(); + + let shared_worker_future = { + let workers_tl = state.workers.lock().unwrap(); + let worker = workers_tl.get(&rid).unwrap(); + worker.clone() + }; + + let op = Box::new( + shared_worker_future.then(move |_result| futures::future::ok(json!({}))), + ); + + Ok(JsonOp::Async(Box::new(op))) +} + +#[derive(Deserialize)] +struct HostGetMessageArgs { + rid: i32, +} + +/// Get message from guest worker as host +pub fn op_host_get_message( + _state: &ThreadSafeState, + args: Value, + _data: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: HostGetMessageArgs = serde_json::from_value(args)?; + + let rid = args.rid as u32; + let op = resources::get_message_from_worker(rid) + .map_err(move |_| -> ErrBox { unimplemented!() }) + .and_then(move |maybe_buf| { + futures::future::ok(json!({ + "data": maybe_buf.map(|buf| buf.to_owned()) + })) + }); + + Ok(JsonOp::Async(Box::new(op))) +} + +#[derive(Deserialize)] +struct HostPostMessageArgs { + rid: i32, +} + +/// Post message to guest worker as host +pub fn op_host_post_message( + _state: &ThreadSafeState, + args: Value, + data: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: HostPostMessageArgs = serde_json::from_value(args)?; + + let rid = args.rid as u32; + + let d = Vec::from(data.unwrap().as_ref()).into_boxed_slice(); + + resources::post_message_to_worker(rid, d) + .wait() + .map_err(|e| DenoError::new(ErrorKind::Other, e.to_string()))?; + + Ok(JsonOp::Sync(json!({}))) +} |