kobako 0.28.0 → 0.29.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 (137) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +1 -1
  3. data/CHANGELOG.md +170 -0
  4. data/Cargo.lock +111 -82
  5. data/README.md +154 -36
  6. data/ROADMAP.md +5 -0
  7. data/SECURITY.md +1 -1
  8. data/crates/kobako-runtime/CHANGELOG.md +86 -0
  9. data/crates/kobako-runtime/Cargo.toml +27 -6
  10. data/crates/kobako-runtime/README.md +13 -8
  11. data/crates/kobako-runtime/src/dispatch.rs +8 -0
  12. data/crates/kobako-runtime/src/filesystem/directory/spelling.rs +194 -0
  13. data/crates/kobako-runtime/src/filesystem/directory.rs +1082 -0
  14. data/crates/kobako-runtime/src/filesystem/memory.rs +1100 -0
  15. data/crates/kobako-runtime/src/filesystem/overlay/exclusive.rs +88 -0
  16. data/crates/kobako-runtime/src/filesystem/overlay/stack.rs +808 -0
  17. data/crates/kobako-runtime/src/filesystem/overlay.rs +1955 -0
  18. data/crates/kobako-runtime/src/filesystem/path.rs +60 -0
  19. data/crates/kobako-runtime/src/filesystem/temporary.rs +167 -0
  20. data/crates/kobako-runtime/src/filesystem.rs +392 -0
  21. data/crates/kobako-runtime/src/grant.rs +20 -0
  22. data/crates/kobako-runtime/src/lib.rs +11 -1
  23. data/crates/kobako-runtime/src/mount.rs +178 -0
  24. data/crates/kobako-runtime/src/profile.rs +4 -4
  25. data/crates/kobako-runtime/src/runtime.rs +12 -0
  26. data/crates/kobako-runtime/src/snapshot.rs +20 -5
  27. data/crates/kobako-runtime/src/yielder.rs +11 -6
  28. data/crates/kobako-transport/CHANGELOG.md +17 -0
  29. data/crates/kobako-transport/Cargo.toml +1 -1
  30. data/crates/kobako-transport/README.md +1 -1
  31. data/crates/kobako-transport/src/abi.rs +1 -1
  32. data/crates/kobako-transport/src/envelope/fault.rs +2 -1
  33. data/crates/kobako-transport/src/envelope/preamble.rs +12 -12
  34. data/crates/kobako-transport/src/envelope/reply.rs +32 -14
  35. data/crates/kobako-transport/src/envelope/request.rs +18 -8
  36. data/crates/kobako-wasmtime/CHANGELOG.md +119 -0
  37. data/crates/kobako-wasmtime/Cargo.toml +21 -16
  38. data/crates/kobako-wasmtime/README.md +5 -3
  39. data/crates/kobako-wasmtime/src/abi.rs +66 -32
  40. data/crates/kobako-wasmtime/src/ambient.rs +63 -4
  41. data/crates/kobako-wasmtime/src/cache.rs +185 -74
  42. data/crates/kobako-wasmtime/src/config.rs +58 -1
  43. data/crates/kobako-wasmtime/src/descriptors.rs +167 -0
  44. data/crates/kobako-wasmtime/src/dispatch.rs +47 -29
  45. data/crates/kobako-wasmtime/src/driver.rs +46 -44
  46. data/crates/kobako-wasmtime/src/guest_mem.rs +13 -7
  47. data/crates/kobako-wasmtime/src/imports.rs +46 -0
  48. data/crates/kobako-wasmtime/src/instance_pre.rs +17 -45
  49. data/crates/kobako-wasmtime/src/invocation.rs +35 -46
  50. data/crates/kobako-wasmtime/src/io.rs +5 -202
  51. data/crates/kobako-wasmtime/src/lib.rs +4 -0
  52. data/crates/kobako-wasmtime/src/preview1/args.rs +60 -0
  53. data/crates/kobako-wasmtime/src/preview1/beneath.rs +188 -0
  54. data/crates/kobako-wasmtime/src/preview1/differential/mount.rs +609 -0
  55. data/crates/kobako-wasmtime/src/preview1/differential.rs +372 -0
  56. data/crates/kobako-wasmtime/src/preview1/errno.rs +267 -0
  57. data/crates/kobako-wasmtime/src/preview1/fd.rs +672 -0
  58. data/crates/kobako-wasmtime/src/preview1/memory.rs +205 -0
  59. data/crates/kobako-wasmtime/src/preview1/mount_view.rs +2499 -0
  60. data/crates/kobako-wasmtime/src/preview1/path.rs +421 -0
  61. data/crates/kobako-wasmtime/src/preview1/poll.rs +333 -0
  62. data/crates/kobako-wasmtime/src/preview1/sock.rs +30 -0
  63. data/crates/kobako-wasmtime/src/preview1.rs +461 -0
  64. data/crates/kobako-wasmtime/src/trap.rs +39 -19
  65. data/crates/kobako-wasmtime/src/wasi.rs +317 -0
  66. data/crates/kobako-wasmtime/tests/compile_posture.rs +43 -0
  67. data/crates/kobako-wasmtime/tests/guest_file.rs +1054 -0
  68. data/crates/kobako-wasmtime/tests/preamble_flags.rs +3 -2
  69. data/crates/kobako-wasmtime/tests/snippet_compile.rs +4 -3
  70. data/crates/kobako-wasmtime/tests/support/mod.rs +9 -13
  71. data/crates/kobako-wasmtime/tests/unanswered_call.rs +86 -0
  72. data/crates/kobako-wasmtime/tests/unsupported_call.rs +1 -0
  73. data/data/kobako.wasm +0 -0
  74. data/ext/kobako/Cargo.toml +4 -2
  75. data/ext/kobako/src/runtime/bridge.rs +154 -81
  76. data/ext/kobako/src/runtime/escape.rs +53 -0
  77. data/ext/kobako/src/runtime/filesystem/grants.rs +91 -0
  78. data/ext/kobako/src/runtime/filesystem/ruby.rs +400 -0
  79. data/ext/kobako/src/runtime/filesystem.rs +236 -0
  80. data/ext/kobako/src/runtime/gvl.rs +7 -1
  81. data/ext/kobako/src/runtime/pinned_value.rs +31 -0
  82. data/ext/kobako/src/runtime/snapshot.rs +127 -0
  83. data/ext/kobako/src/runtime.rs +120 -199
  84. data/lib/kobako/catalog/flags.rb +2 -14
  85. data/lib/kobako/catalog/grants.rb +30 -0
  86. data/lib/kobako/catalog/mount.rb +27 -0
  87. data/lib/kobako/catalog/services.rb +11 -0
  88. data/lib/kobako/catalog/snippets.rb +5 -9
  89. data/lib/kobako/catalog.rb +11 -0
  90. data/lib/kobako/codec/decoder.rb +1 -0
  91. data/lib/kobako/codec/ext_types.rb +1 -1
  92. data/lib/kobako/codec/state.rb +19 -3
  93. data/lib/kobako/codec/value_budget.rb +86 -0
  94. data/lib/kobako/codec.rb +7 -0
  95. data/lib/kobako/context.rb +38 -11
  96. data/lib/kobako/errors.rb +15 -14
  97. data/lib/kobako/filesystem.rb +38 -0
  98. data/lib/kobako/pool.rb +2 -2
  99. data/lib/kobako/sandbox.rb +40 -12
  100. data/lib/kobako/sandbox_options.rb +39 -12
  101. data/lib/kobako/snippet/{binary.rb → bytecode.rb} +1 -1
  102. data/lib/kobako/snippet.rb +1 -1
  103. data/lib/kobako/transport/block_failure.rb +35 -0
  104. data/lib/kobako/transport/dispatcher.rb +30 -26
  105. data/lib/kobako/transport/exposure.rb +5 -7
  106. data/lib/kobako/transport/reflection.rb +79 -23
  107. data/lib/kobako/transport/yielder.rb +8 -30
  108. data/lib/kobako/transport.rb +1 -0
  109. data/lib/kobako/version.rb +1 -1
  110. data/lib/kobako.rb +1 -0
  111. data/sig/kobako/catalog/flags.rbs +0 -4
  112. data/sig/kobako/catalog/grants.rbs +13 -0
  113. data/sig/kobako/catalog/mount.rbs +28 -0
  114. data/sig/kobako/catalog/services.rbs +4 -0
  115. data/sig/kobako/catalog/snippets.rbs +1 -1
  116. data/sig/kobako/catalog.rbs +1 -0
  117. data/sig/kobako/codec/state.rbs +7 -1
  118. data/sig/kobako/codec/value_budget.rbs +23 -0
  119. data/sig/kobako/codec.rbs +2 -0
  120. data/sig/kobako/context.rbs +14 -6
  121. data/sig/kobako/errors.rbs +4 -4
  122. data/sig/kobako/filesystem.rbs +88 -0
  123. data/sig/kobako/guest_yielder.rbs +9 -0
  124. data/sig/kobako/pool.rbs +1 -1
  125. data/sig/kobako/runtime.rbs +21 -49
  126. data/sig/kobako/sandbox.rbs +7 -1
  127. data/sig/kobako/sandbox_options.rbs +24 -2
  128. data/sig/kobako/snapshot.rbs +20 -0
  129. data/sig/kobako/snippet/bytecode.rbs +12 -0
  130. data/sig/kobako/transport/block_failure.rbs +13 -0
  131. data/sig/kobako/transport/dispatcher.rbs +3 -3
  132. data/sig/kobako/transport/exposure.rbs +1 -1
  133. data/sig/kobako/transport/reflection.rbs +14 -1
  134. data/sig/kobako/transport/yielder.rbs +3 -7
  135. data/sig/kobako/transport.rbs +2 -2
  136. metadata +60 -7
  137. data/sig/kobako/snippet/binary.rbs +0 -12
@@ -0,0 +1,1955 @@
1
+ //! `Overlay`: Layers stacked into one Filesystem, each read-only or
2
+ //! writable on its own.
3
+ //!
4
+ //! The rules follow the union filesystems that allow writable Layers
5
+ //! below the top, aufs among them. A removal hides what a read-only Layer
6
+ //! still shows with a whiteout, and a directory made again where one was
7
+ //! removed is marked opaque. Both markers take the OCI image layer
8
+ //! format's names and live in the topmost writable Layer, so they last as
9
+ //! long as it does. Every call holds the Overlay's lock, so a change
10
+ //! across several Layers is never seen half made.
11
+
12
+ mod exclusive;
13
+ mod stack;
14
+
15
+ use std::sync::Arc;
16
+
17
+ pub use exclusive::Blocking;
18
+
19
+ use super::{
20
+ Access, DirEntry, File, Filesystem, FilesystemError, Hold, Metadata, OpenOptions, Rename,
21
+ };
22
+ use exclusive::{Exclusive, Inline};
23
+ use stack::Stack;
24
+
25
+ /// Layers stacked into one Filesystem, the first one added on top. Its
26
+ /// clones share its lock; two Overlays sharing a writable Layer are not
27
+ /// serialized against each other, as overlayfs leaves two mounts sharing
28
+ /// an upper directory undefined.
29
+ #[derive(Clone)]
30
+ pub struct Overlay {
31
+ stack: Stack,
32
+ lock: Arc<Exclusive>,
33
+ }
34
+
35
+ impl Default for Overlay {
36
+ fn default() -> Self {
37
+ Self::new()
38
+ }
39
+ }
40
+
41
+ impl Overlay {
42
+ /// An Overlay with no Layer yet.
43
+ pub fn new() -> Self {
44
+ Self {
45
+ stack: Stack::default(),
46
+ lock: Arc::new(Exclusive::new(Arc::new(Inline))),
47
+ }
48
+ }
49
+
50
+ /// This Overlay with `filesystem` as a new Layer beneath the ones
51
+ /// already added, allowing `access` there. It takes a lock of its own,
52
+ /// so an Overlay holding the one it was built from as a Layer does not
53
+ /// wait on itself.
54
+ pub fn layer(mut self, filesystem: Arc<dyn Filesystem>, access: Access) -> Self {
55
+ self.stack.push(filesystem, access);
56
+ self.lock = Arc::new(Exclusive::new(self.lock.blocking()));
57
+ self
58
+ }
59
+
60
+ /// This Overlay waiting for its lock through `blocking`.
61
+ pub fn with_blocking(self, blocking: Arc<dyn Blocking>) -> Self {
62
+ Self {
63
+ lock: Arc::new(Exclusive::new(blocking)),
64
+ ..self
65
+ }
66
+ }
67
+ }
68
+
69
+ impl Filesystem for Overlay {
70
+ fn open(&self, path: &str, options: &OpenOptions) -> Result<Box<dyn File>, FilesystemError> {
71
+ let _held = self.lock.hold()?;
72
+ self.stack.open(path, options)
73
+ }
74
+
75
+ fn metadata(&self, path: &str) -> Result<Metadata, FilesystemError> {
76
+ let _held = self.lock.hold()?;
77
+ self.stack.metadata(path)
78
+ }
79
+
80
+ fn symlink_metadata(&self, path: &str) -> Result<Metadata, FilesystemError> {
81
+ let _held = self.lock.hold()?;
82
+ self.stack.symlink_metadata(path)
83
+ }
84
+
85
+ fn stored_path(&self, path: &str) -> Result<String, FilesystemError> {
86
+ let _held = self.lock.hold()?;
87
+ self.stack.stored_path(path)
88
+ }
89
+
90
+ fn canonicalize_beneath(&self, base: &str, path: &str) -> Result<String, FilesystemError> {
91
+ let _held = self.lock.hold()?;
92
+ self.stack.canonicalize_beneath(base, path)
93
+ }
94
+
95
+ fn hold_directory(&self, path: &str) -> Result<Option<Hold>, FilesystemError> {
96
+ let _held = self.lock.hold()?;
97
+ self.stack.hold_directory(path)
98
+ }
99
+
100
+ fn read_dir(&self, path: &str) -> Result<Vec<DirEntry>, FilesystemError> {
101
+ let _held = self.lock.hold()?;
102
+ self.stack.read_dir(path)
103
+ }
104
+
105
+ fn create_dir(&self, path: &str) -> Result<(), FilesystemError> {
106
+ let _held = self.lock.hold()?;
107
+ self.stack.create_dir(path)
108
+ }
109
+
110
+ fn remove_dir(&self, path: &str) -> Result<(), FilesystemError> {
111
+ let _held = self.lock.hold()?;
112
+ self.stack.remove_dir(path)
113
+ }
114
+
115
+ fn remove_file(&self, path: &str) -> Result<(), FilesystemError> {
116
+ let _held = self.lock.hold()?;
117
+ self.stack.remove_file(path)
118
+ }
119
+
120
+ fn rename(&self, from: &str, to: &str, rename: Rename) -> Result<(), FilesystemError> {
121
+ let _held = self.lock.hold()?;
122
+ self.stack.rename(from, to, rename)
123
+ }
124
+
125
+ /// A fresh Overlay when any Layer starts each Invocation fresh, over
126
+ /// each Layer's own next Filesystem. It keeps this Overlay's lock,
127
+ /// since the Layers it shares are the same.
128
+ fn for_invocation(&self) -> Option<Arc<dyn Filesystem>> {
129
+ let stack = self.stack.for_invocation()?;
130
+ Some(Arc::new(Self {
131
+ stack,
132
+ lock: self.lock.clone(),
133
+ }))
134
+ }
135
+
136
+ /// An Overlay over each Layer's own Filesystem for a Policy, keeping
137
+ /// this Overlay's lock, since the Layers it shares are the same.
138
+ fn for_policy(&self) -> Option<Arc<dyn Filesystem>> {
139
+ let stack = self.stack.for_policy()?;
140
+ Some(Arc::new(Self {
141
+ stack,
142
+ lock: self.lock.clone(),
143
+ }))
144
+ }
145
+ }
146
+
147
+ #[cfg(test)]
148
+ mod tests {
149
+ use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
150
+ use std::sync::Mutex;
151
+ use std::thread;
152
+ use std::time::{Duration, Instant};
153
+
154
+ use super::super::memory::Memory;
155
+ use super::super::temporary::Temporary;
156
+ use super::super::{FileType, Hold};
157
+ use super::*;
158
+
159
+ /// The answer of a test Filesystem holding no links.
160
+ macro_rules! holds_no_links {
161
+ () => {
162
+ fn symlink_metadata(&self, path: &str) -> Result<Metadata, FilesystemError> {
163
+ self.metadata(path)
164
+ }
165
+
166
+ fn canonicalize_beneath(
167
+ &self,
168
+ _base: &str,
169
+ path: &str,
170
+ ) -> Result<String, FilesystemError> {
171
+ Ok(path.to_string())
172
+ }
173
+
174
+ fn for_policy(&self) -> Option<Arc<dyn Filesystem>> {
175
+ None
176
+ }
177
+ };
178
+ }
179
+
180
+ /// The answer of a test Filesystem matching names byte for byte.
181
+ macro_rules! matches_names_exactly {
182
+ () => {
183
+ fn stored_path(&self, path: &str) -> Result<String, FilesystemError> {
184
+ Ok(path.to_string())
185
+ }
186
+ };
187
+ }
188
+
189
+ /// The answer of a test Filesystem whose identities never repeat.
190
+ macro_rules! never_repeats_identities {
191
+ () => {
192
+ fn hold_directory(&self, _path: &str) -> Result<Option<Hold>, FilesystemError> {
193
+ Ok(None)
194
+ }
195
+ };
196
+ }
197
+
198
+ /// The answer of a test Filesystem every Invocation shares.
199
+ macro_rules! is_shared {
200
+ () => {
201
+ fn for_invocation(&self) -> Option<Arc<dyn Filesystem>> {
202
+ None
203
+ }
204
+ };
205
+ }
206
+
207
+ fn read(fs: &dyn Filesystem, path: &str) -> Vec<u8> {
208
+ let mut file = fs
209
+ .open(
210
+ path,
211
+ &OpenOptions {
212
+ read: true,
213
+ ..OpenOptions::default()
214
+ },
215
+ )
216
+ .expect("the file must open");
217
+ let mut buf = vec![0; 64];
218
+ let read = file.read_at(&mut buf, 0).expect("the file must read");
219
+ buf.truncate(read);
220
+ buf
221
+ }
222
+
223
+ fn write(fs: &dyn Filesystem, path: &str, contents: &[u8]) -> Result<(), FilesystemError> {
224
+ fs.open(
225
+ path,
226
+ &OpenOptions {
227
+ write: true,
228
+ create: true,
229
+ truncate: true,
230
+ ..OpenOptions::default()
231
+ },
232
+ )?
233
+ .write_at(contents, 0)
234
+ .map(drop)
235
+ }
236
+
237
+ fn overwrite(fs: &dyn Filesystem, path: &str, contents: &[u8]) {
238
+ write(fs, path, contents).expect("the write must land");
239
+ }
240
+
241
+ fn names(fs: &dyn Filesystem, path: &str) -> Vec<String> {
242
+ let mut names: Vec<String> = fs
243
+ .read_dir(path)
244
+ .expect("the directory must list")
245
+ .into_iter()
246
+ .map(|entry| entry.name)
247
+ .collect();
248
+ names.sort();
249
+ names
250
+ }
251
+
252
+ fn memory_with(path: &str, contents: &str) -> Arc<Memory> {
253
+ Arc::new([(path, contents)].into_iter().collect())
254
+ }
255
+
256
+ /// A writable `upper` over a read-only Memory holding `dir/file` =
257
+ /// "lower".
258
+ fn overlay_on(upper: Arc<dyn Filesystem>) -> (Overlay, Arc<Memory>) {
259
+ let lower = memory_with("dir/file", "lower");
260
+ let overlay = Overlay::new()
261
+ .layer(upper, Access::ReadWrite)
262
+ .layer(lower.clone(), Access::Read);
263
+ (overlay, lower)
264
+ }
265
+
266
+ /// A host directory holding `target`, `dir/inner`, and the links `link`
267
+ /// to `target`, `dirlink` to `dir`, and `dangling` to nothing, removed
268
+ /// when it drops.
269
+ #[cfg(unix)]
270
+ struct LinkTree(std::path::PathBuf);
271
+
272
+ #[cfg(unix)]
273
+ impl LinkTree {
274
+ fn new(name: &str) -> Self {
275
+ let path =
276
+ std::env::temp_dir().join(format!("kobako-overlay-{name}-{}", std::process::id()));
277
+ let _ = std::fs::remove_dir_all(&path);
278
+ std::fs::create_dir_all(path.join("dir")).expect("the host directory must be created");
279
+ std::fs::write(path.join("target"), "lower").expect("the host file must be written");
280
+ std::fs::write(path.join("dir/inner"), "lower").expect("the host file must be written");
281
+ for (target, link) in [
282
+ ("target", "link"),
283
+ ("dir", "dirlink"),
284
+ ("missing", "dangling"),
285
+ ] {
286
+ std::os::unix::fs::symlink(target, path.join(link)).expect("the link must be made");
287
+ }
288
+ Self(path)
289
+ }
290
+
291
+ /// A writable Memory over the host directory as a read-only Layer.
292
+ fn overlay(&self) -> Overlay {
293
+ let lower =
294
+ super::super::Directory::new(&self.0).expect("the host directory must open");
295
+ Overlay::new()
296
+ .layer(Arc::new(Memory::new()), Access::ReadWrite)
297
+ .layer(Arc::new(lower), Access::Read)
298
+ }
299
+ }
300
+
301
+ #[cfg(unix)]
302
+ impl Drop for LinkTree {
303
+ fn drop(&mut self) {
304
+ let _ = std::fs::remove_dir_all(&self.0);
305
+ }
306
+ }
307
+
308
+ // The link points at a file in the same Layer, so only a lookup that
309
+ // reaches the Layer's own `symlink_metadata` can answer a link.
310
+ // The links stand in the Directory Layer beneath a Memory one, so only
311
+ // the merged view handing them to the Layer holding them resolves them.
312
+ // @behavior FS-098
313
+ #[cfg(unix)]
314
+ #[test]
315
+ fn an_overlay_follows_a_link_beneath_a_directory_within_the_layer_holding_it() {
316
+ let host = LinkTree::new("beneath");
317
+ std::fs::create_dir_all(host.0.join("dir/sub"))
318
+ .expect("the host directory must be created");
319
+ std::fs::write(host.0.join("dir/sub/y"), "inner").expect("the inner file must be written");
320
+ std::os::unix::fs::symlink("sub", host.0.join("dir/in")).expect("the link must be made");
321
+ std::os::unix::fs::symlink("../target", host.0.join("dir/up"))
322
+ .expect("the link must be made");
323
+ let overlay = host.overlay();
324
+ assert_eq!(
325
+ (
326
+ overlay.canonicalize_beneath("dir", "in/y"),
327
+ overlay.canonicalize_beneath("dir", "up"),
328
+ ),
329
+ (Ok("sub/y".to_string()), Err(FilesystemError::NotPermitted)),
330
+ "a link a Directory Layer holds within `dir`, resolved through \
331
+ Overlay::canonicalize_beneath, must resolve to where it leads, and one leading out \
332
+ of `dir` must not be permitted"
333
+ );
334
+ }
335
+
336
+ // The link stands in the Directory Layer beneath a Memory one, so only
337
+ // the Overlay handing each Layer's own Filesystem for a Policy refuses it.
338
+ // @behavior FS-100
339
+ #[cfg(unix)]
340
+ #[test]
341
+ fn an_overlay_for_a_policy_follows_no_link_in_any_layer() {
342
+ let host = LinkTree::new("for-policy");
343
+ let overlay = host.overlay();
344
+ let reading = OpenOptions {
345
+ read: true,
346
+ ..OpenOptions::default()
347
+ };
348
+ let for_policy = overlay
349
+ .for_policy()
350
+ .expect("an Overlay over a Directory has a Filesystem for a Policy");
351
+ assert_eq!(
352
+ (
353
+ overlay.open("dirlink/inner", &reading).map(drop),
354
+ for_policy.open("dirlink/inner", &reading).map(drop),
355
+ ),
356
+ (Ok(()), Err(FilesystemError::FilesystemLoop)),
357
+ "a path through a link a Directory Layer holds, opened through an Overlay's \
358
+ for_policy, must be a link loop, where the Overlay itself reaches it"
359
+ );
360
+ }
361
+
362
+ // @behavior FS-049
363
+ #[cfg(unix)]
364
+ #[test]
365
+ fn an_overlay_reports_a_link_its_layer_holds_as_a_link() {
366
+ let host = LinkTree::new("lstat");
367
+ let overlay = host.overlay();
368
+ let file_type = |path| {
369
+ overlay
370
+ .symlink_metadata(path)
371
+ .map(|metadata| metadata.file_type)
372
+ };
373
+ assert_eq!(
374
+ (file_type("link"), file_type("dangling")),
375
+ (Ok(FileType::Symlink), Ok(FileType::Symlink)),
376
+ "a link a Directory Layer holds, pointing at a file or at nothing, looked up through \
377
+ Overlay::symlink_metadata must be reported as a link"
378
+ );
379
+ }
380
+
381
+ // A link to a directory follows to one, and a dangling link follows to
382
+ // nothing, so only judging each link itself lets both go.
383
+ // @behavior FS-050
384
+ #[cfg(unix)]
385
+ #[test]
386
+ fn an_overlay_removes_a_link_its_read_only_layer_holds_as_itself() {
387
+ let host = LinkTree::new("unlink");
388
+ let overlay = host.overlay();
389
+ let removed = (
390
+ overlay.remove_dir("dirlink"),
391
+ overlay.remove_file("dirlink"),
392
+ overlay.remove_file("dangling"),
393
+ );
394
+ let gone = |path| overlay.symlink_metadata(path).err();
395
+ assert_eq!(
396
+ (
397
+ removed,
398
+ gone("dirlink"),
399
+ gone("dangling"),
400
+ host.0.join("dirlink").is_symlink()
401
+ ),
402
+ (
403
+ (Err(FilesystemError::NotADirectory), Ok(()), Ok(())),
404
+ Some(FilesystemError::NotFound),
405
+ Some(FilesystemError::NotFound),
406
+ true
407
+ ),
408
+ "a link a read-only Directory Layer holds, removed through Overlay::remove_dir and \
409
+ Overlay::remove_file, must be refused as no directory and then removed as itself, \
410
+ leaving the Layer as it was"
411
+ );
412
+ }
413
+
414
+ // A write would copy the FIFO up, which opens it, and a rename would
415
+ // move what no Layer above can hold, so only refusals keep either
416
+ // from waiting on a peer or landing nothing.
417
+ // @behavior FS-092
418
+ #[cfg(unix)]
419
+ #[test]
420
+ fn an_overlay_neither_copies_up_nor_moves_a_fifo_below_it() {
421
+ let host = LinkTree::new("fifo");
422
+ let made = std::process::Command::new("mkfifo")
423
+ .arg(host.0.join("pipe"))
424
+ .status()
425
+ .expect("mkfifo must run");
426
+ assert!(made.success(), "the FIFO must be made");
427
+ let overlay = host.overlay();
428
+ assert_eq!(
429
+ (
430
+ write(&overlay, "pipe", b"upper"),
431
+ overlay.rename("pipe", "moved", Rename::File)
432
+ ),
433
+ (
434
+ Err(FilesystemError::PermissionDenied),
435
+ Err(FilesystemError::CrossesDevices)
436
+ ),
437
+ "a FIFO a read-only Directory Layer holds, written or renamed through an Overlay, must \
438
+ be refused as permission denied and as crossing devices"
439
+ );
440
+ }
441
+
442
+ // A FIFO is no directory, so each rename meets a kind it cannot
443
+ // replace; were it taken for neither, the directory made above would
444
+ // hide the FIFO, and the FIFO's rename would answer for its Layer.
445
+ // @behavior FS-095
446
+ #[cfg(unix)]
447
+ #[test]
448
+ fn an_overlay_replaces_neither_a_fifo_nor_a_directory_with_the_other() {
449
+ let host = LinkTree::new("fifo-kinds");
450
+ let made = std::process::Command::new("mkfifo")
451
+ .arg(host.0.join("pipe"))
452
+ .status()
453
+ .expect("mkfifo must run");
454
+ assert!(made.success(), "the FIFO must be made");
455
+ let overlay = host.overlay();
456
+ overlay
457
+ .create_dir("made")
458
+ .expect("a directory must be made in the writable Layer");
459
+ let file_type = |path| overlay.metadata(path).map(|metadata| metadata.file_type);
460
+ assert_eq!(
461
+ (
462
+ overlay.rename("pipe", "dir", Rename::Any),
463
+ overlay.rename("made", "pipe", Rename::Any),
464
+ file_type("pipe"),
465
+ file_type("made"),
466
+ ),
467
+ (
468
+ Err(FilesystemError::IsADirectory),
469
+ Err(FilesystemError::NotADirectory),
470
+ Ok(FileType::Unknown),
471
+ Ok(FileType::Directory)
472
+ ),
473
+ "a FIFO renamed onto a directory through an Overlay must be refused as a directory, \
474
+ and a directory renamed onto a FIFO as no directory, leaving both in place"
475
+ );
476
+ }
477
+
478
+ // Each change would land in the writable Memory, which holds no link,
479
+ // so only refusing it keeps the write from parting with its target.
480
+ // @behavior FS-051
481
+ #[cfg(unix)]
482
+ #[test]
483
+ fn an_overlay_refuses_a_change_through_a_link_its_read_only_layer_holds() {
484
+ let host = LinkTree::new("through");
485
+ let overlay = host.overlay();
486
+ let creates = OpenOptions {
487
+ write: true,
488
+ create: true,
489
+ ..OpenOptions::default()
490
+ };
491
+ let refused = [
492
+ write(&overlay, "link", b"upper"),
493
+ overlay.open("dirlink/new", &creates).map(drop),
494
+ overlay.open("dangling", &creates).map(drop),
495
+ overlay.rename("link", "moved", Rename::Any),
496
+ ];
497
+ assert_eq!(
498
+ (refused, read(&overlay, "target")),
499
+ (
500
+ [
501
+ Err(FilesystemError::CrossesDevices),
502
+ Err(FilesystemError::CrossesDevices),
503
+ Err(FilesystemError::CrossesDevices),
504
+ Err(FilesystemError::CrossesDevices)
505
+ ],
506
+ b"lower".to_vec()
507
+ ),
508
+ "a write, a create, and a rename reaching through a link a read-only Directory Layer \
509
+ holds, through an Overlay, must be refused as crossing devices and leave the target \
510
+ as it was"
511
+ );
512
+ }
513
+
514
+ // The lower Memory holds a readable `dangling`, so only the upper
515
+ // link standing at that path can keep the read from reaching it.
516
+ // @behavior FS-053
517
+ #[cfg(unix)]
518
+ #[test]
519
+ fn a_link_a_layer_holds_hides_what_the_layers_below_hold_there() {
520
+ let host = LinkTree::new("shadow");
521
+ let upper = super::super::Directory::new(&host.0).expect("the host directory must open");
522
+ let overlay = Overlay::new()
523
+ .layer(Arc::new(upper), Access::ReadWrite)
524
+ .layer(memory_with("dangling", "lower"), Access::Read);
525
+ assert_eq!(
526
+ overlay
527
+ .open(
528
+ "dangling",
529
+ &OpenOptions {
530
+ read: true,
531
+ ..OpenOptions::default()
532
+ }
533
+ )
534
+ .map(drop),
535
+ Err(FilesystemError::NotFound),
536
+ "a lower file beneath a dangling link a higher Directory Layer holds at its path, \
537
+ opened through Overlay::open, must not be found"
538
+ );
539
+ }
540
+
541
+ // The lower Memory holds `dirlink/x` beside where the upper link points,
542
+ // so only the link hiding everything beneath it keeps `x` out.
543
+ // @behavior FS-057
544
+ #[cfg(unix)]
545
+ #[test]
546
+ fn a_link_a_layer_holds_hides_what_the_layers_below_hold_beneath_it() {
547
+ let host = LinkTree::new("shadow-beneath");
548
+ let upper = super::super::Directory::new(&host.0).expect("the host directory must open");
549
+ let overlay = Overlay::new()
550
+ .layer(Arc::new(upper), Access::ReadWrite)
551
+ .layer(memory_with("dirlink/x", "lower"), Access::Read);
552
+ let found = overlay
553
+ .metadata("dirlink/x")
554
+ .map(|metadata| metadata.file_type);
555
+ assert_eq!(
556
+ (names(&overlay, "dirlink"), found),
557
+ (vec!["inner".to_string()], Err(FilesystemError::NotFound)),
558
+ "a directory a higher Directory Layer holds as a link, listed and looked into through \
559
+ an Overlay, must show only where the link points and none of a lower Layer's entries"
560
+ );
561
+ }
562
+
563
+ // The lower directory sits in a writable Layer, so only the whiteout,
564
+ // not a removal that cannot take a directory, keeps the view whole.
565
+ // @behavior FS-058
566
+ #[test]
567
+ fn removing_a_file_over_a_lower_directory_leaves_it_hidden() {
568
+ let lower = memory_with("f/g", "lower");
569
+ let overlay = Overlay::new()
570
+ .layer(memory_with("f", "upper"), Access::ReadWrite)
571
+ .layer(lower.clone(), Access::ReadWrite);
572
+ let removed = overlay.remove_file("f");
573
+ assert_eq!(
574
+ (
575
+ removed,
576
+ overlay.symlink_metadata("f").err(),
577
+ lower.metadata("f/g").is_ok()
578
+ ),
579
+ (Ok(()), Some(FilesystemError::NotFound), true),
580
+ "a file a higher Layer holds over a lower Layer's directory, removed through \
581
+ Overlay::remove_file, must leave the path gone from the view and the lower \
582
+ directory as it was"
583
+ );
584
+ }
585
+
586
+ // The writable Layer's `dir` holds only the whiteout hiding `file`, so
587
+ // only judging the view, not that copy, lets the move go.
588
+ // @behavior FS-059
589
+ #[test]
590
+ fn a_directory_moves_onto_one_holding_only_markers() {
591
+ let (overlay, _) = overlay_on(Arc::new(Memory::new()));
592
+ overlay
593
+ .remove_file("dir/file")
594
+ .expect("the file must be removed");
595
+ overlay
596
+ .create_dir("new")
597
+ .expect("the directory must be made");
598
+ overwrite(&overlay, "new/made", b"made");
599
+ let moved = overlay.rename("new", "dir", Rename::Directory);
600
+ assert_eq!(
601
+ (moved, names(&overlay, "dir")),
602
+ (Ok(()), vec!["made".to_string()]),
603
+ "a directory renamed through Overlay::rename onto one the view shows empty, whose \
604
+ writable copy holds only a whiteout, must take its place and show only its own entries"
605
+ );
606
+ }
607
+
608
+ // The directory made again is marked opaque in the writable Layer, so
609
+ // only that mark tells it apart from one the read-only Layer still holds.
610
+ // @behavior FS-060
611
+ #[test]
612
+ fn a_directory_made_again_moves_as_the_writable_layers_own() {
613
+ let (overlay, _) = overlay_on(Arc::new(Memory::new()));
614
+ overlay
615
+ .remove_file("dir/file")
616
+ .expect("the file must be removed");
617
+ overlay
618
+ .remove_dir("dir")
619
+ .expect("the directory must be removed");
620
+ overlay
621
+ .create_dir("dir")
622
+ .expect("the directory must be made again");
623
+ let moved = overlay.rename("dir", "moved", Rename::Directory);
624
+ assert_eq!(
625
+ (
626
+ moved,
627
+ overlay.metadata("dir").err(),
628
+ overlay.read_dir("moved").map(|entries| entries.len())
629
+ ),
630
+ (Ok(()), Some(FilesystemError::NotFound), Ok(0)),
631
+ "a directory made again through an Overlay where a read-only Layer's one was removed, \
632
+ renamed through Overlay::rename, must move and leave nothing of the lower one behind"
633
+ );
634
+ }
635
+
636
+ // @behavior FS-061
637
+ #[test]
638
+ fn an_overlay_refuses_a_file_moved_beneath_itself_as_no_directory() {
639
+ let (overlay, _) = overlay_on(Arc::new(Memory::new()));
640
+ assert_eq!(
641
+ overlay.rename("dir/file", "dir/file/x", Rename::Any),
642
+ Err(FilesystemError::NotADirectory),
643
+ "a file renamed through Overlay::rename to a path beneath itself must be refused as \
644
+ no directory, as rename(2) refuses it"
645
+ );
646
+ }
647
+
648
+ // The link points at nothing, so only the link itself can be what an
649
+ // exclusive create or a new directory finds there.
650
+ // @behavior FS-052
651
+ #[cfg(unix)]
652
+ #[test]
653
+ fn an_overlay_finds_a_dangling_link_where_a_new_entry_would_go() {
654
+ let host = LinkTree::new("dangling");
655
+ let overlay = host.overlay();
656
+ let exclusive = OpenOptions {
657
+ write: true,
658
+ create_new: true,
659
+ ..OpenOptions::default()
660
+ };
661
+ assert_eq!(
662
+ (
663
+ overlay.open("dangling", &exclusive).map(drop),
664
+ overlay.create_dir("dangling")
665
+ ),
666
+ (
667
+ Err(FilesystemError::AlreadyExists),
668
+ Err(FilesystemError::AlreadyExists)
669
+ ),
670
+ "an exclusive create and a create_dir at a dangling link a read-only Directory Layer \
671
+ holds, through an Overlay, must find it already there"
672
+ );
673
+ }
674
+
675
+ // @behavior FS-008
676
+ #[test]
677
+ fn an_overlay_reads_through_to_a_lower_layer() {
678
+ let (overlay, _) = overlay_on(Arc::new(Memory::new()));
679
+ assert_eq!(
680
+ read(&overlay, "dir/file"),
681
+ b"lower",
682
+ "a lower file read through Overlay::open must read back the lower content"
683
+ );
684
+ }
685
+
686
+ // @behavior FS-009
687
+ #[test]
688
+ fn a_write_to_a_read_only_layers_file_lands_in_the_nearest_writable_layer_above_it() {
689
+ let upper = Arc::new(Memory::new());
690
+ let (overlay, _) = overlay_on(upper.clone());
691
+ overwrite(&overlay, "dir/file", b"upper");
692
+ assert_eq!(
693
+ read(upper.as_ref(), "dir/file"),
694
+ b"upper",
695
+ "a read-only Layer's file written through Overlay::open must land in the writable Layer above it"
696
+ );
697
+ }
698
+
699
+ // @behavior FS-010
700
+ #[test]
701
+ fn an_overlay_leaves_a_read_only_layer_as_it_was() {
702
+ let (overlay, lower) = overlay_on(Arc::new(Memory::new()));
703
+ overwrite(&overlay, "dir/file", b"upper");
704
+ overlay
705
+ .remove_file("dir/file")
706
+ .expect("the file must be removed");
707
+ assert_eq!(
708
+ read(lower.as_ref(), "dir/file"),
709
+ b"lower",
710
+ "a read-only Layer's file written and removed through an Overlay must stay as it was"
711
+ );
712
+ }
713
+
714
+ // @behavior FS-011
715
+ #[test]
716
+ fn a_file_removed_through_an_overlay_is_gone_from_its_view() {
717
+ let (overlay, _) = overlay_on(Arc::new(Memory::new()));
718
+ overlay
719
+ .remove_file("dir/file")
720
+ .expect("the file must be removed");
721
+ assert_eq!(
722
+ overlay.metadata("dir/file"),
723
+ Err(FilesystemError::NotFound),
724
+ "a lower file removed through Overlay::remove_file must no longer be found there"
725
+ );
726
+ }
727
+
728
+ // @behavior FS-012
729
+ #[test]
730
+ fn an_overlay_over_a_temporary_starts_every_invocation_from_its_lower_layers() {
731
+ let (overlay, _) = overlay_on(Arc::new(Temporary::new()));
732
+ overwrite(&overlay, "dir/file", b"upper");
733
+ let next = overlay
734
+ .for_invocation()
735
+ .expect("an Overlay over a Temporary must start each Invocation afresh");
736
+ assert_eq!(
737
+ read(next.as_ref(), "dir/file"),
738
+ b"lower",
739
+ "Overlay::for_invocation over a Temporary must hand the next Invocation the lower content"
740
+ );
741
+ }
742
+
743
+ // @behavior FS-013
744
+ #[test]
745
+ fn an_upper_layer_hides_the_same_path_in_a_lower_one() {
746
+ let overlay = Overlay::new()
747
+ .layer(memory_with("note", "upper"), Access::Read)
748
+ .layer(memory_with("note", "lower"), Access::Read);
749
+ assert_eq!(
750
+ read(&overlay, "note"),
751
+ b"upper",
752
+ "a path both Layers hold read through Overlay::open must read the upper Layer's content"
753
+ );
754
+ }
755
+
756
+ // @behavior FS-014
757
+ #[test]
758
+ fn a_write_to_a_writable_layers_file_changes_it_in_place() {
759
+ let upper = Arc::new(Memory::new());
760
+ let lower = memory_with("note", "lower");
761
+ let overlay = Overlay::new()
762
+ .layer(upper.clone(), Access::ReadWrite)
763
+ .layer(lower.clone(), Access::ReadWrite);
764
+ overwrite(&overlay, "note", b"changed");
765
+ assert_eq!(
766
+ (read(lower.as_ref(), "note"), upper.metadata("note")),
767
+ (b"changed".to_vec(), Err(FilesystemError::NotFound)),
768
+ "a writable lower Layer's file written through Overlay::open must change in that Layer alone"
769
+ );
770
+ }
771
+
772
+ // The lower Layer is writable, so only the refusal itself keeps the
773
+ // move from landing in two Layers.
774
+ // @behavior FS-088
775
+ #[test]
776
+ fn an_overlay_refuses_to_move_a_file_a_lower_writable_layer_holds() {
777
+ let upper = Arc::new(Memory::new());
778
+ let lower = memory_with("note", "lower");
779
+ let overlay = Overlay::new()
780
+ .layer(upper.clone(), Access::ReadWrite)
781
+ .layer(lower.clone(), Access::ReadWrite);
782
+ assert_eq!(
783
+ (
784
+ overlay.rename("note", "moved", Rename::File),
785
+ read(lower.as_ref(), "note"),
786
+ upper.read_dir("").map(|entries| entries.len())
787
+ ),
788
+ (Err(FilesystemError::CrossesDevices), b"lower".to_vec(), Ok(0)),
789
+ "a file a lower writable Layer holds, renamed through Overlay::rename, must be refused \
790
+ as crossing devices with nothing moved or copied"
791
+ );
792
+ }
793
+
794
+ // The writable Layer keeps the whiteout the removal left, so only a
795
+ // call that answers by what landed rather than by the clean-up after
796
+ // it succeeds.
797
+ // @behavior FS-089
798
+ #[test]
799
+ fn an_overlay_change_that_lands_succeeds_though_its_whiteout_stays() {
800
+ let (overlay, _) = overlay_on(Arc::new(KeepsWhiteouts(Memory::new())));
801
+ overlay
802
+ .remove_file("dir/file")
803
+ .expect("the file must be removed");
804
+ assert_eq!(
805
+ write(&overlay, "dir/file", b"again").map(|()| read(&overlay, "dir/file")),
806
+ Ok(b"again".to_vec()),
807
+ "a file made again through Overlay::open where it was removed, while the writable \
808
+ Layer refuses to remove the whiteout, must succeed and show the new file"
809
+ );
810
+ }
811
+
812
+ // @behavior FS-015
813
+ #[test]
814
+ fn a_new_file_lands_in_the_topmost_writable_layer() {
815
+ let first = Arc::new(Memory::new());
816
+ let second = Arc::new(Memory::new());
817
+ let overlay = Overlay::new()
818
+ .layer(Arc::new(Memory::new()), Access::Read)
819
+ .layer(first.clone(), Access::ReadWrite)
820
+ .layer(second.clone(), Access::ReadWrite);
821
+ overwrite(&overlay, "new", b"made");
822
+ assert_eq!(
823
+ (read(first.as_ref(), "new"), second.metadata("new")),
824
+ (b"made".to_vec(), Err(FilesystemError::NotFound)),
825
+ "a file created through Overlay::open must land in the topmost writable Layer"
826
+ );
827
+ }
828
+
829
+ // @behavior FS-016
830
+ #[test]
831
+ fn a_removal_leaves_a_whiteout_in_the_topmost_writable_layer() {
832
+ let upper = Arc::new(Memory::new());
833
+ let (overlay, _) = overlay_on(upper.clone());
834
+ overlay
835
+ .remove_file("dir/file")
836
+ .expect("the file must be removed");
837
+ assert_eq!(
838
+ upper.metadata("dir/.wh.file").map(|metadata| metadata.file_type),
839
+ Ok(FileType::File),
840
+ "a read-only Layer's file removed through Overlay::remove_file must leave .wh.file beside it in the writable Layer"
841
+ );
842
+ }
843
+
844
+ // @behavior FS-017
845
+ #[test]
846
+ fn a_whiteout_lasts_as_long_as_its_writable_layer() {
847
+ let upper = Arc::new(Memory::new());
848
+ let (overlay, lower) = overlay_on(upper.clone());
849
+ overlay
850
+ .remove_file("dir/file")
851
+ .expect("the file must be removed");
852
+ let rebuilt = Overlay::new()
853
+ .layer(upper, Access::ReadWrite)
854
+ .layer(lower, Access::Read);
855
+ assert_eq!(
856
+ rebuilt.metadata("dir/file"),
857
+ Err(FilesystemError::NotFound),
858
+ "a lower file removed through an Overlay over a Memory must stay gone from a new Overlay over the same Layers"
859
+ );
860
+ }
861
+
862
+ // @behavior FS-018
863
+ #[test]
864
+ fn a_directory_made_again_where_one_was_removed_shows_nothing_from_below() {
865
+ let (overlay, _) = overlay_on(Arc::new(Memory::new()));
866
+ overlay
867
+ .remove_file("dir/file")
868
+ .expect("the file must be removed");
869
+ overlay
870
+ .remove_dir("dir")
871
+ .expect("the directory must be removed");
872
+ overlay
873
+ .create_dir("dir")
874
+ .expect("the directory must be made");
875
+ assert_eq!(
876
+ names(&overlay, "dir"),
877
+ Vec::<String>::new(),
878
+ "a directory made again through Overlay::create_dir must list nothing from the read-only Layer"
879
+ );
880
+ }
881
+
882
+ // @behavior FS-019
883
+ #[test]
884
+ fn an_overlay_lists_none_of_its_markers() {
885
+ let (overlay, _) = overlay_on(Arc::new(Memory::new()));
886
+ overwrite(&overlay, "dir/new", b"made");
887
+ overlay
888
+ .remove_file("dir/file")
889
+ .expect("the file must be removed");
890
+ assert_eq!(
891
+ names(&overlay, "dir"),
892
+ ["new"],
893
+ "a directory holding a whiteout listed through Overlay::read_dir must show no .wh. entry"
894
+ );
895
+ }
896
+
897
+ // @behavior FS-020
898
+ #[test]
899
+ fn an_overlay_refuses_a_name_reserved_for_its_markers() {
900
+ let (overlay, _) = overlay_on(Arc::new(Memory::new()));
901
+ assert_eq!(
902
+ write(&overlay, "dir/.wh.note", b"x"),
903
+ Err(FilesystemError::InvalidInput),
904
+ "a file named .wh.note created through Overlay::open must be refused"
905
+ );
906
+ }
907
+
908
+ // A Layer folding case would hold `.WH.note` as the marker hiding `note`.
909
+ // @behavior FS-056
910
+ #[test]
911
+ fn an_overlay_refuses_its_reserved_name_in_any_case() {
912
+ let (overlay, _) = overlay_on(Arc::new(Memory::new()));
913
+ assert_eq!(
914
+ write(&overlay, "dir/.WH.note", b"x"),
915
+ Err(FilesystemError::InvalidInput),
916
+ "a file named .WH.note created through Overlay::open must be refused"
917
+ );
918
+ }
919
+
920
+ // @behavior FS-021
921
+ #[test]
922
+ fn an_overlay_with_no_writable_layer_refuses_every_change_as_read_only() {
923
+ let overlay = Overlay::new().layer(memory_with("dir/file", "lower"), Access::Read);
924
+ assert_eq!(
925
+ write(&overlay, "dir/file", b"x"),
926
+ Err(FilesystemError::ReadOnlyFilesystem),
927
+ "a file written through an Overlay of read-only Layers must be refused as read-only"
928
+ );
929
+ }
930
+
931
+ #[test]
932
+ fn an_overlay_lists_every_layer_without_what_it_hid() {
933
+ let (overlay, _) = overlay_on(Arc::new(Memory::new()));
934
+ overwrite(&overlay, "dir/new", b"made");
935
+ assert_eq!(names(&overlay, "dir"), ["file", "new"]);
936
+ }
937
+
938
+ #[test]
939
+ fn a_lower_file_renamed_through_an_overlay_moves_into_the_writable_layer() {
940
+ let upper = Arc::new(Memory::new());
941
+ let (overlay, lower) = overlay_on(upper.clone());
942
+ overlay
943
+ .rename("dir/file", "dir/moved", Rename::Any)
944
+ .expect("the file must move");
945
+ assert_eq!(
946
+ (
947
+ read(&overlay, "dir/moved"),
948
+ overlay.metadata("dir/file"),
949
+ read(lower.as_ref(), "dir/file"),
950
+ ),
951
+ (
952
+ b"lower".to_vec(),
953
+ Err(FilesystemError::NotFound),
954
+ b"lower".to_vec()
955
+ ),
956
+ );
957
+ }
958
+
959
+ /// A Memory that, the first time it lists a directory, starts another
960
+ /// call through `overlay` on a thread of its own and gives it a while
961
+ /// to finish, recording whether it did before this call returned.
962
+ struct Interleaves {
963
+ memory: Memory,
964
+ overlay: Mutex<Option<Overlay>>,
965
+ finished_inside: Mutex<Option<bool>>,
966
+ other: Mutex<Option<thread::JoinHandle<()>>>,
967
+ }
968
+
969
+ impl Filesystem for Interleaves {
970
+ holds_no_links!();
971
+ never_repeats_identities!();
972
+ is_shared!();
973
+ matches_names_exactly!();
974
+
975
+ fn open(
976
+ &self,
977
+ path: &str,
978
+ options: &OpenOptions,
979
+ ) -> Result<Box<dyn File>, FilesystemError> {
980
+ self.memory.open(path, options)
981
+ }
982
+
983
+ fn metadata(&self, path: &str) -> Result<Metadata, FilesystemError> {
984
+ self.memory.metadata(path)
985
+ }
986
+
987
+ fn read_dir(&self, path: &str) -> Result<Vec<DirEntry>, FilesystemError> {
988
+ if let Some(overlay) = self.overlay.lock().unwrap().take() {
989
+ let finished = Arc::new(AtomicUsize::new(0));
990
+ let marker = finished.clone();
991
+ let handle = thread::spawn(move || {
992
+ let _ = write(&overlay, "dir/new", b"made");
993
+ marker.store(1, Ordering::SeqCst);
994
+ });
995
+ let deadline = Instant::now() + Duration::from_millis(200);
996
+ while finished.load(Ordering::SeqCst) == 0 && Instant::now() < deadline {
997
+ thread::sleep(Duration::from_millis(5));
998
+ }
999
+ *self.finished_inside.lock().unwrap() = Some(finished.load(Ordering::SeqCst) == 1);
1000
+ *self.other.lock().unwrap() = Some(handle);
1001
+ }
1002
+ self.memory.read_dir(path)
1003
+ }
1004
+
1005
+ fn create_dir(&self, path: &str) -> Result<(), FilesystemError> {
1006
+ self.memory.create_dir(path)
1007
+ }
1008
+
1009
+ fn remove_dir(&self, path: &str) -> Result<(), FilesystemError> {
1010
+ self.memory.remove_dir(path)
1011
+ }
1012
+
1013
+ fn remove_file(&self, path: &str) -> Result<(), FilesystemError> {
1014
+ self.memory.remove_file(path)
1015
+ }
1016
+
1017
+ fn rename(&self, from: &str, to: &str, rename: Rename) -> Result<(), FilesystemError> {
1018
+ self.memory.rename(from, to, rename)
1019
+ }
1020
+ }
1021
+
1022
+ // The other call starts inside this one, between its emptiness check
1023
+ // and its removal, so only the Overlay's lock keeps the file it
1024
+ // creates from being removed with the directory.
1025
+ // @behavior FS-037
1026
+ #[test]
1027
+ fn a_change_through_an_overlay_waits_for_the_one_in_progress() {
1028
+ let upper = Arc::new(Interleaves {
1029
+ memory: [("dir/.keep", "")].into_iter().collect(),
1030
+ overlay: Mutex::new(None),
1031
+ finished_inside: Mutex::new(None),
1032
+ other: Mutex::new(None),
1033
+ });
1034
+ upper
1035
+ .memory
1036
+ .remove_file("dir/.keep")
1037
+ .expect("the seed must go");
1038
+ let overlay = Overlay::new().layer(upper.clone(), Access::ReadWrite);
1039
+ *upper.overlay.lock().unwrap() = Some(overlay.clone());
1040
+ let removed = overlay.remove_dir("dir");
1041
+ upper
1042
+ .other
1043
+ .lock()
1044
+ .unwrap()
1045
+ .take()
1046
+ .expect("the other call must have started")
1047
+ .join()
1048
+ .expect("the other call must not panic");
1049
+ assert_eq!(
1050
+ (removed, *upper.finished_inside.lock().unwrap(), overlay.metadata("dir/new").is_ok()),
1051
+ (Ok(()), Some(false), false),
1052
+ "a change through Overlay::open started while Overlay::remove_dir runs must wait for it, \
1053
+ failing rather than landing in the directory being removed"
1054
+ );
1055
+ }
1056
+
1057
+ // @behavior FS-038
1058
+ #[test]
1059
+ fn a_call_through_an_overlay_from_inside_one_answers_a_deadlock() {
1060
+ struct Reenters {
1061
+ memory: Memory,
1062
+ overlay: Mutex<Option<Overlay>>,
1063
+ answer: Mutex<Option<Result<Metadata, FilesystemError>>>,
1064
+ }
1065
+
1066
+ impl Filesystem for Reenters {
1067
+ holds_no_links!();
1068
+ never_repeats_identities!();
1069
+ is_shared!();
1070
+ matches_names_exactly!();
1071
+
1072
+ fn open(
1073
+ &self,
1074
+ path: &str,
1075
+ options: &OpenOptions,
1076
+ ) -> Result<Box<dyn File>, FilesystemError> {
1077
+ self.memory.open(path, options)
1078
+ }
1079
+
1080
+ fn metadata(&self, path: &str) -> Result<Metadata, FilesystemError> {
1081
+ if let Some(overlay) = self.overlay.lock().unwrap().take() {
1082
+ *self.answer.lock().unwrap() = Some(overlay.metadata(path));
1083
+ }
1084
+ self.memory.metadata(path)
1085
+ }
1086
+
1087
+ fn read_dir(&self, path: &str) -> Result<Vec<DirEntry>, FilesystemError> {
1088
+ self.memory.read_dir(path)
1089
+ }
1090
+
1091
+ fn create_dir(&self, path: &str) -> Result<(), FilesystemError> {
1092
+ self.memory.create_dir(path)
1093
+ }
1094
+
1095
+ fn remove_dir(&self, path: &str) -> Result<(), FilesystemError> {
1096
+ self.memory.remove_dir(path)
1097
+ }
1098
+
1099
+ fn remove_file(&self, path: &str) -> Result<(), FilesystemError> {
1100
+ self.memory.remove_file(path)
1101
+ }
1102
+
1103
+ fn rename(&self, from: &str, to: &str, rename: Rename) -> Result<(), FilesystemError> {
1104
+ self.memory.rename(from, to, rename)
1105
+ }
1106
+ }
1107
+
1108
+ let layer = Arc::new(Reenters {
1109
+ memory: [("note", "x")].into_iter().collect(),
1110
+ overlay: Mutex::new(None),
1111
+ answer: Mutex::new(None),
1112
+ });
1113
+ let overlay = Overlay::new().layer(layer.clone(), Access::Read);
1114
+ *layer.overlay.lock().unwrap() = Some(overlay.clone());
1115
+ overlay
1116
+ .metadata("note")
1117
+ .expect("the outer call must answer");
1118
+ assert_eq!(
1119
+ layer.answer.lock().unwrap().take(),
1120
+ Some(Err(FilesystemError::Deadlock)),
1121
+ "a call through an Overlay made from inside one of its own calls on the same thread \
1122
+ must answer Deadlock rather than wait forever"
1123
+ );
1124
+ }
1125
+
1126
+ /// A writable Memory seeded with `files` and room for `inodes`, over a
1127
+ /// read-only one holding `dir/file` = "lower".
1128
+ fn full_over_lower(files: &[(&str, &str)], inodes: usize) -> (Overlay, Arc<Memory>) {
1129
+ let upper = Memory::from_iter(files.iter().copied()).with_inodes(Some(inodes));
1130
+ let upper = Arc::new(upper);
1131
+ let overlay = Overlay::new()
1132
+ .layer(upper.clone(), Access::ReadWrite)
1133
+ .layer(memory_with("dir/file", "lower"), Access::Read);
1134
+ (overlay, upper)
1135
+ }
1136
+
1137
+ // The upper copy is all the room there is, so the whiteout cannot be
1138
+ // made; taking the copy away first would show the lower file again.
1139
+ // @behavior FS-040
1140
+ #[test]
1141
+ fn a_removal_that_cannot_leave_its_whiteout_removes_nothing() {
1142
+ let (overlay, _) = full_over_lower(&[("dir/file", "upper")], 3);
1143
+ assert_eq!(
1144
+ (overlay.remove_file("dir/file"), read(&overlay, "dir/file")),
1145
+ (Err(FilesystemError::StorageFull), b"upper".to_vec()),
1146
+ "a file removed through Overlay::remove_file whose whiteout finds no room must stay as \
1147
+ it was"
1148
+ );
1149
+ }
1150
+
1151
+ // @behavior FS-041
1152
+ #[test]
1153
+ fn a_directory_that_cannot_be_marked_opaque_is_not_made() {
1154
+ let (overlay, upper) = full_over_lower(&[(".wh.dir", "")], 3);
1155
+ assert_eq!(
1156
+ (
1157
+ overlay.create_dir("dir"),
1158
+ overlay.metadata("dir"),
1159
+ upper.metadata("dir")
1160
+ ),
1161
+ (
1162
+ Err(FilesystemError::StorageFull),
1163
+ Err(FilesystemError::NotFound),
1164
+ Err(FilesystemError::NotFound)
1165
+ ),
1166
+ "a directory made through Overlay::create_dir where one was removed, with no room for \
1167
+ its opaque marker, must not be made, nor its whiteout lifted"
1168
+ );
1169
+ }
1170
+
1171
+ // The copy up takes the last room, so the whiteout over the source
1172
+ // cannot be made; moving first would leave the file in two places.
1173
+ // @behavior FS-042
1174
+ #[test]
1175
+ fn a_rename_that_cannot_leave_its_whiteout_moves_nothing() {
1176
+ let (overlay, _) = full_over_lower(&[], 3);
1177
+ assert_eq!(
1178
+ (
1179
+ overlay.rename("dir/file", "dir/moved", Rename::Any),
1180
+ overlay.metadata("dir/moved"),
1181
+ read(&overlay, "dir/file")
1182
+ ),
1183
+ (
1184
+ Err(FilesystemError::StorageFull),
1185
+ Err(FilesystemError::NotFound),
1186
+ b"lower".to_vec()
1187
+ ),
1188
+ "a file renamed through Overlay::rename whose whiteout finds no room must stay where \
1189
+ it was"
1190
+ );
1191
+ }
1192
+
1193
+ // The lower writable copy still holds the file the upper whiteout
1194
+ // hides, so it cannot be removed; removing the upper copy first would
1195
+ // show the file again.
1196
+ // @behavior FS-074
1197
+ #[test]
1198
+ fn a_directory_a_lower_writable_layer_still_fills_is_removed_behind_a_whiteout() {
1199
+ let lower = memory_with("dir/file", "lower");
1200
+ let overlay = Overlay::new()
1201
+ .layer(memory_with("dir/.wh.file", ""), Access::ReadWrite)
1202
+ .layer(lower.clone(), Access::ReadWrite);
1203
+ assert_eq!(
1204
+ (
1205
+ overlay.remove_dir("dir"),
1206
+ overlay.metadata("dir").err(),
1207
+ read(lower.as_ref(), "dir/file")
1208
+ ),
1209
+ (Ok(()), Some(FilesystemError::NotFound), b"lower".to_vec()),
1210
+ "a directory the view shows empty, removed through Overlay::remove_dir while a lower \
1211
+ writable Layer still holds a whited-out file in it, must leave the view and stay \
1212
+ below unseen"
1213
+ );
1214
+ }
1215
+
1216
+ // The marker is the one entry past the room the upper Layer has, so
1217
+ // marking after the move would leave the directory moved.
1218
+ // @behavior FS-075
1219
+ #[test]
1220
+ fn a_rename_that_cannot_mark_its_directory_opaque_moves_nothing() {
1221
+ let upper = Arc::new(Memory::new().with_inodes(Some(2)));
1222
+ upper
1223
+ .create_dir("moving")
1224
+ .expect("the directory must be made");
1225
+ let lower = Arc::new(Memory::new());
1226
+ lower.create_dir("dir").expect("the directory must be made");
1227
+ let overlay = Overlay::new()
1228
+ .layer(upper.clone(), Access::ReadWrite)
1229
+ .layer(lower, Access::Read);
1230
+ assert_eq!(
1231
+ (
1232
+ overlay.rename("moving", "dir", Rename::Directory),
1233
+ upper.metadata("moving").map(|metadata| metadata.file_type),
1234
+ upper.metadata("dir").err()
1235
+ ),
1236
+ (
1237
+ Err(FilesystemError::StorageFull),
1238
+ Ok(FileType::Directory),
1239
+ Some(FilesystemError::NotFound)
1240
+ ),
1241
+ "a directory renamed through Overlay::rename over one a lower Layer holds, with no \
1242
+ room for its opaque marker, must stay where it was"
1243
+ );
1244
+ }
1245
+
1246
+ // The writable Layer sits between two read-only ones, so a rename
1247
+ // landing in it beneath the top Layer's `to` would vanish from view.
1248
+ // @behavior FS-076
1249
+ #[test]
1250
+ fn a_rename_a_read_only_layer_above_would_cover_is_refused() {
1251
+ let overlay = Overlay::new()
1252
+ .layer(memory_with("to", "top"), Access::Read)
1253
+ .layer(Arc::new(Memory::new()), Access::ReadWrite)
1254
+ .layer(memory_with("from", "moved"), Access::Read);
1255
+ assert_eq!(
1256
+ (
1257
+ overlay.rename("from", "to", Rename::Any),
1258
+ read(&overlay, "from"),
1259
+ read(&overlay, "to")
1260
+ ),
1261
+ (
1262
+ Err(FilesystemError::ReadOnlyFilesystem),
1263
+ b"moved".to_vec(),
1264
+ b"top".to_vec()
1265
+ ),
1266
+ "a file renamed through Overlay::rename onto a name a read-only Layer above the \
1267
+ writable one holds must be refused as read-only and move nothing"
1268
+ );
1269
+ }
1270
+
1271
+ // @behavior FS-077
1272
+ #[test]
1273
+ fn a_name_a_read_only_layer_above_hides_is_not_made() {
1274
+ let upper = Arc::new(Memory::new());
1275
+ let overlay = Overlay::new()
1276
+ .layer(
1277
+ Arc::new(
1278
+ [(".wh.file", ""), (".wh.dir", "")]
1279
+ .into_iter()
1280
+ .collect::<Memory>(),
1281
+ ),
1282
+ Access::Read,
1283
+ )
1284
+ .layer(upper.clone(), Access::ReadWrite);
1285
+ assert_eq!(
1286
+ (
1287
+ write(&overlay, "file", b"new"),
1288
+ overlay.create_dir("dir"),
1289
+ upper.metadata("file").err(),
1290
+ upper.metadata("dir").err()
1291
+ ),
1292
+ (
1293
+ Err(FilesystemError::ReadOnlyFilesystem),
1294
+ Err(FilesystemError::ReadOnlyFilesystem),
1295
+ Some(FilesystemError::NotFound),
1296
+ Some(FilesystemError::NotFound)
1297
+ ),
1298
+ "a file and a directory made through an Overlay where a read-only Layer above the \
1299
+ writable one holds their whiteouts must be refused as read-only and not made"
1300
+ );
1301
+ }
1302
+
1303
+ // @behavior FS-043
1304
+ #[test]
1305
+ fn a_directory_a_lower_layer_holds_does_not_move_through_an_overlay() {
1306
+ let (overlay, _) = overlay_on(Arc::new(Memory::new()));
1307
+ assert_eq!(
1308
+ (
1309
+ overlay.rename("dir", "moved", Rename::Any),
1310
+ overlay.metadata("moved")
1311
+ ),
1312
+ (
1313
+ Err(FilesystemError::CrossesDevices),
1314
+ Err(FilesystemError::NotFound)
1315
+ ),
1316
+ "a directory a read-only Layer holds renamed through Overlay::rename must be refused \
1317
+ as crossing devices, as overlayfs refuses it"
1318
+ );
1319
+ }
1320
+
1321
+ // The directory shows only from the read-only Layer, so only a check of
1322
+ // the whole view finds it in the way.
1323
+ // @behavior FS-044
1324
+ #[test]
1325
+ fn a_rename_onto_what_an_overlay_shows_is_checked_against_the_view() {
1326
+ let upper = Arc::new(Memory::new());
1327
+ let (overlay, _) = overlay_on(upper.clone());
1328
+ overwrite(&overlay, "note", b"x");
1329
+ overlay
1330
+ .create_dir("empty")
1331
+ .expect("the directory must be made");
1332
+ assert_eq!(
1333
+ (
1334
+ overlay.rename("note", "dir", Rename::Any),
1335
+ overlay.rename("empty", "dir", Rename::Any),
1336
+ read(&overlay, "dir/file")
1337
+ ),
1338
+ (
1339
+ Err(FilesystemError::IsADirectory),
1340
+ Err(FilesystemError::DirectoryNotEmpty),
1341
+ b"lower".to_vec()
1342
+ ),
1343
+ "a rename through Overlay::rename onto a directory only a read-only Layer shows must \
1344
+ be refused as rename(2) refuses it, leaving that directory as it was"
1345
+ );
1346
+ }
1347
+
1348
+ // A directory moved over an empty one a lower Layer holds must not take
1349
+ // up what that Layer later shows there.
1350
+ // @behavior FS-045
1351
+ #[test]
1352
+ fn a_directory_moved_over_a_lower_one_is_marked_opaque() {
1353
+ let upper = Arc::new(Memory::new());
1354
+ let lower: Arc<Memory> = Arc::new([("dir/.keep", "")].into_iter().collect());
1355
+ lower.remove_file("dir/.keep").expect("the seed must go");
1356
+ let overlay = Overlay::new()
1357
+ .layer(upper.clone(), Access::ReadWrite)
1358
+ .layer(lower.clone(), Access::Read);
1359
+ overlay
1360
+ .create_dir("made")
1361
+ .expect("the directory must be made");
1362
+ overlay
1363
+ .rename("made", "dir", Rename::Any)
1364
+ .expect("the directory must move over the empty one");
1365
+ assert_eq!(
1366
+ upper
1367
+ .metadata("dir/.wh..wh..opq")
1368
+ .map(|metadata| metadata.file_type),
1369
+ Ok(FileType::File),
1370
+ "a directory moved through Overlay::rename over one a read-only Layer holds must be \
1371
+ marked opaque in the writable Layer"
1372
+ );
1373
+ }
1374
+
1375
+ // The writable Layer has room for the copy's entry but for less than
1376
+ // the lower file's bytes, so the copy fails part way.
1377
+ // @behavior FS-046
1378
+ #[test]
1379
+ fn a_copy_up_that_fails_part_way_leaves_nothing_behind() {
1380
+ let upper = Arc::new(Memory::new().with_limit(Some(2)).with_inodes(Some(8)));
1381
+ let (overlay, _) = overlay_on(upper.clone());
1382
+ assert_eq!(
1383
+ (
1384
+ write(&overlay, "dir/file", b"x"),
1385
+ upper.metadata("dir/file"),
1386
+ read(&overlay, "dir/file")
1387
+ ),
1388
+ (
1389
+ Err(FilesystemError::StorageFull),
1390
+ Err(FilesystemError::NotFound),
1391
+ b"lower".to_vec()
1392
+ ),
1393
+ "a write through Overlay::open whose copy up runs out of room must leave no partial \
1394
+ copy hiding the lower file"
1395
+ );
1396
+ }
1397
+
1398
+ /// A Memory whose files write at most three bytes per call and, when
1399
+ /// `refuses_close`, refuse their close.
1400
+ struct Trickles {
1401
+ memory: Memory,
1402
+ refuses_close: bool,
1403
+ }
1404
+
1405
+ struct TrickleFile {
1406
+ file: Box<dyn File>,
1407
+ refuses_close: bool,
1408
+ }
1409
+
1410
+ impl File for TrickleFile {
1411
+ fn read_at(&mut self, buf: &mut [u8], offset: u64) -> Result<usize, FilesystemError> {
1412
+ self.file.read_at(buf, offset)
1413
+ }
1414
+
1415
+ fn write_at(&mut self, buf: &[u8], offset: u64) -> Result<usize, FilesystemError> {
1416
+ self.file.write_at(&buf[..buf.len().min(3)], offset)
1417
+ }
1418
+
1419
+ fn append(&mut self, buf: &[u8]) -> Result<(u64, usize), FilesystemError> {
1420
+ self.file.append(&buf[..buf.len().min(3)])
1421
+ }
1422
+
1423
+ fn metadata(&self) -> Result<Metadata, FilesystemError> {
1424
+ self.file.metadata()
1425
+ }
1426
+
1427
+ fn set_len(&mut self, len: u64) -> Result<(), FilesystemError> {
1428
+ self.file.set_len(len)
1429
+ }
1430
+
1431
+ fn close(self: Box<Self>) -> Result<(), FilesystemError> {
1432
+ if self.refuses_close {
1433
+ return Err(FilesystemError::PermissionDenied);
1434
+ }
1435
+ self.file.close()
1436
+ }
1437
+ }
1438
+
1439
+ impl Filesystem for Trickles {
1440
+ holds_no_links!();
1441
+ never_repeats_identities!();
1442
+ is_shared!();
1443
+ matches_names_exactly!();
1444
+
1445
+ fn open(
1446
+ &self,
1447
+ path: &str,
1448
+ options: &OpenOptions,
1449
+ ) -> Result<Box<dyn File>, FilesystemError> {
1450
+ Ok(Box::new(TrickleFile {
1451
+ file: self.memory.open(path, options)?,
1452
+ refuses_close: self.refuses_close,
1453
+ }))
1454
+ }
1455
+
1456
+ fn metadata(&self, path: &str) -> Result<Metadata, FilesystemError> {
1457
+ self.memory.metadata(path)
1458
+ }
1459
+
1460
+ fn read_dir(&self, path: &str) -> Result<Vec<DirEntry>, FilesystemError> {
1461
+ self.memory.read_dir(path)
1462
+ }
1463
+
1464
+ fn create_dir(&self, path: &str) -> Result<(), FilesystemError> {
1465
+ self.memory.create_dir(path)
1466
+ }
1467
+
1468
+ fn remove_dir(&self, path: &str) -> Result<(), FilesystemError> {
1469
+ self.memory.remove_dir(path)
1470
+ }
1471
+
1472
+ fn remove_file(&self, path: &str) -> Result<(), FilesystemError> {
1473
+ self.memory.remove_file(path)
1474
+ }
1475
+
1476
+ fn rename(&self, from: &str, to: &str, rename: Rename) -> Result<(), FilesystemError> {
1477
+ self.memory.rename(from, to, rename)
1478
+ }
1479
+ }
1480
+
1481
+ /// A Memory refusing to move or remove a directory.
1482
+ struct HoldsDirectories(Memory);
1483
+
1484
+ impl Filesystem for HoldsDirectories {
1485
+ holds_no_links!();
1486
+ never_repeats_identities!();
1487
+ is_shared!();
1488
+ matches_names_exactly!();
1489
+
1490
+ fn open(
1491
+ &self,
1492
+ path: &str,
1493
+ options: &OpenOptions,
1494
+ ) -> Result<Box<dyn File>, FilesystemError> {
1495
+ self.0.open(path, options)
1496
+ }
1497
+
1498
+ fn metadata(&self, path: &str) -> Result<Metadata, FilesystemError> {
1499
+ self.0.metadata(path)
1500
+ }
1501
+
1502
+ fn read_dir(&self, path: &str) -> Result<Vec<DirEntry>, FilesystemError> {
1503
+ self.0.read_dir(path)
1504
+ }
1505
+
1506
+ fn create_dir(&self, path: &str) -> Result<(), FilesystemError> {
1507
+ self.0.create_dir(path)
1508
+ }
1509
+
1510
+ fn remove_dir(&self, _path: &str) -> Result<(), FilesystemError> {
1511
+ Err(FilesystemError::PermissionDenied)
1512
+ }
1513
+
1514
+ fn remove_file(&self, path: &str) -> Result<(), FilesystemError> {
1515
+ self.0.remove_file(path)
1516
+ }
1517
+
1518
+ fn rename(&self, _from: &str, _to: &str, _rename: Rename) -> Result<(), FilesystemError> {
1519
+ Err(FilesystemError::PermissionDenied)
1520
+ }
1521
+ }
1522
+
1523
+ /// A Memory refusing to remove a whiteout.
1524
+ struct KeepsWhiteouts(Memory);
1525
+
1526
+ impl Filesystem for KeepsWhiteouts {
1527
+ holds_no_links!();
1528
+ never_repeats_identities!();
1529
+ is_shared!();
1530
+ matches_names_exactly!();
1531
+
1532
+ fn open(
1533
+ &self,
1534
+ path: &str,
1535
+ options: &OpenOptions,
1536
+ ) -> Result<Box<dyn File>, FilesystemError> {
1537
+ self.0.open(path, options)
1538
+ }
1539
+
1540
+ fn metadata(&self, path: &str) -> Result<Metadata, FilesystemError> {
1541
+ self.0.metadata(path)
1542
+ }
1543
+
1544
+ fn read_dir(&self, path: &str) -> Result<Vec<DirEntry>, FilesystemError> {
1545
+ self.0.read_dir(path)
1546
+ }
1547
+
1548
+ fn create_dir(&self, path: &str) -> Result<(), FilesystemError> {
1549
+ self.0.create_dir(path)
1550
+ }
1551
+
1552
+ fn remove_dir(&self, path: &str) -> Result<(), FilesystemError> {
1553
+ self.0.remove_dir(path)
1554
+ }
1555
+
1556
+ fn remove_file(&self, path: &str) -> Result<(), FilesystemError> {
1557
+ match path.rsplit('/').next() {
1558
+ Some(name) if name.starts_with(".wh.") => Err(FilesystemError::PermissionDenied),
1559
+ _ => self.0.remove_file(path),
1560
+ }
1561
+ }
1562
+
1563
+ fn rename(&self, from: &str, to: &str, rename: Rename) -> Result<(), FilesystemError> {
1564
+ self.0.rename(from, to, rename)
1565
+ }
1566
+ }
1567
+
1568
+ /// A Memory answering a lookup of any marker name with an I/O failure.
1569
+ struct ReadsNoMarker(Memory);
1570
+
1571
+ impl Filesystem for ReadsNoMarker {
1572
+ holds_no_links!();
1573
+ never_repeats_identities!();
1574
+ is_shared!();
1575
+ matches_names_exactly!();
1576
+
1577
+ fn open(
1578
+ &self,
1579
+ path: &str,
1580
+ options: &OpenOptions,
1581
+ ) -> Result<Box<dyn File>, FilesystemError> {
1582
+ self.0.open(path, options)
1583
+ }
1584
+
1585
+ fn metadata(&self, path: &str) -> Result<Metadata, FilesystemError> {
1586
+ if path
1587
+ .rsplit('/')
1588
+ .next()
1589
+ .is_some_and(|name| name.starts_with(".wh."))
1590
+ {
1591
+ return Err(FilesystemError::Other(
1592
+ "the marker cannot be read".to_string(),
1593
+ ));
1594
+ }
1595
+ self.0.metadata(path)
1596
+ }
1597
+
1598
+ fn read_dir(&self, path: &str) -> Result<Vec<DirEntry>, FilesystemError> {
1599
+ self.0.read_dir(path)
1600
+ }
1601
+
1602
+ fn create_dir(&self, path: &str) -> Result<(), FilesystemError> {
1603
+ self.0.create_dir(path)
1604
+ }
1605
+
1606
+ fn remove_dir(&self, path: &str) -> Result<(), FilesystemError> {
1607
+ self.0.remove_dir(path)
1608
+ }
1609
+
1610
+ fn remove_file(&self, path: &str) -> Result<(), FilesystemError> {
1611
+ self.0.remove_file(path)
1612
+ }
1613
+
1614
+ fn rename(&self, from: &str, to: &str, rename: Rename) -> Result<(), FilesystemError> {
1615
+ self.0.rename(from, to, rename)
1616
+ }
1617
+ }
1618
+
1619
+ /// A Memory whose `file` is gone after its first lookup, as a host
1620
+ /// directory another process changes mid-call.
1621
+ struct Vanishes {
1622
+ memory: Memory,
1623
+ seen: AtomicBool,
1624
+ }
1625
+
1626
+ impl Filesystem for Vanishes {
1627
+ holds_no_links!();
1628
+ never_repeats_identities!();
1629
+ is_shared!();
1630
+ matches_names_exactly!();
1631
+
1632
+ fn open(
1633
+ &self,
1634
+ path: &str,
1635
+ options: &OpenOptions,
1636
+ ) -> Result<Box<dyn File>, FilesystemError> {
1637
+ self.memory.open(path, options)
1638
+ }
1639
+
1640
+ fn metadata(&self, path: &str) -> Result<Metadata, FilesystemError> {
1641
+ if path == "file" && self.seen.swap(true, Ordering::SeqCst) {
1642
+ return Err(FilesystemError::NotFound);
1643
+ }
1644
+ self.memory.metadata(path)
1645
+ }
1646
+
1647
+ fn read_dir(&self, path: &str) -> Result<Vec<DirEntry>, FilesystemError> {
1648
+ self.memory.read_dir(path)
1649
+ }
1650
+
1651
+ fn create_dir(&self, path: &str) -> Result<(), FilesystemError> {
1652
+ self.memory.create_dir(path)
1653
+ }
1654
+
1655
+ fn remove_dir(&self, path: &str) -> Result<(), FilesystemError> {
1656
+ self.memory.remove_dir(path)
1657
+ }
1658
+
1659
+ fn remove_file(&self, path: &str) -> Result<(), FilesystemError> {
1660
+ self.memory.remove_file(path)
1661
+ }
1662
+
1663
+ fn rename(&self, from: &str, to: &str, rename: Rename) -> Result<(), FilesystemError> {
1664
+ self.memory.rename(from, to, rename)
1665
+ }
1666
+ }
1667
+
1668
+ /// A Memory storing names in lower case and reaching each by any
1669
+ /// case, as a host that folds case does.
1670
+ struct FoldsCase(Memory);
1671
+
1672
+ impl Filesystem for FoldsCase {
1673
+ holds_no_links!();
1674
+ never_repeats_identities!();
1675
+ is_shared!();
1676
+
1677
+ fn open(
1678
+ &self,
1679
+ path: &str,
1680
+ options: &OpenOptions,
1681
+ ) -> Result<Box<dyn File>, FilesystemError> {
1682
+ self.0.open(&path.to_lowercase(), options)
1683
+ }
1684
+
1685
+ fn metadata(&self, path: &str) -> Result<Metadata, FilesystemError> {
1686
+ self.0.metadata(&path.to_lowercase())
1687
+ }
1688
+
1689
+ fn stored_path(&self, path: &str) -> Result<String, FilesystemError> {
1690
+ Ok(path.to_lowercase())
1691
+ }
1692
+
1693
+ fn read_dir(&self, path: &str) -> Result<Vec<DirEntry>, FilesystemError> {
1694
+ self.0.read_dir(&path.to_lowercase())
1695
+ }
1696
+
1697
+ fn create_dir(&self, path: &str) -> Result<(), FilesystemError> {
1698
+ self.0.create_dir(&path.to_lowercase())
1699
+ }
1700
+
1701
+ fn remove_dir(&self, path: &str) -> Result<(), FilesystemError> {
1702
+ self.0.remove_dir(&path.to_lowercase())
1703
+ }
1704
+
1705
+ fn remove_file(&self, path: &str) -> Result<(), FilesystemError> {
1706
+ self.0.remove_file(&path.to_lowercase())
1707
+ }
1708
+
1709
+ fn rename(&self, from: &str, to: &str, rename: Rename) -> Result<(), FilesystemError> {
1710
+ self.0
1711
+ .rename(&from.to_lowercase(), &to.to_lowercase(), rename)
1712
+ }
1713
+ }
1714
+
1715
+ // @behavior FS-083
1716
+ #[test]
1717
+ fn an_overlay_spells_a_path_as_the_layer_showing_it_stores_it() {
1718
+ let overlay = Overlay::new()
1719
+ .layer(Arc::new(Memory::new()), Access::ReadWrite)
1720
+ .layer(
1721
+ Arc::new(FoldsCase([("private/secret", "x")].into_iter().collect())),
1722
+ Access::Read,
1723
+ );
1724
+ assert_eq!(
1725
+ (
1726
+ overlay.stored_path("PRIVATE/SECRET"),
1727
+ overlay.stored_path("PRIVATE/New")
1728
+ ),
1729
+ (
1730
+ Ok("private/secret".to_string()),
1731
+ Ok("private/New".to_string())
1732
+ ),
1733
+ "a path asked of Overlay::stored_path in another case, shown by a Layer folding case, \
1734
+ must come back as that Layer stores each name, a new name kept as given"
1735
+ );
1736
+ }
1737
+
1738
+ // A whiteout the Layer cannot read would otherwise count as absent and
1739
+ // let the lower file show.
1740
+ // @behavior FS-079
1741
+ #[test]
1742
+ fn a_layer_that_cannot_read_its_markers_fails_the_lookup() {
1743
+ let top: Memory = [(".wh.file", "")].into_iter().collect();
1744
+ let overlay = Overlay::new()
1745
+ .layer(Arc::new(ReadsNoMarker(top)), Access::Read)
1746
+ .layer(memory_with("file", "hidden"), Access::Read);
1747
+ assert!(
1748
+ matches!(overlay.metadata("file"), Err(FilesystemError::Other(_))),
1749
+ "a file looked up through Overlay::metadata, beneath a Layer that cannot read the \
1750
+ whiteout over it, must fail the lookup rather than show the file"
1751
+ );
1752
+ }
1753
+
1754
+ // @behavior FS-080
1755
+ #[test]
1756
+ fn a_file_gone_during_a_rename_answers_not_found() {
1757
+ let lower = Vanishes {
1758
+ memory: [("file", "lower")].into_iter().collect(),
1759
+ seen: AtomicBool::new(false),
1760
+ };
1761
+ let overlay = Overlay::new()
1762
+ .layer(Arc::new(Memory::new()), Access::ReadWrite)
1763
+ .layer(Arc::new(lower), Access::Read);
1764
+ assert_eq!(
1765
+ overlay.rename("file", "moved", Rename::Any),
1766
+ Err(FilesystemError::NotFound),
1767
+ "a file renamed through Overlay::rename that its Layer loses during the call must \
1768
+ answer not found"
1769
+ );
1770
+ }
1771
+
1772
+ // The upper Layer's `dir/sub` stands where the lower Layer holds a
1773
+ // file `dir`, so the merge below it meets a file on the way.
1774
+ // @behavior FS-078
1775
+ #[test]
1776
+ fn a_directory_over_a_lower_file_on_its_way_reads_as_a_directory() {
1777
+ let overlay = Overlay::new()
1778
+ .layer(memory_with("dir/sub/file", "upper"), Access::ReadWrite)
1779
+ .layer(memory_with("dir", "lower"), Access::Read);
1780
+ assert_eq!(
1781
+ (
1782
+ overlay.metadata("dir/sub").map(|metadata| metadata.file_type),
1783
+ read(&overlay, "dir/sub/file")
1784
+ ),
1785
+ (Ok(FileType::Directory), b"upper".to_vec()),
1786
+ "a directory looked up through Overlay::metadata above a lower Layer holding a file on \
1787
+ its path must read as a directory, as its files open"
1788
+ );
1789
+ }
1790
+
1791
+ // The writable Layer refuses each change only after the Overlay has
1792
+ // cleared the whiteout from `dir`, so only putting it back keeps the
1793
+ // read-only Layer's `dir/file` hidden.
1794
+ // @behavior FS-063
1795
+ #[test]
1796
+ fn a_refused_change_leaves_what_a_whiteout_hid_hidden() {
1797
+ let (overlay, _) = overlay_on(Arc::new(HoldsDirectories(Memory::new())));
1798
+ overlay
1799
+ .remove_file("dir/file")
1800
+ .expect("the file must be removed");
1801
+ overlay
1802
+ .create_dir("new")
1803
+ .expect("the directory must be made");
1804
+ let refused = (
1805
+ overlay.rename("new", "dir", Rename::Directory),
1806
+ overlay.remove_dir("dir"),
1807
+ );
1808
+ assert_eq!(
1809
+ (refused, overlay.metadata("dir/file").err()),
1810
+ (
1811
+ (
1812
+ Err(FilesystemError::PermissionDenied),
1813
+ Err(FilesystemError::PermissionDenied)
1814
+ ),
1815
+ Some(FilesystemError::NotFound)
1816
+ ),
1817
+ "a rename onto and a removal of a directory whose writable copy holds only a whiteout, \
1818
+ refused by the Layer through an Overlay, must leave the whited-out file hidden"
1819
+ );
1820
+ }
1821
+
1822
+ fn trickles(refuses_close: bool) -> Arc<Trickles> {
1823
+ Arc::new(Trickles {
1824
+ memory: Memory::new(),
1825
+ refuses_close,
1826
+ })
1827
+ }
1828
+
1829
+ // @behavior FS-054
1830
+ #[test]
1831
+ fn a_copy_up_through_a_layer_writing_in_parts_copies_the_whole_file() {
1832
+ let upper = trickles(false);
1833
+ let (overlay, _) = overlay_on(upper.clone());
1834
+ overlay
1835
+ .open(
1836
+ "dir/file",
1837
+ &OpenOptions {
1838
+ write: true,
1839
+ ..OpenOptions::default()
1840
+ },
1841
+ )
1842
+ .expect("the open must copy the lower file up");
1843
+ assert_eq!(
1844
+ read(&upper.memory, "dir/file"),
1845
+ b"lower".to_vec(),
1846
+ "a write open through Overlay::open must copy the whole lower file up into a Layer \
1847
+ whose writes land a few bytes at a time"
1848
+ );
1849
+ }
1850
+
1851
+ // @behavior FS-055
1852
+ #[test]
1853
+ fn a_copy_up_whose_close_a_layer_refuses_leaves_nothing_behind() {
1854
+ let upper = trickles(true);
1855
+ let (overlay, _) = overlay_on(upper.clone());
1856
+ assert_eq!(
1857
+ (
1858
+ write(&overlay, "dir/file", b"x"),
1859
+ upper.metadata("dir/file"),
1860
+ read(&overlay, "dir/file")
1861
+ ),
1862
+ (
1863
+ Err(FilesystemError::PermissionDenied),
1864
+ Err(FilesystemError::NotFound),
1865
+ b"lower".to_vec()
1866
+ ),
1867
+ "a write through Overlay::open whose copy up the writable Layer refuses to close must \
1868
+ leave no copy hiding the lower file"
1869
+ );
1870
+ }
1871
+
1872
+ /// Counts each wait its Overlay's lock makes through it.
1873
+ struct CountsWaits(AtomicUsize);
1874
+
1875
+ impl Blocking for CountsWaits {
1876
+ fn wait(&self, wait: &mut dyn FnMut()) {
1877
+ self.0.fetch_add(1, Ordering::SeqCst);
1878
+ wait();
1879
+ }
1880
+ }
1881
+
1882
+ // @behavior FS-039
1883
+ #[test]
1884
+ fn a_contended_overlay_waits_through_its_blocking() {
1885
+ let counts = Arc::new(CountsWaits(AtomicUsize::new(0)));
1886
+ let overlay = Overlay::new()
1887
+ .layer(memory_with("note", "x"), Access::Read)
1888
+ .with_blocking(counts.clone());
1889
+ let held = overlay.lock.hold().expect("the lock must be free");
1890
+ let other = overlay.clone();
1891
+ let waiter = thread::spawn(move || other.metadata("note").map(|m| m.file_type));
1892
+ while counts.0.load(Ordering::SeqCst) == 0 {
1893
+ thread::sleep(Duration::from_millis(1));
1894
+ }
1895
+ drop(held);
1896
+ assert_eq!(
1897
+ (
1898
+ waiter.join().expect("the waiter must not panic"),
1899
+ counts.0.load(Ordering::SeqCst)
1900
+ ),
1901
+ (Ok(FileType::File), 1),
1902
+ "a call through an Overlay whose lock another thread holds must wait through the \
1903
+ Blocking given by Overlay::with_blocking, then answer"
1904
+ );
1905
+ }
1906
+
1907
+ // Copying up makes the directory in the writable Layer, whose own
1908
+ // identity differs, so only reporting the lower Layer's keeps it.
1909
+ // @behavior FS-072
1910
+ #[test]
1911
+ fn an_overlay_keeps_a_directorys_identity_when_a_write_copies_it_up() {
1912
+ let lower: Arc<dyn Filesystem> =
1913
+ Arc::new([("dir/a.txt", "a")].into_iter().collect::<Memory>());
1914
+ let overlay = Overlay::new()
1915
+ .layer(Arc::new(Memory::new()), Access::ReadWrite)
1916
+ .layer(lower, Access::Read);
1917
+ let identity = |overlay: &Overlay| {
1918
+ let metadata = overlay.metadata("dir").expect("the directory must stand");
1919
+ (metadata.dev, metadata.ino)
1920
+ };
1921
+ let before = identity(&overlay);
1922
+ overwrite(&overlay, "dir/b.txt", b"b");
1923
+ assert_eq!(
1924
+ (identity(&overlay) == before, before.1 != 0),
1925
+ (true, true),
1926
+ "a directory of a read-only Layer copied up by a write through an Overlay's open must \
1927
+ keep the identity Overlay::metadata reported before"
1928
+ );
1929
+ }
1930
+
1931
+ // @behavior FS-073
1932
+ #[test]
1933
+ fn an_overlay_names_a_directory_alike_whether_a_link_at_its_end_is_followed() {
1934
+ let layer = |name: &str| -> Arc<dyn Filesystem> {
1935
+ Arc::new(
1936
+ [(format!("dir/{name}"), "x")]
1937
+ .into_iter()
1938
+ .collect::<Memory>(),
1939
+ )
1940
+ };
1941
+ let overlay = Overlay::new()
1942
+ .layer(layer("upper"), Access::ReadWrite)
1943
+ .layer(layer("lower"), Access::Read);
1944
+ let identity = |metadata: Result<Metadata, FilesystemError>| {
1945
+ let metadata = metadata.expect("the directory must stand");
1946
+ (metadata.dev, metadata.ino)
1947
+ };
1948
+ assert_eq!(
1949
+ identity(overlay.symlink_metadata("dir")),
1950
+ identity(overlay.metadata("dir")),
1951
+ "a directory two Layers hold, looked up through Overlay::symlink_metadata, must \
1952
+ report the identity Overlay::metadata does"
1953
+ );
1954
+ }
1955
+ }