use core::{ error::Error as StdError, ffi::c_int, fmt::{Display, Formatter, Result as FmtResult}, }; /// An error returned while reading memory from a target process. /// /// `ReadError` is intentionally a small public wrapper around private strategy /// errors. Its [`Display`] implementation describes the high-level failure. Its /// [`core::error::Error::source`] implementation returns a lower-level source /// when there is exactly one failed strategy, including failures from forced /// strategies and failures from a strategy selected by an automatic reader. /// /// Short successful reads are not represented by `ReadError`; they are returned /// from [`crate::ProcessReader::read_at`] as `Ok(n)` where `n` is smaller than /// the requested buffer length. /// /// When [`crate::ProcessReader::new`] tries every strategy and all of them fail, /// there is no single source error. In that case /// [`core::error::Error::source`] returns `None`, and callers can inspect each /// branch with /// [`ReadError::virtual_mem_error`], [`ReadError::file_error`], and /// [`ReadError::ptrace_error`]. /// /// If the `serde` feature is enabled, this type implements `Serialize` and /// `Deserialize`. #[derive(Debug)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct ReadError(pub(crate) ReadErrorInner); impl ReadError { /// Returns the error from the `process_vm_readv(2)` strategy, if present. /// /// This returns `Some` when a reader forced to or cached on /// [`crate::ProcessReader::for_virtual_mem`] fails, and when automatic /// strategy selection fails after attempting `process_vm_readv`. /// /// It returns `None` when the error does not contain a `process_vm_readv` /// failure. pub fn virtual_mem_error(&self) -> Option<&(dyn StdError + 'static)> { match &self.0 { ReadErrorInner::AllStrategies { vmem_err, .. } => Some(vmem_err), ReadErrorInner::VirtualMemStrategy(e) => Some(e), _ => None, } } /// Returns the error from the `/proc//mem` strategy, if present. /// /// This returns `Some` when a reader forced to or cached on /// [`crate::ProcessReader::for_file`] fails, and when automatic strategy /// selection fails after attempting to use `/proc//mem`. /// /// It returns `None` when the error does not contain a `/proc//mem` /// failure. pub fn file_error(&self) -> Option<&(dyn StdError + 'static)> { match &self.0 { ReadErrorInner::AllStrategies { file_err, .. } => Some(file_err), ReadErrorInner::FileStrategy(e) => Some(e), _ => None, } } /// Returns the error from the `ptrace(PTRACE_PEEKDATA)` strategy, if present. /// /// This returns `Some` when a reader forced to or cached on /// [`crate::ProcessReader::for_ptrace`] fails, and when automatic strategy /// selection fails after attempting `ptrace(PTRACE_PEEKDATA)`. /// /// It returns `None` when the error does not contain a ptrace failure. pub fn ptrace_error(&self) -> Option<&(dyn StdError + 'static)> { match &self.0 { ReadErrorInner::AllStrategies { ptrace_err, .. } => Some(ptrace_err), ReadErrorInner::PtraceStrategy(e) => Some(e), _ => None, } } } impl Display for ReadError { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { use ReadErrorInner as E; match &self.0 { E::AllStrategies { .. } => write!(f, "all process reading strategies failed"), E::VirtualMemStrategy(_) => write!(f, "virtual memory strategy failed"), E::FileStrategy(_) => write!(f, "file strategy failed"), E::PtraceStrategy(_) => write!(f, "ptrace strategy failed"), } } } impl StdError for ReadError { fn source(&self) -> Option<&(dyn StdError + 'static)> { use ReadErrorInner as E; match &self.0 { E::AllStrategies { .. } => None, E::VirtualMemStrategy(e) => Some(e), E::FileStrategy(e) => Some(e), E::PtraceStrategy(e) => Some(e), } } } #[derive(Debug)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub(crate) enum ReadErrorInner { AllStrategies { vmem_err: ProcessVmReadvFailed, file_err: FileStrategyError, ptrace_err: PtraceError, }, VirtualMemStrategy(ProcessVmReadvFailed), FileStrategy(FileStrategyError), PtraceStrategy(PtraceError), } /// Error returned by [`ProcessReader::read_exact_at`]. /// /// [`ProcessReader::read_exact_at`] is built on top of /// [`ProcessReader::read_at`]. It repeatedly performs partial reads until the /// caller's buffer is full. This error reports why that exact read could not be /// completed. #[derive(Debug)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub enum ReadExactError { /// An underlying call to [`ProcessReader::read_at`] failed before the buffer /// was completely filled. /// /// The wrapped [`ReadError`] describes the strategy that failed, or the set /// of strategies that failed if automatic strategy selection had not yet /// chosen a strategy. Read(ReadError), /// The reader stopped making progress before the buffer was completely /// filled. /// /// This occurs when [`ProcessReader::read_at`] returns `Ok(0)` while /// `read_exact_at` still has bytes left to read. UnexpectedEof, } impl Display for ReadExactError { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { match self { Self::Read(_) => write!(f, "an error occurred before filling entire buffer"), Self::UnexpectedEof => write!(f, "unexpected end-of-file before filling entire buffer"), } } } impl StdError for ReadExactError { fn source(&self) -> Option<&(dyn StdError + 'static)> { match self { Self::Read(e) => Some(e), Self::UnexpectedEof => None, } } } #[derive(Debug)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub(crate) struct ProcessVmReadvFailed(pub(crate) c_int); impl Display for ProcessVmReadvFailed { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { write!(f, "process_vm_readv() returned an error code: {}", self.0) } } impl StdError for ProcessVmReadvFailed {} #[derive(Debug)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub(crate) enum FileStrategyError { Open(OpenFailed), Read(ReadExactAtError), } impl Display for FileStrategyError { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { match self { Self::Open(_) => write!(f, "failed to open /proc//mem file"), Self::Read(_) => write!(f, "failed to read /proc//mem file"), } } } impl StdError for FileStrategyError { fn source(&self) -> Option<&(dyn StdError + 'static)> { match self { Self::Open(e) => Some(e), Self::Read(e) => Some(e), } } } #[derive(Debug)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub(crate) struct OpenFailed(pub(crate) c_int); impl Display for OpenFailed { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { write!(f, "open64() returned an error code: {}", self.0) } } impl StdError for OpenFailed {} #[derive(Debug)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub(crate) enum ReadExactAtError { ReadAt(ReadAtFailed), AddressOverflow, UnexpectedEof { position: usize }, } impl Display for ReadExactAtError { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { match self { Self::ReadAt(_) => write!(f, "I/O error reading file"), Self::AddressOverflow => write!( f, "the given address/length pair overflowed the machine's memory range" ), Self::UnexpectedEof { position } => write!( f, "unexpected end-of-file encountered at position: {position}" ), } } } impl StdError for ReadExactAtError { fn source(&self) -> Option<&(dyn StdError + 'static)> { match self { Self::ReadAt(e) => Some(e), Self::AddressOverflow => None, Self::UnexpectedEof { .. } => None, } } } #[derive(Debug)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub(crate) enum ReadAtFailed { Syscall(c_int), AddressOutOfBounds, } impl Display for ReadAtFailed { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { match self { Self::Syscall(errno) => write!(f, "pread64() returned an error code: {errno}"), Self::AddressOutOfBounds => { write!(f, "requested address was out-of-bounds for pread64()") } } } } impl StdError for ReadAtFailed {} #[derive(Debug)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub(crate) enum PtraceError { Syscall { errno: c_int, position: usize }, AddressOverflow, } impl Display for PtraceError { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { match self { Self::Syscall { errno, position } => write!( f, "ptrace(PTRACE_PEEKDATA) returned an error code at position {position}: {errno}", ), Self::AddressOverflow => write!( f, "the given address/length pair overflowed the machine's memory range" ), } } } impl StdError for PtraceError {}