aster_forge_cloud_files_linux/
error.rs

1//! Linux adapter errors and their portable errno classifications.
2
3use aster_forge_cloud_files_core::{
4    CloudBackendError, CloudBackendErrorKind, CloudFilesStoreError, CloudFilesStoreErrorKind,
5};
6
7/// Result returned by the Linux cloud-files adapter.
8pub type Result<T> = std::result::Result<T, LinuxCloudFilesError>;
9
10/// Portable errno classification used before the native FUSE boundary.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum LinuxErrorCode {
13    /// No matching inode, handle, item, or directory entry exists.
14    NotFound,
15    /// A namespace create collided with an existing parent/name entry.
16    AlreadyExists,
17    /// A directory removal was rejected because children still exist.
18    DirectoryNotEmpty,
19    /// A regular-file operation resolved a directory.
20    IsDirectory,
21    /// A directory operation resolved another item kind.
22    NotDirectory,
23    /// The caller lacks access or must authenticate before access can proceed.
24    AccessDenied,
25    /// The read-only adapter rejected a mutation attempt.
26    ReadOnlyFilesystem,
27    /// The bounded dispatcher is temporarily saturated.
28    TryAgain,
29    /// A file handle or revision snapshot is stale.
30    Stale,
31    /// The requested operation is not implemented by the active backend or adapter.
32    NotSupported,
33    /// The request shape is invalid at the native boundary.
34    InvalidArgument,
35    /// An internal, contract, or transport failure occurred.
36    Io,
37}
38
39/// Errors produced by Linux inode, handle, and read-only FUSE mechanics.
40#[derive(Debug, thiserror::Error)]
41pub enum LinuxCloudFilesError {
42    /// Inode zero is reserved by the FUSE protocol.
43    #[error("linux inode must be non-zero")]
44    ZeroInode,
45    /// Generation zero is rejected so stale inode records cannot silently bypass fencing.
46    #[error("linux inode generation must be non-zero")]
47    ZeroGeneration,
48    /// The root record did not use inode one.
49    #[error("linux root inode must be one")]
50    RootInodeMismatch,
51    /// A supplied record did not belong to the active root scope.
52    #[error("linux inode record belongs to another cloud scope")]
53    ScopeMismatch,
54    /// Two persisted records attempted to reuse one inode.
55    #[error("duplicate linux inode {inode}")]
56    DuplicateInode {
57        /// Conflicting inode number.
58        inode: u64,
59    },
60    /// Two persisted records attempted to map one stable key to different inode records.
61    #[error("duplicate linux inode record for cloud item")]
62    DuplicateItem,
63    /// A backend item did not have a restored inode mapping.
64    #[error("missing restored linux inode record for cloud item")]
65    MissingInodeRecord,
66    /// An inode was not known to the active mount session.
67    #[error("unknown linux inode {inode}")]
68    UnknownInode {
69        /// Unknown inode number.
70        inode: u64,
71    },
72    /// An open file or directory handle no longer belongs to the requested inode.
73    #[error("stale linux file handle")]
74    StaleHandle,
75    /// A file-handle operation did not match the access mode selected at open time.
76    #[error("linux file handle access mode does not permit this operation")]
77    AccessModeMismatch,
78    /// In-memory handle allocation exhausted the native handle domain.
79    #[error("linux file handle space is exhausted")]
80    HandleExhausted,
81    /// A mount or attribute policy used an invalid value.
82    #[error("invalid linux cloud-files configuration: {reason}")]
83    InvalidConfiguration {
84        /// Stable validation reason.
85        reason: &'static str,
86    },
87    /// A file name cannot be represented by this string-based backend contract.
88    #[error("invalid linux filename: {reason}")]
89    InvalidName {
90        /// Stable validation reason.
91        reason: &'static str,
92    },
93    /// An operation required a directory but received a file.
94    #[error("cloud item is not a directory")]
95    NotDirectory,
96    /// An operation required a regular file but received a directory.
97    #[error("cloud item is not a regular file")]
98    NotFile,
99    /// A product backend returned metadata that violated its scoped item contract.
100    #[error("invalid cloud backend response: {reason}")]
101    InvalidBackendResponse {
102        /// Stable validation reason.
103        reason: &'static str,
104    },
105    /// The product-owned backend reported a classified failure.
106    #[error(transparent)]
107    Backend(#[from] CloudBackendError),
108    /// The product-owned durable writeback store reported a classified failure.
109    #[error(transparent)]
110    WritebackStore(#[from] CloudFilesStoreError),
111    /// The product-owned durable namespace store reported a classified failure.
112    #[error(transparent)]
113    NamespaceStore(#[from] crate::LinuxNamespaceMutationStoreError),
114    /// Native create was requested without a product namespace transaction port.
115    #[error("linux namespace mutation store is not configured")]
116    NamespaceMutationNotConfigured,
117}
118
119impl LinuxCloudFilesError {
120    /// Returns the portable errno classification expected by the FUSE adapter.
121    #[must_use]
122    pub const fn error_code(&self) -> LinuxErrorCode {
123        match self {
124            Self::UnknownInode { .. } => LinuxErrorCode::NotFound,
125            Self::StaleHandle => LinuxErrorCode::Stale,
126            Self::AccessModeMismatch => LinuxErrorCode::AccessDenied,
127            Self::InvalidName { .. } => LinuxErrorCode::InvalidArgument,
128            Self::NotDirectory => LinuxErrorCode::NotDirectory,
129            Self::NotFile => LinuxErrorCode::IsDirectory,
130            Self::Backend(error) => backend_error_code(error.kind()),
131            Self::WritebackStore(error) => writeback_store_error_code(error.kind()),
132            Self::NamespaceStore(error) => namespace_store_error_code(error.kind()),
133            Self::NamespaceMutationNotConfigured => LinuxErrorCode::NotSupported,
134            Self::ZeroInode
135            | Self::ZeroGeneration
136            | Self::RootInodeMismatch
137            | Self::ScopeMismatch
138            | Self::DuplicateInode { .. }
139            | Self::DuplicateItem
140            | Self::MissingInodeRecord
141            | Self::HandleExhausted
142            | Self::InvalidConfiguration { .. }
143            | Self::InvalidBackendResponse { .. } => LinuxErrorCode::Io,
144        }
145    }
146}
147
148/// Maps durable Linux namespace failures to FUSE-visible classifications.
149#[must_use]
150pub const fn namespace_store_error_code(
151    kind: crate::LinuxNamespaceMutationStoreErrorKind,
152) -> LinuxErrorCode {
153    match kind {
154        crate::LinuxNamespaceMutationStoreErrorKind::NotFound => LinuxErrorCode::NotFound,
155        crate::LinuxNamespaceMutationStoreErrorKind::AlreadyExists => LinuxErrorCode::AlreadyExists,
156        crate::LinuxNamespaceMutationStoreErrorKind::DirectoryNotEmpty => {
157            LinuxErrorCode::DirectoryNotEmpty
158        }
159        crate::LinuxNamespaceMutationStoreErrorKind::IsDirectory => LinuxErrorCode::IsDirectory,
160        crate::LinuxNamespaceMutationStoreErrorKind::NotDirectory => LinuxErrorCode::NotDirectory,
161        crate::LinuxNamespaceMutationStoreErrorKind::Unsupported => LinuxErrorCode::NotSupported,
162        crate::LinuxNamespaceMutationStoreErrorKind::Fenced => LinuxErrorCode::Stale,
163        crate::LinuxNamespaceMutationStoreErrorKind::Conflict
164        | crate::LinuxNamespaceMutationStoreErrorKind::PersistenceFailure => LinuxErrorCode::Io,
165    }
166}
167
168/// Maps durable local-write failures to FUSE-visible classifications.
169#[must_use]
170pub const fn writeback_store_error_code(kind: CloudFilesStoreErrorKind) -> LinuxErrorCode {
171    match kind {
172        CloudFilesStoreErrorKind::NotFound => LinuxErrorCode::NotFound,
173        CloudFilesStoreErrorKind::Conflict => LinuxErrorCode::Stale,
174        CloudFilesStoreErrorKind::InvalidTransition
175        | CloudFilesStoreErrorKind::PersistenceFailure => LinuxErrorCode::Io,
176    }
177}
178
179/// Maps product-neutral backend failures to FUSE-visible classifications.
180#[must_use]
181pub const fn backend_error_code(kind: CloudBackendErrorKind) -> LinuxErrorCode {
182    match kind {
183        CloudBackendErrorKind::NotFound => LinuxErrorCode::NotFound,
184        CloudBackendErrorKind::AuthenticationRequired | CloudBackendErrorKind::PermissionDenied => {
185            LinuxErrorCode::AccessDenied
186        }
187        CloudBackendErrorKind::Conflict | CloudBackendErrorKind::PreconditionFailed => {
188            LinuxErrorCode::Stale
189        }
190        CloudBackendErrorKind::InvalidRequest => LinuxErrorCode::InvalidArgument,
191        CloudBackendErrorKind::RateLimited | CloudBackendErrorKind::TemporarilyUnavailable => {
192            LinuxErrorCode::TryAgain
193        }
194        CloudBackendErrorKind::Unsupported => LinuxErrorCode::NotSupported,
195        CloudBackendErrorKind::InvalidResponse | CloudBackendErrorKind::Internal => {
196            LinuxErrorCode::Io
197        }
198    }
199}