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>;