feat: add display UI with splash screen, dashboard, and RSSI sparkline
Implement full SSD1306 OLED display support: - Splash screen on boot (1.5s) with project name, version, board - Dashboard showing radio state, config, counters, RSSI/SNR - RSSI history sparkline (64-sample ring buffer, 2px-wide bars) - DisplayOn/DisplayOff host commands routed via dedicated channel - Pure render functions against generic DrawTarget (no hardware coupling) - Display on/off control bypasses radio task entirely Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
17624 files changed
+4564928
-38
No files matched your search
Vendored
+327
@@ -0,0 +1,327 @@
|
||||
// SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
|
||||
use core::fmt;
|
||||
|
||||
use crate::sys;
|
||||
|
||||
/// A specialized [`Result`] type for I/O operations.
|
||||
///
|
||||
/// See [`std::io::Result` documentation][std] for details.
|
||||
///
|
||||
/// [std]: https://doc.rust-lang.org/std/io/type.Result.html
|
||||
pub type Result<T> = core::result::Result<T, Error>;
|
||||
|
||||
/// The error type for I/O operations.
|
||||
///
|
||||
/// See [`std::io::Error` documentation][std] for details.
|
||||
///
|
||||
/// [std]: https://doc.rust-lang.org/std/io/struct.Error.html
|
||||
pub struct Error {
|
||||
repr: Repr,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Debug::fmt(&self.repr, f)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! const_error {
|
||||
($kind:expr, $message:expr $(,)?) => {
|
||||
$crate::io::Error::from_static_message({
|
||||
const MESSAGE_DATA: $crate::io::error::SimpleMessage =
|
||||
$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 = "fs")]
|
||||
pub(crate) const EINVAL: Self = Self { repr: Repr::Os(sys::EINVAL) };
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl From<alloc::ffi::NulError> 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")]
|
||||
impl From<alloc::collections::TryReserveError> 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.
|
||||
///
|
||||
/// [std]: https://doc.rust-lang.org/std/io/enum.ErrorKind.html
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[non_exhaustive]
|
||||
pub enum ErrorKind {
|
||||
NotFound,
|
||||
PermissionDenied,
|
||||
ConnectionRefused,
|
||||
ConnectionReset,
|
||||
HostUnreachable,
|
||||
NetworkUnreachable,
|
||||
ConnectionAborted,
|
||||
NotConnected,
|
||||
AddrInUse,
|
||||
AddrNotAvailable,
|
||||
NetworkDown,
|
||||
BrokenPipe,
|
||||
AlreadyExists,
|
||||
WouldBlock,
|
||||
NotADirectory,
|
||||
IsADirectory,
|
||||
DirectoryNotEmpty,
|
||||
ReadOnlyFilesystem,
|
||||
#[doc(hidden)]
|
||||
__FilesystemLoop, // unstable https://github.com/rust-lang/rust/issues/130188
|
||||
StaleNetworkFileHandle,
|
||||
InvalidInput,
|
||||
InvalidData,
|
||||
TimedOut,
|
||||
WriteZero,
|
||||
StorageFull,
|
||||
NotSeekable,
|
||||
QuotaExceeded,
|
||||
FileTooLarge,
|
||||
ResourceBusy,
|
||||
ExecutableFileBusy,
|
||||
Deadlock,
|
||||
CrossesDevices,
|
||||
TooManyLinks,
|
||||
InvalidFilename,
|
||||
ArgumentListTooLong,
|
||||
Interrupted,
|
||||
Unsupported,
|
||||
UnexpectedEof,
|
||||
OutOfMemory,
|
||||
#[doc(hidden)]
|
||||
__InProgress, // unstable https://github.com/rust-lang/rust/issues/130840
|
||||
Other,
|
||||
// Uncategorized, // unstable, private api
|
||||
}
|
||||
|
||||
impl ErrorKind {
|
||||
pub(crate) fn as_str(self) -> &'static str {
|
||||
#[allow(clippy::enum_glob_use)]
|
||||
use ErrorKind::*;
|
||||
match self {
|
||||
AddrInUse => "address in use",
|
||||
AddrNotAvailable => "address not available",
|
||||
AlreadyExists => "entity already exists",
|
||||
ArgumentListTooLong => "argument list too long",
|
||||
BrokenPipe => "broken pipe",
|
||||
ConnectionAborted => "connection aborted",
|
||||
ConnectionRefused => "connection refused",
|
||||
ConnectionReset => "connection reset",
|
||||
CrossesDevices => "cross-device link or rename",
|
||||
Deadlock => "deadlock",
|
||||
DirectoryNotEmpty => "directory not empty",
|
||||
ExecutableFileBusy => "executable file busy",
|
||||
__FilesystemLoop => "filesystem loop or indirection limit (e.g. symlink loop)",
|
||||
FileTooLarge => "file too large",
|
||||
HostUnreachable => "host unreachable",
|
||||
__InProgress => "in progress",
|
||||
Interrupted => "operation interrupted",
|
||||
InvalidData => "invalid data",
|
||||
InvalidFilename => "invalid filename",
|
||||
InvalidInput => "invalid input parameter",
|
||||
IsADirectory => "is a directory",
|
||||
NetworkDown => "network down",
|
||||
NetworkUnreachable => "network unreachable",
|
||||
NotADirectory => "not a directory",
|
||||
NotConnected => "not connected",
|
||||
NotFound => "entity not found",
|
||||
NotSeekable => "seek on unseekable file",
|
||||
Other => "other error",
|
||||
OutOfMemory => "out of memory",
|
||||
PermissionDenied => "permission denied",
|
||||
QuotaExceeded => "quota exceeded",
|
||||
ReadOnlyFilesystem => "read-only filesystem or storage medium",
|
||||
ResourceBusy => "resource busy",
|
||||
StaleNetworkFileHandle => "stale network file handle",
|
||||
StorageFull => "no storage space",
|
||||
TimedOut => "timed out",
|
||||
TooManyLinks => "too many links",
|
||||
// Uncategorized => "uncategorized error",
|
||||
UnexpectedEof => "unexpected end of file",
|
||||
Unsupported => "unsupported",
|
||||
WouldBlock => "operation would block",
|
||||
WriteZero => "write zero",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ErrorKind {
|
||||
/// Shows a human-readable description of the `ErrorKind`.
|
||||
///
|
||||
/// This is similar to `impl Display for Error`, but doesn't require first converting to Error.
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
/// Intended for use for errors not exposed to the user, where allocating onto
|
||||
/// the heap (for normal construction via Error::new) is too costly.
|
||||
impl From<ErrorKind> for Error {
|
||||
/// Converts an [`ErrorKind`] into an [`Error`].
|
||||
///
|
||||
/// This conversion creates a new error with a simple representation of error kind.
|
||||
#[inline]
|
||||
fn from(kind: ErrorKind) -> Self {
|
||||
Self { repr: Repr::Simple(kind) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Error {
|
||||
#[inline]
|
||||
pub(crate) const fn from_static_message(msg: &'static SimpleMessage) -> Error {
|
||||
Self { repr: Repr::SimpleMessage(msg) }
|
||||
}
|
||||
|
||||
// TODO(io,alloc): provide new,other when alloc feature is enabled?
|
||||
|
||||
// TODO(io): 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<RawOsError> {
|
||||
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 {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Os(code) => f
|
||||
.debug_struct("Os")
|
||||
.field("code", &code)
|
||||
.field("kind", &sys::decode_error_kind(*code))
|
||||
// TODO(io)
|
||||
// .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")
|
||||
.field("kind", &msg.kind)
|
||||
.field("message", &msg.message)
|
||||
.finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.repr {
|
||||
Repr::Os(code) => {
|
||||
// TODO(io)
|
||||
// let detail = sys::os::error_string(code);
|
||||
// write!(f, "{detail} (os error {code})")
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(semihosting_no_error_in_core))]
|
||||
impl core::error::Error for Error {
|
||||
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
|
||||
match self.repr {
|
||||
Repr::Os(..) | Repr::Simple(..) | Repr::SimpleMessage(..) => None,
|
||||
// Repr::Custom(c) => c.error.source(),
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+499
@@ -0,0 +1,499 @@
|
||||
// SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
|
||||
use core::{cmp, fmt, mem};
|
||||
|
||||
use crate::io::{self, Read, Seek, SeekFrom, Write};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Forwarding implementations
|
||||
|
||||
impl<R: ?Sized + Read> Read for &mut R {
|
||||
#[inline]
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
(**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<usize> {
|
||||
// (**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<u8>) -> io::Result<usize> {
|
||||
// (**self).read_to_end(buf)
|
||||
// }
|
||||
// #[inline]
|
||||
// fn read_to_string(&mut self, buf: &mut String) -> io::Result<usize> {
|
||||
// (**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<W: ?Sized + Write> Write for &mut W {
|
||||
#[inline]
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
(**self).write(buf)
|
||||
}
|
||||
// #[inline]
|
||||
// fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
|
||||
// (**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<S: ?Sized + Seek> Seek for &mut S {
|
||||
#[inline]
|
||||
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
|
||||
(**self).seek(pos)
|
||||
}
|
||||
#[inline]
|
||||
fn rewind(&mut self) -> io::Result<()> {
|
||||
(**self).rewind()
|
||||
}
|
||||
// #[inline]
|
||||
// fn stream_len(&mut self) -> io::Result<u64> {
|
||||
// (**self).stream_len()
|
||||
// }
|
||||
// #[inline]
|
||||
// fn stream_position(&mut self) -> io::Result<u64> {
|
||||
// (**self).stream_position()
|
||||
// }
|
||||
// #[inline]
|
||||
// fn seek_relative(&mut self, offset: i64) -> io::Result<()> {
|
||||
// (**self).seek_relative(offset)
|
||||
// }
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<R: ?Sized + Read> Read for alloc::boxed::Box<R> {
|
||||
#[inline]
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
(**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<usize> {
|
||||
// (**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<u8>) -> io::Result<usize> {
|
||||
// (**self).read_to_end(buf)
|
||||
// }
|
||||
// #[inline]
|
||||
// fn read_to_string(&mut self, buf: &mut String) -> io::Result<usize> {
|
||||
// (**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")]
|
||||
impl<W: ?Sized + Write> Write for alloc::boxed::Box<W> {
|
||||
#[inline]
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
(**self).write(buf)
|
||||
}
|
||||
// #[inline]
|
||||
// fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
|
||||
// (**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")]
|
||||
impl<S: ?Sized + Seek> Seek for alloc::boxed::Box<S> {
|
||||
#[inline]
|
||||
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
|
||||
(**self).seek(pos)
|
||||
}
|
||||
#[inline]
|
||||
fn rewind(&mut self) -> io::Result<()> {
|
||||
(**self).rewind()
|
||||
}
|
||||
// #[inline]
|
||||
// fn stream_len(&mut self) -> io::Result<u64> {
|
||||
// (**self).stream_len()
|
||||
// }
|
||||
// #[inline]
|
||||
// fn stream_position(&mut self) -> io::Result<u64> {
|
||||
// (**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<usize> {
|
||||
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<usize> {
|
||||
// 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<u8>) -> io::Result<usize> {
|
||||
// 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<usize> {
|
||||
// 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<usize> {
|
||||
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<usize> {
|
||||
// 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<u8>` by appending to the vector.
|
||||
/// The vector will grow as needed.
|
||||
#[cfg(feature = "alloc")]
|
||||
impl Write for alloc::vec::Vec<u8> {
|
||||
#[inline]
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.extend_from_slice(buf);
|
||||
Ok(buf.len())
|
||||
}
|
||||
// #[inline]
|
||||
// fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
|
||||
// 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<u8>` by consuming bytes from the front of the `VecDeque`.
|
||||
#[cfg(feature = "alloc")]
|
||||
impl Read for alloc::collections::VecDeque<u8> {
|
||||
/// 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<usize> {
|
||||
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 split_at_mut_checked(buf, front.len()) {
|
||||
None => buf.copy_from_slice(&front[..buf.len()]),
|
||||
Some((buf_front, buf_back)) => match split_at_checked(back, 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<u8>) -> io::Result<usize> {
|
||||
// // 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<usize> {
|
||||
// // SAFETY: We only append to the buffer
|
||||
// unsafe { io::append_to_string(buf, |buf| self.read_to_end(buf)) }
|
||||
// }
|
||||
}
|
||||
// <[_]>::split_at_checked requires Rust 1.80.
|
||||
#[cfg(feature = "alloc")]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
fn split_at_checked<T>(this: &[T], mid: usize) -> Option<(&[T], &[T])> {
|
||||
if mid <= this.len() { Some(this.split_at(mid)) } else { None }
|
||||
}
|
||||
// <[_]>::split_at_mut_checked requires Rust 1.80.
|
||||
#[cfg(feature = "alloc")]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
fn split_at_mut_checked<T>(this: &mut [T], mid: usize) -> Option<(&mut [T], &mut [T])> {
|
||||
if mid <= this.len() { Some(this.split_at_mut(mid)) } else { None }
|
||||
}
|
||||
|
||||
/// Write is implemented for `VecDeque<u8>` by appending to the `VecDeque`, growing it as needed.
|
||||
#[cfg(feature = "alloc")]
|
||||
impl Write for alloc::collections::VecDeque<u8> {
|
||||
#[inline]
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.extend(buf);
|
||||
Ok(buf.len())
|
||||
}
|
||||
// #[inline]
|
||||
// fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
|
||||
// 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(())
|
||||
}
|
||||
}
|
||||
Vendored
+242
@@ -0,0 +1,242 @@
|
||||
// SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
|
||||
//! Traits, helpers, and type definitions for core I/O functionality.
|
||||
//!
|
||||
//! This module is a subset of the [`std::io`] module except that the stdio functions returns
|
||||
//! the `Result`.
|
||||
//!
|
||||
//! [`std::io`]: https://doc.rust-lang.org/std/io/index.html
|
||||
|
||||
// Based on nightly-2025-02-19's std::io module.
|
||||
|
||||
// TODO(io): 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::{IsTerminal, Stderr, Stdin, Stdout, stderr, stdin, stdout};
|
||||
#[cfg(feature = "stdio")]
|
||||
mod stdio;
|
||||
|
||||
use core::fmt;
|
||||
|
||||
const _: fn() = || {
|
||||
fn assert_dyn_compatibility<T: ?Sized>() {}
|
||||
assert_dyn_compatibility::<dyn Read>();
|
||||
assert_dyn_compatibility::<dyn Write>();
|
||||
assert_dyn_compatibility::<dyn Seek>();
|
||||
};
|
||||
|
||||
pub(crate) fn default_read_exact<R: ?Sized + Read>(this: &mut R, mut buf: &mut [u8]) -> Result<()> {
|
||||
while !buf.is_empty() {
|
||||
match this.read(buf) {
|
||||
Ok(0) => break,
|
||||
Ok(n) => {
|
||||
buf = &mut buf[n..];
|
||||
}
|
||||
Err(ref e) if e.is_interrupted() => {}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
if buf.is_empty() { Ok(()) } else { Err(Error::READ_EXACT_EOF) }
|
||||
}
|
||||
|
||||
/// The `no_std` subset of `std::io::Read`.
|
||||
///
|
||||
/// Unless explicitly stated otherwise, API contracts adhere to `std::io::Read`.
|
||||
///
|
||||
/// See [`std::io::Read` documentation][std] for details.
|
||||
///
|
||||
/// [std]: https://doc.rust-lang.org/std/io/trait.Read.html
|
||||
pub trait Read {
|
||||
/// Pull some bytes from this source into the specified buffer, returning
|
||||
/// how many bytes were read.
|
||||
///
|
||||
/// See [`std::io::Read::read` documentation][std] for details.
|
||||
///
|
||||
/// [std]: https://doc.rust-lang.org/std/io/trait.Read.html#tymethod.read
|
||||
fn read(&mut self, buf: &mut [u8]) -> Result<usize>;
|
||||
|
||||
// TODO(io,alloc)
|
||||
// /// Read all bytes until EOF in this source, placing them into `buf`.
|
||||
// ///
|
||||
// /// See [`std::io::Read::read_to_end` documentation][std] for details.
|
||||
// ///
|
||||
// /// [std]: https://doc.rust-lang.org/std/io/trait.Read.html#method.read_to_end
|
||||
// #[cfg(feature = "alloc")]
|
||||
// fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize> {
|
||||
// default_read_to_end(self, buf)
|
||||
// }
|
||||
//
|
||||
// /// Read all bytes until EOF in this source, appending them to `buf`.
|
||||
// ///
|
||||
// /// See [`std::io::Read::read_to_string` documentation][std] for details.
|
||||
// ///
|
||||
// /// [std]: https://doc.rust-lang.org/std/io/trait.Read.html#method.read_to_string
|
||||
// #[cfg(feature = "alloc")]
|
||||
// fn read_to_string(&mut self, buf: &mut String) -> Result<usize> {
|
||||
// default_read_to_string(self, buf)
|
||||
// }
|
||||
|
||||
/// Read the exact number of bytes required to fill `buf`.
|
||||
///
|
||||
/// See [`std::io::Read::read_exact` documentation][std] for details.
|
||||
///
|
||||
/// [std]: https://doc.rust-lang.org/std/io/trait.Read.html#method.read_exact
|
||||
fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> {
|
||||
default_read_exact(self, buf)
|
||||
}
|
||||
}
|
||||
|
||||
/// The `no_std` subset of `std::io::Write`.
|
||||
///
|
||||
/// Unless explicitly stated otherwise, API contracts adhere to `std::io::Write`.
|
||||
///
|
||||
/// See [`std::io::Write` documentation][std] for details.
|
||||
///
|
||||
/// [std]: https://doc.rust-lang.org/std/io/trait.Write.html
|
||||
pub trait Write {
|
||||
/// Write a buffer into this writer, returning how many bytes were written.
|
||||
///
|
||||
/// See [`std::io::Write::write` documentation][std] for details.
|
||||
///
|
||||
/// [std]: https://doc.rust-lang.org/std/io/trait.Write.html#tymethod.write
|
||||
fn write(&mut self, buf: &[u8]) -> Result<usize>;
|
||||
|
||||
/// Flush this output stream, ensuring that all intermediately buffered
|
||||
/// contents reach their destination.
|
||||
///
|
||||
/// See [`std::io::Write::flush` documentation][std] for details.
|
||||
///
|
||||
/// [std]: https://doc.rust-lang.org/std/io/trait.Write.html#tymethod.flush
|
||||
fn flush(&mut self) -> Result<()>;
|
||||
|
||||
/// Attempts to write an entire buffer into this writer.
|
||||
///
|
||||
/// See [`std::io::Write::write_all` documentation][std] for details.
|
||||
///
|
||||
/// [std]: https://doc.rust-lang.org/std/io/trait.Write.html#method.write_all
|
||||
fn write_all(&mut self, mut buf: &[u8]) -> Result<()> {
|
||||
while !buf.is_empty() {
|
||||
match self.write(buf) {
|
||||
Ok(0) => return Err(Error::WRITE_ALL_EOF),
|
||||
Ok(n) => buf = &buf[n..],
|
||||
Err(ref e) if e.is_interrupted() => {}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Writes a formatted string into this writer, returning any error
|
||||
/// encountered.
|
||||
///
|
||||
/// See [`std::io::Write::write_fmt` documentation][std] for details.
|
||||
///
|
||||
/// [std]: https://doc.rust-lang.org/std/io/trait.Write.html#method.write_fmt
|
||||
fn write_fmt(&mut self, f: fmt::Arguments<'_>) -> Result<()> {
|
||||
// Create a shim which translates a Write to a fmt::Write and saves
|
||||
// off I/O errors. instead of discarding them
|
||||
struct Adapter<'a, T: ?Sized> {
|
||||
inner: &'a mut T,
|
||||
error: Result<()>,
|
||||
}
|
||||
|
||||
impl<T: ?Sized + Write> fmt::Write for Adapter<'_, T> {
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
match self.inner.write_all(s.as_bytes()) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(e) => {
|
||||
self.error = Err(e);
|
||||
Err(fmt::Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut output = Adapter { inner: self, error: Ok(()) };
|
||||
match fmt::write(&mut output, f) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(..) => {
|
||||
// check if the error came from the underlying `Write` or not
|
||||
if output.error.is_err() {
|
||||
output.error
|
||||
} else {
|
||||
// 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"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The `no_std` subset of `std::io::Seek`.
|
||||
///
|
||||
/// Unless explicitly stated otherwise, API contracts adhere to `std::io::Seek`.
|
||||
///
|
||||
/// See [`std::io::Seek` documentation][std] for details.
|
||||
///
|
||||
/// [std]: https://doc.rust-lang.org/std/io/trait.Seek.html
|
||||
pub trait Seek {
|
||||
/// Seek to an offset, in bytes, in a stream.
|
||||
///
|
||||
/// See [`std::io::Seek::seek` documentation][std] for details.
|
||||
///
|
||||
/// [std]: https://doc.rust-lang.org/std/io/trait.Seek.html#tymethod.seek
|
||||
fn seek(&mut self, pos: SeekFrom) -> Result<u64>;
|
||||
|
||||
/// Rewind to the beginning of a stream.
|
||||
///
|
||||
/// This is a convenience method, equivalent to `seek(SeekFrom::Start(0))`.
|
||||
fn rewind(&mut self) -> Result<()> {
|
||||
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<u64> {
|
||||
// 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.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[non_exhaustive]
|
||||
pub enum SeekFrom {
|
||||
/// Sets the offset to the provided number of bytes.
|
||||
Start(u64),
|
||||
/// Sets the offset to the size of this object plus the specified number of
|
||||
/// bytes.
|
||||
///
|
||||
/// It is possible to seek beyond the end of an object, but it's an error to
|
||||
/// seek before byte 0.
|
||||
End(i64),
|
||||
// TODO(arm_compat): It appears that SeekFrom::Current cannot be implemented with APIs provided by Arm semihosting...
|
||||
// /// Sets the offset to the current position plus the specified number of
|
||||
// /// bytes.
|
||||
// ///
|
||||
// /// It is possible to seek beyond the end of an object, but it's an error to
|
||||
// /// seek before byte 0.
|
||||
// Current(i64),
|
||||
}
|
||||
Vendored
+139
@@ -0,0 +1,139 @@
|
||||
// SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
|
||||
use core::fmt;
|
||||
|
||||
use crate::{fd::AsFd as _, io, sys};
|
||||
|
||||
/// Constructs a new handle to the standard input of the current process.
|
||||
///
|
||||
/// Unlike [`std::io::stdin`], this function returns the `Result`.
|
||||
///
|
||||
/// # Platform-specific behavior
|
||||
///
|
||||
/// Currently, this function will always success on MIPS32/MIPS64, On other architectures,
|
||||
/// this may fail if semihosting is only partially supported.
|
||||
///
|
||||
/// Also, we have found that reading from stdin does not work well on MIPS32/MIPS64.
|
||||
///
|
||||
/// [`std::io::stdin`]: https://doc.rust-lang.org/std/io/fn.stdin.html
|
||||
pub fn stdin() -> io::Result<Stdin> {
|
||||
sys::stdio::stdin().map(Stdin)
|
||||
}
|
||||
/// Constructs a new handle to the standard output of the current process.
|
||||
///
|
||||
/// Unlike [`std::io::stdout`], this function returns the `Result`.
|
||||
///
|
||||
/// # Platform-specific behavior
|
||||
///
|
||||
/// Currently, this function will always success on MIPS32/MIPS64, On other architectures,
|
||||
/// this may fail if semihosting is only partially supported.
|
||||
///
|
||||
/// [`std::io::stdout`]: https://doc.rust-lang.org/std/io/fn.stdout.html
|
||||
pub fn stdout() -> io::Result<Stdout> {
|
||||
sys::stdio::stdout().map(Stdout)
|
||||
}
|
||||
/// Constructs a new handle to the standard error of the current process.
|
||||
///
|
||||
/// Unlike [`std::io::stderr`], this function returns the `Result`.
|
||||
///
|
||||
/// # Platform-specific behavior
|
||||
///
|
||||
/// Currently, this function will always success on MIPS32/MIPS64, On other architectures,
|
||||
/// this may fail if semihosting is only partially supported.
|
||||
///
|
||||
/// [`std::io::stderr`]: https://doc.rust-lang.org/std/io/fn.stderr.html
|
||||
pub fn stderr() -> io::Result<Stderr> {
|
||||
sys::stdio::stderr().map(Stderr)
|
||||
}
|
||||
|
||||
/// A handle to the standard input stream of a process.
|
||||
///
|
||||
/// Created by the [`io::stdin`] method.
|
||||
pub struct Stdin(sys::stdio::StdioFd);
|
||||
/// A handle to the standard output stream of a process.
|
||||
///
|
||||
/// Created by the [`io::stdout`] method.
|
||||
pub struct Stdout(sys::stdio::StdioFd);
|
||||
/// A handle to the standard error stream of a process.
|
||||
///
|
||||
/// Created by the [`io::stderr`] method.
|
||||
pub struct Stderr(sys::stdio::StdioFd);
|
||||
|
||||
impl_as_fd!(Stdin, Stdout, Stderr);
|
||||
// TODO(stdio): 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<usize> {
|
||||
sys::read(self.as_fd(), buf)
|
||||
}
|
||||
}
|
||||
impl io::Write for Stdout {
|
||||
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
|
||||
sys::write(self.as_fd(), bytes)
|
||||
}
|
||||
#[inline]
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl io::Write for Stderr {
|
||||
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
|
||||
sys::write(self.as_fd(), bytes)
|
||||
}
|
||||
#[inline]
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl fmt::Debug for Stdin {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Stdin").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
impl fmt::Debug for Stdout {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Stdout").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
impl fmt::Debug for Stderr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Stderr").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait to determine if a descriptor/handle refers to a terminal/tty.
|
||||
pub trait IsTerminal: crate::sealed::Sealed {
|
||||
/// Returns `true` if the descriptor/handle refers to a terminal/tty.
|
||||
///
|
||||
/// On platforms where Rust does not know how to detect a terminal yet, this will return
|
||||
/// `false`. This will also return `false` if an unexpected error occurred, such as from
|
||||
/// passing an invalid file descriptor.
|
||||
///
|
||||
/// The following semihosting calls are currently being used:
|
||||
///
|
||||
/// | Platform | Semihosting call |
|
||||
/// | ------------------------------------------------------------- | ---------------- |
|
||||
/// | AArch64, Arm, RISC-V, LoongArch, Xtensa (openocd-semihosting) | [SYS_ISTTY] |
|
||||
/// | MIPS32, MIPS64 | UHI_fstat |
|
||||
///
|
||||
/// [SYS_ISTTY]: https://github.com/ARM-software/abi-aa/blob/2025Q1/semihosting/semihosting.rst#sys-istty-0x09
|
||||
///
|
||||
/// **Disclaimer:** These semihosting calls might change over time.
|
||||
#[doc(alias = "isatty")]
|
||||
#[doc(alias = "SYS_ISTTY")] // arm_compat
|
||||
fn is_terminal(&self) -> bool;
|
||||
}
|
||||
macro_rules! impl_is_terminal {
|
||||
($($t:ty),*$(,)?) => {$(
|
||||
impl crate::sealed::Sealed for $t {}
|
||||
impl crate::io::IsTerminal for $t {
|
||||
#[inline]
|
||||
fn is_terminal(&self) -> bool {
|
||||
use crate::fd::AsFd as _;
|
||||
crate::sys::stdio::is_terminal(self.as_fd())
|
||||
}
|
||||
}
|
||||
)*}
|
||||
}
|
||||
impl_is_terminal!(Stdin, Stdout, Stderr);
|
||||
#[cfg(feature = "fs")]
|
||||
impl_is_terminal!(crate::fs::File);
|
||||
Reference in new issue
Block a user