Skip to main content

mountpoint_s3_fs/
metablock.rs

1//! Metablock trait and related types for generic filesystem implementations
2
3use async_trait::async_trait;
4use mountpoint_s3_client::types::ETag;
5use std::ffi::{OsStr, OsString};
6use time::OffsetDateTime;
7
8// Import core types from submodules
9mod error;
10mod expiry;
11mod lookup;
12mod path;
13mod pending_upload;
14mod stat;
15
16// Re-export all the core types
17pub use error::{InodeError, InodeErrorInfo};
18pub use expiry::{Expiry, NEVER_EXPIRE_TTL};
19pub use lookup::{InodeInformation, Lookup};
20pub use path::{S3Location, ValidKey, ValidKeyError, ValidName};
21pub use pending_upload::PendingUploadHook;
22pub use stat::{InodeKind, InodeNo, InodeStat};
23
24use crate::fs::OpenFlags;
25
26pub const ROOT_INODE_NO: InodeNo = crate::fs::FUSE_ROOT_INODE;
27
28/// A trait for a generic implementation of a structure managing a filesystem backed by S3.
29///
30/// In general, an implementation may implement a subset of the operations and respond with
31/// `InodeError::UnsupportedOperations` for the unsupported ones.
32/// However, minimally `lookup`, `getattr` and the three readdir functions should be implemented
33/// to have a minimally viable file system view.
34/// To allow reading from files, additionally `start_reading` and `finish_reading` must be implemented.
35/// For writing, it is required to implement `start_writing`, `inc_file_size` and `finish_writing`.
36#[async_trait]
37pub trait Metablock: Send + Sync {
38    /// Lookup an inode in the parent directory with the given name.
39    async fn lookup(&self, parent_ino: InodeNo, name: &OsStr) -> Result<Lookup, InodeError>;
40
41    /// Retrieve the attributes for an inode
42    async fn getattr(&self, ino: InodeNo, force_revalidate_if_remote: bool) -> Result<Lookup, InodeError>;
43
44    /// Set the attributes for an inode
45    async fn setattr(
46        &self,
47        ino: InodeNo,
48        atime: Option<OffsetDateTime>,
49        mtime: Option<OffsetDateTime>,
50    ) -> Result<Lookup, InodeError>;
51
52    /// Create a new regular file or directory inode ready to be opened in write-only mode
53    async fn create(&self, dir: InodeNo, name: &OsStr, kind: InodeKind) -> Result<Lookup, InodeError>;
54
55    /// The kernel tells us when it removes a reference to an [InodeNo] from its internal caches via a forget call.
56    /// The kernel may forget a number of references (`n`) in one forget message to our FUSE implementation.
57    async fn forget(&self, ino: InodeNo, n: u64);
58
59    /// Open a new file handle for the given inode in read or write mode depending on flags and inode state.
60    async fn open_handle(
61        &self,
62        ino: InodeNo,
63        fh: u64,
64        write_mode: &WriteMode,
65        flags: OpenFlags,
66    ) -> Result<NewHandle, InodeError>;
67
68    /// Increase the size of a file open for writing.
69    /// Parameter `len` refers to the additional
70    /// Returns the new size after the increase.
71    async fn inc_file_size(&self, ino: InodeNo, len: usize) -> Result<usize, InodeError>;
72
73    /// Called when the filesystem has finished writing to the inode referenced by `ino` using the
74    /// file handle `fh`.
75    ///
76    /// Returns the latest state in S3 for the inode after the writing to S3 is concluded.
77    async fn finish_writing(&self, ino: InodeNo, etag: Option<ETag>, fh: u64) -> Result<Lookup, InodeError>;
78
79    /// Finish reading from the inode (referenced by `ino`) using the file handle `fh`.
80    async fn finish_reading(&self, ino: InodeNo, fh: u64) -> Result<(), InodeError>;
81
82    /// Called when the filesystem has called `flush` on a read handle `fh` for the inode
83    /// referenced by `ino`.
84    async fn flush_reader(&self, ino: InodeNo, fh: u64) -> Result<(), InodeError>;
85
86    /// Called when the filesystem has called `flush` on a write handle `fh` for the inode
87    /// referenced by `ino`.
88    ///
89    /// Attaches a reference to the pending upload hook for this writer, if there's not one attached
90    /// already.
91    ///
92    /// Returns a reference to the existing/new upload hook linked to the inode `ino`, which the
93    /// caller may choose to await the completion of.
94    async fn flush_writer(
95        &self,
96        ino: InodeNo,
97        fh: u64,
98        pending_upload_hook: PendingUploadHook,
99    ) -> Result<Option<PendingUploadHook>, InodeError>;
100
101    /// Called when the filesystem has released a write handle for the inode referenced by `ino`.
102    ///
103    /// The implementer owns completing any pending uploads and cleaning up the internal state for
104    /// this handle.
105    async fn release_writer(
106        &self,
107        ino: InodeNo,
108        fh: u64,
109        pending_upload_hook: PendingUploadHook,
110        location: &S3Location,
111    ) -> Result<(), InodeError>;
112
113    /// Called by filesystem's read/write methods to attempt re-activation of a deactivated file
114    /// handle `fh` for the inode referenced by `ino`.
115    ///
116    /// Returns whether the handle was successfully reactivated.
117    async fn try_reactivate_handle(&self, ino: InodeNo, fh: u64, mode: ReadWriteMode) -> Result<bool, InodeError>;
118
119    /// Start a readdir stream for the given directory referenced inode (`dir_ino`)
120    ///
121    /// Returns a number with which this stream can be accessed in `readdir` and `releasedir`.
122    async fn new_readdir_handle(&self, dir_ino: InodeNo) -> Result<u64, InodeError>;
123
124    /// Reads entries from the readdir stream, for the directory `parent`, referred to by `fh` starting at offset `offset`.
125    ///
126    /// Entries shall be passed to `add` as described in its documentation.
127    async fn readdir<'a>(
128        &self,
129        parent: InodeNo,
130        fh: u64,
131        offset: i64,
132        is_readdirplus: bool,
133        mut add: AddDirEntry<'a>,
134    ) -> Result<(), InodeError>;
135
136    /// Closes the readdir handle.
137    async fn releasedir(&self, fh: u64) -> Result<(), InodeError>;
138
139    /// Rename inode described by source parent and name to instead be linked under the given destination and name.
140    /// If the parameter `allow_overwrite` is set to false, renames where the destination would be replaced shall fail with `InodeError::RenameDestinationExists`.
141    async fn rename(
142        &self,
143        src_parent_ino: InodeNo,
144        src_name: &OsStr,
145        dst_parent_ino: InodeNo,
146        dst_name: &OsStr,
147        allow_overwrite: bool,
148    ) -> Result<(), InodeError>;
149
150    /// Remove a directory given by name and parent directory.
151    async fn rmdir(&self, parent_ino: InodeNo, name: &OsStr) -> Result<(), InodeError>;
152
153    /// Unlink the entry described by `parent_ino` and `name`.
154    async fn unlink(&self, parent_ino: InodeNo, name: &OsStr) -> Result<(), InodeError>;
155}
156
157/// Callback to the file system which adds directory entries to the reply buffer.
158///
159/// # Parameters (in order)
160///
161/// * `information` - Contains metadata about the inode
162/// * `name` - The name of the directory entry
163/// * `offset` - Position of this entry
164/// * `generation` - Generation number[^1]
165///
166/// # Returns
167///
168/// - [AddDirEntryResult::EntryAdded] if the entry was added, or
169/// - [AddDirEntryResult::ReplyBufferFull] if the reply buffer was full.
170///
171///
172/// [^1]: The generation number is used to ensure uniqueness of inode/generation pairs.
173///     If the file system were exported over NFS, these pairs would need to be unique.
174///     For more information, see the [libfuse documentation](https://github.com/libfuse/libfuse/blob/fc1c8da0cf8a18d222cb1feed0057ba44ea4d18f/include/fuse_lowlevel.h#L70).
175pub type AddDirEntry<'r> = Box<dyn FnMut(InodeInformation, OsString, i64, u64) -> AddDirEntryResult + Send + Sync + 'r>;
176
177/// Result of a call to `AddDirEntry`.
178#[derive(Debug, PartialEq, Eq)]
179pub enum AddDirEntryResult {
180    /// The entry was added successfully.
181    EntryAdded,
182    /// The entry was not added because the reply buffer was full.
183    ReplyBufferFull,
184}
185
186#[derive(Debug, Default)]
187pub struct WriteMode {
188    /// Allow overwrite
189    pub allow_overwrite: bool,
190    /// Enable incremental uploads
191    pub incremental_upload: bool,
192}
193
194impl WriteMode {
195    pub fn is_inode_writable(&self, is_truncate: bool) -> bool {
196        if self.incremental_upload || (self.allow_overwrite && is_truncate) {
197            true
198        } else {
199            if is_truncate {
200                tracing::warn!(
201                    "file overwrite is disabled by default, you need to remount with --allow-overwrite flag to enable it"
202                );
203            } else {
204                tracing::warn!(
205                    "modifying an existing file is disabled by default, you need to remount with the --allow-overwrite or the --incremental-upload flag to enable it"
206                );
207            }
208            false
209        }
210    }
211}
212
213/// The mode in which a handle is provisioned to access the (existing in S3 or local) data for a file
214/// (mapped to an inode and backed by a corresponding S3 object)
215#[derive(Debug, Clone, Copy)]
216pub enum ReadWriteMode {
217    /// Allow reads from this and other concurrent readers (but no writer)
218    Read,
219    /// Allow writes from this exclusive writer (but no other writer or readers)
220    Write,
221}
222
223/// A metablock-level abstraction on a file, providing the user with the latest metadata in the
224/// linked inode and the ReadWriteMode in which they're allowed to access the file
225#[derive(Debug)]
226pub struct NewHandle {
227    pub lookup: Lookup,
228    pub mode: ReadWriteMode,
229}
230
231impl NewHandle {
232    pub fn read(lookup: Lookup) -> Self {
233        Self {
234            lookup,
235            mode: ReadWriteMode::Read,
236        }
237    }
238
239    pub fn write(lookup: Lookup) -> Self {
240        Self {
241            lookup,
242            mode: ReadWriteMode::Write,
243        }
244    }
245}