ctru/
error.rs

1//! Error handling interface.
2//!
3//! This module holds the generic error and result types to interface with `ctru_sys` and the [`ctru-rs`](crate) safe wrapper.
4
5use std::borrow::Cow;
6use std::error;
7use std::ffi::CStr;
8use std::fmt;
9use std::ops::{ControlFlow, FromResidual, Try};
10
11use ctru_sys::result::{R_DESCRIPTION, R_LEVEL, R_MODULE, R_SUMMARY};
12
13/// Custom type alias for generic [`ctru-rs`](crate) operations.
14///
15/// This type is compatible with [`ctru_sys::Result`] codes.
16pub type Result<T> = ::std::result::Result<T, Error>;
17
18/// Validity checker of raw [`ctru_sys::Result`] codes.
19///
20/// This struct supports the "try" syntax (`?`) to convert to an [`Error::Os`].
21///
22/// # Example
23///
24/// ```
25/// use ctru::error::{Result, ResultCode};
26///
27/// pub fn main() -> Result<()> {
28/// #   let _runner = test_runner::GdbRunner::default();
29///     // We run an unsafe function which returns a `ctru_sys::Result`.
30///     let result: ctru_sys::Result = unsafe { ctru_sys::hidInit() };
31///
32///     // The result code is parsed and any possible error gets returned by the function.
33///     ResultCode(result)?;
34///     Ok(())
35/// }
36/// ```
37#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord)]
38#[repr(transparent)]
39pub struct ResultCode(pub ctru_sys::Result);
40
41impl Try for ResultCode {
42    type Output = ();
43    type Residual = Error;
44
45    fn from_output(_: Self::Output) -> Self {
46        Self(0)
47    }
48
49    fn branch(self) -> ControlFlow<Self::Residual, Self::Output> {
50        // Wait timeouts aren't counted as "failures" in libctru, but an unfinished task means unsafety for us.
51        // Luckily all summary cases are for system failures (except RS_SUCCESS).
52        // I don't know if there are any cases in libctru where a Result holds a "failing" summary but a "success" code, so we'll just check for both.
53        if ctru_sys::R_FAILED(self.0) || ctru_sys::R_SUMMARY(self.0) != ctru_sys::RS_SUCCESS {
54            ControlFlow::Break(self.into())
55        } else {
56            ControlFlow::Continue(())
57        }
58    }
59}
60
61impl FromResidual for ResultCode {
62    fn from_residual(e: <Self as Try>::Residual) -> Self {
63        match e {
64            Error::Os(result) => Self(result),
65            _ => unreachable!(),
66        }
67    }
68}
69
70impl<T> FromResidual<Error> for Result<T> {
71    fn from_residual(e: Error) -> Self {
72        Err(e)
73    }
74}
75
76/// The generic error enum returned by [`ctru-rs`](crate) functions.
77///
78/// This error enum supports parsing and displaying [`ctru_sys::Result`] codes.
79#[non_exhaustive]
80pub enum Error {
81    /// Raw [`ctru_sys::Result`] codes.
82    Os(ctru_sys::Result),
83    /// Generic [`libc`] errors.
84    Libc(String),
85    /// Requested service is already active and cannot be activated again.
86    ServiceAlreadyActive,
87    /// `stdout` is already being redirected.
88    OutputAlreadyRedirected,
89    /// The buffer provided by the user to store some data is shorter than required.
90    BufferTooShort {
91        /// Length of the buffer provided by the user.
92        provided: usize,
93        /// Size of the requested data (in bytes).
94        wanted: usize,
95    },
96    /// An error that doesn't fit into the other categories.
97    Other(String),
98}
99
100impl Error {
101    /// Create an [`Error`] out of the last set value in `errno`.
102    ///
103    /// This can be used to get a human-readable error string from calls to `libc` functions.
104    pub(crate) fn from_errno() -> Self {
105        let error_str = unsafe {
106            let errno = ctru_sys::errno();
107            let str_ptr = libc::strerror(errno);
108
109            // Safety: strerror should always return a valid string,
110            // even if the error number is unknown
111            CStr::from_ptr(str_ptr)
112        };
113
114        // Copy out of the error string, since it may be changed by other libc calls later
115        Self::Libc(error_str.to_string_lossy().into())
116    }
117
118    /// Check if the error is a timeout.
119    pub fn is_timeout(&self) -> bool {
120        match *self {
121            Error::Os(code) => R_DESCRIPTION(code) == ctru_sys::RD_TIMEOUT,
122            _ => false,
123        }
124    }
125}
126
127impl From<ctru_sys::Result> for Error {
128    fn from(err: ctru_sys::Result) -> Self {
129        Error::Os(err)
130    }
131}
132
133impl From<ResultCode> for Error {
134    fn from(err: ResultCode) -> Self {
135        Self::Os(err.0)
136    }
137}
138
139impl fmt::Debug for Error {
140    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
141        match self {
142            &Self::Os(err) => f
143                .debug_struct("Error")
144                .field("raw", &format_args!("{err:#08X}"))
145                .field("level", &result_code_level_str(err))
146                .field("module", &result_code_module_str(err))
147                .field("summary", &result_code_summary_str(err))
148                .field("description", &result_code_description_str(err))
149                .finish(),
150            Self::Libc(err) => f.debug_tuple("Libc").field(err).finish(),
151            Self::ServiceAlreadyActive => f.debug_tuple("ServiceAlreadyActive").finish(),
152            Self::OutputAlreadyRedirected => f.debug_tuple("OutputAlreadyRedirected").finish(),
153            Self::BufferTooShort { provided, wanted } => f
154                .debug_struct("BufferTooShort")
155                .field("provided", provided)
156                .field("wanted", wanted)
157                .finish(),
158            Self::Other(err) => f.debug_tuple("Other").field(err).finish(),
159        }
160    }
161}
162
163impl fmt::Display for Error {
164    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
165        match self {
166            // TODO: should we consider using ctru_sys::osStrError here as well?
167            // It might do some of the work for us or provide additional details
168            &Self::Os(err) => write!(
169                f,
170                "libctru result code 0x{err:08X}: [{} {}] {}: {}",
171                result_code_level_str(err),
172                result_code_module_str(err),
173                result_code_summary_str(err),
174                result_code_description_str(err)
175            ),
176            Self::Libc(err) => write!(f, "{err}"),
177            Self::ServiceAlreadyActive => write!(f, "service already active"),
178            Self::OutputAlreadyRedirected => {
179                write!(f, "output streams are already redirected to 3dslink")
180            }
181            Self::BufferTooShort { provided, wanted } => write!(
182                f,
183                "the provided buffer's length is too short (length = {provided}) to hold the wanted data (size = {wanted})"
184            ),
185            Self::Other(err) => write!(f, "{err}"),
186        }
187    }
188}
189
190impl error::Error for Error {}
191
192fn result_code_level_str(result: ctru_sys::Result) -> Cow<'static, str> {
193    use ctru_sys::{
194        RL_FATAL, RL_INFO, RL_PERMANENT, RL_REINITIALIZE, RL_RESET, RL_STATUS, RL_SUCCESS,
195        RL_TEMPORARY, RL_USAGE,
196    };
197
198    Cow::Borrowed(match R_LEVEL(result) {
199        RL_SUCCESS => "success",
200        RL_INFO => "info",
201        RL_FATAL => "fatal",
202        RL_RESET => "reset",
203        RL_REINITIALIZE => "reinitialize",
204        RL_USAGE => "usage",
205        RL_PERMANENT => "permanent",
206        RL_TEMPORARY => "temporary",
207        RL_STATUS => "status",
208        code => return Cow::Owned(format!("(unknown level: {code:#x})")),
209    })
210}
211
212fn result_code_summary_str(result: ctru_sys::Result) -> Cow<'static, str> {
213    use ctru_sys::{
214        RS_CANCELED, RS_INTERNAL, RS_INVALIDARG, RS_INVALIDRESVAL, RS_INVALIDSTATE, RS_NOP,
215        RS_NOTFOUND, RS_NOTSUPPORTED, RS_OUTOFRESOURCE, RS_STATUSCHANGED, RS_SUCCESS,
216        RS_WOULDBLOCK, RS_WRONGARG,
217    };
218
219    Cow::Borrowed(match R_SUMMARY(result) {
220        RS_SUCCESS => "success",
221        RS_NOP => "nop",
222        RS_WOULDBLOCK => "would_block",
223        RS_OUTOFRESOURCE => "out_of_resource",
224        RS_NOTFOUND => "not_found",
225        RS_INVALIDSTATE => "invalid_state",
226        RS_NOTSUPPORTED => "not_supported",
227        RS_INVALIDARG => "invalid_arg",
228        RS_WRONGARG => "wrong_arg",
229        RS_CANCELED => "canceled",
230        RS_STATUSCHANGED => "status_changed",
231        RS_INTERNAL => "internal",
232        RS_INVALIDRESVAL => "invalid_res_val",
233        code => return Cow::Owned(format!("(unknown summary: {code:#x})")),
234    })
235}
236
237fn result_code_description_str(result: ctru_sys::Result) -> Cow<'static, str> {
238    use ctru_sys::{
239        RD_ALREADY_DONE, RD_ALREADY_EXISTS, RD_ALREADY_INITIALIZED, RD_BUSY, RD_CANCEL_REQUESTED,
240        RD_INVALID_ADDRESS, RD_INVALID_COMBINATION, RD_INVALID_ENUM_VALUE, RD_INVALID_HANDLE,
241        RD_INVALID_POINTER, RD_INVALID_RESULT_VALUE, RD_INVALID_SELECTION, RD_INVALID_SIZE,
242        RD_MISALIGNED_ADDRESS, RD_MISALIGNED_SIZE, RD_NO_DATA, RD_NOT_AUTHORIZED, RD_NOT_FOUND,
243        RD_NOT_IMPLEMENTED, RD_NOT_INITIALIZED, RD_OUT_OF_MEMORY, RD_OUT_OF_RANGE, RD_SUCCESS,
244        RD_TIMEOUT, RD_TOO_LARGE,
245    };
246
247    Cow::Borrowed(match R_DESCRIPTION(result) {
248        RD_SUCCESS => "success",
249        RD_INVALID_RESULT_VALUE => "invalid_result_value",
250        RD_TIMEOUT => "timeout",
251        RD_OUT_OF_RANGE => "out_of_range",
252        RD_ALREADY_EXISTS => "already_exists",
253        RD_CANCEL_REQUESTED => "cancel_requested",
254        RD_NOT_FOUND => "not_found",
255        RD_ALREADY_INITIALIZED => "already_initialized",
256        RD_NOT_INITIALIZED => "not_initialized",
257        RD_INVALID_HANDLE => "invalid_handle",
258        RD_INVALID_POINTER => "invalid_pointer",
259        RD_INVALID_ADDRESS => "invalid_address",
260        RD_NOT_IMPLEMENTED => "not_implemented",
261        RD_OUT_OF_MEMORY => "out_of_memory",
262        RD_MISALIGNED_SIZE => "misaligned_size",
263        RD_MISALIGNED_ADDRESS => "misaligned_address",
264        RD_BUSY => "busy",
265        RD_NO_DATA => "no_data",
266        RD_INVALID_COMBINATION => "invalid_combination",
267        RD_INVALID_ENUM_VALUE => "invalid_enum_value",
268        RD_INVALID_SIZE => "invalid_size",
269        RD_ALREADY_DONE => "already_done",
270        RD_NOT_AUTHORIZED => "not_authorized",
271        RD_TOO_LARGE => "too_large",
272        RD_INVALID_SELECTION => "invalid_selection",
273        code => {
274            let error = unsafe { CStr::from_ptr(ctru_sys::osStrError(result)) }.to_str();
275            match error {
276                Ok(err) => err,
277                Err(_) => return Cow::Owned(format!("(unknown description: {code:#x})")),
278            }
279        }
280    })
281}
282
283fn result_code_module_str(result: ctru_sys::Result) -> Cow<'static, str> {
284    use ctru_sys::{
285        RM_AC, RM_ACC, RM_ACT, RM_AM, RM_AM_LOW, RM_APPLET, RM_APPLICATION, RM_AVD, RM_BOSS,
286        RM_CAM, RM_CARD, RM_CARD_SPI, RM_CARDNOR, RM_CEC, RM_CODEC, RM_COMMON, RM_CONFIG, RM_CSND,
287        RM_CUP, RM_DBG, RM_DBM, RM_DD, RM_DI, RM_DLP, RM_DMNT, RM_DSP, RM_EC, RM_ENC, RM_FATFS,
288        RM_FILE_SERVER, RM_FND, RM_FRIENDS, RM_FS, RM_FSI, RM_GD, RM_GPIO, RM_GSP, RM_GYROSCOPE,
289        RM_HID, RM_HIO, RM_HIO_LOW, RM_HTTP, RM_I2C, RM_INVALIDRESVAL, RM_IR, RM_KERNEL, RM_L2B,
290        RM_LDR, RM_LOADER_SERVER, RM_MC, RM_MCU, RM_MIC, RM_MIDI, RM_MP, RM_MPWL, RM_MVD, RM_NDM,
291        RM_NEIA, RM_NEWS, RM_NEX, RM_NFC, RM_NFP, RM_NGC, RM_NIM, RM_NPNS, RM_NS, RM_NWM, RM_OLV,
292        RM_OS, RM_PDN, RM_PI, RM_PIA, RM_PL, RM_PM, RM_PM_LOW, RM_PS, RM_PTM, RM_PXI, RM_QTM,
293        RM_RDT, RM_RO, RM_ROMFS, RM_SDMC, RM_SND, RM_SOC, RM_SPI, RM_SPM, RM_SRV, RM_SSL, RM_SWC,
294        RM_TCB, RM_TEST, RM_UART, RM_UDS, RM_UPDATER, RM_UTIL, RM_VCTL, RM_WEB_BROWSER,
295    };
296
297    Cow::Borrowed(match R_MODULE(result) {
298        RM_COMMON => "common",
299        RM_KERNEL => "kernel",
300        RM_UTIL => "util",
301        RM_FILE_SERVER => "file_server",
302        RM_LOADER_SERVER => "loader_server",
303        RM_TCB => "tcb",
304        RM_OS => "os",
305        RM_DBG => "dbg",
306        RM_DMNT => "dmnt",
307        RM_PDN => "pdn",
308        RM_GSP => "gsp",
309        RM_I2C => "i2c",
310        RM_GPIO => "gpio",
311        RM_DD => "dd",
312        RM_CODEC => "codec",
313        RM_SPI => "spi",
314        RM_PXI => "pxi",
315        RM_FS => "fs",
316        RM_DI => "di",
317        RM_HID => "hid",
318        RM_CAM => "cam",
319        RM_PI => "pi",
320        RM_PM => "pm",
321        RM_PM_LOW => "pm_low",
322        RM_FSI => "fsi",
323        RM_SRV => "srv",
324        RM_NDM => "ndm",
325        RM_NWM => "nwm",
326        RM_SOC => "soc",
327        RM_LDR => "ldr",
328        RM_ACC => "acc",
329        RM_ROMFS => "romfs",
330        RM_AM => "am",
331        RM_HIO => "hio",
332        RM_UPDATER => "updater",
333        RM_MIC => "mic",
334        RM_FND => "fnd",
335        RM_MP => "mp",
336        RM_MPWL => "mpwl",
337        RM_AC => "ac",
338        RM_HTTP => "http",
339        RM_DSP => "dsp",
340        RM_SND => "snd",
341        RM_DLP => "dlp",
342        RM_HIO_LOW => "hio_low",
343        RM_CSND => "csnd",
344        RM_SSL => "ssl",
345        RM_AM_LOW => "am_low",
346        RM_NEX => "nex",
347        RM_FRIENDS => "friends",
348        RM_RDT => "rdt",
349        RM_APPLET => "applet",
350        RM_NIM => "nim",
351        RM_PTM => "ptm",
352        RM_MIDI => "midi",
353        RM_MC => "mc",
354        RM_SWC => "swc",
355        RM_FATFS => "fatfs",
356        RM_NGC => "ngc",
357        RM_CARD => "card",
358        RM_CARDNOR => "cardnor",
359        RM_SDMC => "sdmc",
360        RM_BOSS => "boss",
361        RM_DBM => "dbm",
362        RM_CONFIG => "config",
363        RM_PS => "ps",
364        RM_CEC => "cec",
365        RM_IR => "ir",
366        RM_UDS => "uds",
367        RM_PL => "pl",
368        RM_CUP => "cup",
369        RM_GYROSCOPE => "gyroscope",
370        RM_MCU => "mcu",
371        RM_NS => "ns",
372        RM_NEWS => "news",
373        RM_RO => "ro",
374        RM_GD => "gd",
375        RM_CARD_SPI => "card_spi",
376        RM_EC => "ec",
377        RM_WEB_BROWSER => "web_browser",
378        RM_TEST => "test",
379        RM_ENC => "enc",
380        RM_PIA => "pia",
381        RM_ACT => "act",
382        RM_VCTL => "vctl",
383        RM_OLV => "olv",
384        RM_NEIA => "neia",
385        RM_NPNS => "npns",
386        RM_AVD => "avd",
387        RM_L2B => "l2b",
388        RM_MVD => "mvd",
389        RM_NFC => "nfc",
390        RM_UART => "uart",
391        RM_SPM => "spm",
392        RM_QTM => "qtm",
393        RM_NFP => "nfp",
394        RM_APPLICATION => "application",
395        RM_INVALIDRESVAL => "invalid_res_val",
396        code => return Cow::Owned(format!("(unknown module: {code:#x})")),
397    })
398}