Skip to main content

fxcp_core/
error.rs

1// SPDX-License-Identifier: GPL-2.0-or-later
2// Copyright (C) 2025 Joel Wirāmu Pauling <aenertia@aenertia.net>
3//
4// fxcp-core/src/error.rs  --  Error types  --  FxcpError, CopyErrorKind, Result
5
6//! Error types for the fxcp-core copy engine.
7
8use thiserror::Error;
9
10/// Classification of copy operation errors for retry/fallback decisions
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum CopyErrorKind {
13    /// Source file no longer exists (transient lifecycle  --  skip event)
14    SourceNotFound,
15    /// Target file/parent doesn't exist (needs creation/repair)
16    TargetNotFound,
17    /// Operation exceeded deadline
18    Timeout,
19    /// Temporary issue (EAGAIN, interrupted, NFS hiccup)  --  retry with backoff
20    Transient,
21    /// Permanent failure (permission denied, read-only fs)  --  don't retry
22    Permanent,
23    /// NFS compound RPC transient error (stale handle, session expired)  --  retry with re-resolve
24    #[cfg(feature = "nfs-bypass")]
25    NfsTransient,
26    /// NFS bypass unavailable (version mismatch, auth failure)  --  permanent fallback to VFS
27    #[cfg(feature = "nfs-bypass")]
28    NfsBypassUnavailable,
29}
30
31/// Unified error type for all fxcp-core operations.
32#[derive(Error, Debug)]
33pub enum FxcpError {
34    #[error("Config: {0}")]
35    Config(String),
36    #[error("IO: {0}")]
37    Io(#[from] std::io::Error),
38    #[error("System: {0}")]
39    System(#[from] nix::Error),
40    #[error("Security: {0}")]
41    Security(String),
42    #[error("Encoding: {0}")]
43    Utf8(#[from] std::string::FromUtf8Error),
44    #[error("Join: {0}")]
45    Join(#[from] tokio::task::JoinError),
46    #[error("JSON: {0}")]
47    Json(#[from] serde_json::Error),
48    #[error("Versioning: {0}")]
49    Versioning(String),
50    #[error("CString Nul: {0}")]
51    Nul(#[from] std::ffi::NulError),
52    #[error("IOUring Push: {0}")]
53    IouPush(String),
54    #[error("Tracing Error ({correlation_id}): {message}")]
55    Traced {
56        correlation_id: u64,
57        message: String,
58    },
59    #[error("Memory Limit Exhausted: {0}")]
60    MemoryExhausted(String),
61    #[error("CAS Store: {0}")]
62    CasStoreError(String),
63    #[error("Manifest Corrupt: {0}")]
64    ManifestCorrupt(String),
65    #[error("Chunk Missing: {0}")]
66    ChunkMissing(String),
67    #[cfg(feature = "nfs-bypass")]
68    #[error("NFS: {0}")]
69    Nfs(#[from] crate::nfs::NfsError),
70}
71
72impl FxcpError {
73    /// Classify this error for retry/fallback decisions.
74    pub fn copy_error_kind(&self) -> CopyErrorKind {
75        match self {
76            FxcpError::Io(io_err) => match io_err.kind() {
77                std::io::ErrorKind::NotFound => CopyErrorKind::TargetNotFound,
78                std::io::ErrorKind::PermissionDenied => CopyErrorKind::Permanent,
79                std::io::ErrorKind::TimedOut => CopyErrorKind::Timeout,
80                std::io::ErrorKind::WouldBlock | std::io::ErrorKind::Interrupted => CopyErrorKind::Transient,
81                _ => CopyErrorKind::Transient,
82            },
83            FxcpError::Security(_) => CopyErrorKind::Permanent,
84            FxcpError::ManifestCorrupt(_) => CopyErrorKind::Permanent,
85            #[cfg(feature = "nfs-bypass")]
86            FxcpError::Nfs(nfs_err) => match nfs_err {
87                crate::nfs::NfsError::StaleHandle { .. } => CopyErrorKind::NfsTransient,
88                crate::nfs::NfsError::Nfs4Error { code, .. } => {
89                    use crate::nfs::rpc;
90                    match *code {
91                        rpc::NFS4ERR_STALE | rpc::NFS4ERR_DELAY | rpc::NFS4ERR_BADSESSION
92                        | rpc::NFS4ERR_BADSEQ | rpc::NFS4ERR_SEQ_MISORDERED => CopyErrorKind::NfsTransient,
93                        rpc::NFS4ERR_ACCESS => CopyErrorKind::Permanent,
94                        _ => CopyErrorKind::Transient,
95                    }
96                }
97                crate::nfs::NfsError::KerberosRequired => CopyErrorKind::NfsBypassUnavailable,
98                crate::nfs::NfsError::Timeout(_) => CopyErrorKind::Timeout,
99                _ => CopyErrorKind::Transient,
100            },
101            _ => CopyErrorKind::Transient,
102        }
103    }
104}
105
106impl From<io_uring::squeue::PushError> for FxcpError {
107    fn from(err: io_uring::squeue::PushError) -> Self {
108        FxcpError::IouPush(format!("{:?}", err))
109    }
110}
111
112/// Convenience alias for `std::result::Result<T, FxcpError>`.
113pub type Result<T> = std::result::Result<T, FxcpError>;