@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,941 @@
|
|
|
1
|
+
use super::filesystem::InodeResolution;
|
|
2
|
+
use super::*;
|
|
3
|
+
use crate::{FileType, FsError, Metadata, OpenOptionsConfig, Result, VirtualFile};
|
|
4
|
+
use shared_buffer::OwnedBuffer;
|
|
5
|
+
use std::path::Path;
|
|
6
|
+
use tracing::*;
|
|
7
|
+
|
|
8
|
+
impl FileSystem {
|
|
9
|
+
/// Inserts a readonly file into the file system that uses copy-on-write
|
|
10
|
+
/// (this is required for zero-copy creation of the same file)
|
|
11
|
+
pub fn insert_ro_file(&self, path: &Path, contents: OwnedBuffer) -> Result<()> {
|
|
12
|
+
let _ = crate::FileSystem::remove_file(self, path);
|
|
13
|
+
let (inode_of_parent, maybe_inode_of_file, name_of_file) = self.insert_inode(path)?;
|
|
14
|
+
|
|
15
|
+
let inode_of_parent = match inode_of_parent {
|
|
16
|
+
InodeResolution::Found(a) => a,
|
|
17
|
+
InodeResolution::Redirect(..) => {
|
|
18
|
+
return Err(FsError::InvalidInput);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
match maybe_inode_of_file {
|
|
23
|
+
// The file already exists, then it can not be inserted.
|
|
24
|
+
Some(_inode_of_file) => return Err(FsError::AlreadyExists),
|
|
25
|
+
|
|
26
|
+
// The file doesn't already exist; it's OK to create it if
|
|
27
|
+
None => {
|
|
28
|
+
// Write lock.
|
|
29
|
+
let mut fs = self.inner.write().map_err(|_| FsError::Lock)?;
|
|
30
|
+
|
|
31
|
+
let file = ReadOnlyFile::new(contents);
|
|
32
|
+
let file_len = file.len() as u64;
|
|
33
|
+
|
|
34
|
+
// Creating the file in the storage.
|
|
35
|
+
let time = fs.now();
|
|
36
|
+
let inode_of_file = fs.storage.vacant_entry().key();
|
|
37
|
+
let real_inode_of_file = fs.insert_node(Node::ReadOnlyFile(ReadOnlyFileNode {
|
|
38
|
+
inode: inode_of_file,
|
|
39
|
+
name: name_of_file,
|
|
40
|
+
file,
|
|
41
|
+
metadata: {
|
|
42
|
+
Metadata {
|
|
43
|
+
ft: FileType {
|
|
44
|
+
file: true,
|
|
45
|
+
..Default::default()
|
|
46
|
+
},
|
|
47
|
+
accessed: time,
|
|
48
|
+
created: time,
|
|
49
|
+
modified: time,
|
|
50
|
+
len: file_len,
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
lifecycle: Arc::default(),
|
|
54
|
+
}))?;
|
|
55
|
+
|
|
56
|
+
assert_eq!(
|
|
57
|
+
inode_of_file, real_inode_of_file,
|
|
58
|
+
"new file inode should have been correctly calculated",
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
// Adding the new directory to its parent.
|
|
62
|
+
fs.add_child_to_node(inode_of_parent, inode_of_file)?;
|
|
63
|
+
|
|
64
|
+
inode_of_file
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
Ok(())
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/// Inserts a arc file into the file system that references another file
|
|
71
|
+
/// in another file system (does not copy the real data)
|
|
72
|
+
pub fn insert_arc_file_at(
|
|
73
|
+
&self,
|
|
74
|
+
target_path: PathBuf,
|
|
75
|
+
fs: Arc<dyn crate::FileSystem + Send + Sync>,
|
|
76
|
+
source_path: PathBuf,
|
|
77
|
+
) -> Result<()> {
|
|
78
|
+
let _ = crate::FileSystem::remove_file(self, target_path.as_path());
|
|
79
|
+
let (inode_of_parent, maybe_inode_of_file, name_of_file) =
|
|
80
|
+
self.insert_inode(target_path.as_path())?;
|
|
81
|
+
|
|
82
|
+
let inode_of_parent = match inode_of_parent {
|
|
83
|
+
InodeResolution::Found(a) => a,
|
|
84
|
+
InodeResolution::Redirect(..) => {
|
|
85
|
+
return Err(FsError::InvalidInput);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
match maybe_inode_of_file {
|
|
90
|
+
// The file already exists, then it can not be inserted.
|
|
91
|
+
Some(_inode_of_file) => return Err(FsError::AlreadyExists),
|
|
92
|
+
|
|
93
|
+
// The file doesn't already exist; it's OK to create it if
|
|
94
|
+
None => {
|
|
95
|
+
// Write lock.
|
|
96
|
+
let mut fs_lock = self.inner.write().map_err(|_| FsError::Lock)?;
|
|
97
|
+
|
|
98
|
+
// Read the metadata or generate a dummy one
|
|
99
|
+
let meta = match fs.metadata(&target_path) {
|
|
100
|
+
Ok(meta) => meta,
|
|
101
|
+
_ => {
|
|
102
|
+
let time = fs_lock.now();
|
|
103
|
+
Metadata {
|
|
104
|
+
ft: FileType {
|
|
105
|
+
file: true,
|
|
106
|
+
..Default::default()
|
|
107
|
+
},
|
|
108
|
+
accessed: time,
|
|
109
|
+
created: time,
|
|
110
|
+
modified: time,
|
|
111
|
+
len: 0,
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// Creating the file in the storage.
|
|
117
|
+
let inode_of_file = fs_lock.storage.vacant_entry().key();
|
|
118
|
+
let real_inode_of_file = fs_lock.insert_node(Node::ArcFile(ArcFileNode {
|
|
119
|
+
inode: inode_of_file,
|
|
120
|
+
name: name_of_file,
|
|
121
|
+
fs,
|
|
122
|
+
path: source_path,
|
|
123
|
+
metadata: meta,
|
|
124
|
+
lifecycle: Arc::default(),
|
|
125
|
+
}))?;
|
|
126
|
+
|
|
127
|
+
assert_eq!(
|
|
128
|
+
inode_of_file, real_inode_of_file,
|
|
129
|
+
"new file inode should have been correctly calculated",
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
// Adding the new directory to its parent.
|
|
133
|
+
fs_lock.add_child_to_node(inode_of_parent, inode_of_file)?;
|
|
134
|
+
|
|
135
|
+
inode_of_file
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
Ok(())
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/// Inserts a arc file into the file system that references another file
|
|
142
|
+
/// in another file system (does not copy the real data)
|
|
143
|
+
pub fn insert_arc_file(
|
|
144
|
+
&self,
|
|
145
|
+
target_path: PathBuf,
|
|
146
|
+
fs: Arc<dyn crate::FileSystem + Send + Sync>,
|
|
147
|
+
) -> Result<()> {
|
|
148
|
+
self.insert_arc_file_at(target_path.clone(), fs, target_path)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/// Inserts a arc directory into the file system that references another file
|
|
152
|
+
/// in another file system (does not copy the real data)
|
|
153
|
+
pub fn insert_arc_directory_at(
|
|
154
|
+
&self,
|
|
155
|
+
target_path: PathBuf,
|
|
156
|
+
other: Arc<dyn crate::FileSystem + Send + Sync>,
|
|
157
|
+
source_path: PathBuf,
|
|
158
|
+
) -> Result<()> {
|
|
159
|
+
let _ = crate::FileSystem::remove_dir(self, target_path.as_path());
|
|
160
|
+
let (inode_of_parent, maybe_inode_of_file, name_of_file) =
|
|
161
|
+
self.insert_inode(target_path.as_path())?;
|
|
162
|
+
|
|
163
|
+
let inode_of_parent = match inode_of_parent {
|
|
164
|
+
InodeResolution::Found(a) => a,
|
|
165
|
+
InodeResolution::Redirect(..) => {
|
|
166
|
+
return Err(FsError::InvalidInput);
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
match maybe_inode_of_file {
|
|
171
|
+
// The file already exists, then it can not be inserted.
|
|
172
|
+
Some(_inode_of_file) => return Err(FsError::AlreadyExists),
|
|
173
|
+
|
|
174
|
+
// The file doesn't already exist; it's OK to create it if
|
|
175
|
+
None => {
|
|
176
|
+
// Write lock.
|
|
177
|
+
let mut fs_lock = self.inner.write().map_err(|_| FsError::Lock)?;
|
|
178
|
+
|
|
179
|
+
// Creating the file in the storage.
|
|
180
|
+
let time = fs_lock.now();
|
|
181
|
+
let inode_of_file = fs_lock.storage.vacant_entry().key();
|
|
182
|
+
let real_inode_of_file =
|
|
183
|
+
fs_lock.insert_node(Node::ArcDirectory(ArcDirectoryNode {
|
|
184
|
+
inode: inode_of_file,
|
|
185
|
+
name: name_of_file,
|
|
186
|
+
fs: other,
|
|
187
|
+
path: source_path,
|
|
188
|
+
metadata: {
|
|
189
|
+
Metadata {
|
|
190
|
+
ft: FileType {
|
|
191
|
+
file: true,
|
|
192
|
+
..Default::default()
|
|
193
|
+
},
|
|
194
|
+
accessed: time,
|
|
195
|
+
created: time,
|
|
196
|
+
modified: time,
|
|
197
|
+
len: 0,
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
}))?;
|
|
201
|
+
|
|
202
|
+
assert_eq!(
|
|
203
|
+
inode_of_file, real_inode_of_file,
|
|
204
|
+
"new file inode should have been correctly calculated",
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
// Adding the new directory to its parent.
|
|
208
|
+
fs_lock.add_child_to_node(inode_of_parent, inode_of_file)?;
|
|
209
|
+
|
|
210
|
+
inode_of_file
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
Ok(())
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/// Inserts a arc directory into the file system that references another file
|
|
217
|
+
/// in another file system (does not copy the real data)
|
|
218
|
+
pub fn insert_arc_directory(
|
|
219
|
+
&self,
|
|
220
|
+
target_path: PathBuf,
|
|
221
|
+
other: Arc<dyn crate::FileSystem + Send + Sync>,
|
|
222
|
+
) -> Result<()> {
|
|
223
|
+
self.insert_arc_directory_at(target_path.clone(), other, target_path)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/// Inserts a arc file into the file system that references another file
|
|
227
|
+
/// in another file system (does not copy the real data)
|
|
228
|
+
pub fn insert_device_file(
|
|
229
|
+
&self,
|
|
230
|
+
path: PathBuf,
|
|
231
|
+
file: Box<dyn crate::VirtualFile + Send + Sync>,
|
|
232
|
+
) -> Result<()> {
|
|
233
|
+
let _ = crate::FileSystem::remove_file(self, path.as_path());
|
|
234
|
+
let (inode_of_parent, maybe_inode_of_file, name_of_file) =
|
|
235
|
+
self.insert_inode(path.as_path())?;
|
|
236
|
+
|
|
237
|
+
let inode_of_parent = match inode_of_parent {
|
|
238
|
+
InodeResolution::Found(a) => a,
|
|
239
|
+
InodeResolution::Redirect(..) => {
|
|
240
|
+
// TODO: should remove the inode again!
|
|
241
|
+
return Err(FsError::InvalidInput);
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
if let Some(_inode_of_file) = maybe_inode_of_file {
|
|
246
|
+
// TODO: restore previous inode?
|
|
247
|
+
return Err(FsError::AlreadyExists);
|
|
248
|
+
}
|
|
249
|
+
// Write lock.
|
|
250
|
+
let mut fs_lock = self.inner.write().map_err(|_| FsError::Lock)?;
|
|
251
|
+
|
|
252
|
+
// Creating the file in the storage.
|
|
253
|
+
let time = fs_lock.now();
|
|
254
|
+
let inode_of_file = fs_lock.storage.vacant_entry().key();
|
|
255
|
+
let real_inode_of_file = fs_lock.insert_node(Node::CustomFile(CustomFileNode {
|
|
256
|
+
inode: inode_of_file,
|
|
257
|
+
name: name_of_file,
|
|
258
|
+
file: Mutex::new(file),
|
|
259
|
+
metadata: {
|
|
260
|
+
Metadata {
|
|
261
|
+
ft: FileType {
|
|
262
|
+
file: true,
|
|
263
|
+
..Default::default()
|
|
264
|
+
},
|
|
265
|
+
accessed: time,
|
|
266
|
+
created: time,
|
|
267
|
+
modified: time,
|
|
268
|
+
len: 0,
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
lifecycle: Arc::default(),
|
|
272
|
+
}))?;
|
|
273
|
+
|
|
274
|
+
assert_eq!(
|
|
275
|
+
inode_of_file, real_inode_of_file,
|
|
276
|
+
"new file inode should have been correctly calculated",
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
// Adding the new directory to its parent.
|
|
280
|
+
fs_lock.add_child_to_node(inode_of_parent, inode_of_file)?;
|
|
281
|
+
|
|
282
|
+
Ok(())
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
fn insert_inode(
|
|
286
|
+
&self,
|
|
287
|
+
path: &Path,
|
|
288
|
+
) -> Result<(InodeResolution, Option<InodeResolution>, OsString)> {
|
|
289
|
+
// Read lock.
|
|
290
|
+
let fs = self.inner.read().map_err(|_| FsError::Lock)?;
|
|
291
|
+
|
|
292
|
+
// Check the path has a parent.
|
|
293
|
+
let parent_of_path = path.parent().ok_or(FsError::BaseNotDirectory)?;
|
|
294
|
+
|
|
295
|
+
// Check the file name.
|
|
296
|
+
let name_of_file = path
|
|
297
|
+
.file_name()
|
|
298
|
+
.ok_or(FsError::InvalidInput)?
|
|
299
|
+
.to_os_string();
|
|
300
|
+
|
|
301
|
+
// Find the parent inode.
|
|
302
|
+
let inode_of_parent = match fs.inode_of_parent(parent_of_path)? {
|
|
303
|
+
InodeResolution::Found(a) => a,
|
|
304
|
+
InodeResolution::Redirect(fs, parent_path) => {
|
|
305
|
+
return Ok((
|
|
306
|
+
InodeResolution::Redirect(fs, parent_path),
|
|
307
|
+
None,
|
|
308
|
+
name_of_file,
|
|
309
|
+
));
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
// Find the inode of the file if it exists.
|
|
314
|
+
let maybe_inode_of_file = fs
|
|
315
|
+
.as_parent_get_position_and_inode_of_file(inode_of_parent, &name_of_file)?
|
|
316
|
+
.map(|(_nth, inode)| inode);
|
|
317
|
+
|
|
318
|
+
Ok((
|
|
319
|
+
InodeResolution::Found(inode_of_parent),
|
|
320
|
+
maybe_inode_of_file,
|
|
321
|
+
name_of_file,
|
|
322
|
+
))
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
impl crate::FileOpener for FileSystem {
|
|
327
|
+
fn open(
|
|
328
|
+
&self,
|
|
329
|
+
path: &Path,
|
|
330
|
+
conf: &OpenOptionsConfig,
|
|
331
|
+
) -> Result<Box<dyn VirtualFile + Send + Sync + 'static>> {
|
|
332
|
+
debug!(path=%path.display(), "open");
|
|
333
|
+
|
|
334
|
+
let read = conf.read();
|
|
335
|
+
let mut write = conf.write();
|
|
336
|
+
let append = conf.append();
|
|
337
|
+
let mut truncate = conf.truncate();
|
|
338
|
+
let mut create = conf.create();
|
|
339
|
+
let create_new = conf.create_new();
|
|
340
|
+
|
|
341
|
+
// If `create_new` is used, `create` and `truncate ` are ignored.
|
|
342
|
+
if create_new {
|
|
343
|
+
create = false;
|
|
344
|
+
truncate = false;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// To truncate a file, `write` must be used.
|
|
348
|
+
if truncate && !write {
|
|
349
|
+
return Err(FsError::PermissionDenied);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// `append` is semantically equivalent to `write` + `append`
|
|
353
|
+
// but let's keep them exclusive.
|
|
354
|
+
if append {
|
|
355
|
+
write = false;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
let (inode_of_parent, maybe_inode_of_file, name_of_file) = self.insert_inode(path)?;
|
|
359
|
+
|
|
360
|
+
let inode_of_parent = match inode_of_parent {
|
|
361
|
+
InodeResolution::Found(a) => a,
|
|
362
|
+
InodeResolution::Redirect(fs, mut parent_path) => {
|
|
363
|
+
parent_path.push(name_of_file);
|
|
364
|
+
return fs
|
|
365
|
+
.new_open_options()
|
|
366
|
+
.options(conf.clone())
|
|
367
|
+
.open(parent_path);
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
let maybe_inode_of_file = match maybe_inode_of_file {
|
|
371
|
+
Some(InodeResolution::Found(inode)) => Some(inode),
|
|
372
|
+
Some(InodeResolution::Redirect(fs, path)) => {
|
|
373
|
+
return fs.new_open_options().options(conf.clone()).open(path);
|
|
374
|
+
}
|
|
375
|
+
None => None,
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
let cursor = 0u64;
|
|
379
|
+
let (inode_of_file, handle_lifecycle) = match maybe_inode_of_file {
|
|
380
|
+
// The file already exists, and a _new_ one _must_ be
|
|
381
|
+
// created; it's not OK.
|
|
382
|
+
Some(_inode_of_file) if create_new => return Err(FsError::AlreadyExists),
|
|
383
|
+
|
|
384
|
+
// The file already exists; it's OK.
|
|
385
|
+
Some(inode_of_file) => {
|
|
386
|
+
// Write lock.
|
|
387
|
+
let mut fs = self.inner.write().map_err(|_| FsError::Lock)?;
|
|
388
|
+
let time = fs.now();
|
|
389
|
+
|
|
390
|
+
let handle_lifecycle = match fs.storage.get_mut(inode_of_file) {
|
|
391
|
+
Some(Node::File(FileNode {
|
|
392
|
+
metadata,
|
|
393
|
+
file,
|
|
394
|
+
lifecycle,
|
|
395
|
+
..
|
|
396
|
+
})) => {
|
|
397
|
+
// Update the accessed time.
|
|
398
|
+
metadata.accessed = time;
|
|
399
|
+
|
|
400
|
+
// Truncate if needed.
|
|
401
|
+
if truncate {
|
|
402
|
+
file.truncate();
|
|
403
|
+
metadata.len = 0;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
lifecycle.clone()
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
Some(Node::OffloadedFile(OffloadedFileNode {
|
|
410
|
+
metadata,
|
|
411
|
+
file,
|
|
412
|
+
lifecycle,
|
|
413
|
+
..
|
|
414
|
+
})) => {
|
|
415
|
+
// Update the accessed time.
|
|
416
|
+
metadata.accessed = time;
|
|
417
|
+
|
|
418
|
+
// Truncate if needed.
|
|
419
|
+
if truncate {
|
|
420
|
+
file.truncate();
|
|
421
|
+
metadata.len = 0;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
lifecycle.clone()
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
Some(Node::ReadOnlyFile(node)) => {
|
|
428
|
+
// Update the accessed time.
|
|
429
|
+
node.metadata.accessed = time;
|
|
430
|
+
|
|
431
|
+
// Truncate if needed.
|
|
432
|
+
if truncate || append {
|
|
433
|
+
return Err(FsError::PermissionDenied);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
node.lifecycle.clone()
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
Some(Node::CustomFile(node)) => {
|
|
440
|
+
// Update the accessed time.
|
|
441
|
+
node.metadata.accessed = time;
|
|
442
|
+
|
|
443
|
+
// Truncate if needed.
|
|
444
|
+
let mut file = node.file.lock().unwrap();
|
|
445
|
+
if truncate {
|
|
446
|
+
file.set_len(0)?;
|
|
447
|
+
node.metadata.len = 0;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
node.lifecycle.clone()
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
Some(Node::ArcFile(node)) => {
|
|
454
|
+
// Update the accessed time.
|
|
455
|
+
node.metadata.accessed = time;
|
|
456
|
+
|
|
457
|
+
let mut file = node
|
|
458
|
+
.fs
|
|
459
|
+
.new_open_options()
|
|
460
|
+
.read(read)
|
|
461
|
+
.write(write)
|
|
462
|
+
.append(append)
|
|
463
|
+
.truncate(truncate)
|
|
464
|
+
.create(create)
|
|
465
|
+
.create_new(create_new)
|
|
466
|
+
.open(node.path.as_path())?;
|
|
467
|
+
|
|
468
|
+
// Truncate if needed.
|
|
469
|
+
if truncate {
|
|
470
|
+
file.set_len(0)?;
|
|
471
|
+
node.metadata.len = 0;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
node.lifecycle.clone()
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
None => return Err(FsError::EntryNotFound),
|
|
478
|
+
_ => return Err(FsError::NotAFile),
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
handle_lifecycle.opened();
|
|
482
|
+
(inode_of_file, handle_lifecycle)
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// The file doesn't already exist; it's OK to create it if:
|
|
486
|
+
// 1. `create_new` is used with `write` or `append`,
|
|
487
|
+
// 2. `create` is used with `write` or `append`.
|
|
488
|
+
None if (create_new || create) && (create_new || write || append) => {
|
|
489
|
+
// Write lock.
|
|
490
|
+
let mut fs = self.inner.write().map_err(|_| FsError::Lock)?;
|
|
491
|
+
|
|
492
|
+
let handle_lifecycle: Arc<FileLifecycle> = Arc::default();
|
|
493
|
+
let metadata = {
|
|
494
|
+
let time = fs.now();
|
|
495
|
+
Metadata {
|
|
496
|
+
ft: FileType {
|
|
497
|
+
file: true,
|
|
498
|
+
..Default::default()
|
|
499
|
+
},
|
|
500
|
+
accessed: time,
|
|
501
|
+
created: time,
|
|
502
|
+
modified: time,
|
|
503
|
+
len: 0,
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
let inode_of_file = fs.storage.vacant_entry().key();
|
|
507
|
+
|
|
508
|
+
// We might be in optimized mode
|
|
509
|
+
let file = match fs.backing_offload.clone() {
|
|
510
|
+
Some(offload) => {
|
|
511
|
+
let file = OffloadedFile::new(fs.limiter.clone(), offload);
|
|
512
|
+
Node::OffloadedFile(OffloadedFileNode {
|
|
513
|
+
inode: inode_of_file,
|
|
514
|
+
name: name_of_file,
|
|
515
|
+
file,
|
|
516
|
+
metadata,
|
|
517
|
+
lifecycle: handle_lifecycle.clone(),
|
|
518
|
+
})
|
|
519
|
+
}
|
|
520
|
+
_ => {
|
|
521
|
+
let file = File::new(fs.limiter.clone());
|
|
522
|
+
Node::File(FileNode {
|
|
523
|
+
inode: inode_of_file,
|
|
524
|
+
name: name_of_file,
|
|
525
|
+
file,
|
|
526
|
+
metadata,
|
|
527
|
+
lifecycle: handle_lifecycle.clone(),
|
|
528
|
+
})
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
// Creating the file in the storage.
|
|
533
|
+
let real_inode_of_file = fs.insert_node(file)?;
|
|
534
|
+
|
|
535
|
+
assert_eq!(
|
|
536
|
+
inode_of_file, real_inode_of_file,
|
|
537
|
+
"new file inode should have been correctly calculated",
|
|
538
|
+
);
|
|
539
|
+
|
|
540
|
+
// Adding the new directory to its parent.
|
|
541
|
+
fs.add_child_to_node(inode_of_parent, inode_of_file)?;
|
|
542
|
+
|
|
543
|
+
handle_lifecycle.opened();
|
|
544
|
+
(inode_of_file, handle_lifecycle)
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
None if (create_new || create) => return Err(FsError::PermissionDenied),
|
|
548
|
+
|
|
549
|
+
None => return Err(FsError::EntryNotFound),
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
#[cfg(test)]
|
|
553
|
+
test_file_opener::run_open_before_handle_hook();
|
|
554
|
+
|
|
555
|
+
Ok(Box::new(FileHandle::new_opened(
|
|
556
|
+
inode_of_file,
|
|
557
|
+
self.clone(),
|
|
558
|
+
handle_lifecycle,
|
|
559
|
+
read,
|
|
560
|
+
write || append || truncate,
|
|
561
|
+
append,
|
|
562
|
+
cursor,
|
|
563
|
+
)))
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
#[cfg(test)]
|
|
568
|
+
mod test_file_opener {
|
|
569
|
+
use std::cell::RefCell;
|
|
570
|
+
use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
|
|
571
|
+
|
|
572
|
+
use crate::{FileSystem as FS, FsError, mem_fs::*};
|
|
573
|
+
use std::io;
|
|
574
|
+
use std::sync::Arc;
|
|
575
|
+
|
|
576
|
+
macro_rules! path {
|
|
577
|
+
($path:expr) => {
|
|
578
|
+
std::path::Path::new($path)
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
type OpenBeforeHandleHook = Box<dyn FnOnce() + 'static>;
|
|
583
|
+
|
|
584
|
+
#[cfg(test)]
|
|
585
|
+
thread_local! {
|
|
586
|
+
static OPEN_BEFORE_HANDLE_HOOK: RefCell<Option<OpenBeforeHandleHook>> = RefCell::new(None);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
struct OpenBeforeHandleHookGuard;
|
|
590
|
+
|
|
591
|
+
impl OpenBeforeHandleHookGuard {
|
|
592
|
+
fn install(hook: OpenBeforeHandleHook) -> Self {
|
|
593
|
+
OPEN_BEFORE_HANDLE_HOOK.with(|slot| {
|
|
594
|
+
let previous = slot.borrow_mut().replace(hook);
|
|
595
|
+
assert!(
|
|
596
|
+
previous.is_none(),
|
|
597
|
+
"open-before-handle test hook should not already be installed"
|
|
598
|
+
);
|
|
599
|
+
});
|
|
600
|
+
Self
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
impl Drop for OpenBeforeHandleHookGuard {
|
|
605
|
+
fn drop(&mut self) {
|
|
606
|
+
OPEN_BEFORE_HANDLE_HOOK.with(|slot| {
|
|
607
|
+
slot.borrow_mut().take();
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
#[cfg(test)]
|
|
613
|
+
pub(super) fn run_open_before_handle_hook() {
|
|
614
|
+
OPEN_BEFORE_HANDLE_HOOK.with(|slot| {
|
|
615
|
+
if let Some(hook) = slot.borrow_mut().take() {
|
|
616
|
+
hook();
|
|
617
|
+
}
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
#[tokio::test]
|
|
622
|
+
async fn test_create_new_file() {
|
|
623
|
+
let fs = FileSystem::default();
|
|
624
|
+
|
|
625
|
+
assert!(
|
|
626
|
+
fs.new_open_options()
|
|
627
|
+
.write(true)
|
|
628
|
+
.create_new(true)
|
|
629
|
+
.open(path!("/foo.txt"))
|
|
630
|
+
.is_ok(),
|
|
631
|
+
"creating a new file",
|
|
632
|
+
);
|
|
633
|
+
|
|
634
|
+
{
|
|
635
|
+
let fs_inner = fs.inner.read().unwrap();
|
|
636
|
+
|
|
637
|
+
assert_eq!(fs_inner.storage.len(), 2, "storage has the new file");
|
|
638
|
+
assert!(
|
|
639
|
+
matches!(
|
|
640
|
+
fs_inner.storage.get(ROOT_INODE),
|
|
641
|
+
Some(Node::Directory(DirectoryNode {
|
|
642
|
+
inode: ROOT_INODE,
|
|
643
|
+
name,
|
|
644
|
+
children,
|
|
645
|
+
..
|
|
646
|
+
})) if name == "/" && children == &[1]
|
|
647
|
+
),
|
|
648
|
+
"`/` contains `foo.txt`",
|
|
649
|
+
);
|
|
650
|
+
assert!(
|
|
651
|
+
matches!(
|
|
652
|
+
fs_inner.storage.get(1),
|
|
653
|
+
Some(Node::File(FileNode {
|
|
654
|
+
inode: 1,
|
|
655
|
+
name,
|
|
656
|
+
..
|
|
657
|
+
})) if name == "foo.txt"
|
|
658
|
+
),
|
|
659
|
+
"`foo.txt` exists and is a file",
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
assert!(
|
|
664
|
+
matches!(
|
|
665
|
+
fs.new_open_options()
|
|
666
|
+
.write(true)
|
|
667
|
+
.create_new(true)
|
|
668
|
+
.open(path!("/foo.txt")),
|
|
669
|
+
Err(FsError::AlreadyExists)
|
|
670
|
+
),
|
|
671
|
+
"creating a new file that already exist",
|
|
672
|
+
);
|
|
673
|
+
|
|
674
|
+
assert_eq!(
|
|
675
|
+
fs.new_open_options()
|
|
676
|
+
.write(true)
|
|
677
|
+
.create_new(true)
|
|
678
|
+
.open(path!("/foo/bar.txt"))
|
|
679
|
+
.map(|_| ()),
|
|
680
|
+
Err(FsError::EntryNotFound),
|
|
681
|
+
"creating a file in a directory that doesn't exist",
|
|
682
|
+
);
|
|
683
|
+
|
|
684
|
+
assert_eq!(fs.remove_file(path!("/foo.txt")), Ok(()), "removing a file");
|
|
685
|
+
|
|
686
|
+
assert!(
|
|
687
|
+
fs.new_open_options()
|
|
688
|
+
.write(false)
|
|
689
|
+
.create_new(true)
|
|
690
|
+
.open(path!("/foo.txt"))
|
|
691
|
+
.is_ok(),
|
|
692
|
+
"creating a file without the `write` option",
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
#[tokio::test]
|
|
697
|
+
async fn test_truncate_a_read_only_file() {
|
|
698
|
+
let fs = FileSystem::default();
|
|
699
|
+
|
|
700
|
+
assert!(
|
|
701
|
+
matches!(
|
|
702
|
+
fs.new_open_options()
|
|
703
|
+
.write(false)
|
|
704
|
+
.truncate(true)
|
|
705
|
+
.open(path!("/foo.txt")),
|
|
706
|
+
Err(FsError::PermissionDenied),
|
|
707
|
+
),
|
|
708
|
+
"truncating a read-only file",
|
|
709
|
+
);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
#[tokio::test]
|
|
713
|
+
async fn test_truncate() {
|
|
714
|
+
let fs = FileSystem::default();
|
|
715
|
+
|
|
716
|
+
let mut file = fs
|
|
717
|
+
.new_open_options()
|
|
718
|
+
.write(true)
|
|
719
|
+
.create_new(true)
|
|
720
|
+
.open(path!("/foo.txt"))
|
|
721
|
+
.expect("failed to create a new file");
|
|
722
|
+
|
|
723
|
+
assert!(
|
|
724
|
+
matches!(file.write(b"foobar").await, Ok(6)),
|
|
725
|
+
"writing `foobar` at the end of the file",
|
|
726
|
+
);
|
|
727
|
+
|
|
728
|
+
assert!(
|
|
729
|
+
matches!(file.seek(io::SeekFrom::Current(0)).await, Ok(6)),
|
|
730
|
+
"checking the current position is 6",
|
|
731
|
+
);
|
|
732
|
+
assert!(
|
|
733
|
+
matches!(file.seek(io::SeekFrom::End(0)).await, Ok(6)),
|
|
734
|
+
"checking the size is 6",
|
|
735
|
+
);
|
|
736
|
+
|
|
737
|
+
let mut file = fs
|
|
738
|
+
.new_open_options()
|
|
739
|
+
.write(true)
|
|
740
|
+
.truncate(true)
|
|
741
|
+
.open(path!("/foo.txt"))
|
|
742
|
+
.expect("failed to open + truncate `foo.txt`");
|
|
743
|
+
|
|
744
|
+
assert!(
|
|
745
|
+
matches!(file.seek(io::SeekFrom::Current(0)).await, Ok(0)),
|
|
746
|
+
"checking the current position is 0",
|
|
747
|
+
);
|
|
748
|
+
assert!(
|
|
749
|
+
matches!(file.seek(io::SeekFrom::End(0)).await, Ok(0)),
|
|
750
|
+
"checking the size is 0",
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
#[tokio::test]
|
|
755
|
+
async fn test_append() {
|
|
756
|
+
let fs = FileSystem::default();
|
|
757
|
+
|
|
758
|
+
let mut file = fs
|
|
759
|
+
.new_open_options()
|
|
760
|
+
.write(true)
|
|
761
|
+
.create_new(true)
|
|
762
|
+
.open(path!("/foo.txt"))
|
|
763
|
+
.expect("failed to create a new file");
|
|
764
|
+
|
|
765
|
+
assert!(
|
|
766
|
+
matches!(file.write(b"foobar").await, Ok(6)),
|
|
767
|
+
"writing `foobar` at the end of the file",
|
|
768
|
+
);
|
|
769
|
+
|
|
770
|
+
assert!(
|
|
771
|
+
matches!(file.seek(io::SeekFrom::Current(0)).await, Ok(6)),
|
|
772
|
+
"checking the current position is 6",
|
|
773
|
+
);
|
|
774
|
+
assert!(
|
|
775
|
+
matches!(file.seek(io::SeekFrom::End(0)).await, Ok(6)),
|
|
776
|
+
"checking the size is 6",
|
|
777
|
+
);
|
|
778
|
+
|
|
779
|
+
let mut file = fs
|
|
780
|
+
.new_open_options()
|
|
781
|
+
.append(true)
|
|
782
|
+
.open(path!("/foo.txt"))
|
|
783
|
+
.expect("failed to open `foo.txt`");
|
|
784
|
+
|
|
785
|
+
assert!(
|
|
786
|
+
matches!(file.seek(io::SeekFrom::Current(0)).await, Ok(0)),
|
|
787
|
+
"checking the current position in append-mode is 0",
|
|
788
|
+
);
|
|
789
|
+
assert!(
|
|
790
|
+
matches!(file.seek(io::SeekFrom::Start(0)).await, Ok(0)),
|
|
791
|
+
"trying to rewind in append-mode",
|
|
792
|
+
);
|
|
793
|
+
assert!(matches!(file.write(b"baz").await, Ok(3)), "writing `baz`");
|
|
794
|
+
|
|
795
|
+
let mut file = fs
|
|
796
|
+
.new_open_options()
|
|
797
|
+
.read(true)
|
|
798
|
+
.open(path!("/foo.txt"))
|
|
799
|
+
.expect("failed to open `foo.txt");
|
|
800
|
+
|
|
801
|
+
assert!(
|
|
802
|
+
matches!(file.seek(io::SeekFrom::Current(0)).await, Ok(0)),
|
|
803
|
+
"checking the current position is read-mode is 0",
|
|
804
|
+
);
|
|
805
|
+
|
|
806
|
+
let mut string = String::new();
|
|
807
|
+
assert!(
|
|
808
|
+
matches!(file.read_to_string(&mut string).await, Ok(9)),
|
|
809
|
+
"reading the entire `foo.txt` file",
|
|
810
|
+
);
|
|
811
|
+
assert_eq!(
|
|
812
|
+
string, "foobarbaz",
|
|
813
|
+
"checking append-mode is ignoring seek operations",
|
|
814
|
+
);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
#[tokio::test]
|
|
818
|
+
async fn test_opening_a_file_that_already_exists() {
|
|
819
|
+
let fs = FileSystem::default();
|
|
820
|
+
|
|
821
|
+
assert!(
|
|
822
|
+
fs.new_open_options()
|
|
823
|
+
.write(true)
|
|
824
|
+
.create_new(true)
|
|
825
|
+
.open(path!("/foo.txt"))
|
|
826
|
+
.is_ok(),
|
|
827
|
+
"creating a _new_ file",
|
|
828
|
+
);
|
|
829
|
+
|
|
830
|
+
assert!(
|
|
831
|
+
matches!(
|
|
832
|
+
fs.new_open_options()
|
|
833
|
+
.create_new(true)
|
|
834
|
+
.open(path!("/foo.txt")),
|
|
835
|
+
Err(FsError::AlreadyExists),
|
|
836
|
+
),
|
|
837
|
+
"creating a _new_ file that already exists",
|
|
838
|
+
);
|
|
839
|
+
|
|
840
|
+
assert!(
|
|
841
|
+
fs.new_open_options()
|
|
842
|
+
.read(true)
|
|
843
|
+
.open(path!("/foo.txt"))
|
|
844
|
+
.is_ok(),
|
|
845
|
+
"opening a file that already exists",
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
#[tokio::test]
|
|
850
|
+
async fn test_opening_existing_file_in_arc_directory_redirects() {
|
|
851
|
+
let fs = FileSystem::default();
|
|
852
|
+
let backing = FileSystem::default();
|
|
853
|
+
let backing_arc: Arc<dyn crate::FileSystem + Send + Sync> = Arc::new(backing.clone());
|
|
854
|
+
|
|
855
|
+
backing
|
|
856
|
+
.new_open_options()
|
|
857
|
+
.write(true)
|
|
858
|
+
.create_new(true)
|
|
859
|
+
.open(path!("/foo.txt"))
|
|
860
|
+
.expect("create file in backing fs");
|
|
861
|
+
|
|
862
|
+
fs.insert_arc_directory_at(
|
|
863
|
+
path!("/mnt").to_path_buf(),
|
|
864
|
+
backing_arc,
|
|
865
|
+
path!("/").to_path_buf(),
|
|
866
|
+
)
|
|
867
|
+
.expect("mount arc directory");
|
|
868
|
+
|
|
869
|
+
let mut file = fs
|
|
870
|
+
.new_open_options()
|
|
871
|
+
.read(true)
|
|
872
|
+
.open(path!("/mnt/foo.txt"))
|
|
873
|
+
.expect("open should redirect to the backing fs");
|
|
874
|
+
|
|
875
|
+
let mut contents = String::new();
|
|
876
|
+
file.read_to_string(&mut contents)
|
|
877
|
+
.await
|
|
878
|
+
.expect("read redirected file");
|
|
879
|
+
assert_eq!(contents, "");
|
|
880
|
+
|
|
881
|
+
assert!(
|
|
882
|
+
matches!(
|
|
883
|
+
fs.new_open_options()
|
|
884
|
+
.create_new(true)
|
|
885
|
+
.open(path!("/mnt/foo.txt")),
|
|
886
|
+
Err(FsError::AlreadyExists),
|
|
887
|
+
),
|
|
888
|
+
"create_new on a redirected existing file follows the backing fs result",
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
#[tokio::test(flavor = "current_thread")]
|
|
893
|
+
async fn test_open_keeps_unlinked_inode_alive_before_returning_handle() {
|
|
894
|
+
let fs = FileSystem::default();
|
|
895
|
+
|
|
896
|
+
fs.new_open_options()
|
|
897
|
+
.write(true)
|
|
898
|
+
.create_new(true)
|
|
899
|
+
.open(path!("/foo.txt"))
|
|
900
|
+
.expect("create /foo.txt");
|
|
901
|
+
|
|
902
|
+
let _hook = OpenBeforeHandleHookGuard::install(Box::new({
|
|
903
|
+
let fs = fs.clone();
|
|
904
|
+
move || {
|
|
905
|
+
assert_eq!(
|
|
906
|
+
fs.remove_file(path!("/foo.txt")),
|
|
907
|
+
Ok(()),
|
|
908
|
+
"unlink during the open-return window",
|
|
909
|
+
);
|
|
910
|
+
}
|
|
911
|
+
}));
|
|
912
|
+
|
|
913
|
+
let mut file = fs
|
|
914
|
+
.new_open_options()
|
|
915
|
+
.read(true)
|
|
916
|
+
.write(true)
|
|
917
|
+
.open(path!("/foo.txt"))
|
|
918
|
+
.expect(
|
|
919
|
+
"open should succeed even if the path is unlinked before the handle is returned",
|
|
920
|
+
);
|
|
921
|
+
|
|
922
|
+
assert_eq!(
|
|
923
|
+
fs.metadata(path!("/foo.txt")),
|
|
924
|
+
Err(FsError::EntryNotFound),
|
|
925
|
+
"the path is gone immediately after unlink",
|
|
926
|
+
);
|
|
927
|
+
|
|
928
|
+
file.write_all(b"hello")
|
|
929
|
+
.await
|
|
930
|
+
.expect("write after the in-flight unlink should still work");
|
|
931
|
+
file.seek(io::SeekFrom::Start(0))
|
|
932
|
+
.await
|
|
933
|
+
.expect("rewind after in-flight unlink");
|
|
934
|
+
|
|
935
|
+
let mut contents = Vec::new();
|
|
936
|
+
file.read_to_end(&mut contents)
|
|
937
|
+
.await
|
|
938
|
+
.expect("read after the in-flight unlink should still work");
|
|
939
|
+
assert_eq!(contents, b"hello");
|
|
940
|
+
}
|
|
941
|
+
}
|