⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/
error.rs

1//! Comprehensive error types for VeridianOS kernel
2//!
3//! This module provides proper error types to replace string literals
4//! throughout the kernel, as recommended in DEEP-RECOMMENDATIONS.md.
5
6use core::fmt;
7
8/// Main kernel error type
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10#[must_use = "kernel errors must be handled, not silently discarded"]
11pub enum KernelError {
12    /// Memory-related errors
13    OutOfMemory {
14        requested: usize,
15        available: usize,
16    },
17    InvalidAddress {
18        addr: usize,
19    },
20    UnmappedMemory {
21        addr: usize,
22    },
23
24    /// Capability-related errors
25    InvalidCapability {
26        cap_id: u64,
27        reason: CapError,
28    },
29    InsufficientRights {
30        required: u16,
31        actual: u16,
32    },
33    CapabilityRevoked {
34        cap_id: u64,
35    },
36
37    /// Process-related errors
38    ProcessNotFound {
39        pid: u64,
40    },
41    ThreadNotFound {
42        tid: u64,
43    },
44    InvalidState {
45        expected: &'static str,
46        actual: &'static str,
47    },
48
49    /// IPC-related errors
50    IpcError(IpcError),
51
52    /// Scheduler-related errors
53    SchedulerError(SchedError),
54
55    /// System call errors
56    SyscallError(SyscallError),
57
58    /// Filesystem-related errors
59    FsError(FsError),
60
61    /// Hardware errors
62    HardwareError {
63        device: &'static str,
64        code: u32,
65    },
66
67    /// Generic errors
68    InvalidArgument {
69        name: &'static str,
70        value: &'static str,
71    },
72    OperationNotSupported {
73        operation: &'static str,
74    },
75    ResourceExhausted {
76        resource: &'static str,
77    },
78    PermissionDenied {
79        operation: &'static str,
80    },
81    AlreadyExists {
82        resource: &'static str,
83        id: u64,
84    },
85    NotFound {
86        resource: &'static str,
87        id: u64,
88    },
89    Timeout {
90        operation: &'static str,
91        duration_ms: u64,
92    },
93    NotImplemented {
94        feature: &'static str,
95    },
96    /// Operation would block
97    WouldBlock,
98    /// Broken pipe: write end closed or read end closed
99    BrokenPipe,
100    /// Subsystem not initialized (called before init())
101    NotInitialized {
102        subsystem: &'static str,
103    },
104    /// Legacy string error for gradual migration from &'static str patterns.
105    /// New code should use specific error variants instead.
106    LegacyError {
107        message: &'static str,
108    },
109}
110
111/// Capability-specific errors
112#[derive(Debug, Clone, Copy, PartialEq, Eq)]
113pub enum CapError {
114    InvalidCapability,
115    InsufficientRights,
116    CapabilityRevoked,
117    InvalidObject,
118    PermissionDenied,
119    AlreadyExists,
120    NotFound,
121    IdExhausted,
122}
123
124/// IPC-specific errors
125#[derive(Debug, Clone, Copy, PartialEq, Eq)]
126pub enum IpcError {
127    InvalidEndpoint { id: u64 },
128    InvalidChannel { id: u64 },
129    MessageTooLarge { size: usize, max: usize },
130    QueueFull { capacity: usize },
131    QueueEmpty,
132    InvalidCapability,
133    ProcessNotFound { pid: u64 },
134    EndpointNotFound { id: u64 },
135    PermissionDenied,
136    WouldBlock,
137    Timeout,
138}
139
140/// Scheduler-specific errors
141#[derive(Debug, Clone, Copy, PartialEq, Eq)]
142pub enum SchedError {
143    InvalidPriority { priority: u8 },
144    InvalidCpuId { cpu: usize },
145    TaskNotFound { id: u64 },
146    CpuOffline { cpu: usize },
147    InvalidAffinity,
148    QueueEmpty,
149    AlreadyScheduled,
150}
151
152/// System call errors
153#[derive(Debug, Clone, Copy, PartialEq, Eq)]
154pub enum SyscallError {
155    InvalidSyscall { nr: usize },
156    InvalidArgument { arg: usize },
157    InvalidPointer { addr: usize },
158    BufferTooSmall { required: usize, provided: usize },
159    StringTooLong { max: usize },
160    AccessDenied,
161    NotImplemented,
162}
163
164/// Filesystem-specific errors
165#[derive(Debug, Clone, Copy, PartialEq, Eq)]
166pub enum FsError {
167    /// File or directory not found
168    NotFound,
169    /// Path already exists
170    AlreadyExists,
171    /// Permission denied
172    PermissionDenied,
173    /// Target is not a directory
174    NotADirectory,
175    /// Target is not a file
176    NotAFile,
177    /// Target is a directory (when file expected)
178    IsADirectory,
179    /// Filesystem is read-only
180    ReadOnly,
181    /// Invalid path format
182    InvalidPath,
183    /// Root filesystem not mounted
184    NoRootFs,
185    /// Path already has a mount point
186    AlreadyMounted,
187    /// Path is not a mount point
188    NotMounted,
189    /// Unknown filesystem type
190    UnknownFsType,
191    /// I/O error during operation
192    IoError,
193    /// Directory is not empty
194    DirectoryNotEmpty,
195    /// File descriptor table is full
196    TooManyOpenFiles,
197    /// Invalid file descriptor
198    BadFileDescriptor,
199    /// Operation not supported on this node type
200    NotSupported,
201    /// Target is not a symbolic link
202    NotASymlink,
203    /// File size exceeds maximum supported limit
204    FileTooLarge,
205    /// On-disk data is corrupt or has invalid magic number
206    CorruptedData,
207    /// Too many levels of symbolic links (ELOOP)
208    SymlinkLoop,
209    /// No space left on device (ENOSPC)
210    NoSpace,
211}
212
213/// Result type alias for kernel operations
214pub type KernelResult<T> = Result<T, KernelError>;
215
216impl fmt::Display for KernelError {
217    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
218        match self {
219            Self::OutOfMemory {
220                requested,
221                available,
222            } => {
223                write!(
224                    f,
225                    "Out of memory: requested {} bytes, {} available",
226                    requested, available
227                )
228            }
229            Self::InvalidAddress { addr } => write!(f, "Invalid address: 0x{:x}", addr),
230            Self::UnmappedMemory { addr } => write!(f, "Unmapped memory at 0x{:x}", addr),
231            Self::InvalidCapability { cap_id, reason } => {
232                write!(f, "Invalid capability {}: {:?}", cap_id, reason)
233            }
234            Self::InsufficientRights { required, actual } => {
235                write!(
236                    f,
237                    "Insufficient rights: required 0x{:x}, have 0x{:x}",
238                    required, actual
239                )
240            }
241            Self::CapabilityRevoked { cap_id } => {
242                write!(f, "Capability {} has been revoked", cap_id)
243            }
244            Self::ProcessNotFound { pid } => write!(f, "Process {} not found", pid),
245            Self::ThreadNotFound { tid } => write!(f, "Thread {} not found", tid),
246            Self::InvalidState { expected, actual } => {
247                write!(f, "Invalid state: expected {}, got {}", expected, actual)
248            }
249            Self::IpcError(e) => write!(f, "IPC error: {:?}", e),
250            Self::SchedulerError(e) => write!(f, "Scheduler error: {:?}", e),
251            Self::SyscallError(e) => write!(f, "Syscall error: {:?}", e),
252            Self::HardwareError { device, code } => {
253                write!(f, "Hardware error on {}: code 0x{:x}", device, code)
254            }
255            Self::InvalidArgument { name, value } => {
256                write!(f, "Invalid argument '{}': {}", name, value)
257            }
258            Self::OperationNotSupported { operation } => {
259                write!(f, "Operation not supported: {}", operation)
260            }
261            Self::ResourceExhausted { resource } => write!(f, "Resource exhausted: {}", resource),
262            Self::PermissionDenied { operation } => {
263                write!(f, "Permission denied for operation: {}", operation)
264            }
265            Self::AlreadyExists { resource, id } => {
266                write!(f, "{} with id {} already exists", resource, id)
267            }
268            Self::NotFound { resource, id } => write!(f, "{} with id {} not found", resource, id),
269            Self::Timeout {
270                operation,
271                duration_ms,
272            } => {
273                write!(f, "Timeout during {}: {} ms", operation, duration_ms)
274            }
275            Self::NotImplemented { feature } => {
276                write!(f, "Feature not implemented: {}", feature)
277            }
278            Self::WouldBlock => write!(f, "Operation would block"),
279            Self::BrokenPipe => write!(f, "Broken pipe"),
280            Self::FsError(e) => write!(f, "Filesystem error: {:?}", e),
281            Self::NotInitialized { subsystem } => {
282                write!(f, "Subsystem not initialized: {}", subsystem)
283            }
284            Self::LegacyError { message } => write!(f, "{}", message),
285        }
286    }
287}
288
289// Conversion implementations
290impl From<CapError> for KernelError {
291    fn from(err: CapError) -> Self {
292        match err {
293            CapError::InvalidCapability => Self::InvalidCapability {
294                cap_id: 0,
295                reason: err,
296            },
297            CapError::InsufficientRights => Self::InsufficientRights {
298                required: 0,
299                actual: 0,
300            },
301            CapError::CapabilityRevoked => Self::CapabilityRevoked { cap_id: 0 },
302            CapError::IdExhausted => Self::ResourceExhausted {
303                resource: "capability IDs",
304            },
305            _ => Self::InvalidCapability {
306                cap_id: 0,
307                reason: err,
308            },
309        }
310    }
311}
312
313impl From<IpcError> for KernelError {
314    fn from(err: IpcError) -> Self {
315        Self::IpcError(err)
316    }
317}
318
319impl From<SchedError> for KernelError {
320    fn from(err: SchedError) -> Self {
321        Self::SchedulerError(err)
322    }
323}
324
325impl From<SyscallError> for KernelError {
326    fn from(err: SyscallError) -> Self {
327        Self::SyscallError(err)
328    }
329}
330
331impl From<FsError> for KernelError {
332    fn from(err: FsError) -> Self {
333        Self::FsError(err)
334    }
335}
336
337/// Conversion from legacy &'static str errors to KernelError.
338///
339/// This enables gradual migration: functions returning Result<T, &'static str>
340/// can be called with `?` from functions returning Result<T, KernelError>.
341/// New code should prefer specific error variants over this conversion.
342impl From<&'static str> for KernelError {
343    fn from(msg: &'static str) -> Self {
344        Self::LegacyError { message: msg }
345    }
346}
347
348// Helper macro for easy error creation
349#[macro_export]
350macro_rules! kernel_error {
351    (OutOfMemory { requested: $req:expr, available: $avail:expr }) => {
352        $crate::error::KernelError::OutOfMemory {
353            requested: $req,
354            available: $avail,
355        }
356    };
357    (ProcessNotFound { pid: $pid:expr }) => {
358        $crate::error::KernelError::ProcessNotFound { pid: $pid }
359    };
360    (InvalidArgument { $name:expr => $value:expr }) => {
361        $crate::error::KernelError::InvalidArgument {
362            name: $name,
363            value: $value,
364        }
365    };
366    ($variant:ident) => {
367        $crate::error::KernelError::$variant
368    };
369}
370
371#[cfg(test)]
372mod tests {
373    use alloc::string::ToString;
374
375    use super::*;
376
377    // --- KernelError equality and clone ---
378
379    #[test]
380    fn test_kernel_error_out_of_memory_equality() {
381        let e1 = KernelError::OutOfMemory {
382            requested: 4096,
383            available: 0,
384        };
385        let e2 = KernelError::OutOfMemory {
386            requested: 4096,
387            available: 0,
388        };
389        assert_eq!(e1, e2);
390    }
391
392    #[test]
393    fn test_kernel_error_out_of_memory_inequality() {
394        let e1 = KernelError::OutOfMemory {
395            requested: 4096,
396            available: 0,
397        };
398        let e2 = KernelError::OutOfMemory {
399            requested: 8192,
400            available: 0,
401        };
402        assert_ne!(e1, e2);
403    }
404
405    #[test]
406    fn test_kernel_error_clone() {
407        let e1 = KernelError::InvalidAddress { addr: 0xDEAD };
408        let e2 = e1;
409        assert_eq!(e1, e2);
410    }
411
412    // --- Display impls ---
413
414    #[test]
415    fn test_display_out_of_memory() {
416        let e = KernelError::OutOfMemory {
417            requested: 1024,
418            available: 512,
419        };
420        let s = e.to_string();
421        assert!(s.contains("1024"));
422        assert!(s.contains("512"));
423        assert!(s.contains("Out of memory"));
424    }
425
426    #[test]
427    fn test_display_invalid_address() {
428        let e = KernelError::InvalidAddress { addr: 0xCAFE };
429        let s = e.to_string();
430        assert!(s.contains("cafe"));
431        assert!(s.contains("Invalid address"));
432    }
433
434    #[test]
435    fn test_display_process_not_found() {
436        let e = KernelError::ProcessNotFound { pid: 42 };
437        let s = e.to_string();
438        assert!(s.contains("42"));
439        assert!(s.contains("not found"));
440    }
441
442    #[test]
443    fn test_display_would_block() {
444        let e = KernelError::WouldBlock;
445        assert_eq!(e.to_string(), "Operation would block");
446    }
447
448    #[test]
449    fn test_display_broken_pipe() {
450        let e = KernelError::BrokenPipe;
451        assert_eq!(e.to_string(), "Broken pipe");
452    }
453
454    #[test]
455    fn test_display_legacy_error() {
456        let e = KernelError::LegacyError {
457            message: "old error",
458        };
459        assert_eq!(e.to_string(), "old error");
460    }
461
462    #[test]
463    fn test_display_timeout() {
464        let e = KernelError::Timeout {
465            operation: "read",
466            duration_ms: 5000,
467        };
468        let s = e.to_string();
469        assert!(s.contains("read"));
470        assert!(s.contains("5000"));
471    }
472
473    // --- From conversions ---
474
475    #[test]
476    fn test_from_ipc_error() {
477        let ipc = IpcError::QueueEmpty;
478        let ke: KernelError = ipc.into();
479        assert_eq!(ke, KernelError::IpcError(IpcError::QueueEmpty));
480    }
481
482    #[test]
483    fn test_from_sched_error() {
484        let se = SchedError::QueueEmpty;
485        let ke: KernelError = se.into();
486        assert_eq!(ke, KernelError::SchedulerError(SchedError::QueueEmpty));
487    }
488
489    #[test]
490    fn test_from_syscall_error() {
491        let se = SyscallError::AccessDenied;
492        let ke: KernelError = se.into();
493        assert_eq!(ke, KernelError::SyscallError(SyscallError::AccessDenied));
494    }
495
496    #[test]
497    fn test_from_fs_error() {
498        let fe = FsError::NotFound;
499        let ke: KernelError = fe.into();
500        assert_eq!(ke, KernelError::FsError(FsError::NotFound));
501    }
502
503    #[test]
504    fn test_from_static_str() {
505        let ke: KernelError = "something broke".into();
506        assert_eq!(
507            ke,
508            KernelError::LegacyError {
509                message: "something broke"
510            }
511        );
512    }
513
514    #[test]
515    fn test_from_cap_error_invalid() {
516        let ce = CapError::InvalidCapability;
517        let ke: KernelError = ce.into();
518        match ke {
519            KernelError::InvalidCapability { cap_id, reason } => {
520                assert_eq!(cap_id, 0);
521                assert_eq!(reason, CapError::InvalidCapability);
522            }
523            _ => panic!("Expected InvalidCapability variant"),
524        }
525    }
526
527    #[test]
528    fn test_from_cap_error_revoked() {
529        let ce = CapError::CapabilityRevoked;
530        let ke: KernelError = ce.into();
531        assert_eq!(ke, KernelError::CapabilityRevoked { cap_id: 0 });
532    }
533
534    #[test]
535    fn test_from_cap_error_id_exhausted() {
536        let ce = CapError::IdExhausted;
537        let ke: KernelError = ce.into();
538        assert_eq!(
539            ke,
540            KernelError::ResourceExhausted {
541                resource: "capability IDs"
542            }
543        );
544    }
545
546    // --- Sub-error type equality ---
547
548    #[test]
549    fn test_ipc_error_variants() {
550        assert_ne!(IpcError::QueueEmpty, IpcError::WouldBlock);
551        assert_eq!(IpcError::Timeout, IpcError::Timeout);
552        let e = IpcError::MessageTooLarge { size: 100, max: 64 };
553        assert_eq!(e, IpcError::MessageTooLarge { size: 100, max: 64 });
554    }
555
556    #[test]
557    fn test_fs_error_variants() {
558        assert_ne!(FsError::NotFound, FsError::AlreadyExists);
559        assert_eq!(FsError::IoError, FsError::IoError);
560        assert_ne!(FsError::IsADirectory, FsError::NotADirectory);
561    }
562}