1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
// Copyright (c) 2020-2023 MicroDoc Software GmbH.
// See the "LICENSE.txt" file at the top-level directory of this distribution.
//
// Licensed under the MIT license. This file may not be copied, modified,
// or distributed except according to those terms.

/*! Error reporting. */

use std::os::raw::c_int;
use std::sync::{Arc, Mutex};
use std::{fmt, io};

/// A result of a fallible operation.
pub(crate) type Result<T> = std::result::Result<T, Error>;

/// Actual storage for an error.
#[derive(Debug, Clone)]
#[non_exhaustive]
#[allow(variant_size_differences)]
pub enum ErrorKind {
    /// Virtual memory address range contains too many pages.
    TooManyVMPages,

    /// Some [`io::Error`](std::io::Error) occurred.
    #[non_exhaustive]
    Io {
        /// Name of the I/O operation that generated the error.
        operation: &'static str,
        /// The [`io::Error`](std::io::Error) that occurred.
        error: Arc<io::Error>,
        /// Identifier of the process that was the target of the I/O.
        process_id: Option<libc::pid_t>,
    },

    /// Casting an integer caused data loss.
    #[non_exhaustive]
    IntegerCast(std::num::TryFromIntError),
}

/// Call stack back trace where the `Error` object was created.
struct ErrorBackTrace {
    backtrace: backtrace::Backtrace,
    resolved: bool,
}

impl ErrorBackTrace {
    /// Resolve the call stack back trace to resolve all addresses
    /// to their symbolic names.
    fn resolve(&mut self) -> bool {
        if !self.resolved {
            self.resolved = true;
            self.backtrace.resolve();
            true
        } else {
            false
        }
    }
}

impl fmt::Debug for ErrorBackTrace {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        fmt::Debug::fmt(&self.backtrace, f)
    }
}

/// Data describing an `Error` that occurred.
#[derive(Clone)]
struct ErrorData {
    kind: ErrorKind,
    backtrace: Arc<Mutex<ErrorBackTrace>>,
}

impl ErrorData {
    /// Resolve the call stack back trace to resolve all addresses
    /// to their symbolic names.
    fn resolve_back_trace(&self) -> bool {
        let mut back_trace_lock = self.backtrace.lock().unwrap();
        back_trace_lock.resolve()
    }
}

impl fmt::Debug for ErrorData {
    fn fmt(&self, _f: &mut fmt::Formatter) -> fmt::Result {
        if self.resolve_back_trace() {
            fmt::Debug::fmt(self, _f)
        } else {
            Ok(())
        }
    }
}

/// An error is a pointer that allocates when an error happens.
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct Error(Box<ErrorData>);

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match &self.0.kind {
            ErrorKind::TooManyVMPages => {
                write!(f, "virtual memory address range contains too many pages")
            }
            ErrorKind::Io {
                operation,
                error,
                process_id,
            } => match process_id {
                None => write!(f, "{operation}: {error}"),
                Some(process_id) => write!(f, "{operation}({process_id}): {error}"),
            },
            ErrorKind::IntegerCast(err) => err.fmt(f),
        }
    }
}

impl std::error::Error for Error {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match &self.0.kind {
            // Errors that are self-descriptive.
            ErrorKind::TooManyVMPages => None,
            ErrorKind::Io { .. } => None,

            // Errors that defer description to the inner error.
            ErrorKind::IntegerCast(err) => Some(err),
        }
    }
}

/// Convert an `ErrorKind` into an `Error`.
impl From<ErrorKind> for Error {
    fn from(kind: ErrorKind) -> Self {
        let backtrace = backtrace::Backtrace::new_unresolved();

        Self(Box::new(ErrorData {
            kind,
            backtrace: Arc::new(Mutex::new(ErrorBackTrace {
                backtrace,
                resolved: false,
            })),
        }))
    }
}

/// Wrap another error into an instance of `Error`.
impl From<std::num::TryFromIntError> for Error {
    fn from(err: std::num::TryFromIntError) -> Self {
        Self::from(ErrorKind::IntegerCast(err))
    }
}

impl Error {
    /// Wrap an `io::Error` into an instance of `Error`, with an associated process ID.
    pub(crate) fn from_io3(
        error: io::Error,
        operation: &'static str,
        process_id: libc::pid_t,
    ) -> Self {
        ErrorKind::Io {
            operation,
            error: Arc::new(error),
            process_id: Some(process_id),
        }
        .into()
    }

    /// Returns the actual kind of this error.
    pub fn kind(&self) -> &ErrorKind {
        &self.0.kind
    }

    /// Returns the errno code for a given `Error`, if such a code has been
    /// reported by the operating system.
    pub fn os_error_code(&self) -> Option<c_int> {
        match &self.0.kind {
            ErrorKind::TooManyVMPages { .. } => None,
            ErrorKind::Io { error, .. } => error.raw_os_error(),
            ErrorKind::IntegerCast { .. } => None,
        }
    }
}