@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.
Files changed (94) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +6 -0
  3. package/THIRD_PARTY_NOTICES.md +302 -0
  4. package/crates/runtime-core/Cargo.lock +5099 -0
  5. package/crates/runtime-core/Cargo.toml +66 -0
  6. package/crates/runtime-core/README.md +47 -0
  7. package/crates/runtime-core/src/bin/wasm-oj-compiler.rs +418 -0
  8. package/crates/runtime-core/src/bin/wasm-oj-runner.rs +294 -0
  9. package/crates/runtime-core/src/capabilities.rs +118 -0
  10. package/crates/runtime-core/src/compiler.rs +658 -0
  11. package/crates/runtime-core/src/contract.rs +5 -0
  12. package/crates/runtime-core/src/deterministic.rs +1051 -0
  13. package/crates/runtime-core/src/error.rs +58 -0
  14. package/crates/runtime-core/src/filesystem.rs +547 -0
  15. package/crates/runtime-core/src/filesystem_quota.rs +167 -0
  16. package/crates/runtime-core/src/go_compiler_session.rs +297 -0
  17. package/crates/runtime-core/src/interactive.rs +1019 -0
  18. package/crates/runtime-core/src/judge_package.rs +1539 -0
  19. package/crates/runtime-core/src/lib.rs +98 -0
  20. package/crates/runtime-core/src/memory.rs +84 -0
  21. package/crates/runtime-core/src/meter.rs +549 -0
  22. package/crates/runtime-core/src/module_imports.rs +149 -0
  23. package/crates/runtime-core/src/module_policy.rs +714 -0
  24. package/crates/runtime-core/src/output.rs +204 -0
  25. package/crates/runtime-core/src/run/mod.rs +208 -0
  26. package/crates/runtime-core/src/run/native.rs +260 -0
  27. package/crates/runtime-core/src/run/web.rs +229 -0
  28. package/crates/runtime-core/src/run/web_runtime.rs +109 -0
  29. package/crates/runtime-core/src/types.rs +268 -0
  30. package/crates/runtime-core/src/web.rs +83 -0
  31. package/dist/chunks/go-toolchain-Dbt-lp2L.js +426 -0
  32. package/dist/chunks/java-toolchain-DajoRCHu.js +44 -0
  33. package/dist/chunks/python-toolchain-Dx834o2A.js +4 -0
  34. package/dist/chunks/rust-toolchain-CJ3sMxPE.js +252 -0
  35. package/dist/chunks/toolchains-C6KuA1yM.js +224 -0
  36. package/dist/go-stage.mjs +193 -0
  37. package/dist/index.d.ts +221 -0
  38. package/dist/index.js +4393 -0
  39. package/dist/java-stage.mjs +111 -0
  40. package/dist/python-stage.mjs +90 -0
  41. package/dist/rustc-stage.mjs +301 -0
  42. package/dist/server-build-stage.mjs +2564 -0
  43. package/dist/server-runner-stage.mjs +155 -0
  44. package/licenses/fflate-MIT.txt +21 -0
  45. package/licenses/runtime-core-dependencies.html +6253 -0
  46. package/licenses/runtime-core-dependencies.json +3041 -0
  47. package/licenses/wasmer-sdk-MIT.txt +21 -0
  48. package/licenses/wasmer-sdk-dependencies.html +6901 -0
  49. package/licenses/wasmer-sdk-dependencies.json +3013 -0
  50. package/package.json +70 -0
  51. package/rust-toolchain.toml +5 -0
  52. package/testdata/wojjdg02-v2-text.hex +1 -0
  53. package/vendor/shared-buffer/Cargo.toml +22 -0
  54. package/vendor/shared-buffer/LICENSE_APACHE.md +176 -0
  55. package/vendor/shared-buffer/LICENSE_MIT.md +25 -0
  56. package/vendor/shared-buffer/README.md +34 -0
  57. package/vendor/shared-buffer/src/lib.rs +58 -0
  58. package/vendor/shared-buffer/src/mmap.rs +250 -0
  59. package/vendor/shared-buffer/src/owned.rs +389 -0
  60. package/vendor/virtual-fs/Cargo.toml +181 -0
  61. package/vendor/virtual-fs/LICENSE +25 -0
  62. package/vendor/virtual-fs/src/arc_box_file.rs +142 -0
  63. package/vendor/virtual-fs/src/arc_file.rs +182 -0
  64. package/vendor/virtual-fs/src/arc_fs.rs +68 -0
  65. package/vendor/virtual-fs/src/buffer_file.rs +103 -0
  66. package/vendor/virtual-fs/src/builder.rs +232 -0
  67. package/vendor/virtual-fs/src/combine_file.rs +101 -0
  68. package/vendor/virtual-fs/src/cow_file.rs +345 -0
  69. package/vendor/virtual-fs/src/dual_write_file.rs +113 -0
  70. package/vendor/virtual-fs/src/empty_fs.rs +81 -0
  71. package/vendor/virtual-fs/src/filesystems.rs +108 -0
  72. package/vendor/virtual-fs/src/host_fs.rs +1390 -0
  73. package/vendor/virtual-fs/src/lib.rs +782 -0
  74. package/vendor/virtual-fs/src/limiter.rs +252 -0
  75. package/vendor/virtual-fs/src/mem_fs/file.rs +1799 -0
  76. package/vendor/virtual-fs/src/mem_fs/file_opener.rs +941 -0
  77. package/vendor/virtual-fs/src/mem_fs/filesystem.rs +2134 -0
  78. package/vendor/virtual-fs/src/mem_fs/mod.rs +245 -0
  79. package/vendor/virtual-fs/src/mem_fs/offloaded_file.rs +474 -0
  80. package/vendor/virtual-fs/src/mem_fs/stdio.rs +318 -0
  81. package/vendor/virtual-fs/src/mount_fs.rs +2225 -0
  82. package/vendor/virtual-fs/src/null_file.rs +87 -0
  83. package/vendor/virtual-fs/src/ops.rs +364 -0
  84. package/vendor/virtual-fs/src/overlay_fs.rs +2216 -0
  85. package/vendor/virtual-fs/src/passthru_fs.rs +119 -0
  86. package/vendor/virtual-fs/src/pipe.rs +603 -0
  87. package/vendor/virtual-fs/src/random_file.rs +88 -0
  88. package/vendor/virtual-fs/src/special_file.rs +108 -0
  89. package/vendor/virtual-fs/src/static_file.rs +133 -0
  90. package/vendor/virtual-fs/src/static_fs.rs +460 -0
  91. package/vendor/virtual-fs/src/tmp_fs.rs +95 -0
  92. package/vendor/virtual-fs/src/trace_fs.rs +258 -0
  93. package/vendor/virtual-fs/src/webc_volume_fs.rs +829 -0
  94. 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
+ }