@wasm-oj/server 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +6 -0
- package/THIRD_PARTY_NOTICES.md +302 -0
- package/crates/runtime-core/Cargo.lock +5099 -0
- package/crates/runtime-core/Cargo.toml +66 -0
- package/crates/runtime-core/README.md +47 -0
- package/crates/runtime-core/src/bin/wasm-oj-compiler.rs +418 -0
- package/crates/runtime-core/src/bin/wasm-oj-runner.rs +294 -0
- package/crates/runtime-core/src/capabilities.rs +118 -0
- package/crates/runtime-core/src/compiler.rs +658 -0
- package/crates/runtime-core/src/contract.rs +5 -0
- package/crates/runtime-core/src/deterministic.rs +1051 -0
- package/crates/runtime-core/src/error.rs +58 -0
- package/crates/runtime-core/src/filesystem.rs +547 -0
- package/crates/runtime-core/src/filesystem_quota.rs +167 -0
- package/crates/runtime-core/src/go_compiler_session.rs +297 -0
- package/crates/runtime-core/src/interactive.rs +1019 -0
- package/crates/runtime-core/src/judge_package.rs +1539 -0
- package/crates/runtime-core/src/lib.rs +98 -0
- package/crates/runtime-core/src/memory.rs +84 -0
- package/crates/runtime-core/src/meter.rs +549 -0
- package/crates/runtime-core/src/module_imports.rs +149 -0
- package/crates/runtime-core/src/module_policy.rs +714 -0
- package/crates/runtime-core/src/output.rs +204 -0
- package/crates/runtime-core/src/run/mod.rs +208 -0
- package/crates/runtime-core/src/run/native.rs +260 -0
- package/crates/runtime-core/src/run/web.rs +229 -0
- package/crates/runtime-core/src/run/web_runtime.rs +109 -0
- package/crates/runtime-core/src/types.rs +268 -0
- package/crates/runtime-core/src/web.rs +83 -0
- package/dist/chunks/go-toolchain-Dbt-lp2L.js +426 -0
- package/dist/chunks/java-toolchain-DajoRCHu.js +44 -0
- package/dist/chunks/python-toolchain-Dx834o2A.js +4 -0
- package/dist/chunks/rust-toolchain-CJ3sMxPE.js +252 -0
- package/dist/chunks/toolchains-C6KuA1yM.js +224 -0
- package/dist/go-stage.mjs +193 -0
- package/dist/index.d.ts +221 -0
- package/dist/index.js +4393 -0
- package/dist/java-stage.mjs +111 -0
- package/dist/python-stage.mjs +90 -0
- package/dist/rustc-stage.mjs +301 -0
- package/dist/server-build-stage.mjs +2564 -0
- package/dist/server-runner-stage.mjs +155 -0
- package/licenses/fflate-MIT.txt +21 -0
- package/licenses/runtime-core-dependencies.html +6253 -0
- package/licenses/runtime-core-dependencies.json +3041 -0
- package/licenses/wasmer-sdk-MIT.txt +21 -0
- package/licenses/wasmer-sdk-dependencies.html +6901 -0
- package/licenses/wasmer-sdk-dependencies.json +3013 -0
- package/package.json +70 -0
- package/rust-toolchain.toml +5 -0
- package/testdata/wojjdg02-v2-text.hex +1 -0
- package/vendor/shared-buffer/Cargo.toml +22 -0
- package/vendor/shared-buffer/LICENSE_APACHE.md +176 -0
- package/vendor/shared-buffer/LICENSE_MIT.md +25 -0
- package/vendor/shared-buffer/README.md +34 -0
- package/vendor/shared-buffer/src/lib.rs +58 -0
- package/vendor/shared-buffer/src/mmap.rs +250 -0
- package/vendor/shared-buffer/src/owned.rs +389 -0
- package/vendor/virtual-fs/Cargo.toml +181 -0
- package/vendor/virtual-fs/LICENSE +25 -0
- package/vendor/virtual-fs/src/arc_box_file.rs +142 -0
- package/vendor/virtual-fs/src/arc_file.rs +182 -0
- package/vendor/virtual-fs/src/arc_fs.rs +68 -0
- package/vendor/virtual-fs/src/buffer_file.rs +103 -0
- package/vendor/virtual-fs/src/builder.rs +232 -0
- package/vendor/virtual-fs/src/combine_file.rs +101 -0
- package/vendor/virtual-fs/src/cow_file.rs +345 -0
- package/vendor/virtual-fs/src/dual_write_file.rs +113 -0
- package/vendor/virtual-fs/src/empty_fs.rs +81 -0
- package/vendor/virtual-fs/src/filesystems.rs +108 -0
- package/vendor/virtual-fs/src/host_fs.rs +1390 -0
- package/vendor/virtual-fs/src/lib.rs +782 -0
- package/vendor/virtual-fs/src/limiter.rs +252 -0
- package/vendor/virtual-fs/src/mem_fs/file.rs +1799 -0
- package/vendor/virtual-fs/src/mem_fs/file_opener.rs +941 -0
- package/vendor/virtual-fs/src/mem_fs/filesystem.rs +2134 -0
- package/vendor/virtual-fs/src/mem_fs/mod.rs +245 -0
- package/vendor/virtual-fs/src/mem_fs/offloaded_file.rs +474 -0
- package/vendor/virtual-fs/src/mem_fs/stdio.rs +318 -0
- package/vendor/virtual-fs/src/mount_fs.rs +2225 -0
- package/vendor/virtual-fs/src/null_file.rs +87 -0
- package/vendor/virtual-fs/src/ops.rs +364 -0
- package/vendor/virtual-fs/src/overlay_fs.rs +2216 -0
- package/vendor/virtual-fs/src/passthru_fs.rs +119 -0
- package/vendor/virtual-fs/src/pipe.rs +603 -0
- package/vendor/virtual-fs/src/random_file.rs +88 -0
- package/vendor/virtual-fs/src/special_file.rs +108 -0
- package/vendor/virtual-fs/src/static_file.rs +133 -0
- package/vendor/virtual-fs/src/static_fs.rs +460 -0
- package/vendor/virtual-fs/src/tmp_fs.rs +95 -0
- package/vendor/virtual-fs/src/trace_fs.rs +258 -0
- package/vendor/virtual-fs/src/webc_volume_fs.rs +829 -0
- package/vendor/virtual-fs/src/zero_file.rs +90 -0
|
@@ -0,0 +1,2134 @@
|
|
|
1
|
+
//! This module contains the [`FileSystem`] type itself.
|
|
2
|
+
|
|
3
|
+
use self::offloaded_file::OffloadBackingStore;
|
|
4
|
+
|
|
5
|
+
use super::*;
|
|
6
|
+
use crate::{DirEntry, FileType, FsError, Metadata, OpenOptions, ReadDir, Result};
|
|
7
|
+
use futures::future::{BoxFuture, Either};
|
|
8
|
+
use slab::Slab;
|
|
9
|
+
use std::collections::VecDeque;
|
|
10
|
+
use std::convert::identity;
|
|
11
|
+
use std::ffi::OsString;
|
|
12
|
+
use std::fmt;
|
|
13
|
+
use std::path::{Component, Path, PathBuf};
|
|
14
|
+
use std::sync::{Arc, RwLock};
|
|
15
|
+
|
|
16
|
+
/// The in-memory file system!
|
|
17
|
+
///
|
|
18
|
+
/// This `FileSystem` type can be cloned, it's a light copy of the
|
|
19
|
+
/// `FileSystemInner` (which is behind a `Arc` + `RwLock`).
|
|
20
|
+
#[derive(Clone, Default)]
|
|
21
|
+
pub struct FileSystem {
|
|
22
|
+
pub(super) inner: Arc<RwLock<FileSystemInner>>,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
impl FileSystem {
|
|
26
|
+
/// Creates an in-memory filesystem whose implicit metadata updates use a
|
|
27
|
+
/// fixed nanosecond Unix timestamp.
|
|
28
|
+
pub fn with_fixed_timestamp(timestamp: u64) -> Self {
|
|
29
|
+
Self {
|
|
30
|
+
inner: Arc::new(RwLock::new(FileSystemInner::new(MetadataClock::Fixed(
|
|
31
|
+
timestamp,
|
|
32
|
+
)))),
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
pub fn set_memory_limiter(&self, limiter: crate::limiter::DynFsMemoryLimiter) {
|
|
37
|
+
self.inner.write().unwrap().limiter = Some(limiter);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
pub fn new_open_options_ext(&self) -> &FileSystem {
|
|
41
|
+
self
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// Uses a mmap'ed file as a cache for file data thus removing the
|
|
45
|
+
/// need to copy the data into memory.
|
|
46
|
+
///
|
|
47
|
+
/// This is especially important for journals as it means that the
|
|
48
|
+
/// data stored within the journals does not need to be copied
|
|
49
|
+
/// into memory, for very large journals this would otherwise be
|
|
50
|
+
/// a problem.
|
|
51
|
+
pub fn with_backing_offload(self, buffer: OffloadBackingStore) -> Result<Self> {
|
|
52
|
+
let mut lock = self.inner.write().map_err(|_| FsError::Lock)?;
|
|
53
|
+
lock.backing_offload.replace(buffer);
|
|
54
|
+
drop(lock);
|
|
55
|
+
Ok(self)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// Canonicalize a path without validating that it actually exists.
|
|
59
|
+
pub fn canonicalize_unchecked(&self, path: &Path) -> Result<PathBuf> {
|
|
60
|
+
let lock = self.inner.read().map_err(|_| FsError::Lock)?;
|
|
61
|
+
lock.canonicalize_without_inode(path)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/// Merge all items from a given source path (directory) of a different file
|
|
65
|
+
/// system into this file system.
|
|
66
|
+
///
|
|
67
|
+
/// Individual files and directories of the given path are mounted.
|
|
68
|
+
///
|
|
69
|
+
/// This function is not recursive, only the items in the source_path are
|
|
70
|
+
/// mounted.
|
|
71
|
+
///
|
|
72
|
+
/// See [`Self::union`] for mounting all inodes recursively.
|
|
73
|
+
pub fn mount_directory_entries(
|
|
74
|
+
&self,
|
|
75
|
+
target_path: &Path,
|
|
76
|
+
other: &Arc<dyn crate::FileSystem + Send + Sync>,
|
|
77
|
+
mut source_path: &Path,
|
|
78
|
+
) -> Result<()> {
|
|
79
|
+
let fs_lock = self.inner.read().map_err(|_| FsError::Lock)?;
|
|
80
|
+
|
|
81
|
+
if cfg!(windows) {
|
|
82
|
+
// We need to take some care here because
|
|
83
|
+
// canonicalize_without_inode() doesn't accept Windows paths that
|
|
84
|
+
// start with a prefix (drive letters, UNC paths, etc.). If we
|
|
85
|
+
// somehow get one of those paths, we'll automatically trim it away.
|
|
86
|
+
let mut components = source_path.components();
|
|
87
|
+
|
|
88
|
+
if let Some(Component::Prefix(_)) = components.next() {
|
|
89
|
+
source_path = components.as_path();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let (_target_path, root_inode) = match fs_lock.canonicalize(target_path) {
|
|
94
|
+
Ok((p, InodeResolution::Found(inode))) => (p, inode),
|
|
95
|
+
Ok((_p, InodeResolution::Redirect(..))) => {
|
|
96
|
+
return Err(FsError::AlreadyExists);
|
|
97
|
+
}
|
|
98
|
+
Err(_) => {
|
|
99
|
+
// Root directory does not exist, so we can just mount.
|
|
100
|
+
return self.mount(target_path.to_path_buf(), other, source_path.to_path_buf());
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
let _root_node = match fs_lock.storage.get(root_inode).unwrap() {
|
|
105
|
+
Node::Directory(dir) => dir,
|
|
106
|
+
_ => {
|
|
107
|
+
return Err(FsError::AlreadyExists);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
let source_path = fs_lock.canonicalize_without_inode(source_path)?;
|
|
112
|
+
|
|
113
|
+
std::mem::drop(fs_lock);
|
|
114
|
+
|
|
115
|
+
let source = other.read_dir(&source_path)?;
|
|
116
|
+
for entry in source.data {
|
|
117
|
+
let meta = entry.metadata?;
|
|
118
|
+
|
|
119
|
+
let entry_target_path = target_path.join(entry.path.file_name().unwrap());
|
|
120
|
+
|
|
121
|
+
if meta.is_file() {
|
|
122
|
+
self.insert_arc_file_at(entry_target_path, other.clone(), entry.path)?;
|
|
123
|
+
} else if meta.is_dir() {
|
|
124
|
+
self.insert_arc_directory_at(entry_target_path, other.clone(), entry.path)?;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
Ok(())
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
pub fn union(&self, other: &Arc<dyn crate::FileSystem + Send + Sync>) {
|
|
132
|
+
// Iterate all the directories and files in the other filesystem
|
|
133
|
+
// and create references back to them in this filesystem
|
|
134
|
+
let mut remaining = VecDeque::new();
|
|
135
|
+
remaining.push_back(PathBuf::from("/"));
|
|
136
|
+
while let Some(next) = remaining.pop_back() {
|
|
137
|
+
if next
|
|
138
|
+
.file_name()
|
|
139
|
+
.map(|n| n.to_string_lossy().starts_with(".wh."))
|
|
140
|
+
.unwrap_or(false)
|
|
141
|
+
{
|
|
142
|
+
let rm = next.to_string_lossy();
|
|
143
|
+
let rm = &rm[".wh.".len()..];
|
|
144
|
+
let rm = PathBuf::from(rm);
|
|
145
|
+
let _ = crate::FileSystem::remove_dir(self, rm.as_path());
|
|
146
|
+
let _ = crate::FileSystem::remove_file(self, rm.as_path());
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
let _ = crate::FileSystem::create_dir(self, next.as_path());
|
|
150
|
+
|
|
151
|
+
let dir = match other.read_dir(next.as_path()) {
|
|
152
|
+
Ok(dir) => dir,
|
|
153
|
+
Err(_) => {
|
|
154
|
+
// TODO: propagate errors (except NotFound)
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
for sub_dir_res in dir {
|
|
160
|
+
let sub_dir = match sub_dir_res {
|
|
161
|
+
Ok(sub_dir) => sub_dir,
|
|
162
|
+
Err(_) => {
|
|
163
|
+
// TODO: propagate errors (except NotFound)
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
match sub_dir.file_type() {
|
|
169
|
+
Ok(t) if t.is_dir() => {
|
|
170
|
+
remaining.push_back(sub_dir.path());
|
|
171
|
+
}
|
|
172
|
+
Ok(t) if t.is_file() => {
|
|
173
|
+
if sub_dir.file_name().to_string_lossy().starts_with(".wh.") {
|
|
174
|
+
let rm = next.to_string_lossy();
|
|
175
|
+
let rm = &rm[".wh.".len()..];
|
|
176
|
+
let rm = PathBuf::from(rm);
|
|
177
|
+
let _ = crate::FileSystem::remove_dir(self, rm.as_path());
|
|
178
|
+
let _ = crate::FileSystem::remove_file(self, rm.as_path());
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
let _ = self
|
|
182
|
+
.new_open_options_ext()
|
|
183
|
+
.insert_arc_file(sub_dir.path(), other.clone());
|
|
184
|
+
}
|
|
185
|
+
_ => {}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
pub fn mount(
|
|
192
|
+
&self,
|
|
193
|
+
target_path: PathBuf,
|
|
194
|
+
other: &Arc<dyn crate::FileSystem + Send + Sync>,
|
|
195
|
+
source_path: PathBuf,
|
|
196
|
+
) -> Result<()> {
|
|
197
|
+
if crate::FileSystem::read_dir(self, target_path.as_path()).is_ok() {
|
|
198
|
+
return Err(FsError::AlreadyExists);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
let (inode_of_parent, name_of_directory) = {
|
|
202
|
+
// Read lock.
|
|
203
|
+
let guard = self.inner.read().map_err(|_| FsError::Lock)?;
|
|
204
|
+
|
|
205
|
+
// Canonicalize the path without checking the path exists,
|
|
206
|
+
// because it's about to be created.
|
|
207
|
+
let path = guard.canonicalize_without_inode(target_path.as_path())?;
|
|
208
|
+
|
|
209
|
+
// Check the path has a parent.
|
|
210
|
+
let parent_of_path = path.parent().ok_or(FsError::BaseNotDirectory)?;
|
|
211
|
+
|
|
212
|
+
// Check the directory name.
|
|
213
|
+
let name_of_directory = path
|
|
214
|
+
.file_name()
|
|
215
|
+
.ok_or(FsError::InvalidInput)?
|
|
216
|
+
.to_os_string();
|
|
217
|
+
|
|
218
|
+
// Find the parent inode.
|
|
219
|
+
let inode_of_parent = match guard.inode_of_parent(parent_of_path)? {
|
|
220
|
+
InodeResolution::Found(a) => a,
|
|
221
|
+
InodeResolution::Redirect(..) => {
|
|
222
|
+
return Err(FsError::AlreadyExists);
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
(inode_of_parent, name_of_directory)
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
{
|
|
230
|
+
// Write lock.
|
|
231
|
+
let mut fs = self.inner.write().map_err(|_| FsError::Lock)?;
|
|
232
|
+
|
|
233
|
+
// Creating the directory in the storage.
|
|
234
|
+
let time = fs.now();
|
|
235
|
+
let inode_of_directory = fs.storage.vacant_entry().key();
|
|
236
|
+
let real_inode_of_directory = fs.insert_node(Node::ArcDirectory(ArcDirectoryNode {
|
|
237
|
+
inode: inode_of_directory,
|
|
238
|
+
name: name_of_directory,
|
|
239
|
+
fs: other.clone(),
|
|
240
|
+
path: source_path,
|
|
241
|
+
metadata: {
|
|
242
|
+
Metadata {
|
|
243
|
+
ft: FileType {
|
|
244
|
+
dir: true,
|
|
245
|
+
..Default::default()
|
|
246
|
+
},
|
|
247
|
+
accessed: time,
|
|
248
|
+
created: time,
|
|
249
|
+
modified: time,
|
|
250
|
+
len: 0,
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
}))?;
|
|
254
|
+
|
|
255
|
+
assert_eq!(
|
|
256
|
+
inode_of_directory, real_inode_of_directory,
|
|
257
|
+
"new directory inode should have been correctly calculated",
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
// Adding the new directory to its parent.
|
|
261
|
+
fs.add_child_to_node(inode_of_parent, inode_of_directory)?;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
Ok(())
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
pub fn create_symlink(&self, source: &Path, target: &Path) -> Result<()> {
|
|
268
|
+
let (inode_of_parent, name_of_symlink) = {
|
|
269
|
+
let guard = self.inner.read().map_err(|_| FsError::Lock)?;
|
|
270
|
+
let path = guard.canonicalize_without_inode(target)?;
|
|
271
|
+
let parent_of_path = path.parent().ok_or(FsError::BaseNotDirectory)?;
|
|
272
|
+
let name_of_symlink = path
|
|
273
|
+
.file_name()
|
|
274
|
+
.ok_or(FsError::InvalidInput)?
|
|
275
|
+
.to_os_string();
|
|
276
|
+
let inode_of_parent = match guard.inode_of_parent(parent_of_path)? {
|
|
277
|
+
InodeResolution::Found(a) => a,
|
|
278
|
+
InodeResolution::Redirect(fs, mut redirected_path) => {
|
|
279
|
+
redirected_path.push(&name_of_symlink);
|
|
280
|
+
drop(guard);
|
|
281
|
+
let fs_ref: &dyn crate::FileSystem = fs.as_ref();
|
|
282
|
+
if let Some(mem_fs) = fs_ref.downcast_ref::<Self>() {
|
|
283
|
+
return mem_fs.create_symlink(source, redirected_path.as_path());
|
|
284
|
+
}
|
|
285
|
+
return Err(FsError::Unsupported);
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
(inode_of_parent, name_of_symlink)
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
let mut fs = self.inner.write().map_err(|_| FsError::Lock)?;
|
|
292
|
+
if fs.canonicalize(target).is_ok() {
|
|
293
|
+
return Err(FsError::AlreadyExists);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
let time = fs.now();
|
|
297
|
+
let inode_of_symlink = fs.storage.vacant_entry().key();
|
|
298
|
+
let real_inode_of_symlink = fs.insert_node(Node::Symlink(SymlinkNode {
|
|
299
|
+
inode: inode_of_symlink,
|
|
300
|
+
name: name_of_symlink,
|
|
301
|
+
target: source.to_path_buf(),
|
|
302
|
+
metadata: {
|
|
303
|
+
Metadata {
|
|
304
|
+
ft: FileType {
|
|
305
|
+
symlink: true,
|
|
306
|
+
..Default::default()
|
|
307
|
+
},
|
|
308
|
+
accessed: time,
|
|
309
|
+
created: time,
|
|
310
|
+
modified: time,
|
|
311
|
+
len: source.as_os_str().len() as u64,
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
}))?;
|
|
315
|
+
|
|
316
|
+
assert_eq!(
|
|
317
|
+
inode_of_symlink, real_inode_of_symlink,
|
|
318
|
+
"new symlink inode should have been correctly calculated",
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
fs.add_child_to_node(inode_of_parent, inode_of_symlink)?;
|
|
322
|
+
Ok(())
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
impl crate::FileSystem for FileSystem {
|
|
327
|
+
fn readlink(&self, path: &Path) -> Result<PathBuf> {
|
|
328
|
+
// Read lock.
|
|
329
|
+
let guard = self.inner.read().map_err(|_| FsError::Lock)?;
|
|
330
|
+
|
|
331
|
+
// Canonicalize the path.
|
|
332
|
+
let (_, inode_of_directory) = guard.canonicalize(path)?;
|
|
333
|
+
match inode_of_directory {
|
|
334
|
+
InodeResolution::Found(inode) => match guard.storage.get(inode) {
|
|
335
|
+
Some(Node::Symlink(SymlinkNode { target, .. })) => Ok(target.clone()),
|
|
336
|
+
_ => Err(FsError::InvalidInput),
|
|
337
|
+
},
|
|
338
|
+
InodeResolution::Redirect(fs, path) => fs.readlink(path.as_path()),
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
fn read_dir(&self, path: &Path) -> Result<ReadDir> {
|
|
343
|
+
// Read lock.
|
|
344
|
+
let guard = self.inner.read().map_err(|_| FsError::Lock)?;
|
|
345
|
+
|
|
346
|
+
fn rebase_entries(entries: &mut ReadDir, base: &Path) {
|
|
347
|
+
for entry in &mut entries.data {
|
|
348
|
+
let name = entry.file_name();
|
|
349
|
+
entry.path = base.join(name);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Canonicalize the path.
|
|
354
|
+
let (guest_path, inode_of_directory) = guard.canonicalize(path)?;
|
|
355
|
+
let inode_of_directory = match inode_of_directory {
|
|
356
|
+
InodeResolution::Found(a) => a,
|
|
357
|
+
InodeResolution::Redirect(fs, redirect_path) => {
|
|
358
|
+
let mut entries = fs.read_dir(redirect_path.as_path())?;
|
|
359
|
+
rebase_entries(&mut entries, &guest_path);
|
|
360
|
+
return Ok(entries);
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
// Check it's a directory and fetch the immediate children as `DirEntry`.
|
|
365
|
+
let inode = guard.storage.get(inode_of_directory);
|
|
366
|
+
let children = match inode {
|
|
367
|
+
Some(Node::Directory(DirectoryNode { children, .. })) => children
|
|
368
|
+
.iter()
|
|
369
|
+
.filter_map(|inode| guard.storage.get(*inode))
|
|
370
|
+
.map(|node| DirEntry {
|
|
371
|
+
path: {
|
|
372
|
+
let mut entry_path = path.to_path_buf();
|
|
373
|
+
entry_path.push(node.name());
|
|
374
|
+
|
|
375
|
+
entry_path
|
|
376
|
+
},
|
|
377
|
+
metadata: Ok(node.metadata().clone()),
|
|
378
|
+
})
|
|
379
|
+
.collect(),
|
|
380
|
+
|
|
381
|
+
Some(Node::ArcDirectory(ArcDirectoryNode {
|
|
382
|
+
fs, path: fs_path, ..
|
|
383
|
+
})) => {
|
|
384
|
+
let mut entries = fs.read_dir(fs_path.as_path())?;
|
|
385
|
+
rebase_entries(&mut entries, &guest_path);
|
|
386
|
+
return Ok(entries);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
_ => return Err(FsError::InvalidInput),
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
Ok(ReadDir::new(children))
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
fn create_dir(&self, path: &Path) -> Result<()> {
|
|
396
|
+
if self.read_dir(path).is_ok() {
|
|
397
|
+
return Err(FsError::AlreadyExists);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
let (inode_of_parent, name_of_directory) = {
|
|
401
|
+
// Read lock.
|
|
402
|
+
let guard = self.inner.read().map_err(|_| FsError::Lock)?;
|
|
403
|
+
|
|
404
|
+
// Canonicalize the path without checking the path exists,
|
|
405
|
+
// because it's about to be created.
|
|
406
|
+
let path = guard.canonicalize_without_inode(path)?;
|
|
407
|
+
|
|
408
|
+
// Check the path has a parent.
|
|
409
|
+
let parent_of_path = path.parent().ok_or(FsError::BaseNotDirectory)?;
|
|
410
|
+
|
|
411
|
+
// Check the directory name.
|
|
412
|
+
let name_of_directory = path
|
|
413
|
+
.file_name()
|
|
414
|
+
.ok_or(FsError::InvalidInput)?
|
|
415
|
+
.to_os_string();
|
|
416
|
+
|
|
417
|
+
// Find the parent inode.
|
|
418
|
+
let inode_of_parent = match guard.inode_of_parent(parent_of_path)? {
|
|
419
|
+
InodeResolution::Found(a) => a,
|
|
420
|
+
InodeResolution::Redirect(fs, mut path) => {
|
|
421
|
+
drop(guard);
|
|
422
|
+
path.push(name_of_directory);
|
|
423
|
+
return fs.create_dir(path.as_path());
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
(inode_of_parent, name_of_directory)
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
if self.read_dir(path).is_ok() {
|
|
431
|
+
return Err(FsError::AlreadyExists);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
{
|
|
435
|
+
// Write lock.
|
|
436
|
+
let mut fs = self.inner.write().map_err(|_| FsError::Lock)?;
|
|
437
|
+
|
|
438
|
+
// Creating the directory in the storage.
|
|
439
|
+
let time = fs.now();
|
|
440
|
+
let inode_of_directory = fs.storage.vacant_entry().key();
|
|
441
|
+
let real_inode_of_directory = fs.insert_node(Node::Directory(DirectoryNode {
|
|
442
|
+
inode: inode_of_directory,
|
|
443
|
+
name: name_of_directory,
|
|
444
|
+
children: Vec::new(),
|
|
445
|
+
metadata: {
|
|
446
|
+
Metadata {
|
|
447
|
+
ft: FileType {
|
|
448
|
+
dir: true,
|
|
449
|
+
..Default::default()
|
|
450
|
+
},
|
|
451
|
+
accessed: time,
|
|
452
|
+
created: time,
|
|
453
|
+
modified: time,
|
|
454
|
+
len: 0,
|
|
455
|
+
}
|
|
456
|
+
},
|
|
457
|
+
}))?;
|
|
458
|
+
|
|
459
|
+
assert_eq!(
|
|
460
|
+
inode_of_directory, real_inode_of_directory,
|
|
461
|
+
"new directory inode should have been correctly calculated",
|
|
462
|
+
);
|
|
463
|
+
|
|
464
|
+
// Adding the new directory to its parent.
|
|
465
|
+
fs.add_child_to_node(inode_of_parent, inode_of_directory)?;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
Ok(())
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
fn create_symlink(&self, source: &Path, target: &Path) -> Result<()> {
|
|
472
|
+
self.create_symlink(source, target)
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
fn remove_dir(&self, path: &Path) -> Result<()> {
|
|
476
|
+
let (inode_of_parent, position, inode_of_directory) = {
|
|
477
|
+
// Read lock.
|
|
478
|
+
let guard = self.inner.read().map_err(|_| FsError::Lock)?;
|
|
479
|
+
|
|
480
|
+
// Canonicalize the path.
|
|
481
|
+
let (path, _) = guard.canonicalize(path)?;
|
|
482
|
+
|
|
483
|
+
// Check the path has a parent.
|
|
484
|
+
let parent_of_path = path.parent().ok_or(FsError::BaseNotDirectory)?;
|
|
485
|
+
|
|
486
|
+
// Check the directory name.
|
|
487
|
+
let name_of_directory = path
|
|
488
|
+
.file_name()
|
|
489
|
+
.ok_or(FsError::InvalidInput)?
|
|
490
|
+
.to_os_string();
|
|
491
|
+
|
|
492
|
+
// Find the parent inode.
|
|
493
|
+
let inode_of_parent = match guard.inode_of_parent(parent_of_path)? {
|
|
494
|
+
InodeResolution::Found(a) => a,
|
|
495
|
+
InodeResolution::Redirect(fs, mut parent_path) => {
|
|
496
|
+
drop(guard);
|
|
497
|
+
parent_path.push(name_of_directory);
|
|
498
|
+
return fs.remove_dir(parent_path.as_path());
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
// Get the child index to remove in the parent node, in
|
|
503
|
+
// addition to the inode of the directory to remove.
|
|
504
|
+
let (position, inode_of_directory) = guard
|
|
505
|
+
.as_parent_get_position_and_inode_of_directory(
|
|
506
|
+
inode_of_parent,
|
|
507
|
+
&name_of_directory,
|
|
508
|
+
DirectoryMustBeEmpty::Yes,
|
|
509
|
+
)?;
|
|
510
|
+
|
|
511
|
+
(inode_of_parent, position, inode_of_directory)
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
let inode_of_directory = match inode_of_directory {
|
|
515
|
+
InodeResolution::Found(a) => a,
|
|
516
|
+
InodeResolution::Redirect(fs, path) => {
|
|
517
|
+
return fs.remove_dir(path.as_path());
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
|
|
521
|
+
{
|
|
522
|
+
// Write lock.
|
|
523
|
+
let mut fs = self.inner.write().map_err(|_| FsError::Lock)?;
|
|
524
|
+
|
|
525
|
+
// Remove the directory from the storage.
|
|
526
|
+
fs.remove_node(inode_of_directory);
|
|
527
|
+
|
|
528
|
+
// Remove the child from the parent directory.
|
|
529
|
+
fs.remove_child_from_node(inode_of_parent, position)?;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
Ok(())
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
fn rename<'a>(&'a self, from: &'a Path, to: &'a Path) -> BoxFuture<'a, Result<()>> {
|
|
536
|
+
Box::pin(async move {
|
|
537
|
+
let name_of_to;
|
|
538
|
+
|
|
539
|
+
// Read lock.
|
|
540
|
+
let (name_of_from, inode_of_from_parent, name_of_to, inode_of_to_parent) = {
|
|
541
|
+
let fs = self.inner.read().map_err(|_| FsError::Lock)?;
|
|
542
|
+
|
|
543
|
+
let from = fs.canonicalize_without_inode(from)?;
|
|
544
|
+
let to = fs.canonicalize_without_inode(to)?;
|
|
545
|
+
|
|
546
|
+
// Check the paths have parents.
|
|
547
|
+
let parent_of_from = from.parent().ok_or(FsError::BaseNotDirectory)?;
|
|
548
|
+
let parent_of_to = to.parent().ok_or(FsError::BaseNotDirectory)?;
|
|
549
|
+
|
|
550
|
+
// Check the names.
|
|
551
|
+
let name_of_from = from
|
|
552
|
+
.file_name()
|
|
553
|
+
.ok_or(FsError::InvalidInput)?
|
|
554
|
+
.to_os_string();
|
|
555
|
+
name_of_to = to.file_name().ok_or(FsError::InvalidInput)?.to_os_string();
|
|
556
|
+
|
|
557
|
+
// Find the parent inodes.
|
|
558
|
+
let inode_of_from_parent = match fs.inode_of_parent(parent_of_from)? {
|
|
559
|
+
InodeResolution::Found(a) => Either::Left(a),
|
|
560
|
+
InodeResolution::Redirect(fs, mut path) => {
|
|
561
|
+
path.push(&name_of_from);
|
|
562
|
+
Either::Right((fs, path))
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
let inode_of_to_parent = match fs.inode_of_parent(parent_of_to)? {
|
|
566
|
+
InodeResolution::Found(a) => Either::Left(a),
|
|
567
|
+
InodeResolution::Redirect(fs, mut path) => {
|
|
568
|
+
path.push(&name_of_to);
|
|
569
|
+
Either::Right((fs, path))
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
(
|
|
574
|
+
name_of_from,
|
|
575
|
+
inode_of_from_parent,
|
|
576
|
+
name_of_to,
|
|
577
|
+
inode_of_to_parent,
|
|
578
|
+
)
|
|
579
|
+
};
|
|
580
|
+
|
|
581
|
+
match (inode_of_from_parent, inode_of_to_parent) {
|
|
582
|
+
// Rename within this MemFS instance
|
|
583
|
+
(Either::Left(inode_of_from_parent), Either::Left(inode_of_to_parent)) => {
|
|
584
|
+
let fs = self.inner.read().map_err(|_| FsError::Lock)?;
|
|
585
|
+
|
|
586
|
+
// Find the inode of the dest file if it exists
|
|
587
|
+
let maybe_position_and_inode_of_file = fs
|
|
588
|
+
.as_parent_get_position_and_inode_of_file(
|
|
589
|
+
inode_of_to_parent,
|
|
590
|
+
&name_of_to,
|
|
591
|
+
)?;
|
|
592
|
+
|
|
593
|
+
// Get the child indexes to update in the parent nodes, in
|
|
594
|
+
// addition to the inode of the directory to update.
|
|
595
|
+
let (position_of_from, inode) = fs
|
|
596
|
+
.as_parent_get_position_and_inode(inode_of_from_parent, &name_of_from)?
|
|
597
|
+
.ok_or(FsError::EntryNotFound)?;
|
|
598
|
+
|
|
599
|
+
let (
|
|
600
|
+
(position_of_from, inode, inode_of_from_parent),
|
|
601
|
+
(inode_of_to_parent, name_of_to),
|
|
602
|
+
inode_dest,
|
|
603
|
+
) = (
|
|
604
|
+
(position_of_from, inode, inode_of_from_parent),
|
|
605
|
+
(inode_of_to_parent, name_of_to),
|
|
606
|
+
maybe_position_and_inode_of_file,
|
|
607
|
+
);
|
|
608
|
+
|
|
609
|
+
let inode = match inode {
|
|
610
|
+
InodeResolution::Found(a) => a,
|
|
611
|
+
InodeResolution::Redirect(..) => {
|
|
612
|
+
return Err(FsError::InvalidInput);
|
|
613
|
+
}
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
drop(fs);
|
|
617
|
+
|
|
618
|
+
{
|
|
619
|
+
// Write lock.
|
|
620
|
+
let mut fs = self.inner.write().map_err(|_| FsError::Lock)?;
|
|
621
|
+
|
|
622
|
+
if let Some((position, inode_of_file)) = inode_dest {
|
|
623
|
+
// Remove the file from the storage.
|
|
624
|
+
match inode_of_file {
|
|
625
|
+
InodeResolution::Found(inode_of_file) => {
|
|
626
|
+
fs.remove_node(inode_of_file);
|
|
627
|
+
}
|
|
628
|
+
InodeResolution::Redirect(..) => {
|
|
629
|
+
return Err(FsError::InvalidInput);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
fs.remove_child_from_node(inode_of_to_parent, position)?;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// Update the file name, and update the modified time.
|
|
637
|
+
fs.update_node_name(inode, name_of_to)?;
|
|
638
|
+
|
|
639
|
+
// The parents are different. Let's update them.
|
|
640
|
+
if inode_of_from_parent != inode_of_to_parent {
|
|
641
|
+
// Remove the file from its parent, and update the
|
|
642
|
+
// modified time.
|
|
643
|
+
fs.remove_child_from_node(inode_of_from_parent, position_of_from)?;
|
|
644
|
+
|
|
645
|
+
// Add the file to its new parent, and update the modified
|
|
646
|
+
// time.
|
|
647
|
+
fs.add_child_to_node(inode_of_to_parent, inode)?;
|
|
648
|
+
}
|
|
649
|
+
// Otherwise, we need to at least update the modified time of the parent.
|
|
650
|
+
else {
|
|
651
|
+
let time = fs.now();
|
|
652
|
+
let mut inode = fs.storage.get_mut(inode_of_from_parent);
|
|
653
|
+
match inode.as_mut() {
|
|
654
|
+
Some(Node::Directory(node)) => node.metadata.modified = time,
|
|
655
|
+
Some(Node::ArcDirectory(node)) => node.metadata.modified = time,
|
|
656
|
+
_ => return Err(FsError::UnknownError),
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
Ok(())
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// Rename within the same mounted FS instance
|
|
665
|
+
(Either::Right((from_fs, from_path)), Either::Right((to_fs, to_path)))
|
|
666
|
+
if Arc::ptr_eq(&from_fs, &to_fs) =>
|
|
667
|
+
{
|
|
668
|
+
let same_fs = from_fs;
|
|
669
|
+
same_fs.rename(&from_path, &to_path).await
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Rename across file systems; we need to do a create and a delete
|
|
673
|
+
_ => crate::ops::move_across_filesystems(self, self, from, to).await,
|
|
674
|
+
}
|
|
675
|
+
})
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
fn metadata(&self, path: &Path) -> Result<Metadata> {
|
|
679
|
+
// Read lock.
|
|
680
|
+
let guard = self.inner.read().map_err(|_| FsError::Lock)?;
|
|
681
|
+
match guard.inode_of(path)? {
|
|
682
|
+
InodeResolution::Found(inode) => Ok(guard
|
|
683
|
+
.storage
|
|
684
|
+
.get(inode)
|
|
685
|
+
.ok_or(FsError::UnknownError)?
|
|
686
|
+
.metadata()
|
|
687
|
+
.clone()),
|
|
688
|
+
InodeResolution::Redirect(fs, path) => {
|
|
689
|
+
drop(guard);
|
|
690
|
+
fs.metadata(path.as_path())
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
fn symlink_metadata(&self, path: &Path) -> Result<Metadata> {
|
|
696
|
+
// Read lock.
|
|
697
|
+
let guard = self.inner.read().map_err(|_| FsError::Lock)?;
|
|
698
|
+
match guard.inode_of(path)? {
|
|
699
|
+
InodeResolution::Found(inode) => Ok(guard
|
|
700
|
+
.storage
|
|
701
|
+
.get(inode)
|
|
702
|
+
.ok_or(FsError::UnknownError)?
|
|
703
|
+
.metadata()
|
|
704
|
+
.clone()),
|
|
705
|
+
InodeResolution::Redirect(fs, path) => {
|
|
706
|
+
drop(guard);
|
|
707
|
+
fs.symlink_metadata(path.as_path())
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
fn remove_file(&self, path: &Path) -> Result<()> {
|
|
713
|
+
let (inode_of_parent, position, inode_of_file) = {
|
|
714
|
+
// Read lock.
|
|
715
|
+
let guard = self.inner.read().map_err(|_| FsError::Lock)?;
|
|
716
|
+
|
|
717
|
+
// Canonicalize the path.
|
|
718
|
+
let path = guard.canonicalize_without_inode(path)?;
|
|
719
|
+
|
|
720
|
+
// Check the path has a parent.
|
|
721
|
+
let parent_of_path = path.parent().ok_or(FsError::BaseNotDirectory)?;
|
|
722
|
+
|
|
723
|
+
// Check the file name.
|
|
724
|
+
let name_of_file = path
|
|
725
|
+
.file_name()
|
|
726
|
+
.ok_or(FsError::InvalidInput)?
|
|
727
|
+
.to_os_string();
|
|
728
|
+
|
|
729
|
+
// Find the parent inode.
|
|
730
|
+
let inode_of_parent = match guard.inode_of_parent(parent_of_path)? {
|
|
731
|
+
InodeResolution::Found(a) => a,
|
|
732
|
+
InodeResolution::Redirect(fs, mut parent_path) => {
|
|
733
|
+
parent_path.push(name_of_file);
|
|
734
|
+
return fs.remove_file(parent_path.as_path());
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
|
|
738
|
+
// Find the inode of the file if it exists, along with its position.
|
|
739
|
+
let maybe_position_and_inode_of_file =
|
|
740
|
+
guard.as_parent_get_position_and_inode_of_file(inode_of_parent, &name_of_file)?;
|
|
741
|
+
|
|
742
|
+
match maybe_position_and_inode_of_file {
|
|
743
|
+
Some((position, inode_of_file)) => (inode_of_parent, position, inode_of_file),
|
|
744
|
+
None => return Err(FsError::EntryNotFound),
|
|
745
|
+
}
|
|
746
|
+
};
|
|
747
|
+
|
|
748
|
+
let inode_of_file = match inode_of_file {
|
|
749
|
+
InodeResolution::Found(a) => a,
|
|
750
|
+
InodeResolution::Redirect(fs, path) => {
|
|
751
|
+
return fs.remove_file(path.as_path());
|
|
752
|
+
}
|
|
753
|
+
};
|
|
754
|
+
|
|
755
|
+
{
|
|
756
|
+
// Write lock.
|
|
757
|
+
let mut fs = self.inner.write().map_err(|_| FsError::Lock)?;
|
|
758
|
+
fs.unlink_file_inode(inode_of_parent, position, inode_of_file)?;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
Ok(())
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
fn new_open_options(&self) -> OpenOptions<'_> {
|
|
765
|
+
OpenOptions::new(self)
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
impl fmt::Debug for FileSystem {
|
|
770
|
+
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
771
|
+
let fs: &FileSystemInner = &self.inner.read().unwrap();
|
|
772
|
+
|
|
773
|
+
fs.fmt(formatter)
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/// The core of the file system. It contains a collection of `Node`s,
|
|
778
|
+
/// indexed by their respective `Inode` in a slab.
|
|
779
|
+
pub(super) struct FileSystemInner {
|
|
780
|
+
pub(super) storage: Slab<Node>,
|
|
781
|
+
pub(super) backing_offload: Option<OffloadBackingStore>,
|
|
782
|
+
pub(super) limiter: Option<crate::limiter::DynFsMemoryLimiter>,
|
|
783
|
+
clock: MetadataClock,
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
#[derive(Debug)]
|
|
787
|
+
pub(super) enum InodeResolution {
|
|
788
|
+
Found(Inode),
|
|
789
|
+
Redirect(Arc<dyn crate::FileSystem + Send + Sync + 'static>, PathBuf),
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
impl InodeResolution {
|
|
793
|
+
#[allow(dead_code)]
|
|
794
|
+
pub fn unwrap(&self) -> Inode {
|
|
795
|
+
match self {
|
|
796
|
+
Self::Found(a) => *a,
|
|
797
|
+
Self::Redirect(..) => {
|
|
798
|
+
panic!("failed to unwrap the inode as the resolution is a redirect");
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
impl FileSystemInner {
|
|
805
|
+
fn new(clock: MetadataClock) -> Self {
|
|
806
|
+
let time = clock.now();
|
|
807
|
+
let mut storage = Slab::new();
|
|
808
|
+
storage.insert(Node::Directory(DirectoryNode {
|
|
809
|
+
inode: ROOT_INODE,
|
|
810
|
+
name: OsString::from("/"),
|
|
811
|
+
children: Vec::new(),
|
|
812
|
+
metadata: Metadata {
|
|
813
|
+
ft: FileType {
|
|
814
|
+
dir: true,
|
|
815
|
+
..Default::default()
|
|
816
|
+
},
|
|
817
|
+
accessed: time,
|
|
818
|
+
created: time,
|
|
819
|
+
modified: time,
|
|
820
|
+
len: 0,
|
|
821
|
+
},
|
|
822
|
+
}));
|
|
823
|
+
Self {
|
|
824
|
+
storage,
|
|
825
|
+
backing_offload: None,
|
|
826
|
+
limiter: None,
|
|
827
|
+
clock,
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
pub(super) fn now(&self) -> u64 {
|
|
832
|
+
self.clock.now()
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
pub(super) fn insert_node(&mut self, node: Node) -> Result<Inode> {
|
|
836
|
+
if let Some(limiter) = &self.limiter {
|
|
837
|
+
limiter.on_create_entry()?;
|
|
838
|
+
}
|
|
839
|
+
Ok(self.storage.insert(node))
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
pub(super) fn remove_node(&mut self, inode: Inode) -> Node {
|
|
843
|
+
let node = self.storage.remove(inode);
|
|
844
|
+
if let Some(limiter) = &self.limiter {
|
|
845
|
+
limiter.on_remove_entry();
|
|
846
|
+
}
|
|
847
|
+
node
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
pub(super) fn unlink_file_inode(
|
|
851
|
+
&mut self,
|
|
852
|
+
inode_of_parent: Inode,
|
|
853
|
+
position: usize,
|
|
854
|
+
inode_of_file: Inode,
|
|
855
|
+
) -> Result<()> {
|
|
856
|
+
let remove_storage = match self.storage.get(inode_of_file) {
|
|
857
|
+
Some(node) => {
|
|
858
|
+
if let Some(lifecycle) = node.file_lifecycle() {
|
|
859
|
+
lifecycle.mark_unlinked();
|
|
860
|
+
lifecycle.open_handle_count() == 0
|
|
861
|
+
} else {
|
|
862
|
+
true
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
None => return Err(FsError::EntryNotFound),
|
|
866
|
+
};
|
|
867
|
+
|
|
868
|
+
if remove_storage {
|
|
869
|
+
self.remove_node(inode_of_file);
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
self.remove_child_from_node(inode_of_parent, position)
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
/// Get the inode associated to a path if it exists.
|
|
876
|
+
pub(super) fn inode_of(&self, path: &Path) -> Result<InodeResolution> {
|
|
877
|
+
// SAFETY: The root node always exists, so it's safe to unwrap here.
|
|
878
|
+
let mut node = self.storage.get(ROOT_INODE).unwrap();
|
|
879
|
+
let mut components = path.components();
|
|
880
|
+
|
|
881
|
+
match components.next() {
|
|
882
|
+
Some(Component::RootDir) => {}
|
|
883
|
+
_ => return Err(FsError::BaseNotDirectory),
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
while let Some(component) = components.next() {
|
|
887
|
+
node = match node {
|
|
888
|
+
Node::Directory(DirectoryNode { children, .. }) => children
|
|
889
|
+
.iter()
|
|
890
|
+
.filter_map(|inode| self.storage.get(*inode))
|
|
891
|
+
.find(|node| node.name() == component.as_os_str())
|
|
892
|
+
.ok_or(FsError::EntryNotFound)?,
|
|
893
|
+
Node::ArcDirectory(ArcDirectoryNode {
|
|
894
|
+
fs, path: fs_path, ..
|
|
895
|
+
}) => {
|
|
896
|
+
let mut path = fs_path.clone();
|
|
897
|
+
path.push(PathBuf::from(component.as_os_str()));
|
|
898
|
+
for component in components.by_ref() {
|
|
899
|
+
path.push(PathBuf::from(component.as_os_str()));
|
|
900
|
+
}
|
|
901
|
+
return Ok(InodeResolution::Redirect(fs.clone(), path));
|
|
902
|
+
}
|
|
903
|
+
_ => return Err(FsError::BaseNotDirectory),
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
Ok(InodeResolution::Found(node.inode()))
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
/// Get the inode associated to a “parent path”. The returned
|
|
911
|
+
/// inode necessarily represents a directory.
|
|
912
|
+
pub(super) fn inode_of_parent(&self, parent_path: &Path) -> Result<InodeResolution> {
|
|
913
|
+
match self.inode_of(parent_path)? {
|
|
914
|
+
InodeResolution::Found(inode_of_parent) => {
|
|
915
|
+
// Ensure it is a directory.
|
|
916
|
+
match self.storage.get(inode_of_parent) {
|
|
917
|
+
Some(Node::Directory(DirectoryNode { .. })) => {
|
|
918
|
+
Ok(InodeResolution::Found(inode_of_parent))
|
|
919
|
+
}
|
|
920
|
+
Some(Node::ArcDirectory(ArcDirectoryNode { fs, path, .. })) => {
|
|
921
|
+
Ok(InodeResolution::Redirect(fs.clone(), path.clone()))
|
|
922
|
+
}
|
|
923
|
+
_ => Err(FsError::BaseNotDirectory),
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
InodeResolution::Redirect(fs, path) => Ok(InodeResolution::Redirect(fs, path)),
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
/// From the inode of a parent node (so, a directory), returns the
|
|
931
|
+
/// child index of `name_of_directory` along with its inode.
|
|
932
|
+
pub(super) fn as_parent_get_position_and_inode_of_directory(
|
|
933
|
+
&self,
|
|
934
|
+
inode_of_parent: Inode,
|
|
935
|
+
name_of_directory: &OsString,
|
|
936
|
+
directory_must_be_empty: DirectoryMustBeEmpty,
|
|
937
|
+
) -> Result<(usize, InodeResolution)> {
|
|
938
|
+
match self.storage.get(inode_of_parent) {
|
|
939
|
+
Some(Node::Directory(DirectoryNode { children, .. })) => children
|
|
940
|
+
.iter()
|
|
941
|
+
.enumerate()
|
|
942
|
+
.filter_map(|(nth, inode)| self.storage.get(*inode).map(|node| (nth, node)))
|
|
943
|
+
.find_map(|(nth, node)| match node {
|
|
944
|
+
Node::Directory(DirectoryNode {
|
|
945
|
+
inode,
|
|
946
|
+
name,
|
|
947
|
+
children,
|
|
948
|
+
..
|
|
949
|
+
}) if name.as_os_str() == name_of_directory => {
|
|
950
|
+
if directory_must_be_empty.no() || children.is_empty() {
|
|
951
|
+
Some(Ok((nth, InodeResolution::Found(*inode))))
|
|
952
|
+
} else {
|
|
953
|
+
Some(Err(FsError::DirectoryNotEmpty))
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
Node::ArcDirectory(ArcDirectoryNode { name, fs, path, .. })
|
|
957
|
+
if name.as_os_str() == name_of_directory =>
|
|
958
|
+
{
|
|
959
|
+
Some(Ok((0, InodeResolution::Redirect(fs.clone(), path.clone()))))
|
|
960
|
+
}
|
|
961
|
+
_ => None,
|
|
962
|
+
})
|
|
963
|
+
.ok_or(FsError::InvalidInput)
|
|
964
|
+
.and_then(identity), // flatten
|
|
965
|
+
|
|
966
|
+
Some(Node::ArcDirectory(ArcDirectoryNode {
|
|
967
|
+
fs, path: fs_path, ..
|
|
968
|
+
})) => {
|
|
969
|
+
let mut path = fs_path.clone();
|
|
970
|
+
path.push(name_of_directory);
|
|
971
|
+
Ok((0, InodeResolution::Redirect(fs.clone(), path)))
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
_ => Err(FsError::BaseNotDirectory),
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
/// From the inode of a parent node (so, a directory), returns the
|
|
979
|
+
/// child index of `name_of_file` along with its inode.
|
|
980
|
+
pub(super) fn as_parent_get_position_and_inode_of_file(
|
|
981
|
+
&self,
|
|
982
|
+
inode_of_parent: Inode,
|
|
983
|
+
name_of_file: &OsString,
|
|
984
|
+
) -> Result<Option<(usize, InodeResolution)>> {
|
|
985
|
+
match self.storage.get(inode_of_parent) {
|
|
986
|
+
Some(Node::Directory(DirectoryNode { children, .. })) => children
|
|
987
|
+
.iter()
|
|
988
|
+
.enumerate()
|
|
989
|
+
.filter_map(|(nth, inode)| self.storage.get(*inode).map(|node| (nth, node)))
|
|
990
|
+
.find_map(|(nth, node)| match node {
|
|
991
|
+
Node::File(FileNode { inode, name, .. })
|
|
992
|
+
| Node::OffloadedFile(OffloadedFileNode { inode, name, .. })
|
|
993
|
+
| Node::ReadOnlyFile(ReadOnlyFileNode { inode, name, .. })
|
|
994
|
+
| Node::CustomFile(CustomFileNode { inode, name, .. })
|
|
995
|
+
| Node::ArcFile(ArcFileNode { inode, name, .. })
|
|
996
|
+
| Node::Symlink(SymlinkNode { inode, name, .. })
|
|
997
|
+
if name.as_os_str() == name_of_file =>
|
|
998
|
+
{
|
|
999
|
+
Some(Some((nth, InodeResolution::Found(*inode))))
|
|
1000
|
+
}
|
|
1001
|
+
_ => None,
|
|
1002
|
+
})
|
|
1003
|
+
.or(Some(None))
|
|
1004
|
+
.ok_or(FsError::InvalidInput),
|
|
1005
|
+
|
|
1006
|
+
Some(Node::ArcDirectory(ArcDirectoryNode {
|
|
1007
|
+
fs, path: fs_path, ..
|
|
1008
|
+
})) => {
|
|
1009
|
+
let mut path = fs_path.clone();
|
|
1010
|
+
path.push(name_of_file);
|
|
1011
|
+
Ok(Some((0, InodeResolution::Redirect(fs.clone(), path))))
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
_ => Err(FsError::BaseNotDirectory),
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
/// From the inode of a parent node (so, a directory), returns the
|
|
1019
|
+
/// child index of `name_of` along with its inode, whatever the
|
|
1020
|
+
/// type of inode is (directory or file).
|
|
1021
|
+
fn as_parent_get_position_and_inode(
|
|
1022
|
+
&self,
|
|
1023
|
+
inode_of_parent: Inode,
|
|
1024
|
+
name_of: &OsString,
|
|
1025
|
+
) -> Result<Option<(usize, InodeResolution)>> {
|
|
1026
|
+
match self.storage.get(inode_of_parent) {
|
|
1027
|
+
Some(Node::Directory(DirectoryNode { children, .. })) => children
|
|
1028
|
+
.iter()
|
|
1029
|
+
.enumerate()
|
|
1030
|
+
.filter_map(|(nth, inode)| self.storage.get(*inode).map(|node| (nth, node)))
|
|
1031
|
+
.find_map(|(nth, node)| match node {
|
|
1032
|
+
Node::File(FileNode { inode, name, .. })
|
|
1033
|
+
| Node::OffloadedFile(OffloadedFileNode { inode, name, .. })
|
|
1034
|
+
| Node::Directory(DirectoryNode { inode, name, .. })
|
|
1035
|
+
| Node::ReadOnlyFile(ReadOnlyFileNode { inode, name, .. })
|
|
1036
|
+
| Node::CustomFile(CustomFileNode { inode, name, .. })
|
|
1037
|
+
| Node::ArcFile(ArcFileNode { inode, name, .. })
|
|
1038
|
+
if name.as_os_str() == name_of =>
|
|
1039
|
+
{
|
|
1040
|
+
Some(Some((nth, InodeResolution::Found(*inode))))
|
|
1041
|
+
}
|
|
1042
|
+
_ => None,
|
|
1043
|
+
})
|
|
1044
|
+
.or(Some(None))
|
|
1045
|
+
.ok_or(FsError::InvalidInput),
|
|
1046
|
+
|
|
1047
|
+
Some(Node::ArcDirectory(ArcDirectoryNode {
|
|
1048
|
+
fs, path: fs_path, ..
|
|
1049
|
+
})) => {
|
|
1050
|
+
let mut path = fs_path.clone();
|
|
1051
|
+
path.push(name_of);
|
|
1052
|
+
Ok(Some((0, InodeResolution::Redirect(fs.clone(), path))))
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
_ => Err(FsError::BaseNotDirectory),
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
/// Set a new name for the node represented by `inode`.
|
|
1060
|
+
pub(super) fn update_node_name(&mut self, inode: Inode, new_name: OsString) -> Result<()> {
|
|
1061
|
+
let time = self.now();
|
|
1062
|
+
let node = self.storage.get_mut(inode).ok_or(FsError::UnknownError)?;
|
|
1063
|
+
|
|
1064
|
+
node.set_name(new_name);
|
|
1065
|
+
node.metadata_mut().modified = time;
|
|
1066
|
+
|
|
1067
|
+
Ok(())
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
/// Add a child to a directory node represented by `inode`.
|
|
1071
|
+
///
|
|
1072
|
+
/// This function also updates the modified time of the directory.
|
|
1073
|
+
///
|
|
1074
|
+
/// # Safety
|
|
1075
|
+
///
|
|
1076
|
+
/// `inode` must represents an existing directory.
|
|
1077
|
+
pub(super) fn add_child_to_node(&mut self, inode: Inode, new_child: Inode) -> Result<()> {
|
|
1078
|
+
let time = self.now();
|
|
1079
|
+
match self.storage.get_mut(inode) {
|
|
1080
|
+
Some(Node::Directory(DirectoryNode {
|
|
1081
|
+
children,
|
|
1082
|
+
metadata: Metadata { modified, .. },
|
|
1083
|
+
..
|
|
1084
|
+
})) => {
|
|
1085
|
+
children.push(new_child);
|
|
1086
|
+
*modified = time;
|
|
1087
|
+
|
|
1088
|
+
Ok(())
|
|
1089
|
+
}
|
|
1090
|
+
_ => Err(FsError::UnknownError),
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
/// Remove the child at position `position` of a directory node
|
|
1095
|
+
/// represented by `inode`.
|
|
1096
|
+
///
|
|
1097
|
+
/// This function also updates the modified time of the directory.
|
|
1098
|
+
///
|
|
1099
|
+
/// # Safety
|
|
1100
|
+
///
|
|
1101
|
+
/// `inode` must represents an existing directory.
|
|
1102
|
+
pub(super) fn remove_child_from_node(&mut self, inode: Inode, position: usize) -> Result<()> {
|
|
1103
|
+
let time = self.now();
|
|
1104
|
+
match self.storage.get_mut(inode) {
|
|
1105
|
+
Some(Node::Directory(DirectoryNode {
|
|
1106
|
+
children,
|
|
1107
|
+
metadata: Metadata { modified, .. },
|
|
1108
|
+
..
|
|
1109
|
+
})) => {
|
|
1110
|
+
children.remove(position);
|
|
1111
|
+
*modified = time;
|
|
1112
|
+
|
|
1113
|
+
Ok(())
|
|
1114
|
+
}
|
|
1115
|
+
_ => Err(FsError::UnknownError),
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
/// Canonicalize a path, i.e. try to resolve to a canonical,
|
|
1120
|
+
/// absolute form of the path with all intermediate components
|
|
1121
|
+
/// normalized:
|
|
1122
|
+
///
|
|
1123
|
+
/// * A path must starts with a root (`/`),
|
|
1124
|
+
/// * A path can contain `..` or `.` components,
|
|
1125
|
+
/// * A path must not contain a Windows prefix (`C:` or `\\server`),
|
|
1126
|
+
/// * A normalized path exists in the file system.
|
|
1127
|
+
pub(super) fn canonicalize(&self, path: &Path) -> Result<(PathBuf, InodeResolution)> {
|
|
1128
|
+
let new_path = self.canonicalize_without_inode(path)?;
|
|
1129
|
+
let inode = self.inode_of(&new_path)?;
|
|
1130
|
+
|
|
1131
|
+
Ok((new_path, inode))
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
/// Like `Self::canonicalize` but without returning the inode of
|
|
1135
|
+
/// the path, which means that there is no guarantee that the path
|
|
1136
|
+
/// exists in the file system.
|
|
1137
|
+
pub(super) fn canonicalize_without_inode(&self, path: &Path) -> Result<PathBuf> {
|
|
1138
|
+
let mut components = path.components();
|
|
1139
|
+
|
|
1140
|
+
match components.next() {
|
|
1141
|
+
Some(Component::RootDir) => {}
|
|
1142
|
+
_ => return Err(FsError::InvalidInput),
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
let mut new_path = PathBuf::with_capacity(path.as_os_str().len());
|
|
1146
|
+
new_path.push("/");
|
|
1147
|
+
|
|
1148
|
+
for component in components {
|
|
1149
|
+
match component {
|
|
1150
|
+
// That's an error to get a `RootDir` a second time.
|
|
1151
|
+
Component::RootDir => return Err(FsError::UnknownError),
|
|
1152
|
+
|
|
1153
|
+
// Nothing to do on `new_path`.
|
|
1154
|
+
Component::CurDir => (),
|
|
1155
|
+
|
|
1156
|
+
// Pop the lastly inserted component on `new_path` if
|
|
1157
|
+
// any, otherwise it's an error.
|
|
1158
|
+
Component::ParentDir => {
|
|
1159
|
+
if !new_path.pop() {
|
|
1160
|
+
return Err(FsError::InvalidInput);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
// A normal
|
|
1165
|
+
Component::Normal(name) => {
|
|
1166
|
+
new_path.push(name);
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
// We don't support Windows path prefix.
|
|
1170
|
+
Component::Prefix(_) => return Err(FsError::InvalidInput),
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
Ok(new_path)
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
impl fmt::Debug for FileSystemInner {
|
|
1179
|
+
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
1180
|
+
writeln!(
|
|
1181
|
+
formatter,
|
|
1182
|
+
"\n{inode:<8} {ty:<4} name",
|
|
1183
|
+
inode = "inode",
|
|
1184
|
+
ty = "type",
|
|
1185
|
+
)?;
|
|
1186
|
+
|
|
1187
|
+
fn debug(
|
|
1188
|
+
nodes: Vec<&Node>,
|
|
1189
|
+
slf: &FileSystemInner,
|
|
1190
|
+
formatter: &mut fmt::Formatter<'_>,
|
|
1191
|
+
indentation: usize,
|
|
1192
|
+
) -> fmt::Result {
|
|
1193
|
+
for node in nodes {
|
|
1194
|
+
writeln!(
|
|
1195
|
+
formatter,
|
|
1196
|
+
"{inode:<8} {ty:<4} {indentation_symbol:indentation_width$}{name}",
|
|
1197
|
+
inode = node.inode(),
|
|
1198
|
+
ty = match node {
|
|
1199
|
+
Node::File { .. } => "file",
|
|
1200
|
+
Node::OffloadedFile { .. } => "offloaded-file",
|
|
1201
|
+
Node::ReadOnlyFile { .. } => "ro-file",
|
|
1202
|
+
Node::ArcFile { .. } => "arc-file",
|
|
1203
|
+
Node::CustomFile { .. } => "custom-file",
|
|
1204
|
+
Node::Symlink { .. } => "symlink",
|
|
1205
|
+
Node::Directory { .. } => "dir",
|
|
1206
|
+
Node::ArcDirectory { .. } => "arc-dir",
|
|
1207
|
+
},
|
|
1208
|
+
name = node.name().to_string_lossy(),
|
|
1209
|
+
indentation_symbol = " ",
|
|
1210
|
+
indentation_width = indentation * 2 + 1,
|
|
1211
|
+
)?;
|
|
1212
|
+
|
|
1213
|
+
if let Node::Directory(DirectoryNode { children, .. }) = node {
|
|
1214
|
+
debug(
|
|
1215
|
+
children
|
|
1216
|
+
.iter()
|
|
1217
|
+
.filter_map(|inode| slf.storage.get(*inode))
|
|
1218
|
+
.collect(),
|
|
1219
|
+
slf,
|
|
1220
|
+
formatter,
|
|
1221
|
+
indentation + 1,
|
|
1222
|
+
)?;
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
Ok(())
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
debug(
|
|
1230
|
+
vec![self.storage.get(ROOT_INODE).unwrap()],
|
|
1231
|
+
self,
|
|
1232
|
+
formatter,
|
|
1233
|
+
0,
|
|
1234
|
+
)
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
impl Default for FileSystemInner {
|
|
1239
|
+
fn default() -> Self {
|
|
1240
|
+
Self::new(MetadataClock::System)
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
#[allow(dead_code)] // The `No` variant.
|
|
1245
|
+
pub(super) enum DirectoryMustBeEmpty {
|
|
1246
|
+
Yes,
|
|
1247
|
+
No,
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
impl DirectoryMustBeEmpty {
|
|
1251
|
+
pub(super) fn yes(&self) -> bool {
|
|
1252
|
+
matches!(self, Self::Yes)
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
pub(super) fn no(&self) -> bool {
|
|
1256
|
+
!self.yes()
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
#[cfg(test)]
|
|
1261
|
+
mod test_filesystem {
|
|
1262
|
+
use std::{path::Path, sync::Arc};
|
|
1263
|
+
|
|
1264
|
+
use shared_buffer::OwnedBuffer;
|
|
1265
|
+
use tokio::io::AsyncReadExt;
|
|
1266
|
+
|
|
1267
|
+
use crate::{DirEntry, FileSystem as FS, FileType, FsError, mem_fs::*, ops};
|
|
1268
|
+
|
|
1269
|
+
macro_rules! path {
|
|
1270
|
+
($path:expr) => {
|
|
1271
|
+
std::path::Path::new($path)
|
|
1272
|
+
};
|
|
1273
|
+
|
|
1274
|
+
(buf $path:expr) => {
|
|
1275
|
+
std::path::PathBuf::from($path)
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
#[tokio::test]
|
|
1280
|
+
async fn test_new_filesystem() {
|
|
1281
|
+
let fs = FileSystem::default();
|
|
1282
|
+
let fs_inner = fs.inner.read().unwrap();
|
|
1283
|
+
|
|
1284
|
+
assert_eq!(fs_inner.storage.len(), 1, "storage has a root");
|
|
1285
|
+
assert!(
|
|
1286
|
+
matches!(
|
|
1287
|
+
fs_inner.storage.get(ROOT_INODE),
|
|
1288
|
+
Some(Node::Directory(DirectoryNode {
|
|
1289
|
+
inode: ROOT_INODE,
|
|
1290
|
+
name,
|
|
1291
|
+
children,
|
|
1292
|
+
..
|
|
1293
|
+
})) if name == "/" && children.is_empty(),
|
|
1294
|
+
),
|
|
1295
|
+
"storage has a well-defined root",
|
|
1296
|
+
);
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
#[tokio::test]
|
|
1300
|
+
async fn test_create_dir() {
|
|
1301
|
+
let fs = FileSystem::default();
|
|
1302
|
+
|
|
1303
|
+
assert_eq!(
|
|
1304
|
+
fs.create_dir(path!("/")),
|
|
1305
|
+
Err(FsError::AlreadyExists),
|
|
1306
|
+
"creating the root which already exists",
|
|
1307
|
+
);
|
|
1308
|
+
|
|
1309
|
+
assert_eq!(fs.create_dir(path!("/foo")), Ok(()), "creating a directory");
|
|
1310
|
+
|
|
1311
|
+
{
|
|
1312
|
+
let fs_inner = fs.inner.read().unwrap();
|
|
1313
|
+
assert_eq!(
|
|
1314
|
+
fs_inner.storage.len(),
|
|
1315
|
+
2,
|
|
1316
|
+
"storage contains the new directory"
|
|
1317
|
+
);
|
|
1318
|
+
assert!(
|
|
1319
|
+
matches!(
|
|
1320
|
+
fs_inner.storage.get(ROOT_INODE),
|
|
1321
|
+
Some(Node::Directory(DirectoryNode {
|
|
1322
|
+
inode: ROOT_INODE,
|
|
1323
|
+
name,
|
|
1324
|
+
children,
|
|
1325
|
+
..
|
|
1326
|
+
})) if name == "/" && children == &[1]
|
|
1327
|
+
),
|
|
1328
|
+
"the root is updated and well-defined",
|
|
1329
|
+
);
|
|
1330
|
+
assert!(
|
|
1331
|
+
matches!(
|
|
1332
|
+
fs_inner.storage.get(1),
|
|
1333
|
+
Some(Node::Directory(DirectoryNode {
|
|
1334
|
+
inode: 1,
|
|
1335
|
+
name,
|
|
1336
|
+
children,
|
|
1337
|
+
..
|
|
1338
|
+
})) if name == "foo" && children.is_empty(),
|
|
1339
|
+
),
|
|
1340
|
+
"the new directory is well-defined",
|
|
1341
|
+
);
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
assert_eq!(
|
|
1345
|
+
fs.create_dir(path!("/foo/bar")),
|
|
1346
|
+
Ok(()),
|
|
1347
|
+
"creating a sub-directory",
|
|
1348
|
+
);
|
|
1349
|
+
|
|
1350
|
+
{
|
|
1351
|
+
let fs_inner = fs.inner.read().unwrap();
|
|
1352
|
+
assert_eq!(
|
|
1353
|
+
fs_inner.storage.len(),
|
|
1354
|
+
3,
|
|
1355
|
+
"storage contains the new sub-directory",
|
|
1356
|
+
);
|
|
1357
|
+
assert!(
|
|
1358
|
+
matches!(
|
|
1359
|
+
fs_inner.storage.get(ROOT_INODE),
|
|
1360
|
+
Some(Node::Directory(DirectoryNode {
|
|
1361
|
+
inode: ROOT_INODE,
|
|
1362
|
+
name,
|
|
1363
|
+
children,
|
|
1364
|
+
..
|
|
1365
|
+
})) if name == "/" && children == &[1]
|
|
1366
|
+
),
|
|
1367
|
+
"the root is updated again and well-defined",
|
|
1368
|
+
);
|
|
1369
|
+
assert!(
|
|
1370
|
+
matches!(
|
|
1371
|
+
fs_inner.storage.get(1),
|
|
1372
|
+
Some(Node::Directory(DirectoryNode {
|
|
1373
|
+
inode: 1,
|
|
1374
|
+
name,
|
|
1375
|
+
children,
|
|
1376
|
+
..
|
|
1377
|
+
})) if name == "foo" && children == &[2]
|
|
1378
|
+
),
|
|
1379
|
+
"the new directory is updated and well-defined",
|
|
1380
|
+
);
|
|
1381
|
+
assert!(
|
|
1382
|
+
matches!(
|
|
1383
|
+
fs_inner.storage.get(2),
|
|
1384
|
+
Some(Node::Directory(DirectoryNode {
|
|
1385
|
+
inode: 2,
|
|
1386
|
+
name,
|
|
1387
|
+
children,
|
|
1388
|
+
..
|
|
1389
|
+
})) if name == "bar" && children.is_empty()
|
|
1390
|
+
),
|
|
1391
|
+
"the new directory is well-defined",
|
|
1392
|
+
);
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
#[tokio::test]
|
|
1397
|
+
async fn test_remove_dir() {
|
|
1398
|
+
let fs = FileSystem::default();
|
|
1399
|
+
|
|
1400
|
+
assert_eq!(
|
|
1401
|
+
fs.remove_dir(path!("/")),
|
|
1402
|
+
Err(FsError::BaseNotDirectory),
|
|
1403
|
+
"removing a directory that has no parent",
|
|
1404
|
+
);
|
|
1405
|
+
|
|
1406
|
+
assert_eq!(
|
|
1407
|
+
fs.remove_dir(path!("/foo")),
|
|
1408
|
+
Err(FsError::EntryNotFound),
|
|
1409
|
+
"cannot remove a directory that doesn't exist",
|
|
1410
|
+
);
|
|
1411
|
+
|
|
1412
|
+
assert_eq!(fs.create_dir(path!("/foo")), Ok(()), "creating a directory");
|
|
1413
|
+
|
|
1414
|
+
assert_eq!(
|
|
1415
|
+
fs.create_dir(path!("/foo/bar")),
|
|
1416
|
+
Ok(()),
|
|
1417
|
+
"creating a sub-directory",
|
|
1418
|
+
);
|
|
1419
|
+
|
|
1420
|
+
{
|
|
1421
|
+
let fs_inner = fs.inner.read().unwrap();
|
|
1422
|
+
assert_eq!(
|
|
1423
|
+
fs_inner.storage.len(),
|
|
1424
|
+
3,
|
|
1425
|
+
"storage contains all the directories",
|
|
1426
|
+
);
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
assert_eq!(
|
|
1430
|
+
fs.remove_dir(path!("/foo")),
|
|
1431
|
+
Err(FsError::DirectoryNotEmpty),
|
|
1432
|
+
"removing a directory that has children",
|
|
1433
|
+
);
|
|
1434
|
+
|
|
1435
|
+
assert_eq!(
|
|
1436
|
+
fs.remove_dir(path!("/foo/bar")),
|
|
1437
|
+
Ok(()),
|
|
1438
|
+
"removing a sub-directory",
|
|
1439
|
+
);
|
|
1440
|
+
|
|
1441
|
+
assert_eq!(fs.remove_dir(path!("/foo")), Ok(()), "removing a directory");
|
|
1442
|
+
|
|
1443
|
+
{
|
|
1444
|
+
let fs_inner = fs.inner.read().unwrap();
|
|
1445
|
+
assert_eq!(
|
|
1446
|
+
fs_inner.storage.len(),
|
|
1447
|
+
1,
|
|
1448
|
+
"storage contains all the directories",
|
|
1449
|
+
);
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
#[tokio::test]
|
|
1454
|
+
async fn test_rename() {
|
|
1455
|
+
let fs = FileSystem::default();
|
|
1456
|
+
|
|
1457
|
+
let fs2 = FileSystem::default();
|
|
1458
|
+
fs2.create_dir(path!("/boz")).unwrap();
|
|
1459
|
+
let arc_fs2: Arc<dyn crate::FileSystem + Send + Sync> = Arc::new(fs2);
|
|
1460
|
+
|
|
1461
|
+
assert_eq!(
|
|
1462
|
+
fs.rename(path!("/"), path!("/bar")).await,
|
|
1463
|
+
Err(FsError::BaseNotDirectory),
|
|
1464
|
+
"renaming a directory that has no parent",
|
|
1465
|
+
);
|
|
1466
|
+
assert_eq!(
|
|
1467
|
+
fs.rename(path!("/foo"), path!("/")).await,
|
|
1468
|
+
Err(FsError::BaseNotDirectory),
|
|
1469
|
+
"renaming to a directory that has no parent",
|
|
1470
|
+
);
|
|
1471
|
+
|
|
1472
|
+
assert_eq!(fs.create_dir(path!("/foo")), Ok(()));
|
|
1473
|
+
assert_eq!(fs.create_dir(path!("/foo/qux")), Ok(()));
|
|
1474
|
+
|
|
1475
|
+
assert_eq!(
|
|
1476
|
+
fs.rename(path!("/foo"), path!("/bar/baz")).await,
|
|
1477
|
+
Err(FsError::EntryNotFound),
|
|
1478
|
+
"renaming to a directory that has parent that doesn't exist",
|
|
1479
|
+
);
|
|
1480
|
+
|
|
1481
|
+
assert_eq!(fs.create_dir(path!("/bar")), Ok(()));
|
|
1482
|
+
|
|
1483
|
+
assert!(
|
|
1484
|
+
fs.new_open_options()
|
|
1485
|
+
.write(true)
|
|
1486
|
+
.create_new(true)
|
|
1487
|
+
.open(path!("/bar/hello1.txt"))
|
|
1488
|
+
.is_ok(),
|
|
1489
|
+
"creating a new file (`hello1.txt`)",
|
|
1490
|
+
);
|
|
1491
|
+
assert!(
|
|
1492
|
+
fs.new_open_options()
|
|
1493
|
+
.write(true)
|
|
1494
|
+
.create_new(true)
|
|
1495
|
+
.open(path!("/bar/hello2.txt"))
|
|
1496
|
+
.is_ok(),
|
|
1497
|
+
"creating a new file (`hello2.txt`)",
|
|
1498
|
+
);
|
|
1499
|
+
|
|
1500
|
+
fs.mount(path!(buf "/mnt"), &arc_fs2, path!(buf "/"))
|
|
1501
|
+
.unwrap();
|
|
1502
|
+
|
|
1503
|
+
{
|
|
1504
|
+
let fs_inner = fs.inner.read().unwrap();
|
|
1505
|
+
|
|
1506
|
+
assert_eq!(fs_inner.storage.len(), 7, "storage has all files");
|
|
1507
|
+
assert!(
|
|
1508
|
+
matches!(
|
|
1509
|
+
fs_inner.storage.get(ROOT_INODE),
|
|
1510
|
+
Some(Node::Directory(DirectoryNode {
|
|
1511
|
+
inode: ROOT_INODE,
|
|
1512
|
+
name,
|
|
1513
|
+
children,
|
|
1514
|
+
..
|
|
1515
|
+
})) if name == "/" && children == &[1, 3, 6]
|
|
1516
|
+
),
|
|
1517
|
+
"`/` contains `foo` and `bar` and `mnt`",
|
|
1518
|
+
);
|
|
1519
|
+
assert!(
|
|
1520
|
+
matches!(
|
|
1521
|
+
fs_inner.storage.get(1),
|
|
1522
|
+
Some(Node::Directory(DirectoryNode {
|
|
1523
|
+
inode: 1,
|
|
1524
|
+
name,
|
|
1525
|
+
children,
|
|
1526
|
+
..
|
|
1527
|
+
})) if name == "foo" && children == &[2]
|
|
1528
|
+
),
|
|
1529
|
+
"`foo` contains `qux`",
|
|
1530
|
+
);
|
|
1531
|
+
assert!(
|
|
1532
|
+
matches!(
|
|
1533
|
+
fs_inner.storage.get(2),
|
|
1534
|
+
Some(Node::Directory(DirectoryNode {
|
|
1535
|
+
inode: 2,
|
|
1536
|
+
name,
|
|
1537
|
+
children,
|
|
1538
|
+
..
|
|
1539
|
+
})) if name == "qux" && children.is_empty()
|
|
1540
|
+
),
|
|
1541
|
+
"`qux` is empty",
|
|
1542
|
+
);
|
|
1543
|
+
assert!(
|
|
1544
|
+
matches!(
|
|
1545
|
+
fs_inner.storage.get(3),
|
|
1546
|
+
Some(Node::Directory(DirectoryNode {
|
|
1547
|
+
inode: 3,
|
|
1548
|
+
name,
|
|
1549
|
+
children,
|
|
1550
|
+
..
|
|
1551
|
+
})) if name == "bar" && children == &[4, 5]
|
|
1552
|
+
),
|
|
1553
|
+
"`bar` is contains `hello.txt`",
|
|
1554
|
+
);
|
|
1555
|
+
assert!(
|
|
1556
|
+
matches!(
|
|
1557
|
+
fs_inner.storage.get(4),
|
|
1558
|
+
Some(Node::File(FileNode {
|
|
1559
|
+
inode: 4,
|
|
1560
|
+
name,
|
|
1561
|
+
..
|
|
1562
|
+
})) if name == "hello1.txt"
|
|
1563
|
+
),
|
|
1564
|
+
"`hello1.txt` exists",
|
|
1565
|
+
);
|
|
1566
|
+
assert!(
|
|
1567
|
+
matches!(
|
|
1568
|
+
fs_inner.storage.get(5),
|
|
1569
|
+
Some(Node::File(FileNode {
|
|
1570
|
+
inode: 5,
|
|
1571
|
+
name,
|
|
1572
|
+
..
|
|
1573
|
+
})) if name == "hello2.txt"
|
|
1574
|
+
),
|
|
1575
|
+
"`hello2.txt` exists",
|
|
1576
|
+
);
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
assert_eq!(
|
|
1580
|
+
fs.rename(path!("/bar/hello2.txt"), path!("/foo/world2.txt"))
|
|
1581
|
+
.await,
|
|
1582
|
+
Ok(()),
|
|
1583
|
+
"renaming (and moving) a file",
|
|
1584
|
+
);
|
|
1585
|
+
|
|
1586
|
+
assert_eq!(
|
|
1587
|
+
fs.rename(path!("/foo"), path!("/bar/baz")).await,
|
|
1588
|
+
Ok(()),
|
|
1589
|
+
"renaming a directory",
|
|
1590
|
+
);
|
|
1591
|
+
|
|
1592
|
+
assert_eq!(
|
|
1593
|
+
fs.rename(path!("/mnt/boz"), path!("/mnt/cat")).await,
|
|
1594
|
+
Ok(()),
|
|
1595
|
+
"renaming a directory within a mounted FS"
|
|
1596
|
+
);
|
|
1597
|
+
|
|
1598
|
+
assert!(
|
|
1599
|
+
arc_fs2.read_dir(path!("/cat")).is_ok(),
|
|
1600
|
+
"The directory was renamed in the inner FS"
|
|
1601
|
+
);
|
|
1602
|
+
|
|
1603
|
+
assert_eq!(
|
|
1604
|
+
fs.rename(path!("/bar/hello1.txt"), path!("/bar/world1.txt"))
|
|
1605
|
+
.await,
|
|
1606
|
+
Ok(()),
|
|
1607
|
+
"renaming a file (in the same directory)",
|
|
1608
|
+
);
|
|
1609
|
+
|
|
1610
|
+
{
|
|
1611
|
+
let fs_inner = fs.inner.read().unwrap();
|
|
1612
|
+
|
|
1613
|
+
dbg!(&fs_inner);
|
|
1614
|
+
|
|
1615
|
+
assert_eq!(
|
|
1616
|
+
fs_inner.storage.len(),
|
|
1617
|
+
7,
|
|
1618
|
+
"storage has still all directories"
|
|
1619
|
+
);
|
|
1620
|
+
assert!(
|
|
1621
|
+
matches!(
|
|
1622
|
+
fs_inner.storage.get(ROOT_INODE),
|
|
1623
|
+
Some(Node::Directory(DirectoryNode {
|
|
1624
|
+
inode: ROOT_INODE,
|
|
1625
|
+
name,
|
|
1626
|
+
children,
|
|
1627
|
+
..
|
|
1628
|
+
})) if name == "/" && children == &[3, 6]
|
|
1629
|
+
),
|
|
1630
|
+
"`/` contains `bar` and `mnt`",
|
|
1631
|
+
);
|
|
1632
|
+
assert!(
|
|
1633
|
+
matches!(
|
|
1634
|
+
fs_inner.storage.get(1),
|
|
1635
|
+
Some(Node::Directory(DirectoryNode {
|
|
1636
|
+
inode: 1,
|
|
1637
|
+
name,
|
|
1638
|
+
children,
|
|
1639
|
+
..
|
|
1640
|
+
})) if name == "baz" && children == &[2, 5]
|
|
1641
|
+
),
|
|
1642
|
+
"`foo` has been renamed to `baz` and contains `qux` and `world2.txt`",
|
|
1643
|
+
);
|
|
1644
|
+
assert!(
|
|
1645
|
+
matches!(
|
|
1646
|
+
fs_inner.storage.get(2),
|
|
1647
|
+
Some(Node::Directory(DirectoryNode {
|
|
1648
|
+
inode: 2,
|
|
1649
|
+
name,
|
|
1650
|
+
children,
|
|
1651
|
+
..
|
|
1652
|
+
})) if name == "qux" && children.is_empty()
|
|
1653
|
+
),
|
|
1654
|
+
"`qux` is empty",
|
|
1655
|
+
);
|
|
1656
|
+
assert!(
|
|
1657
|
+
matches!(
|
|
1658
|
+
fs_inner.storage.get(3),
|
|
1659
|
+
Some(Node::Directory(DirectoryNode {
|
|
1660
|
+
inode: 3,
|
|
1661
|
+
name,
|
|
1662
|
+
children,
|
|
1663
|
+
..
|
|
1664
|
+
})) if name == "bar" && children == &[4, 1]
|
|
1665
|
+
),
|
|
1666
|
+
"`bar` contains `bar` (ex `foo`) and `world1.txt` (ex `hello1`)",
|
|
1667
|
+
);
|
|
1668
|
+
assert!(
|
|
1669
|
+
matches!(
|
|
1670
|
+
fs_inner.storage.get(4),
|
|
1671
|
+
Some(Node::File(FileNode {
|
|
1672
|
+
inode: 4,
|
|
1673
|
+
name,
|
|
1674
|
+
..
|
|
1675
|
+
})) if name == "world1.txt"
|
|
1676
|
+
),
|
|
1677
|
+
"`hello1.txt` has been renamed to `world1.txt`",
|
|
1678
|
+
);
|
|
1679
|
+
assert!(
|
|
1680
|
+
matches!(
|
|
1681
|
+
fs_inner.storage.get(5),
|
|
1682
|
+
Some(Node::File(FileNode {
|
|
1683
|
+
inode: 5,
|
|
1684
|
+
name,
|
|
1685
|
+
..
|
|
1686
|
+
})) if name == "world2.txt"
|
|
1687
|
+
),
|
|
1688
|
+
"`hello2.txt` has been renamed to `world2.txt`",
|
|
1689
|
+
);
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
#[tokio::test]
|
|
1694
|
+
async fn test_metadata() {
|
|
1695
|
+
use std::thread::sleep;
|
|
1696
|
+
use std::time::Duration;
|
|
1697
|
+
|
|
1698
|
+
let fs = FileSystem::default();
|
|
1699
|
+
let root_metadata = fs.metadata(path!("/"));
|
|
1700
|
+
|
|
1701
|
+
assert!(matches!(
|
|
1702
|
+
root_metadata,
|
|
1703
|
+
Ok(Metadata {
|
|
1704
|
+
ft: FileType { dir: true, .. },
|
|
1705
|
+
accessed,
|
|
1706
|
+
created,
|
|
1707
|
+
modified,
|
|
1708
|
+
len: 0
|
|
1709
|
+
}) if accessed == created && created == modified && modified > 0
|
|
1710
|
+
));
|
|
1711
|
+
|
|
1712
|
+
assert_eq!(fs.create_dir(path!("/foo")), Ok(()));
|
|
1713
|
+
|
|
1714
|
+
let foo_metadata = fs.metadata(path!("/foo"));
|
|
1715
|
+
assert!(foo_metadata.is_ok());
|
|
1716
|
+
let foo_metadata = foo_metadata.unwrap();
|
|
1717
|
+
|
|
1718
|
+
assert!(matches!(
|
|
1719
|
+
foo_metadata,
|
|
1720
|
+
Metadata {
|
|
1721
|
+
ft: FileType { dir: true, .. },
|
|
1722
|
+
accessed,
|
|
1723
|
+
created,
|
|
1724
|
+
modified,
|
|
1725
|
+
len: 0
|
|
1726
|
+
} if accessed == created && created == modified && modified > 0
|
|
1727
|
+
));
|
|
1728
|
+
|
|
1729
|
+
sleep(Duration::from_secs(3));
|
|
1730
|
+
|
|
1731
|
+
assert_eq!(fs.rename(path!("/foo"), path!("/bar")).await, Ok(()));
|
|
1732
|
+
|
|
1733
|
+
assert!(
|
|
1734
|
+
matches!(
|
|
1735
|
+
fs.metadata(path!("/bar")),
|
|
1736
|
+
Ok(Metadata {
|
|
1737
|
+
ft: FileType { dir: true, .. },
|
|
1738
|
+
accessed,
|
|
1739
|
+
created,
|
|
1740
|
+
modified,
|
|
1741
|
+
len: 0
|
|
1742
|
+
}) if
|
|
1743
|
+
accessed == foo_metadata.accessed &&
|
|
1744
|
+
created == foo_metadata.created &&
|
|
1745
|
+
modified > foo_metadata.modified
|
|
1746
|
+
),
|
|
1747
|
+
"the modified time is updated when file is renamed",
|
|
1748
|
+
);
|
|
1749
|
+
assert!(
|
|
1750
|
+
matches!(
|
|
1751
|
+
fs.metadata(path!("/")),
|
|
1752
|
+
Ok(Metadata {
|
|
1753
|
+
ft: FileType { dir: true, .. },
|
|
1754
|
+
accessed,
|
|
1755
|
+
created,
|
|
1756
|
+
modified,
|
|
1757
|
+
len: 0
|
|
1758
|
+
}) if
|
|
1759
|
+
accessed <= foo_metadata.accessed &&
|
|
1760
|
+
created <= foo_metadata.created &&
|
|
1761
|
+
modified > foo_metadata.modified
|
|
1762
|
+
),
|
|
1763
|
+
"the modified time of the parent is updated when file is renamed \n{:?}\n{:?}",
|
|
1764
|
+
fs.metadata(path!("/")),
|
|
1765
|
+
foo_metadata,
|
|
1766
|
+
);
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
#[tokio::test]
|
|
1770
|
+
async fn test_remove_file() {
|
|
1771
|
+
let fs = FileSystem::default();
|
|
1772
|
+
|
|
1773
|
+
let mut file = fs
|
|
1774
|
+
.new_open_options()
|
|
1775
|
+
.read(true)
|
|
1776
|
+
.write(true)
|
|
1777
|
+
.create_new(true)
|
|
1778
|
+
.open(path!("/foo.txt"))
|
|
1779
|
+
.expect("creating a new file");
|
|
1780
|
+
use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
|
|
1781
|
+
file.write_all(b"foo").await.expect("write before remove");
|
|
1782
|
+
|
|
1783
|
+
{
|
|
1784
|
+
let fs_inner = fs.inner.read().unwrap();
|
|
1785
|
+
|
|
1786
|
+
assert_eq!(fs_inner.storage.len(), 2, "storage has all files");
|
|
1787
|
+
assert!(
|
|
1788
|
+
matches!(
|
|
1789
|
+
fs_inner.storage.get(ROOT_INODE),
|
|
1790
|
+
Some(Node::Directory(DirectoryNode {
|
|
1791
|
+
inode: ROOT_INODE,
|
|
1792
|
+
name,
|
|
1793
|
+
children,
|
|
1794
|
+
..
|
|
1795
|
+
})) if name == "/" && children == &[1]
|
|
1796
|
+
),
|
|
1797
|
+
"`/` contains `foo.txt`",
|
|
1798
|
+
);
|
|
1799
|
+
assert!(
|
|
1800
|
+
matches!(
|
|
1801
|
+
fs_inner.storage.get(1),
|
|
1802
|
+
Some(Node::File(FileNode {
|
|
1803
|
+
inode: 1,
|
|
1804
|
+
name,
|
|
1805
|
+
..
|
|
1806
|
+
})) if name == "foo.txt"
|
|
1807
|
+
),
|
|
1808
|
+
"`foo.txt` exists and is a file",
|
|
1809
|
+
);
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
assert_eq!(
|
|
1813
|
+
fs.remove_file(path!("/foo.txt")),
|
|
1814
|
+
Ok(()),
|
|
1815
|
+
"removing a file that exists",
|
|
1816
|
+
);
|
|
1817
|
+
|
|
1818
|
+
{
|
|
1819
|
+
let fs_inner = fs.inner.read().unwrap();
|
|
1820
|
+
|
|
1821
|
+
assert_eq!(
|
|
1822
|
+
fs_inner.storage.len(),
|
|
1823
|
+
2,
|
|
1824
|
+
"storage keeps the unlinked file alive while a handle is open"
|
|
1825
|
+
);
|
|
1826
|
+
assert!(
|
|
1827
|
+
matches!(
|
|
1828
|
+
fs_inner.storage.get(ROOT_INODE),
|
|
1829
|
+
Some(Node::Directory(DirectoryNode {
|
|
1830
|
+
inode: ROOT_INODE,
|
|
1831
|
+
name,
|
|
1832
|
+
children,
|
|
1833
|
+
..
|
|
1834
|
+
})) if name == "/" && children.is_empty()
|
|
1835
|
+
),
|
|
1836
|
+
"`/` is empty",
|
|
1837
|
+
);
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
assert!(
|
|
1841
|
+
matches!(
|
|
1842
|
+
fs.new_open_options().read(true).open(path!("/foo.txt")),
|
|
1843
|
+
Err(FsError::EntryNotFound)
|
|
1844
|
+
),
|
|
1845
|
+
"the removed path can no longer be reopened",
|
|
1846
|
+
);
|
|
1847
|
+
|
|
1848
|
+
file.write_all(b"bar")
|
|
1849
|
+
.await
|
|
1850
|
+
.expect("write after remove_file should still work");
|
|
1851
|
+
file.seek(std::io::SeekFrom::Start(0))
|
|
1852
|
+
.await
|
|
1853
|
+
.expect("rewind after remove_file");
|
|
1854
|
+
let mut contents = String::new();
|
|
1855
|
+
file.read_to_string(&mut contents)
|
|
1856
|
+
.await
|
|
1857
|
+
.expect("read after remove_file should still work");
|
|
1858
|
+
assert_eq!(contents, "foobar");
|
|
1859
|
+
|
|
1860
|
+
drop(file);
|
|
1861
|
+
|
|
1862
|
+
let fs_inner = fs.inner.read().unwrap();
|
|
1863
|
+
assert_eq!(
|
|
1864
|
+
fs_inner.storage.len(),
|
|
1865
|
+
1,
|
|
1866
|
+
"storage drops the file once the last open handle closes"
|
|
1867
|
+
);
|
|
1868
|
+
|
|
1869
|
+
assert_eq!(
|
|
1870
|
+
fs.remove_file(path!("/foo.txt")),
|
|
1871
|
+
Err(FsError::EntryNotFound),
|
|
1872
|
+
"removing a file that exists",
|
|
1873
|
+
);
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
#[tokio::test]
|
|
1877
|
+
async fn test_readdir() {
|
|
1878
|
+
let fs = FileSystem::default();
|
|
1879
|
+
|
|
1880
|
+
assert_eq!(fs.create_dir(path!("/foo")), Ok(()), "creating `foo`");
|
|
1881
|
+
assert_eq!(fs.create_dir(path!("/foo/sub")), Ok(()), "creating `sub`");
|
|
1882
|
+
assert_eq!(fs.create_dir(path!("/bar")), Ok(()), "creating `bar`");
|
|
1883
|
+
assert_eq!(fs.create_dir(path!("/baz")), Ok(()), "creating `bar`");
|
|
1884
|
+
assert!(
|
|
1885
|
+
fs.new_open_options()
|
|
1886
|
+
.write(true)
|
|
1887
|
+
.create_new(true)
|
|
1888
|
+
.open(path!("/a.txt"))
|
|
1889
|
+
.is_ok(),
|
|
1890
|
+
"creating `a.txt`",
|
|
1891
|
+
);
|
|
1892
|
+
assert!(
|
|
1893
|
+
fs.new_open_options()
|
|
1894
|
+
.write(true)
|
|
1895
|
+
.create_new(true)
|
|
1896
|
+
.open(path!("/b.txt"))
|
|
1897
|
+
.is_ok(),
|
|
1898
|
+
"creating `b.txt`",
|
|
1899
|
+
);
|
|
1900
|
+
|
|
1901
|
+
let readdir = fs.read_dir(path!("/"));
|
|
1902
|
+
|
|
1903
|
+
assert!(readdir.is_ok(), "reading the directory `/`");
|
|
1904
|
+
|
|
1905
|
+
let mut readdir = readdir.unwrap();
|
|
1906
|
+
|
|
1907
|
+
assert!(
|
|
1908
|
+
matches!(
|
|
1909
|
+
readdir.next(),
|
|
1910
|
+
Some(Ok(DirEntry {
|
|
1911
|
+
path,
|
|
1912
|
+
metadata: Ok(Metadata { ft, .. }),
|
|
1913
|
+
}))
|
|
1914
|
+
if path.as_path() == path!("/foo") && ft.is_dir()
|
|
1915
|
+
),
|
|
1916
|
+
"checking entry #1",
|
|
1917
|
+
);
|
|
1918
|
+
assert!(
|
|
1919
|
+
matches!(
|
|
1920
|
+
readdir.next(),
|
|
1921
|
+
Some(Ok(DirEntry {
|
|
1922
|
+
path,
|
|
1923
|
+
metadata: Ok(Metadata { ft, .. }),
|
|
1924
|
+
}))
|
|
1925
|
+
if path.as_path() == path!("/bar") && ft.is_dir()
|
|
1926
|
+
),
|
|
1927
|
+
"checking entry #2",
|
|
1928
|
+
);
|
|
1929
|
+
assert!(
|
|
1930
|
+
matches!(
|
|
1931
|
+
readdir.next(),
|
|
1932
|
+
Some(Ok(DirEntry {
|
|
1933
|
+
path,
|
|
1934
|
+
metadata: Ok(Metadata { ft, .. }),
|
|
1935
|
+
}))
|
|
1936
|
+
if path.as_path() == path!("/baz") && ft.is_dir()
|
|
1937
|
+
),
|
|
1938
|
+
"checking entry #3",
|
|
1939
|
+
);
|
|
1940
|
+
assert!(
|
|
1941
|
+
matches!(
|
|
1942
|
+
readdir.next(),
|
|
1943
|
+
Some(Ok(DirEntry {
|
|
1944
|
+
path,
|
|
1945
|
+
metadata: Ok(Metadata { ft, .. }),
|
|
1946
|
+
}))
|
|
1947
|
+
if path.as_path() == path!("/a.txt") && ft.is_file()
|
|
1948
|
+
),
|
|
1949
|
+
"checking entry #4",
|
|
1950
|
+
);
|
|
1951
|
+
assert!(
|
|
1952
|
+
matches!(
|
|
1953
|
+
readdir.next(),
|
|
1954
|
+
Some(Ok(DirEntry {
|
|
1955
|
+
path,
|
|
1956
|
+
metadata: Ok(Metadata { ft, .. }),
|
|
1957
|
+
}))
|
|
1958
|
+
if path.as_path() == path!("/b.txt") && ft.is_file()
|
|
1959
|
+
),
|
|
1960
|
+
"checking entry #5",
|
|
1961
|
+
);
|
|
1962
|
+
assert!(readdir.next().is_none(), "no more entries");
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
#[tokio::test]
|
|
1966
|
+
async fn test_canonicalize() {
|
|
1967
|
+
let fs = FileSystem::default();
|
|
1968
|
+
|
|
1969
|
+
assert_eq!(fs.create_dir(path!("/foo")), Ok(()), "creating `foo`");
|
|
1970
|
+
assert_eq!(fs.create_dir(path!("/foo/bar")), Ok(()), "creating `bar`");
|
|
1971
|
+
assert_eq!(
|
|
1972
|
+
fs.create_dir(path!("/foo/bar/baz")),
|
|
1973
|
+
Ok(()),
|
|
1974
|
+
"creating `baz`",
|
|
1975
|
+
);
|
|
1976
|
+
assert_eq!(
|
|
1977
|
+
fs.create_dir(path!("/foo/bar/baz/qux")),
|
|
1978
|
+
Ok(()),
|
|
1979
|
+
"creating `qux`",
|
|
1980
|
+
);
|
|
1981
|
+
assert!(
|
|
1982
|
+
fs.new_open_options()
|
|
1983
|
+
.write(true)
|
|
1984
|
+
.create_new(true)
|
|
1985
|
+
.open(path!("/foo/bar/baz/qux/hello.txt"))
|
|
1986
|
+
.is_ok(),
|
|
1987
|
+
"creating `hello.txt`",
|
|
1988
|
+
);
|
|
1989
|
+
|
|
1990
|
+
let fs_inner = fs.inner.read().unwrap();
|
|
1991
|
+
|
|
1992
|
+
assert_eq!(
|
|
1993
|
+
fs_inner
|
|
1994
|
+
.canonicalize(path!("/"))
|
|
1995
|
+
.map(|(a, b)| (a, b.unwrap())),
|
|
1996
|
+
Ok((path!(buf "/"), ROOT_INODE)),
|
|
1997
|
+
"canonicalizing `/`",
|
|
1998
|
+
);
|
|
1999
|
+
assert_eq!(
|
|
2000
|
+
fs_inner
|
|
2001
|
+
.canonicalize(path!("foo"))
|
|
2002
|
+
.map(|(a, b)| (a, b.unwrap())),
|
|
2003
|
+
Err(FsError::InvalidInput),
|
|
2004
|
+
"canonicalizing `foo`",
|
|
2005
|
+
);
|
|
2006
|
+
assert_eq!(
|
|
2007
|
+
fs_inner
|
|
2008
|
+
.canonicalize(path!("/././././foo/"))
|
|
2009
|
+
.map(|(a, b)| (a, b.unwrap())),
|
|
2010
|
+
Ok((path!(buf "/foo"), 1)),
|
|
2011
|
+
"canonicalizing `/././././foo/`",
|
|
2012
|
+
);
|
|
2013
|
+
assert_eq!(
|
|
2014
|
+
fs_inner
|
|
2015
|
+
.canonicalize(path!("/foo/bar//"))
|
|
2016
|
+
.map(|(a, b)| (a, b.unwrap())),
|
|
2017
|
+
Ok((path!(buf "/foo/bar"), 2)),
|
|
2018
|
+
"canonicalizing `/foo/bar//`",
|
|
2019
|
+
);
|
|
2020
|
+
assert_eq!(
|
|
2021
|
+
fs_inner
|
|
2022
|
+
.canonicalize(path!("/foo/bar/../bar"))
|
|
2023
|
+
.map(|(a, b)| (a, b.unwrap())),
|
|
2024
|
+
Ok((path!(buf "/foo/bar"), 2)),
|
|
2025
|
+
"canonicalizing `/foo/bar/../bar`",
|
|
2026
|
+
);
|
|
2027
|
+
assert_eq!(
|
|
2028
|
+
fs_inner
|
|
2029
|
+
.canonicalize(path!("/foo/bar/../.."))
|
|
2030
|
+
.map(|(a, b)| (a, b.unwrap())),
|
|
2031
|
+
Ok((path!(buf "/"), ROOT_INODE)),
|
|
2032
|
+
"canonicalizing `/foo/bar/../..`",
|
|
2033
|
+
);
|
|
2034
|
+
assert_eq!(
|
|
2035
|
+
fs_inner
|
|
2036
|
+
.canonicalize(path!("/foo/bar/../../.."))
|
|
2037
|
+
.map(|(a, b)| (a, b.unwrap())),
|
|
2038
|
+
Err(FsError::InvalidInput),
|
|
2039
|
+
"canonicalizing `/foo/bar/../../..`",
|
|
2040
|
+
);
|
|
2041
|
+
assert_eq!(
|
|
2042
|
+
fs_inner
|
|
2043
|
+
.canonicalize(path!("C:/foo/"))
|
|
2044
|
+
.map(|(a, b)| (a, b.unwrap())),
|
|
2045
|
+
Err(FsError::InvalidInput),
|
|
2046
|
+
"canonicalizing `C:/foo/`",
|
|
2047
|
+
);
|
|
2048
|
+
assert_eq!(
|
|
2049
|
+
fs_inner
|
|
2050
|
+
.canonicalize(path!(
|
|
2051
|
+
"/foo/./../foo/bar/../../foo/bar/./baz/./../baz/qux/../../baz/./qux/hello.txt"
|
|
2052
|
+
))
|
|
2053
|
+
.map(|(a, b)| (a, b.unwrap())),
|
|
2054
|
+
Ok((path!(buf "/foo/bar/baz/qux/hello.txt"), 5)),
|
|
2055
|
+
"canonicalizing a crazily stupid path name",
|
|
2056
|
+
);
|
|
2057
|
+
}
|
|
2058
|
+
|
|
2059
|
+
#[tokio::test]
|
|
2060
|
+
#[ignore = "Not yet supported. See https://github.com/wasmerio/wasmer/issues/3678"]
|
|
2061
|
+
async fn mount_to_overlapping_directories() {
|
|
2062
|
+
let top_level = FileSystem::default();
|
|
2063
|
+
ops::touch(&top_level, "/file.txt").unwrap();
|
|
2064
|
+
let nested = FileSystem::default();
|
|
2065
|
+
ops::touch(&nested, "/another-file.txt").unwrap();
|
|
2066
|
+
let top_level: Arc<dyn crate::FileSystem + Send + Sync> = Arc::new(top_level);
|
|
2067
|
+
let nested: Arc<dyn crate::FileSystem + Send + Sync> = Arc::new(nested);
|
|
2068
|
+
|
|
2069
|
+
let fs = FileSystem::default();
|
|
2070
|
+
fs.mount("/top-level".into(), &top_level, "/".into())
|
|
2071
|
+
.unwrap();
|
|
2072
|
+
fs.mount("/top-level/nested".into(), &nested, "/".into())
|
|
2073
|
+
.unwrap();
|
|
2074
|
+
|
|
2075
|
+
assert!(ops::is_dir(&fs, "/top-level"));
|
|
2076
|
+
assert!(ops::is_file(&fs, "/top-level/file.txt"));
|
|
2077
|
+
assert!(ops::is_dir(&fs, "/top-level/nested"));
|
|
2078
|
+
assert!(ops::is_file(&fs, "/top-level/nested/another-file.txt"));
|
|
2079
|
+
}
|
|
2080
|
+
|
|
2081
|
+
#[tokio::test]
|
|
2082
|
+
async fn read_dir_rebases_mount_paths() {
|
|
2083
|
+
let mounted = FileSystem::default();
|
|
2084
|
+
ops::touch(&mounted, "/file.txt").unwrap();
|
|
2085
|
+
let mounted: Arc<dyn crate::FileSystem + Send + Sync> = Arc::new(mounted);
|
|
2086
|
+
|
|
2087
|
+
let fs = FileSystem::default();
|
|
2088
|
+
fs.mount("/mnt".into(), &mounted, "/".into()).unwrap();
|
|
2089
|
+
|
|
2090
|
+
let entries: Vec<_> = fs
|
|
2091
|
+
.read_dir(Path::new("/mnt"))
|
|
2092
|
+
.unwrap()
|
|
2093
|
+
.map(|e| e.unwrap().path)
|
|
2094
|
+
.collect();
|
|
2095
|
+
|
|
2096
|
+
assert_eq!(entries, vec![Path::new("/mnt/file.txt").to_path_buf()]);
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
#[tokio::test]
|
|
2100
|
+
async fn test_merge_flat() {
|
|
2101
|
+
let main = FileSystem::default();
|
|
2102
|
+
|
|
2103
|
+
let other = FileSystem::default();
|
|
2104
|
+
crate::ops::create_dir_all(&other, "/a/x").unwrap();
|
|
2105
|
+
other
|
|
2106
|
+
.insert_ro_file(Path::new("/a/x/a.txt"), OwnedBuffer::from_static(b"a"))
|
|
2107
|
+
.unwrap();
|
|
2108
|
+
other
|
|
2109
|
+
.insert_ro_file(Path::new("/a/x/b.txt"), OwnedBuffer::from_static(b"b"))
|
|
2110
|
+
.unwrap();
|
|
2111
|
+
other
|
|
2112
|
+
.insert_ro_file(Path::new("/a/x/c.txt"), OwnedBuffer::from_static(b"c"))
|
|
2113
|
+
.unwrap();
|
|
2114
|
+
|
|
2115
|
+
let out = other.read_dir(Path::new("/")).unwrap();
|
|
2116
|
+
dbg!(&out);
|
|
2117
|
+
|
|
2118
|
+
let other: Arc<dyn crate::FileSystem + Send + Sync> = Arc::new(other);
|
|
2119
|
+
|
|
2120
|
+
main.mount_directory_entries(Path::new("/"), &other, Path::new("/a"))
|
|
2121
|
+
.unwrap();
|
|
2122
|
+
|
|
2123
|
+
let mut buf = Vec::new();
|
|
2124
|
+
|
|
2125
|
+
let mut f = main
|
|
2126
|
+
.new_open_options()
|
|
2127
|
+
.read(true)
|
|
2128
|
+
.open(Path::new("/x/a.txt"))
|
|
2129
|
+
.unwrap();
|
|
2130
|
+
f.read_to_end(&mut buf).await.unwrap();
|
|
2131
|
+
|
|
2132
|
+
assert_eq!(buf, b"a");
|
|
2133
|
+
}
|
|
2134
|
+
}
|