1#![cfg_attr(not(test), no_std)]
2#![doc = include_str!("../README.md")]
3
4#[cfg(all(axtest, feature = "axtest"))]
5extern crate alloc;
6
7use core::fmt;
8
9use strum::EnumCount;
10
11#[cfg(all(axtest, feature = "axtest"))]
12pub mod axtest;
14
15mod linux_errno {
16 include!(concat!(env!("OUT_DIR"), "/linux_errno.rs"));
17}
18
19pub use linux_errno::LinuxError;
20
21#[repr(i32)]
27#[non_exhaustive]
28#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, EnumCount)]
29pub enum AxErrorKind {
30 AddrInUse = 1,
32 AlreadyConnected,
34 AlreadyExists,
36 ArgumentListTooLong,
38 BadAddress,
40 BadFileDescriptor,
42 BadState,
44 BrokenPipe,
46 ConnectionRefused,
48 ConnectionReset,
50 CrossesDevices,
52 DirectoryNotEmpty,
54 FilesystemLoop,
57 IllegalBytes,
59 InProgress,
62 Interrupted,
64 InvalidData,
75 InvalidExecutable,
77 InvalidInput,
79 Io,
81 IsADirectory,
83 NameTooLong,
85 NoMemory,
87 NoSuchDevice,
89 NoSuchDeviceOrAddress,
93 NoSuchProcess,
95 NotADirectory,
97 NotASocket,
99 NotATty,
101 NotConnected,
103 NotFound,
105 OperationNotPermitted,
107 OperationNotSupported,
109 OutOfRange,
111 PermissionDenied,
113 ReadOnlyFilesystem,
115 ResourceBusy,
117 StorageFull,
119 TimedOut,
121 TooManyOpenFiles,
123 UnexpectedEof,
126 Unsupported,
128 WouldBlock,
131 DestAddrRequired,
134 MessageTooLong,
137 WriteZero,
140}
141
142impl AxErrorKind {
143 pub fn as_str(&self) -> &'static str {
145 use AxErrorKind::*;
146 match *self {
147 AddrInUse => "Address in use",
148 AlreadyConnected => "Already connected",
149 AlreadyExists => "Entity already exists",
150 ArgumentListTooLong => "Argument list too long",
151 BadAddress => "Bad address",
152 BadFileDescriptor => "Bad file descriptor",
153 BadState => "Bad internal state",
154 BrokenPipe => "Broken pipe",
155 ConnectionRefused => "Connection refused",
156 ConnectionReset => "Connection reset",
157 CrossesDevices => "Cross-device link or rename",
158 DirectoryNotEmpty => "Directory not empty",
159 FilesystemLoop => "Filesystem loop or indirection limit",
160 IllegalBytes => "Illegal byte sequence",
161 InProgress => "Operation in progress",
162 Interrupted => "Operation interrupted",
163 InvalidData => "Invalid data",
164 InvalidExecutable => "Invalid executable format",
165 InvalidInput => "Invalid input parameter",
166 Io => "I/O error",
167 IsADirectory => "Is a directory",
168 NameTooLong => "Filename too long",
169 NoMemory => "Out of memory",
170 NoSuchDevice => "No such device",
171 NoSuchDeviceOrAddress => "No such device or address",
172 NoSuchProcess => "No such process",
173 NotADirectory => "Not a directory",
174 NotASocket => "Not a socket",
175 NotATty => "Inappropriate ioctl for device",
176 NotConnected => "Not connected",
177 NotFound => "Entity not found",
178 OperationNotPermitted => "Operation not permitted",
179 OperationNotSupported => "Operation not supported",
180 OutOfRange => "Result out of range",
181 PermissionDenied => "Permission denied",
182 ReadOnlyFilesystem => "Read-only filesystem",
183 ResourceBusy => "Resource busy",
184 StorageFull => "No storage space",
185 TimedOut => "Timed out",
186 TooManyOpenFiles => "Too many open files",
187 UnexpectedEof => "Unexpected end of file",
188 Unsupported => "Operation not supported",
189 WouldBlock => "Operation would block",
190 DestAddrRequired => "Destination address required",
191 MessageTooLong => "Message too long",
192 WriteZero => "Write zero",
193 }
194 }
195
196 pub const fn code(self) -> i32 {
198 self as i32
199 }
200}
201
202impl TryFrom<i32> for AxErrorKind {
203 type Error = i32;
204
205 #[inline]
206 fn try_from(value: i32) -> Result<Self, Self::Error> {
207 if value > 0 && value <= AxErrorKind::COUNT as i32 {
208 Ok(unsafe { core::mem::transmute::<i32, AxErrorKind>(value) })
209 } else {
210 Err(value)
211 }
212 }
213}
214
215impl fmt::Display for AxErrorKind {
216 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
217 write!(f, "{}", self.as_str())
218 }
219}
220
221impl From<AxErrorKind> for LinuxError {
222 fn from(e: AxErrorKind) -> Self {
223 use AxErrorKind::*;
224 use LinuxError::*;
225 match e {
226 AddrInUse => EADDRINUSE,
227 AlreadyConnected => EISCONN,
228 AlreadyExists => EEXIST,
229 ArgumentListTooLong => E2BIG,
230 BadAddress | BadState => EFAULT,
231 BadFileDescriptor => EBADF,
232 BrokenPipe => EPIPE,
233 ConnectionRefused => ECONNREFUSED,
234 ConnectionReset => ECONNRESET,
235 CrossesDevices => EXDEV,
236 DirectoryNotEmpty => ENOTEMPTY,
237 FilesystemLoop => ELOOP,
238 IllegalBytes => EILSEQ,
239 InProgress => EINPROGRESS,
240 Interrupted => EINTR,
241 InvalidExecutable => ENOEXEC,
242 InvalidInput | InvalidData => EINVAL,
243 Io => EIO,
244 IsADirectory => EISDIR,
245 NameTooLong => ENAMETOOLONG,
246 NoMemory => ENOMEM,
247 NoSuchDevice => ENODEV,
248 NoSuchDeviceOrAddress => ENXIO,
249 NoSuchProcess => ESRCH,
250 NotADirectory => ENOTDIR,
251 NotASocket => ENOTSOCK,
252 NotATty => ENOTTY,
253 DestAddrRequired => EDESTADDRREQ,
254 MessageTooLong => EMSGSIZE,
255 NotConnected => ENOTCONN,
256 NotFound => ENOENT,
257 OperationNotPermitted => EPERM,
258 OperationNotSupported => EOPNOTSUPP,
259 OutOfRange => ERANGE,
260 PermissionDenied => EACCES,
261 ReadOnlyFilesystem => EROFS,
262 ResourceBusy => EBUSY,
263 StorageFull => ENOSPC,
264 TimedOut => ETIMEDOUT,
265 TooManyOpenFiles => EMFILE,
266 UnexpectedEof | WriteZero => EIO,
267 Unsupported => ENOSYS,
268 WouldBlock => EAGAIN,
269 }
270 }
271}
272
273impl TryFrom<LinuxError> for AxErrorKind {
274 type Error = LinuxError;
275
276 fn try_from(e: LinuxError) -> Result<Self, Self::Error> {
277 use AxErrorKind::*;
278 use LinuxError::*;
279 Ok(match e {
280 EADDRINUSE => AddrInUse,
281 EISCONN => AlreadyConnected,
282 EEXIST => AlreadyExists,
283 E2BIG => ArgumentListTooLong,
284 EFAULT => BadAddress,
285 EBADF => BadFileDescriptor,
286 EPIPE => BrokenPipe,
287 ECONNREFUSED => ConnectionRefused,
288 ECONNRESET => ConnectionReset,
289 EXDEV => CrossesDevices,
290 ENOTEMPTY => DirectoryNotEmpty,
291 ELOOP => FilesystemLoop,
292 EILSEQ => IllegalBytes,
293 EINPROGRESS => InProgress,
294 EINTR => Interrupted,
295 ENOEXEC => InvalidExecutable,
296 EINVAL => InvalidInput,
297 EIO => Io,
298 EISDIR => IsADirectory,
299 ENAMETOOLONG => NameTooLong,
300 ENOMEM => NoMemory,
301 ENODEV => NoSuchDevice,
302 ENXIO => NoSuchDeviceOrAddress,
303 ESRCH => NoSuchProcess,
304 ENOTDIR => NotADirectory,
305 ENOTSOCK => NotASocket,
306 ENOTTY => NotATty,
307 EDESTADDRREQ => DestAddrRequired,
308 EMSGSIZE => MessageTooLong,
309 ENOTCONN => NotConnected,
310 ENOENT => NotFound,
311 EPERM => OperationNotPermitted,
312 EOPNOTSUPP => OperationNotSupported,
313 ERANGE => OutOfRange,
314 EACCES => PermissionDenied,
315 EROFS => ReadOnlyFilesystem,
316 EBUSY => ResourceBusy,
317 ENOSPC => StorageFull,
318 ETIMEDOUT => TimedOut,
319 EMFILE => TooManyOpenFiles,
320 ENOSYS => Unsupported,
321 EAGAIN => WouldBlock,
322 _ => {
323 return Err(e);
324 }
325 })
326 }
327}
328
329#[repr(transparent)]
331#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
332pub struct AxError(i32);
333
334enum AxErrorData {
335 Ax(AxErrorKind),
336 Linux(LinuxError),
337}
338
339impl AxError {
340 const fn new_ax(kind: AxErrorKind) -> Self {
341 AxError(kind.code())
342 }
343
344 const fn new_linux(kind: LinuxError) -> Self {
345 AxError(-kind.code())
346 }
347
348 const fn data(&self) -> AxErrorData {
349 if self.0 < 0 {
350 AxErrorData::Linux(unsafe { core::mem::transmute::<i32, LinuxError>(-self.0) })
351 } else {
352 AxErrorData::Ax(unsafe { core::mem::transmute::<i32, AxErrorKind>(self.0) })
353 }
354 }
355
356 pub const fn code(self) -> i32 {
358 self.0
359 }
360
361 pub fn canonicalize(self) -> Self {
375 AxErrorKind::try_from(self).map_or_else(Into::into, Into::into)
376 }
377}
378
379impl<E: Into<AxErrorKind>> From<E> for AxError {
380 fn from(e: E) -> Self {
381 AxError::new_ax(e.into())
382 }
383}
384
385impl From<LinuxError> for AxError {
386 fn from(e: LinuxError) -> Self {
387 AxError::new_linux(e)
388 }
389}
390
391impl From<AxError> for LinuxError {
392 fn from(e: AxError) -> Self {
393 match e.data() {
394 AxErrorData::Ax(kind) => LinuxError::from(kind),
395 AxErrorData::Linux(kind) => kind,
396 }
397 }
398}
399
400impl TryFrom<AxError> for AxErrorKind {
401 type Error = LinuxError;
402
403 fn try_from(e: AxError) -> Result<Self, Self::Error> {
404 match e.data() {
405 AxErrorData::Ax(kind) => Ok(kind),
406 AxErrorData::Linux(e) => e.try_into(),
407 }
408 }
409}
410
411impl TryFrom<i32> for AxError {
412 type Error = i32;
413
414 fn try_from(value: i32) -> Result<Self, Self::Error> {
415 if AxErrorKind::try_from(value).is_ok() || LinuxError::try_from(-value).is_ok() {
416 Ok(AxError(value))
417 } else {
418 Err(value)
419 }
420 }
421}
422
423impl From<core::fmt::Error> for AxError {
424 fn from(_: core::fmt::Error) -> Self {
425 AxError::new_ax(AxErrorKind::InvalidInput)
426 }
427}
428
429impl fmt::Debug for AxError {
430 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
431 match self.data() {
432 AxErrorData::Ax(kind) => write!(f, "AxErrorKind::{:?}", kind),
433 AxErrorData::Linux(kind) => write!(f, "LinuxError::{:?}", kind),
434 }
435 }
436}
437
438impl fmt::Display for AxError {
439 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
440 match self.data() {
441 AxErrorData::Ax(kind) => write!(f, "{}", kind),
442 AxErrorData::Linux(kind) => write!(f, "{}", kind),
443 }
444 }
445}
446
447macro_rules! axerror_consts {
448 ($($name:ident),*) => {
449 #[allow(non_upper_case_globals)]
450 impl AxError {
451 $(
452 #[doc = concat!("An [`AxError`] with kind [`AxErrorKind::", stringify!($name), "`].")]
453 pub const $name: Self = Self::new_ax(AxErrorKind::$name);
454 )*
455 }
456 };
457}
458
459axerror_consts!(
460 AddrInUse,
461 AlreadyConnected,
462 AlreadyExists,
463 ArgumentListTooLong,
464 BadAddress,
465 BadFileDescriptor,
466 BadState,
467 BrokenPipe,
468 ConnectionRefused,
469 ConnectionReset,
470 CrossesDevices,
471 DirectoryNotEmpty,
472 FilesystemLoop,
473 IllegalBytes,
474 InProgress,
475 Interrupted,
476 InvalidData,
477 InvalidExecutable,
478 InvalidInput,
479 Io,
480 IsADirectory,
481 NameTooLong,
482 NoMemory,
483 NoSuchDevice,
484 NoSuchDeviceOrAddress,
485 NoSuchProcess,
486 NotADirectory,
487 NotASocket,
488 NotATty,
489 NotConnected,
490 NotFound,
491 OperationNotPermitted,
492 OperationNotSupported,
493 OutOfRange,
494 PermissionDenied,
495 ReadOnlyFilesystem,
496 ResourceBusy,
497 StorageFull,
498 TimedOut,
499 TooManyOpenFiles,
500 UnexpectedEof,
501 Unsupported,
502 WouldBlock,
503 DestAddrRequired,
504 MessageTooLong,
505 WriteZero
506);
507
508pub type AxResult<T = ()> = Result<T, AxError>;
510
511#[macro_export]
530macro_rules! ax_err_type {
531 ($err:ident) => {{
532 use $crate::AxErrorKind::*;
533 let err = $crate::AxError::from($err);
534 $crate::__priv::warn!("[{:?}]", err);
535 err
536 }};
537 ($err:ident, $msg:expr) => {{
538 use $crate::AxErrorKind::*;
539 let err = $crate::AxError::from($err);
540 $crate::__priv::warn!("[{:?}] {}", err, $msg);
541 err
542 }};
543}
544
545#[macro_export]
561macro_rules! ensure {
562 ($predicate:expr, $context_selector:expr $(,)?) => {
563 if !$predicate {
564 return $context_selector;
565 }
566 };
567}
568
569#[macro_export]
591macro_rules! ax_err {
592 ($err:ident) => {
593 Err($crate::ax_err_type!($err))
594 };
595 ($err:ident, $msg:expr) => {
596 Err($crate::ax_err_type!($err, $msg))
597 };
598}
599
600#[macro_export]
603macro_rules! ax_bail {
604 ($($t:tt)*) => {
605 return $crate::ax_err!($($t)*);
606 };
607}
608
609pub type LinuxResult<T = ()> = Result<T, LinuxError>;
611
612impl fmt::Display for LinuxError {
613 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
614 write!(f, "{}", self.as_str())
615 }
616}
617
618#[doc(hidden)]
619pub mod __priv {
620 pub use log::warn;
621}
622
623#[cfg(test)]
624mod tests {
625 use strum::EnumCount;
626
627 use crate::{AxError, AxErrorKind, LinuxError};
628
629 #[test]
630 fn test_try_from() {
631 let max_code = AxErrorKind::COUNT as i32;
632 assert_eq!(max_code, 46);
636 assert_eq!(max_code, AxError::WriteZero.code());
637
638 assert_eq!(AxError::AddrInUse.code(), 1);
639 assert_eq!(Ok(AxError::AddrInUse), AxError::try_from(1));
640 assert_eq!(Ok(AxError::AlreadyConnected), AxError::try_from(2));
641 assert_eq!(Ok(AxError::WriteZero), AxError::try_from(max_code));
642 assert_eq!(Err(max_code + 1), AxError::try_from(max_code + 1));
643 assert_eq!(Err(0), AxError::try_from(0));
644 assert_eq!(Err(i32::MAX), AxError::try_from(i32::MAX));
645 }
646
647 #[test]
648 fn test_conversion() {
649 for i in 1.. {
650 let Ok(err) = LinuxError::try_from(i) else {
651 break;
652 };
653 assert_eq!(err as i32, i);
654 let e = AxError::from(err);
655 assert_eq!(e.code(), -i);
656 assert_eq!(LinuxError::from(e), err);
657 }
658 }
659}