// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. use deno_core::error::null_opbuf; use deno_core::error::resource_unavailable; use deno_core::error::AnyError; use deno_core::error::{bad_resource_id, not_supported}; use deno_core::AsyncMutFuture; use deno_core::AsyncRefCell; use deno_core::CancelHandle; use deno_core::CancelTryFuture; use deno_core::JsRuntime; use deno_core::OpState; use deno_core::RcRef; use deno_core::Resource; use deno_core::ResourceId; use deno_core::ZeroCopyBuf; use std::borrow::Cow; use std::cell::RefCell; use std::io::Read; use std::io::Write; use std::rc::Rc; use tokio::io::split; use tokio::io::AsyncRead; use tokio::io::AsyncReadExt; use tokio::io::AsyncWrite; use tokio::io::AsyncWriteExt; use tokio::io::ReadHalf; use tokio::io::WriteHalf; use tokio::net::tcp; use tokio::net::TcpStream; use tokio::process; use tokio_rustls as tls; #[cfg(unix)] use std::os::unix::io::FromRawFd; #[cfg(unix)] use tokio::net::unix; #[cfg(windows)] use std::os::windows::io::FromRawHandle; lazy_static::lazy_static! { /// Due to portability issues on Windows handle to stdout is created from raw /// file descriptor. The caveat of that approach is fact that when this /// handle is dropped underlying file descriptor is closed - that is highly /// not desirable in case of stdout. That's why we store this global handle /// that is then cloned when obtaining stdio for process. In turn when /// resource table is dropped storing reference to that handle, the handle /// itself won't be closed (so Deno.core.print) will still work. // TODO(ry) It should be possible to close stdout. static ref STDIN_HANDLE: Option = { #[cfg(not(windows))] let stdin = unsafe { Some(std::fs::File::from_raw_fd(0)) }; #[cfg(windows)] let stdin = unsafe { let handle = winapi::um::processenv::GetStdHandle( winapi::um::winbase::STD_INPUT_HANDLE, ); if handle.is_null() { return None; } Some(std::fs::File::from_raw_handle(handle)) }; stdin }; static ref STDOUT_HANDLE: Option = { #[cfg(not(windows))] let stdout = unsafe { Some(std::fs::File::from_raw_fd(1)) }; #[cfg(windows)] let stdout = unsafe { let handle = winapi::um::processenv::GetStdHandle( winapi::um::winbase::STD_OUTPUT_HANDLE, ); if handle.is_null() { return None; } Some(std::fs::File::from_raw_handle(handle)) }; stdout }; static ref STDERR_HANDLE: Option = { #[cfg(not(windows))] let stderr = unsafe { Some(std::fs::File::from_raw_fd(2)) }; #[cfg(windows)] let stderr = unsafe { let handle = winapi::um::processenv::GetStdHandle( winapi::um::winbase::STD_ERROR_HANDLE, ); if handle.is_null() { return None; } Some(std::fs::File::from_raw_handle(handle)) }; stderr }; } pub fn init(rt: &mut JsRuntime) { super::reg_async(rt, "op_read_async", op_read_async); super::reg_async(rt, "op_write_async", op_write_async); super::reg_sync(rt, "op_read_sync", op_read_sync); super::reg_sync(rt, "op_write_sync", op_write_sync); super::reg_async(rt, "op_shutdown", op_shutdown); } pub fn get_stdio() -> ( Option, Option, Option, ) { let stdin = get_stdio_stream(&STDIN_HANDLE, "stdin"); let stdout = get_stdio_stream(&STDOUT_HANDLE, "stdout"); let stderr = get_stdio_stream(&STDERR_HANDLE, "stderr"); (stdin, stdout, stderr) } fn get_stdio_stream( handle: &Option, name: &str, ) -> Option { match handle { None => None, Some(file_handle) => match file_handle.try_clone() { Ok(clone) => { let tokio_file = tokio::fs::File::from_std(clone); Some(StdFileResource::stdio(tokio_file, name)) } Err(_e) => None, }, } } #[cfg(unix)] use nix::sys::termios; #[derive(Default)] pub struct TtyMetadata { #[cfg(unix)] pub mode: Option, } #[derive(Default)] pub struct FileMetadata { pub tty: TtyMetadata, } #[derive(Debug)] pub struct WriteOnlyResource { stream: AsyncRefCell, } impl From for WriteOnlyResource { fn from(stream: S) -> Self { Self { stream: stream.into(), } } } impl WriteOnlyResource where S: AsyncWrite + Unpin + 'static, { pub fn borrow_mut(self: &Rc) -> AsyncMutFuture { RcRef::map(self, |r| &r.stream).borrow_mut() } async fn write(self: &Rc, buf: &[u8]) -> Result { let mut stream = self.borrow_mut().await; let nwritten = stream.write(buf).await?; Ok(nwritten) } async fn shutdown(self: &Rc) -> Result<(), AnyError> { let mut stream = self.borrow_mut().await; stream.shutdown().await?; Ok(()) } } #[derive(Debug)] pub struct ReadOnlyResource { stream: AsyncRefCell, cancel_handle: CancelHandle, } impl From for ReadOnlyResource { fn from(stream: S) -> Self { Self { stream: stream.into(), cancel_handle: Default::default(), } } } impl ReadOnlyResource where S: AsyncRead + Unpin + 'static, { pub fn borrow_mut(self: &Rc) -> AsyncMutFuture { RcRef::map(self, |r| &r.stream).borrow_mut() } pub fn cancel_handle(self: &Rc) -> RcRef { RcRef::map(self, |r| &r.cancel_handle) } pub fn cancel_read_ops(&self) { self.cancel_handle.cancel() } async fn read(self: &Rc, buf: &mut [u8]) -> Result { let mut rd = self.borrow_mut().await; let nread = rd.read(buf).try_or_cancel(self.cancel_handle()).await?; Ok(nread) } } /// A full duplex resource has a read and write ends that are completely /// independent, like TCP/Unix sockets and TLS streams. #[derive(Debug)] pub struct FullDuplexResource { rd: AsyncRefCell, wr: AsyncRefCell, // When a full-duplex resource is closed, all pending 'read' ops are // canceled, while 'write' ops are allowed to complete. Therefore only // 'read' futures should be attached to this cancel handle. cancel_handle: CancelHandle, } impl FullDuplexResource where R: AsyncRead + Unpin + 'static, W: AsyncWrite + Unpin + 'static, { pub fn new((rd, wr): (R, W)) -> Self { Self { rd: rd.into(), wr: wr.into(), cancel_handle: Default::default(), } } pub fn into_inner(self) -> (R, W) { (self.rd.into_inner(), self.wr.into_inner()) } pub fn rd_borrow_mut(self: &Rc) -> AsyncMutFuture { RcRef::map(self, |r| &r.rd).borrow_mut() } pub fn wr_borrow_mut(self: &Rc) -> AsyncMutFuture { RcRef::map(self, |r| &r.wr).borrow_mut() } pub fn cancel_handle(self: &Rc) -> RcRef { RcRef::map(self, |r| &r.cancel_handle) } pub fn cancel_read_ops(&self) { self.cancel_handle.cancel() } async fn read(self: &Rc, buf: &mut [u8]) -> Result { let mut rd = self.rd_borrow_mut().await; let nread = rd.read(buf).try_or_cancel(self.cancel_handle()).await?; Ok(nread) } async fn write(self: &Rc, buf: &[u8]) -> Result { let mut wr = self.wr_borrow_mut().await; let nwritten = wr.write(buf).await?; Ok(nwritten) } async fn shutdown(self: &Rc) -> Result<(), AnyError> { let mut wr = self.wr_borrow_mut().await; wr.shutdown().await?; Ok(()) } } pub type FullDuplexSplitResource = FullDuplexResource, WriteHalf>; impl From for FullDuplexSplitResource where S: AsyncRead + AsyncWrite + 'static, { fn from(stream: S) -> Self { Self::new(split(stream)) } } pub type ChildStdinResource = WriteOnlyResource; impl Resource for ChildStdinResource { fn name(&self) -> Cow { "childStdin".into() } } pub type ChildStdoutResource = ReadOnlyResource; impl Resource for ChildStdoutResource { fn name(&self) -> Cow { "childStdout".into() } fn close(self: Rc) { self.cancel_read_ops(); } } pub type ChildStderrResource = ReadOnlyResource; impl Resource for ChildStderrResource { fn name(&self) -> Cow { "childStderr".into() } fn close(self: Rc) { self.cancel_read_ops(); } } pub type TcpStreamResource = FullDuplexResource; impl Resource for TcpStreamResource { fn name(&self) -> Cow { "tcpStream".into() } fn close(self: Rc) { self.cancel_read_ops(); } } pub type TlsClientStreamResource = FullDuplexSplitResource>; impl Resource for TlsClientStreamResource { fn name(&self) -> Cow { "tlsClientStream".into() } fn close(self: Rc) { self.cancel_read_ops(); } } pub type TlsServerStreamResource = FullDuplexSplitResource>; impl Resource for TlsServerStreamResource { fn name(&self) -> Cow { "tlsServerStream".into() } fn close(self: Rc) { self.cancel_read_ops(); } } #[cfg(unix)] pub type UnixStreamResource = FullDuplexResource; #[cfg(not(unix))] struct UnixStreamResource; #[cfg(not(unix))] impl UnixStreamResource { async fn read(self: &Rc, _buf: &mut [u8]) -> Result { unreachable!() } async fn write(self: &Rc, _buf: &[u8]) -> Result { unreachable!() } async fn shutdown(self: &Rc) -> Result<(), AnyError> { unreachable!() } fn cancel_read_ops(&self) { unreachable!() } } impl Resource for UnixStreamResource { fn name(&self) -> Cow { "unixStream".into() } fn close(self: Rc) { self.cancel_read_ops(); } } #[derive(Debug, Default)] pub struct StdFileResource { pub fs_file: Option, Option)>>, cancel: CancelHandle, name: String, } impl StdFileResource { pub fn stdio(fs_file: tokio::fs::File, name: &str) -> Self { Self { fs_file: Some(AsyncRefCell::new(( Some(fs_file), Some(FileMetadata::default()), ))), name: name.to_string(), ..Default::default() } } pub fn fs_file(fs_file: tokio::fs::File) -> Self { Self { fs_file: Some(AsyncRefCell::new(( Some(fs_file), Some(FileMetadata::default()), ))), name: "fsFile".to_string(), ..Default::default() } } async fn read(self: &Rc, buf: &mut [u8]) -> Result { if self.fs_file.is_some() { let mut fs_file = RcRef::map(&*self, |r| r.fs_file.as_ref().unwrap()) .borrow_mut() .await; let nwritten = fs_file.0.as_mut().unwrap().read(buf).await?; return Ok(nwritten); } else { Err(resource_unavailable()) } } async fn write(self: &Rc, buf: &[u8]) -> Result { if self.fs_file.is_some() { let mut fs_file = RcRef::map(&*self, |r| r.fs_file.as_ref().unwrap()) .borrow_mut() .await; let nwritten = fs_file.0.as_mut().unwrap().write(buf).await?; fs_file.0.as_mut().unwrap().flush().await?; return Ok(nwritten); } else { Err(resource_unavailable()) } } pub fn with( state: &mut OpState, rid: ResourceId, mut f: F, ) -> Result where F: FnMut(Result<&mut std::fs::File, ()>) -> Result, { // First we look up the rid in the resource table. let resource = state .resource_table .get::(rid) .ok_or_else(bad_resource_id)?; // Sync write only works for FsFile. It doesn't make sense to do this // for non-blocking sockets. So we error out if not FsFile. if resource.fs_file.is_none() { return f(Err(())); } // The object in the resource table is a tokio::fs::File - but in // order to do a blocking write on it, we must turn it into a // std::fs::File. Hopefully this code compiles down to nothing. let fs_file_resource = RcRef::map(&resource, |r| r.fs_file.as_ref().unwrap()).try_borrow_mut(); if let Some(mut fs_file) = fs_file_resource { let tokio_file = fs_file.0.take().unwrap(); match tokio_file.try_into_std() { Ok(mut std_file) => { let result = f(Ok(&mut std_file)); // Turn the std_file handle back into a tokio file, put it back // in the resource table. let tokio_file = tokio::fs::File::from_std(std_file); fs_file.0 = Some(tokio_file); // return the result. result } Err(tokio_file) => { // This function will return an error containing the file if // some operation is in-flight. fs_file.0 = Some(tokio_file); Err(resource_unavailable()) } } } else { Err(resource_unavailable()) } } } impl Resource for StdFileResource { fn name(&self) -> Cow { self.name.as_str().into() } fn close(self: Rc) { // TODO: do not cancel file I/O when file is writable. self.cancel.cancel() } } fn op_read_sync( state: &mut OpState, rid: ResourceId, buf: Option, ) -> Result { let mut buf = buf.ok_or_else(null_opbuf)?; StdFileResource::with(state, rid, move |r| match r { Ok(std_file) => std_file .read(&mut buf) .map(|n: usize| n as u32) .map_err(AnyError::from), Err(_) => Err(not_supported()), }) } async fn op_read_async( state: Rc>, rid: ResourceId, buf: Option, ) -> Result { let buf = &mut buf.ok_or_else(null_opbuf)?; let resource = state .borrow() .resource_table .get_any(rid) .ok_or_else(bad_resource_id)?; let nread = if let Some(s) = resource.downcast_rc::() { s.read(buf).await? } else if let Some(s) = resource.downcast_rc::() { s.read(buf).await? } else if let Some(s) = resource.downcast_rc::() { s.read(buf).await? } else if let Some(s) = resource.downcast_rc::() { s.read(buf).await? } else if let Some(s) = resource.downcast_rc::() { s.read(buf).await? } else if let Some(s) = resource.downcast_rc::() { s.read(buf).await? } else if let Some(s) = resource.downcast_rc::() { s.read(buf).await? } else { return Err(not_supported()); }; Ok(nread as u32) } fn op_write_sync( state: &mut OpState, rid: ResourceId, buf: Option, ) -> Result { let buf = buf.ok_or_else(null_opbuf)?; StdFileResource::with(state, rid, move |r| match r { Ok(std_file) => std_file .write(&buf) .map(|nwritten: usize| nwritten as u32) .map_err(AnyError::from), Err(_) => Err(not_supported()), }) } async fn op_write_async( state: Rc>, rid: ResourceId, buf: Option, ) -> Result { let buf = &buf.ok_or_else(null_opbuf)?; let resource = state .borrow() .resource_table .get_any(rid) .ok_or_else(bad_resource_id)?; let nwritten = if let Some(s) = resource.downcast_rc::() { s.write(buf).await? } else if let Some(s) = resource.downcast_rc::() { s.write(buf).await? } else if let Some(s) = resource.downcast_rc::() { s.write(buf).await? } else if let Some(s) = resource.downcast_rc::() { s.write(buf).await? } else if let Some(s) = resource.downcast_rc::() { s.write(buf).await? } else if let Some(s) = resource.downcast_rc::() { s.write(buf).await? } else { return Err(not_supported()); }; Ok(nwritten as u32) } async fn op_shutdown( state: Rc>, rid: ResourceId, _zero_copy: Option, ) -> Result<(), AnyError> { let resource = state .borrow() .resource_table .get_any(rid) .ok_or_else(bad_resource_id)?; if let Some(s) = resource.downcast_rc::() { s.shutdown().await?; } else if let Some(s) = resource.downcast_rc::() { s.shutdown().await?; } else if let Some(s) = resource.downcast_rc::() { s.shutdown().await?; } else if let Some(s) = resource.downcast_rc::() { s.shutdown().await?; } else if let Some(s) = resource.downcast_rc::() { s.shutdown().await?; } else { return Err(not_supported()); } Ok(()) }