Skip to main content

mountpoint_s3_fs/fs/
error.rs

1//! Utilities for handling errors generated by the `fs` module and mapping them to FUSE errors
2
3use mountpoint_s3_client::error::{GetObjectError, ObjectClientError};
4use tracing::Level;
5
6use crate::fs::error_metadata::ErrorMetadata;
7use crate::metablock::InodeError;
8use crate::prefetch::PrefetchReadError;
9use crate::upload::UploadError;
10
11/// Generate an error that includes a conversion to a libc errno for use in replies to FUSE.
12///
13/// `mountpoint-s3` is an application, so we'd be happy to just use the [anyhow] crate directly,
14/// except that we need to be able to convert every error into a C integer for use as an errno to
15/// give FUSE the right reply. This macro builds a little wrapper around an [anyhow::Error] that
16/// includes an errno. We also want to preserve the source information for errors whenever possible,
17/// so we optionally allow providing a `source:` argument that will chain an error together with
18/// this new one.
19///
20/// # Examples
21///
22/// If you already have an error, provide it as the source so that printed versions of the error
23/// include its source. For example:
24///
25/// ```ignore
26/// let err = client.head_object("amzn-s3-demo-bucket", "mykey").await.expect_err("failed");
27/// return Err(err!(libc::ENOENT, source:err, "file does not exist"));
28/// ```
29/// will print "file does not exist: service error: ...".
30///
31///
32/// Otherwise, build a new error with no source:
33///
34/// ```ignore
35/// return Err(err!(libc::EINVAL, "cannot use O_SYNC on file handle {:?}", fh));
36/// ```
37#[macro_export]
38macro_rules! err {
39    // Base case -- don't use directly
40    ($errno:expr, __source:$source:expr, $level:expr, $metadata:expr, $message:literal, $($args:tt)*) => {
41        Error {
42            errno: $errno,
43            message: format!($message, $($args)*),
44            source: $source,
45            level: $level,
46            metadata: $metadata,
47        }
48    };
49    // Actual cases
50    ($errno:expr, source:$source:expr, $level:expr, metadata:$metadata:expr, $message:literal) => {
51        err!($errno, __source:Some(::anyhow::Error::new($source)), $level, $metadata, $message, )
52    };
53    // For the following cases we construct an error with empty metadata
54    ($errno:expr, source:$source:expr, $level:expr, $message:literal, $($args:tt)*) => {
55        err!($errno, __source:Some(::anyhow::Error::new($source)), $level, Default::default(), $message, $($args)*)
56    };
57    ($errno:expr, source:$source:expr, $level:expr, $message:literal) => {
58        err!($errno, __source:Some(::anyhow::Error::new($source)), $level, Default::default(), $message,)
59    };
60    ($errno:expr, source:$source:expr, $message:literal, $($args:tt)*) => {
61        err!($errno, __source:Some(::anyhow::Error::new($source)), ::tracing::Level::WARN, Default::default(), $message, $($args)*)
62    };
63    ($errno:expr, source:$source:expr, $message:literal) => {
64        err!($errno, __source:Some(::anyhow::Error::new($source)), ::tracing::Level::WARN, Default::default(), $message,)
65    };
66    ($errno:expr, $message:literal, $($args:tt)*) => {
67        err!($errno, __source:None, ::tracing::Level::WARN, Default::default(), $message, $($args)*)
68    };
69    ($errno:expr, $message:literal) => {
70        err!($errno, __source:None, ::tracing::Level::WARN, Default::default(), $message,)
71    };
72}
73
74/// A dynamic error type returned by the Mountpoint filesystem. See the [err!] macro for more
75/// details.
76#[derive(Debug, thiserror::Error)]
77pub struct Error {
78    pub(crate) errno: libc::c_int,
79    pub(crate) message: String,
80    pub(crate) source: Option<anyhow::Error>,
81    pub(crate) level: Level,
82    pub(crate) metadata: ErrorMetadata,
83}
84
85impl std::fmt::Display for Error {
86    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87        if let Some(source) = self.source.as_ref() {
88            // {:#} tells anyhow to include the entire chain of sources for the error
89            write!(f, "{}: {:#}", self.message, source)
90        } else {
91            write!(f, "{}", self.message)
92        }
93    }
94}
95
96impl From<InodeError> for Error {
97    fn from(err: InodeError) -> Self {
98        let errno = err.to_errno();
99        let metadata = err.meta().clone();
100        Error {
101            errno,
102            message: String::from("inode error"),
103            source: Some(anyhow::anyhow!(err)),
104            // We are having WARN as the default level of logging for fuse errors
105            level: Level::WARN,
106            metadata,
107        }
108    }
109}
110
111impl<E: std::error::Error + Send + Sync + 'static> From<UploadError<E>> for Error {
112    fn from(err: UploadError<E>) -> Self {
113        let errno = err.to_errno();
114        Error {
115            errno,
116            message: String::from("upload error"),
117            source: Some(anyhow::anyhow!(err)),
118            // We are having WARN as the default level of logging for fuse errors
119            level: Level::WARN,
120            metadata: Default::default(), // TODO (vlaad): must be cloned from UploadError
121        }
122    }
123}
124
125impl<E: std::error::Error + Send + Sync + 'static> From<PrefetchReadError<E>> for Error {
126    fn from(err: PrefetchReadError<E>) -> Self {
127        match err {
128            PrefetchReadError::GetRequestFailed {
129                source: ObjectClientError::ServiceError(GetObjectError::PreconditionFailed(_)),
130                metadata,
131            } => err!(libc::ESTALE, __source:None, Level::WARN, (*metadata).clone(), "object was mutated remotely",),
132            PrefetchReadError::GetRequestFailed { source, metadata } => {
133                err!(libc::EIO, source:source, Level::WARN, metadata:(*metadata).clone(), "get request failed")
134            }
135            PrefetchReadError::Integrity(e) => err!(libc::EIO, source:e, "integrity error"),
136            PrefetchReadError::PartReadFailed(e) => err!(libc::EIO, source:e, "part read failed"),
137            PrefetchReadError::GetRequestTerminatedUnexpectedly
138            | PrefetchReadError::GetRequestReturnedWrongOffset { .. }
139            | PrefetchReadError::BackpressurePreconditionFailed
140            | PrefetchReadError::ReadWindowIncrement => {
141                err!(libc::EIO, source:err, "get request failed")
142            }
143        }
144    }
145}
146
147/// Errors that can be converted to a raw OS error (errno)
148pub trait ToErrno {
149    fn to_errno(&self) -> libc::c_int;
150}
151
152impl ToErrno for Error {
153    fn to_errno(&self) -> libc::c_int {
154        self.errno
155    }
156}
157
158impl ToErrno for InodeError {
159    fn to_errno(&self) -> libc::c_int {
160        match self {
161            InodeError::ClientError { .. } => libc::EIO,
162            InodeError::FileDoesNotExist(_, _) => libc::ENOENT,
163            InodeError::InodeDoesNotExist(_) => libc::ENOENT,
164            InodeError::InvalidFileName(_) => libc::EINVAL,
165            InodeError::NotADirectory(_) => libc::ENOTDIR,
166            InodeError::IsDirectory(_) => libc::EISDIR,
167            InodeError::FileAlreadyExists(_) => libc::EEXIST,
168            // Not obvious what InodeNotWritable, InodeAlreadyWriting, InodeNotReadableWhileWriting should be.
169            // EINVAL or EROFS would also be reasonable -- but we'll treat them like sealed files.
170            InodeError::InodeNotWritable(_) => libc::EPERM,
171            InodeError::InodeInvalidWriteStatus(_) => libc::EPERM,
172            InodeError::InodeAlreadyWriting(_) => libc::EPERM,
173            InodeError::InodeNotReadableWhileWriting(_) => libc::EPERM,
174            InodeError::InodeNotWritableWhileReading(_) => libc::EPERM,
175            InodeError::CannotRemoveRemoteDirectory(_) => libc::EPERM,
176            InodeError::DirectoryNotEmpty(_) => libc::ENOTEMPTY,
177            InodeError::UnlinkNotPermittedWhileWriting(_) => libc::EPERM,
178            InodeError::CorruptedMetadata(_) => libc::EIO,
179            InodeError::SetAttrNotPermittedOnRemoteInode(_) => libc::EPERM,
180            InodeError::StaleInode { .. } => libc::ESTALE,
181            InodeError::CannotRenameDirectory(_) => libc::EPERM,
182            InodeError::RenameDestinationExists { .. } => libc::EEXIST,
183            InodeError::RenameNotPermittedWhileWriting(_) => libc::EPERM,
184            InodeError::RenameNotSupported() => libc::ENOSYS,
185            InodeError::NameTooLong(_) => libc::ENAMETOOLONG,
186            #[cfg(feature = "manifest")]
187            InodeError::ManifestError { .. } => libc::EIO,
188            InodeError::OperationNotSupportedOnSyntheticInode { .. } => libc::EIO,
189            InodeError::OutOfOrderReadDir { .. } => libc::EBADF,
190            InodeError::NoSuchDirHandle { .. } => libc::EINVAL,
191            InodeError::FlexibleRetrievalObjectNotAccessible(_) => libc::EACCES,
192        }
193    }
194}
195
196impl<E: std::error::Error> ToErrno for UploadError<E> {
197    fn to_errno(&self) -> libc::c_int {
198        match self {
199            UploadError::PutRequestFailed(_) => libc::EIO,
200            UploadError::UploadAlreadyTerminated => libc::EIO,
201            UploadError::SseCorruptedError(_) => libc::EIO,
202            UploadError::ChecksumComputationFailed(_) => libc::EIO,
203            UploadError::HeadObjectFailed(_) => libc::EIO,
204            UploadError::OutOfOrderWrite { .. } => libc::EINVAL,
205            UploadError::ObjectTooBig { .. } => libc::EFBIG,
206        }
207    }
208}
209
210impl Error {
211    pub fn meta(&self) -> &ErrorMetadata {
212        &self.metadata
213    }
214}