process_vm_io/
errors.rs

1// Copyright (c) 2020-2025 MicroDoc Software GmbH.
2// See the "LICENSE.txt" file at the top-level directory of this distribution.
3//
4// Licensed under the MIT license. This file may not be copied, modified,
5// or distributed except according to those terms.
6
7/*! Error reporting. */
8
9use alloc::sync::Arc;
10use core::fmt;
11use std::io;
12use std::os::raw::c_int;
13use std::sync::Mutex;
14
15/// A result of a fallible operation.
16pub(crate) type Result<T> = core::result::Result<T, Error>;
17
18/// Actual storage for an error.
19#[derive(Debug, Clone)]
20#[non_exhaustive]
21pub enum ErrorKind {
22    /// Virtual memory address range contains too many pages.
23    TooManyVMPages,
24
25    /// Failed to query the system page size.
26    UnknownPageSize,
27
28    /// Invalid system page size.
29    InvalidPageSize(u64),
30
31    /// Some [`io::Error`](std::io::Error) occurred.
32    #[non_exhaustive]
33    Io {
34        /// Name of the I/O operation that generated the error.
35        operation: &'static str,
36        /// The [`io::Error`](std::io::Error) that occurred.
37        error: Arc<io::Error>,
38        /// Identifier of the process that was the target of the I/O.
39        process_id: Option<libc::pid_t>,
40    },
41
42    /// Casting an integer caused data loss.
43    #[non_exhaustive]
44    IntegerCast(core::num::TryFromIntError),
45}
46
47/// Call stack back trace where the `Error` object was created.
48struct ErrorBackTrace {
49    backtrace: backtrace::Backtrace,
50    resolved: bool,
51}
52
53impl ErrorBackTrace {
54    /// Resolve the call stack back trace to resolve all addresses
55    /// to their symbolic names.
56    fn resolve(&mut self) {
57        if !self.resolved {
58            self.resolved = true;
59            self.backtrace.resolve();
60        }
61    }
62}
63
64impl fmt::Debug for ErrorBackTrace {
65    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
66        fmt::Debug::fmt(&self.backtrace, f)
67    }
68}
69
70/// Data describing an `Error` that occurred.
71#[derive(Clone)]
72struct ErrorData {
73    kind: ErrorKind,
74    backtrace: Arc<Mutex<ErrorBackTrace>>,
75}
76
77impl fmt::Debug for ErrorData {
78    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
79        let mut back_trace_lock = self.backtrace.lock().unwrap();
80        back_trace_lock.resolve();
81        write!(f, "Error: {:?}. At:\n{:?}", self.kind, *back_trace_lock)
82    }
83}
84
85/// An error is a pointer that allocates when an error happens.
86#[derive(Debug, Clone)]
87#[non_exhaustive]
88pub struct Error(Box<ErrorData>);
89
90impl fmt::Display for Error {
91    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
92        match &self.0.kind {
93            ErrorKind::TooManyVMPages => {
94                write!(f, "virtual memory address range contains too many pages")
95            }
96            ErrorKind::UnknownPageSize => {
97                write!(f, "failed to query the system page size")
98            }
99            ErrorKind::InvalidPageSize(size) => {
100                write!(f, "invalid system page size: {size} bytes")
101            }
102            ErrorKind::Io {
103                operation,
104                error,
105                process_id,
106            } => match process_id {
107                None => write!(f, "{operation}: {error}"),
108                Some(process_id) => write!(f, "{operation}({process_id}): {error}"),
109            },
110            ErrorKind::IntegerCast(err) => err.fmt(f),
111        }
112    }
113}
114
115impl core::error::Error for Error {
116    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
117        match &self.0.kind {
118            // Errors that are self-descriptive.
119            ErrorKind::TooManyVMPages
120            | ErrorKind::UnknownPageSize
121            | ErrorKind::InvalidPageSize(_)
122            | ErrorKind::Io { .. } => None,
123
124            // Errors that defer description to the inner error.
125            ErrorKind::IntegerCast(err) => Some(err),
126        }
127    }
128}
129
130/// Convert an `ErrorKind` into an `Error`.
131impl From<ErrorKind> for Error {
132    fn from(kind: ErrorKind) -> Self {
133        let backtrace = backtrace::Backtrace::new_unresolved();
134
135        Self(Box::new(ErrorData {
136            kind,
137            backtrace: Arc::new(Mutex::new(ErrorBackTrace {
138                backtrace,
139                resolved: false,
140            })),
141        }))
142    }
143}
144
145/// Wrap another error into an instance of `Error`.
146impl From<core::num::TryFromIntError> for Error {
147    fn from(err: core::num::TryFromIntError) -> Self {
148        Self::from(ErrorKind::IntegerCast(err))
149    }
150}
151
152impl Error {
153    /// Wrap an `io::Error` into an instance of `Error`, with an associated process ID.
154    pub(crate) fn from_io3(
155        error: io::Error,
156        operation: &'static str,
157        process_id: libc::pid_t,
158    ) -> Self {
159        ErrorKind::Io {
160            operation,
161            error: Arc::new(error),
162            process_id: Some(process_id),
163        }
164        .into()
165    }
166
167    /// Returns the actual kind of this error.
168    #[must_use]
169    pub fn kind(&self) -> &ErrorKind {
170        &self.0.kind
171    }
172
173    /// Returns the errno code for a given `Error`, if such a code has been
174    /// reported by the operating system.
175    #[must_use]
176    pub fn os_error_code(&self) -> Option<c_int> {
177        match &self.0.kind {
178            ErrorKind::TooManyVMPages
179            | ErrorKind::UnknownPageSize
180            | ErrorKind::InvalidPageSize(_)
181            | ErrorKind::IntegerCast { .. } => None,
182
183            ErrorKind::Io { error, .. } => error.raw_os_error(),
184        }
185    }
186}