From 9dbfbbac811075bb4474f81ceb0ac810ae202989 Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Thu, 20 Feb 2025 02:11:46 +0900 Subject: [PATCH] io: Add more trait impls --- .github/.cspell/project-dictionary.txt | 1 + CHANGELOG.md | 8 + src/io/error.rs | 126 +++++-- src/io/impls.rs | 495 +++++++++++++++++++++++++ src/io/mod.rs | 54 ++- src/io/stdio.rs | 1 + src/sys/errno.rs | 5 + src/sys/mod.rs | 2 +- 8 files changed, 637 insertions(+), 55 deletions(-) create mode 100644 src/io/impls.rs diff --git a/.github/.cspell/project-dictionary.txt b/.github/.cspell/project-dictionary.txt index 28a67c5..e58e4b1 100644 --- a/.github/.cspell/project-dictionary.txt +++ b/.github/.cspell/project-dictionary.txt @@ -6,6 +6,7 @@ blksize CMDLINE CREAT demangle +discontiguous ebreak espup fipe diff --git a/CHANGELOG.md b/CHANGELOG.md index 0427b97..c468ca4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,14 @@ Note: In this file, do not use the hard wrap in the middle of a sentence for com ## [Unreleased] +- Implement `From` and `From` for `io::Error` when `alloc` feature is enabled to align `std::io::Error`. + +- Implement `io::{Read,Write,Seek}` for `&mut impl io::{Read,Write,Seek}` and `alloc::boxed::Box` (when `alloc` feature is enabled) to align `std::io`. + +- Implement `io::Read` for `&[u8]` and `alloc::collections::VecDeque` (when `alloc` feature is enabled) to align `std::io`. + +- Implement `io::Write` for `&mut [u8]`, `alloc::vec::Vec` (when `alloc` feature is enabled), and `alloc::collections::VecDeque` (when `alloc` feature is enabled) to align `std::io`. + ## [0.1.18] - 2025-01-06 - Add `io::ErrorKind::CrossesDevices` variant to reflect [upstream stabilization in Rust 1.85](https://github.com/rust-lang/rust/pull/130209). ([0a09ce5](https://github.com/taiki-e/semihosting/commit/0a09ce540784739f972e76fe719a573a744b98eb)) diff --git a/src/io/error.rs b/src/io/error.rs index c9af491..9b096ac 100644 --- a/src/io/error.rs +++ b/src/io/error.rs @@ -11,13 +11,6 @@ use crate::sys; /// [std]: https://doc.rust-lang.org/std/io/type.Result.html pub type Result = core::result::Result; -/// The type of raw OS error codes returned by [`Error::raw_os_error`]. -/// -/// See [`std::io::RawOsError` documentation][std] for details. -/// -/// [std]: https://doc.rust-lang.org/nightly/std/io/type.RawOsError.html -pub type RawOsError = i32; - /// The error type for I/O operations. /// /// See [`std::io::Error` documentation][std] for details. @@ -27,35 +20,85 @@ pub struct Error { repr: Repr, } -enum Repr { - Os(RawOsError), - Simple(ErrorKind), - SimpleMessage(&'static SimpleMessage), -} - -pub(crate) struct SimpleMessage { - kind: ErrorKind, - message: &'static str, -} - -impl SimpleMessage { - pub(crate) const fn new(kind: ErrorKind, message: &'static str) -> Self { - Self { kind, message } +impl fmt::Debug for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&self.repr, f) } } -/// Create and return an `io::Error` for a given `ErrorKind` and constant -/// message. This doesn't allocate. -macro_rules! const_io_error { +macro_rules! const_error { ($kind:expr, $message:expr $(,)?) => { - $crate::io::error::Error::from_static_message({ + $crate::io::Error::from_static_message({ const MESSAGE_DATA: $crate::io::error::SimpleMessage = - $crate::io::error::SimpleMessage::new($kind, $message); + $crate::io::error::SimpleMessage { kind: $kind, message: $message }; &MESSAGE_DATA }) }; } +#[allow(dead_code)] +impl Error { + pub(crate) const INVALID_UTF8: Self = + const_error!(ErrorKind::InvalidData, "stream did not contain valid UTF-8"); + + pub(crate) const READ_EXACT_EOF: Self = + const_error!(ErrorKind::UnexpectedEof, "failed to fill whole buffer"); + + pub(crate) const UNKNOWN_THREAD_COUNT: Self = const_error!( + ErrorKind::NotFound, + "The number of hardware threads is not known for the target platform", + ); + + pub(crate) const UNSUPPORTED_PLATFORM: Self = + const_error!(ErrorKind::Unsupported, "operation not supported on this platform"); + + pub(crate) const WRITE_ALL_EOF: Self = + const_error!(ErrorKind::WriteZero, "failed to write whole buffer"); + + pub(crate) const ZERO_TIMEOUT: Self = + const_error!(ErrorKind::InvalidInput, "cannot set a 0 duration timeout"); +} + +#[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] +impl From for Error { + /// Converts a [`alloc::ffi::NulError`] into a [`Error`]. + fn from(_: alloc::ffi::NulError) -> Error { + const_error!(ErrorKind::InvalidInput, "data provided contains a nul byte") + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] +impl From for Error { + /// Converts `TryReserveError` to an error with [`ErrorKind::OutOfMemory`]. + /// + /// `TryReserveError` won't be available as the error `source()`, + /// but this may change in the future. + fn from(_: alloc::collections::TryReserveError) -> Error { + // ErrorData::Custom allocates, which isn't great for handling OOM errors. + ErrorKind::OutOfMemory.into() + } +} + +enum Repr { + Os(RawOsError), + Simple(ErrorKind), + SimpleMessage(&'static SimpleMessage), +} + +/// The type of raw OS error codes returned by [`Error::raw_os_error`]. +/// +/// See [`std::io::RawOsError` documentation][std] for details. +/// +/// [std]: https://doc.rust-lang.org/nightly/std/io/type.RawOsError.html +pub type RawOsError = i32; + +pub(crate) struct SimpleMessage { + pub(crate) kind: ErrorKind, + pub(crate) message: &'static str, +} + /// A list specifying general categories of I/O error. /// /// See [`std::io::ErrorKind` documentation][std] for details. @@ -129,11 +172,11 @@ impl ErrorKind { Deadlock => "deadlock", DirectoryNotEmpty => "directory not empty", ExecutableFileBusy => "executable file busy", - FileTooLarge => "file too large", __FilesystemLoop => "filesystem loop or indirection limit (e.g. symlink loop)", + FileTooLarge => "file too large", HostUnreachable => "host unreachable", - Interrupted => "operation interrupted", __InProgress => "in progress", + Interrupted => "operation interrupted", InvalidData => "invalid data", __InvalidFilename => "invalid filename", InvalidInput => "invalid input parameter", @@ -189,31 +232,50 @@ impl Error { pub(crate) const fn from_static_message(msg: &'static SimpleMessage) -> Error { Self { repr: Repr::SimpleMessage(msg) } } + + // TODO: provide new,other when alloc feature is enabled? + + // TODO: last_os_error: Arm semihosting has sys_errno, but MIPS UHI doesn't. + /// Creates a new instance of an `Error` from a particular OS error code. #[inline] #[must_use] pub fn from_raw_os_error(os: RawOsError) -> Self { Self { repr: Repr::Os(os) } } + /// Returns the OS error that this error represents (if any). #[inline] #[must_use] pub fn raw_os_error(&self) -> Option { match self.repr { Repr::Os(code) => Some(code), + // Repr::Custom(..) | Repr::Simple(..) | Repr::SimpleMessage(..) => None, } } + /// Returns the corresponding [`ErrorKind`] for this error. #[inline] #[must_use] pub fn kind(&self) -> ErrorKind { match self.repr { Repr::Os(code) => sys::decode_error_kind(code), + // Repr::Custom(ref c) => c.kind, Repr::Simple(kind) => kind, Repr::SimpleMessage(msg) => msg.kind, } } + + #[inline] + pub(crate) fn is_interrupted(&self) -> bool { + match self.repr { + Repr::Os(code) => sys::is_interrupted(code), + // Repr::Custom(ref c) => c.kind == ErrorKind::Interrupted, + Repr::Simple(kind) => kind == ErrorKind::Interrupted, + Repr::SimpleMessage(m) => m.kind == ErrorKind::Interrupted, + } + } } impl fmt::Debug for Repr { @@ -226,6 +288,7 @@ impl fmt::Debug for Repr { // TODO // .field("message", &sys::os::error_string(code)) .finish(), + // Self::Custom(c) => fmt::Debug::fmt(&c, fmt), Self::Simple(kind) => f.debug_tuple("Kind").field(&kind).finish(), Self::SimpleMessage(msg) => f .debug_struct("Error") @@ -236,12 +299,6 @@ impl fmt::Debug for Repr { } } -impl fmt::Debug for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(&self.repr, f) - } -} - impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.repr { @@ -252,6 +309,7 @@ impl fmt::Display for Error { let detail = sys::decode_error_kind(code); write!(f, "{detail} (os error {code})") } + // Repr::Custom(ref c) => c.error.fmt(fmt), Repr::Simple(kind) => f.write_str(kind.as_str()), Repr::SimpleMessage(msg) => msg.message.fmt(f), } diff --git a/src/io/impls.rs b/src/io/impls.rs new file mode 100644 index 0000000..ff72423 --- /dev/null +++ b/src/io/impls.rs @@ -0,0 +1,495 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use core::{cmp, fmt, mem}; + +use crate::io::{self, Read, Seek, SeekFrom, Write}; + +// ----------------------------------------------------------------------------- +// Forwarding implementations + +impl Read for &mut R { + #[inline] + fn read(&mut self, buf: &mut [u8]) -> io::Result { + (**self).read(buf) + } + // #[inline] + // fn read_buf(&mut self, cursor: BorrowedCursor<'_>) -> io::Result<()> { + // (**self).read_buf(cursor) + // } + // #[inline] + // fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { + // (**self).read_vectored(bufs) + // } + // #[inline] + // fn is_read_vectored(&self) -> bool { + // (**self).is_read_vectored() + // } + // #[inline] + // fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { + // (**self).read_to_end(buf) + // } + // #[inline] + // fn read_to_string(&mut self, buf: &mut String) -> io::Result { + // (**self).read_to_string(buf) + // } + #[inline] + fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { + (**self).read_exact(buf) + } + // #[inline] + // fn read_buf_exact(&mut self, cursor: BorrowedCursor<'_>) -> io::Result<()> { + // (**self).read_buf_exact(cursor) + // } +} +impl Write for &mut W { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + (**self).write(buf) + } + // #[inline] + // fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + // (**self).write_vectored(bufs) + // } + // #[inline] + // fn is_write_vectored(&self) -> bool { + // (**self).is_write_vectored() + // } + #[inline] + fn flush(&mut self) -> io::Result<()> { + (**self).flush() + } + #[inline] + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + (**self).write_all(buf) + } + // #[inline] + // fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { + // (**self).write_all_vectored(bufs) + // } + #[inline] + fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> io::Result<()> { + (**self).write_fmt(fmt) + } +} +impl Seek for &mut S { + #[inline] + fn seek(&mut self, pos: SeekFrom) -> io::Result { + (**self).seek(pos) + } + #[inline] + fn rewind(&mut self) -> io::Result<()> { + (**self).rewind() + } + // #[inline] + // fn stream_len(&mut self) -> io::Result { + // (**self).stream_len() + // } + // #[inline] + // fn stream_position(&mut self) -> io::Result { + // (**self).stream_position() + // } + // #[inline] + // fn seek_relative(&mut self, offset: i64) -> io::Result<()> { + // (**self).seek_relative(offset) + // } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] +impl Read for alloc::boxed::Box { + #[inline] + fn read(&mut self, buf: &mut [u8]) -> io::Result { + (**self).read(buf) + } + // #[inline] + // fn read_buf(&mut self, cursor: BorrowedCursor<'_>) -> io::Result<()> { + // (**self).read_buf(cursor) + // } + // #[inline] + // fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { + // (**self).read_vectored(bufs) + // } + // #[inline] + // fn is_read_vectored(&self) -> bool { + // (**self).is_read_vectored() + // } + // #[inline] + // fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { + // (**self).read_to_end(buf) + // } + // #[inline] + // fn read_to_string(&mut self, buf: &mut String) -> io::Result { + // (**self).read_to_string(buf) + // } + #[inline] + fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { + (**self).read_exact(buf) + } + // #[inline] + // fn read_buf_exact(&mut self, cursor: BorrowedCursor<'_>) -> io::Result<()> { + // (**self).read_buf_exact(cursor) + // } +} +#[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] +impl Write for alloc::boxed::Box { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + (**self).write(buf) + } + // #[inline] + // fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + // (**self).write_vectored(bufs) + // } + // #[inline] + // fn is_write_vectored(&self) -> bool { + // (**self).is_write_vectored() + // } + #[inline] + fn flush(&mut self) -> io::Result<()> { + (**self).flush() + } + #[inline] + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + (**self).write_all(buf) + } + // #[inline] + // fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { + // (**self).write_all_vectored(bufs) + // } + #[inline] + fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> io::Result<()> { + (**self).write_fmt(fmt) + } +} +#[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] +impl Seek for alloc::boxed::Box { + #[inline] + fn seek(&mut self, pos: SeekFrom) -> io::Result { + (**self).seek(pos) + } + #[inline] + fn rewind(&mut self) -> io::Result<()> { + (**self).rewind() + } + // #[inline] + // fn stream_len(&mut self) -> io::Result { + // (**self).stream_len() + // } + // #[inline] + // fn stream_position(&mut self) -> io::Result { + // (**self).stream_position() + // } + // #[inline] + // fn seek_relative(&mut self, offset: i64) -> io::Result<()> { + // (**self).seek_relative(offset) + // } +} + +// ----------------------------------------------------------------------------- +// In-memory buffer implementations + +/// Read is implemented for `&[u8]` by copying from the slice. +/// +/// Note that reading updates the slice to point to the yet unread part. +/// The slice will be empty when EOF is reached. +impl Read for &[u8] { + #[inline] + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let amt = cmp::min(buf.len(), self.len()); + let (a, b) = self.split_at(amt); + + // First check if the amount of bytes we want to read is small: + // `copy_from_slice` will generally expand to a call to `memcpy`, and + // for a single byte the overhead is significant. + if amt == 1 { + buf[0] = a[0]; + } else { + buf[..amt].copy_from_slice(a); + } + + *self = b; + Ok(amt) + } + // #[inline] + // fn read_buf(&mut self, mut cursor: BorrowedCursor<'_>) -> io::Result<()> { + // let amt = cmp::min(cursor.capacity(), self.len()); + // let (a, b) = self.split_at(amt); + // + // cursor.append(a); + // + // *self = b; + // Ok(()) + // } + // #[inline] + // fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { + // let mut n_read = 0; + // for buf in bufs { + // n_read += self.read(buf)?; + // if self.is_empty() { + // break; + // } + // } + // + // Ok(n_read) + // } + // #[inline] + // fn is_read_vectored(&self) -> bool { + // true + // } + #[inline] + fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { + if buf.len() > self.len() { + // `read_exact` makes no promise about the content of `buf` if it + // fails so don't bother about that. + *self = &self[self.len()..]; + return Err(io::Error::READ_EXACT_EOF); + } + let (a, b) = self.split_at(buf.len()); + + // First check if the amount of bytes we want to read is small: + // `copy_from_slice` will generally expand to a call to `memcpy`, and + // for a single byte the overhead is significant. + if buf.len() == 1 { + buf[0] = a[0]; + } else { + buf.copy_from_slice(a); + } + + *self = b; + Ok(()) + } + // #[inline] + // fn read_buf_exact(&mut self, mut cursor: BorrowedCursor<'_>) -> io::Result<()> { + // if cursor.capacity() > self.len() { + // // Append everything we can to the cursor. + // cursor.append(*self); + // *self = &self[self.len()..]; + // return Err(io::Error::READ_EXACT_EOF); + // } + // let (a, b) = self.split_at(cursor.capacity()); + // + // cursor.append(a); + // + // *self = b; + // Ok(()) + // } + // #[inline] + // fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { + // let len = self.len(); + // buf.try_reserve(len)?; + // buf.extend_from_slice(*self); + // *self = &self[len..]; + // Ok(len) + // } + // #[inline] + // fn read_to_string(&mut self, buf: &mut String) -> io::Result { + // let content = str::from_utf8(self).map_err(|_| io::Error::INVALID_UTF8)?; + // let len = self.len(); + // buf.try_reserve(len)?; + // buf.push_str(content); + // *self = &self[len..]; + // Ok(len) + // } +} + +/// Write is implemented for `&mut [u8]` by copying into the slice, overwriting +/// its data. +/// +/// Note that writing updates the slice to point to the yet unwritten part. +/// The slice will be empty when it has been completely overwritten. +/// +/// If the number of bytes to be written exceeds the size of the slice, write operations will +/// return short writes: ultimately, `Ok(0)`; in this situation, `write_all` returns an error of +/// kind `ErrorKind::WriteZero`. +impl Write for &mut [u8] { + #[inline] + fn write(&mut self, data: &[u8]) -> io::Result { + let amt = cmp::min(data.len(), self.len()); + let (a, b) = mem::take(self).split_at_mut(amt); + a.copy_from_slice(&data[..amt]); + *self = b; + Ok(amt) + } + // #[inline] + // fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + // let mut n_written = 0; + // for buf in bufs { + // n_written += self.write(buf)?; + // if self.is_empty() { + // break; + // } + // } + // + // Ok(n_written) + // } + // #[inline] + // fn is_write_vectored(&self) -> bool { + // true + // } + #[inline] + fn write_all(&mut self, data: &[u8]) -> io::Result<()> { + if self.write(data)? == data.len() { + Ok(()) + } else { + Err(io::Error::WRITE_ALL_EOF) + } + } + #[inline] + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +/// Write is implemented for `Vec` by appending to the vector. +/// The vector will grow as needed. +#[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] +impl Write for alloc::vec::Vec { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + self.extend_from_slice(buf); + Ok(buf.len()) + } + // #[inline] + // fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + // let len = bufs.iter().map(|b| b.len()).sum(); + // self.reserve(len); + // for buf in bufs { + // self.extend_from_slice(buf); + // } + // Ok(len) + // } + // #[inline] + // fn is_write_vectored(&self) -> bool { + // true + // } + #[inline] + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + self.extend_from_slice(buf); + Ok(()) + } + #[inline] + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +/// Read is implemented for `VecDeque` by consuming bytes from the front of the `VecDeque`. +#[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] +impl Read for alloc::collections::VecDeque { + /// Fill `buf` with the contents of the "front" slice as returned by + /// [`as_slices`][`alloc::collections::VecDeque::as_slices`]. If the contained byte slices of the `VecDeque` are + /// discontiguous, multiple calls to `read` will be needed to read the entire content. + #[inline] + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let (ref mut front, _) = self.as_slices(); + let n = Read::read(front, buf)?; + self.drain(..n); + Ok(n) + } + fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { + let (front, back) = self.as_slices(); + + // Use only the front buffer if it is big enough to fill `buf`, else use + // the back buffer too. + match buf.split_at_mut_checked(front.len()) { + None => buf.copy_from_slice(&front[..buf.len()]), + Some((buf_front, buf_back)) => match back.split_at_checked(buf_back.len()) { + Some((back, _)) => { + buf_front.copy_from_slice(front); + buf_back.copy_from_slice(back); + } + None => { + self.clear(); + return Err(io::Error::READ_EXACT_EOF); + } + }, + } + + self.drain(..buf.len()); + Ok(()) + } + // #[inline] + // fn read_buf(&mut self, cursor: BorrowedCursor<'_>) -> io::Result<()> { + // let (ref mut front, _) = self.as_slices(); + // let n = cmp::min(cursor.capacity(), front.len()); + // Read::read_buf(front, cursor)?; + // self.drain(..n); + // Ok(()) + // } + // fn read_buf_exact(&mut self, mut cursor: BorrowedCursor<'_>) -> io::Result<()> { + // let len = cursor.capacity(); + // let (front, back) = self.as_slices(); + // + // match front.split_at_checked(cursor.capacity()) { + // Some((front, _)) => cursor.append(front), + // None => { + // cursor.append(front); + // match back.split_at_checked(cursor.capacity()) { + // Some((back, _)) => cursor.append(back), + // None => { + // cursor.append(back); + // self.clear(); + // return Err(io::Error::READ_EXACT_EOF); + // } + // } + // } + // } + // + // self.drain(..len); + // Ok(()) + // } + // #[inline] + // fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { + // // The total len is known upfront so we can reserve it in a single call. + // let len = self.len(); + // buf.try_reserve(len)?; + // + // let (front, back) = self.as_slices(); + // buf.extend_from_slice(front); + // buf.extend_from_slice(back); + // self.clear(); + // Ok(len) + // } + // #[inline] + // fn read_to_string(&mut self, buf: &mut String) -> io::Result { + // // SAFETY: We only append to the buffer + // unsafe { io::append_to_string(buf, |buf| self.read_to_end(buf)) } + // } +} + +/// Write is implemented for `VecDeque` by appending to the `VecDeque`, growing it as needed. +#[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] +impl Write for alloc::collections::VecDeque { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + self.extend(buf); + Ok(buf.len()) + } + // #[inline] + // fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + // let len = bufs.iter().map(|b| b.len()).sum(); + // self.reserve(len); + // for buf in bufs { + // self.extend(&**buf); + // } + // Ok(len) + // } + // #[inline] + // fn is_write_vectored(&self) -> bool { + // true + // } + #[inline] + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + self.extend(buf); + Ok(()) + } + #[inline] + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} diff --git a/src/io/mod.rs b/src/io/mod.rs index 35cb20a..5cb2587 100644 --- a/src/io/mod.rs +++ b/src/io/mod.rs @@ -7,10 +7,16 @@ //! //! [`std::io`]: https://doc.rust-lang.org/std/io/index.html +// Based on nightly-2025-02-19's std::io module. + +// TODO: io utilities e.g., Cursor? + pub use self::error::{Error, ErrorKind, RawOsError, Result}; #[macro_use] mod error; +mod impls; + #[cfg(feature = "stdio")] pub use self::stdio::{stderr, stdin, stdout, IsTerminal, Stderr, Stdin, Stdout}; #[cfg(feature = "stdio")] @@ -26,22 +32,21 @@ const _: fn() = || { assert_dyn_compatibility::(); }; -pub(crate) fn default_read_exact(this: &mut R, mut buf: &mut [u8]) -> Result<()> { +pub(crate) fn default_read_exact(this: &mut R, mut buf: &mut [u8]) -> Result<()> { while !buf.is_empty() { match this.read(buf) { Ok(0) => break, Ok(n) => { - let tmp = buf; - buf = &mut tmp[n..]; + buf = &mut buf[n..]; } - Err(ref e) if e.kind() == ErrorKind::Interrupted => {} + Err(ref e) if e.is_interrupted() => {} Err(e) => return Err(e), } } if buf.is_empty() { Ok(()) } else { - Err(const_io_error!(ErrorKind::UnexpectedEof, "failed to fill whole buffer")) + Err(Error::READ_EXACT_EOF) } } @@ -124,14 +129,9 @@ pub trait Write { fn write_all(&mut self, mut buf: &[u8]) -> Result<()> { while !buf.is_empty() { match self.write(buf) { - Ok(0) => { - return Err(const_io_error!( - ErrorKind::WriteZero, - "failed to write whole buffer", - )); - } + Ok(0) => return Err(Error::WRITE_ALL_EOF), Ok(n) => buf = &buf[n..], - Err(ref e) if e.kind() == ErrorKind::Interrupted => {} + Err(ref e) if e.is_interrupted() => {} Err(e) => return Err(e), } } @@ -172,10 +172,11 @@ pub trait Write { if output.error.is_err() { output.error } else { - Err(const_io_error!( - ErrorKind::Other, /* Uncategorized */ - "formatter error" - )) + // This shouldn't happen: the underlying stream did not error, but somehow + // the formatter still errored? + panic!( + "a formatting trait implementation returned an error when the underlying stream did not" + ); } } } @@ -204,15 +205,28 @@ pub trait Seek { self.seek(SeekFrom::Start(0))?; Ok(()) } + + // /// Returns the current seek position from the start of the stream. + // /// + // /// This is equivalent to `self.seek(SeekFrom::Current(0))`. + // fn stream_position(&mut self) -> Result { + // self.seek(SeekFrom::Current(0)) + // } + + // /// Seeks relative to the current position. + // /// + // /// This is equivalent to `self.seek(SeekFrom::Current(offset))` but + // /// doesn't return the new position which can allow some implementations + // /// such as [`BufReader`] to perform more efficient seeks. + // fn seek_relative(&mut self, offset: i64) -> Result<()> { + // self.seek(SeekFrom::Current(offset))?; + // Ok(()) + // } } /// Enumeration of possible methods to seek within an I/O object. /// /// It is used by the [`Seek`] trait. -/// -/// See [`std::io::SeekFrom` documentation][std] for details. -/// -/// [std]: https://doc.rust-lang.org/std/io/enum.SeekFrom.html #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[non_exhaustive] pub enum SeekFrom { diff --git a/src/io/stdio.rs b/src/io/stdio.rs index a5c3baa..b3dcce4 100644 --- a/src/io/stdio.rs +++ b/src/io/stdio.rs @@ -60,6 +60,7 @@ pub struct Stdout(sys::StdioFd); pub struct Stderr(sys::StdioFd); impl_as_fd!(Stdin, Stdout, Stderr); +// TODO: std provides io trait implementations on &Std{in,out,err} as they uses locks. impl io::Read for Stdin { fn read(&mut self, buf: &mut [u8]) -> io::Result { sys::read(self.as_fd(), buf) diff --git a/src/sys/errno.rs b/src/sys/errno.rs index 3203660..a05122d 100644 --- a/src/sys/errno.rs +++ b/src/sys/errno.rs @@ -5,6 +5,11 @@ use crate::{ sys::arch::errno, }; +#[inline] +pub(crate) fn is_interrupted(errno: i32) -> bool { + errno == errno::EINTR +} + // From https://github.com/rust-lang/rust/blob/1.84.0/library/std/src/sys/pal/unix/mod.rs#L245. pub(crate) fn decode_error_kind(errno: RawOsError) -> io::ErrorKind { #[allow(clippy::enum_glob_use)] diff --git a/src/sys/mod.rs b/src/sys/mod.rs index cf4c24a..8e880a1 100644 --- a/src/sys/mod.rs +++ b/src/sys/mod.rs @@ -73,5 +73,5 @@ pub(crate) use self::arch::{is_terminal, stderr, stdin, stdout, StdioFd}; pub(crate) use self::arch::{read, write}; pub(crate) use self::{ arch::{close, exit, should_close}, - errno::decode_error_kind, + errno::{decode_error_kind, is_interrupted}, };