diff --git a/Cargo.lock b/Cargo.lock index c1bab2c7ece050..86dd968c3be271 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -274,6 +274,8 @@ dependencies = [ "deno 0.16.0", "deno_cli_snapshots 0.0.3", "deno_typescript 0.0.3", + "deno_dispatch_json 0.16.0", + "deno_ops_fs 0.16.0", "dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", "fwdansi 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -325,6 +327,34 @@ dependencies = [ "deno 0.16.0", "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", +name = "deno_dispatch_json" +version = "0.16.0" +dependencies = [ + "deno 0.16.0", + "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-threadpool 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "deno_ops_fs" +version = "0.16.0" +dependencies = [ + "deno 0.16.0", + "deno_dispatch_json 0.16.0", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", + "url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "utime 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -575,6 +605,16 @@ dependencies = [ "unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "idna" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "indexmap" version = "1.1.0" @@ -822,6 +862,11 @@ name = "percent-encoding" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "ppv-lite86" version = "0.2.5" @@ -1693,6 +1738,16 @@ dependencies = [ "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "url" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "utf8parse" version = "0.1.1" @@ -1972,6 +2027,7 @@ dependencies = [ "checksum hyper 0.12.33 (registry+https://github.com/rust-lang/crates.io-index)" = "7cb44cbce9d8ee4fb36e4c0ad7b794ac44ebaad924b9c8291a63215bb44c2c8f" "checksum hyper-rustls 0.17.1 (registry+https://github.com/rust-lang/crates.io-index)" = "719d85c7df4a7f309a77d145340a063ea929dcb2e025bae46a80345cffec2952" "checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" +"checksum idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" "checksum indexmap 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a4d6d89e0948bf10c08b9ecc8ac5b83f07f857ebe2c0cbe38de15b4e4f510356" "checksum integer-atomics 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5c33cd4d18b4ade167caace0e92364e8568c1e47c193738397b4b48a3e414139" "checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" @@ -2002,6 +2058,7 @@ dependencies = [ "checksum parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab41b4aed082705d1056416ae4468b6ea99d52599ecf3169b00088d43113e337" "checksum parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94c8c7923936b28d546dfd14d4472eaf34c99b14e1c973a32b3e6d4eb04298c9" "checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" +"checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" "checksum ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e3cbf9f658cdb5000fcf6f362b8ea2ba154b9f146a61c7a20d647034c6b6561b" "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" "checksum proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4c5c2380ae88876faae57698be9e9775e3544decad214599c3a6266cca6ac802" @@ -2093,6 +2150,7 @@ dependencies = [ "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" "checksum untrusted 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60369ef7a31de49bcb3f6ca728d4ba7300d9a1658f94c727d4cab8c8d9f4aece" "checksum url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" +"checksum url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "75b414f6c464c879d7f9babf951f23bc3743fb7313c081b2e6ca719067ea9d61" "checksum utf8parse 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8772a4ccbb4e89959023bc5b7cb8623a795caa7092d99f3aa9501b9484d4557d" "checksum utime 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "055058552ca15c566082fc61da433ae678f78986a6f16957e33162d1b218792a" "checksum uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a" diff --git a/Cargo.toml b/Cargo.toml index 084258c324fcfd..9e19624b88f627 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,4 +5,6 @@ members = [ "tools/hyper_hello", "deno_typescript", "cli_snapshots", + "ops/dispatch_json", + "ops/fs", ] diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 02d24100363990..02c4791de50e4d 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -17,6 +17,8 @@ edition = "2018" [dependencies] deno = { path = "../core" } +deno_dispatch_json = { path = "../ops/dispatch_json" } +deno_ops_fs = { path = "../ops/fs" } ansi_term = "0.12.0" atty = "0.2.13" diff --git a/cli/compilers/ts.rs b/cli/compilers/ts.rs index 414f18e148ca34..f7378fbe2cbe87 100644 --- a/cli/compilers/ts.rs +++ b/cli/compilers/ts.rs @@ -639,9 +639,9 @@ impl TsCompiler { #[cfg(test)] mod tests { use super::*; - use crate::fs as deno_fs; use crate::tokio_util; use deno::ModuleSpecifier; + use deno_ops_fs::fs as deno_fs; use std::path::PathBuf; use tempfile::TempDir; diff --git a/cli/disk_cache.rs b/cli/disk_cache.rs index 975a31f45683d5..5feede8aef8903 100644 --- a/cli/disk_cache.rs +++ b/cli/disk_cache.rs @@ -1,4 +1,4 @@ -use crate::fs as deno_fs; +use deno_ops_fs::fs as deno_fs; use std::ffi::OsStr; use std::fs; use std::path::Component; diff --git a/cli/file_fetcher.rs b/cli/file_fetcher.rs index ae5454cd935210..768cf5fa6b2593 100644 --- a/cli/file_fetcher.rs +++ b/cli/file_fetcher.rs @@ -607,7 +607,7 @@ impl SourceCodeHeaders { #[cfg(test)] mod tests { use super::*; - use crate::fs as deno_fs; + use deno_ops_fs::fs as deno_fs; use tempfile::TempDir; impl SourceFileFetcher { diff --git a/cli/flags.rs b/cli/flags.rs index c9c7a59c772fd9..e0d5d2ca18413c 100644 --- a/cli/flags.rs +++ b/cli/flags.rs @@ -1,5 +1,4 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -use crate::fs as deno_fs; use clap::App; use clap::AppSettings; use clap::Arg; @@ -7,6 +6,7 @@ use clap::ArgMatches; use clap::Shell; use clap::SubCommand; use deno::ModuleSpecifier; +use deno_ops_fs::fs as deno_fs; use log::Level; use std; use std::str; diff --git a/cli/ops/files.rs b/cli/ops/files.rs index 775a0cc71c9327..abc0891190a24e 100644 --- a/cli/ops/files.rs +++ b/cli/ops/files.rs @@ -1,11 +1,11 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. use super::dispatch_json::{wrap_json_op, Deserialize, JsonOp}; use crate::deno_error; -use crate::fs as deno_fs; use crate::resources; use crate::state::DenoOpDispatcher; use crate::state::ThreadSafeState; use deno::*; +use deno_ops_fs::fs as deno_fs; use futures::Future; use std; use std::convert::From; diff --git a/cli/ops/mod.rs b/cli/ops/mod.rs index 64d5fa40800df9..dd8544c7488661 100644 --- a/cli/ops/mod.rs +++ b/cli/ops/mod.rs @@ -1,15 +1,16 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. use crate::state::ThreadSafeState; use deno::*; +use deno_ops_fs as fs; use std::sync::Arc; mod compiler; +// TODO(afinch7) remove this. mod dispatch_json; mod dispatch_minimal; mod errors; mod fetch; mod files; -mod fs; mod io; mod metrics; mod net; @@ -46,23 +47,30 @@ pub fn setup_dispatcher_registry(state: ThreadSafeState) -> Arc { registry.register_op(OP_NAMESPACE, state.wrap_op(files::OpClose)); registry.register_op(OP_NAMESPACE, state.wrap_op(files::OpSeek)); + let state_ = state.clone(); + let state__ = state.clone(); + let fs_state = fs::TSFsOpsState::new( + move |filename| state_.check_read(filename), + move |filename| state__.check_write(filename), + ); + // Fs - registry.register_op(OP_NAMESPACE, state.wrap_op(fs::OpChdir)); - registry.register_op(OP_NAMESPACE, state.wrap_op(fs::OpMkdir)); - registry.register_op(OP_NAMESPACE, state.wrap_op(fs::OpChmod)); - registry.register_op(OP_NAMESPACE, state.wrap_op(fs::OpChown)); - registry.register_op(OP_NAMESPACE, state.wrap_op(fs::OpRemove)); - registry.register_op(OP_NAMESPACE, state.wrap_op(fs::OpCopyFile)); - registry.register_op(OP_NAMESPACE, state.wrap_op(fs::OpStat)); - registry.register_op(OP_NAMESPACE, state.wrap_op(fs::OpReadDir)); - registry.register_op(OP_NAMESPACE, state.wrap_op(fs::OpRename)); - registry.register_op(OP_NAMESPACE, state.wrap_op(fs::OpLink)); - registry.register_op(OP_NAMESPACE, state.wrap_op(fs::OpSymlink)); - registry.register_op(OP_NAMESPACE, state.wrap_op(fs::OpReadLink)); - registry.register_op(OP_NAMESPACE, state.wrap_op(fs::OpTruncate)); - registry.register_op(OP_NAMESPACE, state.wrap_op(fs::OpMakeTempDir)); - registry.register_op(OP_NAMESPACE, state.wrap_op(fs::OpUtime)); - registry.register_op(OP_NAMESPACE, state.wrap_op(fs::OpCwd)); + registry.register_op(OP_NAMESPACE, fs_state.wrap_op(fs::OpChdir)); + registry.register_op(OP_NAMESPACE, fs_state.wrap_op(fs::OpMkdir)); + registry.register_op(OP_NAMESPACE, fs_state.wrap_op(fs::OpChmod)); + registry.register_op(OP_NAMESPACE, fs_state.wrap_op(fs::OpChown)); + registry.register_op(OP_NAMESPACE, fs_state.wrap_op(fs::OpRemove)); + registry.register_op(OP_NAMESPACE, fs_state.wrap_op(fs::OpCopyFile)); + registry.register_op(OP_NAMESPACE, fs_state.wrap_op(fs::OpStat)); + registry.register_op(OP_NAMESPACE, fs_state.wrap_op(fs::OpReadDir)); + registry.register_op(OP_NAMESPACE, fs_state.wrap_op(fs::OpRename)); + registry.register_op(OP_NAMESPACE, fs_state.wrap_op(fs::OpLink)); + registry.register_op(OP_NAMESPACE, fs_state.wrap_op(fs::OpSymlink)); + registry.register_op(OP_NAMESPACE, fs_state.wrap_op(fs::OpReadLink)); + registry.register_op(OP_NAMESPACE, fs_state.wrap_op(fs::OpTruncate)); + registry.register_op(OP_NAMESPACE, fs_state.wrap_op(fs::OpMakeTempDir)); + registry.register_op(OP_NAMESPACE, fs_state.wrap_op(fs::OpUtime)); + registry.register_op(OP_NAMESPACE, fs_state.wrap_op(fs::OpCwd)); // Io registry.register_op(OP_NAMESPACE, state.wrap_op(io::OpRead)); diff --git a/cli/ops/os.rs b/cli/ops/os.rs index 5d8f74c351bebc..d1ee5787adacc1 100644 --- a/cli/ops/os.rs +++ b/cli/ops/os.rs @@ -1,12 +1,12 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. use super::dispatch_json::{wrap_json_op, Deserialize, JsonOp}; use crate::ansi; -use crate::fs as deno_fs; use crate::state::DenoOpDispatcher; use crate::state::ThreadSafeState; use crate::version; use atty; use deno::*; +use deno_ops_fs::fs as deno_fs; use log; use std::collections::HashMap; use std::env; diff --git a/ops/dispatch_json/Cargo.toml b/ops/dispatch_json/Cargo.toml new file mode 100644 index 00000000000000..aeec7f8bc0638f --- /dev/null +++ b/ops/dispatch_json/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "deno_dispatch_json" +version = "0.16.0" +edition = "2018" +authors = ["The deno authors "] +license = "MIT" +readme = "README.md" +repository = "https://github.com/denoland/deno" + +[lib] +path = "lib.rs" + +[dependencies] +deno = { path = "../../core" } + +log = "0.4.8" +futures = "0.1.28" +serde = { version = "1.0.99", features = ["derive"] } +serde_derive = "1.0.99" +serde_json = { version = "1.0.40", features = [ "preserve_order" ] } +tokio-executor = "0.1.8" +tokio-threadpool = "0.1.15" \ No newline at end of file diff --git a/ops/dispatch_json/lib.rs b/ops/dispatch_json/lib.rs new file mode 100644 index 00000000000000..dfc43e54a10b1f --- /dev/null +++ b/ops/dispatch_json/lib.rs @@ -0,0 +1,139 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +#[macro_use] +extern crate log; + +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 + Send>; + +pub enum JsonOp { + Sync(Value), + Async(AsyncJsonOp), +} + +fn json_err(err: ErrBox) -> Value { + json!({ + "message": err.to_string(), + }) +} + +fn serialize_result( + promise_id: Option, + result: Result, +) -> 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, +} + +pub fn wrap_json_op( + d: D, + control: &[u8], + zero_copy: Option, +) -> CoreOp +where + D: FnOnce(Value, Option) -> Result, +{ + 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(move |args| d(args, zero_copy)); + + 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 { + 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) -> Poll +where + F: FnOnce() -> Result, +{ + 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(is_sync: bool, f: F) -> Result +where + F: 'static + Send + FnOnce() -> Result, +{ + if is_sync { + Ok(JsonOp::Sync(f()?)) + } else { + Ok(JsonOp::Async(Box::new(futures::sync::oneshot::spawn( + poll_fn(move || convert_blocking_json(f)), + &tokio_executor::DefaultExecutor::current(), + )))) + } +} + +/// `futures::future::poll_fn` only support `F: FnMut()->Poll` +/// However, we require that `F: FnOnce()->Poll`. +/// Therefore, we created our version of `poll_fn`. +fn poll_fn(f: F) -> PollFn +where + F: FnOnce() -> Poll, +{ + PollFn { inner: Some(f) } +} + +struct PollFn { + inner: Option, +} + +impl Future for PollFn +where + F: FnOnce() -> Poll, +{ + type Item = T; + type Error = E; + + fn poll(&mut self) -> Poll { + let f = self.inner.take().expect("Inner fn has been taken."); + f() + } +} diff --git a/ops/fs/Cargo.toml b/ops/fs/Cargo.toml new file mode 100644 index 00000000000000..59f67d39c278f5 --- /dev/null +++ b/ops/fs/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "deno_ops_fs" +version = "0.16.0" +edition = "2018" +authors = ["The deno authors "] +license = "MIT" +readme = "README.md" +repository = "https://github.com/denoland/deno" + +[lib] +path = "lib.rs" + +[dependencies] +deno = { path = "../../core" } +deno_dispatch_json = { path = "../dispatch_json" } + +log = "0.4.8" +rand = "0.6.5" +remove_dir_all = "0.5.2" +serde = { version = "1.0.99", features = ["derive"] } +serde_derive = "1.0.99" +serde_json = { version = "1.0.40", features = [ "preserve_order" ] } +url = "2.1.0" +utime = "0.2.1" + +[target.'cfg(unix)'.dependencies] +nix = "0.14.1" \ No newline at end of file diff --git a/ops/fs/fs.rs b/ops/fs/fs.rs new file mode 100644 index 00000000000000..8b8693fcda44f4 --- /dev/null +++ b/ops/fs/fs.rs @@ -0,0 +1,158 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +use std; +use std::fs::{create_dir, DirBuilder, File, OpenOptions}; +use std::io::ErrorKind; +use std::io::Write; +use std::path::{Path, PathBuf}; + +use deno::ErrBox; +use rand; +use rand::Rng; +use url::Url; + +#[cfg(unix)] +use nix::unistd::{chown as unix_chown, Gid, Uid}; +#[cfg(any(unix))] +use std::os::unix::fs::DirBuilderExt; +#[cfg(any(unix))] +use std::os::unix::fs::PermissionsExt; + +pub fn write_file>( + filename: &Path, + data: T, + perm: u32, +) -> std::io::Result<()> { + write_file_2(filename, data, true, perm, true, false) +} + +pub fn write_file_2>( + filename: &Path, + data: T, + update_perm: bool, + perm: u32, + is_create: bool, + is_append: bool, +) -> std::io::Result<()> { + let mut file = OpenOptions::new() + .read(false) + .write(true) + .append(is_append) + .truncate(!is_append) + .create(is_create) + .open(filename)?; + + if update_perm { + set_permissions(&mut file, perm)?; + } + + file.write_all(data.as_ref()) +} + +#[cfg(any(unix))] +fn set_permissions(file: &mut File, perm: u32) -> std::io::Result<()> { + debug!("set file perm to {}", perm); + file.set_permissions(PermissionsExt::from_mode(perm & 0o777)) +} +#[cfg(not(any(unix)))] +fn set_permissions(_file: &mut File, _perm: u32) -> std::io::Result<()> { + // NOOP on windows + Ok(()) +} + +pub fn make_temp_dir( + dir: Option<&Path>, + prefix: Option<&str>, + suffix: Option<&str>, +) -> std::io::Result { + let prefix_ = prefix.unwrap_or(""); + let suffix_ = suffix.unwrap_or(""); + let mut buf: PathBuf = match dir { + Some(ref p) => p.to_path_buf(), + None => std::env::temp_dir(), + } + .join("_"); + let mut rng = rand::thread_rng(); + loop { + let unique = rng.gen::(); + buf.set_file_name(format!("{}{:08x}{}", prefix_, unique, suffix_)); + // TODO: on posix, set mode flags to 0o700. + let r = create_dir(buf.as_path()); + match r { + Err(ref e) if e.kind() == ErrorKind::AlreadyExists => continue, + Ok(_) => return Ok(buf), + Err(e) => return Err(e), + } + } +} + +pub fn mkdir(path: &Path, perm: u32, recursive: bool) -> std::io::Result<()> { + debug!("mkdir -p {}", path.display()); + let mut builder = DirBuilder::new(); + builder.recursive(recursive); + set_dir_permission(&mut builder, perm); + builder.create(path) +} + +#[cfg(any(unix))] +fn set_dir_permission(builder: &mut DirBuilder, perm: u32) { + debug!("set dir perm to {}", perm); + builder.mode(perm & 0o777); +} + +#[cfg(not(any(unix)))] +fn set_dir_permission(_builder: &mut DirBuilder, _perm: u32) { + // NOOP on windows +} + +pub fn normalize_path(path: &Path) -> String { + let s = String::from(path.to_str().unwrap()); + if cfg!(windows) { + // TODO This isn't correct. Probbly should iterate over components. + s.replace("\\", "/") + } else { + s + } +} + +#[cfg(unix)] +pub fn chown(path: &str, uid: u32, gid: u32) -> Result<(), ErrBox> { + let nix_uid = Uid::from_raw(uid); + let nix_gid = Gid::from_raw(gid); + unix_chown(path, Option::Some(nix_uid), Option::Some(nix_gid)) + .map_err(ErrBox::from) +} + +#[cfg(not(unix))] +pub fn chown(_path: &str, _uid: u32, _gid: u32) -> Result<(), ErrBox> { + // Noop + // TODO: implement chown for Windows + Err(crate::deno_error::op_not_implemented()) +} + +pub fn resolve_from_cwd(path: &str) -> Result<(PathBuf, String), ErrBox> { + let candidate_path = Path::new(path); + + let resolved_path = if candidate_path.is_absolute() { + candidate_path.to_owned() + } else { + let cwd = std::env::current_dir().unwrap(); + cwd.join(path) + }; + + // HACK: `Url::from_directory_path` is used here because it normalizes the path. + // Joining `/dev/deno/" with "./tests" using `PathBuf` yields `/deno/dev/./tests/`. + // On the other hand joining `/dev/deno/" with "./tests" using `Url` yields "/dev/deno/tests" + // - and that's what we want. + // There exists similar method on `PathBuf` - `PathBuf.canonicalize`, but the problem + // is `canonicalize` resolves symlinks and we don't want that. + // We just want o normalize the path... + let resolved_url = Url::from_file_path(resolved_path) + .expect("PathBuf should be parseable URL"); + let normalized_path = resolved_url + .to_file_path() + .expect("URL from PathBuf should be valid path"); + + let path_string = normalized_path.to_str().unwrap().to_string(); + + Ok((normalized_path, path_string)) +} diff --git a/ops/fs/lib.rs b/ops/fs/lib.rs new file mode 100644 index 00000000000000..1e7f21592dbe21 --- /dev/null +++ b/ops/fs/lib.rs @@ -0,0 +1,13 @@ +#[macro_use] +extern crate log; +#[macro_use] +extern crate serde_json; +extern crate serde; +extern crate serde_derive; + +pub mod fs; +mod ops; +mod state; + +pub use crate::ops::*; +pub use crate::state::TSFsOpsState; diff --git a/cli/ops/fs.rs b/ops/fs/ops.rs similarity index 90% rename from cli/ops/fs.rs rename to ops/fs/ops.rs index 24316e0ffee23e..0ccccbc8466c0c 100644 --- a/cli/ops/fs.rs +++ b/ops/fs/ops.rs @@ -1,19 +1,16 @@ -// 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, wrap_json_op, Deserialize, JsonOp}; -use crate::deno_error::DenoError; -use crate::deno_error::ErrorKind; +use crate::state::TSFsOpsState; +use crate::state::FsOpDispatcher; use crate::fs as deno_fs; -use crate::state::DenoOpDispatcher; -use crate::state::ThreadSafeState; -use deno::*; +use deno::PinnedBuf; +use deno::CoreOp; +use deno_dispatch_json::{blocking_json, wrap_json_op, Deserialize, JsonOp}; 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; @@ -26,10 +23,10 @@ struct ChdirArgs { directory: String, } -impl DenoOpDispatcher for OpChdir { +impl FsOpDispatcher for OpChdir { fn dispatch( &self, - _state: &ThreadSafeState, + _state: &TSFsOpsState, control: &[u8], buf: Option, ) -> CoreOp { @@ -60,10 +57,10 @@ struct MkdirArgs { mode: u32, } -impl DenoOpDispatcher for OpMkdir { +impl FsOpDispatcher for OpMkdir { fn dispatch( &self, - state: &ThreadSafeState, + state: &TSFsOpsState, control: &[u8], buf: Option, ) -> CoreOp { @@ -101,10 +98,10 @@ struct ChmodArgs { mode: u32, } -impl DenoOpDispatcher for OpChmod { +impl FsOpDispatcher for OpChmod { fn dispatch( &self, - state: &ThreadSafeState, + state: &TSFsOpsState, control: &[u8], buf: Option, ) -> CoreOp { @@ -150,10 +147,10 @@ struct ChownArgs { gid: u32, } -impl DenoOpDispatcher for OpChown { +impl FsOpDispatcher for OpChown { fn dispatch( &self, - state: &ThreadSafeState, + state: &TSFsOpsState, control: &[u8], buf: Option, ) -> CoreOp { @@ -192,10 +189,10 @@ struct RemoveArgs { recursive: bool, } -impl DenoOpDispatcher for OpRemove { +impl FsOpDispatcher for OpRemove { fn dispatch( &self, - state: &ThreadSafeState, + state: &TSFsOpsState, control: &[u8], buf: Option, ) -> CoreOp { @@ -241,10 +238,10 @@ struct CopyFileArgs { to: String, } -impl DenoOpDispatcher for OpCopyFile { +impl FsOpDispatcher for OpCopyFile { fn dispatch( &self, - state: &ThreadSafeState, + state: &TSFsOpsState, control: &[u8], buf: Option, ) -> CoreOp { @@ -265,10 +262,9 @@ impl DenoOpDispatcher for OpCopyFile { // 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(), - ); + return Err(std::io::Error::new( + std::io::ErrorKind::NotFound, "File not found".to_string() + ).into()); } fs::copy(&from, &to)?; @@ -315,10 +311,10 @@ struct StatArgs { lstat: bool, } -impl DenoOpDispatcher for OpStat { +impl FsOpDispatcher for OpStat { fn dispatch( &self, - state: &ThreadSafeState, + state: &TSFsOpsState, control: &[u8], buf: Option, ) -> CoreOp { @@ -372,10 +368,10 @@ struct ReadDirArgs { path: String, } -impl DenoOpDispatcher for OpReadDir { +impl FsOpDispatcher for OpReadDir { fn dispatch( &self, - state: &ThreadSafeState, + state: &TSFsOpsState, control: &[u8], buf: Option, ) -> CoreOp { @@ -433,10 +429,10 @@ struct RenameArgs { newpath: String, } -impl DenoOpDispatcher for OpRename { +impl FsOpDispatcher for OpRename { fn dispatch( &self, - state: &ThreadSafeState, + state: &TSFsOpsState, control: &[u8], buf: Option, ) -> CoreOp { @@ -480,10 +476,10 @@ struct LinkArgs { newname: String, } -impl DenoOpDispatcher for OpLink { +impl FsOpDispatcher for OpLink { fn dispatch( &self, - state: &ThreadSafeState, + state: &TSFsOpsState, control: &[u8], buf: Option, ) -> CoreOp { @@ -526,10 +522,10 @@ struct SymlinkArgs { newname: String, } -impl DenoOpDispatcher for OpSymlink { +impl FsOpDispatcher for OpSymlink { fn dispatch( &self, - state: &ThreadSafeState, + state: &TSFsOpsState, control: &[u8], buf: Option, ) -> CoreOp { @@ -545,9 +541,9 @@ impl DenoOpDispatcher for OpSymlink { state.check_write(&newname_)?; // TODO Use type for Windows. if cfg!(windows) { - return Err( - DenoError::new(ErrorKind::Other, "Not implemented".to_string()) - .into(), + return Err(std::io::Error::new( + std::io::ErrorKind::Other, "Not implemented".to_string() + ).into() ); } let is_sync = args.promise_id.is_none(); @@ -577,10 +573,10 @@ struct ReadLinkArgs { name: String, } -impl DenoOpDispatcher for OpReadLink { +impl FsOpDispatcher for OpReadLink { fn dispatch( &self, - state: &ThreadSafeState, + state: &TSFsOpsState, control: &[u8], buf: Option, ) -> CoreOp { @@ -621,10 +617,10 @@ struct TruncateArgs { len: u64, } -impl DenoOpDispatcher for OpTruncate { +impl FsOpDispatcher for OpTruncate { fn dispatch( &self, - state: &ThreadSafeState, + state: &TSFsOpsState, control: &[u8], buf: Option, ) -> CoreOp { @@ -667,10 +663,10 @@ struct MakeTempDirArgs { suffix: Option, } -impl DenoOpDispatcher for OpMakeTempDir { +impl FsOpDispatcher for OpMakeTempDir { fn dispatch( &self, - state: &ThreadSafeState, + state: &TSFsOpsState, control: &[u8], buf: Option, ) -> CoreOp { @@ -722,10 +718,10 @@ struct UtimeArgs { mtime: u64, } -impl DenoOpDispatcher for OpUtime { +impl FsOpDispatcher for OpUtime { fn dispatch( &self, - state: &ThreadSafeState, + state: &TSFsOpsState, control: &[u8], buf: Option, ) -> CoreOp { @@ -752,10 +748,10 @@ impl DenoOpDispatcher for OpUtime { pub struct OpCwd; -impl DenoOpDispatcher for OpCwd { +impl FsOpDispatcher for OpCwd { fn dispatch( &self, - _state: &ThreadSafeState, + _state: &TSFsOpsState, control: &[u8], buf: Option, ) -> CoreOp { @@ -772,3 +768,13 @@ impl DenoOpDispatcher for OpCwd { const NAME: &'static str = "cwd"; } + + + +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +} diff --git a/ops/fs/state.rs b/ops/fs/state.rs new file mode 100644 index 00000000000000..c2edecd782ae56 --- /dev/null +++ b/ops/fs/state.rs @@ -0,0 +1,102 @@ +use deno::PinnedBuf; +use deno::OpDispatcher; +use deno::Named; +use deno::CoreOp; +use deno::ErrBox; +use std::ops::Deref; +use std::sync::Arc; + +pub type CheckReadFn = dyn Fn(&str) -> Result<(), ErrBox> + Send + Sync + 'static; +pub type CheckWriteFn = dyn Fn(&str) -> Result<(), ErrBox> + Send + Sync + 'static; + +pub struct FsOpsState { + check_read_fn: Box, + check_write_fn: Box, +} + +// TODO(afinch7) maybe replace this with a common permissions +// trait? +/// Thread safe state for fs op dispatchers +pub struct TSFsOpsState(Arc); + +impl Clone for TSFsOpsState { + fn clone(&self) -> Self { + TSFsOpsState(self.0.clone()) + } +} + +impl Deref for TSFsOpsState { + type Target = Arc; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + + +impl TSFsOpsState { + pub fn new(check_read_fn: R, check_write_fn: W) -> Self + where + R: Fn(&str) -> Result<(), ErrBox> + Send + Sync + 'static, + W: Fn(&str) -> Result<(), ErrBox> + Send + Sync + 'static, + { + Self(Arc::new(FsOpsState{ + check_read_fn: Box::new(check_read_fn), + check_write_fn: Box::new(check_write_fn), + })) + } + + #[inline] + pub fn check_read(&self, filename: &str) -> Result<(), ErrBox> { + (*self.check_read_fn)(filename) + } + + #[inline] + pub fn check_write(&self, filename: &str) -> Result<(), ErrBox> { + (*self.check_write_fn)(filename) + } + + pub fn wrap_op(&self, d: D) -> WrappedFsOpDispatcher + where + D: FsOpDispatcher, + { + WrappedFsOpDispatcher::new(d, self.clone()) + } +} + +pub trait FsOpDispatcher: Send + Sync { + fn dispatch( + &self, + state: &TSFsOpsState, + args: &[u8], + buf: Option, + ) -> CoreOp; + + const NAME: &'static str; +} + +pub struct WrappedFsOpDispatcher { + inner: D, + state: TSFsOpsState, +} + +impl WrappedFsOpDispatcher { + pub fn new(d: D, state: TSFsOpsState) -> Self { + Self { inner: d, state } + } +} + +impl OpDispatcher for WrappedFsOpDispatcher +where + D: FsOpDispatcher, +{ + fn dispatch(&self, control: &[u8], zero_copy: Option) -> CoreOp { + self.inner.dispatch(&self.state, control, zero_copy) + } +} + +impl Named for WrappedFsOpDispatcher +where + D: FsOpDispatcher, +{ + const NAME: &'static str = D::NAME; +}