vminer_core/
error.rs

1use alloc::{
2    boxed::Box,
3    string::{String, ToString},
4};
5use core::{error::Error, fmt};
6
7use crate::seal;
8
9// Add a few common conversions for convenience
10
11#[derive(Debug)]
12pub enum VcpuError {
13    Unsupported,
14    InvalidId,
15    BadArchitecture,
16    #[cfg(feature = "std")]
17    Io(std::io::Error),
18}
19
20pub type VcpuResult<T> = Result<T, VcpuError>;
21
22impl fmt::Display for VcpuError {
23    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24        match self {
25            Self::Unsupported => f.write_str("unsupported operation"),
26            Self::InvalidId => f.write_str("invalid vCPU ID"),
27            Self::BadArchitecture => f.write_str("wrong architecture"),
28            #[cfg(feature = "std")]
29            Self::Io(err) => err.fmt(f),
30        }
31    }
32}
33
34impl Error for VcpuError {
35    #[inline]
36    fn source(&self) -> Option<&(dyn Error + 'static)> {
37        match self {
38            #[cfg(feature = "std")]
39            Self::Io(err) => err.source(),
40            _ => None,
41        }
42    }
43}
44
45#[derive(Debug)]
46#[non_exhaustive]
47pub enum MemoryAccessError {
48    OutOfBounds,
49    Unsupported,
50    #[cfg(feature = "std")]
51    Io(std::io::Error),
52}
53
54impl fmt::Display for MemoryAccessError {
55    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
56        match self {
57            Self::OutOfBounds => f.write_str("out of bounds memory access"),
58            Self::Unsupported => f.write_str("unsupported operation"),
59            #[cfg(feature = "std")]
60            Self::Io(err) => err.fmt(f),
61        }
62    }
63}
64
65impl Error for MemoryAccessError {
66    fn source(&self) -> Option<&(dyn Error + 'static)> {
67        match self {
68            #[cfg(feature = "std")]
69            Self::Io(err) => err.source(),
70            _ => None,
71        }
72    }
73}
74
75#[cfg(feature = "std")]
76impl From<std::io::Error> for MemoryAccessError {
77    #[cold]
78    #[inline]
79    fn from(error: std::io::Error) -> Self {
80        Self::Io(error)
81    }
82}
83
84#[cfg(feature = "std")]
85impl From<MemoryAccessError> for std::io::Error {
86    fn from(error: MemoryAccessError) -> Self {
87        match error {
88            MemoryAccessError::OutOfBounds => std::io::Error::new(
89                std::io::ErrorKind::UnexpectedEof,
90                "out of bounds memory access",
91            ),
92            MemoryAccessError::Unsupported => std::io::Error::from(std::io::ErrorKind::Unsupported),
93            MemoryAccessError::Io(error) => error,
94        }
95    }
96}
97
98pub type MemoryAccessResult<T> = Result<T, MemoryAccessError>;
99
100#[derive(Debug)]
101pub enum TranslationError {
102    Memory(MemoryAccessError),
103    Invalid(u64),
104}
105
106pub type TranslationResult<T> = Result<T, TranslationError>;
107
108impl From<MemoryAccessError> for TranslationError {
109    fn from(err: MemoryAccessError) -> Self {
110        Self::Memory(err)
111    }
112}
113
114impl From<TranslationError> for VmError {
115    fn from(err: TranslationError) -> Self {
116        match err {
117            TranslationError::Memory(err) => err.into(),
118            TranslationError::Invalid(entry) => VmError::from_repr(Repr::InvalidPage(entry)),
119        }
120    }
121}
122
123impl fmt::Display for TranslationError {
124    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125        match self {
126            TranslationError::Memory(err) => err.fmt(f),
127            TranslationError::Invalid(_) => f.write_str("invalid MMU entry"),
128        }
129    }
130}
131
132impl Error for TranslationError {
133    fn source(&self) -> Option<&(dyn Error + 'static)> {
134        match self {
135            TranslationError::Memory(err) => err.source(),
136            TranslationError::Invalid(_) => None,
137        }
138    }
139}
140
141#[derive(Debug)]
142enum Repr {
143    Memory(MemoryAccessError),
144    Vcpu(VcpuError),
145    #[allow(dead_code)]
146    InvalidPage(u64),
147
148    UnsupportedArchitecture,
149    Unsupported,
150    Unimplemented,
151
152    MissingModule(Box<str>),
153    MissingSymbol(Box<str>),
154    MissingField(Box<str>, Box<str>),
155
156    NullPtr,
157
158    #[cfg(feature = "std")]
159    Io(std::io::Error),
160    Other(Box<dyn Error + Send + Sync>),
161    Context(Box<str>, Option<Box<dyn Error + Send + Sync>>),
162}
163
164#[derive(Debug)]
165#[repr(transparent)]
166pub struct VmError {
167    repr: Box<Repr>,
168}
169
170pub type VmResult<T> = Result<T, VmError>;
171
172impl VmError {
173    #[inline]
174    fn from_repr(repr: Repr) -> Self {
175        Self {
176            repr: Box::new(repr),
177        }
178    }
179
180    #[cold]
181    pub fn missing_module(sym: &str) -> Self {
182        Self::from_repr(Repr::MissingModule(sym.into()))
183    }
184
185    #[cold]
186    pub fn missing_symbol(sym: &str) -> Self {
187        Self::from_repr(Repr::MissingSymbol(sym.into()))
188    }
189
190    #[cold]
191    pub fn missing_field(field: &str, typ: &str) -> Self {
192        Self::from_repr(Repr::MissingField(field.into(), typ.into()))
193    }
194
195    #[cold]
196    pub fn deref_null_ptr() -> Self {
197        Self::from_repr(Repr::NullPtr)
198    }
199
200    #[cold]
201    pub fn new(err: impl Into<Box<dyn Error + Send + Sync>>) -> Self {
202        Self::from_repr(Repr::Other(err.into()))
203    }
204
205    #[cold]
206    pub fn with_context(msg: impl ToString, err: impl Into<Box<dyn Error + Send + Sync>>) -> Self {
207        Self::from_repr(Repr::Context(msg.to_string().into(), Some(err.into())))
208    }
209
210    #[cold]
211    pub fn unsupported_architecture() -> Self {
212        Self::from_repr(Repr::UnsupportedArchitecture)
213    }
214
215    #[cold]
216    pub fn unsupported() -> Self {
217        Self::from_repr(Repr::Unsupported)
218    }
219
220    #[cold]
221    pub fn unimplemented() -> Self {
222        Self::from_repr(Repr::Unimplemented)
223    }
224
225    pub fn print_backtrace(&self) -> String {
226        let mut trace = String::new();
227        fmt::write(&mut trace, format_args!("{self:#}")).unwrap();
228        trace
229    }
230}
231
232impl fmt::Display for Repr {
233    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
234        match self {
235            Repr::Memory(_) => f.write_str("failed to access physical memory"),
236            Repr::Vcpu(_) => f.write_str("failed to access registers"),
237            Repr::InvalidPage(_) => f.write_str("encountered invalid page"),
238            Repr::UnsupportedArchitecture => {
239                f.write_str("operation unsupported by the architecture")
240            }
241            Repr::Unsupported => f.write_str("unsupported operation"),
242            Repr::Unimplemented => f.write_str("unimplemented"),
243            Repr::MissingModule(name) => {
244                f.write_fmt(format_args!("missing required module \"{name}\""))
245            }
246            Repr::MissingSymbol(sym) => {
247                f.write_fmt(format_args!("missing required symbol \"{sym}\""))
248            }
249            Repr::MissingField(field, typ) => f.write_fmt(format_args!(
250                "missing required field \"{field}\" in type \"{typ}\""
251            )),
252            Repr::NullPtr => f.write_str("attempted to deref NULL pointer"),
253            #[cfg(feature = "std")]
254            Repr::Io(_) => f.write_str("I/O error"),
255            Repr::Context(msg, _) => f.write_str(msg),
256            Repr::Other(err) => err.fmt(f),
257        }
258    }
259}
260
261impl fmt::Display for VmError {
262    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
263        if f.alternate() {
264            f.write_fmt(format_args!("{}", self.repr))?;
265
266            let mut current = self.source();
267
268            if current.is_some() {
269                f.write_str("\n\nCaused by:")?;
270            }
271
272            while let Some(cause) = current {
273                f.write_fmt(format_args!("\n    {cause}"))?;
274                current = cause.source();
275            }
276
277            Ok(())
278        } else {
279            self.repr.fmt(f)
280        }
281    }
282}
283
284impl Error for VmError {
285    fn source(&self) -> Option<&(dyn Error + 'static)> {
286        match &*self.repr {
287            Repr::Memory(err) => Some(err),
288            Repr::Vcpu(err) => Some(err),
289            #[cfg(feature = "std")]
290            Repr::Io(err) => Some(err),
291            Repr::Context(_, err) => Some(&**err.as_ref()?),
292            Repr::Other(err) => err.source(),
293            _ => None,
294        }
295    }
296}
297
298impl From<&str> for VmError {
299    #[cold]
300    fn from(msg: &str) -> Self {
301        Self::from_repr(Repr::Context(msg.into(), None))
302    }
303}
304
305impl From<String> for VmError {
306    #[cold]
307    fn from(msg: String) -> Self {
308        Self::from_repr(Repr::Context(msg.into(), None))
309    }
310}
311
312impl From<MemoryAccessError> for VmError {
313    #[cold]
314    fn from(err: MemoryAccessError) -> Self {
315        Self::from_repr(Repr::Memory(err))
316    }
317}
318
319impl From<VcpuError> for VmError {
320    #[cold]
321    fn from(err: VcpuError) -> Self {
322        Self::from_repr(Repr::Vcpu(err))
323    }
324}
325
326impl From<core::str::Utf8Error> for VmError {
327    #[cold]
328    fn from(error: core::str::Utf8Error) -> Self {
329        Self::from(error.to_string())
330    }
331}
332
333#[cfg(feature = "std")]
334impl From<std::io::Error> for VmError {
335    #[cold]
336    fn from(error: std::io::Error) -> Self {
337        Self::from_repr(Repr::Io(error))
338    }
339}
340
341pub trait TranslationResultExt<T>: seal::Sealed {
342    fn maybe_invalid(self) -> MemoryAccessResult<Option<T>>;
343}
344
345impl<T> TranslationResultExt<T> for TranslationResult<T> {
346    fn maybe_invalid(self) -> MemoryAccessResult<Option<T>> {
347        match self {
348            Ok(x) => Ok(Some(x)),
349            Err(TranslationError::Invalid(_)) => Ok(None),
350            Err(TranslationError::Memory(err)) => Err(err),
351        }
352    }
353}
354
355pub trait ResultExt<T>: seal::Sealed {
356    fn context(self, msg: impl ToString) -> VmResult<T>;
357
358    fn with_context<F, S>(self, msg: F) -> VmResult<T>
359    where
360        F: FnOnce() -> S,
361        S: ToString;
362}
363
364impl<T, E> ResultExt<T> for Result<T, E>
365where
366    E: Into<Box<dyn Error + Send + Sync>>,
367{
368    fn context(self, msg: impl ToString) -> VmResult<T> {
369        self.map_err(|err| VmError::with_context(msg.to_string(), err))
370    }
371
372    fn with_context<F, S>(self, msg: F) -> VmResult<T>
373    where
374        F: FnOnce() -> S,
375        S: ToString,
376    {
377        self.map_err(|err| VmError::with_context(msg().to_string(), err))
378    }
379}
380
381impl<T> ResultExt<T> for Option<T> {
382    fn context(self, msg: impl ToString) -> VmResult<T> {
383        self.ok_or_else(|| VmError::new(msg.to_string()))
384    }
385
386    fn with_context<F, S>(self, msg: F) -> VmResult<T>
387    where
388        F: FnOnce() -> S,
389        S: ToString,
390    {
391        self.ok_or_else(|| VmError::new(msg().to_string()))
392    }
393}