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`.
49    #[error("device holds no descriptor")]
50    NoDevice,
51
52    /// The commit was rejected because the caller is not DRM master.
53    ///
54    /// Split out from [`CoreError::Io`] deliberately: a scene suspends on this
55    /// and on nothing else (invariant 2), so burying it in a generic errno
56    /// would break the contract silently.
57    #[error("not DRM master")]
58    NotMaster,
59
60    /// An underlying syscall failed.
61    #[error("DRM operation failed: {0}")]
62    Io(#[from] Errno),
63}
64
65impl CoreError {
66    /// Classify an `errno` from a commit or ioctl, promoting `EACCES` to
67    /// [`CoreError::NotMaster`].
68    pub(crate) fn from_errno(errno: Errno) -> Self {
69        if errno == Errno::ACCESS {
70            Self::NotMaster
71        } else {
72            Self::Io(errno)
73        }
74    }
75
76    /// Classify an [`io::Error`] from the `drm` crate, which returns
77    /// `io::Result` throughout.
78    pub(crate) fn from_io(error: &io::Error) -> Self {
79        Errno::from_io_error(error).map_or(Self::NoDevice, Self::from_errno)
80    }
81
82    /// The underlying `errno`, when there is one.
83    #[must_use]
84    pub const fn errno(&self) -> Option<Errno> {
85        match self {
86            Self::Io(errno) => Some(*errno),
87            Self::NotMaster => Some(Errno::ACCESS),
88            _ => None,
89        }
90    }
91}
92
93impl From<CoreError> for io::Error {
94    fn from(e: CoreError) -> Self {
95        e.errno().map_or_else(
96            || Self::from(io::ErrorKind::NotFound),
97            |errno| Self::from_raw_os_error(errno.raw_os_error()),
98        )
99    }
100}
101
102/// Convenience alias.
103pub type Result<T> = std::result::Result<T, CoreError>;