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