From c52e0596c5ef1d68bf0f283342fdb05db63248f9 Mon Sep 17 00:00:00 2001 From: joboet Date: Sun, 9 Feb 2025 18:42:07 +0100 Subject: std: move stdio to `sys` As per #117276, this moves the platform definitions of `Stdout` and friends into `sys`. This PR also unifies the UNIX and Hermit implementations and moves the `__rust_print_err` function needed by libunwind on SGX into the dedicated module for such helper functions. --- library/std/src/sys/mod.rs | 1 + library/std/src/sys/pal/hermit/mod.rs | 1 - library/std/src/sys/pal/hermit/stdio.rs | 97 ----- library/std/src/sys/pal/sgx/abi/mod.rs | 2 +- .../std/src/sys/pal/sgx/libunwind_integration.rs | 12 + library/std/src/sys/pal/sgx/mod.rs | 1 - library/std/src/sys/pal/sgx/stdio.rs | 88 ---- library/std/src/sys/pal/solid/mod.rs | 1 - library/std/src/sys/pal/solid/stdio.rs | 80 ---- library/std/src/sys/pal/teeos/mod.rs | 1 - library/std/src/sys/pal/teeos/stdio.rs | 89 ---- library/std/src/sys/pal/uefi/mod.rs | 1 - library/std/src/sys/pal/uefi/stdio.rs | 219 ---------- library/std/src/sys/pal/unix/mod.rs | 1 - library/std/src/sys/pal/unix/stdio.rs | 99 ----- library/std/src/sys/pal/unsupported/mod.rs | 1 - library/std/src/sys/pal/unsupported/stdio.rs | 59 --- library/std/src/sys/pal/wasi/mod.rs | 1 - library/std/src/sys/pal/wasi/stdio.rs | 117 ----- library/std/src/sys/pal/wasip2/mod.rs | 2 - library/std/src/sys/pal/wasm/mod.rs | 2 - library/std/src/sys/pal/windows/mod.rs | 1 - library/std/src/sys/pal/windows/stdio.rs | 472 --------------------- library/std/src/sys/pal/windows/stdio/tests.rs | 6 - library/std/src/sys/pal/xous/mod.rs | 1 - library/std/src/sys/pal/xous/stdio.rs | 133 ------ library/std/src/sys/pal/zkvm/mod.rs | 1 - library/std/src/sys/pal/zkvm/stdio.rs | 73 ---- library/std/src/sys/stdio/mod.rs | 38 ++ library/std/src/sys/stdio/sgx.rs | 70 +++ library/std/src/sys/stdio/solid.rs | 80 ++++ library/std/src/sys/stdio/teeos.rs | 89 ++++ library/std/src/sys/stdio/uefi.rs | 219 ++++++++++ library/std/src/sys/stdio/unix.rs | 106 +++++ library/std/src/sys/stdio/unsupported.rs | 59 +++ library/std/src/sys/stdio/wasi.rs | 117 +++++ library/std/src/sys/stdio/windows.rs | 472 +++++++++++++++++++++ library/std/src/sys/stdio/windows/tests.rs | 6 + library/std/src/sys/stdio/xous.rs | 133 ++++++ library/std/src/sys/stdio/zkvm.rs | 72 ++++ 40 files changed, 1475 insertions(+), 1548 deletions(-) delete mode 100644 library/std/src/sys/pal/hermit/stdio.rs delete mode 100644 library/std/src/sys/pal/sgx/stdio.rs delete mode 100644 library/std/src/sys/pal/solid/stdio.rs delete mode 100644 library/std/src/sys/pal/teeos/stdio.rs delete mode 100644 library/std/src/sys/pal/uefi/stdio.rs delete mode 100644 library/std/src/sys/pal/unix/stdio.rs delete mode 100644 library/std/src/sys/pal/unsupported/stdio.rs delete mode 100644 library/std/src/sys/pal/wasi/stdio.rs delete mode 100644 library/std/src/sys/pal/windows/stdio.rs delete mode 100644 library/std/src/sys/pal/windows/stdio/tests.rs delete mode 100644 library/std/src/sys/pal/xous/stdio.rs delete mode 100644 library/std/src/sys/pal/zkvm/stdio.rs create mode 100644 library/std/src/sys/stdio/mod.rs create mode 100644 library/std/src/sys/stdio/sgx.rs create mode 100644 library/std/src/sys/stdio/solid.rs create mode 100644 library/std/src/sys/stdio/teeos.rs create mode 100644 library/std/src/sys/stdio/uefi.rs create mode 100644 library/std/src/sys/stdio/unix.rs create mode 100644 library/std/src/sys/stdio/unsupported.rs create mode 100644 library/std/src/sys/stdio/wasi.rs create mode 100644 library/std/src/sys/stdio/windows.rs create mode 100644 library/std/src/sys/stdio/windows/tests.rs create mode 100644 library/std/src/sys/stdio/xous.rs create mode 100644 library/std/src/sys/stdio/zkvm.rs (limited to 'library/std/src') diff --git a/library/std/src/sys/mod.rs b/library/std/src/sys/mod.rs index 9f8c1e53131..09677b9d642 100644 --- a/library/std/src/sys/mod.rs +++ b/library/std/src/sys/mod.rs @@ -18,6 +18,7 @@ pub mod net; pub mod os_str; pub mod path; pub mod random; +pub mod stdio; pub mod sync; pub mod thread_local; diff --git a/library/std/src/sys/pal/hermit/mod.rs b/library/std/src/sys/pal/hermit/mod.rs index 5605c92db5b..608245bd430 100644 --- a/library/std/src/sys/pal/hermit/mod.rs +++ b/library/std/src/sys/pal/hermit/mod.rs @@ -27,7 +27,6 @@ pub mod os; pub mod pipe; #[path = "../unsupported/process.rs"] pub mod process; -pub mod stdio; pub mod thread; pub mod time; diff --git a/library/std/src/sys/pal/hermit/stdio.rs b/library/std/src/sys/pal/hermit/stdio.rs deleted file mode 100644 index 3ea00f5cc5e..00000000000 --- a/library/std/src/sys/pal/hermit/stdio.rs +++ /dev/null @@ -1,97 +0,0 @@ -use super::hermit_abi; -use crate::io; -use crate::io::{IoSlice, IoSliceMut}; -use crate::mem::ManuallyDrop; -use crate::os::hermit::io::FromRawFd; -use crate::sys::fd::FileDesc; - -pub struct Stdin; -pub struct Stdout; -pub struct Stderr; - -impl Stdin { - pub const fn new() -> Stdin { - Stdin - } -} - -impl io::Read for Stdin { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - unsafe { ManuallyDrop::new(FileDesc::from_raw_fd(hermit_abi::STDIN_FILENO)).read(buf) } - } - - fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { - unsafe { - ManuallyDrop::new(FileDesc::from_raw_fd(hermit_abi::STDIN_FILENO)).read_vectored(bufs) - } - } - - #[inline] - fn is_read_vectored(&self) -> bool { - true - } -} - -impl Stdout { - pub const fn new() -> Stdout { - Stdout - } -} - -impl io::Write for Stdout { - fn write(&mut self, buf: &[u8]) -> io::Result { - unsafe { ManuallyDrop::new(FileDesc::from_raw_fd(hermit_abi::STDOUT_FILENO)).write(buf) } - } - - fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { - unsafe { - ManuallyDrop::new(FileDesc::from_raw_fd(hermit_abi::STDOUT_FILENO)).write_vectored(bufs) - } - } - - #[inline] - fn is_write_vectored(&self) -> bool { - true - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -impl Stderr { - pub const fn new() -> Stderr { - Stderr - } -} - -impl io::Write for Stderr { - fn write(&mut self, buf: &[u8]) -> io::Result { - unsafe { ManuallyDrop::new(FileDesc::from_raw_fd(hermit_abi::STDERR_FILENO)).write(buf) } - } - - fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { - unsafe { - ManuallyDrop::new(FileDesc::from_raw_fd(hermit_abi::STDERR_FILENO)).write_vectored(bufs) - } - } - - #[inline] - fn is_write_vectored(&self) -> bool { - true - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -pub const STDIN_BUF_SIZE: usize = 128; - -pub fn is_ebadf(err: &io::Error) -> bool { - err.raw_os_error() == Some(hermit_abi::EBADF) -} - -pub fn panic_output() -> Option { - Some(Stderr::new()) -} diff --git a/library/std/src/sys/pal/sgx/abi/mod.rs b/library/std/src/sys/pal/sgx/abi/mod.rs index 90981bd6a6a..2c805a4d0af 100644 --- a/library/std/src/sys/pal/sgx/abi/mod.rs +++ b/library/std/src/sys/pal/sgx/abi/mod.rs @@ -6,7 +6,7 @@ use core::sync::atomic::{AtomicUsize, Ordering}; use crate::io::Write; // runtime features -pub(super) mod panic; +pub mod panic; mod reloc; // library features diff --git a/library/std/src/sys/pal/sgx/libunwind_integration.rs b/library/std/src/sys/pal/sgx/libunwind_integration.rs index 6d0d78d1eb9..b5419ad05de 100644 --- a/library/std/src/sys/pal/sgx/libunwind_integration.rs +++ b/library/std/src/sys/pal/sgx/libunwind_integration.rs @@ -4,6 +4,7 @@ #![cfg(not(test))] use crate::sys::sync::RwLock; +use crate::{slice, str}; // Verify that the byte pattern libunwind uses to initialize an RwLock is // equivalent to the value of RwLock::new(). If the value changes, @@ -44,3 +45,14 @@ pub unsafe extern "C" fn __rust_rwlock_unlock(p: *mut RwLock) -> i32 { unsafe { (*p).write_unlock() }; return 0; } + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn __rust_print_err(m: *mut u8, s: i32) { + if s < 0 { + return; + } + let buf = unsafe { slice::from_raw_parts(m as *const u8, s as _) }; + if let Ok(s) = str::from_utf8(&buf[..buf.iter().position(|&b| b == 0).unwrap_or(buf.len())]) { + eprint!("{s}"); + } +} diff --git a/library/std/src/sys/pal/sgx/mod.rs b/library/std/src/sys/pal/sgx/mod.rs index 16aa5ab116a..bb419c2530e 100644 --- a/library/std/src/sys/pal/sgx/mod.rs +++ b/library/std/src/sys/pal/sgx/mod.rs @@ -18,7 +18,6 @@ pub mod os; pub mod pipe; #[path = "../unsupported/process.rs"] pub mod process; -pub mod stdio; pub mod thread; pub mod thread_parking; pub mod time; diff --git a/library/std/src/sys/pal/sgx/stdio.rs b/library/std/src/sys/pal/sgx/stdio.rs deleted file mode 100644 index 726a93acae4..00000000000 --- a/library/std/src/sys/pal/sgx/stdio.rs +++ /dev/null @@ -1,88 +0,0 @@ -use fortanix_sgx_abi as abi; - -use crate::io; -#[cfg(not(test))] -use crate::slice; -#[cfg(not(test))] -use crate::str; -use crate::sys::fd::FileDesc; - -pub struct Stdin(()); -pub struct Stdout(()); -pub struct Stderr(()); - -fn with_std_fd R, R>(fd: abi::Fd, f: F) -> R { - let fd = FileDesc::new(fd); - let ret = f(&fd); - fd.into_raw(); - ret -} - -impl Stdin { - pub const fn new() -> Stdin { - Stdin(()) - } -} - -impl io::Read for Stdin { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - with_std_fd(abi::FD_STDIN, |fd| fd.read(buf)) - } -} - -impl Stdout { - pub const fn new() -> Stdout { - Stdout(()) - } -} - -impl io::Write for Stdout { - fn write(&mut self, buf: &[u8]) -> io::Result { - with_std_fd(abi::FD_STDOUT, |fd| fd.write(buf)) - } - - fn flush(&mut self) -> io::Result<()> { - with_std_fd(abi::FD_STDOUT, |fd| fd.flush()) - } -} - -impl Stderr { - pub const fn new() -> Stderr { - Stderr(()) - } -} - -impl io::Write for Stderr { - fn write(&mut self, buf: &[u8]) -> io::Result { - with_std_fd(abi::FD_STDERR, |fd| fd.write(buf)) - } - - fn flush(&mut self) -> io::Result<()> { - with_std_fd(abi::FD_STDERR, |fd| fd.flush()) - } -} - -pub const STDIN_BUF_SIZE: usize = crate::sys::io::DEFAULT_BUF_SIZE; - -pub fn is_ebadf(err: &io::Error) -> bool { - // FIXME: Rust normally maps Unix EBADF to `Uncategorized` - err.raw_os_error() == Some(abi::Error::BrokenPipe as _) -} - -pub fn panic_output() -> Option { - super::abi::panic::SgxPanicOutput::new() -} - -// This function is needed by libunwind. The symbol is named in pre-link args -// for the target specification, so keep that in sync. -#[cfg(not(test))] -#[unsafe(no_mangle)] -pub unsafe extern "C" fn __rust_print_err(m: *mut u8, s: i32) { - if s < 0 { - return; - } - let buf = unsafe { slice::from_raw_parts(m as *const u8, s as _) }; - if let Ok(s) = str::from_utf8(&buf[..buf.iter().position(|&b| b == 0).unwrap_or(buf.len())]) { - eprint!("{s}"); - } -} diff --git a/library/std/src/sys/pal/solid/mod.rs b/library/std/src/sys/pal/solid/mod.rs index 2c3e50ed594..e4a61fdcfe3 100644 --- a/library/std/src/sys/pal/solid/mod.rs +++ b/library/std/src/sys/pal/solid/mod.rs @@ -27,7 +27,6 @@ pub mod os; pub mod pipe; #[path = "../unsupported/process.rs"] pub mod process; -pub mod stdio; pub use self::itron::{thread, thread_parking}; pub mod time; diff --git a/library/std/src/sys/pal/solid/stdio.rs b/library/std/src/sys/pal/solid/stdio.rs deleted file mode 100644 index 50f0176967b..00000000000 --- a/library/std/src/sys/pal/solid/stdio.rs +++ /dev/null @@ -1,80 +0,0 @@ -use super::abi; -use crate::io; - -pub struct Stdin; -pub struct Stdout; -pub struct Stderr; -struct PanicOutput; - -impl Stdin { - pub const fn new() -> Stdin { - Stdin - } -} - -impl io::Read for Stdin { - fn read(&mut self, _buf: &mut [u8]) -> io::Result { - Ok(0) - } -} - -impl Stdout { - pub const fn new() -> Stdout { - Stdout - } -} - -impl io::Write for Stdout { - fn write(&mut self, buf: &[u8]) -> io::Result { - unsafe { abi::SOLID_LOG_write(buf.as_ptr(), buf.len()) }; - Ok(buf.len()) - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -impl Stderr { - pub const fn new() -> Stderr { - Stderr - } -} - -impl io::Write for Stderr { - fn write(&mut self, buf: &[u8]) -> io::Result { - unsafe { abi::SOLID_LOG_write(buf.as_ptr(), buf.len()) }; - Ok(buf.len()) - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -impl PanicOutput { - pub const fn new() -> PanicOutput { - PanicOutput - } -} - -impl io::Write for PanicOutput { - fn write(&mut self, buf: &[u8]) -> io::Result { - unsafe { abi::SOLID_LOG_write(buf.as_ptr(), buf.len()) }; - Ok(buf.len()) - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -pub const STDIN_BUF_SIZE: usize = 0; - -pub fn is_ebadf(_err: &io::Error) -> bool { - true -} - -pub fn panic_output() -> Option { - Some(PanicOutput::new()) -} diff --git a/library/std/src/sys/pal/teeos/mod.rs b/library/std/src/sys/pal/teeos/mod.rs index 2aeaf20134f..41b25121592 100644 --- a/library/std/src/sys/pal/teeos/mod.rs +++ b/library/std/src/sys/pal/teeos/mod.rs @@ -16,7 +16,6 @@ pub mod os; pub mod pipe; #[path = "../unsupported/process.rs"] pub mod process; -pub mod stdio; pub mod thread; #[allow(non_upper_case_globals)] #[path = "../unix/time.rs"] diff --git a/library/std/src/sys/pal/teeos/stdio.rs b/library/std/src/sys/pal/teeos/stdio.rs deleted file mode 100644 index 67e251812da..00000000000 --- a/library/std/src/sys/pal/teeos/stdio.rs +++ /dev/null @@ -1,89 +0,0 @@ -#![deny(unsafe_op_in_unsafe_fn)] - -use core::arch::asm; - -use crate::io; - -pub struct Stdin; -pub struct Stdout; -pub struct Stderr; - -const KCALL_DEBUG_CMD_PUT_BYTES: i64 = 2; - -unsafe fn debug_call(cap_ref: u64, call_no: i64, arg1: u64, arg2: u64) -> i32 { - let ret: u64; - unsafe { - asm!( - "svc #99", - inout("x0") cap_ref => ret, - in("x1") call_no, - in("x2") arg1, - in("x3") arg2, - ); - } - - ret as i32 -} - -fn print_buf(s: &[u8]) -> io::Result { - // Corresponds to `HM_DEBUG_PUT_BYTES_LIMIT`. - const MAX_LEN: usize = 512; - let len = if s.len() > MAX_LEN { MAX_LEN } else { s.len() }; - let result = unsafe { debug_call(0, KCALL_DEBUG_CMD_PUT_BYTES, s.as_ptr() as u64, len as u64) }; - - if result == 0 { Ok(len) } else { Err(io::Error::from(io::ErrorKind::InvalidInput)) } -} - -impl Stdin { - pub const fn new() -> Stdin { - Stdin - } -} - -impl io::Read for Stdin { - fn read(&mut self, _buf: &mut [u8]) -> io::Result { - Ok(0) - } -} - -impl Stdout { - pub const fn new() -> Stdout { - Stdout - } -} - -impl io::Write for Stdout { - fn write(&mut self, buf: &[u8]) -> io::Result { - print_buf(buf) - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -impl Stderr { - pub const fn new() -> Stderr { - Stderr - } -} - -impl io::Write for Stderr { - fn write(&mut self, buf: &[u8]) -> io::Result { - print_buf(buf) - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -pub const STDIN_BUF_SIZE: usize = 0; - -pub fn is_ebadf(err: &io::Error) -> bool { - err.raw_os_error() == Some(libc::EBADF as i32) -} - -pub fn panic_output() -> Option { - Some(Stderr::new()) -} diff --git a/library/std/src/sys/pal/uefi/mod.rs b/library/std/src/sys/pal/uefi/mod.rs index 3dc83f6f654..714dc392688 100644 --- a/library/std/src/sys/pal/uefi/mod.rs +++ b/library/std/src/sys/pal/uefi/mod.rs @@ -20,7 +20,6 @@ pub mod os; #[path = "../unsupported/pipe.rs"] pub mod pipe; pub mod process; -pub mod stdio; pub mod thread; pub mod time; diff --git a/library/std/src/sys/pal/uefi/stdio.rs b/library/std/src/sys/pal/uefi/stdio.rs deleted file mode 100644 index 257e321dd03..00000000000 --- a/library/std/src/sys/pal/uefi/stdio.rs +++ /dev/null @@ -1,219 +0,0 @@ -use crate::io; -use crate::iter::Iterator; -use crate::mem::MaybeUninit; -use crate::os::uefi; -use crate::ptr::NonNull; - -pub struct Stdin { - surrogate: Option, - incomplete_utf8: IncompleteUtf8, -} - -struct IncompleteUtf8 { - bytes: [u8; 4], - len: u8, -} - -impl IncompleteUtf8 { - pub const fn new() -> IncompleteUtf8 { - IncompleteUtf8 { bytes: [0; 4], len: 0 } - } - - // Implemented for use in Stdin::read. - fn read(&mut self, buf: &mut [u8]) -> usize { - // Write to buffer until the buffer is full or we run out of bytes. - let to_write = crate::cmp::min(buf.len(), self.len as usize); - buf[..to_write].copy_from_slice(&self.bytes[..to_write]); - - // Rotate the remaining bytes if not enough remaining space in buffer. - if usize::from(self.len) > buf.len() { - self.bytes.copy_within(to_write.., 0); - self.len -= to_write as u8; - } else { - self.len = 0; - } - - to_write - } -} - -pub struct Stdout; -pub struct Stderr; - -impl Stdin { - pub const fn new() -> Stdin { - Stdin { surrogate: None, incomplete_utf8: IncompleteUtf8::new() } - } -} - -impl io::Read for Stdin { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - // If there are bytes in the incomplete utf-8, start with those. - // (No-op if there is nothing in the buffer.) - let mut bytes_copied = self.incomplete_utf8.read(buf); - - let stdin: *mut r_efi::protocols::simple_text_input::Protocol = unsafe { - let st: NonNull = uefi::env::system_table().cast(); - (*st.as_ptr()).con_in - }; - - if bytes_copied == buf.len() { - return Ok(bytes_copied); - } - - let ch = simple_text_input_read(stdin)?; - // Only 1 character should be returned. - let mut ch: Vec> = - if let Some(x) = self.surrogate.take() { - char::decode_utf16([x, ch]).collect() - } else { - char::decode_utf16([ch]).collect() - }; - - if ch.len() > 1 { - return Err(io::const_error!(io::ErrorKind::InvalidData, "invalid UTF-16 sequence")); - } - - match ch.pop().unwrap() { - Err(e) => { - self.surrogate = Some(e.unpaired_surrogate()); - } - Ok(x) => { - // This will always be > 0 - let buf_free_count = buf.len() - bytes_copied; - assert!(buf_free_count > 0); - - if buf_free_count >= x.len_utf8() { - // There is enough space in the buffer for the character. - bytes_copied += x.encode_utf8(&mut buf[bytes_copied..]).len(); - } else { - // There is not enough space in the buffer for the character. - // Store the character in the incomplete buffer. - self.incomplete_utf8.len = - x.encode_utf8(&mut self.incomplete_utf8.bytes).len() as u8; - // write partial character to buffer. - bytes_copied += self.incomplete_utf8.read(buf); - } - } - } - - Ok(bytes_copied) - } -} - -impl Stdout { - pub const fn new() -> Stdout { - Stdout - } -} - -impl io::Write for Stdout { - fn write(&mut self, buf: &[u8]) -> io::Result { - let st: NonNull = uefi::env::system_table().cast(); - let stdout = unsafe { (*st.as_ptr()).con_out }; - - write(stdout, buf) - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -impl Stderr { - pub const fn new() -> Stderr { - Stderr - } -} - -impl io::Write for Stderr { - fn write(&mut self, buf: &[u8]) -> io::Result { - let st: NonNull = uefi::env::system_table().cast(); - let stderr = unsafe { (*st.as_ptr()).std_err }; - - write(stderr, buf) - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -// UTF-16 character should occupy 4 bytes at most in UTF-8 -pub const STDIN_BUF_SIZE: usize = 4; - -pub fn is_ebadf(_err: &io::Error) -> bool { - false -} - -pub fn panic_output() -> Option { - uefi::env::try_system_table().map(|_| Stderr::new()) -} - -fn write( - protocol: *mut r_efi::protocols::simple_text_output::Protocol, - buf: &[u8], -) -> io::Result { - // Get valid UTF-8 buffer - let utf8 = match crate::str::from_utf8(buf) { - Ok(x) => x, - Err(e) => unsafe { crate::str::from_utf8_unchecked(&buf[..e.valid_up_to()]) }, - }; - - let mut utf16: Vec = utf8.encode_utf16().collect(); - // NULL terminate the string - utf16.push(0); - - unsafe { simple_text_output(protocol, &mut utf16) }?; - - Ok(utf8.len()) -} - -unsafe fn simple_text_output( - protocol: *mut r_efi::protocols::simple_text_output::Protocol, - buf: &mut [u16], -) -> io::Result<()> { - let res = unsafe { ((*protocol).output_string)(protocol, buf.as_mut_ptr()) }; - if res.is_error() { Err(io::Error::from_raw_os_error(res.as_usize())) } else { Ok(()) } -} - -fn simple_text_input_read( - stdin: *mut r_efi::protocols::simple_text_input::Protocol, -) -> io::Result { - loop { - match read_key_stroke(stdin) { - Ok(x) => return Ok(x.unicode_char), - Err(e) if e == r_efi::efi::Status::NOT_READY => wait_stdin(stdin)?, - Err(e) => return Err(io::Error::from_raw_os_error(e.as_usize())), - } - } -} - -fn wait_stdin(stdin: *mut r_efi::protocols::simple_text_input::Protocol) -> io::Result<()> { - let boot_services: NonNull = - uefi::env::boot_services().unwrap().cast(); - let wait_for_event = unsafe { (*boot_services.as_ptr()).wait_for_event }; - let wait_for_key_event = unsafe { (*stdin).wait_for_key }; - - let r = { - let mut x: usize = 0; - (wait_for_event)(1, [wait_for_key_event].as_mut_ptr(), &mut x) - }; - if r.is_error() { Err(io::Error::from_raw_os_error(r.as_usize())) } else { Ok(()) } -} - -fn read_key_stroke( - stdin: *mut r_efi::protocols::simple_text_input::Protocol, -) -> Result { - let mut input_key: MaybeUninit = - MaybeUninit::uninit(); - - let r = unsafe { ((*stdin).read_key_stroke)(stdin, input_key.as_mut_ptr()) }; - - if r.is_error() { - Err(r) - } else { - let input_key = unsafe { input_key.assume_init() }; - Ok(input_key) - } -} diff --git a/library/std/src/sys/pal/unix/mod.rs b/library/std/src/sys/pal/unix/mod.rs index 0565e3ecfb9..419abe732ac 100644 --- a/library/std/src/sys/pal/unix/mod.rs +++ b/library/std/src/sys/pal/unix/mod.rs @@ -18,7 +18,6 @@ pub mod os; pub mod pipe; pub mod process; pub mod stack_overflow; -pub mod stdio; pub mod sync; pub mod thread; pub mod thread_parking; diff --git a/library/std/src/sys/pal/unix/stdio.rs b/library/std/src/sys/pal/unix/stdio.rs deleted file mode 100644 index 8c2f61a40de..00000000000 --- a/library/std/src/sys/pal/unix/stdio.rs +++ /dev/null @@ -1,99 +0,0 @@ -use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut}; -use crate::mem::ManuallyDrop; -use crate::os::unix::io::FromRawFd; -use crate::sys::fd::FileDesc; - -pub struct Stdin(()); -pub struct Stdout(()); -pub struct Stderr(()); - -impl Stdin { - pub const fn new() -> Stdin { - Stdin(()) - } -} - -impl io::Read for Stdin { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - unsafe { ManuallyDrop::new(FileDesc::from_raw_fd(libc::STDIN_FILENO)).read(buf) } - } - - fn read_buf(&mut self, buf: BorrowedCursor<'_>) -> io::Result<()> { - unsafe { ManuallyDrop::new(FileDesc::from_raw_fd(libc::STDIN_FILENO)).read_buf(buf) } - } - - fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { - unsafe { ManuallyDrop::new(FileDesc::from_raw_fd(libc::STDIN_FILENO)).read_vectored(bufs) } - } - - #[inline] - fn is_read_vectored(&self) -> bool { - true - } -} - -impl Stdout { - pub const fn new() -> Stdout { - Stdout(()) - } -} - -impl io::Write for Stdout { - fn write(&mut self, buf: &[u8]) -> io::Result { - unsafe { ManuallyDrop::new(FileDesc::from_raw_fd(libc::STDOUT_FILENO)).write(buf) } - } - - fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { - unsafe { - ManuallyDrop::new(FileDesc::from_raw_fd(libc::STDOUT_FILENO)).write_vectored(bufs) - } - } - - #[inline] - fn is_write_vectored(&self) -> bool { - true - } - - #[inline] - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -impl Stderr { - pub const fn new() -> Stderr { - Stderr(()) - } -} - -impl io::Write for Stderr { - fn write(&mut self, buf: &[u8]) -> io::Result { - unsafe { ManuallyDrop::new(FileDesc::from_raw_fd(libc::STDERR_FILENO)).write(buf) } - } - - fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { - unsafe { - ManuallyDrop::new(FileDesc::from_raw_fd(libc::STDERR_FILENO)).write_vectored(bufs) - } - } - - #[inline] - fn is_write_vectored(&self) -> bool { - true - } - - #[inline] - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -pub fn is_ebadf(err: &io::Error) -> bool { - err.raw_os_error() == Some(libc::EBADF as i32) -} - -pub const STDIN_BUF_SIZE: usize = crate::sys::io::DEFAULT_BUF_SIZE; - -pub fn panic_output() -> Option { - Some(Stderr::new()) -} diff --git a/library/std/src/sys/pal/unsupported/mod.rs b/library/std/src/sys/pal/unsupported/mod.rs index 526e5008bd5..bcea699f3b2 100644 --- a/library/std/src/sys/pal/unsupported/mod.rs +++ b/library/std/src/sys/pal/unsupported/mod.rs @@ -5,7 +5,6 @@ pub mod env; pub mod os; pub mod pipe; pub mod process; -pub mod stdio; pub mod thread; pub mod time; diff --git a/library/std/src/sys/pal/unsupported/stdio.rs b/library/std/src/sys/pal/unsupported/stdio.rs deleted file mode 100644 index b5e3f5be988..00000000000 --- a/library/std/src/sys/pal/unsupported/stdio.rs +++ /dev/null @@ -1,59 +0,0 @@ -use crate::io; - -pub struct Stdin; -pub struct Stdout; -pub struct Stderr; - -impl Stdin { - pub const fn new() -> Stdin { - Stdin - } -} - -impl io::Read for Stdin { - fn read(&mut self, _buf: &mut [u8]) -> io::Result { - Ok(0) - } -} - -impl Stdout { - pub const fn new() -> Stdout { - Stdout - } -} - -impl io::Write for Stdout { - fn write(&mut self, buf: &[u8]) -> io::Result { - Ok(buf.len()) - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -impl Stderr { - pub const fn new() -> Stderr { - Stderr - } -} - -impl io::Write for Stderr { - fn write(&mut self, buf: &[u8]) -> io::Result { - Ok(buf.len()) - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -pub const STDIN_BUF_SIZE: usize = 0; - -pub fn is_ebadf(_err: &io::Error) -> bool { - true -} - -pub fn panic_output() -> Option> { - None -} diff --git a/library/std/src/sys/pal/wasi/mod.rs b/library/std/src/sys/pal/wasi/mod.rs index a5ae1f513dc..c89832857dd 100644 --- a/library/std/src/sys/pal/wasi/mod.rs +++ b/library/std/src/sys/pal/wasi/mod.rs @@ -25,7 +25,6 @@ pub mod os; pub mod pipe; #[path = "../unsupported/process.rs"] pub mod process; -pub mod stdio; pub mod thread; pub mod time; diff --git a/library/std/src/sys/pal/wasi/stdio.rs b/library/std/src/sys/pal/wasi/stdio.rs deleted file mode 100644 index fb21cb4d393..00000000000 --- a/library/std/src/sys/pal/wasi/stdio.rs +++ /dev/null @@ -1,117 +0,0 @@ -#![forbid(unsafe_op_in_unsafe_fn)] - -use super::fd::WasiFd; -use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut}; -use crate::mem::ManuallyDrop; -use crate::os::raw; -use crate::os::wasi::io::{AsRawFd, FromRawFd}; - -pub struct Stdin; -pub struct Stdout; -pub struct Stderr; - -impl Stdin { - pub const fn new() -> Stdin { - Stdin - } -} - -impl AsRawFd for Stdin { - #[inline] - fn as_raw_fd(&self) -> raw::c_int { - 0 - } -} - -impl io::Read for Stdin { - fn read(&mut self, data: &mut [u8]) -> io::Result { - self.read_vectored(&mut [IoSliceMut::new(data)]) - } - - fn read_buf(&mut self, buf: BorrowedCursor<'_>) -> io::Result<()> { - ManuallyDrop::new(unsafe { WasiFd::from_raw_fd(self.as_raw_fd()) }).read_buf(buf) - } - - fn read_vectored(&mut self, data: &mut [IoSliceMut<'_>]) -> io::Result { - ManuallyDrop::new(unsafe { WasiFd::from_raw_fd(self.as_raw_fd()) }).read(data) - } - - #[inline] - fn is_read_vectored(&self) -> bool { - true - } -} - -impl Stdout { - pub const fn new() -> Stdout { - Stdout - } -} - -impl AsRawFd for Stdout { - #[inline] - fn as_raw_fd(&self) -> raw::c_int { - 1 - } -} - -impl io::Write for Stdout { - fn write(&mut self, data: &[u8]) -> io::Result { - self.write_vectored(&[IoSlice::new(data)]) - } - - fn write_vectored(&mut self, data: &[IoSlice<'_>]) -> io::Result { - ManuallyDrop::new(unsafe { WasiFd::from_raw_fd(self.as_raw_fd()) }).write(data) - } - - #[inline] - fn is_write_vectored(&self) -> bool { - true - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -impl Stderr { - pub const fn new() -> Stderr { - Stderr - } -} - -impl AsRawFd for Stderr { - #[inline] - fn as_raw_fd(&self) -> raw::c_int { - 2 - } -} - -impl io::Write for Stderr { - fn write(&mut self, data: &[u8]) -> io::Result { - self.write_vectored(&[IoSlice::new(data)]) - } - - fn write_vectored(&mut self, data: &[IoSlice<'_>]) -> io::Result { - ManuallyDrop::new(unsafe { WasiFd::from_raw_fd(self.as_raw_fd()) }).write(data) - } - - #[inline] - fn is_write_vectored(&self) -> bool { - true - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -pub const STDIN_BUF_SIZE: usize = crate::sys::io::DEFAULT_BUF_SIZE; - -pub fn is_ebadf(err: &io::Error) -> bool { - err.raw_os_error() == Some(wasi::ERRNO_BADF.raw().into()) -} - -pub fn panic_output() -> Option { - Some(Stderr::new()) -} diff --git a/library/std/src/sys/pal/wasip2/mod.rs b/library/std/src/sys/pal/wasip2/mod.rs index d4eb4369673..3008ba88753 100644 --- a/library/std/src/sys/pal/wasip2/mod.rs +++ b/library/std/src/sys/pal/wasip2/mod.rs @@ -22,8 +22,6 @@ pub mod os; pub mod pipe; #[path = "../unsupported/process.rs"] pub mod process; -#[path = "../wasi/stdio.rs"] -pub mod stdio; #[path = "../wasi/thread.rs"] pub mod thread; #[path = "../wasi/time.rs"] diff --git a/library/std/src/sys/pal/wasm/mod.rs b/library/std/src/sys/pal/wasm/mod.rs index cfadbdfe00c..175fe75357f 100644 --- a/library/std/src/sys/pal/wasm/mod.rs +++ b/library/std/src/sys/pal/wasm/mod.rs @@ -25,8 +25,6 @@ pub mod os; pub mod pipe; #[path = "../unsupported/process.rs"] pub mod process; -#[path = "../unsupported/stdio.rs"] -pub mod stdio; #[path = "../unsupported/time.rs"] pub mod time; diff --git a/library/std/src/sys/pal/windows/mod.rs b/library/std/src/sys/pal/windows/mod.rs index bed79af395b..6eb68f3a3bc 100644 --- a/library/std/src/sys/pal/windows/mod.rs +++ b/library/std/src/sys/pal/windows/mod.rs @@ -23,7 +23,6 @@ pub mod handle; pub mod os; pub mod pipe; pub mod process; -pub mod stdio; pub mod thread; pub mod time; cfg_if::cfg_if! { diff --git a/library/std/src/sys/pal/windows/stdio.rs b/library/std/src/sys/pal/windows/stdio.rs deleted file mode 100644 index 58d3406e138..00000000000 --- a/library/std/src/sys/pal/windows/stdio.rs +++ /dev/null @@ -1,472 +0,0 @@ -#![unstable(issue = "none", feature = "windows_stdio")] - -use core::char::MAX_LEN_UTF8; -use core::str::utf8_char_width; - -use super::api::{self, WinError}; -use crate::mem::MaybeUninit; -use crate::os::windows::io::{FromRawHandle, IntoRawHandle}; -use crate::sys::handle::Handle; -use crate::sys::{c, cvt}; -use crate::{cmp, io, ptr, str}; - -#[cfg(test)] -mod tests; - -// Don't cache handles but get them fresh for every read/write. This allows us to track changes to -// the value over time (such as if a process calls `SetStdHandle` while it's running). See #40490. -pub struct Stdin { - surrogate: u16, - incomplete_utf8: IncompleteUtf8, -} - -pub struct Stdout { - incomplete_utf8: IncompleteUtf8, -} - -pub struct Stderr { - incomplete_utf8: IncompleteUtf8, -} - -struct IncompleteUtf8 { - bytes: [u8; 4], - len: u8, -} - -impl IncompleteUtf8 { - // Implemented for use in Stdin::read. - fn read(&mut self, buf: &mut [u8]) -> usize { - // Write to buffer until the buffer is full or we run out of bytes. - let to_write = cmp::min(buf.len(), self.len as usize); - buf[..to_write].copy_from_slice(&self.bytes[..to_write]); - - // Rotate the remaining bytes if not enough remaining space in buffer. - if usize::from(self.len) > buf.len() { - self.bytes.copy_within(to_write.., 0); - self.len -= to_write as u8; - } else { - self.len = 0; - } - - to_write - } -} - -// Apparently Windows doesn't handle large reads on stdin or writes to stdout/stderr well (see -// #13304 for details). -// -// From MSDN (2011): "The storage for this buffer is allocated from a shared heap for the -// process that is 64 KB in size. The maximum size of the buffer will depend on heap usage." -// -// We choose the cap at 8 KiB because libuv does the same, and it seems to be acceptable so far. -const MAX_BUFFER_SIZE: usize = 8192; - -// The standard buffer size of BufReader for Stdin should be able to hold 3x more bytes than there -// are `u16`'s in MAX_BUFFER_SIZE. This ensures the read data can always be completely decoded from -// UTF-16 to UTF-8. -pub const STDIN_BUF_SIZE: usize = MAX_BUFFER_SIZE / 2 * 3; - -pub fn get_handle(handle_id: u32) -> io::Result { - let handle = unsafe { c::GetStdHandle(handle_id) }; - if handle == c::INVALID_HANDLE_VALUE { - Err(io::Error::last_os_error()) - } else if handle.is_null() { - Err(io::Error::from_raw_os_error(c::ERROR_INVALID_HANDLE as i32)) - } else { - Ok(handle) - } -} - -fn is_console(handle: c::HANDLE) -> bool { - // `GetConsoleMode` will return false (0) if this is a pipe (we don't care about the reported - // mode). This will only detect Windows Console, not other terminals connected to a pipe like - // MSYS. Which is exactly what we need, as only Windows Console needs a conversion to UTF-16. - let mut mode = 0; - unsafe { c::GetConsoleMode(handle, &mut mode) != 0 } -} - -/// Returns true if the attached console's code page is currently UTF-8. -#[cfg(not(target_vendor = "win7"))] -fn is_utf8_console() -> bool { - unsafe { c::GetConsoleOutputCP() == c::CP_UTF8 } -} - -#[cfg(target_vendor = "win7")] -fn is_utf8_console() -> bool { - // Windows 7 has a fun "feature" where WriteFile on a console handle will return - // the number of UTF-16 code units written and not the number of bytes from the input string. - // So we always claim the console isn't UTF-8 to trigger the WriteConsole fallback code. - false -} - -fn write(handle_id: u32, data: &[u8], incomplete_utf8: &mut IncompleteUtf8) -> io::Result { - if data.is_empty() { - return Ok(0); - } - - let handle = get_handle(handle_id)?; - if !is_console(handle) || is_utf8_console() { - unsafe { - let handle = Handle::from_raw_handle(handle); - let ret = handle.write(data); - let _ = handle.into_raw_handle(); // Don't close the handle - return ret; - } - } else { - write_console_utf16(data, incomplete_utf8, handle) - } -} - -fn write_console_utf16( - data: &[u8], - incomplete_utf8: &mut IncompleteUtf8, - handle: c::HANDLE, -) -> io::Result { - if incomplete_utf8.len > 0 { - assert!( - incomplete_utf8.len < 4, - "Unexpected number of bytes for incomplete UTF-8 codepoint." - ); - if data[0] >> 6 != 0b10 { - // not a continuation byte - reject - incomplete_utf8.len = 0; - return Err(io::const_error!( - io::ErrorKind::InvalidData, - "Windows stdio in console mode does not support writing non-UTF-8 byte sequences", - )); - } - incomplete_utf8.bytes[incomplete_utf8.len as usize] = data[0]; - incomplete_utf8.len += 1; - let char_width = utf8_char_width(incomplete_utf8.bytes[0]); - if (incomplete_utf8.len as usize) < char_width { - // more bytes needed - return Ok(1); - } - let s = str::from_utf8(&incomplete_utf8.bytes[0..incomplete_utf8.len as usize]); - incomplete_utf8.len = 0; - match s { - Ok(s) => { - assert_eq!(char_width, s.len()); - let written = write_valid_utf8_to_console(handle, s)?; - assert_eq!(written, s.len()); // guaranteed by write_valid_utf8_to_console() for single codepoint writes - return Ok(1); - } - Err(_) => { - return Err(io::const_error!( - io::ErrorKind::InvalidData, - "Windows stdio in console mode does not support writing non-UTF-8 byte sequences", - )); - } - } - } - - // As the console is meant for presenting text, we assume bytes of `data` are encoded as UTF-8, - // which needs to be encoded as UTF-16. - // - // If the data is not valid UTF-8 we write out as many bytes as are valid. - // If the first byte is invalid it is either first byte of a multi-byte sequence but the - // provided byte slice is too short or it is the first byte of an invalid multi-byte sequence. - let len = cmp::min(data.len(), MAX_BUFFER_SIZE / 2); - let utf8 = match str::from_utf8(&data[..len]) { - Ok(s) => s, - Err(ref e) if e.valid_up_to() == 0 => { - let first_byte_char_width = utf8_char_width(data[0]); - if first_byte_char_width > 1 && data.len() < first_byte_char_width { - incomplete_utf8.bytes[0] = data[0]; - incomplete_utf8.len = 1; - return Ok(1); - } else { - return Err(io::const_error!( - io::ErrorKind::InvalidData, - "Windows stdio in console mode does not support writing non-UTF-8 byte sequences", - )); - } - } - Err(e) => str::from_utf8(&data[..e.valid_up_to()]).unwrap(), - }; - - write_valid_utf8_to_console(handle, utf8) -} - -fn write_valid_utf8_to_console(handle: c::HANDLE, utf8: &str) -> io::Result { - debug_assert!(!utf8.is_empty()); - - let mut utf16 = [MaybeUninit::::uninit(); MAX_BUFFER_SIZE / 2]; - let utf8 = &utf8[..utf8.floor_char_boundary(utf16.len())]; - - let utf16: &[u16] = unsafe { - // Note that this theoretically checks validity twice in the (most common) case - // where the underlying byte sequence is valid utf-8 (given the check in `write()`). - let result = c::MultiByteToWideChar( - c::CP_UTF8, // CodePage - c::MB_ERR_INVALID_CHARS, // dwFlags - utf8.as_ptr(), // lpMultiByteStr - utf8.len() as i32, // cbMultiByte - utf16.as_mut_ptr() as *mut c::WCHAR, // lpWideCharStr - utf16.len() as i32, // cchWideChar - ); - assert!(result != 0, "Unexpected error in MultiByteToWideChar"); - - // Safety: MultiByteToWideChar initializes `result` values. - utf16[..result as usize].assume_init_ref() - }; - - let mut written = write_u16s(handle, utf16)?; - - // Figure out how many bytes of as UTF-8 were written away as UTF-16. - if written == utf16.len() { - Ok(utf8.len()) - } else { - // Make sure we didn't end up writing only half of a surrogate pair (even though the chance - // is tiny). Because it is not possible for user code to re-slice `data` in such a way that - // a missing surrogate can be produced (and also because of the UTF-8 validation above), - // write the missing surrogate out now. - // Buffering it would mean we have to lie about the number of bytes written. - let first_code_unit_remaining = utf16[written]; - if matches!(first_code_unit_remaining, 0xDCEE..=0xDFFF) { - // low surrogate - // We just hope this works, and give up otherwise - let _ = write_u16s(handle, &utf16[written..written + 1]); - written += 1; - } - // Calculate the number of bytes of `utf8` that were actually written. - let mut count = 0; - for ch in utf16[..written].iter() { - count += match ch { - 0x0000..=0x007F => 1, - 0x0080..=0x07FF => 2, - 0xDCEE..=0xDFFF => 1, // Low surrogate. We already counted 3 bytes for the other. - _ => 3, - }; - } - debug_assert!(String::from_utf16(&utf16[..written]).unwrap() == utf8[..count]); - Ok(count) - } -} - -fn write_u16s(handle: c::HANDLE, data: &[u16]) -> io::Result { - debug_assert!(data.len() < u32::MAX as usize); - let mut written = 0; - cvt(unsafe { - c::WriteConsoleW(handle, data.as_ptr(), data.len() as u32, &mut written, ptr::null_mut()) - })?; - Ok(written as usize) -} - -impl Stdin { - pub const fn new() -> Stdin { - Stdin { surrogate: 0, incomplete_utf8: IncompleteUtf8::new() } - } -} - -impl io::Read for Stdin { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - let handle = get_handle(c::STD_INPUT_HANDLE)?; - if !is_console(handle) { - unsafe { - let handle = Handle::from_raw_handle(handle); - let ret = handle.read(buf); - let _ = handle.into_raw_handle(); // Don't close the handle - return ret; - } - } - - // If there are bytes in the incomplete utf-8, start with those. - // (No-op if there is nothing in the buffer.) - let mut bytes_copied = self.incomplete_utf8.read(buf); - - if bytes_copied == buf.len() { - Ok(bytes_copied) - } else if buf.len() - bytes_copied < 4 { - // Not enough space to get a UTF-8 byte. We will use the incomplete UTF8. - let mut utf16_buf = [MaybeUninit::new(0); 1]; - // Read one u16 character. - let read = read_u16s_fixup_surrogates(handle, &mut utf16_buf, 1, &mut self.surrogate)?; - // Read bytes, using the (now-empty) self.incomplete_utf8 as extra space. - let read_bytes = utf16_to_utf8( - unsafe { utf16_buf[..read].assume_init_ref() }, - &mut self.incomplete_utf8.bytes, - )?; - - // Read in the bytes from incomplete_utf8 until the buffer is full. - self.incomplete_utf8.len = read_bytes as u8; - // No-op if no bytes. - bytes_copied += self.incomplete_utf8.read(&mut buf[bytes_copied..]); - Ok(bytes_copied) - } else { - let mut utf16_buf = [MaybeUninit::::uninit(); MAX_BUFFER_SIZE / 2]; - - // In the worst case, a UTF-8 string can take 3 bytes for every `u16` of a UTF-16. So - // we can read at most a third of `buf.len()` chars and uphold the guarantee no data gets - // lost. - let amount = cmp::min(buf.len() / 3, utf16_buf.len()); - let read = - read_u16s_fixup_surrogates(handle, &mut utf16_buf, amount, &mut self.surrogate)?; - // Safety `read_u16s_fixup_surrogates` returns the number of items - // initialized. - let utf16s = unsafe { utf16_buf[..read].assume_init_ref() }; - match utf16_to_utf8(utf16s, buf) { - Ok(value) => return Ok(bytes_copied + value), - Err(e) => return Err(e), - } - } - } -} - -// We assume that if the last `u16` is an unpaired surrogate they got sliced apart by our -// buffer size, and keep it around for the next read hoping to put them together. -// This is a best effort, and might not work if we are not the only reader on Stdin. -fn read_u16s_fixup_surrogates( - handle: c::HANDLE, - buf: &mut [MaybeUninit], - mut amount: usize, - surrogate: &mut u16, -) -> io::Result { - // Insert possibly remaining unpaired surrogate from last read. - let mut start = 0; - if *surrogate != 0 { - buf[0] = MaybeUninit::new(*surrogate); - *surrogate = 0; - start = 1; - if amount == 1 { - // Special case: `Stdin::read` guarantees we can always read at least one new `u16` - // and combine it with an unpaired surrogate, because the UTF-8 buffer is at least - // 4 bytes. - amount = 2; - } - } - let mut amount = read_u16s(handle, &mut buf[start..amount])? + start; - - if amount > 0 { - // Safety: The returned `amount` is the number of values initialized, - // and it is not 0, so we know that `buf[amount - 1]` have been - // initialized. - let last_char = unsafe { buf[amount - 1].assume_init() }; - if matches!(last_char, 0xD800..=0xDBFF) { - // high surrogate - *surrogate = last_char; - amount -= 1; - } - } - Ok(amount) -} - -// Returns `Ok(n)` if it initialized `n` values in `buf`. -fn read_u16s(handle: c::HANDLE, buf: &mut [MaybeUninit]) -> io::Result { - // Configure the `pInputControl` parameter to not only return on `\r\n` but also Ctrl-Z, the - // traditional DOS method to indicate end of character stream / user input (SUB). - // See #38274 and https://stackoverflow.com/questions/43836040/win-api-readconsole. - const CTRL_Z: u16 = 0x1A; - const CTRL_Z_MASK: u32 = 1 << CTRL_Z; - let input_control = c::CONSOLE_READCONSOLE_CONTROL { - nLength: size_of::() as u32, - nInitialChars: 0, - dwCtrlWakeupMask: CTRL_Z_MASK, - dwControlKeyState: 0, - }; - - let mut amount = 0; - loop { - cvt(unsafe { - c::SetLastError(0); - c::ReadConsoleW( - handle, - buf.as_mut_ptr() as *mut core::ffi::c_void, - buf.len() as u32, - &mut amount, - &input_control, - ) - })?; - - // ReadConsoleW returns success with ERROR_OPERATION_ABORTED for Ctrl-C or Ctrl-Break. - // Explicitly check for that case here and try again. - if amount == 0 && api::get_last_error() == WinError::OPERATION_ABORTED { - continue; - } - break; - } - // Safety: if `amount > 0`, then that many bytes were written, so - // `buf[amount as usize - 1]` has been initialized. - if amount > 0 && unsafe { buf[amount as usize - 1].assume_init() } == CTRL_Z { - amount -= 1; - } - Ok(amount as usize) -} - -fn utf16_to_utf8(utf16: &[u16], utf8: &mut [u8]) -> io::Result { - debug_assert!(utf16.len() <= i32::MAX as usize); - debug_assert!(utf8.len() <= i32::MAX as usize); - - if utf16.is_empty() { - return Ok(0); - } - - let result = unsafe { - c::WideCharToMultiByte( - c::CP_UTF8, // CodePage - c::WC_ERR_INVALID_CHARS, // dwFlags - utf16.as_ptr(), // lpWideCharStr - utf16.len() as i32, // cchWideChar - utf8.as_mut_ptr(), // lpMultiByteStr - utf8.len() as i32, // cbMultiByte - ptr::null(), // lpDefaultChar - ptr::null_mut(), // lpUsedDefaultChar - ) - }; - if result == 0 { - // We can't really do any better than forget all data and return an error. - Err(io::const_error!( - io::ErrorKind::InvalidData, - "Windows stdin in console mode does not support non-UTF-16 input; \ - encountered unpaired surrogate", - )) - } else { - Ok(result as usize) - } -} - -impl IncompleteUtf8 { - pub const fn new() -> IncompleteUtf8 { - IncompleteUtf8 { bytes: [0; MAX_LEN_UTF8], len: 0 } - } -} - -impl Stdout { - pub const fn new() -> Stdout { - Stdout { incomplete_utf8: IncompleteUtf8::new() } - } -} - -impl io::Write for Stdout { - fn write(&mut self, buf: &[u8]) -> io::Result { - write(c::STD_OUTPUT_HANDLE, buf, &mut self.incomplete_utf8) - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -impl Stderr { - pub const fn new() -> Stderr { - Stderr { incomplete_utf8: IncompleteUtf8::new() } - } -} - -impl io::Write for Stderr { - fn write(&mut self, buf: &[u8]) -> io::Result { - write(c::STD_ERROR_HANDLE, buf, &mut self.incomplete_utf8) - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -pub fn is_ebadf(err: &io::Error) -> bool { - err.raw_os_error() == Some(c::ERROR_INVALID_HANDLE as i32) -} - -pub fn panic_output() -> Option { - Some(Stderr::new()) -} diff --git a/library/std/src/sys/pal/windows/stdio/tests.rs b/library/std/src/sys/pal/windows/stdio/tests.rs deleted file mode 100644 index 1e53e0bee63..00000000000 --- a/library/std/src/sys/pal/windows/stdio/tests.rs +++ /dev/null @@ -1,6 +0,0 @@ -use super::utf16_to_utf8; - -#[test] -fn zero_size_read() { - assert_eq!(utf16_to_utf8(&[], &mut []).unwrap(), 0); -} diff --git a/library/std/src/sys/pal/xous/mod.rs b/library/std/src/sys/pal/xous/mod.rs index 3022d717b4a..7d823012ad1 100644 --- a/library/std/src/sys/pal/xous/mod.rs +++ b/library/std/src/sys/pal/xous/mod.rs @@ -8,7 +8,6 @@ pub mod os; pub mod pipe; #[path = "../unsupported/process.rs"] pub mod process; -pub mod stdio; pub mod thread; pub mod time; diff --git a/library/std/src/sys/pal/xous/stdio.rs b/library/std/src/sys/pal/xous/stdio.rs deleted file mode 100644 index 71736145221..00000000000 --- a/library/std/src/sys/pal/xous/stdio.rs +++ /dev/null @@ -1,133 +0,0 @@ -use crate::io; - -pub struct Stdin; -pub struct Stdout {} -pub struct Stderr; - -use crate::os::xous::ffi::{Connection, lend, try_lend, try_scalar}; -use crate::os::xous::services::{LogLend, LogScalar, log_server, try_connect}; - -impl Stdin { - pub const fn new() -> Stdin { - Stdin - } -} - -impl io::Read for Stdin { - fn read(&mut self, _buf: &mut [u8]) -> io::Result { - Ok(0) - } -} - -impl Stdout { - pub const fn new() -> Stdout { - Stdout {} - } -} - -impl io::Write for Stdout { - fn write(&mut self, buf: &[u8]) -> io::Result { - #[repr(C, align(4096))] - struct LendBuffer([u8; 4096]); - let mut lend_buffer = LendBuffer([0u8; 4096]); - let connection = log_server(); - for chunk in buf.chunks(lend_buffer.0.len()) { - for (dest, src) in lend_buffer.0.iter_mut().zip(chunk) { - *dest = *src; - } - lend(connection, LogLend::StandardOutput.into(), &lend_buffer.0, 0, chunk.len()) - .unwrap(); - } - Ok(buf.len()) - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -impl Stderr { - pub const fn new() -> Stderr { - Stderr - } -} - -impl io::Write for Stderr { - fn write(&mut self, buf: &[u8]) -> io::Result { - #[repr(C, align(4096))] - struct LendBuffer([u8; 4096]); - let mut lend_buffer = LendBuffer([0u8; 4096]); - let connection = log_server(); - for chunk in buf.chunks(lend_buffer.0.len()) { - for (dest, src) in lend_buffer.0.iter_mut().zip(chunk) { - *dest = *src; - } - lend(connection, LogLend::StandardError.into(), &lend_buffer.0, 0, chunk.len()) - .unwrap(); - } - Ok(buf.len()) - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -pub const STDIN_BUF_SIZE: usize = 0; - -pub fn is_ebadf(_err: &io::Error) -> bool { - true -} - -#[derive(Copy, Clone)] -pub struct PanicWriter { - log: Connection, - gfx: Option, -} - -impl io::Write for PanicWriter { - fn write(&mut self, s: &[u8]) -> core::result::Result { - for c in s.chunks(size_of::() * 4) { - // Text is grouped into 4x `usize` words. The id is 1100 plus - // the number of characters in this message. - // Ignore errors since we're already panicking. - try_scalar(self.log, LogScalar::AppendPanicMessage(&c).into()).ok(); - } - - // Serialize the text to the graphics panic handler, only if we were able - // to acquire a connection to it. Text length is encoded in the `valid` field, - // the data itself in the buffer. Typically several messages are require to - // fully transmit the entire panic message. - if let Some(gfx) = self.gfx { - #[repr(C, align(4096))] - struct Request([u8; 4096]); - let mut request = Request([0u8; 4096]); - for (&s, d) in s.iter().zip(request.0.iter_mut()) { - *d = s; - } - try_lend(gfx, 0 /* AppendPanicText */, &request.0, 0, s.len()).ok(); - } - Ok(s.len()) - } - - // Tests show that this does not seem to be reliably called at the end of a panic - // print, so, we can't rely on this to e.g. trigger a graphics update. - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -pub fn panic_output() -> Option { - // Generally this won't fail because every server has already connected, so - // this is likely to succeed. - let log = log_server(); - - // Send the "We're panicking" message (1000). - try_scalar(log, LogScalar::BeginPanic.into()).ok(); - - // This is will fail in the case that the connection table is full, or if the - // graphics server is not running. Most servers do not already have this connection. - let gfx = try_connect("panic-to-screen!"); - - Some(PanicWriter { log, gfx }) -} diff --git a/library/std/src/sys/pal/zkvm/mod.rs b/library/std/src/sys/pal/zkvm/mod.rs index ca5b479296d..499e2787201 100644 --- a/library/std/src/sys/pal/zkvm/mod.rs +++ b/library/std/src/sys/pal/zkvm/mod.rs @@ -19,7 +19,6 @@ pub mod os; pub mod pipe; #[path = "../unsupported/process.rs"] pub mod process; -pub mod stdio; #[path = "../unsupported/thread.rs"] pub mod thread; #[path = "../unsupported/time.rs"] diff --git a/library/std/src/sys/pal/zkvm/stdio.rs b/library/std/src/sys/pal/zkvm/stdio.rs deleted file mode 100644 index 0bcb54744b0..00000000000 --- a/library/std/src/sys/pal/zkvm/stdio.rs +++ /dev/null @@ -1,73 +0,0 @@ -use super::abi; -use super::abi::fileno; -use crate::io::{self, BorrowedCursor}; - -pub struct Stdin; -pub struct Stdout; -pub struct Stderr; - -impl Stdin { - pub const fn new() -> Stdin { - Stdin - } -} - -impl io::Read for Stdin { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - Ok(unsafe { abi::sys_read(fileno::STDIN, buf.as_mut_ptr(), buf.len()) }) - } - - fn read_buf(&mut self, mut buf: BorrowedCursor<'_>) -> io::Result<()> { - unsafe { - let n = abi::sys_read(fileno::STDIN, buf.as_mut().as_mut_ptr().cast(), buf.capacity()); - buf.advance_unchecked(n); - } - Ok(()) - } -} - -impl Stdout { - pub const fn new() -> Stdout { - Stdout - } -} - -impl io::Write for Stdout { - fn write(&mut self, buf: &[u8]) -> io::Result { - unsafe { abi::sys_write(fileno::STDOUT, buf.as_ptr(), buf.len()) } - - Ok(buf.len()) - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -impl Stderr { - pub const fn new() -> Stderr { - Stderr - } -} - -impl io::Write for Stderr { - fn write(&mut self, buf: &[u8]) -> io::Result { - unsafe { abi::sys_write(fileno::STDERR, buf.as_ptr(), buf.len()) } - - Ok(buf.len()) - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -pub const STDIN_BUF_SIZE: usize = crate::sys::io::DEFAULT_BUF_SIZE; - -pub fn is_ebadf(_err: &io::Error) -> bool { - true -} - -pub fn panic_output() -> Option { - Some(Stderr::new()) -} diff --git a/library/std/src/sys/stdio/mod.rs b/library/std/src/sys/stdio/mod.rs new file mode 100644 index 00000000000..2a9167bfe96 --- /dev/null +++ b/library/std/src/sys/stdio/mod.rs @@ -0,0 +1,38 @@ +#![forbid(unsafe_op_in_unsafe_fn)] + +cfg_if::cfg_if! { + if #[cfg(any( + target_family = "unix", + target_os = "hermit" + ))] { + mod unix; + pub use unix::*; + } else if #[cfg(target_os = "windows")] { + mod windows; + pub use windows::*; + } else if #[cfg(all(target_vendor = "fortanix", target_env = "sgx"))] { + mod sgx; + pub use sgx::*; + } else if #[cfg(target_os = "solid_asp3")] { + mod solid; + pub use solid::*; + } else if #[cfg(target_os = "teeos")] { + mod teeos; + pub use teeos::*; + } else if #[cfg(target_os = "uefi")] { + mod uefi; + pub use uefi::*; + } else if #[cfg(target_os = "wasi")] { + mod wasi; + pub use wasi::*; + } else if #[cfg(target_os = "xous")] { + mod xous; + pub use xous::*; + } else if #[cfg(target_os = "zkvm")] { + mod zkvm; + pub use zkvm::*; + } else { + mod unsupported; + pub use unsupported::*; + } +} diff --git a/library/std/src/sys/stdio/sgx.rs b/library/std/src/sys/stdio/sgx.rs new file mode 100644 index 00000000000..03d754cb217 --- /dev/null +++ b/library/std/src/sys/stdio/sgx.rs @@ -0,0 +1,70 @@ +use fortanix_sgx_abi as abi; + +use crate::io; +use crate::sys::fd::FileDesc; + +pub struct Stdin(()); +pub struct Stdout(()); +pub struct Stderr(()); + +fn with_std_fd R, R>(fd: abi::Fd, f: F) -> R { + let fd = FileDesc::new(fd); + let ret = f(&fd); + fd.into_raw(); + ret +} + +impl Stdin { + pub const fn new() -> Stdin { + Stdin(()) + } +} + +impl io::Read for Stdin { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + with_std_fd(abi::FD_STDIN, |fd| fd.read(buf)) + } +} + +impl Stdout { + pub const fn new() -> Stdout { + Stdout(()) + } +} + +impl io::Write for Stdout { + fn write(&mut self, buf: &[u8]) -> io::Result { + with_std_fd(abi::FD_STDOUT, |fd| fd.write(buf)) + } + + fn flush(&mut self) -> io::Result<()> { + with_std_fd(abi::FD_STDOUT, |fd| fd.flush()) + } +} + +impl Stderr { + pub const fn new() -> Stderr { + Stderr(()) + } +} + +impl io::Write for Stderr { + fn write(&mut self, buf: &[u8]) -> io::Result { + with_std_fd(abi::FD_STDERR, |fd| fd.write(buf)) + } + + fn flush(&mut self) -> io::Result<()> { + with_std_fd(abi::FD_STDERR, |fd| fd.flush()) + } +} + +pub const STDIN_BUF_SIZE: usize = crate::sys::io::DEFAULT_BUF_SIZE; + +pub fn is_ebadf(err: &io::Error) -> bool { + // FIXME: Rust normally maps Unix EBADF to `Uncategorized` + err.raw_os_error() == Some(abi::Error::BrokenPipe as _) +} + +pub fn panic_output() -> Option { + crate::sys::pal::abi::panic::SgxPanicOutput::new() +} diff --git a/library/std/src/sys/stdio/solid.rs b/library/std/src/sys/stdio/solid.rs new file mode 100644 index 00000000000..a2ff4bb212f --- /dev/null +++ b/library/std/src/sys/stdio/solid.rs @@ -0,0 +1,80 @@ +use crate::io; +use crate::sys::pal::abi; + +pub struct Stdin; +pub struct Stdout; +pub struct Stderr; +struct PanicOutput; + +impl Stdin { + pub const fn new() -> Stdin { + Stdin + } +} + +impl io::Read for Stdin { + fn read(&mut self, _buf: &mut [u8]) -> io::Result { + Ok(0) + } +} + +impl Stdout { + pub const fn new() -> Stdout { + Stdout + } +} + +impl io::Write for Stdout { + fn write(&mut self, buf: &[u8]) -> io::Result { + unsafe { abi::SOLID_LOG_write(buf.as_ptr(), buf.len()) }; + Ok(buf.len()) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl Stderr { + pub const fn new() -> Stderr { + Stderr + } +} + +impl io::Write for Stderr { + fn write(&mut self, buf: &[u8]) -> io::Result { + unsafe { abi::SOLID_LOG_write(buf.as_ptr(), buf.len()) }; + Ok(buf.len()) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl PanicOutput { + pub const fn new() -> PanicOutput { + PanicOutput + } +} + +impl io::Write for PanicOutput { + fn write(&mut self, buf: &[u8]) -> io::Result { + unsafe { abi::SOLID_LOG_write(buf.as_ptr(), buf.len()) }; + Ok(buf.len()) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +pub const STDIN_BUF_SIZE: usize = 0; + +pub fn is_ebadf(_err: &io::Error) -> bool { + true +} + +pub fn panic_output() -> Option { + Some(PanicOutput::new()) +} diff --git a/library/std/src/sys/stdio/teeos.rs b/library/std/src/sys/stdio/teeos.rs new file mode 100644 index 00000000000..67e251812da --- /dev/null +++ b/library/std/src/sys/stdio/teeos.rs @@ -0,0 +1,89 @@ +#![deny(unsafe_op_in_unsafe_fn)] + +use core::arch::asm; + +use crate::io; + +pub struct Stdin; +pub struct Stdout; +pub struct Stderr; + +const KCALL_DEBUG_CMD_PUT_BYTES: i64 = 2; + +unsafe fn debug_call(cap_ref: u64, call_no: i64, arg1: u64, arg2: u64) -> i32 { + let ret: u64; + unsafe { + asm!( + "svc #99", + inout("x0") cap_ref => ret, + in("x1") call_no, + in("x2") arg1, + in("x3") arg2, + ); + } + + ret as i32 +} + +fn print_buf(s: &[u8]) -> io::Result { + // Corresponds to `HM_DEBUG_PUT_BYTES_LIMIT`. + const MAX_LEN: usize = 512; + let len = if s.len() > MAX_LEN { MAX_LEN } else { s.len() }; + let result = unsafe { debug_call(0, KCALL_DEBUG_CMD_PUT_BYTES, s.as_ptr() as u64, len as u64) }; + + if result == 0 { Ok(len) } else { Err(io::Error::from(io::ErrorKind::InvalidInput)) } +} + +impl Stdin { + pub const fn new() -> Stdin { + Stdin + } +} + +impl io::Read for Stdin { + fn read(&mut self, _buf: &mut [u8]) -> io::Result { + Ok(0) + } +} + +impl Stdout { + pub const fn new() -> Stdout { + Stdout + } +} + +impl io::Write for Stdout { + fn write(&mut self, buf: &[u8]) -> io::Result { + print_buf(buf) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl Stderr { + pub const fn new() -> Stderr { + Stderr + } +} + +impl io::Write for Stderr { + fn write(&mut self, buf: &[u8]) -> io::Result { + print_buf(buf) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +pub const STDIN_BUF_SIZE: usize = 0; + +pub fn is_ebadf(err: &io::Error) -> bool { + err.raw_os_error() == Some(libc::EBADF as i32) +} + +pub fn panic_output() -> Option { + Some(Stderr::new()) +} diff --git a/library/std/src/sys/stdio/uefi.rs b/library/std/src/sys/stdio/uefi.rs new file mode 100644 index 00000000000..257e321dd03 --- /dev/null +++ b/library/std/src/sys/stdio/uefi.rs @@ -0,0 +1,219 @@ +use crate::io; +use crate::iter::Iterator; +use crate::mem::MaybeUninit; +use crate::os::uefi; +use crate::ptr::NonNull; + +pub struct Stdin { + surrogate: Option, + incomplete_utf8: IncompleteUtf8, +} + +struct IncompleteUtf8 { + bytes: [u8; 4], + len: u8, +} + +impl IncompleteUtf8 { + pub const fn new() -> IncompleteUtf8 { + IncompleteUtf8 { bytes: [0; 4], len: 0 } + } + + // Implemented for use in Stdin::read. + fn read(&mut self, buf: &mut [u8]) -> usize { + // Write to buffer until the buffer is full or we run out of bytes. + let to_write = crate::cmp::min(buf.len(), self.len as usize); + buf[..to_write].copy_from_slice(&self.bytes[..to_write]); + + // Rotate the remaining bytes if not enough remaining space in buffer. + if usize::from(self.len) > buf.len() { + self.bytes.copy_within(to_write.., 0); + self.len -= to_write as u8; + } else { + self.len = 0; + } + + to_write + } +} + +pub struct Stdout; +pub struct Stderr; + +impl Stdin { + pub const fn new() -> Stdin { + Stdin { surrogate: None, incomplete_utf8: IncompleteUtf8::new() } + } +} + +impl io::Read for Stdin { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + // If there are bytes in the incomplete utf-8, start with those. + // (No-op if there is nothing in the buffer.) + let mut bytes_copied = self.incomplete_utf8.read(buf); + + let stdin: *mut r_efi::protocols::simple_text_input::Protocol = unsafe { + let st: NonNull = uefi::env::system_table().cast(); + (*st.as_ptr()).con_in + }; + + if bytes_copied == buf.len() { + return Ok(bytes_copied); + } + + let ch = simple_text_input_read(stdin)?; + // Only 1 character should be returned. + let mut ch: Vec> = + if let Some(x) = self.surrogate.take() { + char::decode_utf16([x, ch]).collect() + } else { + char::decode_utf16([ch]).collect() + }; + + if ch.len() > 1 { + return Err(io::const_error!(io::ErrorKind::InvalidData, "invalid UTF-16 sequence")); + } + + match ch.pop().unwrap() { + Err(e) => { + self.surrogate = Some(e.unpaired_surrogate()); + } + Ok(x) => { + // This will always be > 0 + let buf_free_count = buf.len() - bytes_copied; + assert!(buf_free_count > 0); + + if buf_free_count >= x.len_utf8() { + // There is enough space in the buffer for the character. + bytes_copied += x.encode_utf8(&mut buf[bytes_copied..]).len(); + } else { + // There is not enough space in the buffer for the character. + // Store the character in the incomplete buffer. + self.incomplete_utf8.len = + x.encode_utf8(&mut self.incomplete_utf8.bytes).len() as u8; + // write partial character to buffer. + bytes_copied += self.incomplete_utf8.read(buf); + } + } + } + + Ok(bytes_copied) + } +} + +impl Stdout { + pub const fn new() -> Stdout { + Stdout + } +} + +impl io::Write for Stdout { + fn write(&mut self, buf: &[u8]) -> io::Result { + let st: NonNull = uefi::env::system_table().cast(); + let stdout = unsafe { (*st.as_ptr()).con_out }; + + write(stdout, buf) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl Stderr { + pub const fn new() -> Stderr { + Stderr + } +} + +impl io::Write for Stderr { + fn write(&mut self, buf: &[u8]) -> io::Result { + let st: NonNull = uefi::env::system_table().cast(); + let stderr = unsafe { (*st.as_ptr()).std_err }; + + write(stderr, buf) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +// UTF-16 character should occupy 4 bytes at most in UTF-8 +pub const STDIN_BUF_SIZE: usize = 4; + +pub fn is_ebadf(_err: &io::Error) -> bool { + false +} + +pub fn panic_output() -> Option { + uefi::env::try_system_table().map(|_| Stderr::new()) +} + +fn write( + protocol: *mut r_efi::protocols::simple_text_output::Protocol, + buf: &[u8], +) -> io::Result { + // Get valid UTF-8 buffer + let utf8 = match crate::str::from_utf8(buf) { + Ok(x) => x, + Err(e) => unsafe { crate::str::from_utf8_unchecked(&buf[..e.valid_up_to()]) }, + }; + + let mut utf16: Vec = utf8.encode_utf16().collect(); + // NULL terminate the string + utf16.push(0); + + unsafe { simple_text_output(protocol, &mut utf16) }?; + + Ok(utf8.len()) +} + +unsafe fn simple_text_output( + protocol: *mut r_efi::protocols::simple_text_output::Protocol, + buf: &mut [u16], +) -> io::Result<()> { + let res = unsafe { ((*protocol).output_string)(protocol, buf.as_mut_ptr()) }; + if res.is_error() { Err(io::Error::from_raw_os_error(res.as_usize())) } else { Ok(()) } +} + +fn simple_text_input_read( + stdin: *mut r_efi::protocols::simple_text_input::Protocol, +) -> io::Result { + loop { + match read_key_stroke(stdin) { + Ok(x) => return Ok(x.unicode_char), + Err(e) if e == r_efi::efi::Status::NOT_READY => wait_stdin(stdin)?, + Err(e) => return Err(io::Error::from_raw_os_error(e.as_usize())), + } + } +} + +fn wait_stdin(stdin: *mut r_efi::protocols::simple_text_input::Protocol) -> io::Result<()> { + let boot_services: NonNull = + uefi::env::boot_services().unwrap().cast(); + let wait_for_event = unsafe { (*boot_services.as_ptr()).wait_for_event }; + let wait_for_key_event = unsafe { (*stdin).wait_for_key }; + + let r = { + let mut x: usize = 0; + (wait_for_event)(1, [wait_for_key_event].as_mut_ptr(), &mut x) + }; + if r.is_error() { Err(io::Error::from_raw_os_error(r.as_usize())) } else { Ok(()) } +} + +fn read_key_stroke( + stdin: *mut r_efi::protocols::simple_text_input::Protocol, +) -> Result { + let mut input_key: MaybeUninit = + MaybeUninit::uninit(); + + let r = unsafe { ((*stdin).read_key_stroke)(stdin, input_key.as_mut_ptr()) }; + + if r.is_error() { + Err(r) + } else { + let input_key = unsafe { input_key.assume_init() }; + Ok(input_key) + } +} diff --git a/library/std/src/sys/stdio/unix.rs b/library/std/src/sys/stdio/unix.rs new file mode 100644 index 00000000000..8d133857c59 --- /dev/null +++ b/library/std/src/sys/stdio/unix.rs @@ -0,0 +1,106 @@ +#[cfg(target_os = "hermit")] +use hermit_abi::{EBADF, STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO}; +#[cfg(target_family = "unix")] +use libc::{EBADF, STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO}; + +#[cfg(target_family = "unix")] +use crate::io::BorrowedCursor; +use crate::io::{self, IoSlice, IoSliceMut}; +use crate::mem::ManuallyDrop; +#[cfg(target_os = "hermit")] +use crate::os::hermit::io::FromRawFd; +#[cfg(target_family = "unix")] +use crate::os::unix::io::FromRawFd; +use crate::sys::fd::FileDesc; + +pub struct Stdin(()); +pub struct Stdout(()); +pub struct Stderr(()); + +impl Stdin { + pub const fn new() -> Stdin { + Stdin(()) + } +} + +impl io::Read for Stdin { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + unsafe { ManuallyDrop::new(FileDesc::from_raw_fd(STDIN_FILENO)).read(buf) } + } + + #[cfg(not(target_os = "hermit"))] + fn read_buf(&mut self, buf: BorrowedCursor<'_>) -> io::Result<()> { + unsafe { ManuallyDrop::new(FileDesc::from_raw_fd(STDIN_FILENO)).read_buf(buf) } + } + + fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { + unsafe { ManuallyDrop::new(FileDesc::from_raw_fd(STDIN_FILENO)).read_vectored(bufs) } + } + + #[inline] + fn is_read_vectored(&self) -> bool { + true + } +} + +impl Stdout { + pub const fn new() -> Stdout { + Stdout(()) + } +} + +impl io::Write for Stdout { + fn write(&mut self, buf: &[u8]) -> io::Result { + unsafe { ManuallyDrop::new(FileDesc::from_raw_fd(STDOUT_FILENO)).write(buf) } + } + + fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + unsafe { ManuallyDrop::new(FileDesc::from_raw_fd(STDOUT_FILENO)).write_vectored(bufs) } + } + + #[inline] + fn is_write_vectored(&self) -> bool { + true + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl Stderr { + pub const fn new() -> Stderr { + Stderr(()) + } +} + +impl io::Write for Stderr { + fn write(&mut self, buf: &[u8]) -> io::Result { + unsafe { ManuallyDrop::new(FileDesc::from_raw_fd(STDERR_FILENO)).write(buf) } + } + + fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + unsafe { ManuallyDrop::new(FileDesc::from_raw_fd(STDERR_FILENO)).write_vectored(bufs) } + } + + #[inline] + fn is_write_vectored(&self) -> bool { + true + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +pub fn is_ebadf(err: &io::Error) -> bool { + err.raw_os_error() == Some(EBADF as i32) +} + +pub const STDIN_BUF_SIZE: usize = crate::sys::io::DEFAULT_BUF_SIZE; + +pub fn panic_output() -> Option { + Some(Stderr::new()) +} diff --git a/library/std/src/sys/stdio/unsupported.rs b/library/std/src/sys/stdio/unsupported.rs new file mode 100644 index 00000000000..b5e3f5be988 --- /dev/null +++ b/library/std/src/sys/stdio/unsupported.rs @@ -0,0 +1,59 @@ +use crate::io; + +pub struct Stdin; +pub struct Stdout; +pub struct Stderr; + +impl Stdin { + pub const fn new() -> Stdin { + Stdin + } +} + +impl io::Read for Stdin { + fn read(&mut self, _buf: &mut [u8]) -> io::Result { + Ok(0) + } +} + +impl Stdout { + pub const fn new() -> Stdout { + Stdout + } +} + +impl io::Write for Stdout { + fn write(&mut self, buf: &[u8]) -> io::Result { + Ok(buf.len()) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl Stderr { + pub const fn new() -> Stderr { + Stderr + } +} + +impl io::Write for Stderr { + fn write(&mut self, buf: &[u8]) -> io::Result { + Ok(buf.len()) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +pub const STDIN_BUF_SIZE: usize = 0; + +pub fn is_ebadf(_err: &io::Error) -> bool { + true +} + +pub fn panic_output() -> Option> { + None +} diff --git a/library/std/src/sys/stdio/wasi.rs b/library/std/src/sys/stdio/wasi.rs new file mode 100644 index 00000000000..8105b0cfa2f --- /dev/null +++ b/library/std/src/sys/stdio/wasi.rs @@ -0,0 +1,117 @@ +#![forbid(unsafe_op_in_unsafe_fn)] + +use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut}; +use crate::mem::ManuallyDrop; +use crate::os::raw; +use crate::os::wasi::io::{AsRawFd, FromRawFd}; +use crate::sys::pal::fd::WasiFd; + +pub struct Stdin; +pub struct Stdout; +pub struct Stderr; + +impl Stdin { + pub const fn new() -> Stdin { + Stdin + } +} + +impl AsRawFd for Stdin { + #[inline] + fn as_raw_fd(&self) -> raw::c_int { + 0 + } +} + +impl io::Read for Stdin { + fn read(&mut self, data: &mut [u8]) -> io::Result { + self.read_vectored(&mut [IoSliceMut::new(data)]) + } + + fn read_buf(&mut self, buf: BorrowedCursor<'_>) -> io::Result<()> { + ManuallyDrop::new(unsafe { WasiFd::from_raw_fd(self.as_raw_fd()) }).read_buf(buf) + } + + fn read_vectored(&mut self, data: &mut [IoSliceMut<'_>]) -> io::Result { + ManuallyDrop::new(unsafe { WasiFd::from_raw_fd(self.as_raw_fd()) }).read(data) + } + + #[inline] + fn is_read_vectored(&self) -> bool { + true + } +} + +impl Stdout { + pub const fn new() -> Stdout { + Stdout + } +} + +impl AsRawFd for Stdout { + #[inline] + fn as_raw_fd(&self) -> raw::c_int { + 1 + } +} + +impl io::Write for Stdout { + fn write(&mut self, data: &[u8]) -> io::Result { + self.write_vectored(&[IoSlice::new(data)]) + } + + fn write_vectored(&mut self, data: &[IoSlice<'_>]) -> io::Result { + ManuallyDrop::new(unsafe { WasiFd::from_raw_fd(self.as_raw_fd()) }).write(data) + } + + #[inline] + fn is_write_vectored(&self) -> bool { + true + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl Stderr { + pub const fn new() -> Stderr { + Stderr + } +} + +impl AsRawFd for Stderr { + #[inline] + fn as_raw_fd(&self) -> raw::c_int { + 2 + } +} + +impl io::Write for Stderr { + fn write(&mut self, data: &[u8]) -> io::Result { + self.write_vectored(&[IoSlice::new(data)]) + } + + fn write_vectored(&mut self, data: &[IoSlice<'_>]) -> io::Result { + ManuallyDrop::new(unsafe { WasiFd::from_raw_fd(self.as_raw_fd()) }).write(data) + } + + #[inline] + fn is_write_vectored(&self) -> bool { + true + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +pub const STDIN_BUF_SIZE: usize = crate::sys::io::DEFAULT_BUF_SIZE; + +pub fn is_ebadf(err: &io::Error) -> bool { + err.raw_os_error() == Some(wasi::ERRNO_BADF.raw().into()) +} + +pub fn panic_output() -> Option { + Some(Stderr::new()) +} diff --git a/library/std/src/sys/stdio/windows.rs b/library/std/src/sys/stdio/windows.rs new file mode 100644 index 00000000000..9b27f76b9dd --- /dev/null +++ b/library/std/src/sys/stdio/windows.rs @@ -0,0 +1,472 @@ +#![unstable(issue = "none", feature = "windows_stdio")] + +use core::char::MAX_LEN_UTF8; +use core::str::utf8_char_width; + +use crate::mem::MaybeUninit; +use crate::os::windows::io::{FromRawHandle, IntoRawHandle}; +use crate::sys::handle::Handle; +use crate::sys::pal::api::{self, WinError}; +use crate::sys::{c, cvt}; +use crate::{cmp, io, ptr, str}; + +#[cfg(test)] +mod tests; + +// Don't cache handles but get them fresh for every read/write. This allows us to track changes to +// the value over time (such as if a process calls `SetStdHandle` while it's running). See #40490. +pub struct Stdin { + surrogate: u16, + incomplete_utf8: IncompleteUtf8, +} + +pub struct Stdout { + incomplete_utf8: IncompleteUtf8, +} + +pub struct Stderr { + incomplete_utf8: IncompleteUtf8, +} + +struct IncompleteUtf8 { + bytes: [u8; 4], + len: u8, +} + +impl IncompleteUtf8 { + // Implemented for use in Stdin::read. + fn read(&mut self, buf: &mut [u8]) -> usize { + // Write to buffer until the buffer is full or we run out of bytes. + let to_write = cmp::min(buf.len(), self.len as usize); + buf[..to_write].copy_from_slice(&self.bytes[..to_write]); + + // Rotate the remaining bytes if not enough remaining space in buffer. + if usize::from(self.len) > buf.len() { + self.bytes.copy_within(to_write.., 0); + self.len -= to_write as u8; + } else { + self.len = 0; + } + + to_write + } +} + +// Apparently Windows doesn't handle large reads on stdin or writes to stdout/stderr well (see +// #13304 for details). +// +// From MSDN (2011): "The storage for this buffer is allocated from a shared heap for the +// process that is 64 KB in size. The maximum size of the buffer will depend on heap usage." +// +// We choose the cap at 8 KiB because libuv does the same, and it seems to be acceptable so far. +const MAX_BUFFER_SIZE: usize = 8192; + +// The standard buffer size of BufReader for Stdin should be able to hold 3x more bytes than there +// are `u16`'s in MAX_BUFFER_SIZE. This ensures the read data can always be completely decoded from +// UTF-16 to UTF-8. +pub const STDIN_BUF_SIZE: usize = MAX_BUFFER_SIZE / 2 * 3; + +pub fn get_handle(handle_id: u32) -> io::Result { + let handle = unsafe { c::GetStdHandle(handle_id) }; + if handle == c::INVALID_HANDLE_VALUE { + Err(io::Error::last_os_error()) + } else if handle.is_null() { + Err(io::Error::from_raw_os_error(c::ERROR_INVALID_HANDLE as i32)) + } else { + Ok(handle) + } +} + +fn is_console(handle: c::HANDLE) -> bool { + // `GetConsoleMode` will return false (0) if this is a pipe (we don't care about the reported + // mode). This will only detect Windows Console, not other terminals connected to a pipe like + // MSYS. Which is exactly what we need, as only Windows Console needs a conversion to UTF-16. + let mut mode = 0; + unsafe { c::GetConsoleMode(handle, &mut mode) != 0 } +} + +/// Returns true if the attached console's code page is currently UTF-8. +#[cfg(not(target_vendor = "win7"))] +fn is_utf8_console() -> bool { + unsafe { c::GetConsoleOutputCP() == c::CP_UTF8 } +} + +#[cfg(target_vendor = "win7")] +fn is_utf8_console() -> bool { + // Windows 7 has a fun "feature" where WriteFile on a console handle will return + // the number of UTF-16 code units written and not the number of bytes from the input string. + // So we always claim the console isn't UTF-8 to trigger the WriteConsole fallback code. + false +} + +fn write(handle_id: u32, data: &[u8], incomplete_utf8: &mut IncompleteUtf8) -> io::Result { + if data.is_empty() { + return Ok(0); + } + + let handle = get_handle(handle_id)?; + if !is_console(handle) || is_utf8_console() { + unsafe { + let handle = Handle::from_raw_handle(handle); + let ret = handle.write(data); + let _ = handle.into_raw_handle(); // Don't close the handle + return ret; + } + } else { + write_console_utf16(data, incomplete_utf8, handle) + } +} + +fn write_console_utf16( + data: &[u8], + incomplete_utf8: &mut IncompleteUtf8, + handle: c::HANDLE, +) -> io::Result { + if incomplete_utf8.len > 0 { + assert!( + incomplete_utf8.len < 4, + "Unexpected number of bytes for incomplete UTF-8 codepoint." + ); + if data[0] >> 6 != 0b10 { + // not a continuation byte - reject + incomplete_utf8.len = 0; + return Err(io::const_error!( + io::ErrorKind::InvalidData, + "Windows stdio in console mode does not support writing non-UTF-8 byte sequences", + )); + } + incomplete_utf8.bytes[incomplete_utf8.len as usize] = data[0]; + incomplete_utf8.len += 1; + let char_width = utf8_char_width(incomplete_utf8.bytes[0]); + if (incomplete_utf8.len as usize) < char_width { + // more bytes needed + return Ok(1); + } + let s = str::from_utf8(&incomplete_utf8.bytes[0..incomplete_utf8.len as usize]); + incomplete_utf8.len = 0; + match s { + Ok(s) => { + assert_eq!(char_width, s.len()); + let written = write_valid_utf8_to_console(handle, s)?; + assert_eq!(written, s.len()); // guaranteed by write_valid_utf8_to_console() for single codepoint writes + return Ok(1); + } + Err(_) => { + return Err(io::const_error!( + io::ErrorKind::InvalidData, + "Windows stdio in console mode does not support writing non-UTF-8 byte sequences", + )); + } + } + } + + // As the console is meant for presenting text, we assume bytes of `data` are encoded as UTF-8, + // which needs to be encoded as UTF-16. + // + // If the data is not valid UTF-8 we write out as many bytes as are valid. + // If the first byte is invalid it is either first byte of a multi-byte sequence but the + // provided byte slice is too short or it is the first byte of an invalid multi-byte sequence. + let len = cmp::min(data.len(), MAX_BUFFER_SIZE / 2); + let utf8 = match str::from_utf8(&data[..len]) { + Ok(s) => s, + Err(ref e) if e.valid_up_to() == 0 => { + let first_byte_char_width = utf8_char_width(data[0]); + if first_byte_char_width > 1 && data.len() < first_byte_char_width { + incomplete_utf8.bytes[0] = data[0]; + incomplete_utf8.len = 1; + return Ok(1); + } else { + return Err(io::const_error!( + io::ErrorKind::InvalidData, + "Windows stdio in console mode does not support writing non-UTF-8 byte sequences", + )); + } + } + Err(e) => str::from_utf8(&data[..e.valid_up_to()]).unwrap(), + }; + + write_valid_utf8_to_console(handle, utf8) +} + +fn write_valid_utf8_to_console(handle: c::HANDLE, utf8: &str) -> io::Result { + debug_assert!(!utf8.is_empty()); + + let mut utf16 = [MaybeUninit::::uninit(); MAX_BUFFER_SIZE / 2]; + let utf8 = &utf8[..utf8.floor_char_boundary(utf16.len())]; + + let utf16: &[u16] = unsafe { + // Note that this theoretically checks validity twice in the (most common) case + // where the underlying byte sequence is valid utf-8 (given the check in `write()`). + let result = c::MultiByteToWideChar( + c::CP_UTF8, // CodePage + c::MB_ERR_INVALID_CHARS, // dwFlags + utf8.as_ptr(), // lpMultiByteStr + utf8.len() as i32, // cbMultiByte + utf16.as_mut_ptr() as *mut c::WCHAR, // lpWideCharStr + utf16.len() as i32, // cchWideChar + ); + assert!(result != 0, "Unexpected error in MultiByteToWideChar"); + + // Safety: MultiByteToWideChar initializes `result` values. + utf16[..result as usize].assume_init_ref() + }; + + let mut written = write_u16s(handle, utf16)?; + + // Figure out how many bytes of as UTF-8 were written away as UTF-16. + if written == utf16.len() { + Ok(utf8.len()) + } else { + // Make sure we didn't end up writing only half of a surrogate pair (even though the chance + // is tiny). Because it is not possible for user code to re-slice `data` in such a way that + // a missing surrogate can be produced (and also because of the UTF-8 validation above), + // write the missing surrogate out now. + // Buffering it would mean we have to lie about the number of bytes written. + let first_code_unit_remaining = utf16[written]; + if matches!(first_code_unit_remaining, 0xDCEE..=0xDFFF) { + // low surrogate + // We just hope this works, and give up otherwise + let _ = write_u16s(handle, &utf16[written..written + 1]); + written += 1; + } + // Calculate the number of bytes of `utf8` that were actually written. + let mut count = 0; + for ch in utf16[..written].iter() { + count += match ch { + 0x0000..=0x007F => 1, + 0x0080..=0x07FF => 2, + 0xDCEE..=0xDFFF => 1, // Low surrogate. We already counted 3 bytes for the other. + _ => 3, + }; + } + debug_assert!(String::from_utf16(&utf16[..written]).unwrap() == utf8[..count]); + Ok(count) + } +} + +fn write_u16s(handle: c::HANDLE, data: &[u16]) -> io::Result { + debug_assert!(data.len() < u32::MAX as usize); + let mut written = 0; + cvt(unsafe { + c::WriteConsoleW(handle, data.as_ptr(), data.len() as u32, &mut written, ptr::null_mut()) + })?; + Ok(written as usize) +} + +impl Stdin { + pub const fn new() -> Stdin { + Stdin { surrogate: 0, incomplete_utf8: IncompleteUtf8::new() } + } +} + +impl io::Read for Stdin { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let handle = get_handle(c::STD_INPUT_HANDLE)?; + if !is_console(handle) { + unsafe { + let handle = Handle::from_raw_handle(handle); + let ret = handle.read(buf); + let _ = handle.into_raw_handle(); // Don't close the handle + return ret; + } + } + + // If there are bytes in the incomplete utf-8, start with those. + // (No-op if there is nothing in the buffer.) + let mut bytes_copied = self.incomplete_utf8.read(buf); + + if bytes_copied == buf.len() { + Ok(bytes_copied) + } else if buf.len() - bytes_copied < 4 { + // Not enough space to get a UTF-8 byte. We will use the incomplete UTF8. + let mut utf16_buf = [MaybeUninit::new(0); 1]; + // Read one u16 character. + let read = read_u16s_fixup_surrogates(handle, &mut utf16_buf, 1, &mut self.surrogate)?; + // Read bytes, using the (now-empty) self.incomplete_utf8 as extra space. + let read_bytes = utf16_to_utf8( + unsafe { utf16_buf[..read].assume_init_ref() }, + &mut self.incomplete_utf8.bytes, + )?; + + // Read in the bytes from incomplete_utf8 until the buffer is full. + self.incomplete_utf8.len = read_bytes as u8; + // No-op if no bytes. + bytes_copied += self.incomplete_utf8.read(&mut buf[bytes_copied..]); + Ok(bytes_copied) + } else { + let mut utf16_buf = [MaybeUninit::::uninit(); MAX_BUFFER_SIZE / 2]; + + // In the worst case, a UTF-8 string can take 3 bytes for every `u16` of a UTF-16. So + // we can read at most a third of `buf.len()` chars and uphold the guarantee no data gets + // lost. + let amount = cmp::min(buf.len() / 3, utf16_buf.len()); + let read = + read_u16s_fixup_surrogates(handle, &mut utf16_buf, amount, &mut self.surrogate)?; + // Safety `read_u16s_fixup_surrogates` returns the number of items + // initialized. + let utf16s = unsafe { utf16_buf[..read].assume_init_ref() }; + match utf16_to_utf8(utf16s, buf) { + Ok(value) => return Ok(bytes_copied + value), + Err(e) => return Err(e), + } + } + } +} + +// We assume that if the last `u16` is an unpaired surrogate they got sliced apart by our +// buffer size, and keep it around for the next read hoping to put them together. +// This is a best effort, and might not work if we are not the only reader on Stdin. +fn read_u16s_fixup_surrogates( + handle: c::HANDLE, + buf: &mut [MaybeUninit], + mut amount: usize, + surrogate: &mut u16, +) -> io::Result { + // Insert possibly remaining unpaired surrogate from last read. + let mut start = 0; + if *surrogate != 0 { + buf[0] = MaybeUninit::new(*surrogate); + *surrogate = 0; + start = 1; + if amount == 1 { + // Special case: `Stdin::read` guarantees we can always read at least one new `u16` + // and combine it with an unpaired surrogate, because the UTF-8 buffer is at least + // 4 bytes. + amount = 2; + } + } + let mut amount = read_u16s(handle, &mut buf[start..amount])? + start; + + if amount > 0 { + // Safety: The returned `amount` is the number of values initialized, + // and it is not 0, so we know that `buf[amount - 1]` have been + // initialized. + let last_char = unsafe { buf[amount - 1].assume_init() }; + if matches!(last_char, 0xD800..=0xDBFF) { + // high surrogate + *surrogate = last_char; + amount -= 1; + } + } + Ok(amount) +} + +// Returns `Ok(n)` if it initialized `n` values in `buf`. +fn read_u16s(handle: c::HANDLE, buf: &mut [MaybeUninit]) -> io::Result { + // Configure the `pInputControl` parameter to not only return on `\r\n` but also Ctrl-Z, the + // traditional DOS method to indicate end of character stream / user input (SUB). + // See #38274 and https://stackoverflow.com/questions/43836040/win-api-readconsole. + const CTRL_Z: u16 = 0x1A; + const CTRL_Z_MASK: u32 = 1 << CTRL_Z; + let input_control = c::CONSOLE_READCONSOLE_CONTROL { + nLength: size_of::() as u32, + nInitialChars: 0, + dwCtrlWakeupMask: CTRL_Z_MASK, + dwControlKeyState: 0, + }; + + let mut amount = 0; + loop { + cvt(unsafe { + c::SetLastError(0); + c::ReadConsoleW( + handle, + buf.as_mut_ptr() as *mut core::ffi::c_void, + buf.len() as u32, + &mut amount, + &input_control, + ) + })?; + + // ReadConsoleW returns success with ERROR_OPERATION_ABORTED for Ctrl-C or Ctrl-Break. + // Explicitly check for that case here and try again. + if amount == 0 && api::get_last_error() == WinError::OPERATION_ABORTED { + continue; + } + break; + } + // Safety: if `amount > 0`, then that many bytes were written, so + // `buf[amount as usize - 1]` has been initialized. + if amount > 0 && unsafe { buf[amount as usize - 1].assume_init() } == CTRL_Z { + amount -= 1; + } + Ok(amount as usize) +} + +fn utf16_to_utf8(utf16: &[u16], utf8: &mut [u8]) -> io::Result { + debug_assert!(utf16.len() <= i32::MAX as usize); + debug_assert!(utf8.len() <= i32::MAX as usize); + + if utf16.is_empty() { + return Ok(0); + } + + let result = unsafe { + c::WideCharToMultiByte( + c::CP_UTF8, // CodePage + c::WC_ERR_INVALID_CHARS, // dwFlags + utf16.as_ptr(), // lpWideCharStr + utf16.len() as i32, // cchWideChar + utf8.as_mut_ptr(), // lpMultiByteStr + utf8.len() as i32, // cbMultiByte + ptr::null(), // lpDefaultChar + ptr::null_mut(), // lpUsedDefaultChar + ) + }; + if result == 0 { + // We can't really do any better than forget all data and return an error. + Err(io::const_error!( + io::ErrorKind::InvalidData, + "Windows stdin in console mode does not support non-UTF-16 input; \ + encountered unpaired surrogate", + )) + } else { + Ok(result as usize) + } +} + +impl IncompleteUtf8 { + pub const fn new() -> IncompleteUtf8 { + IncompleteUtf8 { bytes: [0; MAX_LEN_UTF8], len: 0 } + } +} + +impl Stdout { + pub const fn new() -> Stdout { + Stdout { incomplete_utf8: IncompleteUtf8::new() } + } +} + +impl io::Write for Stdout { + fn write(&mut self, buf: &[u8]) -> io::Result { + write(c::STD_OUTPUT_HANDLE, buf, &mut self.incomplete_utf8) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl Stderr { + pub const fn new() -> Stderr { + Stderr { incomplete_utf8: IncompleteUtf8::new() } + } +} + +impl io::Write for Stderr { + fn write(&mut self, buf: &[u8]) -> io::Result { + write(c::STD_ERROR_HANDLE, buf, &mut self.incomplete_utf8) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +pub fn is_ebadf(err: &io::Error) -> bool { + err.raw_os_error() == Some(c::ERROR_INVALID_HANDLE as i32) +} + +pub fn panic_output() -> Option { + Some(Stderr::new()) +} diff --git a/library/std/src/sys/stdio/windows/tests.rs b/library/std/src/sys/stdio/windows/tests.rs new file mode 100644 index 00000000000..1e53e0bee63 --- /dev/null +++ b/library/std/src/sys/stdio/windows/tests.rs @@ -0,0 +1,6 @@ +use super::utf16_to_utf8; + +#[test] +fn zero_size_read() { + assert_eq!(utf16_to_utf8(&[], &mut []).unwrap(), 0); +} diff --git a/library/std/src/sys/stdio/xous.rs b/library/std/src/sys/stdio/xous.rs new file mode 100644 index 00000000000..71736145221 --- /dev/null +++ b/library/std/src/sys/stdio/xous.rs @@ -0,0 +1,133 @@ +use crate::io; + +pub struct Stdin; +pub struct Stdout {} +pub struct Stderr; + +use crate::os::xous::ffi::{Connection, lend, try_lend, try_scalar}; +use crate::os::xous::services::{LogLend, LogScalar, log_server, try_connect}; + +impl Stdin { + pub const fn new() -> Stdin { + Stdin + } +} + +impl io::Read for Stdin { + fn read(&mut self, _buf: &mut [u8]) -> io::Result { + Ok(0) + } +} + +impl Stdout { + pub const fn new() -> Stdout { + Stdout {} + } +} + +impl io::Write for Stdout { + fn write(&mut self, buf: &[u8]) -> io::Result { + #[repr(C, align(4096))] + struct LendBuffer([u8; 4096]); + let mut lend_buffer = LendBuffer([0u8; 4096]); + let connection = log_server(); + for chunk in buf.chunks(lend_buffer.0.len()) { + for (dest, src) in lend_buffer.0.iter_mut().zip(chunk) { + *dest = *src; + } + lend(connection, LogLend::StandardOutput.into(), &lend_buffer.0, 0, chunk.len()) + .unwrap(); + } + Ok(buf.len()) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl Stderr { + pub const fn new() -> Stderr { + Stderr + } +} + +impl io::Write for Stderr { + fn write(&mut self, buf: &[u8]) -> io::Result { + #[repr(C, align(4096))] + struct LendBuffer([u8; 4096]); + let mut lend_buffer = LendBuffer([0u8; 4096]); + let connection = log_server(); + for chunk in buf.chunks(lend_buffer.0.len()) { + for (dest, src) in lend_buffer.0.iter_mut().zip(chunk) { + *dest = *src; + } + lend(connection, LogLend::StandardError.into(), &lend_buffer.0, 0, chunk.len()) + .unwrap(); + } + Ok(buf.len()) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +pub const STDIN_BUF_SIZE: usize = 0; + +pub fn is_ebadf(_err: &io::Error) -> bool { + true +} + +#[derive(Copy, Clone)] +pub struct PanicWriter { + log: Connection, + gfx: Option, +} + +impl io::Write for PanicWriter { + fn write(&mut self, s: &[u8]) -> core::result::Result { + for c in s.chunks(size_of::() * 4) { + // Text is grouped into 4x `usize` words. The id is 1100 plus + // the number of characters in this message. + // Ignore errors since we're already panicking. + try_scalar(self.log, LogScalar::AppendPanicMessage(&c).into()).ok(); + } + + // Serialize the text to the graphics panic handler, only if we were able + // to acquire a connection to it. Text length is encoded in the `valid` field, + // the data itself in the buffer. Typically several messages are require to + // fully transmit the entire panic message. + if let Some(gfx) = self.gfx { + #[repr(C, align(4096))] + struct Request([u8; 4096]); + let mut request = Request([0u8; 4096]); + for (&s, d) in s.iter().zip(request.0.iter_mut()) { + *d = s; + } + try_lend(gfx, 0 /* AppendPanicText */, &request.0, 0, s.len()).ok(); + } + Ok(s.len()) + } + + // Tests show that this does not seem to be reliably called at the end of a panic + // print, so, we can't rely on this to e.g. trigger a graphics update. + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +pub fn panic_output() -> Option { + // Generally this won't fail because every server has already connected, so + // this is likely to succeed. + let log = log_server(); + + // Send the "We're panicking" message (1000). + try_scalar(log, LogScalar::BeginPanic.into()).ok(); + + // This is will fail in the case that the connection table is full, or if the + // graphics server is not running. Most servers do not already have this connection. + let gfx = try_connect("panic-to-screen!"); + + Some(PanicWriter { log, gfx }) +} diff --git a/library/std/src/sys/stdio/zkvm.rs b/library/std/src/sys/stdio/zkvm.rs new file mode 100644 index 00000000000..f31c6c26e87 --- /dev/null +++ b/library/std/src/sys/stdio/zkvm.rs @@ -0,0 +1,72 @@ +use crate::io::{self, BorrowedCursor}; +use crate::sys::pal::abi::{self, fileno}; + +pub struct Stdin; +pub struct Stdout; +pub struct Stderr; + +impl Stdin { + pub const fn new() -> Stdin { + Stdin + } +} + +impl io::Read for Stdin { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + Ok(unsafe { abi::sys_read(fileno::STDIN, buf.as_mut_ptr(), buf.len()) }) + } + + fn read_buf(&mut self, mut buf: BorrowedCursor<'_>) -> io::Result<()> { + unsafe { + let n = abi::sys_read(fileno::STDIN, buf.as_mut().as_mut_ptr().cast(), buf.capacity()); + buf.advance_unchecked(n); + } + Ok(()) + } +} + +impl Stdout { + pub const fn new() -> Stdout { + Stdout + } +} + +impl io::Write for Stdout { + fn write(&mut self, buf: &[u8]) -> io::Result { + unsafe { abi::sys_write(fileno::STDOUT, buf.as_ptr(), buf.len()) } + + Ok(buf.len()) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl Stderr { + pub const fn new() -> Stderr { + Stderr + } +} + +impl io::Write for Stderr { + fn write(&mut self, buf: &[u8]) -> io::Result { + unsafe { abi::sys_write(fileno::STDERR, buf.as_ptr(), buf.len()) } + + Ok(buf.len()) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +pub const STDIN_BUF_SIZE: usize = crate::sys::io::DEFAULT_BUF_SIZE; + +pub fn is_ebadf(_err: &io::Error) -> bool { + true +} + +pub fn panic_output() -> Option { + Some(Stderr::new()) +} -- cgit 1.4.1-3-g733a5