Skip to main content

mountpoint_s3_fs/metablock/
error.rs

1use anyhow::anyhow;
2use mountpoint_s3_client::error_metadata::ProvideErrorMetadata;
3use std::ffi::OsString;
4use thiserror::Error;
5
6use crate::fs::error_metadata::{ErrorMetadata, MOUNTPOINT_ERROR_CLIENT};
7#[cfg(feature = "manifest")]
8use crate::manifest::ManifestError;
9use crate::metablock::S3Location;
10use crate::sync::Arc;
11use crate::upload::UploadError;
12
13use super::InodeNo;
14
15#[derive(Debug, Error, Clone)]
16pub enum InodeError {
17    /// Avoid constructing this directly, but use `InodeError::client_error` instead
18    #[error("error from ObjectClient")]
19    ClientError {
20        source: Arc<anyhow::Error>,
21        metadata: Box<ErrorMetadata>,
22    },
23    #[error("file {0:?} does not exist in parent inode {1}")]
24    FileDoesNotExist(String, InodeErrorInfo),
25    #[error("inode {0} does not exist")]
26    InodeDoesNotExist(InodeNo),
27    #[error("invalid file name {0:?}")]
28    InvalidFileName(OsString),
29    #[error("inode {0} is not a directory")]
30    NotADirectory(InodeErrorInfo),
31    #[error("inode {0} is a directory")]
32    IsDirectory(InodeErrorInfo),
33    #[error("file already exists at inode {0}")]
34    FileAlreadyExists(InodeErrorInfo),
35    #[error("inode {0} is not writable")]
36    InodeNotWritable(InodeErrorInfo),
37    #[error("Invalid state of inode {0} to be written. Aborting the write.")]
38    InodeInvalidWriteStatus(InodeErrorInfo),
39    #[error("inode {0} is already being written")]
40    InodeAlreadyWriting(InodeErrorInfo),
41    #[error("inode {0} is not readable while being written")]
42    InodeNotReadableWhileWriting(InodeErrorInfo),
43    #[error("inode {0} is not writable while being read")]
44    InodeNotWritableWhileReading(InodeErrorInfo),
45    #[error("remote directory cannot be removed at inode {0}")]
46    CannotRemoveRemoteDirectory(InodeErrorInfo),
47    #[error("non-empty directory cannot be removed at inode {0}")]
48    DirectoryNotEmpty(InodeErrorInfo),
49    #[error("inode {0} cannot be unlinked while being written")]
50    UnlinkNotPermittedWhileWriting(InodeErrorInfo),
51    #[error("inode {0} is a directory and cannot be renamed")]
52    CannotRenameDirectory(InodeErrorInfo),
53    #[error("inode {0} cannot be renamed while being written")]
54    RenameNotPermittedWhileWriting(InodeErrorInfo),
55    #[error("rename destination {dest_key:?} already exists, cannot rename inode {src_inode}")]
56    RenameDestinationExists {
57        dest_key: String,
58        src_inode: InodeErrorInfo,
59    },
60    #[error("rename is not supported on this bucket")]
61    RenameNotSupported(),
62    #[error("S3 key {0:?} was too long")]
63    NameTooLong(String),
64    #[error("corrupted metadata for inode {0}")]
65    CorruptedMetadata(InodeErrorInfo),
66    #[error("inode {0} is a remote inode and its attributes cannot be modified")]
67    SetAttrNotPermittedOnRemoteInode(InodeErrorInfo),
68    #[error("inode {old_inode} for remote key {remote_key:?} is stale, replaced by inode {new_inode}")]
69    StaleInode {
70        remote_key: String,
71        old_inode: InodeErrorInfo,
72        new_inode: InodeErrorInfo,
73    },
74    #[cfg(feature = "manifest")]
75    #[error("manifest error")]
76    ManifestError(#[source] Arc<ManifestError>),
77    #[error("operation not supported on virtual inode {ino}")]
78    OperationNotSupportedOnSyntheticInode { ino: InodeNo },
79    #[error("out-of-order readdir, expected offset {expected} but got {actual} on dir handle {fh}")]
80    OutOfOrderReadDir { expected: i64, actual: i64, fh: u64 },
81    #[error("invalid directory handle {fh}")]
82    NoSuchDirHandle { fh: u64 },
83    #[error("objects in flexible retrieval storage classes are not accessible")]
84    FlexibleRetrievalObjectNotAccessible(InodeErrorInfo),
85}
86
87impl InodeError {
88    /// Constructs InodeError::ClientError enriching metadata with error_code, bucket and key.
89    ///
90    /// Detailed information about an error is gathered in different frames of the call stack.
91    /// To make it manageable the idea is to enrich metadata with error_code, bucket and key
92    /// on the construction of mountpoint crate's errors, i.e. InodeError, PrefetchReadError
93    /// and UploadPutError.
94    pub fn client_error<E>(err: E, context: &'static str, bucket: &str, key: &str) -> Self
95    where
96        E: ProvideErrorMetadata + std::error::Error + Send + Sync + 'static,
97    {
98        let metadata = ErrorMetadata {
99            client_error_meta: err.meta(),
100            error_code: Some(MOUNTPOINT_ERROR_CLIENT.to_string()),
101            s3_bucket_name: Some(bucket.to_string()),
102            s3_object_key: Some(key.to_string()),
103        };
104        let metadata = Box::new(metadata);
105        InodeError::ClientError {
106            source: Arc::new(anyhow!(err).context(context)),
107            metadata,
108        }
109    }
110
111    pub fn upload_error<E>(err: UploadError<E>, key: S3Location) -> Self
112    where
113        E: ProvideErrorMetadata + std::error::Error + Send + Sync + 'static,
114    {
115        let metadata = ErrorMetadata {
116            client_error_meta: err.meta(),
117            error_code: Some(MOUNTPOINT_ERROR_CLIENT.to_string()),
118            s3_bucket_name: Some(key.bucket_name().to_string()),
119            s3_object_key: Some(key.full_key().to_string()),
120        };
121        let metadata = Box::new(metadata);
122        InodeError::ClientError {
123            source: Arc::new(anyhow!(err)),
124            metadata,
125        }
126    }
127}
128
129impl InodeError {
130    pub fn meta(&self) -> ErrorMetadata {
131        match self {
132            Self::ClientError { source: _, metadata } => (**metadata).clone(),
133            _ => Default::default(),
134        }
135    }
136}
137
138#[cfg(feature = "manifest")]
139impl From<ManifestError> for InodeError {
140    fn from(value: ManifestError) -> Self {
141        Self::ManifestError(Arc::new(value))
142    }
143}
144
145/// A wrapper that prints useful customer-facing error messages for inodes by including the object
146/// key and bucket rather than just the inode number.
147#[derive(Debug, Clone)]
148pub struct InodeErrorInfo {
149    pub ino: InodeNo,
150    pub key: Box<str>,
151    pub bucket: Option<Box<str>>,
152}
153
154impl std::fmt::Display for InodeErrorInfo {
155    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156        if let Some(bucket) = &self.bucket {
157            write!(f, "{} (bucket {:?}, full key {:?})", self.ino, bucket, self.key)
158        } else {
159            write!(f, "{} (partial key {:?})", self.ino, self.key)
160        }
161    }
162}