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