@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,2216 @@
|
|
|
1
|
+
use std::{
|
|
2
|
+
collections::HashSet,
|
|
3
|
+
fmt::Debug,
|
|
4
|
+
io::{self, SeekFrom},
|
|
5
|
+
path::{Path, PathBuf},
|
|
6
|
+
pin::Pin,
|
|
7
|
+
sync::Arc,
|
|
8
|
+
task::{Context, Poll},
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
use futures::future::BoxFuture;
|
|
12
|
+
use replace_with::replace_with_or_abort;
|
|
13
|
+
use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite, ReadBuf};
|
|
14
|
+
|
|
15
|
+
use crate::{
|
|
16
|
+
FileOpener, FileSystem, FileSystems, FsError, Metadata, OpenOptions, OpenOptionsConfig,
|
|
17
|
+
ReadDir, VirtualFile, ops,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
fn unlink_overlay_path<P>(primary: &Arc<P>, path: &Path) -> Result<(), FsError>
|
|
21
|
+
where
|
|
22
|
+
P: FileSystem + ?Sized,
|
|
23
|
+
{
|
|
24
|
+
// Create the whiteout before removing from primary. If whiteout creation
|
|
25
|
+
// fails we abort early so the lower-layer entry never becomes visible.
|
|
26
|
+
// Removing from primary first and then failing to create the whiteout
|
|
27
|
+
// would cause the secondary file to re-appear.
|
|
28
|
+
match ops::create_white_out(primary, path) {
|
|
29
|
+
Ok(()) | Err(FsError::AlreadyExists) => {}
|
|
30
|
+
Err(e) => return Err(e),
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
match primary.remove_file(path) {
|
|
34
|
+
// File was not in primary (only in a secondary) – the whiteout alone
|
|
35
|
+
// is sufficient to suppress it.
|
|
36
|
+
Err(e) if should_continue(e) => Ok(()),
|
|
37
|
+
other => other,
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
struct SecondaryFile<P> {
|
|
42
|
+
path: PathBuf,
|
|
43
|
+
primary: Arc<P>,
|
|
44
|
+
inner: Box<dyn VirtualFile + Send + Sync>,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
impl<P> Debug for SecondaryFile<P>
|
|
48
|
+
where
|
|
49
|
+
P: FileSystem + 'static,
|
|
50
|
+
{
|
|
51
|
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
52
|
+
f.debug_struct("SecondaryFile").finish()
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
impl<P> VirtualFile for SecondaryFile<P>
|
|
57
|
+
where
|
|
58
|
+
P: FileSystem + 'static,
|
|
59
|
+
{
|
|
60
|
+
fn last_accessed(&self) -> u64 {
|
|
61
|
+
self.inner.last_accessed()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
fn last_modified(&self) -> u64 {
|
|
65
|
+
self.inner.last_modified()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
fn created_time(&self) -> u64 {
|
|
69
|
+
self.inner.created_time()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
fn set_times(&mut self, atime: Option<u64>, mtime: Option<u64>) -> crate::Result<()> {
|
|
73
|
+
self.inner.set_times(atime, mtime)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
fn size(&self) -> u64 {
|
|
77
|
+
self.inner.size()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
fn set_len(&mut self, new_size: u64) -> crate::Result<()> {
|
|
81
|
+
self.inner.set_len(new_size)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
fn unlink(&mut self) -> crate::Result<()> {
|
|
85
|
+
// A SecondaryFile is only created when the primary has no binding for
|
|
86
|
+
// this path at open time. Any file currently at this path in primary is
|
|
87
|
+
// a different, independently-created object. Only create a whiteout to
|
|
88
|
+
// hide the secondary entry; never remove from primary.
|
|
89
|
+
match ops::create_white_out(&self.primary, &self.path) {
|
|
90
|
+
Ok(()) => Ok(()),
|
|
91
|
+
// The whiteout already exists: the path was already deleted from the
|
|
92
|
+
// overlay's perspective, so report it as not found.
|
|
93
|
+
Err(FsError::AlreadyExists) => Err(FsError::EntryNotFound),
|
|
94
|
+
Err(e) => Err(e),
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
fn is_open(&self) -> bool {
|
|
99
|
+
self.inner.is_open()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
fn get_special_fd(&self) -> Option<u32> {
|
|
103
|
+
self.inner.get_special_fd()
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
fn write_from_mmap(&mut self, offset: u64, len: u64) -> std::io::Result<()> {
|
|
107
|
+
self.inner.write_from_mmap(offset, len)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
fn as_owned_buffer(&self) -> Option<shared_buffer::OwnedBuffer> {
|
|
111
|
+
self.inner.as_owned_buffer()
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
fn poll_read_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
|
|
115
|
+
Pin::new(self.inner.as_mut()).poll_read_ready(cx)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
fn poll_write_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
|
|
119
|
+
Pin::new(self.inner.as_mut()).poll_write_ready(cx)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
impl<P> AsyncRead for SecondaryFile<P>
|
|
124
|
+
where
|
|
125
|
+
P: FileSystem + 'static,
|
|
126
|
+
{
|
|
127
|
+
fn poll_read(
|
|
128
|
+
mut self: Pin<&mut Self>,
|
|
129
|
+
cx: &mut Context<'_>,
|
|
130
|
+
buf: &mut ReadBuf<'_>,
|
|
131
|
+
) -> Poll<io::Result<()>> {
|
|
132
|
+
Pin::new(self.inner.as_mut()).poll_read(cx, buf)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
impl<P> AsyncWrite for SecondaryFile<P>
|
|
137
|
+
where
|
|
138
|
+
P: FileSystem + 'static,
|
|
139
|
+
{
|
|
140
|
+
fn poll_write(
|
|
141
|
+
mut self: Pin<&mut Self>,
|
|
142
|
+
cx: &mut Context<'_>,
|
|
143
|
+
buf: &[u8],
|
|
144
|
+
) -> Poll<io::Result<usize>> {
|
|
145
|
+
Pin::new(self.inner.as_mut()).poll_write(cx, buf)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
|
149
|
+
Pin::new(self.inner.as_mut()).poll_flush(cx)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
|
153
|
+
Pin::new(self.inner.as_mut()).poll_shutdown(cx)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
fn poll_write_vectored(
|
|
157
|
+
mut self: Pin<&mut Self>,
|
|
158
|
+
cx: &mut Context<'_>,
|
|
159
|
+
bufs: &[io::IoSlice<'_>],
|
|
160
|
+
) -> Poll<io::Result<usize>> {
|
|
161
|
+
Pin::new(self.inner.as_mut()).poll_write_vectored(cx, bufs)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
fn is_write_vectored(&self) -> bool {
|
|
165
|
+
self.inner.is_write_vectored()
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
impl<P> AsyncSeek for SecondaryFile<P>
|
|
170
|
+
where
|
|
171
|
+
P: FileSystem + 'static,
|
|
172
|
+
{
|
|
173
|
+
fn start_seek(mut self: Pin<&mut Self>, position: SeekFrom) -> io::Result<()> {
|
|
174
|
+
Pin::new(self.inner.as_mut()).start_seek(position)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
fn poll_complete(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
|
|
178
|
+
Pin::new(self.inner.as_mut()).poll_complete(cx)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/// A primary filesystem and chain of secondary filesystems that are overlaid
|
|
183
|
+
/// on top of each other.
|
|
184
|
+
///
|
|
185
|
+
/// # Precedence
|
|
186
|
+
///
|
|
187
|
+
/// The [`OverlayFileSystem`] will execute operations based on precedence.
|
|
188
|
+
///
|
|
189
|
+
///
|
|
190
|
+
/// Most importantly, this means earlier filesystems can shadow files and
|
|
191
|
+
/// directories that have a lower precedence.
|
|
192
|
+
///
|
|
193
|
+
///# Examples
|
|
194
|
+
///
|
|
195
|
+
/// Something useful to know is that the [`FileSystems`] trait is implemented
|
|
196
|
+
/// for both arrays and tuples.
|
|
197
|
+
///
|
|
198
|
+
/// For example, if you want to create a [`crate::FileSystem`] which will
|
|
199
|
+
/// create files in-memory while still being able to read from the host, you
|
|
200
|
+
/// might do something like this:
|
|
201
|
+
///
|
|
202
|
+
/// ```rust
|
|
203
|
+
/// use virtual_fs::{
|
|
204
|
+
/// mem_fs::FileSystem as MemFS,
|
|
205
|
+
/// host_fs::FileSystem as HostFS,
|
|
206
|
+
/// OverlayFileSystem,
|
|
207
|
+
/// };
|
|
208
|
+
///
|
|
209
|
+
/// let runtime = tokio::runtime::Builder::new_current_thread()
|
|
210
|
+
/// .enable_all()
|
|
211
|
+
/// .build()
|
|
212
|
+
/// .unwrap();
|
|
213
|
+
/// let _guard = runtime.enter();
|
|
214
|
+
///
|
|
215
|
+
/// let fs = OverlayFileSystem::new(MemFS::default(), [HostFS::new(tokio::runtime::Handle::current(), "/").unwrap()]);
|
|
216
|
+
///
|
|
217
|
+
/// // This also has the benefit of storing the two values in-line with no extra
|
|
218
|
+
/// // overhead or indirection.
|
|
219
|
+
/// assert_eq!(
|
|
220
|
+
/// std::mem::size_of_val(&fs),
|
|
221
|
+
/// std::mem::size_of::<(MemFS, HostFS)>(),
|
|
222
|
+
/// );
|
|
223
|
+
/// ```
|
|
224
|
+
///
|
|
225
|
+
/// A more complex example is
|
|
226
|
+
#[derive(Clone, PartialEq, Eq)]
|
|
227
|
+
pub struct OverlayFileSystem<P, S> {
|
|
228
|
+
primary: Arc<P>,
|
|
229
|
+
secondaries: S,
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
impl<P, S> OverlayFileSystem<P, S>
|
|
233
|
+
where
|
|
234
|
+
P: FileSystem + Send + Sync + 'static,
|
|
235
|
+
S: for<'a> FileSystems<'a> + Send + Sync + 'static,
|
|
236
|
+
{
|
|
237
|
+
/// Create a new [`FileSystem`] using a primary [`crate::FileSystem`] and a
|
|
238
|
+
/// chain of secondary [`FileSystems`].
|
|
239
|
+
pub fn new(primary: P, secondaries: S) -> Self {
|
|
240
|
+
OverlayFileSystem {
|
|
241
|
+
primary: Arc::new(primary),
|
|
242
|
+
secondaries,
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/// Get a reference to the primary filesystem.
|
|
247
|
+
pub fn primary(&self) -> &P {
|
|
248
|
+
&self.primary
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/// Get a reference to the secondary filesystems.
|
|
252
|
+
pub fn secondaries(&self) -> &S {
|
|
253
|
+
&self.secondaries
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/// Get a mutable reference to the secondary filesystems.
|
|
257
|
+
pub fn secondaries_mut(&mut self) -> &mut S {
|
|
258
|
+
&mut self.secondaries
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
fn permission_error_or_not_found(&self, path: &Path) -> Result<(), FsError> {
|
|
262
|
+
for fs in self.secondaries.filesystems() {
|
|
263
|
+
if ops::exists(fs, path) {
|
|
264
|
+
return Err(FsError::PermissionDenied);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
Err(FsError::EntryNotFound)
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
impl<P, S> FileSystem for OverlayFileSystem<P, S>
|
|
273
|
+
where
|
|
274
|
+
P: FileSystem + Send + 'static,
|
|
275
|
+
S: for<'a> FileSystems<'a> + Send + Sync + 'static,
|
|
276
|
+
for<'a> <<S as FileSystems<'a>>::Iter as IntoIterator>::IntoIter: Send,
|
|
277
|
+
{
|
|
278
|
+
fn readlink(&self, path: &Path) -> crate::Result<PathBuf> {
|
|
279
|
+
// Whiteout files can not be read, they are just markers
|
|
280
|
+
if ops::is_white_out(path).is_some() {
|
|
281
|
+
return Err(FsError::EntryNotFound);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Check if the file is in the primary
|
|
285
|
+
match self.primary.readlink(path) {
|
|
286
|
+
Ok(meta) => return Ok(meta),
|
|
287
|
+
Err(e) if should_continue(e) => {}
|
|
288
|
+
Err(e) => return Err(e),
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// There might be a whiteout, search for this
|
|
292
|
+
if ops::has_white_out(&self.primary, path) {
|
|
293
|
+
return Err(FsError::EntryNotFound);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Otherwise scan the secondaries
|
|
297
|
+
for fs in self.secondaries.filesystems() {
|
|
298
|
+
match fs.readlink(path) {
|
|
299
|
+
Err(e) if should_continue(e) => continue,
|
|
300
|
+
other => return other,
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
Err(FsError::EntryNotFound)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
fn read_dir(&self, path: &Path) -> Result<ReadDir, FsError> {
|
|
308
|
+
let mut entries = Vec::new();
|
|
309
|
+
let mut had_at_least_one_success = false;
|
|
310
|
+
let mut white_outs = HashSet::new();
|
|
311
|
+
|
|
312
|
+
let filesystems = std::iter::once(&self.primary as &(dyn FileSystem + Send))
|
|
313
|
+
.chain(self.secondaries().filesystems());
|
|
314
|
+
|
|
315
|
+
for fs in filesystems {
|
|
316
|
+
match fs.read_dir(path) {
|
|
317
|
+
Ok(r) => {
|
|
318
|
+
for entry in r {
|
|
319
|
+
let entry = entry?;
|
|
320
|
+
|
|
321
|
+
// White out entries block any later entries in the secondaries
|
|
322
|
+
// unless the entry has comes before the white out, thus the order
|
|
323
|
+
// that the file systems are parsed is important to this logic.
|
|
324
|
+
if let Some(path) = entry.is_white_out() {
|
|
325
|
+
tracing::trace!(
|
|
326
|
+
path=%path.display(),
|
|
327
|
+
"Found whiteout file",
|
|
328
|
+
);
|
|
329
|
+
white_outs.insert(path);
|
|
330
|
+
continue;
|
|
331
|
+
} else if white_outs.contains(&entry.path) {
|
|
332
|
+
tracing::trace!(
|
|
333
|
+
path=%path.display(),
|
|
334
|
+
"Skipping path because a whiteout exists",
|
|
335
|
+
);
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
entries.push(entry);
|
|
340
|
+
}
|
|
341
|
+
had_at_least_one_success = true;
|
|
342
|
+
}
|
|
343
|
+
Err(e) if should_continue(e) => continue,
|
|
344
|
+
Err(e) => return Err(e),
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if had_at_least_one_success {
|
|
349
|
+
// Make sure later entries are removed in favour of earlier ones.
|
|
350
|
+
// Note: this sort is guaranteed to be stable, meaning filesystems
|
|
351
|
+
// "higher up" the chain will be further towards the start and kept
|
|
352
|
+
// when deduplicating.
|
|
353
|
+
entries.sort_by(|a, b| a.path.cmp(&b.path));
|
|
354
|
+
entries.dedup_by(|a, b| a.path == b.path);
|
|
355
|
+
|
|
356
|
+
Ok(ReadDir::new(entries))
|
|
357
|
+
} else {
|
|
358
|
+
Err(FsError::BaseNotDirectory)
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
fn create_dir(&self, path: &Path) -> Result<(), FsError> {
|
|
363
|
+
// You can not create directories that use the whiteout prefix
|
|
364
|
+
if ops::is_white_out(path).is_some() {
|
|
365
|
+
return Err(FsError::InvalidInput);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// It could be the case that the directory was earlier hidden in the secondaries
|
|
369
|
+
// by a whiteout file, hence we need to make sure those are cleared out.
|
|
370
|
+
ops::remove_white_out(self.primary.as_ref(), path);
|
|
371
|
+
|
|
372
|
+
// Make sure the parent tree is in place on the primary, this is to cover the
|
|
373
|
+
// scenario where the secondaries has a parent structure that is not yet in the
|
|
374
|
+
// primary and the primary needs it to create a sub-directory
|
|
375
|
+
if let Some(parent) = path.parent()
|
|
376
|
+
&& self.read_dir(parent).is_ok()
|
|
377
|
+
{
|
|
378
|
+
ops::create_dir_all(&self.primary, parent).ok();
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Create the directory in the primary
|
|
382
|
+
match self.primary.create_dir(path) {
|
|
383
|
+
Err(e) if should_continue(e) => {}
|
|
384
|
+
other => return other,
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
self.permission_error_or_not_found(path)
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
fn create_symlink(&self, source: &Path, target: &Path) -> Result<(), FsError> {
|
|
391
|
+
if ops::is_white_out(target).is_some() {
|
|
392
|
+
return Err(FsError::InvalidInput);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
ops::remove_white_out(self.primary.as_ref(), target);
|
|
396
|
+
|
|
397
|
+
if let Some(parent) = target.parent()
|
|
398
|
+
&& self.read_dir(parent).is_ok()
|
|
399
|
+
{
|
|
400
|
+
ops::create_dir_all(&self.primary, parent).ok();
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
self.primary.create_symlink(source, target)
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
fn hard_link(&self, source: &Path, target: &Path) -> Result<(), FsError> {
|
|
407
|
+
if ops::is_white_out(source).is_some() || ops::is_white_out(target).is_some() {
|
|
408
|
+
return Err(FsError::InvalidInput);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
ops::remove_white_out(self.primary.as_ref(), target);
|
|
412
|
+
|
|
413
|
+
if let Some(parent) = target.parent()
|
|
414
|
+
&& self.read_dir(parent).is_ok()
|
|
415
|
+
{
|
|
416
|
+
ops::create_dir_all(&self.primary, parent).ok();
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
self.primary.hard_link(source, target)
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
fn remove_dir(&self, path: &Path) -> Result<(), FsError> {
|
|
423
|
+
// Whiteout files can not be removed, instead the original directory
|
|
424
|
+
// must be removed or recreated.
|
|
425
|
+
if ops::is_white_out(path).is_some() {
|
|
426
|
+
tracing::trace!(
|
|
427
|
+
path=%path.display(),
|
|
428
|
+
"Unable to remove a whited out directory",
|
|
429
|
+
);
|
|
430
|
+
return Err(FsError::EntryNotFound);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// If the directory is contained in a secondary file system then we need to create a
|
|
434
|
+
// whiteout file so that it is suppressed and is no longer returned in `readdir` calls.
|
|
435
|
+
|
|
436
|
+
let had_at_least_one_success = self.secondaries.filesystems().into_iter().any(|fs| {
|
|
437
|
+
fs.read_dir(path).is_ok() && ops::create_white_out(&self.primary, path).is_ok()
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
// Attempt to remove it from the primary, if this succeeds then we may have also
|
|
441
|
+
// added the whiteout file in the earlier step, but are required in this case to
|
|
442
|
+
// properly delete the directory.
|
|
443
|
+
match self.primary.remove_dir(path) {
|
|
444
|
+
Err(e) if should_continue(e) => {}
|
|
445
|
+
other => return other,
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if had_at_least_one_success {
|
|
449
|
+
return Ok(());
|
|
450
|
+
}
|
|
451
|
+
self.permission_error_or_not_found(path)
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
fn rename<'a>(&'a self, from: &'a Path, to: &'a Path) -> BoxFuture<'a, Result<(), FsError>> {
|
|
455
|
+
let from = from.to_owned();
|
|
456
|
+
let to = to.to_owned();
|
|
457
|
+
Box::pin(async move {
|
|
458
|
+
// Whiteout files can not be renamed
|
|
459
|
+
if ops::is_white_out(&from).is_some() {
|
|
460
|
+
tracing::trace!(
|
|
461
|
+
from=%from.display(),
|
|
462
|
+
to=%to.display(),
|
|
463
|
+
"Attempting to rename a file that was whited out"
|
|
464
|
+
);
|
|
465
|
+
return Err(FsError::EntryNotFound);
|
|
466
|
+
}
|
|
467
|
+
// You can not rename a file into a whiteout file
|
|
468
|
+
if ops::is_white_out(&to).is_some() {
|
|
469
|
+
tracing::trace!(
|
|
470
|
+
from=%from.display(),
|
|
471
|
+
to=%to.display(),
|
|
472
|
+
"Attempting to rename a file into a whiteout file"
|
|
473
|
+
);
|
|
474
|
+
return Err(FsError::InvalidInput);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// We attempt to rename the file or directory in the primary
|
|
478
|
+
// if this succeeds then we also need to ensure the white out
|
|
479
|
+
// files are created where we need them, so we do not immediately
|
|
480
|
+
// return until that is done
|
|
481
|
+
let mut had_at_least_one_success = false;
|
|
482
|
+
match self.primary.rename(&from, &to).await {
|
|
483
|
+
Err(e) if should_continue(e) => {}
|
|
484
|
+
Ok(()) => {
|
|
485
|
+
had_at_least_one_success = true;
|
|
486
|
+
}
|
|
487
|
+
other => return other,
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// If we have not yet renamed the file it may still reside in
|
|
491
|
+
// the secondaries, in which case we need to copy it to the
|
|
492
|
+
// primary rather than rename it
|
|
493
|
+
if !had_at_least_one_success {
|
|
494
|
+
for fs in self.secondaries.filesystems() {
|
|
495
|
+
if fs.metadata(&from).is_ok() {
|
|
496
|
+
ops::copy_reference_ext(fs, &self.primary, &from, &to).await?;
|
|
497
|
+
had_at_least_one_success = true;
|
|
498
|
+
break;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// If the rename operation was a success then we need to update any
|
|
504
|
+
// whiteout files on the primary before we return success.
|
|
505
|
+
if had_at_least_one_success {
|
|
506
|
+
for fs in self.secondaries.filesystems() {
|
|
507
|
+
if fs.metadata(&from).is_ok() {
|
|
508
|
+
tracing::trace!(
|
|
509
|
+
path=%from.display(),
|
|
510
|
+
"Creating a whiteout for the file that was renamed",
|
|
511
|
+
);
|
|
512
|
+
ops::create_white_out(&self.primary, &from).ok();
|
|
513
|
+
break;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
ops::remove_white_out(&self.primary, &to);
|
|
517
|
+
return Ok(());
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Otherwise we are in a failure scenario
|
|
521
|
+
self.permission_error_or_not_found(&from)
|
|
522
|
+
})
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
fn metadata(&self, path: &Path) -> Result<Metadata, FsError> {
|
|
526
|
+
// Whiteout files can not be read, they are just markers
|
|
527
|
+
if ops::is_white_out(path).is_some() {
|
|
528
|
+
return Err(FsError::EntryNotFound);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Check if the file is in the primary
|
|
532
|
+
match self.primary.metadata(path) {
|
|
533
|
+
Ok(meta) => return Ok(meta),
|
|
534
|
+
Err(e) if should_continue(e) => {}
|
|
535
|
+
Err(e) => return Err(e),
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// There might be a whiteout, search for this
|
|
539
|
+
if ops::has_white_out(&self.primary, path) {
|
|
540
|
+
return Err(FsError::EntryNotFound);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Otherwise scan the secondaries
|
|
544
|
+
for fs in self.secondaries.filesystems() {
|
|
545
|
+
match fs.metadata(path) {
|
|
546
|
+
Err(e) if should_continue(e) => continue,
|
|
547
|
+
other => return other,
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
Err(FsError::EntryNotFound)
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
fn symlink_metadata(&self, path: &Path) -> crate::Result<Metadata> {
|
|
555
|
+
// Whiteout files can not be read, they are just markers
|
|
556
|
+
if ops::is_white_out(path).is_some() {
|
|
557
|
+
return Err(FsError::EntryNotFound);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Check if the file is in the primary
|
|
561
|
+
match self.primary.symlink_metadata(path) {
|
|
562
|
+
Ok(meta) => return Ok(meta),
|
|
563
|
+
Err(e) if should_continue(e) => {}
|
|
564
|
+
Err(e) => return Err(e),
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// There might be a whiteout, search for this
|
|
568
|
+
if ops::has_white_out(&self.primary, path) {
|
|
569
|
+
return Err(FsError::EntryNotFound);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Otherwise scan the secondaries
|
|
573
|
+
for fs in self.secondaries.filesystems() {
|
|
574
|
+
match fs.symlink_metadata(path) {
|
|
575
|
+
Err(e) if should_continue(e) => continue,
|
|
576
|
+
other => return other,
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
Err(FsError::EntryNotFound)
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
fn remove_file(&self, path: &Path) -> Result<(), FsError> {
|
|
584
|
+
// It is not possible to delete whiteout files directly, instead
|
|
585
|
+
// one must delete the original file
|
|
586
|
+
if ops::is_white_out(path).is_some() {
|
|
587
|
+
return Err(FsError::InvalidInput);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// If the file is contained in a secondary then then we need to create a
|
|
591
|
+
// whiteout file so that it is suppressed.
|
|
592
|
+
let had_at_least_one_success = self.secondaries.filesystems().into_iter().any(|fs| {
|
|
593
|
+
fs.metadata(path).is_ok() && ops::create_white_out(&self.primary, path).is_ok()
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
// Attempt to remove it from the primary
|
|
597
|
+
match self.primary.remove_file(path) {
|
|
598
|
+
Err(e) if should_continue(e) => {}
|
|
599
|
+
other => return other,
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
if had_at_least_one_success {
|
|
603
|
+
return Ok(());
|
|
604
|
+
}
|
|
605
|
+
self.permission_error_or_not_found(path)
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
fn new_open_options(&self) -> OpenOptions<'_> {
|
|
609
|
+
OpenOptions::new(self)
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
impl<P, S> FileOpener for OverlayFileSystem<P, S>
|
|
614
|
+
where
|
|
615
|
+
P: FileSystem + Send + 'static,
|
|
616
|
+
S: for<'a> FileSystems<'a> + Send + Sync + 'static,
|
|
617
|
+
for<'a> <<S as FileSystems<'a>>::Iter as IntoIterator>::IntoIter: Send,
|
|
618
|
+
{
|
|
619
|
+
fn open(
|
|
620
|
+
&self,
|
|
621
|
+
path: &Path,
|
|
622
|
+
conf: &OpenOptionsConfig,
|
|
623
|
+
) -> Result<Box<dyn VirtualFile + Send + Sync + 'static>, FsError> {
|
|
624
|
+
// Whiteout files can not be read, they are just markers
|
|
625
|
+
if ops::is_white_out(path).is_some() {
|
|
626
|
+
tracing::trace!(
|
|
627
|
+
path=%path.display(),
|
|
628
|
+
options=?conf,
|
|
629
|
+
"Whiteout files can't be opened",
|
|
630
|
+
);
|
|
631
|
+
return Err(FsError::InvalidInput);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// Check if the file is in the primary (without actually creating it) as
|
|
635
|
+
// when the file is in the primary it takes preference over any of file
|
|
636
|
+
{
|
|
637
|
+
let mut conf = conf.clone();
|
|
638
|
+
conf.create = false;
|
|
639
|
+
conf.create_new = false;
|
|
640
|
+
match self.primary.new_open_options().options(conf).open(path) {
|
|
641
|
+
Err(e) if should_continue(e) => {}
|
|
642
|
+
other => return other,
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// In the scenario that we are creating the file then there is
|
|
647
|
+
// special handling that will ensure its setup correctly
|
|
648
|
+
if conf.create_new {
|
|
649
|
+
// When the secondary has the directory structure but the primary
|
|
650
|
+
// does not then we need to make sure we create all the structure
|
|
651
|
+
// in the primary
|
|
652
|
+
if let Some(parent) = path.parent() {
|
|
653
|
+
if ops::exists(self, parent) {
|
|
654
|
+
// We create the directory structure on the primary so that
|
|
655
|
+
// the new file can be created, this will make it override
|
|
656
|
+
// whatever is in the primary
|
|
657
|
+
ops::create_dir_all(&self.primary, parent)?;
|
|
658
|
+
} else {
|
|
659
|
+
return Err(FsError::EntryNotFound);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// Remove any whiteout
|
|
664
|
+
ops::remove_white_out(&self.primary, path);
|
|
665
|
+
|
|
666
|
+
// Create the file in the primary
|
|
667
|
+
return self
|
|
668
|
+
.primary
|
|
669
|
+
.new_open_options()
|
|
670
|
+
.options(conf.clone())
|
|
671
|
+
.open(path);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// There might be a whiteout, search for this and if its found then
|
|
675
|
+
// we are done as the secondary file or directory has been earlier
|
|
676
|
+
// deleted via a white out (when the create flag is set then
|
|
677
|
+
// the white out marker is ignored)
|
|
678
|
+
if !conf.create && ops::has_white_out(&self.primary, path) {
|
|
679
|
+
tracing::trace!(
|
|
680
|
+
path=%path.display(),
|
|
681
|
+
"The file has been whited out",
|
|
682
|
+
);
|
|
683
|
+
return Err(FsError::EntryNotFound);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// Determine if a mutation will be possible with the opened file
|
|
687
|
+
let require_mutations = conf.append || conf.write || conf.create_new | conf.truncate;
|
|
688
|
+
|
|
689
|
+
// If the file is on a secondary then we should open it
|
|
690
|
+
if !ops::has_white_out(&self.primary, path) {
|
|
691
|
+
for fs in self.secondaries.filesystems() {
|
|
692
|
+
let mut sub_conf = conf.clone();
|
|
693
|
+
sub_conf.create = false;
|
|
694
|
+
sub_conf.create_new = false;
|
|
695
|
+
sub_conf.append = false;
|
|
696
|
+
sub_conf.truncate = false;
|
|
697
|
+
match fs.new_open_options().options(sub_conf.clone()).open(path) {
|
|
698
|
+
Err(e) if should_continue(e) => continue,
|
|
699
|
+
Ok(file) if require_mutations => {
|
|
700
|
+
// If the file was opened with the ability to mutate then we need
|
|
701
|
+
// to return a copy on write emulation so that the file can be
|
|
702
|
+
// copied from the secondary to the primary in the scenario that
|
|
703
|
+
// it is edited
|
|
704
|
+
return open_copy_on_write(path, conf, &self.primary, file);
|
|
705
|
+
}
|
|
706
|
+
Ok(file) => {
|
|
707
|
+
return Ok(Box::new(SecondaryFile {
|
|
708
|
+
path: path.to_path_buf(),
|
|
709
|
+
primary: Arc::clone(&self.primary),
|
|
710
|
+
inner: file,
|
|
711
|
+
}));
|
|
712
|
+
}
|
|
713
|
+
Err(err) => return Err(err),
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// If we are creating the file then do so
|
|
719
|
+
if conf.create {
|
|
720
|
+
// Create the parent structure and remove any whiteouts
|
|
721
|
+
if let Some(parent) = path.parent()
|
|
722
|
+
&& ops::exists(self, parent)
|
|
723
|
+
{
|
|
724
|
+
ops::create_dir_all(&self.primary, parent)?;
|
|
725
|
+
}
|
|
726
|
+
ops::remove_white_out(&self.primary, path);
|
|
727
|
+
|
|
728
|
+
// Create the file in the primary
|
|
729
|
+
return self
|
|
730
|
+
.primary
|
|
731
|
+
.new_open_options()
|
|
732
|
+
.options(conf.clone())
|
|
733
|
+
.open(path);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// The file does not exist anywhere
|
|
737
|
+
Err(FsError::EntryNotFound)
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
fn open_copy_on_write<P>(
|
|
742
|
+
path: &Path,
|
|
743
|
+
conf: &OpenOptionsConfig,
|
|
744
|
+
primary: &Arc<P>,
|
|
745
|
+
file: Box<dyn VirtualFile + Send + Sync>,
|
|
746
|
+
) -> Result<Box<dyn VirtualFile + Send + Sync>, FsError>
|
|
747
|
+
where
|
|
748
|
+
P: FileSystem,
|
|
749
|
+
{
|
|
750
|
+
struct CopyOnWriteFile<P> {
|
|
751
|
+
path: PathBuf,
|
|
752
|
+
primary: Arc<P>,
|
|
753
|
+
state: CowState,
|
|
754
|
+
readable: bool,
|
|
755
|
+
append: bool,
|
|
756
|
+
new_size: Option<u64>,
|
|
757
|
+
unlinked: bool,
|
|
758
|
+
}
|
|
759
|
+
enum CowState {
|
|
760
|
+
// The original file is still open and can be accessed for all
|
|
761
|
+
// read operations
|
|
762
|
+
ReadOnly(Box<dyn VirtualFile + Send + Sync>),
|
|
763
|
+
// The copy has started but first we need to get the cursor
|
|
764
|
+
// position within the source file so that it can be restored
|
|
765
|
+
SeekingGet(Box<dyn VirtualFile + Send + Sync>),
|
|
766
|
+
// Now we have the original starting cursor location we need
|
|
767
|
+
// to move the position of the read to the start of the source
|
|
768
|
+
// file
|
|
769
|
+
SeekingSet {
|
|
770
|
+
original_offset: u64,
|
|
771
|
+
src: Box<dyn VirtualFile + Send + Sync>,
|
|
772
|
+
},
|
|
773
|
+
// We are now copying the data in parts held in the buffer piece
|
|
774
|
+
// by piece until the original file is completely copied
|
|
775
|
+
Copying {
|
|
776
|
+
original_offset: u64,
|
|
777
|
+
buf: Vec<u8>,
|
|
778
|
+
buf_pos: usize,
|
|
779
|
+
dst: Box<dyn VirtualFile + Send + Sync>,
|
|
780
|
+
src: Box<dyn VirtualFile + Send + Sync>,
|
|
781
|
+
},
|
|
782
|
+
// After copying the file we need to seek the position back
|
|
783
|
+
// to its original location on the newly copied file
|
|
784
|
+
SeekingRestore {
|
|
785
|
+
dst: Box<dyn VirtualFile + Send + Sync>,
|
|
786
|
+
},
|
|
787
|
+
// We have copied the file and can use all the normal operations
|
|
788
|
+
Copied(Box<dyn VirtualFile + Send + Sync>),
|
|
789
|
+
// An error occurred during the copy operation and we are now in a
|
|
790
|
+
// failed state, after the error is consumed it will reset back
|
|
791
|
+
// to the original file
|
|
792
|
+
Error {
|
|
793
|
+
err: io::Error,
|
|
794
|
+
src: Box<dyn VirtualFile + Send + Sync>,
|
|
795
|
+
},
|
|
796
|
+
}
|
|
797
|
+
impl CowState {
|
|
798
|
+
fn as_ref(&self) -> &(dyn VirtualFile + Send + Sync) {
|
|
799
|
+
match self {
|
|
800
|
+
Self::ReadOnly(inner) => inner.as_ref(),
|
|
801
|
+
Self::SeekingGet(inner) => inner.as_ref(),
|
|
802
|
+
Self::SeekingSet { src, .. } => src.as_ref(),
|
|
803
|
+
Self::Copying { src, .. } => src.as_ref(),
|
|
804
|
+
Self::SeekingRestore { dst, .. } => dst.as_ref(),
|
|
805
|
+
Self::Copied(inner) => inner.as_ref(),
|
|
806
|
+
Self::Error { src, .. } => src.as_ref(),
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
fn as_mut(&mut self) -> &mut (dyn VirtualFile + Send + Sync) {
|
|
810
|
+
match self {
|
|
811
|
+
Self::ReadOnly(inner) => inner.as_mut(),
|
|
812
|
+
Self::SeekingGet(inner) => inner.as_mut(),
|
|
813
|
+
Self::SeekingSet { src, .. } => src.as_mut(),
|
|
814
|
+
Self::Copying { src, .. } => src.as_mut(),
|
|
815
|
+
Self::SeekingRestore { dst, .. } => dst.as_mut(),
|
|
816
|
+
Self::Copied(inner) => inner.as_mut(),
|
|
817
|
+
Self::Error { src, .. } => src.as_mut(),
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
impl<P> CopyOnWriteFile<P>
|
|
823
|
+
where
|
|
824
|
+
P: FileSystem + 'static,
|
|
825
|
+
{
|
|
826
|
+
fn poll_copy_progress(&mut self, cx: &mut Context) -> Poll<io::Result<()>> {
|
|
827
|
+
// Enter a loop until we go pending
|
|
828
|
+
let mut again = true;
|
|
829
|
+
while again {
|
|
830
|
+
again = false;
|
|
831
|
+
|
|
832
|
+
// The state machine is updated during the poll operation
|
|
833
|
+
replace_with_or_abort(&mut self.state, |state| match state {
|
|
834
|
+
// We record the current position of the file so that it can be
|
|
835
|
+
// restored after the copy-on-write is finished
|
|
836
|
+
CowState::SeekingGet(mut src) => {
|
|
837
|
+
match Pin::new(src.as_mut()).poll_complete(cx) {
|
|
838
|
+
Poll::Ready(Ok(offset)) => {
|
|
839
|
+
if let Err(err) =
|
|
840
|
+
Pin::new(src.as_mut()).start_seek(SeekFrom::Start(0))
|
|
841
|
+
{
|
|
842
|
+
return CowState::Error { err, src };
|
|
843
|
+
}
|
|
844
|
+
again = true;
|
|
845
|
+
CowState::SeekingSet {
|
|
846
|
+
original_offset: offset,
|
|
847
|
+
src,
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
Poll::Ready(Err(err)) => CowState::Error { err, src },
|
|
851
|
+
Poll::Pending => CowState::SeekingGet(src),
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
// We complete the seek operation to the start of the source file
|
|
856
|
+
CowState::SeekingSet {
|
|
857
|
+
original_offset,
|
|
858
|
+
mut src,
|
|
859
|
+
} => {
|
|
860
|
+
match Pin::new(src.as_mut()).poll_complete(cx).map_ok(|_| ()) {
|
|
861
|
+
Poll::Ready(Ok(())) => {
|
|
862
|
+
if self.unlinked {
|
|
863
|
+
again = true;
|
|
864
|
+
return CowState::Copying {
|
|
865
|
+
original_offset,
|
|
866
|
+
buf: Vec::new(),
|
|
867
|
+
buf_pos: 0,
|
|
868
|
+
dst: Box::new(crate::BufferFile::default()),
|
|
869
|
+
src,
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// Remove the whiteout, create the parent structure and open
|
|
874
|
+
// the new file on the primary
|
|
875
|
+
if let Some(parent) = self.path.parent() {
|
|
876
|
+
ops::create_dir_all(&self.primary, parent).ok();
|
|
877
|
+
}
|
|
878
|
+
let mut had_white_out = false;
|
|
879
|
+
if ops::has_white_out(&self.primary, &self.path) {
|
|
880
|
+
ops::remove_white_out(&self.primary, &self.path);
|
|
881
|
+
had_white_out = true;
|
|
882
|
+
}
|
|
883
|
+
let dst = self
|
|
884
|
+
.primary
|
|
885
|
+
.new_open_options()
|
|
886
|
+
.create(true)
|
|
887
|
+
.read(self.readable)
|
|
888
|
+
.write(true)
|
|
889
|
+
.truncate(true)
|
|
890
|
+
.open(&self.path);
|
|
891
|
+
match dst {
|
|
892
|
+
Ok(dst) if had_white_out => {
|
|
893
|
+
again = true;
|
|
894
|
+
CowState::Copied(dst)
|
|
895
|
+
}
|
|
896
|
+
Ok(dst) => {
|
|
897
|
+
again = true;
|
|
898
|
+
CowState::Copying {
|
|
899
|
+
original_offset,
|
|
900
|
+
buf: Vec::new(),
|
|
901
|
+
buf_pos: 0,
|
|
902
|
+
src,
|
|
903
|
+
dst,
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
Err(err) => CowState::Error {
|
|
907
|
+
err: err.into(),
|
|
908
|
+
src,
|
|
909
|
+
},
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
Poll::Ready(Err(err)) => CowState::Error { err, src },
|
|
913
|
+
Poll::Pending => CowState::SeekingSet {
|
|
914
|
+
original_offset,
|
|
915
|
+
src,
|
|
916
|
+
},
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
// We are now copying all the data on blocks
|
|
920
|
+
CowState::Copying {
|
|
921
|
+
mut src,
|
|
922
|
+
mut dst,
|
|
923
|
+
mut buf,
|
|
924
|
+
mut buf_pos,
|
|
925
|
+
original_offset,
|
|
926
|
+
} => {
|
|
927
|
+
loop {
|
|
928
|
+
// We are either copying more data from the source
|
|
929
|
+
// or we are copying the data to the destination
|
|
930
|
+
if buf_pos < buf.len() {
|
|
931
|
+
let dst_pinned = Pin::new(dst.as_mut());
|
|
932
|
+
match dst_pinned.poll_write(cx, &buf[buf_pos..]) {
|
|
933
|
+
Poll::Ready(Ok(0)) => {}
|
|
934
|
+
Poll::Ready(Ok(amt)) => {
|
|
935
|
+
buf_pos += amt;
|
|
936
|
+
continue;
|
|
937
|
+
}
|
|
938
|
+
Poll::Ready(Err(err)) => {
|
|
939
|
+
return CowState::Error { err, src };
|
|
940
|
+
}
|
|
941
|
+
Poll::Pending => {}
|
|
942
|
+
}
|
|
943
|
+
} else {
|
|
944
|
+
buf.resize_with(8192, || 0);
|
|
945
|
+
buf_pos = 8192;
|
|
946
|
+
let mut read_buf = ReadBuf::new(&mut buf);
|
|
947
|
+
match Pin::new(src.as_mut()).poll_read(cx, &mut read_buf) {
|
|
948
|
+
Poll::Ready(Ok(())) if read_buf.filled().is_empty() => {
|
|
949
|
+
again = true;
|
|
950
|
+
|
|
951
|
+
if self.append {
|
|
952
|
+
// When we append then we leave the cursor at the
|
|
953
|
+
// end of the file
|
|
954
|
+
return CowState::Copied(dst);
|
|
955
|
+
} else {
|
|
956
|
+
// No more data exists to be read so we now move on to
|
|
957
|
+
// restoring the cursor back to the original position
|
|
958
|
+
if let Err(err) = Pin::new(dst.as_mut())
|
|
959
|
+
.start_seek(SeekFrom::Start(original_offset))
|
|
960
|
+
{
|
|
961
|
+
return CowState::Error { err, src };
|
|
962
|
+
}
|
|
963
|
+
return CowState::SeekingRestore { dst };
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
Poll::Ready(Ok(())) => {
|
|
967
|
+
// There is more data to be processed
|
|
968
|
+
let new_len = read_buf.filled().len();
|
|
969
|
+
unsafe { buf.set_len(new_len) };
|
|
970
|
+
buf_pos = 0;
|
|
971
|
+
continue;
|
|
972
|
+
}
|
|
973
|
+
Poll::Ready(Err(err)) => return CowState::Error { err, src },
|
|
974
|
+
Poll::Pending => {}
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
return CowState::Copying {
|
|
978
|
+
original_offset,
|
|
979
|
+
buf,
|
|
980
|
+
buf_pos,
|
|
981
|
+
src,
|
|
982
|
+
dst,
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
// Now once the restoration of the seek position completes we set the copied state
|
|
987
|
+
CowState::SeekingRestore { mut dst } => {
|
|
988
|
+
match Pin::new(dst.as_mut()).poll_complete(cx) {
|
|
989
|
+
Poll::Ready(_) => {
|
|
990
|
+
// If we have changed the length then set it
|
|
991
|
+
if let Some(new_size) = self.new_size.take() {
|
|
992
|
+
dst.set_len(new_size).ok();
|
|
993
|
+
}
|
|
994
|
+
CowState::Copied(dst)
|
|
995
|
+
}
|
|
996
|
+
Poll::Pending => CowState::SeekingRestore { dst },
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
s => s,
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// Determine what response to give based off the state, when an error occurs
|
|
1004
|
+
// this will be returned and the copy-on-write will be reset
|
|
1005
|
+
let mut ret = Poll::Pending;
|
|
1006
|
+
replace_with_or_abort(&mut self.state, |state| match state {
|
|
1007
|
+
CowState::ReadOnly(src) => {
|
|
1008
|
+
ret = Poll::Ready(Ok(()));
|
|
1009
|
+
CowState::ReadOnly(src)
|
|
1010
|
+
}
|
|
1011
|
+
CowState::Copied(src) => {
|
|
1012
|
+
ret = Poll::Ready(Ok(()));
|
|
1013
|
+
CowState::Copied(src)
|
|
1014
|
+
}
|
|
1015
|
+
CowState::Error { err, src } => {
|
|
1016
|
+
ret = Poll::Ready(Err(err));
|
|
1017
|
+
CowState::ReadOnly(src)
|
|
1018
|
+
}
|
|
1019
|
+
state => {
|
|
1020
|
+
ret = Poll::Pending;
|
|
1021
|
+
state
|
|
1022
|
+
}
|
|
1023
|
+
});
|
|
1024
|
+
ret
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
fn poll_copy_start_and_progress(&mut self, cx: &mut Context) -> Poll<io::Result<()>> {
|
|
1028
|
+
replace_with_or_abort(&mut self.state, |state| match state {
|
|
1029
|
+
CowState::ReadOnly(inner) => {
|
|
1030
|
+
tracing::trace!("COW file touched, starting file clone");
|
|
1031
|
+
CowState::SeekingGet(inner)
|
|
1032
|
+
}
|
|
1033
|
+
state => state,
|
|
1034
|
+
});
|
|
1035
|
+
self.poll_copy_progress(cx)
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
impl<P> Debug for CopyOnWriteFile<P>
|
|
1040
|
+
where
|
|
1041
|
+
P: FileSystem + 'static,
|
|
1042
|
+
{
|
|
1043
|
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
1044
|
+
f.debug_struct("CopyOnWriteFile").finish()
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
impl<P> VirtualFile for CopyOnWriteFile<P>
|
|
1049
|
+
where
|
|
1050
|
+
P: FileSystem + 'static,
|
|
1051
|
+
{
|
|
1052
|
+
fn last_accessed(&self) -> u64 {
|
|
1053
|
+
self.state.as_ref().last_accessed()
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
fn last_modified(&self) -> u64 {
|
|
1057
|
+
self.state.as_ref().last_modified()
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
fn created_time(&self) -> u64 {
|
|
1061
|
+
self.state.as_ref().created_time()
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
fn size(&self) -> u64 {
|
|
1065
|
+
self.state.as_ref().size()
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
fn set_len(&mut self, new_size: u64) -> crate::Result<()> {
|
|
1069
|
+
self.new_size = Some(new_size);
|
|
1070
|
+
replace_with_or_abort(&mut self.state, |state| match state {
|
|
1071
|
+
CowState::Copied(mut file) => {
|
|
1072
|
+
file.set_len(new_size).ok();
|
|
1073
|
+
CowState::Copied(file)
|
|
1074
|
+
}
|
|
1075
|
+
state => {
|
|
1076
|
+
if self.unlinked {
|
|
1077
|
+
return match state {
|
|
1078
|
+
CowState::ReadOnly(inner) => CowState::SeekingGet(inner),
|
|
1079
|
+
state => state,
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
// in the scenario where the length is set but the file is not
|
|
1084
|
+
// polled then we need to make sure we create a file properly
|
|
1085
|
+
if let Some(parent) = self.path.parent() {
|
|
1086
|
+
ops::create_dir_all(&self.primary, parent).ok();
|
|
1087
|
+
}
|
|
1088
|
+
let dst = self
|
|
1089
|
+
.primary
|
|
1090
|
+
.new_open_options()
|
|
1091
|
+
.create(true)
|
|
1092
|
+
.write(true)
|
|
1093
|
+
.open(&self.path);
|
|
1094
|
+
if let Ok(mut file) = dst {
|
|
1095
|
+
file.set_len(new_size).ok();
|
|
1096
|
+
}
|
|
1097
|
+
state
|
|
1098
|
+
}
|
|
1099
|
+
});
|
|
1100
|
+
Ok(())
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
fn unlink(&mut self) -> crate::Result<()> {
|
|
1104
|
+
match &self.state {
|
|
1105
|
+
CowState::Copied(_) => {
|
|
1106
|
+
// The COW copy has landed in primary – it is our file, so it
|
|
1107
|
+
// is safe to remove it and create a whiteout for the secondary.
|
|
1108
|
+
unlink_overlay_path(&self.primary, &self.path)?;
|
|
1109
|
+
}
|
|
1110
|
+
_ => {
|
|
1111
|
+
// The file has not yet been written to primary. Only create a
|
|
1112
|
+
// whiteout to hide the secondary entry; do NOT remove anything
|
|
1113
|
+
// from primary, because whatever is currently bound to this
|
|
1114
|
+
// path may be an independently-created file.
|
|
1115
|
+
match ops::create_white_out(&self.primary, &self.path) {
|
|
1116
|
+
Ok(()) => {}
|
|
1117
|
+
// The whiteout already exists: the path was already deleted
|
|
1118
|
+
// from the overlay's perspective.
|
|
1119
|
+
Err(FsError::AlreadyExists) => return Err(FsError::EntryNotFound),
|
|
1120
|
+
Err(e) => return Err(e),
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
self.unlinked = true;
|
|
1125
|
+
Ok(())
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
fn poll_read_ready(
|
|
1129
|
+
mut self: Pin<&mut Self>,
|
|
1130
|
+
cx: &mut std::task::Context<'_>,
|
|
1131
|
+
) -> Poll<std::io::Result<usize>> {
|
|
1132
|
+
match self.poll_copy_progress(cx) {
|
|
1133
|
+
Poll::Ready(Ok(())) => {}
|
|
1134
|
+
Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
|
|
1135
|
+
Poll::Pending => return Poll::Pending,
|
|
1136
|
+
}
|
|
1137
|
+
Pin::new(self.state.as_mut()).poll_read_ready(cx)
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
fn poll_write_ready(
|
|
1141
|
+
mut self: Pin<&mut Self>,
|
|
1142
|
+
cx: &mut std::task::Context<'_>,
|
|
1143
|
+
) -> Poll<std::io::Result<usize>> {
|
|
1144
|
+
match self.poll_copy_progress(cx) {
|
|
1145
|
+
Poll::Ready(Ok(())) => {}
|
|
1146
|
+
Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
|
|
1147
|
+
Poll::Pending => return Poll::Pending,
|
|
1148
|
+
}
|
|
1149
|
+
Pin::new(self.state.as_mut()).poll_write_ready(cx)
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
impl<P> AsyncWrite for CopyOnWriteFile<P>
|
|
1154
|
+
where
|
|
1155
|
+
P: FileSystem + 'static,
|
|
1156
|
+
{
|
|
1157
|
+
fn poll_write(
|
|
1158
|
+
mut self: Pin<&mut Self>,
|
|
1159
|
+
cx: &mut std::task::Context<'_>,
|
|
1160
|
+
buf: &[u8],
|
|
1161
|
+
) -> Poll<Result<usize, std::io::Error>> {
|
|
1162
|
+
match self.poll_copy_start_and_progress(cx) {
|
|
1163
|
+
Poll::Pending => return Poll::Pending,
|
|
1164
|
+
Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
|
|
1165
|
+
Poll::Ready(Ok(())) => {}
|
|
1166
|
+
}
|
|
1167
|
+
Pin::new(self.state.as_mut()).poll_write(cx, buf)
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
fn poll_write_vectored(
|
|
1171
|
+
mut self: Pin<&mut Self>,
|
|
1172
|
+
cx: &mut Context<'_>,
|
|
1173
|
+
bufs: &[io::IoSlice<'_>],
|
|
1174
|
+
) -> Poll<Result<usize, io::Error>> {
|
|
1175
|
+
match self.poll_copy_start_and_progress(cx) {
|
|
1176
|
+
Poll::Pending => return Poll::Pending,
|
|
1177
|
+
Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
|
|
1178
|
+
Poll::Ready(Ok(())) => {}
|
|
1179
|
+
}
|
|
1180
|
+
Pin::new(self.state.as_mut()).poll_write_vectored(cx, bufs)
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
fn poll_flush(
|
|
1184
|
+
mut self: Pin<&mut Self>,
|
|
1185
|
+
cx: &mut std::task::Context<'_>,
|
|
1186
|
+
) -> Poll<Result<(), std::io::Error>> {
|
|
1187
|
+
match self.poll_copy_progress(cx) {
|
|
1188
|
+
Poll::Ready(Ok(())) => {}
|
|
1189
|
+
Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
|
|
1190
|
+
Poll::Pending => return Poll::Pending,
|
|
1191
|
+
}
|
|
1192
|
+
// The file may actually be read-only and not support flush operations
|
|
1193
|
+
// at all, and there's nothing to flush in read-only state anyway.
|
|
1194
|
+
match self.state {
|
|
1195
|
+
CowState::ReadOnly(_) => Poll::Ready(Ok(())),
|
|
1196
|
+
_ => Pin::new(self.state.as_mut()).poll_flush(cx),
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
fn poll_shutdown(
|
|
1201
|
+
mut self: Pin<&mut Self>,
|
|
1202
|
+
cx: &mut std::task::Context<'_>,
|
|
1203
|
+
) -> Poll<Result<(), std::io::Error>> {
|
|
1204
|
+
match self.poll_copy_progress(cx) {
|
|
1205
|
+
Poll::Ready(Ok(())) => {}
|
|
1206
|
+
Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
|
|
1207
|
+
Poll::Pending => return Poll::Pending,
|
|
1208
|
+
}
|
|
1209
|
+
// Same deal as flush above
|
|
1210
|
+
match self.state {
|
|
1211
|
+
CowState::ReadOnly(_) => Poll::Ready(Ok(())),
|
|
1212
|
+
_ => Pin::new(self.state.as_mut()).poll_shutdown(cx),
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
impl<P> AsyncRead for CopyOnWriteFile<P>
|
|
1218
|
+
where
|
|
1219
|
+
P: FileSystem + 'static,
|
|
1220
|
+
{
|
|
1221
|
+
fn poll_read(
|
|
1222
|
+
mut self: Pin<&mut Self>,
|
|
1223
|
+
cx: &mut std::task::Context<'_>,
|
|
1224
|
+
buf: &mut tokio::io::ReadBuf<'_>,
|
|
1225
|
+
) -> Poll<std::io::Result<()>> {
|
|
1226
|
+
match self.poll_copy_progress(cx) {
|
|
1227
|
+
Poll::Ready(Ok(())) => {}
|
|
1228
|
+
p => return p,
|
|
1229
|
+
}
|
|
1230
|
+
Pin::new(self.state.as_mut()).poll_read(cx, buf)
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
impl<P> AsyncSeek for CopyOnWriteFile<P>
|
|
1235
|
+
where
|
|
1236
|
+
P: FileSystem + 'static,
|
|
1237
|
+
{
|
|
1238
|
+
fn start_seek(
|
|
1239
|
+
mut self: Pin<&mut Self>,
|
|
1240
|
+
position: std::io::SeekFrom,
|
|
1241
|
+
) -> std::io::Result<()> {
|
|
1242
|
+
match &mut self.state {
|
|
1243
|
+
CowState::ReadOnly(file)
|
|
1244
|
+
| CowState::SeekingGet(file)
|
|
1245
|
+
| CowState::Error { src: file, .. }
|
|
1246
|
+
| CowState::Copied(file)
|
|
1247
|
+
| CowState::SeekingRestore { dst: file, .. } => {
|
|
1248
|
+
Pin::new(file.as_mut()).start_seek(position)
|
|
1249
|
+
}
|
|
1250
|
+
CowState::SeekingSet {
|
|
1251
|
+
original_offset,
|
|
1252
|
+
src,
|
|
1253
|
+
..
|
|
1254
|
+
}
|
|
1255
|
+
| CowState::Copying {
|
|
1256
|
+
original_offset,
|
|
1257
|
+
src,
|
|
1258
|
+
..
|
|
1259
|
+
} => {
|
|
1260
|
+
*original_offset = match position {
|
|
1261
|
+
SeekFrom::Current(delta) => original_offset
|
|
1262
|
+
.checked_add_signed(delta)
|
|
1263
|
+
.unwrap_or(*original_offset),
|
|
1264
|
+
SeekFrom::Start(pos) => pos,
|
|
1265
|
+
SeekFrom::End(pos) => src
|
|
1266
|
+
.size()
|
|
1267
|
+
.checked_add_signed(pos)
|
|
1268
|
+
.unwrap_or(*original_offset),
|
|
1269
|
+
};
|
|
1270
|
+
Ok(())
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
fn poll_complete(
|
|
1276
|
+
mut self: Pin<&mut Self>,
|
|
1277
|
+
cx: &mut std::task::Context<'_>,
|
|
1278
|
+
) -> Poll<std::io::Result<u64>> {
|
|
1279
|
+
match self.poll_copy_progress(cx) {
|
|
1280
|
+
Poll::Ready(Ok(())) => {}
|
|
1281
|
+
Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
|
|
1282
|
+
Poll::Pending => return Poll::Pending,
|
|
1283
|
+
}
|
|
1284
|
+
Pin::new(self.state.as_mut()).poll_complete(cx)
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
tracing::trace!(
|
|
1289
|
+
path=%path.display(),
|
|
1290
|
+
options=?conf,
|
|
1291
|
+
"Opening the file in copy-on-write mode",
|
|
1292
|
+
);
|
|
1293
|
+
Ok(Box::new(CopyOnWriteFile::<P> {
|
|
1294
|
+
path: path.to_path_buf(),
|
|
1295
|
+
primary: primary.clone(),
|
|
1296
|
+
state: CowState::ReadOnly(file),
|
|
1297
|
+
readable: conf.read,
|
|
1298
|
+
append: conf.append,
|
|
1299
|
+
new_size: None,
|
|
1300
|
+
unlinked: false,
|
|
1301
|
+
}))
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
impl<P, S> Debug for OverlayFileSystem<P, S>
|
|
1305
|
+
where
|
|
1306
|
+
P: FileSystem,
|
|
1307
|
+
S: for<'a> FileSystems<'a>,
|
|
1308
|
+
{
|
|
1309
|
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
1310
|
+
struct IterFilesystems<'a, S>(&'a S);
|
|
1311
|
+
impl<S> Debug for IterFilesystems<'_, S>
|
|
1312
|
+
where
|
|
1313
|
+
S: for<'b> FileSystems<'b>,
|
|
1314
|
+
{
|
|
1315
|
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
1316
|
+
let mut f = f.debug_list();
|
|
1317
|
+
|
|
1318
|
+
for fs in self.0.filesystems() {
|
|
1319
|
+
f.entry(&fs);
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
f.finish()
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
f.debug_struct("OverlayFileSystem")
|
|
1327
|
+
.field("primary", &self.primary)
|
|
1328
|
+
.field("secondaries", &IterFilesystems(&self.secondaries))
|
|
1329
|
+
.finish()
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
fn should_continue(e: FsError) -> bool {
|
|
1334
|
+
// HACK: We shouldn't really be ignoring FsError::BaseNotDirectory, but
|
|
1335
|
+
// it's needed because the mem_fs::FileSystem doesn't return
|
|
1336
|
+
// FsError::EntryNotFound when an intermediate directory doesn't exist
|
|
1337
|
+
// (i.e. the "/path/to" in "/path/to/file.txt").
|
|
1338
|
+
matches!(
|
|
1339
|
+
e,
|
|
1340
|
+
FsError::EntryNotFound | FsError::InvalidInput | FsError::BaseNotDirectory
|
|
1341
|
+
)
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
#[cfg(test)]
|
|
1345
|
+
mod tests {
|
|
1346
|
+
use std::path::PathBuf;
|
|
1347
|
+
|
|
1348
|
+
use super::*;
|
|
1349
|
+
use crate::mem_fs::FileSystem as MemFS;
|
|
1350
|
+
use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
|
|
1351
|
+
|
|
1352
|
+
#[test]
|
|
1353
|
+
fn overlay_read_dir_rebases_mounted_entries() {
|
|
1354
|
+
let primary = MemFS::default();
|
|
1355
|
+
ops::create_dir_all(&primary, "/app").unwrap();
|
|
1356
|
+
|
|
1357
|
+
let volume = MemFS::default();
|
|
1358
|
+
ops::create_dir_all(&volume, "/themes/twentytwentyfour").unwrap();
|
|
1359
|
+
|
|
1360
|
+
let container = MemFS::default();
|
|
1361
|
+
ops::create_dir_all(&container, "/app/wp-content/themes/twentytwentyfour").unwrap();
|
|
1362
|
+
|
|
1363
|
+
let volume: Arc<dyn FileSystem + Send + Sync> = Arc::new(volume);
|
|
1364
|
+
primary
|
|
1365
|
+
.mount(
|
|
1366
|
+
PathBuf::from("/app/wp-content"),
|
|
1367
|
+
&volume,
|
|
1368
|
+
PathBuf::from("/"),
|
|
1369
|
+
)
|
|
1370
|
+
.unwrap();
|
|
1371
|
+
|
|
1372
|
+
let overlay = OverlayFileSystem::new(primary, [container]);
|
|
1373
|
+
|
|
1374
|
+
let mut entries: Vec<_> = overlay
|
|
1375
|
+
.read_dir(Path::new("/app/wp-content/themes"))
|
|
1376
|
+
.unwrap()
|
|
1377
|
+
.map(|entry| entry.unwrap().path)
|
|
1378
|
+
.collect();
|
|
1379
|
+
entries.sort();
|
|
1380
|
+
|
|
1381
|
+
assert_eq!(
|
|
1382
|
+
entries,
|
|
1383
|
+
vec![PathBuf::from("/app/wp-content/themes/twentytwentyfour")],
|
|
1384
|
+
);
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
#[tokio::test]
|
|
1388
|
+
async fn remove_directory() {
|
|
1389
|
+
let primary = MemFS::default();
|
|
1390
|
+
let secondary = MemFS::default();
|
|
1391
|
+
let first = Path::new("/first");
|
|
1392
|
+
let second = Path::new("/second");
|
|
1393
|
+
let file_txt = second.join("file.txt");
|
|
1394
|
+
let third = Path::new("/third");
|
|
1395
|
+
primary.create_dir(first).unwrap();
|
|
1396
|
+
primary.create_dir(second).unwrap();
|
|
1397
|
+
primary
|
|
1398
|
+
.new_open_options()
|
|
1399
|
+
.create(true)
|
|
1400
|
+
.write(true)
|
|
1401
|
+
.open(&file_txt)
|
|
1402
|
+
.unwrap()
|
|
1403
|
+
.write_all(b"Hello, World!")
|
|
1404
|
+
.await
|
|
1405
|
+
.unwrap();
|
|
1406
|
+
secondary.create_dir(third).unwrap();
|
|
1407
|
+
|
|
1408
|
+
let overlay = OverlayFileSystem::new(primary, [secondary]);
|
|
1409
|
+
|
|
1410
|
+
// Delete a folder on the primary filesystem
|
|
1411
|
+
overlay.remove_dir(first).unwrap();
|
|
1412
|
+
assert_eq!(
|
|
1413
|
+
overlay.primary().metadata(first).unwrap_err(),
|
|
1414
|
+
FsError::EntryNotFound,
|
|
1415
|
+
"Deleted from primary"
|
|
1416
|
+
);
|
|
1417
|
+
assert!(!ops::exists(&overlay.secondaries[0], second));
|
|
1418
|
+
|
|
1419
|
+
// Directory on the primary fs isn't empty
|
|
1420
|
+
assert_eq!(
|
|
1421
|
+
overlay.remove_dir(second).unwrap_err(),
|
|
1422
|
+
FsError::DirectoryNotEmpty,
|
|
1423
|
+
);
|
|
1424
|
+
|
|
1425
|
+
// Try to remove something on one of the overlay filesystems
|
|
1426
|
+
assert_eq!(overlay.remove_dir(third), Ok(()));
|
|
1427
|
+
|
|
1428
|
+
// It should no longer exist
|
|
1429
|
+
assert_eq!(overlay.metadata(third).unwrap_err(), FsError::EntryNotFound);
|
|
1430
|
+
|
|
1431
|
+
assert!(ops::exists(&overlay.secondaries[0], third));
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
#[tokio::test]
|
|
1435
|
+
async fn open_files() {
|
|
1436
|
+
let primary = MemFS::default();
|
|
1437
|
+
let secondary = MemFS::default();
|
|
1438
|
+
ops::create_dir_all(&primary, "/primary").unwrap();
|
|
1439
|
+
ops::touch(&primary, "/primary/read.txt").unwrap();
|
|
1440
|
+
ops::touch(&primary, "/primary/write.txt").unwrap();
|
|
1441
|
+
ops::create_dir_all(&secondary, "/secondary").unwrap();
|
|
1442
|
+
ops::touch(&secondary, "/secondary/read.txt").unwrap();
|
|
1443
|
+
ops::touch(&secondary, "/secondary/write.txt").unwrap();
|
|
1444
|
+
ops::create_dir_all(&secondary, "/primary").unwrap();
|
|
1445
|
+
ops::write(&secondary, "/primary/read.txt", "This is shadowed")
|
|
1446
|
+
.await
|
|
1447
|
+
.unwrap();
|
|
1448
|
+
|
|
1449
|
+
let fs = OverlayFileSystem::new(primary, [secondary]);
|
|
1450
|
+
|
|
1451
|
+
// Any new files will be created on the primary fs
|
|
1452
|
+
let _ = fs
|
|
1453
|
+
.new_open_options()
|
|
1454
|
+
.create(true)
|
|
1455
|
+
.write(true)
|
|
1456
|
+
.open("/new.txt")
|
|
1457
|
+
.unwrap();
|
|
1458
|
+
assert!(ops::exists(&fs.primary, "/new.txt"));
|
|
1459
|
+
assert!(!ops::exists(&fs.secondaries[0], "/new.txt"));
|
|
1460
|
+
|
|
1461
|
+
// You can open a file for reading and writing on the primary fs
|
|
1462
|
+
let _ = fs
|
|
1463
|
+
.new_open_options()
|
|
1464
|
+
.create(false)
|
|
1465
|
+
.write(true)
|
|
1466
|
+
.read(true)
|
|
1467
|
+
.open("/primary/write.txt")
|
|
1468
|
+
.unwrap();
|
|
1469
|
+
|
|
1470
|
+
// Files on the primary should always shadow the secondary
|
|
1471
|
+
let content = ops::read_to_string(&fs, "/primary/read.txt").await.unwrap();
|
|
1472
|
+
assert_ne!(content, "This is shadowed");
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
#[tokio::test]
|
|
1476
|
+
async fn open_secondary_file_preserves_owned_buffer_access() {
|
|
1477
|
+
let primary = MemFS::default();
|
|
1478
|
+
let secondary = MemFS::default();
|
|
1479
|
+
ops::create_dir_all(&secondary, "/secondary").unwrap();
|
|
1480
|
+
secondary
|
|
1481
|
+
.insert_ro_file(
|
|
1482
|
+
"/secondary/buffer.txt".as_ref(),
|
|
1483
|
+
b"overlay-buffer".to_vec().into(),
|
|
1484
|
+
)
|
|
1485
|
+
.unwrap();
|
|
1486
|
+
|
|
1487
|
+
let fs = OverlayFileSystem::new(primary, [secondary]);
|
|
1488
|
+
|
|
1489
|
+
let file = fs
|
|
1490
|
+
.new_open_options()
|
|
1491
|
+
.read(true)
|
|
1492
|
+
.open("/secondary/buffer.txt")
|
|
1493
|
+
.unwrap();
|
|
1494
|
+
|
|
1495
|
+
let buffer = file
|
|
1496
|
+
.as_owned_buffer()
|
|
1497
|
+
.expect("secondary wrapper should preserve inner owned-buffer access");
|
|
1498
|
+
assert_eq!(buffer.as_slice(), b"overlay-buffer");
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
#[tokio::test]
|
|
1502
|
+
async fn create_file_that_looks_like_it_is_in_a_secondary_filesystem_folder() {
|
|
1503
|
+
let primary = MemFS::default();
|
|
1504
|
+
let secondary = MemFS::default();
|
|
1505
|
+
ops::create_dir_all(&secondary, "/path/to/").unwrap();
|
|
1506
|
+
assert!(!ops::is_dir(&primary, "/path/to/"));
|
|
1507
|
+
let fs = OverlayFileSystem::new(primary, [secondary]);
|
|
1508
|
+
|
|
1509
|
+
ops::touch(&fs, "/path/to/file.txt").unwrap();
|
|
1510
|
+
|
|
1511
|
+
assert!(ops::is_dir(&fs.primary, "/path/to/"));
|
|
1512
|
+
assert!(ops::is_file(&fs.primary, "/path/to/file.txt"));
|
|
1513
|
+
assert!(!ops::is_file(&fs.secondaries[0], "/path/to/file.txt"));
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
#[tokio::test]
|
|
1517
|
+
async fn listed_files_appear_overlaid() {
|
|
1518
|
+
let primary = MemFS::default();
|
|
1519
|
+
let secondary = MemFS::default();
|
|
1520
|
+
let secondary_overlaid = MemFS::default();
|
|
1521
|
+
ops::create_dir_all(&primary, "/primary").unwrap();
|
|
1522
|
+
ops::touch(&primary, "/primary/read.txt").unwrap();
|
|
1523
|
+
ops::touch(&primary, "/primary/write.txt").unwrap();
|
|
1524
|
+
ops::create_dir_all(&secondary, "/secondary").unwrap();
|
|
1525
|
+
ops::touch(&secondary, "/secondary/read.txt").unwrap();
|
|
1526
|
+
ops::touch(&secondary, "/secondary/write.txt").unwrap();
|
|
1527
|
+
// This second "secondary" filesystem should share the same folders as
|
|
1528
|
+
// the first one.
|
|
1529
|
+
ops::create_dir_all(&secondary_overlaid, "/secondary").unwrap();
|
|
1530
|
+
ops::touch(&secondary_overlaid, "/secondary/overlayed.txt").unwrap();
|
|
1531
|
+
|
|
1532
|
+
let fs = OverlayFileSystem::new(primary, [secondary, secondary_overlaid]);
|
|
1533
|
+
|
|
1534
|
+
let paths: Vec<_> = ops::walk(&fs, "/").map(|entry| entry.path()).collect();
|
|
1535
|
+
assert_eq!(
|
|
1536
|
+
paths,
|
|
1537
|
+
vec![
|
|
1538
|
+
PathBuf::from("/secondary"),
|
|
1539
|
+
PathBuf::from("/secondary/write.txt"),
|
|
1540
|
+
PathBuf::from("/secondary/read.txt"),
|
|
1541
|
+
PathBuf::from("/secondary/overlayed.txt"),
|
|
1542
|
+
PathBuf::from("/primary"),
|
|
1543
|
+
PathBuf::from("/primary/write.txt"),
|
|
1544
|
+
PathBuf::from("/primary/read.txt"),
|
|
1545
|
+
]
|
|
1546
|
+
);
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
#[tokio::test]
|
|
1550
|
+
async fn open_secondary_fs_files_in_write_mode() {
|
|
1551
|
+
let primary = MemFS::default();
|
|
1552
|
+
let secondary = MemFS::default();
|
|
1553
|
+
ops::create_dir_all(&secondary, "/secondary").unwrap();
|
|
1554
|
+
ops::write(&secondary, "/secondary/file.txt", b"Hello, World!")
|
|
1555
|
+
.await
|
|
1556
|
+
.unwrap();
|
|
1557
|
+
|
|
1558
|
+
let fs = OverlayFileSystem::new(primary, [secondary]);
|
|
1559
|
+
|
|
1560
|
+
let mut f = fs
|
|
1561
|
+
.new_open_options()
|
|
1562
|
+
.write(true)
|
|
1563
|
+
.read(true)
|
|
1564
|
+
.open("/secondary/file.txt")
|
|
1565
|
+
.unwrap();
|
|
1566
|
+
// reading is fine
|
|
1567
|
+
let mut buf = String::new();
|
|
1568
|
+
f.read_to_string(&mut buf).await.unwrap();
|
|
1569
|
+
assert_eq!(buf, "Hello, World!");
|
|
1570
|
+
f.seek(SeekFrom::Start(0)).await.unwrap();
|
|
1571
|
+
// next we will write a new set of bytes
|
|
1572
|
+
f.set_len(0).unwrap();
|
|
1573
|
+
assert_eq!(f.write(b"Hi").await.unwrap(), 2);
|
|
1574
|
+
// Same with flushing
|
|
1575
|
+
assert_eq!(f.flush().await.unwrap(), ());
|
|
1576
|
+
|
|
1577
|
+
// if we now read it then the data should be different
|
|
1578
|
+
buf = String::new();
|
|
1579
|
+
f.seek(SeekFrom::Start(0)).await.unwrap();
|
|
1580
|
+
f.read_to_string(&mut buf).await.unwrap();
|
|
1581
|
+
assert_eq!(buf, "Hi");
|
|
1582
|
+
drop(f);
|
|
1583
|
+
|
|
1584
|
+
// including if we open it again
|
|
1585
|
+
let mut f = fs
|
|
1586
|
+
.new_open_options()
|
|
1587
|
+
.read(true)
|
|
1588
|
+
.open("/secondary/file.txt")
|
|
1589
|
+
.unwrap();
|
|
1590
|
+
buf = String::new();
|
|
1591
|
+
f.read_to_string(&mut buf).await.unwrap();
|
|
1592
|
+
assert_eq!(buf, "Hi");
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
#[tokio::test]
|
|
1596
|
+
async fn open_secondary_fs_files_unlink() {
|
|
1597
|
+
let primary = MemFS::default();
|
|
1598
|
+
let secondary = MemFS::default();
|
|
1599
|
+
ops::create_dir_all(&secondary, "/secondary").unwrap();
|
|
1600
|
+
ops::write(&secondary, "/secondary/file.txt", b"Hello, World!")
|
|
1601
|
+
.await
|
|
1602
|
+
.unwrap();
|
|
1603
|
+
|
|
1604
|
+
let fs = OverlayFileSystem::new(primary, [secondary]);
|
|
1605
|
+
|
|
1606
|
+
fs.metadata(Path::new("/secondary/file.txt")).unwrap();
|
|
1607
|
+
|
|
1608
|
+
// Now delete the file and make sure its not found
|
|
1609
|
+
fs.remove_file(Path::new("/secondary/file.txt")).unwrap();
|
|
1610
|
+
assert_eq!(
|
|
1611
|
+
fs.metadata(Path::new("/secondary/file.txt")).unwrap_err(),
|
|
1612
|
+
FsError::EntryNotFound
|
|
1613
|
+
)
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
#[tokio::test]
|
|
1617
|
+
async fn open_secondary_fs_without_cow() {
|
|
1618
|
+
let primary = MemFS::default();
|
|
1619
|
+
let secondary = MemFS::default();
|
|
1620
|
+
ops::create_dir_all(&secondary, "/secondary").unwrap();
|
|
1621
|
+
ops::write(&secondary, "/secondary/file.txt", b"Hello, World!")
|
|
1622
|
+
.await
|
|
1623
|
+
.unwrap();
|
|
1624
|
+
|
|
1625
|
+
let fs = OverlayFileSystem::new(primary, [secondary]);
|
|
1626
|
+
|
|
1627
|
+
let mut f = fs
|
|
1628
|
+
.new_open_options()
|
|
1629
|
+
.create(true)
|
|
1630
|
+
.read(true)
|
|
1631
|
+
.open(Path::new("/secondary/file.txt"))
|
|
1632
|
+
.unwrap();
|
|
1633
|
+
assert_eq!(f.size() as usize, 13);
|
|
1634
|
+
|
|
1635
|
+
let mut buf = String::new();
|
|
1636
|
+
f.read_to_string(&mut buf).await.unwrap();
|
|
1637
|
+
assert_eq!(buf, "Hello, World!");
|
|
1638
|
+
|
|
1639
|
+
// it should not be in the primary and nor should the secondary folder
|
|
1640
|
+
assert!(!ops::is_dir(&fs.primary, "/secondary"));
|
|
1641
|
+
assert!(!ops::is_file(&fs.primary, "/secondary/file.txt"));
|
|
1642
|
+
assert!(ops::is_dir(&fs.secondaries[0], "/secondary"));
|
|
1643
|
+
assert!(ops::is_file(&fs.secondaries[0], "/secondary/file.txt"));
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
#[tokio::test]
|
|
1647
|
+
async fn unlink_open_secondary_fs_without_cow_keeps_handle_alive() {
|
|
1648
|
+
let primary = MemFS::default();
|
|
1649
|
+
let secondary = MemFS::default();
|
|
1650
|
+
ops::create_dir_all(&secondary, "/secondary").unwrap();
|
|
1651
|
+
ops::write(&secondary, "/secondary/file.txt", b"Hello, World!")
|
|
1652
|
+
.await
|
|
1653
|
+
.unwrap();
|
|
1654
|
+
|
|
1655
|
+
let fs = OverlayFileSystem::new(primary, [secondary]);
|
|
1656
|
+
|
|
1657
|
+
let mut f = fs
|
|
1658
|
+
.new_open_options()
|
|
1659
|
+
.read(true)
|
|
1660
|
+
.open(Path::new("/secondary/file.txt"))
|
|
1661
|
+
.unwrap();
|
|
1662
|
+
|
|
1663
|
+
f.unlink().unwrap();
|
|
1664
|
+
assert_eq!(
|
|
1665
|
+
fs.metadata(Path::new("/secondary/file.txt")).unwrap_err(),
|
|
1666
|
+
FsError::EntryNotFound
|
|
1667
|
+
);
|
|
1668
|
+
assert!(ops::is_file(&fs.primary, "/secondary/.wh.file.txt"));
|
|
1669
|
+
assert!(!ops::is_file(&fs.primary, "/secondary/file.txt"));
|
|
1670
|
+
assert!(ops::is_file(&fs.secondaries[0], "/secondary/file.txt"));
|
|
1671
|
+
|
|
1672
|
+
let mut buf = String::new();
|
|
1673
|
+
f.read_to_string(&mut buf).await.unwrap();
|
|
1674
|
+
assert_eq!(buf, "Hello, World!");
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
#[tokio::test]
|
|
1678
|
+
async fn unlink_open_secondary_fs_without_cow_returns_not_found_when_whiteout_already_exists() {
|
|
1679
|
+
let primary = MemFS::default();
|
|
1680
|
+
let secondary = MemFS::default();
|
|
1681
|
+
ops::create_dir_all(&secondary, "/secondary").unwrap();
|
|
1682
|
+
ops::write(&secondary, "/secondary/file.txt", b"Hello, World!")
|
|
1683
|
+
.await
|
|
1684
|
+
.unwrap();
|
|
1685
|
+
|
|
1686
|
+
let fs = OverlayFileSystem::new(primary, [secondary]);
|
|
1687
|
+
|
|
1688
|
+
let mut f = fs
|
|
1689
|
+
.new_open_options()
|
|
1690
|
+
.read(true)
|
|
1691
|
+
.open(Path::new("/secondary/file.txt"))
|
|
1692
|
+
.unwrap();
|
|
1693
|
+
|
|
1694
|
+
// The path was already deleted via another route before this handle
|
|
1695
|
+
// called unlink(). The handle should see EntryNotFound, not Ok(()).
|
|
1696
|
+
ops::create_white_out(&fs.primary, "/secondary/file.txt").unwrap();
|
|
1697
|
+
assert_eq!(f.unlink(), Err(FsError::EntryNotFound));
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
#[tokio::test]
|
|
1701
|
+
async fn unlink_open_secondary_fs_without_cow_preserves_whiteout_creation_error() {
|
|
1702
|
+
let primary = MemFS::default();
|
|
1703
|
+
let secondary = MemFS::default();
|
|
1704
|
+
ops::write(&primary, "/secondary", b"not a directory")
|
|
1705
|
+
.await
|
|
1706
|
+
.unwrap();
|
|
1707
|
+
ops::create_dir_all(&secondary, "/secondary").unwrap();
|
|
1708
|
+
ops::write(&secondary, "/secondary/file.txt", b"Hello, World!")
|
|
1709
|
+
.await
|
|
1710
|
+
.unwrap();
|
|
1711
|
+
|
|
1712
|
+
let fs = OverlayFileSystem::new(primary, [secondary]);
|
|
1713
|
+
|
|
1714
|
+
let mut f = fs
|
|
1715
|
+
.new_open_options()
|
|
1716
|
+
.read(true)
|
|
1717
|
+
.open(Path::new("/secondary/file.txt"))
|
|
1718
|
+
.unwrap();
|
|
1719
|
+
|
|
1720
|
+
assert_eq!(f.unlink(), Err(FsError::BaseNotDirectory));
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
#[tokio::test]
|
|
1724
|
+
async fn create_and_append_secondary_fs_with_cow() {
|
|
1725
|
+
let primary = MemFS::default();
|
|
1726
|
+
let secondary = MemFS::default();
|
|
1727
|
+
ops::create_dir_all(&secondary, "/secondary").unwrap();
|
|
1728
|
+
ops::write(&secondary, "/secondary/file.txt", b"Hello, World!")
|
|
1729
|
+
.await
|
|
1730
|
+
.unwrap();
|
|
1731
|
+
|
|
1732
|
+
let fs = OverlayFileSystem::new(primary, [secondary]);
|
|
1733
|
+
|
|
1734
|
+
let mut f = fs
|
|
1735
|
+
.new_open_options()
|
|
1736
|
+
.create(true)
|
|
1737
|
+
.append(true)
|
|
1738
|
+
.read(true)
|
|
1739
|
+
.open(Path::new("/secondary/file.txt"))
|
|
1740
|
+
.unwrap();
|
|
1741
|
+
assert_eq!(f.size() as usize, 13);
|
|
1742
|
+
|
|
1743
|
+
f.write_all(b"asdf").await.unwrap();
|
|
1744
|
+
assert_eq!(f.size() as usize, 17);
|
|
1745
|
+
|
|
1746
|
+
f.seek(SeekFrom::Start(0)).await.unwrap();
|
|
1747
|
+
|
|
1748
|
+
let mut buf = String::new();
|
|
1749
|
+
f.read_to_string(&mut buf).await.unwrap();
|
|
1750
|
+
assert_eq!(buf, "Hello, World!asdf");
|
|
1751
|
+
|
|
1752
|
+
// Now lets check the file systems under
|
|
1753
|
+
let f = fs
|
|
1754
|
+
.primary
|
|
1755
|
+
.new_open_options()
|
|
1756
|
+
.create(true)
|
|
1757
|
+
.append(true)
|
|
1758
|
+
.read(true)
|
|
1759
|
+
.open(Path::new("/secondary/file.txt"))
|
|
1760
|
+
.unwrap();
|
|
1761
|
+
assert_eq!(f.size() as usize, 17);
|
|
1762
|
+
let f = fs.secondaries[0]
|
|
1763
|
+
.new_open_options()
|
|
1764
|
+
.create(true)
|
|
1765
|
+
.append(true)
|
|
1766
|
+
.read(true)
|
|
1767
|
+
.open(Path::new("/secondary/file.txt"))
|
|
1768
|
+
.unwrap();
|
|
1769
|
+
assert_eq!(f.size() as usize, 13);
|
|
1770
|
+
|
|
1771
|
+
// it should now exist in both the primary and secondary
|
|
1772
|
+
assert!(ops::is_dir(&fs.primary, "/secondary"));
|
|
1773
|
+
assert!(ops::is_file(&fs.primary, "/secondary/file.txt"));
|
|
1774
|
+
assert!(ops::is_dir(&fs.secondaries[0], "/secondary"));
|
|
1775
|
+
assert!(ops::is_file(&fs.secondaries[0], "/secondary/file.txt"));
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
#[tokio::test]
|
|
1779
|
+
async fn unlink_open_secondary_fs_with_cow_does_not_recreate_path() {
|
|
1780
|
+
let primary = MemFS::default();
|
|
1781
|
+
let secondary = MemFS::default();
|
|
1782
|
+
ops::create_dir_all(&secondary, "/secondary").unwrap();
|
|
1783
|
+
ops::write(&secondary, "/secondary/file.txt", b"Hello, World!")
|
|
1784
|
+
.await
|
|
1785
|
+
.unwrap();
|
|
1786
|
+
|
|
1787
|
+
let fs = OverlayFileSystem::new(primary, [secondary]);
|
|
1788
|
+
|
|
1789
|
+
let mut f = fs
|
|
1790
|
+
.new_open_options()
|
|
1791
|
+
.append(true)
|
|
1792
|
+
.read(true)
|
|
1793
|
+
.open(Path::new("/secondary/file.txt"))
|
|
1794
|
+
.unwrap();
|
|
1795
|
+
|
|
1796
|
+
f.unlink().unwrap();
|
|
1797
|
+
assert_eq!(
|
|
1798
|
+
fs.metadata(Path::new("/secondary/file.txt")).unwrap_err(),
|
|
1799
|
+
FsError::EntryNotFound
|
|
1800
|
+
);
|
|
1801
|
+
assert!(ops::is_file(&fs.primary, "/secondary/.wh.file.txt"));
|
|
1802
|
+
assert!(!ops::is_file(&fs.primary, "/secondary/file.txt"));
|
|
1803
|
+
assert!(ops::is_file(&fs.secondaries[0], "/secondary/file.txt"));
|
|
1804
|
+
|
|
1805
|
+
f.write_all(b"asdf").await.unwrap();
|
|
1806
|
+
f.seek(SeekFrom::Start(0)).await.unwrap();
|
|
1807
|
+
|
|
1808
|
+
let mut buf = String::new();
|
|
1809
|
+
f.read_to_string(&mut buf).await.unwrap();
|
|
1810
|
+
assert_eq!(buf, "Hello, World!asdf");
|
|
1811
|
+
|
|
1812
|
+
assert_eq!(
|
|
1813
|
+
fs.metadata(Path::new("/secondary/file.txt")).unwrap_err(),
|
|
1814
|
+
FsError::EntryNotFound
|
|
1815
|
+
);
|
|
1816
|
+
assert!(ops::is_file(&fs.primary, "/secondary/.wh.file.txt"));
|
|
1817
|
+
assert!(!ops::is_file(&fs.primary, "/secondary/file.txt"));
|
|
1818
|
+
assert!(ops::is_file(&fs.secondaries[0], "/secondary/file.txt"));
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
/// Regression test: unlinking a handle opened from the secondary must not
|
|
1822
|
+
/// destroy a file that was created in the primary at the same path *after*
|
|
1823
|
+
/// the handle was opened.
|
|
1824
|
+
#[tokio::test]
|
|
1825
|
+
async fn unlink_secondary_handle_does_not_delete_later_primary_file() {
|
|
1826
|
+
let primary = MemFS::default();
|
|
1827
|
+
let secondary = MemFS::default();
|
|
1828
|
+
ops::create_dir_all(&secondary, "/dir").unwrap();
|
|
1829
|
+
ops::write(&secondary, "/dir/file.txt", b"secondary")
|
|
1830
|
+
.await
|
|
1831
|
+
.unwrap();
|
|
1832
|
+
|
|
1833
|
+
let fs = OverlayFileSystem::new(primary, [secondary]);
|
|
1834
|
+
|
|
1835
|
+
// Open from secondary (read-only → SecondaryFile).
|
|
1836
|
+
let mut f = fs
|
|
1837
|
+
.new_open_options()
|
|
1838
|
+
.read(true)
|
|
1839
|
+
.open(Path::new("/dir/file.txt"))
|
|
1840
|
+
.unwrap();
|
|
1841
|
+
|
|
1842
|
+
// A new file is created at the same path in primary AFTER the handle
|
|
1843
|
+
// was opened.
|
|
1844
|
+
ops::create_dir_all(&fs.primary, "/dir").unwrap();
|
|
1845
|
+
ops::write(&fs.primary, "/dir/file.txt", b"primary replacement")
|
|
1846
|
+
.await
|
|
1847
|
+
.unwrap();
|
|
1848
|
+
|
|
1849
|
+
// Unlinking the old handle should only whiteout the secondary entry,
|
|
1850
|
+
// never delete the new primary file.
|
|
1851
|
+
f.unlink().unwrap();
|
|
1852
|
+
|
|
1853
|
+
// The whiteout must exist.
|
|
1854
|
+
assert!(ops::is_file(&fs.primary, "/dir/.wh.file.txt"));
|
|
1855
|
+
// The newer primary file must still be intact.
|
|
1856
|
+
assert!(ops::is_file(&fs.primary, "/dir/file.txt"));
|
|
1857
|
+
assert_eq!(
|
|
1858
|
+
ops::read_to_string(&fs.primary, "/dir/file.txt")
|
|
1859
|
+
.await
|
|
1860
|
+
.unwrap(),
|
|
1861
|
+
"primary replacement"
|
|
1862
|
+
);
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
/// Regression test: same correctness requirement for a COW handle that has
|
|
1866
|
+
/// not yet been flushed to primary when unlink() is called.
|
|
1867
|
+
#[tokio::test]
|
|
1868
|
+
async fn unlink_cow_handle_does_not_delete_later_primary_file() {
|
|
1869
|
+
let primary = MemFS::default();
|
|
1870
|
+
let secondary = MemFS::default();
|
|
1871
|
+
ops::create_dir_all(&secondary, "/dir").unwrap();
|
|
1872
|
+
ops::write(&secondary, "/dir/file.txt", b"secondary")
|
|
1873
|
+
.await
|
|
1874
|
+
.unwrap();
|
|
1875
|
+
|
|
1876
|
+
let fs = OverlayFileSystem::new(primary, [secondary]);
|
|
1877
|
+
|
|
1878
|
+
// Open with write access → CopyOnWriteFile (still in ReadOnly state,
|
|
1879
|
+
// no write has happened yet so nothing is in primary).
|
|
1880
|
+
let mut f = fs
|
|
1881
|
+
.new_open_options()
|
|
1882
|
+
.write(true)
|
|
1883
|
+
.read(true)
|
|
1884
|
+
.open(Path::new("/dir/file.txt"))
|
|
1885
|
+
.unwrap();
|
|
1886
|
+
|
|
1887
|
+
// A new file is created at the same path in primary AFTER the handle
|
|
1888
|
+
// was opened.
|
|
1889
|
+
ops::create_dir_all(&fs.primary, "/dir").unwrap();
|
|
1890
|
+
ops::write(&fs.primary, "/dir/file.txt", b"primary replacement")
|
|
1891
|
+
.await
|
|
1892
|
+
.unwrap();
|
|
1893
|
+
|
|
1894
|
+
// Unlink before any write → COW copy has not started, so the primary
|
|
1895
|
+
// file we just created must not be deleted.
|
|
1896
|
+
f.unlink().unwrap();
|
|
1897
|
+
|
|
1898
|
+
assert!(ops::is_file(&fs.primary, "/dir/.wh.file.txt"));
|
|
1899
|
+
assert!(ops::is_file(&fs.primary, "/dir/file.txt"));
|
|
1900
|
+
assert_eq!(
|
|
1901
|
+
ops::read_to_string(&fs.primary, "/dir/file.txt")
|
|
1902
|
+
.await
|
|
1903
|
+
.unwrap(),
|
|
1904
|
+
"primary replacement"
|
|
1905
|
+
);
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
#[tokio::test]
|
|
1909
|
+
async fn unlink_file_from_secondary_fs() {
|
|
1910
|
+
let primary = MemFS::default();
|
|
1911
|
+
let secondary = MemFS::default();
|
|
1912
|
+
ops::create_dir_all(&secondary, "/secondary").unwrap();
|
|
1913
|
+
ops::write(&secondary, "/secondary/file.txt", b"Hello, World!")
|
|
1914
|
+
.await
|
|
1915
|
+
.unwrap();
|
|
1916
|
+
|
|
1917
|
+
let fs = OverlayFileSystem::new(primary, [secondary]);
|
|
1918
|
+
|
|
1919
|
+
fs.remove_file(Path::new("/secondary/file.txt")).unwrap();
|
|
1920
|
+
assert_eq!(ops::exists(&fs, Path::new("/secondary/file.txt")), false);
|
|
1921
|
+
|
|
1922
|
+
assert!(ops::is_file(&fs.primary, "/secondary/.wh.file.txt"));
|
|
1923
|
+
assert!(ops::is_file(&fs.secondaries[0], "/secondary/file.txt"));
|
|
1924
|
+
|
|
1925
|
+
// Now create the file again after the unlink
|
|
1926
|
+
let mut f = fs
|
|
1927
|
+
.new_open_options()
|
|
1928
|
+
.create(true)
|
|
1929
|
+
.write(true)
|
|
1930
|
+
.read(true)
|
|
1931
|
+
.open(Path::new("/secondary/file.txt"))
|
|
1932
|
+
.unwrap();
|
|
1933
|
+
assert_eq!(f.size() as usize, 0);
|
|
1934
|
+
f.write_all(b"asdf").await.unwrap();
|
|
1935
|
+
assert_eq!(f.size() as usize, 4);
|
|
1936
|
+
|
|
1937
|
+
// The whiteout should be gone and new file exist
|
|
1938
|
+
assert!(!ops::is_file(&fs.primary, "/secondary/.wh.file.txt"));
|
|
1939
|
+
assert!(ops::is_file(&fs.primary, "/secondary/file.txt"));
|
|
1940
|
+
assert!(ops::is_file(&fs.secondaries[0], "/secondary/file.txt"));
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
#[tokio::test]
|
|
1944
|
+
async fn rmdir_from_secondary_fs() {
|
|
1945
|
+
let primary = MemFS::default();
|
|
1946
|
+
let secondary = MemFS::default();
|
|
1947
|
+
ops::create_dir_all(&secondary, "/secondary").unwrap();
|
|
1948
|
+
|
|
1949
|
+
let fs = OverlayFileSystem::new(primary, [secondary]);
|
|
1950
|
+
|
|
1951
|
+
assert!(ops::is_dir(&fs, "/secondary"));
|
|
1952
|
+
fs.remove_dir(Path::new("/secondary")).unwrap();
|
|
1953
|
+
|
|
1954
|
+
assert!(!ops::is_dir(&fs, "/secondary"));
|
|
1955
|
+
assert!(ops::is_file(&fs.primary, "/.wh.secondary"));
|
|
1956
|
+
assert!(ops::is_dir(&fs.secondaries[0], "/secondary"));
|
|
1957
|
+
|
|
1958
|
+
fs.create_dir(Path::new("/secondary")).unwrap();
|
|
1959
|
+
assert!(ops::is_dir(&fs, "/secondary"));
|
|
1960
|
+
assert!(ops::is_dir(&fs.primary, "/secondary"));
|
|
1961
|
+
assert!(!ops::is_file(&fs.primary, "/.wh.secondary"));
|
|
1962
|
+
assert!(ops::is_dir(&fs.secondaries[0], "/secondary"));
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
#[tokio::test]
|
|
1966
|
+
async fn rmdir_sub_from_secondary_fs() {
|
|
1967
|
+
let primary = MemFS::default();
|
|
1968
|
+
let secondary = MemFS::default();
|
|
1969
|
+
ops::create_dir_all(&secondary, "/first/secondary").unwrap();
|
|
1970
|
+
|
|
1971
|
+
let fs = OverlayFileSystem::new(primary, [secondary]);
|
|
1972
|
+
|
|
1973
|
+
assert!(ops::is_dir(&fs, "/first/secondary"));
|
|
1974
|
+
fs.remove_dir(Path::new("/first/secondary")).unwrap();
|
|
1975
|
+
|
|
1976
|
+
assert!(!ops::is_dir(&fs, "/first/secondary"));
|
|
1977
|
+
assert!(ops::is_file(&fs.primary, "/first/.wh.secondary"));
|
|
1978
|
+
assert!(ops::is_dir(&fs.secondaries[0], "/first/secondary"));
|
|
1979
|
+
|
|
1980
|
+
fs.create_dir(Path::new("/first/secondary")).unwrap();
|
|
1981
|
+
assert!(ops::is_dir(&fs, "/first/secondary"));
|
|
1982
|
+
assert!(ops::is_dir(&fs.primary, "/first/secondary"));
|
|
1983
|
+
assert!(!ops::is_file(&fs.primary, "/first/.wh.secondary"));
|
|
1984
|
+
assert!(ops::is_dir(&fs.secondaries[0], "/first/secondary"));
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
#[tokio::test]
|
|
1988
|
+
async fn create_new_secondary_fs_without_cow() {
|
|
1989
|
+
let primary = MemFS::default();
|
|
1990
|
+
let secondary = MemFS::default();
|
|
1991
|
+
ops::create_dir_all(&secondary, "/secondary").unwrap();
|
|
1992
|
+
ops::write(&secondary, "/secondary/file.txt", b"Hello, World!")
|
|
1993
|
+
.await
|
|
1994
|
+
.unwrap();
|
|
1995
|
+
|
|
1996
|
+
let fs = OverlayFileSystem::new(primary, [secondary]);
|
|
1997
|
+
|
|
1998
|
+
let mut f = fs
|
|
1999
|
+
.new_open_options()
|
|
2000
|
+
.create_new(true)
|
|
2001
|
+
.read(true)
|
|
2002
|
+
.open(Path::new("/secondary/file.txt"))
|
|
2003
|
+
.unwrap();
|
|
2004
|
+
assert_eq!(f.size() as usize, 0);
|
|
2005
|
+
|
|
2006
|
+
let mut buf = String::new();
|
|
2007
|
+
f.read_to_string(&mut buf).await.unwrap();
|
|
2008
|
+
assert_eq!(buf, "");
|
|
2009
|
+
|
|
2010
|
+
// it should now exist in both the primary and secondary
|
|
2011
|
+
assert!(ops::is_dir(&fs.primary, "/secondary"));
|
|
2012
|
+
assert!(ops::is_file(&fs.primary, "/secondary/file.txt"));
|
|
2013
|
+
assert!(ops::is_dir(&fs.secondaries[0], "/secondary"));
|
|
2014
|
+
assert!(ops::is_file(&fs.secondaries[0], "/secondary/file.txt"));
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
#[tokio::test]
|
|
2018
|
+
async fn open_secondary_fs_files_remove_dir() {
|
|
2019
|
+
let primary = MemFS::default();
|
|
2020
|
+
let secondary = MemFS::default();
|
|
2021
|
+
ops::create_dir_all(&secondary, "/secondary").unwrap();
|
|
2022
|
+
|
|
2023
|
+
let fs = OverlayFileSystem::new(primary, [secondary]);
|
|
2024
|
+
|
|
2025
|
+
fs.metadata(Path::new("/secondary")).unwrap();
|
|
2026
|
+
|
|
2027
|
+
// Now delete the file and make sure its not found
|
|
2028
|
+
fs.remove_dir(Path::new("/secondary")).unwrap();
|
|
2029
|
+
assert_eq!(
|
|
2030
|
+
fs.metadata(Path::new("/secondary")).unwrap_err(),
|
|
2031
|
+
FsError::EntryNotFound
|
|
2032
|
+
)
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
/// Make sure files that are never written are not copied to the primary,
|
|
2036
|
+
/// even when opened with write permissions.
|
|
2037
|
+
/// Regression test for https://github.com/wasmerio/wasmer/issues/5445
|
|
2038
|
+
#[tokio::test]
|
|
2039
|
+
async fn test_overlayfs_readonly_files_not_copied() {
|
|
2040
|
+
let primary = MemFS::default();
|
|
2041
|
+
let secondary = MemFS::default();
|
|
2042
|
+
ops::create_dir_all(&secondary, "/secondary").unwrap();
|
|
2043
|
+
ops::write(&secondary, "/secondary/file.txt", b"Hello, World!")
|
|
2044
|
+
.await
|
|
2045
|
+
.unwrap();
|
|
2046
|
+
|
|
2047
|
+
let fs = OverlayFileSystem::new(primary, [secondary]);
|
|
2048
|
+
|
|
2049
|
+
{
|
|
2050
|
+
let mut f = fs
|
|
2051
|
+
.new_open_options()
|
|
2052
|
+
.read(true)
|
|
2053
|
+
.write(true)
|
|
2054
|
+
.open(Path::new("/secondary/file.txt"))
|
|
2055
|
+
.unwrap();
|
|
2056
|
+
let mut s = String::new();
|
|
2057
|
+
f.read_to_string(&mut s).await.unwrap();
|
|
2058
|
+
assert_eq!(s, "Hello, World!");
|
|
2059
|
+
|
|
2060
|
+
f.flush().await.unwrap();
|
|
2061
|
+
f.shutdown().await.unwrap();
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
// Primary should not have the file
|
|
2065
|
+
assert!(!ops::is_file(&fs.primary, "/secondary/file.txt"));
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
// OLD tests that used WebcFileSystem.
|
|
2069
|
+
// Should be re-implemented with WebcVolumeFs
|
|
2070
|
+
// #[tokio::test]
|
|
2071
|
+
// async fn wasi_runner_use_case() {
|
|
2072
|
+
// // Set up some dummy files on the host
|
|
2073
|
+
// let temp = TempDir::new().unwrap();
|
|
2074
|
+
// let first = temp.path().join("first");
|
|
2075
|
+
// let file_txt = first.join("file.txt");
|
|
2076
|
+
// let second = temp.path().join("second");
|
|
2077
|
+
// std::fs::create_dir_all(&first).unwrap();
|
|
2078
|
+
// std::fs::write(&file_txt, b"First!").unwrap();
|
|
2079
|
+
// std::fs::create_dir_all(&second).unwrap();
|
|
2080
|
+
// // configure the union FS so things are saved in memory by default
|
|
2081
|
+
// // (initialized with a set of unix-like folders), but certain folders
|
|
2082
|
+
// // are first to the host.
|
|
2083
|
+
// let primary = RootFileSystemBuilder::new().build();
|
|
2084
|
+
// let host_fs: Arc<dyn FileSystem + Send + Sync> =
|
|
2085
|
+
// Arc::new(crate::host_fs::FileSystem::default());
|
|
2086
|
+
// let first_dirs = [(&first, "/first"), (&second, "/second")];
|
|
2087
|
+
// for (host, guest) in first_dirs {
|
|
2088
|
+
// primary
|
|
2089
|
+
// .mount(PathBuf::from(guest), &host_fs, host.clone())
|
|
2090
|
+
// .unwrap();
|
|
2091
|
+
// }
|
|
2092
|
+
// // Set up the secondary file systems
|
|
2093
|
+
// let webc = WebCOwned::parse(Bytes::from_static(PYTHON), &ParseOptions::default()).unwrap();
|
|
2094
|
+
// let webc = WebcFileSystem::init_all(Arc::new(webc));
|
|
2095
|
+
//
|
|
2096
|
+
// let fs = OverlayFileSystem::new(primary, [webc]);
|
|
2097
|
+
//
|
|
2098
|
+
// // We should get all the normal directories from rootfs (primary)
|
|
2099
|
+
// assert!(ops::is_dir(&fs, "/lib"));
|
|
2100
|
+
// assert!(ops::is_dir(&fs, "/bin"));
|
|
2101
|
+
// assert!(ops::is_file(&fs, "/dev/stdin"));
|
|
2102
|
+
// assert!(ops::is_file(&fs, "/dev/stdout"));
|
|
2103
|
+
// // We also want to see files from the WEBC volumes (secondary)
|
|
2104
|
+
// assert!(ops::is_dir(&fs, "/lib/python3.6"));
|
|
2105
|
+
// assert!(ops::is_file(&fs, "/lib/python3.6/collections/__init__.py"));
|
|
2106
|
+
// #[cfg(never)]
|
|
2107
|
+
// {
|
|
2108
|
+
// // files on a secondary fs aren't writable
|
|
2109
|
+
// // TODO(Michael-F-Bryan): re-enable this if/when we fix
|
|
2110
|
+
// // open_readonly_file_hack()
|
|
2111
|
+
// assert_eq!(
|
|
2112
|
+
// fs.new_open_options()
|
|
2113
|
+
// .append(true)
|
|
2114
|
+
// .open("/lib/python3.6/collections/__init__.py")
|
|
2115
|
+
// .unwrap_err(),
|
|
2116
|
+
// FsError::PermissionDenied,
|
|
2117
|
+
// );
|
|
2118
|
+
// }
|
|
2119
|
+
// // you are allowed to create files that look like they are in a secondary
|
|
2120
|
+
// // folder, though
|
|
2121
|
+
// ops::touch(&fs, "/lib/python3.6/collections/something-else.py").unwrap();
|
|
2122
|
+
// // But it'll be on the primary filesystem, not the secondary one
|
|
2123
|
+
// assert!(ops::is_file(
|
|
2124
|
+
// &fs.primary,
|
|
2125
|
+
// "/lib/python3.6/collections/something-else.py"
|
|
2126
|
+
// ));
|
|
2127
|
+
// assert!(!ops::is_file(
|
|
2128
|
+
// &fs.secondaries[0],
|
|
2129
|
+
// "/lib/python3.6/collections/something-else.py"
|
|
2130
|
+
// ));
|
|
2131
|
+
// // You can do the same thing with folders
|
|
2132
|
+
// fs.create_dir("/lib/python3.6/something-else".as_ref())
|
|
2133
|
+
// .unwrap();
|
|
2134
|
+
// assert!(ops::is_dir(&fs.primary, "/lib/python3.6/something-else"));
|
|
2135
|
+
// assert!(!ops::is_dir(
|
|
2136
|
+
// &fs.secondaries[0],
|
|
2137
|
+
// "/lib/python3.6/something-else"
|
|
2138
|
+
// ));
|
|
2139
|
+
// // It only works when you are directly inside an existing directory
|
|
2140
|
+
// // on the secondary filesystem, though
|
|
2141
|
+
// assert_eq!(
|
|
2142
|
+
// ops::touch(&fs, "/lib/python3.6/collections/this/doesnt/exist.txt").unwrap_err(),
|
|
2143
|
+
// FsError::EntryNotFound
|
|
2144
|
+
// );
|
|
2145
|
+
// // you should also be able to read files mounted from the host
|
|
2146
|
+
// assert!(ops::is_dir(&fs, "/first"));
|
|
2147
|
+
// assert!(ops::is_file(&fs, "/first/file.txt"));
|
|
2148
|
+
// assert_eq!(
|
|
2149
|
+
// ops::read_to_string(&fs, "/first/file.txt").await.unwrap(),
|
|
2150
|
+
// "First!"
|
|
2151
|
+
// );
|
|
2152
|
+
// // Overwriting them is fine and we'll see the changes on the host
|
|
2153
|
+
// ops::write(&fs, "/first/file.txt", "Updated").await.unwrap();
|
|
2154
|
+
// assert_eq!(std::fs::read_to_string(&file_txt).unwrap(), "Updated");
|
|
2155
|
+
// // The filesystem will see changes on the host that happened after it was
|
|
2156
|
+
// // set up
|
|
2157
|
+
// let another = second.join("another.txt");
|
|
2158
|
+
// std::fs::write(&another, "asdf").unwrap();
|
|
2159
|
+
// assert_eq!(
|
|
2160
|
+
// ops::read_to_string(&fs, "/second/another.txt")
|
|
2161
|
+
// .await
|
|
2162
|
+
// .unwrap(),
|
|
2163
|
+
// "asdf"
|
|
2164
|
+
// );
|
|
2165
|
+
// }
|
|
2166
|
+
//
|
|
2167
|
+
// #[tokio::test]
|
|
2168
|
+
// async fn absolute_and_relative_paths_are_passed_through() {
|
|
2169
|
+
// let python = Arc::new(load_webc(PYTHON));
|
|
2170
|
+
//
|
|
2171
|
+
// // The underlying filesystem doesn't care about absolute/relative paths
|
|
2172
|
+
// assert_eq!(python.read_dir("/lib".as_ref()).unwrap().count(), 4);
|
|
2173
|
+
// assert_eq!(python.read_dir("lib".as_ref()).unwrap().count(), 4);
|
|
2174
|
+
//
|
|
2175
|
+
// // read_dir() should be passed through to the primary
|
|
2176
|
+
// let webc_primary =
|
|
2177
|
+
// OverlayFileSystem::new(Arc::clone(&python), [crate::EmptyFileSystem::default()]);
|
|
2178
|
+
// assert_same_directory_contents(&python, "/lib", &webc_primary);
|
|
2179
|
+
// assert_same_directory_contents(&python, "lib", &webc_primary);
|
|
2180
|
+
//
|
|
2181
|
+
// // read_dir() should also be passed through to the secondary
|
|
2182
|
+
// let webc_secondary =
|
|
2183
|
+
// OverlayFileSystem::new(crate::EmptyFileSystem::default(), [Arc::clone(&python)]);
|
|
2184
|
+
// assert_same_directory_contents(&python, "/lib", &webc_secondary);
|
|
2185
|
+
// assert_same_directory_contents(&python, "lib", &webc_secondary);
|
|
2186
|
+
//
|
|
2187
|
+
// // It should be fine to overlay the root fs on top of our webc file
|
|
2188
|
+
// let overlay_rootfs = OverlayFileSystem::new(
|
|
2189
|
+
// RootFileSystemBuilder::default().build(),
|
|
2190
|
+
// [Arc::clone(&python)],
|
|
2191
|
+
// );
|
|
2192
|
+
// assert_same_directory_contents(&python, "/lib", &overlay_rootfs);
|
|
2193
|
+
// assert_same_directory_contents(&python, "lib", &overlay_rootfs);
|
|
2194
|
+
// }
|
|
2195
|
+
// #[track_caller]
|
|
2196
|
+
// fn assert_same_directory_contents(
|
|
2197
|
+
// original: &dyn FileSystem,
|
|
2198
|
+
// path: impl AsRef<Path>,
|
|
2199
|
+
// candidate: &dyn FileSystem,
|
|
2200
|
+
// ) {
|
|
2201
|
+
// let path = path.as_ref();
|
|
2202
|
+
//
|
|
2203
|
+
// let original_entries: Vec<_> = original
|
|
2204
|
+
// .read_dir(path)
|
|
2205
|
+
// .unwrap()
|
|
2206
|
+
// .map(|r| r.unwrap())
|
|
2207
|
+
// .collect();
|
|
2208
|
+
// let candidate_entries: Vec<_> = candidate
|
|
2209
|
+
// .read_dir(path)
|
|
2210
|
+
// .unwrap()
|
|
2211
|
+
// .map(|r| r.unwrap())
|
|
2212
|
+
// .collect();
|
|
2213
|
+
//
|
|
2214
|
+
// assert_eq!(original_entries, candidate_entries);
|
|
2215
|
+
// }
|
|
2216
|
+
}
|