diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ae75f9b6e7ad8c..3abdbfc90397c9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -84,7 +84,7 @@ jobs: - name: Install Rust uses: hecrj/setup-rust-action@v1 with: - rust-version: 1.55.0 + rust-version: 1.56.0 - name: Install clippy and rustfmt if: matrix.job == 'lint' @@ -184,15 +184,20 @@ jobs: sudo ln --force --target /sysroot/etc \ /etc/passwd /etc/shadow /etc/group /etc/gshadow - # Install clang-12 and lld-12 into the chroot environment. - echo "deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-12 main" | - sudo dd of=/sysroot/etc/apt/sources.list.d/llvm-toolchain-bionic-12.list + # Install clang-13 and lld-13 into the chroot environment. + echo "deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-13 main" | + sudo dd of=/sysroot/etc/apt/sources.list.d/llvm-toolchain-bionic-13.list curl https://apt.llvm.org/llvm-snapshot.gpg.key | gpg --dearmor | sudo dd of=/sysroot/etc/apt/trusted.gpg.d/llvm-snapshot.gpg sudo chroot /sysroot apt update -y sudo chroot /sysroot apt install --no-install-recommends -y \ - clang-12 lld-12 + clang-13 lld-13 + + # Redirect ld invocations to ld.lld-13 inside the chroot environment. + # Setting the 'LD' environment variable doesn't always work. + sudo chroot /sysroot bash -c \ + 'ln -f "$(which ld.lld-13)" "$(which ld)"' # Make rust available inside the chroot environment. sudo mkdir -p /sysroot/usr/share/rust @@ -221,8 +226,8 @@ jobs: export CARGO_PROFILE_RELEASE_LTO=false export RUSTFLAGS=" -C linker-plugin-lto=true - -C linker=clang-12 - -C link-arg=-fuse-ld=lld-12 + -C linker=clang-13 + -C link-arg=-fuse-ld=lld-13 -C link-arg=-Wl,--thinlto-cache-dir=$(pwd)/target/release/lto-cache -C link-arg=-Wl,--thinlto-cache-policy,cache_size_bytes=700m -D warnings @@ -231,8 +236,9 @@ jobs: unset RUSTC_FORCE_INCREMENTAL # C build configuration. - export CC=clang-12 # Compile c source files with clang. + export CC=clang-13 # Compile c source files with clang. export CFLAGS=-flto=thin # Tell clang to produce llvm bitcode. + export LD=ld.lld-13 # Use the lld linker. # Miscellaneous flags. export CARGO_TERM_COLOR=always @@ -260,7 +266,7 @@ jobs: ~/.cargo/registry/index ~/.cargo/registry/cache ~/.cargo/git/db - key: l-cargo-home-${{ matrix.os }}-${{ hashFiles('Cargo.lock') }} + key: 0-cargo-home-${{ matrix.os }}-${{ hashFiles('Cargo.lock') }} # In main branch, always creates fresh cache - name: Cache build output (main) @@ -276,7 +282,7 @@ jobs: !./target/*/*.zip !./target/*/*.tar.gz key: | - l-cargo-target-${{ matrix.os }}-${{ matrix.profile }}-${{ github.sha }} + 0-cargo-target-${{ matrix.os }}-${{ matrix.profile }}-${{ github.sha }} # Restore cache from the latest 'main' branch build. - name: Cache build output (PR) @@ -292,7 +298,7 @@ jobs: !./target/*/*.tar.gz key: never_saved restore-keys: | - l-cargo-target-${{ matrix.os }}-${{ matrix.profile }}- + 0-cargo-target-${{ matrix.os }}-${{ matrix.profile }}- # Don't save cache after building PRs or branches other than 'main'. - name: Skip save cache (PR) diff --git a/cli/main.rs b/cli/main.rs index d36145bea87953..f4d4046df10cde 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -35,6 +35,7 @@ mod tools; mod tsc; mod unix_util; mod version; +mod windows_util; use crate::file_fetcher::File; use crate::file_watcher::ResolutionResult; @@ -1364,9 +1365,11 @@ fn unwrap_or_exit(result: Result) -> T { pub fn main() { setup_exit_process_panic_hook(); + + unix_util::raise_fd_limit(); + windows_util::ensure_stdio_open(); #[cfg(windows)] colors::enable_ansi(); // For Windows 10 - unix_util::raise_fd_limit(); let args: Vec = env::args().collect(); let standalone_res = match standalone::extract_standalone(args.clone()) { diff --git a/cli/windows_util.rs b/cli/windows_util.rs new file mode 100644 index 00000000000000..0f30bb04c5f4c0 --- /dev/null +++ b/cli/windows_util.rs @@ -0,0 +1,89 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +/// Ensures that stdin, stdout, and stderr are open and have valid HANDLEs +/// associated with them. There are many places where a `std::fs::File` is +/// constructed from a stdio handle; if the handle is null this causes a panic. +pub fn ensure_stdio_open() { + #[cfg(windows)] + unsafe { + use std::mem::size_of; + use winapi::shared::minwindef::DWORD; + use winapi::shared::minwindef::FALSE; + use winapi::shared::minwindef::TRUE; + use winapi::shared::ntdef::NULL; + use winapi::shared::winerror::ERROR_INVALID_HANDLE; + use winapi::um::errhandlingapi::GetLastError; + use winapi::um::fileapi::CreateFileA; + use winapi::um::fileapi::OPEN_EXISTING; + use winapi::um::handleapi::GetHandleInformation; + use winapi::um::handleapi::INVALID_HANDLE_VALUE; + use winapi::um::minwinbase::SECURITY_ATTRIBUTES; + use winapi::um::processenv::GetStdHandle; + use winapi::um::processenv::SetStdHandle; + use winapi::um::winbase::STD_ERROR_HANDLE; + use winapi::um::winbase::STD_INPUT_HANDLE; + use winapi::um::winbase::STD_OUTPUT_HANDLE; + use winapi::um::winnt::FILE_ATTRIBUTE_NORMAL; + use winapi::um::winnt::FILE_GENERIC_READ; + use winapi::um::winnt::FILE_GENERIC_WRITE; + use winapi::um::winnt::FILE_READ_ATTRIBUTES; + use winapi::um::winnt::FILE_SHARE_READ; + use winapi::um::winnt::FILE_SHARE_WRITE; + + for std_handle in [STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, STD_ERROR_HANDLE] { + // Check whether stdio handle is open. + let is_valid = match GetStdHandle(std_handle) { + NULL | INVALID_HANDLE_VALUE => false, + handle => { + // The stdio handle is open; check whether its handle is valid. + let mut flags: DWORD = 0; + match GetHandleInformation(handle, &mut flags) { + TRUE => true, + FALSE if GetLastError() == ERROR_INVALID_HANDLE => false, + FALSE => { + panic!("GetHandleInformation failed (error {})", GetLastError()); + } + _ => unreachable!(), + } + } + }; + + if !is_valid { + // Open NUL device. + let desired_access = match std_handle { + STD_INPUT_HANDLE => FILE_GENERIC_READ, + _ => FILE_GENERIC_WRITE | FILE_READ_ATTRIBUTES, + }; + let security_attributes = SECURITY_ATTRIBUTES { + nLength: size_of::() as DWORD, + lpSecurityDescriptor: NULL, + bInheritHandle: TRUE, + }; + let file_handle = CreateFileA( + b"\\\\?\\NUL\0" as *const _ as *mut _, + desired_access, + FILE_SHARE_READ | FILE_SHARE_WRITE, + &security_attributes as *const _ as *mut _, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL, + ); + match file_handle { + NULL => unreachable!(), + INVALID_HANDLE_VALUE => { + panic!("Could not open NUL device (error {})", GetLastError()); + } + _ => {} + } + + // Assign the opened NUL handle to the missing stdio handle. + let success = SetStdHandle(std_handle, file_handle); + match success { + TRUE => {} + FALSE => panic!("SetStdHandle failed (error {})", GetLastError()), + _ => unreachable!(), + } + } + } + } +} diff --git a/runtime/ops/io.rs b/runtime/ops/io.rs index 7eb27bd125325a..5b98cd725d26a0 100644 --- a/runtime/ops/io.rs +++ b/runtime/ops/io.rs @@ -20,6 +20,7 @@ use deno_net::io::UnixStreamResource; use deno_net::ops_tls::TlsStreamResource; use std::borrow::Cow; use std::cell::RefCell; +use std::fs::File as StdFile; use std::io::Read; use std::io::Write; use std::rc::Rc; @@ -33,8 +34,19 @@ use tokio::process; use std::os::unix::io::FromRawFd; #[cfg(windows)] -use std::os::windows::io::FromRawHandle; +use { + std::os::windows::io::FromRawHandle, + winapi::um::{processenv::GetStdHandle, winbase}, +}; +#[cfg(unix)] +lazy_static::lazy_static! { + static ref STDIN_HANDLE: StdFile = unsafe { StdFile::from_raw_fd(0) }; + static ref STDOUT_HANDLE: StdFile = unsafe { StdFile::from_raw_fd(1) }; + static ref STDERR_HANDLE: StdFile = unsafe { StdFile::from_raw_fd(2) }; +} + +#[cfg(windows)] 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 @@ -44,50 +56,14 @@ lazy_static::lazy_static! { /// 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 STDIN_HANDLE: StdFile = unsafe { + StdFile::from_raw_handle(GetStdHandle(winbase::STD_INPUT_HANDLE)) }; - 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 STDOUT_HANDLE: StdFile = unsafe { + StdFile::from_raw_handle(GetStdHandle(winbase::STD_OUTPUT_HANDLE)) }; - 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 + static ref STDERR_HANDLE: StdFile = unsafe { + StdFile::from_raw_handle(GetStdHandle(winbase::STD_ERROR_HANDLE)) }; } @@ -107,49 +83,14 @@ pub fn init_stdio() -> Extension { Extension::builder() .state(|state| { let t = &mut state.resource_table; - let (stdin, stdout, stderr) = get_stdio(); - if let Some(stream) = stdin { - t.add(stream); - } - if let Some(stream) = stdout { - t.add(stream); - } - if let Some(stream) = stderr { - t.add(stream); - } + t.add(StdFileResource::stdio(&STDIN_HANDLE, "stdin")); + t.add(StdFileResource::stdio(&STDOUT_HANDLE, "stdout")); + t.add(StdFileResource::stdio(&STDERR_HANDLE, "stderr")); Ok(()) }) .build() } -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; @@ -277,10 +218,10 @@ pub struct StdFileResource { } impl StdFileResource { - pub fn stdio(fs_file: tokio::fs::File, name: &str) -> Self { + pub fn stdio(std_file: &StdFile, name: &str) -> Self { Self { fs_file: Some(AsyncRefCell::new(( - Some(fs_file), + std_file.try_clone().map(tokio::fs::File::from_std).ok(), Some(FileMetadata::default()), ))), name: name.to_string(),