diff options
Diffstat (limited to 'cli/ops')
-rw-r--r-- | cli/ops/files.rs | 11 | ||||
-rw-r--r-- | cli/ops/io.rs | 28 | ||||
-rw-r--r-- | cli/ops/mod.rs | 1 | ||||
-rw-r--r-- | cli/ops/os.rs | 14 | ||||
-rw-r--r-- | cli/ops/process.rs | 2 | ||||
-rw-r--r-- | cli/ops/tty.rs | 246 |
6 files changed, 275 insertions, 27 deletions
diff --git a/cli/ops/files.rs b/cli/ops/files.rs index 4bf8b1688..916cbdc69 100644 --- a/cli/ops/files.rs +++ b/cli/ops/files.rs @@ -1,6 +1,6 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. use super::dispatch_json::{Deserialize, JsonOp, Value}; -use super::io::StreamResource; +use super::io::{FileMetadata, StreamResource}; use crate::fs as deno_fs; use crate::op_error::OpError; use crate::state::State; @@ -125,9 +125,10 @@ fn op_open( let fut = async move { let fs_file = open_options.open(filename).await?; let mut state = state_.borrow_mut(); - let rid = state - .resource_table - .add("fsFile", Box::new(StreamResource::FsFile(fs_file))); + let rid = state.resource_table.add( + "fsFile", + Box::new(StreamResource::FsFile(fs_file, FileMetadata::default())), + ); Ok(json!(rid)) }; @@ -197,7 +198,7 @@ fn op_seek( .ok_or_else(OpError::bad_resource)?; let tokio_file = match resource { - StreamResource::FsFile(ref file) => file, + StreamResource::FsFile(ref file, _) => file, _ => return Err(OpError::bad_resource()), }; let mut file = futures::executor::block_on(tokio_file.try_clone())?; diff --git a/cli/ops/io.rs b/cli/ops/io.rs index 8edb1f748..ad3949a03 100644 --- a/cli/ops/io.rs +++ b/cli/ops/io.rs @@ -57,7 +57,7 @@ pub fn init(i: &mut Isolate, s: &State) { } pub fn get_stdio() -> (StreamResource, StreamResource, StreamResource) { - let stdin = StreamResource::Stdin(tokio::io::stdin()); + let stdin = StreamResource::Stdin(tokio::io::stdin(), TTYMetadata::default()); let stdout = StreamResource::Stdout({ let stdout = STDOUT_HANDLE .try_clone() @@ -69,11 +69,25 @@ pub fn get_stdio() -> (StreamResource, StreamResource, StreamResource) { (stdin, stdout, stderr) } +#[cfg(unix)] +use nix::sys::termios; + +#[derive(Default)] +pub struct TTYMetadata { + #[cfg(unix)] + pub mode: Option<termios::Termios>, +} + +#[derive(Default)] +pub struct FileMetadata { + pub tty: TTYMetadata, +} + pub enum StreamResource { - Stdin(tokio::io::Stdin), + Stdin(tokio::io::Stdin, TTYMetadata), Stdout(tokio::fs::File), Stderr(tokio::io::Stderr), - FsFile(tokio::fs::File), + FsFile(tokio::fs::File, FileMetadata), TcpStream(tokio::net::TcpStream), ServerTlsStream(Box<ServerTlsStream<TcpStream>>), ClientTlsStream(Box<ClientTlsStream<TcpStream>>), @@ -101,8 +115,8 @@ impl DenoAsyncRead for StreamResource { ) -> Poll<Result<usize, OpError>> { use StreamResource::*; let mut f: Pin<Box<dyn AsyncRead>> = match self { - FsFile(f) => Box::pin(f), - Stdin(f) => Box::pin(f), + FsFile(f, _) => Box::pin(f), + Stdin(f, _) => Box::pin(f), TcpStream(f) => Box::pin(f), ClientTlsStream(f) => Box::pin(f), ServerTlsStream(f) => Box::pin(f), @@ -203,7 +217,7 @@ impl DenoAsyncWrite for StreamResource { ) -> Poll<Result<usize, OpError>> { use StreamResource::*; let mut f: Pin<Box<dyn AsyncWrite>> = match self { - FsFile(f) => Box::pin(f), + FsFile(f, _) => Box::pin(f), Stdout(f) => Box::pin(f), Stderr(f) => Box::pin(f), TcpStream(f) => Box::pin(f), @@ -220,7 +234,7 @@ impl DenoAsyncWrite for StreamResource { fn poll_flush(&mut self, cx: &mut Context) -> Poll<Result<(), OpError>> { use StreamResource::*; let mut f: Pin<Box<dyn AsyncWrite>> = match self { - FsFile(f) => Box::pin(f), + FsFile(f, _) => Box::pin(f), Stdout(f) => Box::pin(f), Stderr(f) => Box::pin(f), TcpStream(f) => Box::pin(f), diff --git a/cli/ops/mod.rs b/cli/ops/mod.rs index 7746143db..32d4e3b96 100644 --- a/cli/ops/mod.rs +++ b/cli/ops/mod.rs @@ -28,5 +28,6 @@ pub mod runtime_compiler; pub mod signal; pub mod timers; pub mod tls; +pub mod tty; pub mod web_worker; pub mod worker_host; diff --git a/cli/ops/os.rs b/cli/ops/os.rs index c0479c656..2df9470dd 100644 --- a/cli/ops/os.rs +++ b/cli/ops/os.rs @@ -2,7 +2,6 @@ use super::dispatch_json::{Deserialize, JsonOp, Value}; use crate::op_error::OpError; use crate::state::State; -use atty; use deno_core::*; use std::collections::HashMap; use std::env; @@ -12,7 +11,6 @@ use url::Url; pub fn init(i: &mut Isolate, s: &State) { i.register_op("op_exit", s.stateful_json_op(op_exit)); - i.register_op("op_is_tty", s.stateful_json_op(op_is_tty)); i.register_op("op_env", s.stateful_json_op(op_env)); i.register_op("op_exec_path", s.stateful_json_op(op_exec_path)); i.register_op("op_set_env", s.stateful_json_op(op_set_env)); @@ -151,18 +149,6 @@ fn op_exit( std::process::exit(args.code) } -fn op_is_tty( - _s: &State, - _args: Value, - _zero_copy: Option<ZeroCopyBuf>, -) -> Result<JsonOp, OpError> { - Ok(JsonOp::Sync(json!({ - "stdin": atty::is(atty::Stream::Stdin), - "stdout": atty::is(atty::Stream::Stdout), - "stderr": atty::is(atty::Stream::Stderr), - }))) -} - fn op_loadavg( state: &State, _args: Value, diff --git a/cli/ops/process.rs b/cli/ops/process.rs index 9da87bd39..82ac25bbe 100644 --- a/cli/ops/process.rs +++ b/cli/ops/process.rs @@ -33,7 +33,7 @@ fn clone_file(rid: u32, state: &State) -> Result<std::fs::File, OpError> { .get_mut::<StreamResource>(rid) .ok_or_else(OpError::bad_resource)?; let file = match repr { - StreamResource::FsFile(ref mut file) => file, + StreamResource::FsFile(ref mut file, _) => file, _ => return Err(OpError::bad_resource()), }; let tokio_file = futures::executor::block_on(file.try_clone())?; diff --git a/cli/ops/tty.rs b/cli/ops/tty.rs new file mode 100644 index 000000000..e83d2edfd --- /dev/null +++ b/cli/ops/tty.rs @@ -0,0 +1,246 @@ +use super::dispatch_json::JsonOp; +use super::io::StreamResource; +use crate::op_error::OpError; +use crate::ops::json_op; +use crate::state::State; +use atty; +use deno_core::*; +#[cfg(unix)] +use nix::sys::termios; +use serde_derive::Deserialize; +use serde_json::Value; + +#[cfg(windows)] +use winapi::shared::minwindef::DWORD; +#[cfg(windows)] +use winapi::um::wincon; +#[cfg(windows)] +const RAW_MODE_MASK: DWORD = wincon::ENABLE_LINE_INPUT + | wincon::ENABLE_ECHO_INPUT + | wincon::ENABLE_PROCESSED_INPUT; +#[cfg(windows)] +fn get_windows_handle( + f: &std::fs::File, +) -> Result<std::os::windows::io::RawHandle, OpError> { + use std::os::windows::io::AsRawHandle; + use winapi::um::handleapi; + + let handle = f.as_raw_handle(); + if handle == handleapi::INVALID_HANDLE_VALUE { + return Err(OpError::from(std::io::Error::last_os_error())); + } else if handle.is_null() { + return Err(OpError::other("null handle".to_owned())); + } + Ok(handle) +} + +pub fn init(i: &mut Isolate, s: &State) { + i.register_op("op_set_raw", s.core_op(json_op(s.stateful_op(op_set_raw)))); + i.register_op("op_isatty", s.core_op(json_op(s.stateful_op(op_isatty)))); +} + +#[cfg(windows)] +macro_rules! wincheck { + ($funcall:expr) => {{ + let rc = unsafe { $funcall }; + if rc == 0 { + Err(OpError::from(std::io::Error::last_os_error()))?; + } + rc + }}; +} + +#[derive(Deserialize)] +struct SetRawArgs { + rid: u32, + mode: bool, +} + +pub fn op_set_raw( + state_: &State, + args: Value, + _zero_copy: Option<ZeroCopyBuf>, +) -> Result<JsonOp, OpError> { + let args: SetRawArgs = serde_json::from_value(args)?; + let rid = args.rid; + let is_raw = args.mode; + + // From https://github.com/kkawakam/rustyline/blob/master/src/tty/windows.rs + // and https://github.com/kkawakam/rustyline/blob/master/src/tty/unix.rs + // and https://github.com/crossterm-rs/crossterm/blob/e35d4d2c1cc4c919e36d242e014af75f6127ab50/src/terminal/sys/windows.rs + // Copyright (c) 2015 Katsu Kawakami & Rustyline authors. MIT license. + // Copyright (c) 2019 Timon. MIT license. + #[cfg(windows)] + { + use std::os::windows::io::AsRawHandle; + use winapi::um::{consoleapi, handleapi}; + + let state = state_.borrow_mut(); + let resource = state.resource_table.get::<StreamResource>(rid); + if resource.is_none() { + return Err(OpError::bad_resource()); + } + + // For now, only stdin. + let handle = match resource.unwrap() { + StreamResource::Stdin(_, _) => std::io::stdin().as_raw_handle(), + StreamResource::FsFile(f, _) => { + let tokio_file = futures::executor::block_on(f.try_clone())?; + let std_file = futures::executor::block_on(tokio_file.into_std()); + std_file.as_raw_handle() + } + _ => { + return Err(OpError::other("Not supported".to_owned())); + } + }; + + if handle == handleapi::INVALID_HANDLE_VALUE { + return Err(OpError::from(std::io::Error::last_os_error())); + } else if handle.is_null() { + return Err(OpError::other("null handle".to_owned())); + } + let mut original_mode: DWORD = 0; + wincheck!(consoleapi::GetConsoleMode(handle, &mut original_mode)); + let new_mode = if is_raw { + original_mode & !RAW_MODE_MASK + } else { + original_mode | RAW_MODE_MASK + }; + wincheck!(consoleapi::SetConsoleMode(handle, new_mode)); + + Ok(JsonOp::Sync(json!({}))) + } + #[cfg(unix)] + { + use std::os::unix::io::AsRawFd; + + let mut state = state_.borrow_mut(); + let resource = state.resource_table.get_mut::<StreamResource>(rid); + if resource.is_none() { + return Err(OpError::bad_resource()); + } + + if is_raw { + let (raw_fd, maybe_tty_mode) = match resource.unwrap() { + StreamResource::Stdin(_, ref mut metadata) => { + (std::io::stdin().as_raw_fd(), &mut metadata.mode) + } + StreamResource::FsFile(f, ref mut metadata) => { + let tokio_file = futures::executor::block_on(f.try_clone())?; + let std_file = futures::executor::block_on(tokio_file.into_std()); + (std_file.as_raw_fd(), &mut metadata.tty.mode) + } + _ => { + return Err(OpError::other("Not supported".to_owned())); + } + }; + + if maybe_tty_mode.is_some() { + // Already raw. Skip. + return Ok(JsonOp::Sync(json!({}))); + } + + let original_mode = termios::tcgetattr(raw_fd)?; + let mut raw = original_mode.clone(); + // Save original mode. + maybe_tty_mode.replace(original_mode); + + raw.input_flags &= !(termios::InputFlags::BRKINT + | termios::InputFlags::ICRNL + | termios::InputFlags::INPCK + | termios::InputFlags::ISTRIP + | termios::InputFlags::IXON); + + raw.control_flags |= termios::ControlFlags::CS8; + + raw.local_flags &= !(termios::LocalFlags::ECHO + | termios::LocalFlags::ICANON + | termios::LocalFlags::IEXTEN + | termios::LocalFlags::ISIG); + raw.control_chars[termios::SpecialCharacterIndices::VMIN as usize] = 1; + raw.control_chars[termios::SpecialCharacterIndices::VTIME as usize] = 0; + termios::tcsetattr(raw_fd, termios::SetArg::TCSADRAIN, &raw)?; + Ok(JsonOp::Sync(json!({}))) + } else { + // Try restore saved mode. + let (raw_fd, maybe_tty_mode) = match resource.unwrap() { + StreamResource::Stdin(_, ref mut metadata) => { + (std::io::stdin().as_raw_fd(), &mut metadata.mode) + } + StreamResource::FsFile(f, ref mut metadata) => { + let tokio_file = futures::executor::block_on(f.try_clone())?; + let std_file = futures::executor::block_on(tokio_file.into_std()); + (std_file.as_raw_fd(), &mut metadata.tty.mode) + } + _ => { + return Err(OpError::other("Not supported".to_owned())); + } + }; + + if let Some(mode) = maybe_tty_mode.take() { + termios::tcsetattr(raw_fd, termios::SetArg::TCSADRAIN, &mode)?; + } + + Ok(JsonOp::Sync(json!({}))) + } + } +} + +#[derive(Deserialize)] +struct IsattyArgs { + rid: u32, +} + +pub fn op_isatty( + state_: &State, + args: Value, + _zero_copy: Option<ZeroCopyBuf>, +) -> Result<JsonOp, OpError> { + let args: IsattyArgs = serde_json::from_value(args)?; + let rid = args.rid; + + let state = state_.borrow_mut(); + if !state.resource_table.has(rid) { + return Err(OpError::bad_resource()); + } + + let resource = state.resource_table.get::<StreamResource>(rid); + if resource.is_none() { + return Ok(JsonOp::Sync(json!(false))); + } + + match resource.unwrap() { + StreamResource::Stdin(_, _) => { + Ok(JsonOp::Sync(json!(atty::is(atty::Stream::Stdin)))) + } + StreamResource::Stdout(_) => { + Ok(JsonOp::Sync(json!(atty::is(atty::Stream::Stdout)))) + } + StreamResource::Stderr(_) => { + Ok(JsonOp::Sync(json!(atty::is(atty::Stream::Stderr)))) + } + StreamResource::FsFile(f, _) => { + let tokio_file = futures::executor::block_on(f.try_clone())?; + let std_file = futures::executor::block_on(tokio_file.into_std()); + #[cfg(windows)] + { + use winapi::um::consoleapi; + + let handle = get_windows_handle(&std_file)?; + let mut test_mode: DWORD = 0; + // If I cannot get mode out of console, it is not a console. + let result = + unsafe { consoleapi::GetConsoleMode(handle, &mut test_mode) != 0 }; + Ok(JsonOp::Sync(json!(result))) + } + #[cfg(unix)] + { + use std::os::unix::io::AsRawFd; + let raw_fd = std_file.as_raw_fd(); + let result = unsafe { libc::isatty(raw_fd as libc::c_int) == 1 }; + Ok(JsonOp::Sync(json!(result))) + } + } + _ => Ok(JsonOp::Sync(json!(false))), + } +} |