Skip to main content

drmkit_core/
error.rs

1// SPDX-FileCopyrightText: (c) 2026 Joel Winarske
2// SPDX-License-Identifier: MIT
3
4//! Error type for `drmkit-core`.
5
6use std::io;
7
8use rustix::io::Errno;
9
10/// Errors from device, property, and atomic-request operations.
11///
12/// The variants carry the same discriminants the C++ `std::error_code` paths
13/// document, so a consumer bridging both implementations classifies failures
14/// identically. `EACCES` in particular stays distinguishable: the scene's
15/// suspend-on-`EACCES` rule (invariant 2) depends on telling it apart from
16/// every other commit failure, and it must never be flattened into a generic
17/// I/O error.
18#[derive(Debug, thiserror::Error)]
19#[non_exhaustive]
20pub enum CoreError {
21    /// The path opened, but the kernel does not report it as a DRM device.
22    /// `errc::no_such_device`.
23    #[error("`{path}` is not a DRM device")]
24    NotADrmDevice {
25        /// The path that was opened.
26        path: String,
27    },
28
29    /// No properties are cached for the object.
30    /// `errc::no_such_file_or_directory`.
31    #[error("no properties cached for object {object_id}")]
32    NoSuchObject {
33        /// The DRM object id.
34        object_id: u32,
35    },
36
37    /// The object has no property by that name.
38    /// `errc::no_such_file_or_directory`.
39    #[error("object {object_id} has no property `{name}`")]
40    NoSuchProperty {
41        /// The DRM object id.
42        object_id: u32,
43        /// The property name that was looked up.
44        name: String,
45    },
46
47    /// The operation needs a live descriptor and the handle has none.
48    /// `errc::bad_file_descriptor`. Also what an I/O error from the `drm` crate
49    /// becomes when it carries no `errno`.
50    #[error("device holds no descriptor")]
51    NoDevice,
52
53    /// The commit was rejected because the caller is not DRM master.
54    ///
55    /// Split out from [`CoreError::Io`] deliberately: a scene suspends on this
56    /// and on nothing else (invariant 2), so burying it in a generic errno
57    /// would break the contract silently.
58    #[error("not DRM master")]
59    NotMaster,
60
61    /// An underlying syscall failed.
62    #[error("DRM operation failed: {0}")]
63    Io(#[from] Errno),
64}
65
66impl CoreError {
67    /// Classify an `errno` from a commit or ioctl, promoting `EACCES` to
68    /// [`CoreError::NotMaster`].
69    pub(crate) fn from_errno(errno: Errno) -> Self {
70        if errno == Errno::ACCESS {
71            Self::NotMaster
72        } else {
73            Self::Io(errno)
74        }
75    }
76
77    /// Classify an [`io::Error`] from the `drm` crate, which returns
78    /// `io::Result` throughout.
79    pub(crate) fn from_io(error: &io::Error) -> Self {
80        Errno::from_io_error(error).map_or(Self::NoDevice, Self::from_errno)
81    }
82
83    /// The underlying `errno`, when there is one.
84    #[must_use]
85    pub const fn errno(&self) -> Option<Errno> {
86        match self {
87            Self::Io(errno) => Some(*errno),
88            Self::NotMaster => Some(Errno::ACCESS),
89            _ => None,
90        }
91    }
92}
93
94impl From<CoreError> for io::Error {
95    fn from(e: CoreError) -> Self {
96        e.errno().map_or_else(
97            || Self::from(io::ErrorKind::NotFound),
98            |errno| Self::from_raw_os_error(errno.raw_os_error()),
99        )
100    }
101}
102
103/// Convenience alias.
104pub type Result<T> = std::result::Result<T, CoreError>;