kobako 0.29.0 → 0.30.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 (201) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +1 -1
  3. data/CHANGELOG.md +217 -10
  4. data/Cargo.lock +139 -141
  5. data/README.md +96 -574
  6. data/ROADMAP.md +2 -1
  7. data/crates/kobako-runtime/CHANGELOG.md +269 -1
  8. data/crates/kobako-runtime/Cargo.toml +25 -15
  9. data/crates/kobako-runtime/README.md +7 -3
  10. data/crates/kobako-runtime/src/dispatch.rs +1 -1
  11. data/crates/kobako-runtime/src/endpoint/buffer.rs +192 -0
  12. data/crates/kobako-runtime/src/endpoint/capture.rs +115 -0
  13. data/crates/kobako-runtime/src/endpoint/dispatch.rs +253 -0
  14. data/crates/kobako-runtime/src/endpoint/invoke.rs +194 -0
  15. data/crates/kobako-runtime/src/endpoint/memory_cap.rs +238 -0
  16. data/crates/kobako-runtime/src/endpoint.rs +166 -0
  17. data/crates/kobako-runtime/src/filesystem/bind/fid.rs +82 -0
  18. data/crates/kobako-runtime/src/filesystem/{directory/spelling.rs → bind/lookup.rs} +91 -49
  19. data/crates/kobako-runtime/src/filesystem/bind/walk.rs +249 -0
  20. data/crates/kobako-runtime/src/filesystem/bind.rs +2665 -0
  21. data/crates/kobako-runtime/src/filesystem/overlay/file.rs +89 -0
  22. data/crates/kobako-runtime/src/filesystem/overlay/inode.rs +106 -0
  23. data/crates/kobako-runtime/src/filesystem/overlay/path.rs +285 -0
  24. data/crates/kobako-runtime/src/filesystem/overlay/stack/copy_up.rs +135 -0
  25. data/crates/kobako-runtime/src/filesystem/overlay/stack/dir.rs +263 -0
  26. data/crates/kobako-runtime/src/filesystem/overlay/stack/namei.rs +135 -0
  27. data/crates/kobako-runtime/src/filesystem/overlay/stack/readdir.rs +57 -0
  28. data/crates/kobako-runtime/src/filesystem/overlay/stack.rs +227 -734
  29. data/crates/kobako-runtime/src/filesystem/overlay.rs +2649 -1135
  30. data/crates/kobako-runtime/src/filesystem/path.rs +2 -41
  31. data/crates/kobako-runtime/src/filesystem/pipe.rs +221 -0
  32. data/crates/kobako-runtime/src/filesystem/rdir.rs +101 -0
  33. data/crates/kobako-runtime/src/filesystem/tmpfs/tmpfile.rs +32 -0
  34. data/crates/kobako-runtime/src/filesystem/tmpfs/whiteout.rs +85 -0
  35. data/crates/kobako-runtime/src/filesystem/tmpfs/xattr.rs +22 -0
  36. data/crates/kobako-runtime/src/filesystem/tmpfs.rs +1985 -0
  37. data/crates/kobako-runtime/src/filesystem/wrap.rs +224 -0
  38. data/crates/kobako-runtime/src/filesystem.rs +21 -387
  39. data/crates/kobako-runtime/src/hashtable.rs +44 -0
  40. data/crates/kobako-runtime/src/lib.rs +11 -6
  41. data/crates/kobako-runtime/src/locking/seqlock.rs +66 -0
  42. data/crates/kobako-runtime/src/locking.rs +218 -0
  43. data/crates/kobako-runtime/src/mount.rs +31 -104
  44. data/crates/kobako-runtime/src/profile.rs +137 -1
  45. data/crates/kobako-runtime/src/sched.rs +210 -0
  46. data/crates/kobako-runtime/src/security.rs +696 -0
  47. data/crates/kobako-runtime/src/vfs/attr.rs +162 -0
  48. data/crates/kobako-runtime/src/vfs/cred.rs +334 -0
  49. data/crates/kobako-runtime/src/vfs/dentry.rs +1412 -0
  50. data/crates/kobako-runtime/src/vfs/errno.rs +153 -0
  51. data/crates/kobako-runtime/src/vfs/file.rs +1915 -0
  52. data/crates/kobako-runtime/src/vfs/inode.rs +429 -0
  53. data/crates/kobako-runtime/src/vfs/kern_path.rs +217 -0
  54. data/crates/kobako-runtime/src/vfs/mount.rs +59 -0
  55. data/crates/kobako-runtime/src/vfs/namei.rs +5708 -0
  56. data/crates/kobako-runtime/src/vfs/openat2.rs +116 -0
  57. data/crates/kobako-runtime/src/vfs/path.rs +64 -0
  58. data/crates/kobako-runtime/src/vfs/readdir.rs +163 -0
  59. data/crates/kobako-runtime/src/vfs/stat.rs +157 -0
  60. data/crates/kobako-runtime/src/vfs/super_block.rs +122 -0
  61. data/crates/kobako-runtime/src/vfs/utimes.rs +289 -0
  62. data/crates/kobako-runtime/src/vfs.rs +60 -0
  63. data/crates/kobako-runtime/tests/linux_baseline/calls.rs +171 -0
  64. data/crates/kobako-runtime/tests/linux_baseline/concurrent.rs +276 -0
  65. data/crates/kobako-runtime/tests/linux_baseline/kobako.rs +323 -0
  66. data/crates/kobako-runtime/tests/linux_baseline/linux-baseline-expected.txt +16 -0
  67. data/crates/kobako-runtime/tests/linux_baseline/linux.rs +449 -0
  68. data/crates/kobako-runtime/tests/linux_baseline/main.rs +611 -0
  69. data/crates/kobako-runtime/tests/linux_baseline/permissions.rs +236 -0
  70. data/crates/kobako-transport/CHANGELOG.md +8 -2
  71. data/crates/kobako-transport/Cargo.toml +3 -3
  72. data/crates/kobako-transport/README.md +6 -5
  73. data/crates/kobako-transport/src/envelope/bytes.rs +33 -25
  74. data/crates/kobako-transport/src/envelope/call.rs +5 -5
  75. data/crates/kobako-transport/src/envelope/compile_reply.rs +2 -2
  76. data/crates/kobako-transport/src/envelope/error_record.rs +3 -3
  77. data/crates/kobako-transport/src/envelope/fault.rs +2 -2
  78. data/crates/kobako-transport/src/envelope/outcome.rs +3 -3
  79. data/crates/kobako-transport/src/envelope/preamble.rs +12 -12
  80. data/crates/kobako-transport/src/envelope/reply.rs +2 -2
  81. data/crates/kobako-transport/src/envelope/request.rs +2 -2
  82. data/crates/kobako-transport/src/envelope/run.rs +1 -1
  83. data/crates/kobako-transport/src/envelope/unsupported.rs +2 -2
  84. data/crates/kobako-transport/src/envelope.rs +2 -2
  85. data/crates/kobako-wasmtime/CHANGELOG.md +154 -8
  86. data/crates/kobako-wasmtime/Cargo.toml +15 -14
  87. data/crates/kobako-wasmtime/README.md +9 -5
  88. data/crates/kobako-wasmtime/src/abi.rs +12 -20
  89. data/crates/kobako-wasmtime/src/ambient.rs +1 -2
  90. data/crates/kobako-wasmtime/src/cache.rs +5 -5
  91. data/crates/kobako-wasmtime/src/config.rs +22 -7
  92. data/crates/kobako-wasmtime/src/descriptors.rs +133 -100
  93. data/crates/kobako-wasmtime/src/driver.rs +29 -96
  94. data/crates/kobako-wasmtime/src/exports.rs +5 -16
  95. data/crates/kobako-wasmtime/src/guest.rs +38 -0
  96. data/crates/kobako-wasmtime/src/imports.rs +14 -8
  97. data/crates/kobako-wasmtime/src/instance_pre.rs +3 -3
  98. data/crates/kobako-wasmtime/src/invocation.rs +26 -46
  99. data/crates/kobako-wasmtime/src/lib.rs +1 -4
  100. data/crates/kobako-wasmtime/src/limiter.rs +6 -230
  101. data/crates/kobako-wasmtime/src/preview1/differential/mount.rs +69 -46
  102. data/crates/kobako-wasmtime/src/preview1/differential.rs +121 -7
  103. data/crates/kobako-wasmtime/src/preview1/errno.rs +74 -206
  104. data/crates/kobako-wasmtime/src/preview1/fd.rs +1248 -500
  105. data/crates/kobako-wasmtime/src/preview1/memory.rs +117 -150
  106. data/crates/kobako-wasmtime/src/preview1/mount_view/wrap.rs +211 -0
  107. data/crates/kobako-wasmtime/src/preview1/mount_view.rs +1939 -672
  108. data/crates/kobako-wasmtime/src/preview1/path.rs +230 -349
  109. data/crates/kobako-wasmtime/src/preview1/poll.rs +148 -100
  110. data/crates/kobako-wasmtime/src/preview1/sock.rs +6 -23
  111. data/crates/kobako-wasmtime/src/preview1/testsuite-expected.txt +6 -0
  112. data/crates/kobako-wasmtime/src/preview1/testsuite.rs +157 -0
  113. data/crates/kobako-wasmtime/src/preview1.rs +464 -413
  114. data/crates/kobako-wasmtime/src/trap.rs +9 -9
  115. data/crates/kobako-wasmtime/src/wasi.rs +53 -37
  116. data/crates/kobako-wasmtime/tests/{guest_file.rs → integration/guest_file.rs} +232 -201
  117. data/crates/kobako-wasmtime/tests/integration/main.rs +29 -0
  118. data/crates/kobako-wasmtime/tests/{preamble_flags.rs → integration/preamble_flags.rs} +1 -1
  119. data/crates/kobako-wasmtime/tests/{snippet_compile.rs → integration/snippet_compile.rs} +1 -1
  120. data/crates/kobako-wasmtime/tests/{unanswered_call.rs → integration/unanswered_call.rs} +1 -1
  121. data/crates/kobako-wasmtime/tests/{unsupported_call.rs → integration/unsupported_call.rs} +1 -1
  122. data/crates/kobako-wasmtime/witx/typenames.witx +750 -0
  123. data/crates/kobako-wasmtime/witx/wasi_snapshot_preview1.witx +441 -0
  124. data/data/kobako.wasm +0 -0
  125. data/ext/kobako/Cargo.toml +1 -1
  126. data/ext/kobako/src/runtime/bridge.rs +1 -1
  127. data/ext/kobako/src/runtime/errors.rs +1 -1
  128. data/ext/kobako/src/runtime/escape.rs +28 -14
  129. data/ext/kobako/src/runtime/filesystem/grants.rs +21 -5
  130. data/ext/kobako/src/runtime/filesystem/ruby.rs +464 -156
  131. data/ext/kobako/src/runtime/filesystem.rs +109 -137
  132. data/ext/kobako/src/runtime/gvl.rs +43 -8
  133. data/ext/kobako/src/runtime/snapshot.rs +1 -1
  134. data/ext/kobako/src/runtime.rs +6 -1
  135. data/lib/kobako/catalog/extensions.rb +5 -5
  136. data/lib/kobako/catalog/flags.rb +2 -2
  137. data/lib/kobako/catalog/handles.rb +6 -6
  138. data/lib/kobako/catalog/mount.rb +2 -10
  139. data/lib/kobako/catalog/services.rb +2 -2
  140. data/lib/kobako/catalog/snippets.rb +7 -7
  141. data/lib/kobako/catalog.rb +1 -1
  142. data/lib/kobako/codec/decoder.rb +10 -10
  143. data/lib/kobako/codec/ext_types.rb +3 -5
  144. data/lib/kobako/codec/handle_walk.rb +4 -6
  145. data/lib/kobako/codec/nesting.rb +7 -7
  146. data/lib/kobako/codec/utils.rb +1 -1
  147. data/lib/kobako/codec/value_budget.rb +3 -3
  148. data/lib/kobako/context.rb +5 -5
  149. data/lib/kobako/filesystem.rb +44 -13
  150. data/lib/kobako/payload/arguments.rb +2 -2
  151. data/lib/kobako/pool.rb +4 -4
  152. data/lib/kobako/sandbox.rb +28 -28
  153. data/lib/kobako/sandbox_options.rb +2 -3
  154. data/lib/kobako/snippet/bytecode.rb +1 -1
  155. data/lib/kobako/snippet/source.rb +1 -1
  156. data/lib/kobako/transport/dispatcher.rb +5 -5
  157. data/lib/kobako/transport/exposure.rb +2 -2
  158. data/lib/kobako/transport/reflection.rb +7 -7
  159. data/lib/kobako/transport/run.rb +5 -5
  160. data/lib/kobako/transport/yielder.rb +1 -1
  161. data/lib/kobako/version.rb +1 -1
  162. data/lib/kobako.rb +34 -0
  163. data/release-please-config.json +1 -0
  164. data/sig/kobako/catalog/extensions.rbs +3 -3
  165. data/sig/kobako/catalog/handles.rbs +3 -3
  166. data/sig/kobako/catalog/mount.rbs +0 -4
  167. data/sig/kobako/catalog/services.rbs +1 -1
  168. data/sig/kobako/catalog/snippets.rbs +3 -3
  169. data/sig/kobako/catalog.rbs +1 -1
  170. data/sig/kobako/codec/decoder.rbs +1 -1
  171. data/sig/kobako/codec/handle_walk.rbs +1 -1
  172. data/sig/kobako/codec/nesting.rbs +2 -2
  173. data/sig/kobako/codec/utils.rbs +1 -1
  174. data/sig/kobako/codec/value_budget.rbs +1 -1
  175. data/sig/kobako/codec.rbs +1 -2
  176. data/sig/kobako/context.rbs +1 -1
  177. data/sig/kobako/filesystem.rbs +19 -14
  178. data/sig/kobako/guest_yielder.rbs +1 -1
  179. data/sig/kobako/payload/arguments.rbs +1 -1
  180. data/sig/kobako/pool.rbs +2 -2
  181. data/sig/kobako/runtime.rbs +1 -1
  182. data/sig/kobako/sandbox.rbs +3 -5
  183. data/sig/kobako/sandbox_options.rbs +1 -1
  184. data/sig/kobako/transport/dispatcher.rbs +2 -2
  185. data/sig/kobako/transport/exposure.rbs +1 -1
  186. data/sig/kobako/transport/reflection.rbs +2 -2
  187. data/sig/kobako/transport/run.rbs +3 -3
  188. data/sig/kobako/transport.rbs +1 -1
  189. metadata +67 -19
  190. data/crates/kobako-runtime/src/filesystem/directory.rs +0 -1082
  191. data/crates/kobako-runtime/src/filesystem/memory.rs +0 -1100
  192. data/crates/kobako-runtime/src/filesystem/overlay/exclusive.rs +0 -88
  193. data/crates/kobako-runtime/src/filesystem/temporary.rs +0 -167
  194. data/crates/kobako-wasmtime/src/capture.rs +0 -96
  195. data/crates/kobako-wasmtime/src/dispatch.rs +0 -125
  196. data/crates/kobako-wasmtime/src/guest_mem.rs +0 -271
  197. data/crates/kobako-wasmtime/src/io.rs +0 -89
  198. data/crates/kobako-wasmtime/src/preview1/args.rs +0 -60
  199. data/crates/kobako-wasmtime/src/preview1/beneath.rs +0 -188
  200. /data/crates/kobako-wasmtime/tests/{compile_posture.rs → integration/compile_posture.rs} +0 -0
  201. /data/crates/kobako-wasmtime/tests/{support → integration/support}/mod.rs +0 -0
@@ -1,146 +1,425 @@
1
- //! `Overlay`: Layers stacked into one Filesystem, each read-only or
2
- //! writable on its own.
1
+ //! `Overlay`: read-only lower Layers under an upper Tmpfs, stacked into
2
+ //! one Filesystem, as Linux overlayfs stacks a mount's layers.
3
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;
4
+ //! The upper must be a Tmpfs, as gVisor takes only an upper that holds
5
+ //! its marks. A removal hides what a lower Layer still shows with a
6
+ //! whiteout, and a directory made again where one was removed is marked
7
+ //! opaque, each as Linux marks it, in the upper, so they last as long as
8
+ //! it does. The VFS holds each directory a call changes, as Linux's
9
+ //! `i_rwsem` does above overlayfs, and a copy up holds the lock of what
10
+ //! it copies, so a change across several Layers is never seen half made.
11
+
12
+ mod file;
13
+ mod inode;
14
+ mod path;
13
15
  mod stack;
14
16
 
17
+ use std::any::Any;
15
18
  use std::sync::Arc;
16
19
 
17
- pub use exclusive::Blocking;
18
-
20
+ use super::rdir::Rdir;
19
21
  use super::{
20
- Access, DirEntry, File, Filesystem, FilesystemError, Hold, Metadata, OpenOptions, Rename,
22
+ DentryOperations, FileOperations, FileSystemType, FileType, Filesystem, FilesystemError, Iattr,
23
+ InodeOperations, Kstat, Lookup, OpenHow, RenameFlag, Tmpfs,
21
24
  };
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.
25
+ use crate::vfs::{
26
+ generic_file_llseek, generic_read_dir, notify_change, Dentry, DirContext, Inode, Whence,
27
+ NAME_MAX,
28
+ };
29
+ use inode::{OverlayDirInode, OverlayFileInode};
30
+ use path::OverlayPath;
31
+ use stack::OverlayFs;
32
+
33
+ /// Read-only lower Layers under an upper Tmpfs, stacked into one
34
+ /// Filesystem. Two Overlays sharing an upper are not serialized against
35
+ /// each other, as overlayfs leaves two mounts sharing an upper directory
36
+ /// undefined.
37
+ ///
38
+ /// Each directory of the view keeps each Layer's directory it stands for
39
+ /// on its dentry, so it is reached wherever those directories move, as a
40
+ /// descriptor reaches its directory, and a directory opened beneath it is
41
+ /// this Overlay asked about that dentry. overlayfs leaves a change made to
42
+ /// a Layer outside it undefined; here such a change is the host's, and a
43
+ /// directory reached before it keeps the Layers' directories it holds.
29
44
  #[derive(Clone)]
30
45
  pub struct Overlay {
31
- stack: Stack,
32
- lock: Arc<Exclusive>,
46
+ fs: OverlayFs,
47
+ /// How many Overlays stack here, itself among them, as Linux counts
48
+ /// a super block's `s_stack_depth`.
49
+ stack_depth: usize,
50
+ }
51
+
52
+ /// The deepest Overlays may stack, as Linux's `FILESYSTEM_MAX_STACK_DEPTH`
53
+ /// bounds it, so a call through them cannot run out of stack.
54
+ const MAX_STACK_DEPTH: usize = 2;
55
+
56
+ /// The most lower Layers an Overlay takes, as overlayfs's `OVL_MAX_STACK`.
57
+ const MAX_LOWER_LAYERS: usize = 500;
58
+
59
+ impl Overlay {
60
+ /// An Overlay of `lower`, topmost first, under `upper`. Each Layer is
61
+ /// asked through that Filesystem's dentries, as overlayfs asks each
62
+ /// layer by its own lookup. As overlayfs's mount does, it refuses no
63
+ /// lower Layer, a read-only view of fewer than two, too many lower
64
+ /// Layers, a stack too deep, and one instance as two Layers, as
65
+ /// overlayfs answers overlapping layers; as gVisor's does, it refuses an
66
+ /// upper that is no Tmpfs.
67
+ pub fn new(
68
+ lower: Vec<Arc<Filesystem>>,
69
+ upper: Option<Arc<Filesystem>>,
70
+ ) -> Result<Self, FilesystemError> {
71
+ let layers: Vec<_> = lower.iter().chain(&upper).collect();
72
+ let overlapping = layers
73
+ .iter()
74
+ .enumerate()
75
+ .any(|(at, layer)| layers[..at].iter().any(|seen| Arc::ptr_eq(seen, layer)));
76
+ if overlapping {
77
+ return Err(FilesystemError::InvalidInput);
78
+ }
79
+ let too_few = if upper.is_some() { 1 } else { 2 };
80
+ let upper_tmpfs = upper.as_ref().is_none_or(|upper| {
81
+ upper
82
+ .root()
83
+ .and_then(|root| root.inode())
84
+ .is_some_and(|root| (root.ops().as_ref() as &dyn Any).is::<Tmpfs>())
85
+ });
86
+ let stack_depth = 1 + lower
87
+ .iter()
88
+ .filter_map(|layer| layer.root()?.inode())
89
+ .filter_map(|root| {
90
+ let overlay = (root.ops().as_ref() as &dyn Any).downcast_ref::<Self>()?;
91
+ Some(overlay.stack_depth)
92
+ })
93
+ .max()
94
+ .unwrap_or(0);
95
+ if !(too_few..=MAX_LOWER_LAYERS).contains(&lower.len())
96
+ || !upper_tmpfs
97
+ || stack_depth > MAX_STACK_DEPTH
98
+ {
99
+ return Err(FilesystemError::InvalidInput);
100
+ }
101
+ Ok(Self {
102
+ fs: OverlayFs::new(lower, upper)?,
103
+ stack_depth,
104
+ })
105
+ }
33
106
  }
34
107
 
35
- impl Default for Overlay {
36
- fn default() -> Self {
37
- Self::new()
108
+ /// The Layer's dentry the entry `dentry` answers through, and the inode the view
109
+ /// recorded, as `ovl_getattr` asks the real path.
110
+ fn real_of(dentry: &Dentry) -> Option<(Arc<Dentry>, Arc<Inode>)> {
111
+ let inode = dentry.inode()?;
112
+ let real = OverlayFileInode::of(&inode)?.real();
113
+ Some((real, inode))
114
+ }
115
+
116
+ /// Whether `real`, a Layer's dentry, still names what it recorded, as
117
+ /// `ovl_revalidate_real` asks it.
118
+ fn ovl_revalidate_real(real: &Dentry) -> Result<bool, FilesystemError> {
119
+ match real.ops() {
120
+ Some(ops) => ops.revalidate(real.parent().inode_ops()?.as_ref(), real),
121
+ None => Ok(true),
38
122
  }
39
123
  }
40
124
 
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))),
125
+ /// A name stays valid while each Layer's dentry its entry keeps does, as
126
+ /// `ovl_dentry_revalidate` asks each real dentry, and while a directory still
127
+ /// stands for what each Layer holds there, as `ovl_clear_empty` drops a dentry
128
+ /// whose upper no longer matches.
129
+ impl DentryOperations for Overlay {
130
+ fn revalidate(
131
+ &self,
132
+ _dir: &dyn InodeOperations,
133
+ dentry: &Dentry,
134
+ ) -> Result<bool, FilesystemError> {
135
+ let Some(inode) = dentry.inode() else {
136
+ return Ok(matches!(self.lookup(dentry)?, Lookup::Absent));
137
+ };
138
+ if let Some(kept) = OverlayFileInode::of(&inode) {
139
+ return ovl_revalidate_real(&kept.real());
47
140
  }
141
+ let Some(kept) = OverlayDirInode::of(&inode) else {
142
+ return Ok(true);
143
+ };
144
+ let shown = kept.level();
145
+ for layer in shown.iter().flatten() {
146
+ if !ovl_revalidate_real(layer.dentry())? {
147
+ return Ok(false);
148
+ }
149
+ }
150
+ let name = dentry.name();
151
+ let above = self.fs.parent(dentry)?.level();
152
+ Ok(above.iter().zip(&shown).all(|pair| match pair {
153
+ (Some(layer), Some(shown)) => layer.matches_real(&name, shown.dentry()),
154
+ _ => true,
155
+ }))
48
156
  }
49
157
 
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
158
+ /// A name goes once nothing holds it where any Layer behind it would let
159
+ /// its own go, since its `revalidate` answers only as those Layers do.
160
+ fn delete(&self, dentry: &Dentry) -> bool {
161
+ let Some(inode) = dentry.inode() else {
162
+ return false;
163
+ };
164
+ if let Some(kept) = OverlayFileInode::of(&inode) {
165
+ return kept.real().goes_when_unused();
166
+ }
167
+ OverlayDirInode::of(&inode).is_some_and(|kept| {
168
+ kept.level()
169
+ .iter()
170
+ .flatten()
171
+ .any(|layer| layer.dentry().goes_when_unused())
172
+ })
58
173
  }
174
+ }
59
175
 
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
- }
176
+ /// A file of the view, or an entry a Layer holds that is none of the others, as
177
+ /// `ovl_file_inode_operations` answers for one.
178
+ struct OverlayRegular(Overlay);
179
+
180
+ /// A link of the view, as `ovl_symlink_inode_operations` answers for one.
181
+ struct OverlayLink(Overlay);
182
+
183
+ impl Overlay {
184
+ /// The operations an entry of `kind` answers through.
185
+ fn ops_of(&self, kind: FileType) -> Arc<dyn InodeOperations> {
186
+ match kind {
187
+ FileType::Dir => Arc::new(self.clone()),
188
+ FileType::Symlink => Arc::new(OverlayLink(self.clone())),
189
+ _ => Arc::new(OverlayRegular(self.clone())),
190
+ }
191
+ }
192
+
193
+ /// What the Layer the entry's name is found in reports, under the identity
194
+ /// the view recorded.
195
+ fn real_getattr(&self, dentry: &Dentry) -> Result<Kstat, FilesystemError> {
196
+ let (real, inode) = real_of(dentry).ok_or(FilesystemError::NotFound)?;
197
+ let found = real.getattr()?;
198
+ Ok(match inode.identity() {
199
+ Some(identity) => identity.onto(found),
200
+ None => found,
201
+ })
202
+ }
203
+ }
204
+
205
+ impl InodeOperations for OverlayRegular {
206
+ fn getattr(&self, dentry: &Dentry) -> Result<Kstat, FilesystemError> {
207
+ self.0.real_getattr(dentry)
208
+ }
209
+
210
+ /// Copied up first, as `ovl_setattr` does, so a lower Layer stays as it
211
+ /// was whatever its Filesystem would keep.
212
+ fn setattr(&self, dentry: &Dentry, attr: &Iattr) -> Result<(), FilesystemError> {
213
+ let overlay = &self.0;
214
+ overlay
215
+ .fs
216
+ .copy_up_file(&overlay.fs.parent(dentry)?, dentry, false)?;
217
+ let (real, _) = real_of(dentry).ok_or(FilesystemError::NotFound)?;
218
+ // The open's truncate never reached the upper's open, so the upper must
219
+ // empty the file itself, and the view's open file is none of the upper's,
220
+ // as `ovl_setattr` clears `ATTR_OPEN` and `ATTR_FILE`.
221
+ let held = real.inode_lock()?;
222
+ notify_change(
223
+ &real,
224
+ &held,
225
+ &Iattr {
226
+ open: false,
227
+ file: None,
228
+ ..*attr
229
+ },
230
+ )
231
+ }
232
+
233
+ fn open(
234
+ &self,
235
+ dentry: &Dentry,
236
+ how: &OpenHow,
237
+ ) -> Result<Box<dyn FileOperations>, FilesystemError> {
238
+ let fs = &self.0.fs;
239
+ fs.open(&fs.parent(dentry)?, dentry, how)
66
240
  }
67
241
  }
68
242
 
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)
243
+ impl InodeOperations for OverlayLink {
244
+ fn getattr(&self, dentry: &Dentry) -> Result<Kstat, FilesystemError> {
245
+ self.0.real_getattr(dentry)
73
246
  }
74
247
 
75
- fn metadata(&self, path: &str) -> Result<Metadata, FilesystemError> {
76
- let _held = self.lock.hold()?;
77
- self.stack.metadata(path)
248
+ /// Where the link points, as `ovl_get_link` asks the real dentry.
249
+ fn get_link(&self, dentry: &Dentry) -> Result<String, FilesystemError> {
250
+ let (real, _) = real_of(dentry).ok_or(FilesystemError::NotFound)?;
251
+ real.get_link()
78
252
  }
253
+ }
79
254
 
80
- fn symlink_metadata(&self, path: &str) -> Result<Metadata, FilesystemError> {
81
- let _held = self.lock.hold()?;
82
- self.stack.symlink_metadata(path)
255
+ impl FileSystemType for Overlay {
256
+ /// With no upper nothing can change, as `ovl_fill_super` sets `SB_RDONLY`.
257
+ /// Its root takes the identity its topmost Layer's root recorded, as
258
+ /// `ovl_get_root` reads a real root's `i_ino`, so making the view asks no
259
+ /// Layer anything.
260
+ fn fill_super(self: Arc<Self>, sb: &mut Filesystem) -> Result<(), FilesystemError> {
261
+ sb.set_default_d_op(self.clone());
262
+ if !self.fs.has_upper() {
263
+ sb.set_rdonly();
264
+ }
265
+ let root = Kstat::new(FileType::Dir, 0);
266
+ let root = match self.fs.root_identity() {
267
+ Some(identity) => identity.onto(root),
268
+ None => root,
269
+ };
270
+ let root_dir = self.fs.root_dir();
271
+ let root = Inode::new(&root, self);
272
+ inode::ovl_inode_init(&root, root_dir);
273
+ sb.d_make_root(root);
274
+ Ok(())
83
275
  }
276
+ }
84
277
 
85
- fn stored_path(&self, path: &str) -> Result<String, FilesystemError> {
86
- let _held = self.lock.hold()?;
87
- self.stack.stored_path(path)
278
+ /// A directory of the view, finding what a call is about through the dentry it
279
+ /// is handed, as `ovl_dir_inode_operations` answers for one.
280
+ impl InodeOperations for Overlay {
281
+ /// It reports its merged Layers.
282
+ fn getattr(&self, dentry: &Dentry) -> Result<Kstat, FilesystemError> {
283
+ self.fs.dir_getattr(&self.fs.dir(dentry)?.level())
284
+ }
285
+
286
+ /// Copied up first, as `ovl_setattr` does for any entry, so a lower Layer
287
+ /// stays as it was.
288
+ fn setattr(&self, dentry: &Dentry, attr: &Iattr) -> Result<(), FilesystemError> {
289
+ let upper = self.fs.upper_dir(&self.fs.dir(dentry)?)?;
290
+ let held = upper.dentry().inode_lock()?;
291
+ notify_change(upper.dentry(), &held, attr)
292
+ }
293
+
294
+ /// The entry as a Layer spells it, as `ovl_lookup` hands
295
+ /// `d_splice_alias` the inode it made, a name no Layer could hold refused
296
+ /// before any is asked, as it compares `namelen`.
297
+ fn lookup(&self, dentry: &Dentry) -> Result<Lookup, FilesystemError> {
298
+ let level = self.fs.parent(dentry)?.level();
299
+ let name = dentry.name();
300
+ if name.len() > NAME_MAX {
301
+ return Err(FilesystemError::InvalidFilename);
302
+ }
303
+ let Some(found) = self.fs.lookup_layer(&level, &name)? else {
304
+ return Ok(Lookup::Absent);
305
+ };
306
+ let stored_name = found.1.stored_name().to_string();
307
+ let inode = self
308
+ .fs
309
+ .lookup(&level, &stored_name, found, |kind| self.ops_of(kind))?;
310
+ match stored_name == name {
311
+ true => Ok(Lookup::Found(inode)),
312
+ false => Ok(Lookup::Spelled {
313
+ name: stored_name,
314
+ inode,
315
+ }),
316
+ }
317
+ }
318
+
319
+ fn open(
320
+ &self,
321
+ dentry: &Dentry,
322
+ _how: &OpenHow,
323
+ ) -> Result<Box<dyn FileOperations>, FilesystemError> {
324
+ let _real = self
325
+ .fs
326
+ .dir(dentry)?
327
+ .level()
328
+ .iter()
329
+ .flatten()
330
+ .map(OverlayPath::open_real)
331
+ .collect::<Result<_, _>>()?;
332
+ let fs = self.fs.clone();
333
+ Ok(Box::new(OverlayDir {
334
+ fs,
335
+ _real,
336
+ cache: Rdir::default(),
337
+ }))
88
338
  }
89
339
 
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)
340
+ /// What was made is found through the view's own lookup, as `ovl_instantiate`
341
+ /// records the inode for the real dentry.
342
+ fn create(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
343
+ self.fs.create(&self.fs.parent(dentry)?, &dentry.name())?;
344
+ match self.lookup(dentry)? {
345
+ Lookup::Found(inode) => dentry.add(Some(inode)),
346
+ _ => return Err(FilesystemError::NotFound),
347
+ }
348
+ Ok(())
93
349
  }
94
350
 
95
- fn hold_directory(&self, path: &str) -> Result<Option<Hold>, FilesystemError> {
96
- let _held = self.lock.hold()?;
97
- self.stack.hold_directory(path)
351
+ fn mkdir(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
352
+ self.fs.mkdir(&self.fs.parent(dentry)?, &dentry.name())
98
353
  }
99
354
 
100
- fn read_dir(&self, path: &str) -> Result<Vec<DirEntry>, FilesystemError> {
101
- let _held = self.lock.hold()?;
102
- self.stack.read_dir(path)
355
+ fn rmdir(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
356
+ self.fs.rmdir(&self.fs.parent(dentry)?, dentry)
103
357
  }
104
358
 
105
- fn create_dir(&self, path: &str) -> Result<(), FilesystemError> {
106
- let _held = self.lock.hold()?;
107
- self.stack.create_dir(path)
359
+ fn unlink(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
360
+ self.fs.unlink(&self.fs.parent(dentry)?, dentry)
108
361
  }
109
362
 
110
- fn remove_dir(&self, path: &str) -> Result<(), FilesystemError> {
111
- let _held = self.lock.hold()?;
112
- self.stack.remove_dir(path)
363
+ /// Both directories lie in one Overlay, however many directories of
364
+ /// it are open. Only its upper takes a flag, which it asks itself.
365
+ fn rename(
366
+ &self,
367
+ from: &Dentry,
368
+ _to_dir: &dyn InodeOperations,
369
+ to: &Dentry,
370
+ flag: Option<RenameFlag>,
371
+ ) -> Result<(), FilesystemError> {
372
+ if flag.is_some() {
373
+ return Err(FilesystemError::InvalidInput);
374
+ }
375
+ self.fs
376
+ .rename(&self.fs.parent(from)?, from, &self.fs.parent(to)?, to)
113
377
  }
378
+ }
379
+
380
+ /// A directory of the view, open: it merges its Layers each time it is
381
+ /// listed, finding them through the dentry it is handed, and holds each Layer's
382
+ /// directory open while it is, as `struct ovl_dir_file` holds its `realfile`.
383
+ struct OverlayDir {
384
+ fs: OverlayFs,
385
+ _real: Vec<Box<dyn FileOperations>>,
386
+ /// What a listing merged, held until it ends, as `ovl_dir_cache` holds it.
387
+ cache: Rdir,
388
+ }
114
389
 
115
- fn remove_file(&self, path: &str) -> Result<(), FilesystemError> {
116
- let _held = self.lock.hold()?;
117
- self.stack.remove_file(path)
390
+ impl FileOperations for OverlayDir {
391
+ fn llseek(
392
+ &mut self,
393
+ dentry: &Dentry,
394
+ pos: u64,
395
+ offset: i64,
396
+ whence: Whence,
397
+ ) -> Result<u64, FilesystemError> {
398
+ generic_file_llseek(dentry, pos, offset, whence)
118
399
  }
119
400
 
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)
401
+ fn read_iter(
402
+ &mut self,
403
+ _iocb: &mut super::Kiocb,
404
+ _buf: &mut [u8],
405
+ ) -> Result<usize, FilesystemError> {
406
+ generic_read_dir()
123
407
  }
124
408
 
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
- }))
409
+ fn iterate_shared(
410
+ &mut self,
411
+ dentry: &Dentry,
412
+ ctx: &mut DirContext<'_>,
413
+ ) -> Result<(), FilesystemError> {
414
+ let (fs, cache) = (&self.fs, &mut self.cache);
415
+ cache.iterate(dentry, ctx, || fs.dir_read_merged(&fs.dir(dentry)?.level()))
134
416
  }
135
417
 
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
- }))
418
+ /// Its Layers answer for what they hold, and its upper is a Tmpfs, so
419
+ /// nothing is left to make durable, as `ovl_dir_fsync` finds with no upper
420
+ /// of its own to sync.
421
+ fn fsync(&mut self, _datasync: bool) -> Result<(), FilesystemError> {
422
+ Ok(())
144
423
  }
145
424
  }
146
425
 
@@ -151,96 +430,173 @@ mod tests {
151
430
  use std::thread;
152
431
  use std::time::{Duration, Instant};
153
432
 
154
- use super::super::memory::Memory;
155
- use super::super::temporary::Temporary;
156
- use super::super::{FileType, Hold};
433
+ use super::super::tmpfs::Tmpfs;
434
+ use super::super::wrap::{Hook, Wrap};
435
+ use super::super::{FileType, Finish, Kiocb};
157
436
  use super::*;
437
+ use crate::vfs::kern_path::{kern_path, KernPath};
438
+ use crate::vfs::Mask;
158
439
 
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
- }
440
+ /// `filesystem` reached through the VFS.
441
+ fn at(filesystem: impl FileSystemType) -> KernPath {
442
+ kern_path(Arc::new(filesystem))
443
+ }
165
444
 
166
- fn canonicalize_beneath(
167
- &self,
168
- _base: &str,
169
- path: &str,
170
- ) -> Result<String, FilesystemError> {
171
- Ok(path.to_string())
172
- }
445
+ /// The Filesystem `filesystem` holds, reached through the VFS.
446
+ fn on<F: FileSystemType>(filesystem: &Arc<F>) -> KernPath {
447
+ kern_path(filesystem.clone())
448
+ }
173
449
 
174
- fn for_policy(&self) -> Option<Arc<dyn Filesystem>> {
175
- None
176
- }
177
- };
450
+ #[derive(Default)]
451
+ struct CountsLookups(AtomicUsize);
452
+
453
+ impl Hook for CountsLookups {
454
+ fn lookup(
455
+ &self,
456
+ inner: &dyn InodeOperations,
457
+ dentry: &Dentry,
458
+ ) -> Result<Lookup, FilesystemError> {
459
+ self.0.fetch_add(1, Ordering::SeqCst);
460
+ inner.lookup(dentry)
461
+ }
178
462
  }
179
463
 
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
- }
464
+ // A Filesystem written in Ruby pays for each ask and keeps no dentry, so a
465
+ // name found absent, which no Filesystem keeps, shows how often a Layer is
466
+ // asked by itself.
467
+ // @behavior FS-184
468
+ #[test]
469
+ fn a_lookup_asks_each_layer_once_for_a_name() {
470
+ let lower: Tmpfs = [("file", "x")].into_iter().collect();
471
+ let counts = Arc::new(CountsLookups::default());
472
+ let view = at(Overlay::new(
473
+ vec![
474
+ Filesystem::new(Arc::new(Wrap::new(Arc::new(lower), counts.clone())))
475
+ .expect("the instance must stand"),
476
+ ],
477
+ Some(Filesystem::new(Arc::new(Tmpfs::new())).expect("the instance must stand")),
478
+ )
479
+ .expect("the Overlay must stack"));
480
+ let asks = |path: &str| {
481
+ counts.0.store(0, Ordering::SeqCst);
482
+ let _ = view.vfs_lstat(path);
483
+ counts.0.load(Ordering::SeqCst)
186
484
  };
485
+ assert_eq!(
486
+ (asks("missing"), asks("file")),
487
+ (1, 1),
488
+ "a stat through an Overlay of a name its lower Layer lacks, and of one it holds, \
489
+ must ask that Layer once"
490
+ );
187
491
  }
188
492
 
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
- };
493
+ // Only the upper could refuse the name once the directory was copied up, so
494
+ // the directory standing nowhere in the upper is what shows no change began.
495
+ // @behavior FS-183
496
+ #[test]
497
+ fn a_name_past_name_max_answers_too_long_and_copies_nothing_up() {
498
+ let upper = Arc::new(Tmpfs::new());
499
+ let (view, _lower) = overlay_on(upper.clone());
500
+ let long = format!("dir/{}", "n".repeat(NAME_MAX + 1));
501
+ assert_eq!(
502
+ (
503
+ view.vfs_lstat(&long).err(),
504
+ view.vfs_mkdir(&long).err(),
505
+ on(&upper).vfs_lstat("upper/dir").err(),
506
+ ),
507
+ (
508
+ Some(FilesystemError::InvalidFilename),
509
+ Some(FilesystemError::InvalidFilename),
510
+ Some(FilesystemError::NotFound),
511
+ ),
512
+ "a stat and a make of a name past NAME_MAX in a lower directory, through an Overlay, \
513
+ must answer ENAMETOOLONG and leave that directory uncopied"
514
+ );
196
515
  }
197
516
 
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
- };
517
+ // @behavior FS-140
518
+ #[test]
519
+ fn a_file_with_nothing_to_flush_syncs() {
520
+ let lower: Tmpfs = [("file", "x")].into_iter().collect();
521
+ let upper = Tmpfs::new();
522
+ let overlay = at(Overlay::new(
523
+ vec![Filesystem::new(Arc::new(lower)).expect("the instance must stand")],
524
+ Some(Filesystem::new(Arc::new(upper)).expect("the instance must stand")),
525
+ )
526
+ .expect("the Overlay must stack"));
527
+ assert_eq!(
528
+ open_to_read(&overlay, "file").fsync(false),
529
+ Ok(()),
530
+ "FileOperations::fsync on an Overlay's file open to read from a Tmpfs Layer must \
531
+ succeed"
532
+ );
533
+ let tmpfs = at([("file", "x")].into_iter().collect::<Tmpfs>());
534
+ assert_eq!(
535
+ open_to_read(&tmpfs, "file").fsync(false),
536
+ Ok(()),
537
+ "FileOperations::fsync on a Tmpfs file must succeed"
538
+ );
205
539
  }
206
540
 
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");
541
+ fn open_to_read(fs: &KernPath, path: &str) -> Box<dyn FileOperations> {
542
+ fs.open(
543
+ path,
544
+ &OpenHow {
545
+ read: true,
546
+ ..OpenHow::default()
547
+ },
548
+ )
549
+ .expect("the file must open to read")
550
+ }
551
+
552
+ fn read_from(file: &mut dyn FileOperations) -> Vec<u8> {
217
553
  let mut buf = vec![0; 64];
218
- let read = file.read_at(&mut buf, 0).expect("the file must read");
554
+ let read = file
555
+ .read_iter(&mut Kiocb::at(0), &mut buf)
556
+ .expect("the file must read");
219
557
  buf.truncate(read);
220
558
  buf
221
559
  }
222
560
 
223
- fn write(fs: &dyn Filesystem, path: &str, contents: &[u8]) -> Result<(), FilesystemError> {
224
- fs.open(
561
+ fn read(fs: &KernPath, path: &str) -> Vec<u8> {
562
+ read_from(open_to_read(fs, path).as_mut())
563
+ }
564
+
565
+ fn write(fs: &KernPath, path: &str, contents: &[u8]) -> Result<(), FilesystemError> {
566
+ fs.open_or_create(
225
567
  path,
226
- &OpenOptions {
568
+ &OpenHow {
227
569
  write: true,
228
- create: true,
229
570
  truncate: true,
230
- ..OpenOptions::default()
571
+ ..OpenHow::default()
231
572
  },
232
573
  )?
233
- .write_at(contents, 0)
574
+ .write_iter(&mut Kiocb::at(0), contents)
234
575
  .map(drop)
235
576
  }
236
577
 
237
- fn overwrite(fs: &dyn Filesystem, path: &str, contents: &[u8]) {
578
+ /// Write at the start of what stands at `path`, so a copy up brings its
579
+ /// bytes along.
580
+ fn write_over(fs: &KernPath, path: &str, contents: &[u8]) -> Result<(), FilesystemError> {
581
+ let how = OpenHow {
582
+ write: true,
583
+ ..OpenHow::default()
584
+ };
585
+ fs.open(path, &how)?
586
+ .write_iter(&mut Kiocb::at(0), contents)
587
+ .map(drop)
588
+ }
589
+
590
+ fn overwrite(fs: &KernPath, path: &str, contents: &[u8]) {
238
591
  write(fs, path, contents).expect("the write must land");
239
592
  }
240
593
 
241
- fn names(fs: &dyn Filesystem, path: &str) -> Vec<String> {
242
- let mut names: Vec<String> = fs
243
- .read_dir(path)
594
+ fn names(fs: &KernPath, path: &str) -> Vec<String> {
595
+ let listed = match path {
596
+ "" => fs.iterate_dir(),
597
+ path => fs.open_dir(path).and_then(|dir| dir.iterate_dir()),
598
+ };
599
+ let mut names: Vec<String> = listed
244
600
  .expect("the directory must list")
245
601
  .into_iter()
246
602
  .map(|entry| entry.name)
@@ -249,18 +605,90 @@ mod tests {
249
605
  names
250
606
  }
251
607
 
252
- fn memory_with(path: &str, contents: &str) -> Arc<Memory> {
608
+ fn tmpfs_with(path: &str, contents: &str) -> Arc<Tmpfs> {
253
609
  Arc::new([(path, contents)].into_iter().collect())
254
610
  }
255
611
 
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)
612
+ // A helper beneath the VFS skips the super block's read-only answer, so only
613
+ // the view's own upper standing empty keeps the change off its lower Layers.
614
+ // @behavior FS-197
615
+ #[test]
616
+ fn a_change_reaching_an_overlay_with_no_upper_leaves_its_lower_layers_as_they_are() {
617
+ let top = tmpfs_with("dir/file", "top");
618
+ let overlay = at(lowers(top.clone(), Arc::new(Tmpfs::new())));
619
+ assert_eq!(
620
+ (
621
+ overlay.vfs_mkdir("made").err(),
622
+ on(&top).vfs_lstat("made").err()
623
+ ),
624
+ (
625
+ Some(FilesystemError::ReadOnlyFilesystem),
626
+ Some(FilesystemError::NotFound)
627
+ ),
628
+ "OverlayFs::mkdir with no upper, reached beneath the VFS's read-only answer, must \
629
+ answer EROFS and make nothing in its topmost lower Layer"
630
+ );
631
+ }
632
+
633
+ /// A security module recording the name of each directory it is asked to
634
+ /// let change.
635
+ struct RecordsChanges(Arc<Mutex<Vec<String>>>);
636
+
637
+ impl crate::vfs::SecurityOperations for RecordsChanges {
638
+ fn inode_permission(
639
+ &self,
640
+ dentry: &Dentry,
641
+ mask: crate::vfs::Mask,
642
+ ) -> Result<(), FilesystemError> {
643
+ if mask.contains(crate::vfs::Mask::WRITE) {
644
+ self.0.lock().expect("never poisoned").push(dentry.name());
645
+ }
646
+ Ok(())
647
+ }
648
+ }
649
+
650
+ // A Layer's directories carry names of their own, so a module asked about
651
+ // one would record more than the view's root.
652
+ // @behavior FS-198
653
+ #[test]
654
+ fn a_call_through_an_overlay_asks_a_module_of_the_view_alone() {
655
+ let overlay = over(tmpfs_with("f", "x"), Arc::new(Tmpfs::new()));
656
+ let mount = crate::mount::Mount::new(
657
+ "/data",
658
+ Filesystem::new(Arc::new(overlay)).expect("the instance must stand"),
659
+ crate::mount::Access::ReadWrite,
660
+ )
661
+ .expect("/data is a guest path");
662
+ let asked = Arc::new(Mutex::new(Vec::new()));
663
+ let cred = crate::vfs::Cred::new(vec![Arc::new(RecordsChanges(asked.clone()))]);
664
+ let root = crate::vfs::Path::root(mount.mnt());
665
+ let made = crate::vfs::filename_mkdirat(&cred, &root, "d");
666
+ assert_eq!(
667
+ (made, asked.lock().expect("never poisoned").clone()),
668
+ (Ok(()), vec![String::new()]),
669
+ "vfs::filename_mkdirat through an Overlay must ask a security module of the view's \
670
+ directory alone, as overlayfs makes its Layers' calls with its creator_cred"
671
+ );
672
+ }
673
+
674
+ /// `upper` over the one lower Layer `lower`.
675
+ fn over(lower: Arc<dyn FileSystemType>, upper: Arc<Tmpfs>) -> Overlay {
676
+ let instance = |fs| Filesystem::new(fs).expect("the instance must stand");
677
+ Overlay::new(vec![instance(lower)], Some(instance(upper)))
678
+ .expect("an upper over a lower Layer must make a view")
679
+ }
680
+
681
+ /// The lower Layers `top` over `bottom`, with no upper.
682
+ fn lowers(top: Arc<dyn FileSystemType>, bottom: Arc<dyn FileSystemType>) -> Overlay {
683
+ let instance = |fs| Filesystem::new(fs).expect("the instance must stand");
684
+ Overlay::new(vec![instance(top), instance(bottom)], None)
685
+ .expect("two lower Layers must make a view")
686
+ }
687
+
688
+ /// `upper` over a lower Tmpfs holding `dir/file` = "lower".
689
+ fn overlay_on(upper: Arc<Tmpfs>) -> (KernPath, Arc<Tmpfs>) {
690
+ let lower = tmpfs_with("dir/file", "lower");
691
+ (at(over(lower.clone(), upper)), lower)
264
692
  }
265
693
 
266
694
  /// A host directory holding `target`, `dir/inner`, and the links `link`
@@ -288,74 +716,67 @@ mod tests {
288
716
  Self(path)
289
717
  }
290
718
 
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)
719
+ /// An empty upper over the host directory as the lower Layer.
720
+ fn overlay(&self) -> KernPath {
721
+ self.under(Arc::new(Tmpfs::new()))
298
722
  }
299
- }
300
723
 
301
- #[cfg(unix)]
302
- impl Drop for LinkTree {
303
- fn drop(&mut self) {
304
- let _ = std::fs::remove_dir_all(&self.0);
724
+ /// The host directory as the lower Layer beneath `upper`.
725
+ fn under(&self, upper: Arc<Tmpfs>) -> KernPath {
726
+ at(over(Arc::new(self.bind()), upper))
727
+ }
728
+
729
+ fn bind(&self) -> super::super::Bind {
730
+ super::super::Bind::new(&self.0).expect("the host directory must open")
305
731
  }
306
732
  }
307
733
 
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
734
+ // The upper Layer's `dirlink` is a directory and the lower one's a link,
735
+ // so only merging directory over directory keeps the lower link's
736
+ // target out of the view.
737
+ // @behavior FS-113
313
738
  #[cfg(unix)]
314
739
  #[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();
740
+ fn a_lower_layers_link_beneath_an_upper_directory_is_not_merged() {
741
+ let tree = LinkTree::new("not-merged");
742
+ let overlay = tree.under(tmpfs_with("upper/dirlink/.keep", ""));
743
+ overlay
744
+ .vfs_unlink("dirlink/.keep")
745
+ .expect("the seed must go");
324
746
  assert_eq!(
325
747
  (
326
- overlay.canonicalize_beneath("dir", "in/y"),
327
- overlay.canonicalize_beneath("dir", "up"),
748
+ kind(&overlay, "dirlink/inner"),
749
+ names(&overlay, "dirlink").len()
328
750
  ),
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"
751
+ (Err(FilesystemError::NotFound), 0),
752
+ "a name beneath a directory an upper Layer holds, where a lower Layer holds a link, \
753
+ through vfs_lstat and its listing must show nothing the link leads to"
333
754
  );
334
755
  }
335
756
 
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.
757
+ #[cfg(unix)]
758
+ impl Drop for LinkTree {
759
+ fn drop(&mut self) {
760
+ let _ = std::fs::remove_dir_all(&self.0);
761
+ }
762
+ }
763
+
764
+ // The link stands in the Bind Layer beneath a Tmpfs one, so only
765
+ // the Overlay asking that Layer without following refuses it.
338
766
  // @behavior FS-100
339
767
  #[cfg(unix)]
340
768
  #[test]
341
- fn an_overlay_for_a_policy_follows_no_link_in_any_layer() {
342
- let host = LinkTree::new("for-policy");
769
+ fn an_overlay_opens_no_link_as_a_directory_where_asked_not_to_follow() {
770
+ let host = LinkTree::new("nofollow");
343
771
  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
772
  assert_eq!(
352
773
  (
353
- overlay.open("dirlink/inner", &reading).map(drop),
354
- for_policy.open("dirlink/inner", &reading).map(drop),
774
+ overlay.open_dir("dirlink").map(drop),
775
+ overlay.vfs_readlink("dirlink")
355
776
  ),
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"
777
+ (Err(FilesystemError::FilesystemLoop), Ok("dir".to_string())),
778
+ "a link a Bind Layer holds, opened through Overlay::open_dir, must be a \
779
+ link loop, and through vfs_readlink must answer where it points"
359
780
  );
360
781
  }
361
782
 
@@ -365,16 +786,12 @@ mod tests {
365
786
  fn an_overlay_reports_a_link_its_layer_holds_as_a_link() {
366
787
  let host = LinkTree::new("lstat");
367
788
  let overlay = host.overlay();
368
- let file_type = |path| {
369
- overlay
370
- .symlink_metadata(path)
371
- .map(|metadata| metadata.file_type)
372
- };
789
+ let file_type = |path| kind(&overlay, path);
373
790
  assert_eq!(
374
791
  (file_type("link"), file_type("dangling")),
375
792
  (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"
793
+ "a link a Bind Layer holds, pointing at a file or at nothing, looked up through \
794
+ vfs_lstat must be reported as a link"
378
795
  );
379
796
  }
380
797
 
@@ -387,11 +804,11 @@ mod tests {
387
804
  let host = LinkTree::new("unlink");
388
805
  let overlay = host.overlay();
389
806
  let removed = (
390
- overlay.remove_dir("dirlink"),
391
- overlay.remove_file("dirlink"),
392
- overlay.remove_file("dangling"),
807
+ overlay.vfs_rmdir("dirlink"),
808
+ overlay.vfs_unlink("dirlink"),
809
+ overlay.vfs_unlink("dangling"),
393
810
  );
394
- let gone = |path| overlay.symlink_metadata(path).err();
811
+ let gone = |path| overlay.vfs_lstat(path).err();
395
812
  assert_eq!(
396
813
  (
397
814
  removed,
@@ -405,8 +822,8 @@ mod tests {
405
822
  Some(FilesystemError::NotFound),
406
823
  true
407
824
  ),
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, \
825
+ "a link a read-only Bind Layer holds, removed through vfs_rmdir and \
826
+ vfs_unlink, must be refused as no directory and then removed as itself, \
410
827
  leaving the Layer as it was"
411
828
  );
412
829
  }
@@ -428,13 +845,13 @@ mod tests {
428
845
  assert_eq!(
429
846
  (
430
847
  write(&overlay, "pipe", b"upper"),
431
- overlay.rename("pipe", "moved", Rename::File)
848
+ overlay.rename("pipe", &overlay, "moved")
432
849
  ),
433
850
  (
434
851
  Err(FilesystemError::PermissionDenied),
435
852
  Err(FilesystemError::CrossesDevices)
436
853
  ),
437
- "a FIFO a read-only Directory Layer holds, written or renamed through an Overlay, must \
854
+ "a FIFO a read-only Bind Layer holds, written or renamed through an Overlay, must \
438
855
  be refused as permission denied and as crossing devices"
439
856
  );
440
857
  }
@@ -454,181 +871,287 @@ mod tests {
454
871
  assert!(made.success(), "the FIFO must be made");
455
872
  let overlay = host.overlay();
456
873
  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);
874
+ .vfs_mkdir("made")
875
+ .expect("a directory must be made in the upper");
876
+ let file_type = |path| kind(&overlay, path);
460
877
  assert_eq!(
461
878
  (
462
- overlay.rename("pipe", "dir", Rename::Any),
463
- overlay.rename("made", "pipe", Rename::Any),
879
+ overlay.rename("pipe", &overlay, "dir"),
880
+ overlay.rename("made", &overlay, "pipe"),
464
881
  file_type("pipe"),
465
882
  file_type("made"),
466
883
  ),
467
884
  (
468
885
  Err(FilesystemError::IsADirectory),
469
886
  Err(FilesystemError::NotADirectory),
470
- Ok(FileType::Unknown),
471
- Ok(FileType::Directory)
887
+ Ok(FileType::Fifo),
888
+ Ok(FileType::Dir)
472
889
  ),
473
890
  "a FIFO renamed onto a directory through an Overlay must be refused as a directory, \
474
891
  and a directory renamed onto a FIFO as no directory, leaving both in place"
475
892
  );
476
893
  }
477
894
 
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.
895
+ /// A lower Layer whose one file, `file`, would keep any time it is asked
896
+ /// to, recording that it was.
897
+ struct KeepsTimes(Arc<AtomicUsize>);
898
+
899
+ impl FileSystemType for KeepsTimes {
900
+ fn fill_super(self: Arc<Self>, sb: &mut Filesystem) -> Result<(), FilesystemError> {
901
+ sb.d_make_root(Inode::new(&Kstat::new(FileType::Dir, 0), self));
902
+ Ok(())
903
+ }
904
+ }
905
+
906
+ impl InodeOperations for KeepsTimes {
907
+ fn getattr(&self, dentry: &Dentry) -> Result<Kstat, FilesystemError> {
908
+ Ok(match dentry.name().as_str() {
909
+ "file" => Kstat::new(FileType::RegFile, 0).with_identity(1, 2),
910
+ _ => Kstat::new(FileType::Dir, 0).with_identity(1, 1),
911
+ })
912
+ }
913
+
914
+ fn lookup(&self, dentry: &Dentry) -> Result<Lookup, FilesystemError> {
915
+ Ok(match dentry.name().as_str() {
916
+ "file" => Lookup::Found(Inode::new(
917
+ &Kstat::new(FileType::RegFile, 0).with_identity(1, 2),
918
+ Arc::new(Self(self.0.clone())),
919
+ )),
920
+ _ => Lookup::Absent,
921
+ })
922
+ }
923
+
924
+ fn setattr(&self, _dentry: &Dentry, _attr: &Iattr) -> Result<(), FilesystemError> {
925
+ self.0.fetch_add(1, Ordering::SeqCst);
926
+ Ok(())
927
+ }
928
+
929
+ fn open(
930
+ &self,
931
+ _dentry: &Dentry,
932
+ _how: &OpenHow,
933
+ ) -> Result<Box<dyn FileOperations>, FilesystemError> {
934
+ struct Empty;
935
+ impl FileOperations for Empty {
936
+ fn read_iter(
937
+ &mut self,
938
+ _iocb: &mut Kiocb,
939
+ _buf: &mut [u8],
940
+ ) -> Result<usize, FilesystemError> {
941
+ Ok(0)
942
+ }
943
+ }
944
+ Ok(Box::new(Empty))
945
+ }
946
+ }
947
+
948
+ // A lower Layer that would keep the time shows the call stopped at the upper.
949
+ // @behavior FS-147
950
+ #[test]
951
+ fn a_time_set_through_an_overlay_leaves_its_lower_layer_as_it_was() {
952
+ let asked = Arc::new(AtomicUsize::new(0));
953
+ let upper = Arc::new(Tmpfs::new());
954
+ let overlay = Overlay::new(
955
+ vec![Filesystem::new(Arc::new(KeepsTimes(asked.clone())))
956
+ .expect("the instance must stand")],
957
+ Some(Filesystem::new(upper.clone()).expect("the instance must stand")),
958
+ )
959
+ .unwrap_or_else(|_| panic!("the Overlay must make"));
960
+ let mount = crate::mount::Mount::new(
961
+ "/data",
962
+ Filesystem::new(Arc::new(overlay)).expect("the instance must stand"),
963
+ crate::mount::Access::ReadWrite,
964
+ )
965
+ .expect("/data is a guest path");
966
+ let answer = crate::vfs::do_utimes_path(
967
+ &Arc::default(),
968
+ &crate::vfs::Path::root(mount.mnt()),
969
+ "file",
970
+ false,
971
+ &Iattr::times(None, Some(crate::filesystem::SetTime::Now)),
972
+ );
973
+ assert_eq!(
974
+ (
975
+ answer,
976
+ asked.load(Ordering::SeqCst),
977
+ kern_path(upper).vfs_lstat("upper/file").is_ok()
978
+ ),
979
+ (Ok(()), 0, true),
980
+ "vfs::do_utimes_paththrough an Overlay on a file of its lower Layer must copy the file \
981
+ up, set the time there, and never ask the lower Layer"
982
+ );
983
+ }
984
+
985
+ // A recursive climb needs more than this stack at this depth, in a build
986
+ // of any optimization, so only a loop passes.
987
+ // @behavior FS-146
988
+ #[test]
989
+ fn a_change_copies_up_however_deep_it_lands() {
990
+ let deep = vec!["a"; 1500].join("/");
991
+ let lower: Tmpfs = [(format!("{deep}/file"), "x")].into_iter().collect();
992
+ let overlay = Overlay::new(
993
+ vec![Filesystem::new(Arc::new(lower)).expect("the instance must stand")],
994
+ Some(Filesystem::new(Arc::new(Tmpfs::new())).expect("the instance must stand")),
995
+ )
996
+ .unwrap_or_else(|_| panic!("the Overlay must make"));
997
+ let made = std::thread::Builder::new()
998
+ .stack_size(256 * 1024)
999
+ .spawn(move || {
1000
+ let view = kern_path(Arc::new(overlay));
1001
+ view.vfs_mkdir(&format!("{deep}/made"))?;
1002
+ view.vfs_lstat(&format!("{deep}/made"))
1003
+ .map(|found| found.file_type)
1004
+ })
1005
+ .expect("the thread must start")
1006
+ .join()
1007
+ .expect("the thread must not panic");
1008
+ assert_eq!(
1009
+ made,
1010
+ Ok(FileType::Dir),
1011
+ "a directory made 1500 directories deep in a lower Layer, through an Overlay on a \
1012
+ 256 KiB stack, must copy every directory above it up and stand"
1013
+ );
1014
+ }
1015
+
1016
+ // The upper is a Tmpfs, which holds no link, so a link cannot land there.
480
1017
  // @behavior FS-051
481
1018
  #[cfg(unix)]
482
1019
  #[test]
483
- fn an_overlay_refuses_a_change_through_a_link_its_read_only_layer_holds() {
1020
+ fn an_overlay_refuses_to_move_a_link_its_read_only_layer_holds() {
484
1021
  let host = LinkTree::new("through");
485
1022
  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
1023
  assert_eq!(
498
- (refused, read(&overlay, "target")),
499
1024
  (
500
- [
501
- Err(FilesystemError::CrossesDevices),
502
- Err(FilesystemError::CrossesDevices),
503
- Err(FilesystemError::CrossesDevices),
504
- Err(FilesystemError::CrossesDevices)
505
- ],
506
- b"lower".to_vec()
1025
+ overlay.rename("link", &overlay, "moved"),
1026
+ read(&overlay, "target")
507
1027
  ),
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"
1028
+ (Err(FilesystemError::CrossesDevices), b"lower".to_vec()),
1029
+ "a rename of a link a read-only Bind Layer holds, through an Overlay, must be refused \
1030
+ as crossing devices and leave the target as it was"
511
1031
  );
512
1032
  }
513
1033
 
514
- // The lower Memory holds a readable `dangling`, so only the upper
1034
+ // The lower Tmpfs holds a readable `dangling`, so only the upper
515
1035
  // link standing at that path can keep the read from reaching it.
516
1036
  // @behavior FS-053
517
1037
  #[cfg(unix)]
518
1038
  #[test]
519
1039
  fn a_link_a_layer_holds_hides_what_the_layers_below_hold_there() {
520
1040
  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);
1041
+ let overlay = at(lowers(
1042
+ Arc::new(host.bind()),
1043
+ tmpfs_with("dangling", "lower"),
1044
+ ));
525
1045
  assert_eq!(
526
1046
  overlay
527
1047
  .open(
528
1048
  "dangling",
529
- &OpenOptions {
1049
+ &OpenHow {
530
1050
  read: true,
531
- ..OpenOptions::default()
1051
+ ..OpenHow::default()
532
1052
  }
533
1053
  )
534
1054
  .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"
1055
+ Err(FilesystemError::FilesystemLoop),
1056
+ "a lower file beneath a dangling link a higher Bind Layer holds at its path, \
1057
+ opened through Overlay::open, must be refused as the link, the file unreached"
538
1058
  );
539
1059
  }
540
1060
 
541
- // The lower Memory holds `dirlink/x` beside where the upper link points,
1061
+ // The lower Tmpfs holds `dirlink/x` beside where the upper link points,
542
1062
  // so only the link hiding everything beneath it keeps `x` out.
543
1063
  // @behavior FS-057
544
1064
  #[cfg(unix)]
545
1065
  #[test]
546
1066
  fn a_link_a_layer_holds_hides_what_the_layers_below_hold_beneath_it() {
547
1067
  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);
1068
+ let overlay = at(lowers(
1069
+ Arc::new(host.bind()),
1070
+ tmpfs_with("dirlink/x", "lower"),
1071
+ ));
555
1072
  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"
1073
+ (
1074
+ overlay
1075
+ .vfs_lstat("dirlink")
1076
+ .map(|metadata| metadata.file_type),
1077
+ overlay.open_dir("dirlink").map(drop)
1078
+ ),
1079
+ (Ok(FileType::Symlink), Err(FilesystemError::FilesystemLoop)),
1080
+ "a directory a higher Bind Layer holds as a link, looked up and opened through an \
1081
+ Overlay, must stand as the link, so none of a lower Layer's entries beneath it show"
560
1082
  );
561
1083
  }
562
1084
 
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.
1085
+ // The lower directory stays in its Layer, so only the whiteout keeps
1086
+ // it from showing once the upper file goes.
565
1087
  // @behavior FS-058
566
1088
  #[test]
567
1089
  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");
1090
+ let lower = tmpfs_with("f/g", "lower");
1091
+ let overlay = at(over(lower.clone(), tmpfs_with("upper/f", "upper")));
1092
+ let removed = overlay.vfs_unlink("f");
573
1093
  assert_eq!(
574
1094
  (
575
1095
  removed,
576
- overlay.symlink_metadata("f").err(),
577
- lower.metadata("f/g").is_ok()
1096
+ overlay.vfs_lstat("f").err(),
1097
+ on(&lower).vfs_lstat("f/g").is_ok()
578
1098
  ),
579
1099
  (Ok(()), Some(FilesystemError::NotFound), true),
580
1100
  "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 \
1101
+ vfs_unlink, must leave the path gone from the view and the lower \
582
1102
  directory as it was"
583
1103
  );
584
1104
  }
585
1105
 
586
- // The writable Layer's `dir` holds only the whiteout hiding `file`, so
1106
+ // The upper's `dir` holds only the whiteout hiding `file`, so
587
1107
  // only judging the view, not that copy, lets the move go.
588
1108
  // @behavior FS-059
589
1109
  #[test]
590
1110
  fn a_directory_moves_onto_one_holding_only_markers() {
591
- let (overlay, _) = overlay_on(Arc::new(Memory::new()));
1111
+ let (overlay, _) = overlay_on(Arc::new(Tmpfs::new()));
592
1112
  overlay
593
- .remove_file("dir/file")
1113
+ .vfs_unlink("dir/file")
594
1114
  .expect("the file must be removed");
595
1115
  overlay
596
- .create_dir("new")
1116
+ .vfs_mkdir("new")
597
1117
  .expect("the directory must be made");
598
1118
  overwrite(&overlay, "new/made", b"made");
599
- let moved = overlay.rename("new", "dir", Rename::Directory);
1119
+ let moved = overlay.rename("new", &overlay, "dir");
600
1120
  assert_eq!(
601
1121
  (moved, names(&overlay, "dir")),
602
1122
  (Ok(()), vec!["made".to_string()]),
603
1123
  "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"
1124
+ upper copy holds only a whiteout, must take its place and show only its own entries"
605
1125
  );
606
1126
  }
607
1127
 
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.
1128
+ // The directory made again is marked opaque in the upper, so only that
1129
+ // mark tells it apart from one the lower Layer still holds.
610
1130
  // @behavior FS-060
611
1131
  #[test]
612
- fn a_directory_made_again_moves_as_the_writable_layers_own() {
613
- let (overlay, _) = overlay_on(Arc::new(Memory::new()));
1132
+ fn a_directory_made_again_moves_as_the_uppers_own() {
1133
+ let (overlay, _) = overlay_on(Arc::new(Tmpfs::new()));
614
1134
  overlay
615
- .remove_file("dir/file")
1135
+ .vfs_unlink("dir/file")
616
1136
  .expect("the file must be removed");
617
1137
  overlay
618
- .remove_dir("dir")
1138
+ .vfs_rmdir("dir")
619
1139
  .expect("the directory must be removed");
620
1140
  overlay
621
- .create_dir("dir")
1141
+ .vfs_mkdir("dir")
622
1142
  .expect("the directory must be made again");
623
- let moved = overlay.rename("dir", "moved", Rename::Directory);
1143
+ let moved = overlay.rename("dir", &overlay, "moved");
624
1144
  assert_eq!(
625
1145
  (
626
1146
  moved,
627
- overlay.metadata("dir").err(),
628
- overlay.read_dir("moved").map(|entries| entries.len())
1147
+ overlay.vfs_lstat("dir").err(),
1148
+ overlay
1149
+ .open_dir("moved")
1150
+ .and_then(|dir| dir.iterate_dir())
1151
+ .map(|entries| entries.len())
629
1152
  ),
630
1153
  (Ok(()), Some(FilesystemError::NotFound), Ok(0)),
631
- "a directory made again through an Overlay where a read-only Layer's one was removed, \
1154
+ "a directory made again through an Overlay where a lower Layer's one was removed, \
632
1155
  renamed through Overlay::rename, must move and leave nothing of the lower one behind"
633
1156
  );
634
1157
  }
@@ -636,9 +1159,9 @@ mod tests {
636
1159
  // @behavior FS-061
637
1160
  #[test]
638
1161
  fn an_overlay_refuses_a_file_moved_beneath_itself_as_no_directory() {
639
- let (overlay, _) = overlay_on(Arc::new(Memory::new()));
1162
+ let (overlay, _) = overlay_on(Arc::new(Tmpfs::new()));
640
1163
  assert_eq!(
641
- overlay.rename("dir/file", "dir/file/x", Rename::Any),
1164
+ overlay.rename("dir/file", &overlay, "dir/file/x"),
642
1165
  Err(FilesystemError::NotADirectory),
643
1166
  "a file renamed through Overlay::rename to a path beneath itself must be refused as \
644
1167
  no directory, as rename(2) refuses it"
@@ -653,21 +1176,20 @@ mod tests {
653
1176
  fn an_overlay_finds_a_dangling_link_where_a_new_entry_would_go() {
654
1177
  let host = LinkTree::new("dangling");
655
1178
  let overlay = host.overlay();
656
- let exclusive = OpenOptions {
1179
+ let exclusive = OpenHow {
657
1180
  write: true,
658
- create_new: true,
659
- ..OpenOptions::default()
1181
+ ..OpenHow::default()
660
1182
  };
661
1183
  assert_eq!(
662
1184
  (
663
- overlay.open("dangling", &exclusive).map(drop),
664
- overlay.create_dir("dangling")
1185
+ overlay.create("dangling", &exclusive).map(drop),
1186
+ overlay.vfs_mkdir("dangling")
665
1187
  ),
666
1188
  (
667
1189
  Err(FilesystemError::AlreadyExists),
668
1190
  Err(FilesystemError::AlreadyExists)
669
1191
  ),
670
- "an exclusive create and a create_dir at a dangling link a read-only Directory Layer \
1192
+ "an exclusive create and a create_dir at a dangling link a lower Bind Layer \
671
1193
  holds, through an Overlay, must find it already there"
672
1194
  );
673
1195
  }
@@ -675,7 +1197,7 @@ mod tests {
675
1197
  // @behavior FS-008
676
1198
  #[test]
677
1199
  fn an_overlay_reads_through_to_a_lower_layer() {
678
- let (overlay, _) = overlay_on(Arc::new(Memory::new()));
1200
+ let (overlay, _) = overlay_on(Arc::new(Tmpfs::new()));
679
1201
  assert_eq!(
680
1202
  read(&overlay, "dir/file"),
681
1203
  b"lower",
@@ -685,67 +1207,53 @@ mod tests {
685
1207
 
686
1208
  // @behavior FS-009
687
1209
  #[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());
1210
+ fn a_write_to_a_lower_layers_file_lands_in_the_upper() {
1211
+ let upper = Arc::new(Tmpfs::new());
690
1212
  let (overlay, _) = overlay_on(upper.clone());
691
1213
  overwrite(&overlay, "dir/file", b"upper");
692
1214
  assert_eq!(
693
- read(upper.as_ref(), "dir/file"),
1215
+ read(&on(&upper), "upper/dir/file"),
694
1216
  b"upper",
695
- "a read-only Layer's file written through Overlay::open must land in the writable Layer above it"
1217
+ "a lower Layer's file written through Overlay::open must land in the upper"
696
1218
  );
697
1219
  }
698
1220
 
699
1221
  // @behavior FS-010
700
1222
  #[test]
701
- fn an_overlay_leaves_a_read_only_layer_as_it_was() {
702
- let (overlay, lower) = overlay_on(Arc::new(Memory::new()));
1223
+ fn an_overlay_leaves_a_lower_layer_as_it_was() {
1224
+ let (overlay, lower) = overlay_on(Arc::new(Tmpfs::new()));
703
1225
  overwrite(&overlay, "dir/file", b"upper");
704
1226
  overlay
705
- .remove_file("dir/file")
1227
+ .vfs_unlink("dir/file")
706
1228
  .expect("the file must be removed");
707
1229
  assert_eq!(
708
- read(lower.as_ref(), "dir/file"),
1230
+ read(&on(&lower), "dir/file"),
709
1231
  b"lower",
710
- "a read-only Layer's file written and removed through an Overlay must stay as it was"
1232
+ "a lower Layer's file written and removed through an Overlay must stay as it was"
711
1233
  );
712
1234
  }
713
1235
 
714
1236
  // @behavior FS-011
715
1237
  #[test]
716
1238
  fn a_file_removed_through_an_overlay_is_gone_from_its_view() {
717
- let (overlay, _) = overlay_on(Arc::new(Memory::new()));
1239
+ let (overlay, _) = overlay_on(Arc::new(Tmpfs::new()));
718
1240
  overlay
719
- .remove_file("dir/file")
1241
+ .vfs_unlink("dir/file")
720
1242
  .expect("the file must be removed");
721
1243
  assert_eq!(
722
- overlay.metadata("dir/file"),
1244
+ overlay.vfs_lstat("dir/file"),
723
1245
  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"
1246
+ "a lower file removed through vfs_unlink must no longer be found there"
740
1247
  );
741
1248
  }
742
1249
 
743
1250
  // @behavior FS-013
744
1251
  #[test]
745
1252
  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);
1253
+ let overlay = at(lowers(
1254
+ tmpfs_with("note", "upper"),
1255
+ tmpfs_with("note", "lower"),
1256
+ ));
749
1257
  assert_eq!(
750
1258
  read(&overlay, "note"),
751
1259
  b"upper",
@@ -755,267 +1263,586 @@ mod tests {
755
1263
 
756
1264
  // @behavior FS-014
757
1265
  #[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);
1266
+ fn a_write_to_the_uppers_file_changes_it_in_place() {
1267
+ let upper = tmpfs_with("upper/note", "upper");
1268
+ let overlay = at(over(tmpfs_with("note", "lower"), upper.clone()));
764
1269
  overwrite(&overlay, "note", b"changed");
765
1270
  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"
1271
+ read(&on(&upper), "upper/note"),
1272
+ b"changed",
1273
+ "the upper's file written through Overlay::open must change in place"
769
1274
  );
770
1275
  }
771
1276
 
772
- // The lower Layer is writable, so only the refusal itself keeps the
773
- // move from landing in two Layers.
774
- // @behavior FS-088
1277
+ // @behavior FS-015
775
1278
  #[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);
1279
+ fn a_new_file_lands_in_the_upper() {
1280
+ let upper = Arc::new(Tmpfs::new());
1281
+ let lower = Arc::new(Tmpfs::new());
1282
+ let overlay = at(over(lower.clone(), upper.clone()));
1283
+ overwrite(&overlay, "new", b"made");
782
1284
  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"
1285
+ (read(&on(&upper), "upper/new"), on(&lower).vfs_lstat("new")),
1286
+ (b"made".to_vec(), Err(FilesystemError::NotFound)),
1287
+ "a file created through Overlay::open must land in the upper alone"
791
1288
  );
792
1289
  }
793
1290
 
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
1291
+ // @behavior FS-016
798
1292
  #[test]
799
- fn an_overlay_change_that_lands_succeeds_though_its_whiteout_stays() {
800
- let (overlay, _) = overlay_on(Arc::new(KeepsWhiteouts(Memory::new())));
1293
+ fn a_removal_leaves_a_whiteout_in_the_upper() {
1294
+ let upper = Arc::new(Tmpfs::new());
1295
+ let (overlay, _) = overlay_on(upper.clone());
801
1296
  overlay
802
- .remove_file("dir/file")
1297
+ .vfs_unlink("dir/file")
803
1298
  .expect("the file must be removed");
804
1299
  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"
1300
+ on(&upper)
1301
+ .vfs_lstat("upper/dir/file")
1302
+ .map(|metadata| metadata.file_type),
1303
+ Ok(FileType::Chrdev),
1304
+ "a lower Layer's file removed through vfs_unlink must leave a whiteout, a \
1305
+ device of no kind the guest opens, at its name in the upper"
809
1306
  );
810
1307
  }
811
1308
 
812
- // @behavior FS-015
1309
+ // The Layer changes behind the view between the lookup and the listing, so
1310
+ // only Layers bound as the name is found keep the directory it found, as
1311
+ // they cannot be taken for another one by a move in that window.
1312
+ // @behavior FS-165
813
1313
  #[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");
1314
+ fn a_directory_found_stands_for_the_layers_it_was_found_in() {
1315
+ use crate::vfs::lookup_one_unlocked;
1316
+ let lower = Arc::new([("dir/old", "x")].into_iter().collect::<Tmpfs>());
1317
+ let overlay = Arc::new(over(lower.clone(), Arc::new(Tmpfs::new())));
1318
+ let filesystem = Filesystem::new(overlay.clone()).expect("the instance must stand");
1319
+ let root = filesystem.s_root();
1320
+ let dir = lookup_one_unlocked(&Default::default(), &filesystem, &root, "dir")
1321
+ .expect("the directory must be found");
1322
+ let layer = on(&lower);
1323
+ layer
1324
+ .rename("dir", &layer, "moved")
1325
+ .expect("the Layer's directory must move");
1326
+ layer.vfs_mkdir("dir").expect("another must be made");
1327
+ overwrite(&layer, "dir/new", b"y");
1328
+ let names: Vec<String> = overlay
1329
+ .fs
1330
+ .dir_read_merged(
1331
+ &overlay
1332
+ .fs
1333
+ .dir(&dir)
1334
+ .expect("the directory must stand")
1335
+ .level(),
1336
+ )
1337
+ .expect("the directory must list")
1338
+ .into_iter()
1339
+ .map(|entry| entry.name)
1340
+ .collect();
822
1341
  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"
1342
+ names,
1343
+ ["old"],
1344
+ "OverlayFs::dir_read_merged of a directory OverlayFs found before its Layer changed must list \
1345
+ the Layer's directory found with it"
826
1346
  );
827
1347
  }
828
1348
 
829
- // @behavior FS-016
1349
+ // What fails after the exchange only a second writer's use of the inodes
1350
+ // can force, so the exchange is called alone: the next walk must find the
1351
+ // empty directory rather than the dentry whose upper no longer matches.
1352
+ // @behavior FS-164
830
1353
  #[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")
1354
+ fn a_directory_emptied_for_its_removal_stays_empty_should_the_removal_fail() {
1355
+ use crate::vfs::{lookup_one_unlocked, vfs_unlink};
1356
+ let lower = Arc::new([("dir/file", "x")].into_iter().collect::<Tmpfs>());
1357
+ let overlay = Arc::new(over(lower, Arc::new(Tmpfs::new())));
1358
+ let filesystem = Filesystem::new(overlay.clone()).expect("the instance must stand");
1359
+ let root = filesystem.s_root();
1360
+ let dir = lookup_one_unlocked(&Default::default(), &filesystem, &root, "dir")
1361
+ .expect("the directory must be found");
1362
+ let file = lookup_one_unlocked(&Default::default(), &filesystem, &dir, "file")
1363
+ .expect("the file must be found");
1364
+ vfs_unlink(&Default::default(), &filesystem, &dir, &file)
836
1365
  .expect("the file must be removed");
1366
+ let upper_root = overlay.fs.dir(&root).expect("the root must stand").level()[0]
1367
+ .clone()
1368
+ .expect("the upper shows the root");
1369
+ overlay
1370
+ .fs
1371
+ .clear_empty(&upper_root, "dir")
1372
+ .expect("the directory must be exchanged");
1373
+ let walked = lookup_one_unlocked(&Default::default(), &filesystem, &root, "dir")
1374
+ .expect("the directory must be found again");
1375
+ let names = overlay.fs.dir_read_merged(
1376
+ &overlay
1377
+ .fs
1378
+ .dir(&walked)
1379
+ .expect("the directory must stand")
1380
+ .level(),
1381
+ );
837
1382
  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"
1383
+ names.map(|entries| entries.len()),
1384
+ Ok(0),
1385
+ "OverlayFs::dir_read_merged of a directory found by a walk after OverlayFs::clear_empty \
1386
+ replaced it must show nothing of the lower Layer below it"
841
1387
  );
842
1388
  }
843
1389
 
844
1390
  // @behavior FS-017
845
1391
  #[test]
846
- fn a_whiteout_lasts_as_long_as_its_writable_layer() {
847
- let upper = Arc::new(Memory::new());
1392
+ fn a_whiteout_lasts_as_long_as_the_upper() {
1393
+ let upper = Arc::new(Tmpfs::new());
848
1394
  let (overlay, lower) = overlay_on(upper.clone());
849
1395
  overlay
850
- .remove_file("dir/file")
1396
+ .vfs_unlink("dir/file")
851
1397
  .expect("the file must be removed");
852
- let rebuilt = Overlay::new()
853
- .layer(upper, Access::ReadWrite)
854
- .layer(lower, Access::Read);
1398
+ let rebuilt = at(over(lower, upper));
855
1399
  assert_eq!(
856
- rebuilt.metadata("dir/file"),
1400
+ rebuilt.vfs_lstat("dir/file"),
857
1401
  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"
1402
+ "a lower file removed through an Overlay over a Tmpfs must stay gone from a new Overlay over the same Layers"
859
1403
  );
860
1404
  }
861
1405
 
862
1406
  // @behavior FS-018
863
1407
  #[test]
864
1408
  fn a_directory_made_again_where_one_was_removed_shows_nothing_from_below() {
865
- let (overlay, _) = overlay_on(Arc::new(Memory::new()));
1409
+ let (overlay, _) = overlay_on(Arc::new(Tmpfs::new()));
866
1410
  overlay
867
- .remove_file("dir/file")
1411
+ .vfs_unlink("dir/file")
868
1412
  .expect("the file must be removed");
869
1413
  overlay
870
- .remove_dir("dir")
1414
+ .vfs_rmdir("dir")
871
1415
  .expect("the directory must be removed");
872
1416
  overlay
873
- .create_dir("dir")
1417
+ .vfs_mkdir("dir")
874
1418
  .expect("the directory must be made");
875
1419
  assert_eq!(
876
1420
  names(&overlay, "dir"),
877
1421
  Vec::<String>::new(),
878
- "a directory made again through Overlay::create_dir must list nothing from the read-only Layer"
1422
+ "a directory made again through vfs_mkdir must list nothing from the lower Layer"
879
1423
  );
880
1424
  }
881
1425
 
882
1426
  // @behavior FS-019
883
1427
  #[test]
884
1428
  fn an_overlay_lists_none_of_its_markers() {
885
- let (overlay, _) = overlay_on(Arc::new(Memory::new()));
1429
+ let (overlay, _) = overlay_on(Arc::new(Tmpfs::new()));
886
1430
  overwrite(&overlay, "dir/new", b"made");
887
1431
  overlay
888
- .remove_file("dir/file")
1432
+ .vfs_unlink("dir/file")
889
1433
  .expect("the file must be removed");
890
1434
  assert_eq!(
891
1435
  names(&overlay, "dir"),
892
1436
  ["new"],
893
- "a directory holding a whiteout listed through Overlay::read_dir must show no .wh. entry"
1437
+ "a directory holding a whiteout listed through Overlay::read_dir must not list the \
1438
+ whited-out name"
894
1439
  );
895
1440
  }
896
1441
 
897
- // @behavior FS-020
1442
+ // Linux's `ovl_rename` answers ESTALE once the upper moved beneath it; the
1443
+ // view is undefined here, so the promise is only that the call answers,
1444
+ // and the bound turns a hang into a failure.
1445
+ // @behavior FS-204
1446
+ #[test]
1447
+ fn an_upper_changed_beside_its_overlay_still_answers() {
1448
+ use crate::vfs;
1449
+ let (overlay, upper) = beside_its_upper(
1450
+ [("upper/a/x", "x"), ("upper/a/k", "k")]
1451
+ .into_iter()
1452
+ .collect(),
1453
+ );
1454
+ let cred = Arc::default();
1455
+ vfs::vfs_fstatat(&cred, &overlay, "a/x", false).expect("a/x must stand");
1456
+ vfs::filename_renameat2(&cred, &upper, "upper/a", &upper, "upper/b")
1457
+ .expect("the upper's directory must move");
1458
+ let answered = crate::sched::testing::within_max_wait(|| {
1459
+ vfs::filename_renameat2(&cred, &overlay, "a/x", &overlay, "a/y").err()
1460
+ });
1461
+ assert_ne!(
1462
+ answered,
1463
+ Some(FilesystemError::Interrupted),
1464
+ "vfs::filename_renameat2 through an Overlay whose upper another Mount changed must \
1465
+ answer within its bound"
1466
+ );
1467
+ }
1468
+
1469
+ // The lower Layer is a Tmpfs seen through a wrapper, so it reports the
1470
+ // device a whiteout is, yet is no Tmpfs whose marks the Overlay reads.
1471
+ // @behavior FS-203
898
1472
  #[test]
899
- fn an_overlay_refuses_a_name_reserved_for_its_markers() {
900
- let (overlay, _) = overlay_on(Arc::new(Memory::new()));
1473
+ fn an_overlay_reads_a_whiteout_only_from_a_tmpfs() {
1474
+ let tmpfs = Tmpfs::new();
1475
+ tmpfs.whiteout("device").expect("the device must be made");
1476
+ let lower = Wrap::new(Arc::new(tmpfs), Arc::new(CountsLookups::default()));
1477
+ let overlay = at(over(Arc::new(lower), Arc::new(Tmpfs::new())));
901
1478
  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"
1479
+ (
1480
+ names(&overlay, ""),
1481
+ overlay.vfs_lstat("device").map(|found| found.file_type)
1482
+ ),
1483
+ (vec!["device".to_string()], Ok(FileType::Chrdev)),
1484
+ "a character device in a lower Layer that is no Tmpfs, listed and stated through an \
1485
+ Overlay, must be listed and found rather than read as a whiteout"
905
1486
  );
906
1487
  }
907
1488
 
908
- // A Layer folding case would hold `.WH.note` as the marker hiding `note`.
909
- // @behavior FS-056
1489
+ // A whiteout is a device rather than a name, so no name is the
1490
+ // Overlay's to reserve.
1491
+ // @behavior FS-020
910
1492
  #[test]
911
- fn an_overlay_refuses_its_reserved_name_in_any_case() {
912
- let (overlay, _) = overlay_on(Arc::new(Memory::new()));
1493
+ fn a_name_starting_with_wh_is_an_ordinary_name() {
1494
+ let (overlay, _) = overlay_on(Arc::new(Tmpfs::new()));
1495
+ overwrite(&overlay, "dir/.wh.file", b"made");
913
1496
  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"
1497
+ (read(&overlay, "dir/.wh.file"), read(&overlay, "dir/file")),
1498
+ (b"made".to_vec(), b"lower".to_vec()),
1499
+ "a file named .wh.file created through Overlay::open must be made, and must hide \
1500
+ nothing"
917
1501
  );
918
1502
  }
919
1503
 
1504
+ /// An Overlay of two lower Layers and no upper on a Mount allowing writes,
1505
+ /// and the root of that Mount.
1506
+ fn mounted_with_no_upper() -> (crate::mount::Mount, crate::vfs::Path) {
1507
+ let overlay = lowers(tmpfs_with("dir/file", "lower"), Arc::new(Tmpfs::new()));
1508
+ let mount = crate::mount::Mount::new(
1509
+ "/data",
1510
+ Filesystem::new(Arc::new(overlay)).expect("the instance must stand"),
1511
+ crate::mount::Access::ReadWrite,
1512
+ )
1513
+ .expect("/data is a guest path");
1514
+ let root = crate::vfs::Path::root(mount.mnt());
1515
+ (mount, root)
1516
+ }
1517
+
920
1518
  // @behavior FS-021
921
1519
  #[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);
1520
+ fn an_overlay_with_no_upper_refuses_every_change_as_read_only() {
1521
+ let (mount, root) = mounted_with_no_upper();
1522
+ let how = crate::vfs::OpenHow {
1523
+ write: true,
1524
+ ..Default::default()
1525
+ };
1526
+ let written = crate::vfs::kern_path::filp_open(&Arc::default(), &root, "dir/file", &how);
924
1527
  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"
1528
+ (written.err(), mount.mnt().is_readonly()),
1529
+ (Some(FilesystemError::ReadOnlyFilesystem), true),
1530
+ "a file opened to write through an Overlay with no upper on a read-write Mount must \
1531
+ be refused as read-only"
928
1532
  );
929
1533
  }
930
1534
 
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"]);
1535
+ /// A Layer holding a name past what it can hold, as a Layer on a host with
1536
+ /// a shorter limit answers one.
1537
+ struct RefusesLongName;
1538
+
1539
+ impl crate::filesystem::wrap::Hook for RefusesLongName {
1540
+ fn lookup(
1541
+ &self,
1542
+ inner: &dyn InodeOperations,
1543
+ dentry: &Dentry,
1544
+ ) -> Result<Lookup, FilesystemError> {
1545
+ match dentry.name().as_str() {
1546
+ "long" => Err(FilesystemError::InvalidFilename),
1547
+ _ => inner.lookup(dentry),
1548
+ }
1549
+ }
936
1550
  }
937
1551
 
1552
+ // `ovl_lookup_single` moves on from ENAMETOOLONG, so a name one Layer cannot
1553
+ // hold is only absent there, and the upper takes it.
1554
+ // @behavior FS-174
938
1555
  #[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");
1556
+ fn a_name_too_long_for_a_lower_layer_lands_in_the_upper() {
1557
+ let lower =
1558
+ crate::filesystem::wrap::Wrap::new(Arc::new(Tmpfs::new()), Arc::new(RefusesLongName));
1559
+ let upper = Arc::new(Tmpfs::new());
1560
+ let overlay = at(over(Arc::new(lower), upper.clone()));
945
1561
  assert_eq!(
946
1562
  (
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()
1563
+ overlay.vfs_mkdir("long"),
1564
+ on(&upper)
1565
+ .vfs_lstat("upper/long")
1566
+ .map(|found| found.file_type)
955
1567
  ),
1568
+ (Ok(()), Ok(FileType::Dir)),
1569
+ "a directory made through an Overlay at a name a lower Layer answers ENAMETOOLONG for \
1570
+ must land in the upper"
956
1571
  );
957
1572
  }
958
1573
 
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<()>>>,
1574
+ // Replacing the target needs an inode only where it holds whiteouts to clear
1575
+ // away, so with none left only a rename leaving an empty one alone lands.
1576
+ // @behavior FS-175
1577
+ #[test]
1578
+ fn a_rename_onto_an_empty_upper_directory_builds_no_replacement() {
1579
+ let upper = Arc::new(Tmpfs::new().with_nr_inodes(Some(5)));
1580
+ let lower: Tmpfs = [("base", "x")].into_iter().collect();
1581
+ let overlay = at(over(Arc::new(lower), upper));
1582
+ overlay.vfs_mkdir("from").expect("the source must be made");
1583
+ overlay.vfs_mkdir("to").expect("the target must be made");
1584
+ assert_eq!(
1585
+ overlay.rename("from", &overlay, "to"),
1586
+ Ok(()),
1587
+ "a rename through an Overlay of an upper directory onto an empty upper directory, with \
1588
+ no inode left in the upper, must land, as ovl_rename_upper clears only what holds \
1589
+ whiteouts"
1590
+ );
967
1591
  }
968
1592
 
969
- impl Filesystem for Interleaves {
970
- holds_no_links!();
971
- never_repeats_identities!();
972
- is_shared!();
973
- matches_names_exactly!();
1593
+ // overlayfs's `ovl_path_lower` is the topmost lower layer, and its stat of a
1594
+ // merged directory is the real one's, so a Layer's entries change the size.
1595
+ // @behavior FS-176
1596
+ #[test]
1597
+ fn a_merged_directory_reports_its_topmost_layer_under_the_lowers_identity() {
1598
+ let lower: Arc<Tmpfs> = Arc::new([("dir/a", "x"), ("dir/b", "x")].into_iter().collect());
1599
+ let upper = Arc::new(Tmpfs::new());
1600
+ let overlay = at(over(lower.clone(), upper));
1601
+ overlay
1602
+ .vfs_mkdir("dir/made")
1603
+ .expect("the directory must be made");
1604
+ let found = overlay.vfs_lstat("dir").expect("the directory must stand");
1605
+ let below = on(&lower)
1606
+ .vfs_lstat("dir")
1607
+ .expect("the directory must stand");
1608
+ assert_eq!(
1609
+ (found.ino, found.dev, found.nlink, found.size),
1610
+ (below.ino, below.dev, 1, 3 * 20),
1611
+ "a directory two Layers show, stated through an Overlay, must report the lower \
1612
+ Layer's identity, one name, and the size of the upper's directory holding one entry"
1613
+ );
1614
+ }
974
1615
 
975
- fn open(
976
- &self,
977
- path: &str,
978
- options: &OpenOptions,
979
- ) -> Result<Box<dyn File>, FilesystemError> {
980
- self.memory.open(path, options)
981
- }
1616
+ // Bind reports the same identity for both names, so without the view keeping
1617
+ // them apart the VFS would answer the move as having nothing to do.
1618
+ // @behavior FS-170
1619
+ #[cfg(unix)]
1620
+ #[test]
1621
+ fn a_lower_layers_hard_link_moves_onto_the_other_as_two_entries() {
1622
+ let host = LinkTree::new("hard-link");
1623
+ std::fs::write(host.0.join("a"), "one").expect("the host file must be written");
1624
+ std::fs::hard_link(host.0.join("a"), host.0.join("b")).expect("the host link must be made");
1625
+ let overlay = at(over(Arc::new(host.bind()), Arc::new(Tmpfs::new())));
1626
+ write(&overlay, "a", b"two").expect("the file must copy up and be written");
1627
+ overlay
1628
+ .rename("a", &overlay, "b")
1629
+ .expect("the move must land");
1630
+ assert_eq!(
1631
+ (
1632
+ overlay.vfs_lstat("a").map(drop),
1633
+ read(&overlay, "b"),
1634
+ std::fs::read(host.0.join("b")).expect("the host file must be read"),
1635
+ ),
1636
+ (
1637
+ Err(FilesystemError::NotFound),
1638
+ b"two".to_vec(),
1639
+ b"one".to_vec()
1640
+ ),
1641
+ "a hard link in a lower Layer written and moved onto its other name through an \
1642
+ Overlay must leave only the new name, holding what was written, and the lower Layer \
1643
+ as it was"
1644
+ );
1645
+ }
982
1646
 
983
- fn metadata(&self, path: &str) -> Result<Metadata, FilesystemError> {
984
- self.memory.metadata(path)
985
- }
1647
+ // overlayfs's super block is read-only with no upper, so `mnt_want_write`
1648
+ // answers before anything looks at what stands at the name.
1649
+ // @behavior FS-169
1650
+ #[test]
1651
+ fn an_overlay_with_no_upper_refuses_a_change_before_what_stands_answers() {
1652
+ use crate::vfs::{filename_renameat2, filename_rmdir, filename_unlinkat};
1653
+ let (_mount, root) = mounted_with_no_upper();
1654
+ let cred = Arc::default();
1655
+ let read_only = || Err(FilesystemError::ReadOnlyFilesystem);
1656
+ assert_eq!(
1657
+ (
1658
+ filename_renameat2(&cred, &root, "dir/file", &root, "dir/file"),
1659
+ filename_unlinkat(&cred, &root, "missing"),
1660
+ filename_rmdir(&cred, &root, "dir"),
1661
+ ),
1662
+ (read_only(), read_only(), read_only()),
1663
+ "a move onto its own name, a removal of nothing, and a removal of a directory holding \
1664
+ an entry through an Overlay with no upper must each answer EROFS"
1665
+ );
1666
+ }
986
1667
 
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
- }
1668
+ /// Why `Overlay::new` refuses `lower` under `upper`, or `None`.
1669
+ fn refusal(
1670
+ lower: Vec<Arc<dyn FileSystemType>>,
1671
+ upper: Option<Arc<dyn FileSystemType>>,
1672
+ ) -> Option<FilesystemError> {
1673
+ let instance = |fs| Filesystem::new(fs).expect("the instance must stand");
1674
+ Overlay::new(
1675
+ lower.into_iter().map(instance).collect(),
1676
+ upper.map(instance),
1677
+ )
1678
+ .err()
1679
+ }
1004
1680
 
1005
- fn create_dir(&self, path: &str) -> Result<(), FilesystemError> {
1006
- self.memory.create_dir(path)
1007
- }
1681
+ // gVisor takes only an upper that holds its markers, and only a
1682
+ // Tmpfs does here.
1683
+ // @behavior FS-123
1684
+ #[test]
1685
+ fn an_overlay_refuses_an_upper_that_is_no_tmpfs() {
1686
+ let host = std::env::temp_dir();
1687
+ let bind = super::super::Bind::new(&host).expect("the host directory must open");
1688
+ assert_eq!(
1689
+ refusal(vec![Arc::new(Tmpfs::new())], Some(Arc::new(bind))),
1690
+ Some(FilesystemError::InvalidInput),
1691
+ "Overlay::new with a Bind as its upper must be refused as invalid input"
1692
+ );
1693
+ }
1008
1694
 
1009
- fn remove_dir(&self, path: &str) -> Result<(), FilesystemError> {
1010
- self.memory.remove_dir(path)
1011
- }
1695
+ // Nothing else tells these apart: every other rule holds of each list, and
1696
+ // a view over them would show the upper's own workdir.
1697
+ // @behavior FS-148
1698
+ #[test]
1699
+ fn an_overlay_refuses_one_instance_as_two_layers() {
1700
+ let instance = || Filesystem::new(Arc::new(Tmpfs::new())).expect("the instance must stand");
1701
+ let (shared, doubled) = (instance(), instance());
1702
+ assert_eq!(
1703
+ (
1704
+ Overlay::new(vec![shared.clone()], Some(shared)).err(),
1705
+ Overlay::new(vec![doubled.clone(), doubled, instance()], None).err(),
1706
+ ),
1707
+ (
1708
+ Some(FilesystemError::InvalidInput),
1709
+ Some(FilesystemError::InvalidInput),
1710
+ ),
1711
+ "Overlay::new with one instance as its upper and a lower Layer, or as two lower \
1712
+ Layers, must be refused as invalid input"
1713
+ );
1714
+ }
1715
+
1716
+ // @behavior FS-124
1717
+ #[test]
1718
+ fn an_overlay_without_an_upper_needs_two_lower_layers() {
1719
+ assert_eq!(
1720
+ refusal(vec![Arc::new(Tmpfs::new())], None),
1721
+ Some(FilesystemError::InvalidInput),
1722
+ "Overlay::new with one lower Layer and no upper must be refused as invalid input"
1723
+ );
1724
+ }
1725
+
1726
+ // Two Overlays deep is what Linux allows, so only a third is refused.
1727
+ // @behavior FS-126
1728
+ #[test]
1729
+ fn an_overlay_stacks_at_most_two_deep() {
1730
+ let first: Arc<dyn FileSystemType> =
1731
+ Arc::new(lowers(Arc::new(Tmpfs::new()), Arc::new(Tmpfs::new())));
1732
+ let second: Arc<dyn FileSystemType> = Arc::new(over(first.clone(), Arc::new(Tmpfs::new())));
1733
+ assert_eq!(
1734
+ (
1735
+ refusal(vec![first], Some(Arc::new(Tmpfs::new()))),
1736
+ refusal(vec![second], Some(Arc::new(Tmpfs::new())))
1737
+ ),
1738
+ (None, Some(FilesystemError::InvalidInput)),
1739
+ "Overlay::new over an Overlay must make a view, and over an Overlay over an Overlay \
1740
+ must be refused as invalid input"
1741
+ );
1742
+ }
1743
+
1744
+ // @behavior FS-127
1745
+ #[test]
1746
+ fn an_overlay_takes_at_most_five_hundred_lower_layers() {
1747
+ let layers = |count: usize| -> Vec<Arc<dyn FileSystemType>> {
1748
+ (0..count)
1749
+ .map(|_| Arc::new(Tmpfs::new()) as Arc<dyn FileSystemType>)
1750
+ .collect()
1751
+ };
1752
+ assert_eq!(
1753
+ (refusal(layers(500), None), refusal(layers(501), None)),
1754
+ (None, Some(FilesystemError::InvalidInput)),
1755
+ "Overlay::new must take 500 lower Layers and refuse 501 as invalid input"
1756
+ );
1757
+ }
1758
+
1759
+ // @behavior FS-125
1760
+ #[test]
1761
+ fn an_overlay_needs_a_lower_layer() {
1762
+ assert_eq!(
1763
+ refusal(Vec::new(), Some(Arc::new(Tmpfs::new()))),
1764
+ Some(FilesystemError::InvalidInput),
1765
+ "Overlay::new with an upper and no lower Layer must be refused as invalid input"
1766
+ );
1767
+ }
1768
+
1769
+ #[test]
1770
+ fn an_overlay_lists_every_layer_without_what_it_hid() {
1771
+ let (overlay, _) = overlay_on(Arc::new(Tmpfs::new()));
1772
+ overwrite(&overlay, "dir/new", b"made");
1773
+ assert_eq!(names(&overlay, "dir"), ["file", "new"]);
1774
+ }
1775
+
1776
+ #[test]
1777
+ fn a_lower_file_renamed_through_an_overlay_moves_into_the_upper() {
1778
+ let upper = Arc::new(Tmpfs::new());
1779
+ let (overlay, lower) = overlay_on(upper.clone());
1780
+ overlay
1781
+ .rename("dir/file", &overlay, "dir/moved")
1782
+ .expect("the file must move");
1783
+ assert_eq!(
1784
+ (
1785
+ read(&overlay, "dir/moved"),
1786
+ overlay.vfs_lstat("dir/file"),
1787
+ read(&on(&lower), "dir/file"),
1788
+ ),
1789
+ (
1790
+ b"lower".to_vec(),
1791
+ Err(FilesystemError::NotFound),
1792
+ b"lower".to_vec()
1793
+ ),
1794
+ );
1795
+ }
1796
+
1797
+ /// Another call through the VFS at `overlay`, started once on a thread
1798
+ /// of its own and given a while to finish, recording whether it did
1799
+ /// before the call starting it went on.
1800
+ #[derive(Default)]
1801
+ struct Interleave {
1802
+ overlay: Mutex<Option<crate::vfs::Path>>,
1803
+ finished_inside: Mutex<Option<bool>>,
1804
+ other: Mutex<Option<thread::JoinHandle<()>>>,
1805
+ }
1012
1806
 
1013
- fn remove_file(&self, path: &str) -> Result<(), FilesystemError> {
1014
- self.memory.remove_file(path)
1807
+ impl Interleave {
1808
+ fn start(&self) {
1809
+ let Some(overlay) = self.overlay.lock().unwrap().take() else {
1810
+ return;
1811
+ };
1812
+ let finished = Arc::new(AtomicUsize::new(0));
1813
+ let marker = finished.clone();
1814
+ let handle = thread::spawn(move || {
1815
+ let how = crate::vfs::OpenHow {
1816
+ write: true,
1817
+ create: true,
1818
+ ..crate::vfs::OpenHow::default()
1819
+ };
1820
+ let _ =
1821
+ crate::vfs::kern_path::filp_open(&Arc::default(), &overlay, "dir/new", &how);
1822
+ marker.store(1, Ordering::SeqCst);
1823
+ });
1824
+ let deadline = Instant::now() + Duration::from_millis(200);
1825
+ while finished.load(Ordering::SeqCst) == 0 && Instant::now() < deadline {
1826
+ thread::sleep(Duration::from_millis(5));
1827
+ }
1828
+ *self.finished_inside.lock().unwrap() = Some(finished.load(Ordering::SeqCst) == 1);
1829
+ *self.other.lock().unwrap() = Some(handle);
1015
1830
  }
1831
+ }
1016
1832
 
1017
- fn rename(&self, from: &str, to: &str, rename: Rename) -> Result<(), FilesystemError> {
1018
- self.memory.rename(from, to, rename)
1833
+ /// A Tmpfs whose directory beneath its root starts `Interleave` the first
1834
+ /// time one is listed, as a removal's emptiness check lists it.
1835
+ impl Hook for Interleave {
1836
+ fn open(
1837
+ &self,
1838
+ inner: &dyn InodeOperations,
1839
+ dentry: &Dentry,
1840
+ how: &OpenHow,
1841
+ ) -> Result<Box<dyn FileOperations>, FilesystemError> {
1842
+ if dentry.kind() == Ok(FileType::Dir) && !dentry.is_root() {
1843
+ self.start();
1844
+ }
1845
+ inner.open(dentry, how)
1019
1846
  }
1020
1847
  }
1021
1848
 
@@ -1025,113 +1852,112 @@ mod tests {
1025
1852
  // @behavior FS-037
1026
1853
  #[test]
1027
1854
  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")
1855
+ let tmpfs = tmpfs_with("dir/.keep", "");
1856
+ let interleave = Arc::new(Interleave::default());
1857
+ on(&tmpfs)
1858
+ .vfs_unlink("dir/.keep")
1037
1859
  .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
1860
+ let mount = crate::mount::Mount::new(
1861
+ "/data",
1862
+ Filesystem::new(Arc::new(over(
1863
+ Arc::new(Wrap::new(tmpfs, interleave.clone())),
1864
+ Arc::new(Tmpfs::new()),
1865
+ )))
1866
+ .expect("the instance must stand"),
1867
+ crate::mount::Access::ReadWrite,
1868
+ )
1869
+ .expect("/data is a guest path");
1870
+ let root = crate::vfs::Path::root(mount.mnt());
1871
+ *interleave.overlay.lock().unwrap() = Some(root.clone());
1872
+ let removed = crate::vfs::filename_rmdir(&Arc::default(), &root, "dir").is_ok();
1873
+ let other = interleave
1042
1874
  .other
1043
1875
  .lock()
1044
1876
  .unwrap()
1045
1877
  .take()
1046
- .expect("the other call must have started")
1047
- .join()
1048
- .expect("the other call must not panic");
1878
+ .expect("the other call must have started");
1879
+ crate::sched::testing::join("the other call", other);
1049
1880
  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"
1881
+ (
1882
+ removed,
1883
+ *interleave.finished_inside.lock().unwrap(),
1884
+ crate::vfs::vfs_fstatat(&Arc::default(), &root, "dir/new", false).is_ok()
1885
+ ),
1886
+ (true, Some(false), false),
1887
+ "a create through vfs::do_file_open into an Overlay directory vfs::filename_rmdir is removing must \
1888
+ wait for it, failing rather than landing in the directory being removed"
1054
1889
  );
1055
1890
  }
1056
1891
 
1057
1892
  // @behavior FS-038
1058
1893
  #[test]
1059
- fn a_call_through_an_overlay_from_inside_one_answers_a_deadlock() {
1894
+ fn a_change_through_an_overlay_from_inside_one_answers_a_deadlock() {
1895
+ type Answers = (bool, Result<(), FilesystemError>);
1896
+
1060
1897
  struct Reenters {
1061
- memory: Memory,
1062
- overlay: Mutex<Option<Overlay>>,
1063
- answer: Mutex<Option<Result<Metadata, FilesystemError>>>,
1898
+ overlay: Mutex<Option<crate::vfs::Path>>,
1899
+ answer: Mutex<Option<Answers>>,
1064
1900
  }
1065
1901
 
1066
- impl Filesystem for Reenters {
1067
- holds_no_links!();
1068
- never_repeats_identities!();
1069
- is_shared!();
1070
- matches_names_exactly!();
1071
-
1072
- fn open(
1902
+ impl Hook for Reenters {
1903
+ fn getattr(
1073
1904
  &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));
1905
+ inner: &dyn InodeOperations,
1906
+ dentry: &Dentry,
1907
+ ) -> Result<Kstat, FilesystemError> {
1908
+ let reentering = self.overlay.lock().unwrap().take();
1909
+ if let Some(overlay) = reentering {
1910
+ *self.answer.lock().unwrap() = Some((
1911
+ crate::vfs::vfs_fstatat(&Arc::default(), &overlay, &dentry.name(), false)
1912
+ .is_ok(),
1913
+ crate::vfs::filename_mkdirat(&Arc::default(), &overlay, "made"),
1914
+ ));
1083
1915
  }
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)
1916
+ inner.getattr(dentry)
1105
1917
  }
1106
1918
  }
1107
1919
 
1108
1920
  let layer = Arc::new(Reenters {
1109
- memory: [("note", "x")].into_iter().collect(),
1110
1921
  overlay: Mutex::new(None),
1111
1922
  answer: Mutex::new(None),
1112
1923
  });
1113
- let overlay = Overlay::new().layer(layer.clone(), Access::Read);
1114
- *layer.overlay.lock().unwrap() = Some(overlay.clone());
1115
- overlay
1116
- .metadata("note")
1924
+ let tmpfs: Tmpfs = [("note", "x")].into_iter().collect();
1925
+ let mount = crate::mount::Mount::new(
1926
+ "/data",
1927
+ Filesystem::new(Arc::new(over(
1928
+ Arc::new(Wrap::new(Arc::new(tmpfs), layer.clone())),
1929
+ Arc::new(Tmpfs::new()),
1930
+ )))
1931
+ .expect("the instance must stand"),
1932
+ crate::mount::Access::ReadWrite,
1933
+ )
1934
+ .expect("/data is a guest path");
1935
+ let root = crate::vfs::Path::root(mount.mnt());
1936
+ *layer.overlay.lock().unwrap() = Some(root.clone());
1937
+ crate::vfs::vfs_fstatat(&Arc::default(), &root, "note", false)
1117
1938
  .expect("the outer call must answer");
1939
+ let answers = layer.answer.lock().unwrap().take();
1118
1940
  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"
1941
+ answers,
1942
+ Some((true, Err(FilesystemError::Deadlock))),
1943
+ "a read through an Overlay made from inside one of its own reads on the same thread \
1944
+ must answer, and a change must answer Deadlock rather than wait forever"
1123
1945
  );
1124
1946
  }
1125
1947
 
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));
1948
+ /// An upper whose `upper` is seeded with `files`, with room for
1949
+ /// `inodes` more than its root, `upper`, and `work` take, over a lower
1950
+ /// Tmpfs holding `dir/file` = "lower".
1951
+ fn full_over_lower(files: &[(&str, &str)], inodes: usize) -> (KernPath, Arc<Tmpfs>) {
1952
+ let seeded = files
1953
+ .iter()
1954
+ .map(|(path, contents)| (format!("upper/{path}"), *contents));
1955
+ let upper = Tmpfs::from_iter(seeded).with_nr_inodes(Some(inodes + 3));
1130
1956
  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)
1957
+ (
1958
+ at(over(tmpfs_with("dir/file", "lower"), upper.clone())),
1959
+ upper,
1960
+ )
1135
1961
  }
1136
1962
 
1137
1963
  // The upper copy is all the room there is, so the whiteout cannot be
@@ -1139,32 +1965,37 @@ mod tests {
1139
1965
  // @behavior FS-040
1140
1966
  #[test]
1141
1967
  fn a_removal_that_cannot_leave_its_whiteout_removes_nothing() {
1142
- let (overlay, _) = full_over_lower(&[("dir/file", "upper")], 3);
1968
+ let (overlay, _) = full_over_lower(&[("dir/file", "upper")], 2);
1143
1969
  assert_eq!(
1144
- (overlay.remove_file("dir/file"), read(&overlay, "dir/file")),
1970
+ (overlay.vfs_unlink("dir/file"), read(&overlay, "dir/file")),
1145
1971
  (Err(FilesystemError::StorageFull), b"upper".to_vec()),
1146
- "a file removed through Overlay::remove_file whose whiteout finds no room must stay as \
1972
+ "a file removed through vfs_unlink whose whiteout finds no room must stay as \
1147
1973
  it was"
1148
1974
  );
1149
1975
  }
1150
1976
 
1977
+ // The directory is built in the workdir before it replaces the
1978
+ // whiteout, so a workdir with no room leaves the whiteout standing.
1151
1979
  // @behavior FS-041
1152
1980
  #[test]
1153
- fn a_directory_that_cannot_be_marked_opaque_is_not_made() {
1154
- let (overlay, upper) = full_over_lower(&[(".wh.dir", "")], 3);
1981
+ fn a_directory_made_over_a_whiteout_without_room_is_not_made() {
1982
+ let lower = Arc::new(Tmpfs::new());
1983
+ on(&lower)
1984
+ .vfs_mkdir("gone")
1985
+ .expect("the directory must be made");
1986
+ let upper = Arc::new(Tmpfs::new().with_nr_inodes(Some(4)));
1987
+ let overlay = at(over(lower, upper));
1988
+ overlay
1989
+ .vfs_rmdir("gone")
1990
+ .expect("the directory must be removed");
1155
1991
  assert_eq!(
1156
- (
1157
- overlay.create_dir("dir"),
1158
- overlay.metadata("dir"),
1159
- upper.metadata("dir")
1160
- ),
1992
+ (overlay.vfs_mkdir("gone"), overlay.vfs_lstat("gone")),
1161
1993
  (
1162
1994
  Err(FilesystemError::StorageFull),
1163
- Err(FilesystemError::NotFound),
1164
1995
  Err(FilesystemError::NotFound)
1165
1996
  ),
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"
1997
+ "a directory made through vfs_mkdir over a whiteout, with no room left, \
1998
+ must be refused as out of space and leave the name hidden"
1168
1999
  );
1169
2000
  }
1170
2001
 
@@ -1173,11 +2004,11 @@ mod tests {
1173
2004
  // @behavior FS-042
1174
2005
  #[test]
1175
2006
  fn a_rename_that_cannot_leave_its_whiteout_moves_nothing() {
1176
- let (overlay, _) = full_over_lower(&[], 3);
2007
+ let (overlay, _) = full_over_lower(&[], 2);
1177
2008
  assert_eq!(
1178
2009
  (
1179
- overlay.rename("dir/file", "dir/moved", Rename::Any),
1180
- overlay.metadata("dir/moved"),
2010
+ overlay.rename("dir/file", &overlay, "dir/moved"),
2011
+ overlay.vfs_lstat("dir/moved"),
1181
2012
  read(&overlay, "dir/file")
1182
2013
  ),
1183
2014
  (
@@ -1190,149 +2021,39 @@ mod tests {
1190
2021
  );
1191
2022
  }
1192
2023
 
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
2024
  // @behavior FS-043
1304
2025
  #[test]
1305
2026
  fn a_directory_a_lower_layer_holds_does_not_move_through_an_overlay() {
1306
- let (overlay, _) = overlay_on(Arc::new(Memory::new()));
2027
+ let (overlay, _) = overlay_on(Arc::new(Tmpfs::new()));
1307
2028
  assert_eq!(
1308
2029
  (
1309
- overlay.rename("dir", "moved", Rename::Any),
1310
- overlay.metadata("moved")
2030
+ overlay.rename("dir", &overlay, "moved"),
2031
+ overlay.vfs_lstat("moved")
1311
2032
  ),
1312
2033
  (
1313
2034
  Err(FilesystemError::CrossesDevices),
1314
2035
  Err(FilesystemError::NotFound)
1315
2036
  ),
1316
- "a directory a read-only Layer holds renamed through Overlay::rename must be refused \
2037
+ "a directory a lower Layer holds renamed through Overlay::rename must be refused \
1317
2038
  as crossing devices, as overlayfs refuses it"
1318
2039
  );
1319
2040
  }
1320
2041
 
1321
- // The directory shows only from the read-only Layer, so only a check of
2042
+ // The directory shows only from the lower Layer, so only a check of
1322
2043
  // the whole view finds it in the way.
1323
2044
  // @behavior FS-044
1324
2045
  #[test]
1325
2046
  fn a_rename_onto_what_an_overlay_shows_is_checked_against_the_view() {
1326
- let upper = Arc::new(Memory::new());
2047
+ let upper = Arc::new(Tmpfs::new());
1327
2048
  let (overlay, _) = overlay_on(upper.clone());
1328
2049
  overwrite(&overlay, "note", b"x");
1329
2050
  overlay
1330
- .create_dir("empty")
2051
+ .vfs_mkdir("empty")
1331
2052
  .expect("the directory must be made");
1332
2053
  assert_eq!(
1333
2054
  (
1334
- overlay.rename("note", "dir", Rename::Any),
1335
- overlay.rename("empty", "dir", Rename::Any),
2055
+ overlay.rename("note", &overlay, "dir"),
2056
+ overlay.rename("empty", &overlay, "dir"),
1336
2057
  read(&overlay, "dir/file")
1337
2058
  ),
1338
2059
  (
@@ -1340,7 +2061,7 @@ mod tests {
1340
2061
  Err(FilesystemError::DirectoryNotEmpty),
1341
2062
  b"lower".to_vec()
1342
2063
  ),
1343
- "a rename through Overlay::rename onto a directory only a read-only Layer shows must \
2064
+ "a rename through Overlay::rename onto a directory only a lower Layer shows must \
1344
2065
  be refused as rename(2) refuses it, leaving that directory as it was"
1345
2066
  );
1346
2067
  }
@@ -1350,39 +2071,245 @@ mod tests {
1350
2071
  // @behavior FS-045
1351
2072
  #[test]
1352
2073
  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);
2074
+ let upper = Arc::new(Tmpfs::new());
2075
+ let lower: Arc<Tmpfs> = Arc::new([("dir/.keep", "")].into_iter().collect());
2076
+ on(&lower)
2077
+ .vfs_unlink("dir/.keep")
2078
+ .expect("the seed must go");
2079
+ let overlay = at(over(lower.clone(), upper.clone()));
1359
2080
  overlay
1360
- .create_dir("made")
2081
+ .vfs_mkdir("made")
1361
2082
  .expect("the directory must be made");
1362
2083
  overlay
1363
- .rename("made", "dir", Rename::Any)
2084
+ .rename("made", &overlay, "dir")
1364
2085
  .expect("the directory must move over the empty one");
2086
+ overwrite(&on(&lower), "dir/later", b"lower");
1365
2087
  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"
2088
+ names(&overlay, "dir"),
2089
+ Vec::<String>::new(),
2090
+ "a directory moved through Overlay::rename over one a lower Layer holds must be \
2091
+ marked opaque, so a file the lower Layer gains there later does not show"
2092
+ );
2093
+ }
2094
+
2095
+ // A change is built in `work` before it lands, so `work` holds nothing
2096
+ // once each change is done, and only `upper` holds what the view shows.
2097
+ // @behavior FS-128
2098
+ #[test]
2099
+ fn an_overlay_keeps_its_changes_in_its_tmpfss_upper() {
2100
+ let upper = Arc::new(Tmpfs::new());
2101
+ let (overlay, _) = overlay_on(upper.clone());
2102
+ overwrite(&overlay, "dir/file", b"changed");
2103
+ overlay
2104
+ .vfs_unlink("dir/file")
2105
+ .expect("the file must be removed");
2106
+ assert_eq!(
2107
+ (names(&on(&upper), ""), names(&on(&upper), "work")),
2108
+ (
2109
+ vec!["upper".to_string(), "work".to_string()],
2110
+ Vec::<String>::new()
2111
+ ),
2112
+ "an Overlay's upper Tmpfs, after a copy up and a removal through it, must hold only \
2113
+ upper and an empty work"
2114
+ );
2115
+ }
2116
+
2117
+ // The upper's `dir` holds the whiteout the file's removal left, so only
2118
+ // clearing it before the removal lets what is exchanged out go.
2119
+ // @behavior FS-134
2120
+ #[test]
2121
+ fn a_directory_removed_through_an_overlay_leaves_nothing_in_its_workdir() {
2122
+ let upper = Arc::new(Tmpfs::new());
2123
+ let (overlay, _) = overlay_on(upper.clone());
2124
+ overlay
2125
+ .vfs_unlink("dir/file")
2126
+ .expect("the file must be removed");
2127
+ overlay
2128
+ .vfs_rmdir("dir")
2129
+ .expect("the emptied directory must be removed");
2130
+ assert_eq!(
2131
+ (names(&on(&upper), "work"), kind(&overlay, "dir")),
2132
+ (Vec::<String>::new(), Err(FilesystemError::NotFound)),
2133
+ "vfs_rmdir of a lower directory whose file vfs_unlink removed \
2134
+ must leave the upper Tmpfs's work empty and the directory gone"
2135
+ );
2136
+ }
2137
+
2138
+ // The open writes nothing, so only the copy taking the lower file's
2139
+ // times, rather than the time it was made, keeps them.
2140
+ // @behavior FS-129
2141
+ #[test]
2142
+ fn a_copy_up_keeps_the_lower_files_times() {
2143
+ let lower = tmpfs_with("dir/file", "lower");
2144
+ let before = on(&lower)
2145
+ .vfs_lstat("dir/file")
2146
+ .expect("the lower file must stand");
2147
+ std::thread::sleep(Duration::from_millis(10));
2148
+ let overlay = at(over(lower, Arc::new(Tmpfs::new())));
2149
+ overlay
2150
+ .open(
2151
+ "dir/file",
2152
+ &OpenHow {
2153
+ write: true,
2154
+ ..OpenHow::default()
2155
+ },
2156
+ )
2157
+ .expect("the open must copy the file up");
2158
+ assert_eq!(
2159
+ overlay.vfs_lstat("dir/file").map(|stat| stat.mtime),
2160
+ Ok(before.mtime),
2161
+ "a lower file opened to write through Overlay::open, writing nothing, must keep the \
2162
+ lower file's modified time once copied up"
2163
+ );
2164
+ }
2165
+
2166
+ // The second copy up lands in a directory the first made, so only
2167
+ // restoring its times leaves what the view reports unchanged.
2168
+ // @behavior FS-130
2169
+ #[test]
2170
+ fn a_copy_up_leaves_its_directorys_times_as_they_were() {
2171
+ let lower: Arc<Tmpfs> = Arc::new([("dir/a", "a"), ("dir/b", "b")].into_iter().collect());
2172
+ let overlay = at(over(lower, Arc::new(Tmpfs::new())));
2173
+ overwrite(&overlay, "dir/a", b"changed");
2174
+ let before = overlay
2175
+ .vfs_lstat("dir")
2176
+ .expect("the directory must stand")
2177
+ .mtime;
2178
+ std::thread::sleep(Duration::from_millis(10));
2179
+ overlay
2180
+ .open(
2181
+ "dir/b",
2182
+ &OpenHow {
2183
+ write: true,
2184
+ ..OpenHow::default()
2185
+ },
2186
+ )
2187
+ .expect("the open must copy the file up");
2188
+ assert_eq!(
2189
+ overlay.vfs_lstat("dir").map(|stat| stat.mtime),
2190
+ Ok(before),
2191
+ "a lower file copied up through Overlay::open into a directory the upper holds must \
2192
+ leave that directory's modified time as it was"
2193
+ );
2194
+ }
2195
+
2196
+ /// An Overlay over an empty lower Layer under the Tmpfs `upper`, and
2197
+ /// that same Tmpfs mounted on its own, each reached through the VFS.
2198
+ fn beside_its_upper(upper: Tmpfs) -> (crate::vfs::Path, crate::vfs::Path) {
2199
+ let upper = Filesystem::new(Arc::new(upper)).expect("the instance must stand");
2200
+ let overlay = Overlay::new(
2201
+ vec![Filesystem::new(Arc::new(Tmpfs::new())).expect("the instance must stand")],
2202
+ Some(upper.clone()),
2203
+ )
2204
+ .expect("an upper over a lower Layer must make a view");
2205
+ let root = |filesystem| {
2206
+ let mount =
2207
+ crate::mount::Mount::new("/data", filesystem, crate::mount::Access::ReadWrite)
2208
+ .expect("/data is a guest path");
2209
+ crate::vfs::Path::root(mount.mnt())
2210
+ };
2211
+ (
2212
+ root(Filesystem::new(Arc::new(overlay)).expect("the instance must stand")),
2213
+ root(upper),
2214
+ )
2215
+ }
2216
+
2217
+ // The move holds the directory it leaves and the one above it, and the
2218
+ // removal holds the same two, so only taking the one above first in
2219
+ // both keeps them from waiting on each other; unordered, they wait
2220
+ // within a few moves.
2221
+ // @behavior FS-131
2222
+ #[test]
2223
+ fn an_overlay_and_a_mount_of_its_upper_never_wait_on_each_other() {
2224
+ let (overlay, upper) = beside_its_upper(
2225
+ [("upper/a/b/c", "c"), ("upper/a/b/keep", "")]
2226
+ .into_iter()
2227
+ .collect(),
2228
+ );
2229
+ let moved = Arc::new(AtomicBool::new(false));
2230
+ let removing = {
2231
+ let moved = moved.clone();
2232
+ thread::spawn(move || {
2233
+ while !moved.load(Ordering::SeqCst) {
2234
+ let _ = crate::vfs::filename_rmdir(&Arc::default(), &upper, "upper/a/b");
2235
+ }
2236
+ })
2237
+ };
2238
+ let (done, finished) = std::sync::mpsc::channel();
2239
+ thread::spawn(move || {
2240
+ for _ in 0..500 {
2241
+ crate::vfs::filename_renameat2(&Arc::default(), &overlay, "a/b/c", &overlay, "a/c")
2242
+ .expect("the file must move up");
2243
+ crate::vfs::filename_renameat2(&Arc::default(), &overlay, "a/c", &overlay, "a/b/c")
2244
+ .expect("the file must move back");
2245
+ }
2246
+ let _ = done.send(());
2247
+ });
2248
+ let returned = finished
2249
+ .recv_timeout(crate::sched::testing::MAX_WAIT)
2250
+ .is_ok();
2251
+ moved.store(true, Ordering::SeqCst);
2252
+ if returned {
2253
+ crate::sched::testing::join("the removal", removing);
2254
+ }
2255
+ assert!(
2256
+ returned,
2257
+ "vfs::filename_renameat2 of a file up one directory through an Overlay, while vfs::filename_rmdir \
2258
+ removes that directory through a Mount of its upper Tmpfs, must return"
2259
+ );
2260
+ }
2261
+
2262
+ // The make holds only the directory it makes in, so only the removal
2263
+ // holding that directory too, and removing it only once empty, keeps
2264
+ // what the make made from going with it. The two land in the same
2265
+ // instant only a few times in this many tries.
2266
+ // @behavior FS-132
2267
+ #[test]
2268
+ fn a_removal_through_an_overlay_waits_for_a_change_in_that_directory() {
2269
+ let (overlay, upper) = beside_its_upper([("upper/a/.keep", "")].into_iter().collect());
2270
+ let stop = Arc::new(AtomicBool::new(false));
2271
+ let churning = {
2272
+ let stop = stop.clone();
2273
+ thread::spawn(move || {
2274
+ while !stop.load(Ordering::SeqCst) {
2275
+ let _ = crate::vfs::filename_mkdirat(&Arc::default(), &overlay, "a/b");
2276
+ let _ = crate::vfs::filename_rmdir(&Arc::default(), &overlay, "a/b");
2277
+ }
2278
+ })
2279
+ };
2280
+ let mut lost = 0;
2281
+ for _ in 0..100_000 {
2282
+ if crate::vfs::filename_mkdirat(&Arc::default(), &upper, "upper/a/b/made").is_ok() {
2283
+ match crate::vfs::vfs_fstatat(&Arc::default(), &upper, "upper/a/b/made", false) {
2284
+ Ok(_) => {
2285
+ let _ =
2286
+ crate::vfs::filename_rmdir(&Arc::default(), &upper, "upper/a/b/made");
2287
+ }
2288
+ Err(_) => lost += 1,
2289
+ }
2290
+ }
2291
+ }
2292
+ stop.store(true, Ordering::SeqCst);
2293
+ churning.join().expect("the Overlay's calls must not panic");
2294
+ assert_eq!(
2295
+ lost, 0,
2296
+ "vfs::filename_mkdirat in a directory through a Mount of an Overlay's upper Tmpfs, while \
2297
+ vfs::filename_rmdir removes that directory through the Overlay, must leave every \
2298
+ directory it made standing"
1372
2299
  );
1373
2300
  }
1374
2301
 
1375
- // The writable Layer has room for the copy's entry but for less than
2302
+ // The upper has room for the copy's entry but for less than
1376
2303
  // the lower file's bytes, so the copy fails part way.
1377
2304
  // @behavior FS-046
1378
2305
  #[test]
1379
2306
  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)));
2307
+ let upper = Arc::new(Tmpfs::new().with_size(Some(2)).with_nr_inodes(Some(8)));
1381
2308
  let (overlay, _) = overlay_on(upper.clone());
1382
2309
  assert_eq!(
1383
2310
  (
1384
- write(&overlay, "dir/file", b"x"),
1385
- upper.metadata("dir/file"),
2311
+ write_over(&overlay, "dir/file", b"x"),
2312
+ on(&upper).vfs_lstat("upper/dir/file"),
1386
2313
  read(&overlay, "dir/file")
1387
2314
  ),
1388
2315
  (
@@ -1395,374 +2322,894 @@ mod tests {
1395
2322
  );
1396
2323
  }
1397
2324
 
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,
2325
+ /// A Tmpfs answering a stat of `dir` at any depth with an I/O failure,
2326
+ /// though it opens `dir` as any other directory.
2327
+ struct StatsNoDir;
2328
+
2329
+ impl Hook for StatsNoDir {
2330
+ fn getattr(
2331
+ &self,
2332
+ inner: &dyn InodeOperations,
2333
+ dentry: &Dentry,
2334
+ ) -> Result<Kstat, FilesystemError> {
2335
+ if dentry.name() == "dir" {
2336
+ return Err(FilesystemError::Other("dir has no stat".to_string()));
2337
+ }
2338
+ inner.getattr(dentry)
2339
+ }
1403
2340
  }
1404
2341
 
1405
- struct TrickleFile {
1406
- file: Box<dyn File>,
1407
- refuses_close: bool,
2342
+ /// A Tmpfs whose `file` is gone after its first lookup, as a host
2343
+ /// directory another process changes mid-call.
2344
+ #[derive(Default)]
2345
+ struct Vanishes {
2346
+ seen: AtomicBool,
1408
2347
  }
1409
2348
 
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)
2349
+ impl Hook for Vanishes {
2350
+ fn getattr(
2351
+ &self,
2352
+ inner: &dyn InodeOperations,
2353
+ dentry: &Dentry,
2354
+ ) -> Result<Kstat, FilesystemError> {
2355
+ if dentry.name() == "file" && self.seen.swap(true, Ordering::SeqCst) {
2356
+ return Err(FilesystemError::NotFound);
2357
+ }
2358
+ inner.getattr(dentry)
1413
2359
  }
2360
+ }
2361
+
2362
+ /// A Tmpfs storing names in lower case and reaching each by any
2363
+ /// case, as a host that folds case does, answering each call by the
2364
+ /// folded path from its root.
2365
+ #[derive(Clone)]
2366
+ struct FoldsCase {
2367
+ root: KernPath,
2368
+ }
1414
2369
 
1415
- fn write_at(&mut self, buf: &[u8], offset: u64) -> Result<usize, FilesystemError> {
1416
- self.file.write_at(&buf[..buf.len().min(3)], offset)
2370
+ impl FoldsCase {
2371
+ fn new(tmpfs: Tmpfs) -> Self {
2372
+ Self { root: at(tmpfs) }
1417
2373
  }
2374
+ }
2375
+
2376
+ /// The path `dentry` stands at beneath the root, folded.
2377
+ fn folded_path_of(dentry: &Dentry) -> String {
2378
+ dentry.path_raw()[1..].to_lowercase()
2379
+ }
1418
2380
 
1419
- fn append(&mut self, buf: &[u8]) -> Result<(u64, usize), FilesystemError> {
1420
- self.file.append(&buf[..buf.len().min(3)])
2381
+ /// A directory of a `FoldsCase`, open.
2382
+ struct FoldedDir(KernPath);
2383
+
2384
+ impl FileOperations for FoldedDir {
2385
+ fn iterate_shared(
2386
+ &mut self,
2387
+ dentry: &Dentry,
2388
+ ctx: &mut DirContext<'_>,
2389
+ ) -> Result<(), FilesystemError> {
2390
+ let entries = match folded_path_of(dentry).as_str() {
2391
+ "" => self.0.iterate_dir(),
2392
+ at => self.0.open_dir(at)?.iterate_dir(),
2393
+ }?;
2394
+ crate::filesystem::Rdir::default().iterate(dentry, ctx, || Ok(entries))
1421
2395
  }
2396
+ }
1422
2397
 
1423
- fn metadata(&self) -> Result<Metadata, FilesystemError> {
1424
- self.file.metadata()
2398
+ impl FileSystemType for FoldsCase {
2399
+ fn fill_super(self: Arc<Self>, sb: &mut Filesystem) -> Result<(), FilesystemError> {
2400
+ sb.d_make_root(Inode::new(&Kstat::new(FileType::Dir, 0), self));
2401
+ Ok(())
1425
2402
  }
2403
+ }
1426
2404
 
1427
- fn set_len(&mut self, len: u64) -> Result<(), FilesystemError> {
1428
- self.file.set_len(len)
2405
+ impl InodeOperations for FoldsCase {
2406
+ fn getattr(&self, dentry: &Dentry) -> Result<Kstat, FilesystemError> {
2407
+ self.root.vfs_lstat(&folded_path_of(dentry))
1429
2408
  }
1430
2409
 
1431
- fn close(self: Box<Self>) -> Result<(), FilesystemError> {
1432
- if self.refuses_close {
1433
- return Err(FilesystemError::PermissionDenied);
2410
+ fn lookup(&self, dentry: &Dentry) -> Result<Lookup, FilesystemError> {
2411
+ let name = dentry.name();
2412
+ let lower = name.to_lowercase();
2413
+ match self.root.vfs_lstat(&folded_path_of(dentry)) {
2414
+ Ok(found) if lower != name => Ok(Lookup::Spelled {
2415
+ name: lower,
2416
+ inode: Inode::new(&found, Arc::new(self.clone())),
2417
+ }),
2418
+ Ok(found) => Ok(Lookup::Found(Inode::new(&found, Arc::new(self.clone())))),
2419
+ Err(FilesystemError::NotFound) => Ok(Lookup::Absent),
2420
+ Err(err) => Err(err),
1434
2421
  }
1435
- self.file.close()
1436
2422
  }
1437
- }
1438
-
1439
- impl Filesystem for Trickles {
1440
- holds_no_links!();
1441
- never_repeats_identities!();
1442
- is_shared!();
1443
- matches_names_exactly!();
1444
2423
 
1445
2424
  fn open(
1446
2425
  &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
- }))
2426
+ dentry: &Dentry,
2427
+ how: &OpenHow,
2428
+ ) -> Result<Box<dyn FileOperations>, FilesystemError> {
2429
+ if dentry.kind() == Ok(FileType::Dir) {
2430
+ return Ok(Box::new(FoldedDir(self.root.clone())));
2431
+ }
2432
+ self.root.open(&folded_path_of(dentry), how)
1454
2433
  }
1455
2434
 
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)
2435
+ fn atomic_open(&self, dentry: &Dentry, how: &OpenHow) -> Result<Finish, FilesystemError> {
2436
+ if dentry.in_lookup() {
2437
+ let found = self.lookup(dentry)?;
2438
+ if !how.create || !matches!(found, Lookup::Absent) {
2439
+ return Ok(Finish::Lookup(found));
2440
+ }
2441
+ }
2442
+ if !how.create {
2443
+ return Ok(Finish::NoOpen { created: false });
2444
+ }
2445
+ let file = self.root.create(&folded_path_of(dentry), how)?;
2446
+ Ok(Finish::Open {
2447
+ file,
2448
+ created: true,
2449
+ })
1466
2450
  }
1467
2451
 
1468
- fn remove_dir(&self, path: &str) -> Result<(), FilesystemError> {
1469
- self.memory.remove_dir(path)
2452
+ fn mkdir(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
2453
+ self.root.vfs_mkdir(&folded_path_of(dentry))
1470
2454
  }
1471
2455
 
1472
- fn remove_file(&self, path: &str) -> Result<(), FilesystemError> {
1473
- self.memory.remove_file(path)
2456
+ fn rmdir(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
2457
+ self.root.vfs_rmdir(&folded_path_of(dentry))
1474
2458
  }
1475
2459
 
1476
- fn rename(&self, from: &str, to: &str, rename: Rename) -> Result<(), FilesystemError> {
1477
- self.memory.rename(from, to, rename)
2460
+ fn unlink(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
2461
+ self.root.vfs_unlink(&folded_path_of(dentry))
1478
2462
  }
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
2463
 
1490
- fn open(
2464
+ fn rename(
1491
2465
  &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)
2466
+ from: &Dentry,
2467
+ _to_dir: &dyn InodeOperations,
2468
+ to: &Dentry,
2469
+ _flag: Option<RenameFlag>,
2470
+ ) -> Result<(), FilesystemError> {
2471
+ self.root
2472
+ .rename(&folded_path_of(from), &self.root, &folded_path_of(to))
1508
2473
  }
2474
+ }
1509
2475
 
1510
- fn remove_dir(&self, _path: &str) -> Result<(), FilesystemError> {
1511
- Err(FilesystemError::PermissionDenied)
1512
- }
2476
+ /// What `path` names through `fs`, or why it names nothing.
2477
+ fn kind(fs: &KernPath, path: &str) -> Result<FileType, FilesystemError> {
2478
+ fs.vfs_lstat(path).map(|metadata| metadata.file_type)
2479
+ }
1513
2480
 
1514
- fn remove_file(&self, path: &str) -> Result<(), FilesystemError> {
1515
- self.0.remove_file(path)
1516
- }
2481
+ // The inner directory is opened too, so only directories held as the
2482
+ // Layers' directories themselves, not reached again by their path,
2483
+ // take it along.
2484
+ // @behavior FS-110
2485
+ #[test]
2486
+ fn a_directory_opened_from_an_overlay_follows_it_when_it_moves() {
2487
+ let upper = Arc::new(Tmpfs::new());
2488
+ let overlay = at(over(Arc::new(Tmpfs::new()), upper.clone()));
2489
+ overlay.vfs_mkdir("dir").expect("dir must be made");
2490
+ overlay.vfs_mkdir("dir/inner").expect("inner must be made");
2491
+ let dir = overlay.open_dir("dir").expect("dir must open");
2492
+ let inner = overlay.open_dir("dir/inner").expect("inner must open");
2493
+ overlay
2494
+ .rename("dir", &overlay, "moved")
2495
+ .expect("the directory must move");
2496
+ overwrite(&dir, "a", b"x");
2497
+ overwrite(&inner, "b", b"x");
2498
+ assert_eq!(
2499
+ (
2500
+ kind(&overlay, "moved/a"),
2501
+ kind(&overlay, "moved/inner/b"),
2502
+ kind(&overlay, "dir")
2503
+ ),
2504
+ (
2505
+ Ok(FileType::RegFile),
2506
+ Ok(FileType::RegFile),
2507
+ Err(FilesystemError::NotFound)
2508
+ ),
2509
+ "files made through directories Overlay::open_dir opened before Overlay::rename moved \
2510
+ the outer one must stand beneath its new path"
2511
+ );
2512
+ }
1517
2513
 
1518
- fn rename(&self, _from: &str, _to: &str, _rename: Rename) -> Result<(), FilesystemError> {
1519
- Err(FilesystemError::PermissionDenied)
1520
- }
2514
+ // @behavior FS-111
2515
+ #[test]
2516
+ fn an_overlay_directory_removed_while_opened_makes_nothing_more() {
2517
+ let overlay = at(over(Arc::new(Tmpfs::new()), Arc::new(Tmpfs::new())));
2518
+ overlay.vfs_mkdir("dir").expect("dir must be made");
2519
+ let dir = overlay.open_dir("dir").expect("dir must open");
2520
+ overlay.vfs_rmdir("dir").expect("dir must remove");
2521
+ overlay.vfs_mkdir("dir").expect("another dir must be made");
2522
+ assert_eq!(
2523
+ (
2524
+ write(&dir, "made", b"x"),
2525
+ dir.iterate_dir().map(|entries| entries.len()),
2526
+ names(&overlay, "dir").len()
2527
+ ),
2528
+ (Err(FilesystemError::NotFound), Ok(0), 0),
2529
+ "a directory Overlay::open_dir opened, once vfs_rmdir removed it and another \
2530
+ was made in its place, must make nothing, list nothing, and leave the new one empty"
2531
+ );
1521
2532
  }
1522
2533
 
1523
- /// A Memory refusing to remove a whiteout.
1524
- struct KeepsWhiteouts(Memory);
2534
+ // A change made to a Layer outside its Overlay is the host's, so the
2535
+ // opened directory keeps the Layer's directory it holds, as a
2536
+ // descriptor keeps its directory, rather than reach the one made in
2537
+ // its place.
2538
+ // @behavior FS-112
2539
+ #[test]
2540
+ fn an_overlay_directory_keeps_its_layers_directory_through_a_change_outside_it() {
2541
+ let upper = tmpfs_with("upper/dir/file", "x");
2542
+ let overlay = at(over(Arc::new(Tmpfs::new()), upper.clone()));
2543
+ let dir = overlay.open_dir("dir").expect("dir must open");
2544
+ let outside = on(&upper);
2545
+ outside
2546
+ .rename("upper/dir", &outside, "upper/moved")
2547
+ .expect("the Layer must move it");
2548
+ outside
2549
+ .vfs_mkdir("upper/dir")
2550
+ .expect("the Layer must make another");
2551
+ overwrite(&outside, "upper/dir/file", b"other");
2552
+ assert_eq!(
2553
+ read(&dir, "file"),
2554
+ b"x".to_vec(),
2555
+ "a read through a directory Overlay::open_dir opened, once its Layer moved it away \
2556
+ and made another in its place outside the Overlay, must read the directory it \
2557
+ opened"
2558
+ );
2559
+ }
1525
2560
 
1526
- impl Filesystem for KeepsWhiteouts {
1527
- holds_no_links!();
1528
- never_repeats_identities!();
1529
- is_shared!();
1530
- matches_names_exactly!();
2561
+ /// A lower Layer running what it holds right after its first open, while a
2562
+ /// file of it is being opened through the view.
2563
+ #[derive(Default)]
2564
+ struct RunsAfterOpen(Mutex<Option<Box<dyn FnOnce() + Send>>>);
1531
2565
 
2566
+ impl Hook for RunsAfterOpen {
1532
2567
  fn open(
1533
2568
  &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),
2569
+ inner: &dyn InodeOperations,
2570
+ dentry: &Dentry,
2571
+ how: &OpenHow,
2572
+ ) -> Result<Box<dyn FileOperations>, FilesystemError> {
2573
+ let opened = inner.open(dentry, how);
2574
+ let run = self.0.lock().expect("never poisoned").take();
2575
+ if let Some(run) = run {
2576
+ crate::sched::testing::join("the writer", thread::spawn(run));
1560
2577
  }
2578
+ opened
1561
2579
  }
2580
+ }
1562
2581
 
1563
- fn rename(&self, from: &str, to: &str, rename: Rename) -> Result<(), FilesystemError> {
1564
- self.0.rename(from, to, rename)
1565
- }
2582
+ // The copy up lands between the lower open and whatever the file does to
2583
+ // learn of one, so only a file asking where the data is at each read sees
2584
+ // it. A count taken after the open would say no reader was there to tell.
2585
+ // @behavior FS-157
2586
+ #[test]
2587
+ fn a_file_opened_as_a_copy_up_lands_reads_the_copy() {
2588
+ let hook = Arc::new(RunsAfterOpen::default());
2589
+ let lower = Wrap::new(tmpfs_with("note", "lower"), hook.clone());
2590
+ let overlay = at(over(Arc::new(lower), Arc::new(Tmpfs::new())));
2591
+ *hook.0.lock().expect("never poisoned") = Some({
2592
+ let writer = overlay.clone();
2593
+ Box::new(move || overwrite(&writer, "note", b"upper"))
2594
+ });
2595
+ assert_eq!(
2596
+ read(&overlay, "note"),
2597
+ b"upper".to_vec(),
2598
+ "a read through a file of a lower Layer opened while a write copied it up must read \
2599
+ the copy"
2600
+ );
1566
2601
  }
1567
2602
 
1568
- /// A Memory answering a lookup of any marker name with an I/O failure.
1569
- struct ReadsNoMarker(Memory);
2603
+ // A descriptor held across the removal still names the lower file, while
2604
+ // what stands at its name now is another entry, so only a copy up refusing
2605
+ // a name taken keeps the lower Layer from being asked.
2606
+ // @behavior FS-158
2607
+ #[test]
2608
+ fn a_time_set_on_a_file_whose_name_was_taken_leaves_its_lower_layer() {
2609
+ use crate::mount::{Access, Mount};
2610
+ use crate::vfs::{self, OpenHow, Path};
2611
+ let asked = Arc::new(AtomicUsize::new(0));
2612
+ let overlay = Overlay::new(
2613
+ vec![Filesystem::new(Arc::new(KeepsTimes(asked.clone())))
2614
+ .expect("the instance must stand")],
2615
+ Some(Filesystem::new(Arc::new(Tmpfs::new())).expect("the instance must stand")),
2616
+ )
2617
+ .expect("the Overlay must stack");
2618
+ let mount = Mount::new(
2619
+ "/data",
2620
+ Filesystem::new(Arc::new(overlay)).expect("the instance must stand"),
2621
+ Access::ReadWrite,
2622
+ )
2623
+ .expect("/data is a guest path");
2624
+ let root = Path::root(mount.mnt());
2625
+ let cred = Arc::default();
2626
+ let (read, create) = (
2627
+ OpenHow {
2628
+ read: true,
2629
+ ..OpenHow::default()
2630
+ },
2631
+ OpenHow {
2632
+ write: true,
2633
+ create: true,
2634
+ ..OpenHow::default()
2635
+ },
2636
+ );
2637
+ let held = vfs::kern_path::filp_open(&cred, &root, "file", &read)
2638
+ .unwrap_or_else(|_| panic!("the file must open"));
2639
+ vfs::filename_unlinkat(&cred, &root, "file").expect("the file must be removed");
2640
+ drop(vfs::kern_path::filp_open(&cred, &root, "file", &create));
2641
+ let answer = vfs::do_utimes_fd(
2642
+ &cred,
2643
+ held.path(),
2644
+ &Iattr::times(None, Some(crate::vfs::SetTime::Now)),
2645
+ );
2646
+ assert_eq!(
2647
+ (answer, asked.load(Ordering::SeqCst)),
2648
+ (Err(FilesystemError::AlreadyExists), 0),
2649
+ "vfs::do_utimes_fd on a held lower file whose name another entry took must answer \
2650
+ EEXIST and leave the lower Layer unasked"
2651
+ );
2652
+ }
1570
2653
 
1571
- impl Filesystem for ReadsNoMarker {
1572
- holds_no_links!();
1573
- never_repeats_identities!();
1574
- is_shared!();
1575
- matches_names_exactly!();
2654
+ // The held file's directory was removed and made again, and the upper
2655
+ // directory it knows of is the removed one, so nothing lands in the new one.
2656
+ // @behavior FS-179
2657
+ #[test]
2658
+ fn a_held_file_of_a_directory_made_again_stays_out_of_it() {
2659
+ use crate::mount::{Access, Mount};
2660
+ use crate::vfs::{self, OpenHow, Path};
2661
+ let lower: Tmpfs = [("d/f", "removed")].into_iter().collect();
2662
+ let overlay = Overlay::new(
2663
+ vec![Filesystem::new(Arc::new(lower)).expect("the instance must stand")],
2664
+ Some(Filesystem::new(Arc::new(Tmpfs::new())).expect("the instance must stand")),
2665
+ )
2666
+ .expect("the Overlay must stack");
2667
+ let mount = Mount::new(
2668
+ "/data",
2669
+ Filesystem::new(Arc::new(overlay)).expect("the instance must stand"),
2670
+ Access::ReadWrite,
2671
+ )
2672
+ .expect("/data is a guest path");
2673
+ let root = Path::root(mount.mnt());
2674
+ let cred = Arc::default();
2675
+ let read = OpenHow {
2676
+ read: true,
2677
+ ..OpenHow::default()
2678
+ };
2679
+ let held = vfs::kern_path::filp_open(&cred, &root, "d/f", &read)
2680
+ .unwrap_or_else(|_| panic!("the file must open"));
2681
+ vfs::filename_unlinkat(&cred, &root, "d/f").expect("the file must be removed");
2682
+ vfs::filename_rmdir(&cred, &root, "d").expect("the directory must be removed");
2683
+ vfs::filename_mkdirat(&cred, &root, "d").expect("the directory must be made again");
2684
+ let answer = vfs::do_utimes_fd(
2685
+ &cred,
2686
+ held.path(),
2687
+ &Iattr::times(None, Some(crate::vfs::SetTime::Now)),
2688
+ );
2689
+ assert_eq!(
2690
+ (
2691
+ answer,
2692
+ vfs::vfs_fstatat(&cred, &root, "d/f", false).map(|_| ())
2693
+ ),
2694
+ (
2695
+ Err(FilesystemError::NotFound),
2696
+ Err(FilesystemError::NotFound)
2697
+ ),
2698
+ "vfs::do_utimes_fd on a held lower file whose directory was removed and made again \
2699
+ must answer ENOENT and leave the file out of the view"
2700
+ );
2701
+ }
1576
2702
 
1577
- fn open(
1578
- &self,
1579
- path: &str,
1580
- options: &OpenOptions,
1581
- ) -> Result<Box<dyn File>, FilesystemError> {
1582
- self.0.open(path, options)
1583
- }
2703
+ /// Slows the first ask about the directory `e` once armed, so a second copy
2704
+ /// up of it finds the first under way.
2705
+ struct SlowsDirectory(AtomicBool);
1584
2706
 
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
- ));
2707
+ impl Hook for SlowsDirectory {
2708
+ fn getattr(
2709
+ &self,
2710
+ inner: &dyn InodeOperations,
2711
+ dentry: &Dentry,
2712
+ ) -> Result<Kstat, FilesystemError> {
2713
+ if dentry.name() == "e" && self.0.swap(false, Ordering::SeqCst) {
2714
+ thread::sleep(Duration::from_millis(100));
1594
2715
  }
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)
2716
+ inner.getattr(dentry)
1616
2717
  }
1617
2718
  }
1618
2719
 
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,
2720
+ // The second copy up decided the directory was not up yet, and waited for the
2721
+ // first. Without asking again under the lock, it would find the first's copy
2722
+ // standing at the name and refuse.
2723
+ // @behavior FS-181
2724
+ #[test]
2725
+ fn a_directory_two_files_copy_up_at_once_takes_both() {
2726
+ use crate::mount::{Access, Mount};
2727
+ use crate::vfs::{self, OpenHow, Path};
2728
+ let lower: Tmpfs = [("d/e/f", "1"), ("d/e/g", "2")].into_iter().collect();
2729
+ let hook = Arc::new(SlowsDirectory(AtomicBool::new(false)));
2730
+ let overlay = Overlay::new(
2731
+ vec![
2732
+ Filesystem::new(Arc::new(Wrap::new(Arc::new(lower), hook.clone())))
2733
+ .expect("the instance must stand"),
2734
+ ],
2735
+ Some(Filesystem::new(Arc::new(Tmpfs::new())).expect("the instance must stand")),
2736
+ )
2737
+ .expect("the Overlay must stack");
2738
+ let mount = Mount::new(
2739
+ "/data",
2740
+ Filesystem::new(Arc::new(overlay)).expect("the instance must stand"),
2741
+ Access::ReadWrite,
2742
+ )
2743
+ .expect("/data is a guest path");
2744
+ let root = Path::root(mount.mnt());
2745
+ let cred = Arc::default();
2746
+ let write = OpenHow {
2747
+ write: true,
2748
+ ..OpenHow::default()
2749
+ };
2750
+ for name in ["d/e/f", "d/e/g"] {
2751
+ vfs::vfs_fstatat(&cred, &root, name, false).expect("the file stands");
2752
+ }
2753
+ hook.0.store(true, Ordering::SeqCst);
2754
+ let opened = thread::scope(|scope| {
2755
+ let (first, second) = (
2756
+ scope.spawn(|| vfs::kern_path::filp_open(&cred, &root, "d/e/f", &write).map(drop)),
2757
+ scope.spawn(|| vfs::kern_path::filp_open(&cred, &root, "d/e/g", &write).map(drop)),
2758
+ );
2759
+ (
2760
+ crate::sched::testing::join_scoped("the first open", first),
2761
+ crate::sched::testing::join_scoped("the second open", second),
2762
+ )
2763
+ });
2764
+ assert_eq!(
2765
+ opened,
2766
+ (Ok(()), Ok(())),
2767
+ "vfs::do_file_open to write two files of one lower directory at once must copy the \
2768
+ directory up once and open both"
2769
+ );
1624
2770
  }
1625
2771
 
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);
2772
+ mod stress {
2773
+ use super::*;
2774
+ use crate::mount::{Access, Mount};
2775
+ use crate::vfs::{self, OpenHow, Path};
2776
+
2777
+ /// What a removal and an open to write answer racing over one lower file,
2778
+ /// with what the descriptor the open made reports of itself.
2779
+ fn race_removal_with_a_write() -> (
2780
+ Result<(), FilesystemError>,
2781
+ Option<Result<FileType, FilesystemError>>,
2782
+ ) {
2783
+ let lower: Tmpfs = [("d/f", "lower")].into_iter().collect();
2784
+ let overlay = Overlay::new(
2785
+ vec![Filesystem::new(Arc::new(lower)).expect("the instance must stand")],
2786
+ Some(Filesystem::new(Arc::new(Tmpfs::new())).expect("the instance must stand")),
2787
+ )
2788
+ .expect("the Overlay must stack");
2789
+ let mount = Mount::new(
2790
+ "/data",
2791
+ Filesystem::new(Arc::new(overlay)).expect("the instance must stand"),
2792
+ Access::ReadWrite,
2793
+ )
2794
+ .expect("/data is a guest path");
2795
+ let root = Path::root(mount.mnt());
2796
+ let cred = Arc::default();
2797
+ let write = OpenHow {
2798
+ write: true,
2799
+ ..OpenHow::default()
2800
+ };
2801
+ let ready = std::sync::atomic::AtomicBool::new(false);
2802
+ std::thread::scope(|scope| {
2803
+ let opening = scope.spawn(|| {
2804
+ crate::sched::testing::wait_until("the removal starting", || {
2805
+ ready.load(Ordering::SeqCst)
2806
+ });
2807
+ vfs::kern_path::filp_open(&cred, &root, "d/f", &write)
2808
+ .ok()
2809
+ .map(|file| file.getattr().map(|metadata| metadata.file_type))
2810
+ });
2811
+ ready.store(true, Ordering::SeqCst);
2812
+ let removed = crate::sched::testing::within_max_wait(|| {
2813
+ vfs::filename_unlinkat(&cred, &root, "d/f")
2814
+ });
2815
+ (
2816
+ removed,
2817
+ crate::sched::testing::join_scoped("the open", opening),
2818
+ )
2819
+ })
2820
+ }
2821
+
2822
+ // A removal deciding how to hide the name before the copy up landed asked
2823
+ // the rename to find nothing there.
2824
+ // @behavior FS-182
2825
+ #[test]
2826
+ #[ignore = "a stress witness; run by rake crates:stress"]
2827
+ fn a_file_removed_as_it_is_copied_up_goes() {
2828
+ for _ in 0..50_000 {
2829
+ let (removed, _) = race_removal_with_a_write();
2830
+ assert_eq!(
2831
+ removed,
2832
+ Ok(()),
2833
+ "vfs::filename_unlinkat of a file another Thread opens to write through an \
2834
+ Overlay must remove it"
2835
+ );
1643
2836
  }
1644
- self.memory.metadata(path)
1645
2837
  }
1646
2838
 
1647
- fn read_dir(&self, path: &str) -> Result<Vec<DirEntry>, FilesystemError> {
1648
- self.memory.read_dir(path)
2839
+ // A copy up reading its name back after its lock went found the whiteout
2840
+ // the removal left there.
2841
+ // @behavior FS-180
2842
+ #[test]
2843
+ #[ignore = "a stress witness; run by rake crates:stress"]
2844
+ fn a_file_copied_up_as_its_name_is_removed_stays_a_file() {
2845
+ for _ in 0..50_000 {
2846
+ let (_, opened) = race_removal_with_a_write();
2847
+ if let Some(kind) = opened {
2848
+ assert_eq!(
2849
+ kind,
2850
+ Ok(FileType::RegFile),
2851
+ "File::getattr of a file opened to write through an Overlay as it is \
2852
+ removed must report a file"
2853
+ );
2854
+ }
2855
+ }
1649
2856
  }
2857
+ }
1650
2858
 
1651
- fn create_dir(&self, path: &str) -> Result<(), FilesystemError> {
1652
- self.memory.create_dir(path)
1653
- }
2859
+ /// A lower Layer whose one entry, `pipe`, is a FIFO, counting each open.
2860
+ struct HoldsFifo(Arc<AtomicUsize>);
1654
2861
 
1655
- fn remove_dir(&self, path: &str) -> Result<(), FilesystemError> {
1656
- self.memory.remove_dir(path)
2862
+ impl FileSystemType for HoldsFifo {
2863
+ fn fill_super(self: Arc<Self>, sb: &mut Filesystem) -> Result<(), FilesystemError> {
2864
+ sb.d_make_root(Inode::new(&Kstat::new(FileType::Dir, 0), self));
2865
+ Ok(())
1657
2866
  }
2867
+ }
1658
2868
 
1659
- fn remove_file(&self, path: &str) -> Result<(), FilesystemError> {
1660
- self.memory.remove_file(path)
2869
+ impl InodeOperations for HoldsFifo {
2870
+ fn getattr(&self, dentry: &Dentry) -> Result<Kstat, FilesystemError> {
2871
+ Ok(match dentry.name().as_str() {
2872
+ "pipe" => Kstat::new(FileType::Fifo, 0).with_identity(1, 2),
2873
+ _ => Kstat::new(FileType::Dir, 0).with_identity(1, 1),
2874
+ })
1661
2875
  }
1662
2876
 
1663
- fn rename(&self, from: &str, to: &str, rename: Rename) -> Result<(), FilesystemError> {
1664
- self.memory.rename(from, to, rename)
2877
+ fn lookup(&self, dentry: &Dentry) -> Result<Lookup, FilesystemError> {
2878
+ Ok(match dentry.name().as_str() {
2879
+ "pipe" => Lookup::Found(Inode::new(
2880
+ &Kstat::new(FileType::Fifo, 0).with_identity(1, 2),
2881
+ Arc::new(Self(self.0.clone())),
2882
+ )),
2883
+ _ => Lookup::Absent,
2884
+ })
1665
2885
  }
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
2886
 
1677
2887
  fn open(
1678
2888
  &self,
1679
- path: &str,
1680
- options: &OpenOptions,
1681
- ) -> Result<Box<dyn File>, FilesystemError> {
1682
- self.0.open(&path.to_lowercase(), options)
2889
+ _dentry: &Dentry,
2890
+ _how: &OpenHow,
2891
+ ) -> Result<Box<dyn FileOperations>, FilesystemError> {
2892
+ self.0.fetch_add(1, Ordering::SeqCst);
2893
+ Err(FilesystemError::Interrupted)
1683
2894
  }
2895
+ }
1684
2896
 
1685
- fn metadata(&self, path: &str) -> Result<Metadata, FilesystemError> {
1686
- self.0.metadata(&path.to_lowercase())
1687
- }
2897
+ // A special file answers a time without any open having been asked, as
2898
+ // an open of one could wait for a peer past the invocation's timeout.
2899
+ // @behavior FS-162
2900
+ #[test]
2901
+ fn a_time_set_on_a_special_file_below_an_overlay_opens_nothing() {
2902
+ use crate::mount::{Access, Mount};
2903
+ use crate::vfs::{self, Path};
2904
+ let opened = Arc::new(AtomicUsize::new(0));
2905
+ let overlay = Overlay::new(
2906
+ vec![Filesystem::new(Arc::new(HoldsFifo(opened.clone())))
2907
+ .expect("the instance must stand")],
2908
+ Some(Filesystem::new(Arc::new(Tmpfs::new())).expect("the instance must stand")),
2909
+ )
2910
+ .expect("the Overlay must stack");
2911
+ let mount = Mount::new(
2912
+ "/data",
2913
+ Filesystem::new(Arc::new(overlay)).expect("the instance must stand"),
2914
+ Access::ReadWrite,
2915
+ )
2916
+ .expect("/data is a guest path");
2917
+ let answer = vfs::do_utimes_path(
2918
+ &Arc::default(),
2919
+ &Path::root(mount.mnt()),
2920
+ "pipe",
2921
+ false,
2922
+ &Iattr::times(None, Some(crate::vfs::SetTime::Now)),
2923
+ );
2924
+ assert_eq!(
2925
+ (answer, opened.load(Ordering::SeqCst)),
2926
+ (Err(FilesystemError::PermissionDenied), 0),
2927
+ "vfs::do_utimes_pathon a FIFO a lower Layer holds through an Overlay must answer EACCES \
2928
+ and open nothing"
2929
+ );
2930
+ }
1688
2931
 
1689
- fn stored_path(&self, path: &str) -> Result<String, FilesystemError> {
1690
- Ok(path.to_lowercase())
1691
- }
2932
+ // An earlier Overlay's upper, stacked as a lower Layer of another, holds a
2933
+ // whiteout in a directory only that Layer shows, so only a walk reading
2934
+ // such a directory for marks finds nothing there.
2935
+ // @behavior FS-159
2936
+ #[test]
2937
+ fn a_whiteout_in_a_directory_one_layer_shows_names_nothing() {
2938
+ let lower: Tmpfs = [("file", "x")].into_iter().collect();
2939
+ let first_upper = Filesystem::new(Arc::new(Tmpfs::new())).expect("the instance must stand");
2940
+ let first = kern_path(Arc::new(
2941
+ Overlay::new(
2942
+ vec![Filesystem::new(Arc::new(lower)).expect("the instance must stand")],
2943
+ Some(first_upper.clone()),
2944
+ )
2945
+ .expect("the Overlay must stack"),
2946
+ ));
2947
+ first.vfs_unlink("file").expect("the file must be removed");
2948
+ let second = at(Overlay::new(
2949
+ vec![first_upper],
2950
+ Some(Filesystem::new(Arc::new(Tmpfs::new())).expect("the instance must stand")),
2951
+ )
2952
+ .expect("the Overlay must stack"));
2953
+ assert_eq!(
2954
+ second.vfs_lstat("upper/file").err(),
2955
+ Some(FilesystemError::NotFound),
2956
+ "a stat through an Overlay of a whiteout in a directory one Layer shows must answer \
2957
+ ENOENT, as the listing shows nothing there"
2958
+ );
2959
+ }
1692
2960
 
1693
- fn read_dir(&self, path: &str) -> Result<Vec<DirEntry>, FilesystemError> {
1694
- self.0.read_dir(&path.to_lowercase())
1695
- }
2961
+ // A lower directory moved onto a full one answers why it cannot move, so
2962
+ // only asking that first keeps what stands at the target from answering.
2963
+ // @behavior FS-160
2964
+ #[test]
2965
+ fn a_lower_directory_answers_that_it_cannot_move_before_its_target_is_read() {
2966
+ let lower: Tmpfs = [("dir/file", "x"), ("full/file", "x")]
2967
+ .into_iter()
2968
+ .collect();
2969
+ let overlay = at(over(Arc::new(lower), Arc::new(Tmpfs::new())));
2970
+ assert_eq!(
2971
+ overlay.rename("dir", &overlay, "full"),
2972
+ Err(FilesystemError::CrossesDevices),
2973
+ "a rename through an Overlay of a lower Layer's directory onto one holding entries \
2974
+ must answer EXDEV, as ovl_rename_start asks whether it can move first"
2975
+ );
2976
+ }
1696
2977
 
1697
- fn create_dir(&self, path: &str) -> Result<(), FilesystemError> {
1698
- self.0.create_dir(&path.to_lowercase())
1699
- }
2978
+ // The lower file is far bigger than the upper holds, so only a copy up
2979
+ // leaving its bytes behind can land.
2980
+ // @behavior FS-155
2981
+ #[test]
2982
+ fn a_truncating_open_copies_up_none_of_the_bytes() {
2983
+ use crate::mount::{Access, Mount};
2984
+ use crate::vfs::{self, OpenHow, Path};
2985
+ let lower: Tmpfs = [("file", vec![b'x'; 1 << 20])].into_iter().collect();
2986
+ let upper = Tmpfs::new().with_size(Some(4096)).with_nr_inodes(None);
2987
+ let overlay = Overlay::new(
2988
+ vec![Filesystem::new(Arc::new(lower)).expect("the instance must stand")],
2989
+ Some(Filesystem::new(Arc::new(upper)).expect("the instance must stand")),
2990
+ )
2991
+ .expect("the Overlay must stack");
2992
+ let mount = Mount::new(
2993
+ "/data",
2994
+ Filesystem::new(Arc::new(overlay)).expect("the instance must stand"),
2995
+ Access::ReadWrite,
2996
+ )
2997
+ .expect("/data is a guest path");
2998
+ let root = Path::root(mount.mnt());
2999
+ let cred = Arc::default();
3000
+ let how = OpenHow {
3001
+ read: true,
3002
+ write: true,
3003
+ truncate: true,
3004
+ ..OpenHow::default()
3005
+ };
3006
+ let opened = vfs::kern_path::filp_open(&cred, &root, "file", &how);
3007
+ assert!(
3008
+ opened.is_ok_and(|mut file| file.read(&mut [0; 8]) == Ok(0)),
3009
+ "vfs::do_file_open with O_TRUNC of a lower file past what the upper holds must land \
3010
+ on an empty file"
3011
+ );
3012
+ }
1700
3013
 
1701
- fn remove_dir(&self, path: &str) -> Result<(), FilesystemError> {
1702
- self.0.remove_dir(&path.to_lowercase())
1703
- }
3014
+ // Only the directory the view holds open keeps its Layer's host directory
3015
+ // through walks past a Bind's bound, so only that reaches it once the host
3016
+ // moved it.
3017
+ // @behavior FS-152
3018
+ #[cfg(unix)]
3019
+ #[test]
3020
+ fn an_overlay_directory_held_open_keeps_its_bind_layers_directory_past_the_bound() {
3021
+ use crate::mount::{Access, Mount};
3022
+ use crate::vfs::{self, OpenHow, Path};
3023
+ let host = std::env::temp_dir().join(format!("kobako-overlay-held-{}", std::process::id()));
3024
+ let _ = std::fs::remove_dir_all(&host);
3025
+ let deep = host.join(vec!["d"; 100].join("/"));
3026
+ std::fs::create_dir_all(&deep).expect("the host tree must be made");
3027
+ std::fs::create_dir_all(host.join("held")).expect("the host tree must be made");
3028
+ std::fs::write(host.join("held/f"), "x").expect("the host file must be written");
3029
+ let lower = Filesystem::new(Arc::new(
3030
+ crate::filesystem::Bind::new(&host).expect("the host directory must open"),
3031
+ ))
3032
+ .expect("the instance must stand");
3033
+ let overlay = Overlay::new(
3034
+ vec![lower],
3035
+ Some(Filesystem::new(Arc::new(Tmpfs::new())).expect("the instance must stand")),
3036
+ )
3037
+ .expect("the Overlay must stack");
3038
+ let mount = Mount::new(
3039
+ "/data",
3040
+ Filesystem::new(Arc::new(overlay)).expect("the instance must stand"),
3041
+ Access::Read,
3042
+ )
3043
+ .expect("/data is a guest path");
3044
+ let root = Path::root(mount.mnt());
3045
+ let cred = Arc::default();
3046
+ let directory = OpenHow {
3047
+ read: true,
3048
+ directory: true,
3049
+ ..OpenHow::default()
3050
+ };
3051
+ let Ok(held) = vfs::kern_path::filp_open(&cred, &root, "held", &directory) else {
3052
+ panic!("the directory must open");
3053
+ };
3054
+ vfs::vfs_fstatat(&cred, &root, &vec!["d"; 100].join("/"), false)
3055
+ .unwrap_or_else(|_| panic!("the deep directory must stand"));
3056
+ std::fs::rename(host.join("held"), host.join("moved")).expect("the host must move it");
3057
+ let reached = vfs::vfs_fstatat(&cred, held.path(), "f", false);
3058
+ let _ = std::fs::remove_dir_all(&host);
3059
+ assert!(
3060
+ reached.is_ok(),
3061
+ "vfs::vfs_fstatat through an Overlay directory held open over a Bind Layer, once a \
3062
+ walk passed the bound and the host moved it, must still reach the directory it opened"
3063
+ );
3064
+ }
1704
3065
 
1705
- fn remove_file(&self, path: &str) -> Result<(), FilesystemError> {
1706
- self.0.remove_file(&path.to_lowercase())
1707
- }
3066
+ // Only the lower Layer holds `dir` when both handles open it, so the
3067
+ // second sees the file only if the copy up through the first reaches
3068
+ // it too.
3069
+ // @behavior FS-119
3070
+ #[test]
3071
+ fn a_copy_up_through_one_overlay_directory_is_seen_through_another() {
3072
+ let lower: Arc<dyn FileSystemType> = tmpfs_with("dir/a.txt", "a");
3073
+ let overlay = at(over(lower, Arc::new(Tmpfs::new())));
3074
+ let first = overlay.open_dir("dir").expect("dir must open");
3075
+ let second = overlay.open_dir("dir").expect("dir must open again");
3076
+ write(&first, "b.txt", b"b").expect("the write must copy dir up");
3077
+ assert_eq!(
3078
+ read(&second, "b.txt"),
3079
+ b"b".to_vec(),
3080
+ "a read through one directory Overlay::open_dir opened, of a file written through \
3081
+ another opened before the write copied it up, must read the file"
3082
+ );
3083
+ }
1708
3084
 
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
- }
3085
+ // The lower Layer holds no `dir`, so reaching it again by the path it
3086
+ // was opened at would miss where it moved.
3087
+ // @behavior FS-120
3088
+ #[test]
3089
+ fn a_write_through_a_moved_overlay_directory_lands_where_it_went() {
3090
+ let upper = tmpfs_with("upper/dir/a.txt", "a");
3091
+ let overlay = at(over(Arc::new(Tmpfs::new()), upper.clone()));
3092
+ let dir = overlay.open_dir("dir").expect("dir must open");
3093
+ overlay
3094
+ .rename("dir", &overlay, "moved")
3095
+ .expect("the directory must move");
3096
+ assert_eq!(
3097
+ (
3098
+ write(&dir, "b.txt", b"b"),
3099
+ read(&on(&upper), "upper/moved/b.txt")
3100
+ ),
3101
+ (Ok(()), b"b".to_vec()),
3102
+ "a write through a directory Overlay::open_dir opened, once Overlay::rename moved it \
3103
+ over a lower Layer, must land where it moved"
3104
+ );
1713
3105
  }
1714
3106
 
1715
3107
  // @behavior FS-083
1716
3108
  #[test]
1717
3109
  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
- );
3110
+ let overlay = at(over(
3111
+ Arc::new(FoldsCase::new(
3112
+ [("private/secret", "x")].into_iter().collect(),
3113
+ )),
3114
+ Arc::new(Tmpfs::new()),
3115
+ ));
3116
+ let private = overlay
3117
+ .open_dir("PRIVATE")
3118
+ .expect("the directory must open in any case");
1724
3119
  assert_eq!(
1725
3120
  (
1726
- overlay.stored_path("PRIVATE/SECRET"),
1727
- overlay.stored_path("PRIVATE/New")
3121
+ overlay.lookup("PRIVATE"),
3122
+ private.lookup("SECRET"),
3123
+ private.lookup("New")
1728
3124
  ),
1729
3125
  (
1730
- Ok("private/secret".to_string()),
1731
- Ok("private/New".to_string())
3126
+ Ok("private".to_string()),
3127
+ Ok("secret".to_string()),
3128
+ Ok("New".to_string())
1732
3129
  ),
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"
3130
+ "a name asked of Overlay::lookup in another case, shown by a Layer folding case, \
3131
+ must come back as that Layer stores it, a new name kept as given"
1735
3132
  );
1736
3133
  }
1737
3134
 
1738
- // A whiteout the Layer cannot read would otherwise count as absent and
1739
- // let the lower file show.
1740
- // @behavior FS-079
3135
+ // A directory only one Layer shows has nothing to merge or hide, so the
3136
+ // walk keeps what that Layer's lookup recorded, as overlayfs leaves it
3137
+ // unmerged and reads `d_is_dir` rather than ask for a stat.
3138
+ // @behavior FS-122
1741
3139
  #[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"
3140
+ fn a_directory_one_layer_shows_is_walked_without_its_stat() {
3141
+ let only: Arc<dyn FileSystemType> = tmpfs_with("top/dir/file", "only");
3142
+ let overlay = at(over(
3143
+ Arc::new(Wrap::new(only, Arc::new(StatsNoDir))),
3144
+ Arc::new(Tmpfs::new()),
3145
+ ));
3146
+ assert_eq!(
3147
+ read(&overlay, "top/dir/file"),
3148
+ b"only",
3149
+ "a file read through Overlay::open beneath a directory only the lower Layer shows, \
3150
+ that Layer answering no stat of the directory, must read its content"
3151
+ );
3152
+ }
3153
+
3154
+ /// Refuses every open, as a Layer whose directory cannot open does.
3155
+ struct RefusesOpen;
3156
+
3157
+ impl Hook for RefusesOpen {
3158
+ fn open(
3159
+ &self,
3160
+ _inner: &dyn InodeOperations,
3161
+ _dentry: &Dentry,
3162
+ _how: &OpenHow,
3163
+ ) -> Result<Box<dyn FileOperations>, FilesystemError> {
3164
+ Err(FilesystemError::PermissionDenied)
3165
+ }
3166
+ }
3167
+
3168
+ // A host removing a Bind's directory cannot witness this, as the fid the
3169
+ // lookup took still opens it, so a Layer refuses instead.
3170
+ // @behavior FS-166
3171
+ #[test]
3172
+ fn a_directory_a_layer_cannot_open_answers_why() {
3173
+ use crate::mount::{Access, Mount};
3174
+ use crate::vfs::{self, OpenHow};
3175
+ let lower: Arc<dyn FileSystemType> = tmpfs_with("top/file", "lower");
3176
+ let overlay = over(
3177
+ Arc::new(Wrap::new(lower, Arc::new(RefusesOpen))),
3178
+ Arc::new(Tmpfs::new()),
3179
+ );
3180
+ let mount = Mount::new(
3181
+ "/data",
3182
+ Filesystem::new(Arc::new(overlay)).expect("the instance must stand"),
3183
+ Access::Read,
3184
+ )
3185
+ .expect("/data is a guest path");
3186
+ let directory = OpenHow {
3187
+ read: true,
3188
+ directory: true,
3189
+ ..OpenHow::default()
3190
+ };
3191
+ let answer = vfs::kern_path::filp_open(
3192
+ &Arc::default(),
3193
+ &vfs::Path::root(mount.mnt()),
3194
+ "top",
3195
+ &directory,
3196
+ );
3197
+ assert_eq!(
3198
+ answer.err(),
3199
+ Some(FilesystemError::PermissionDenied),
3200
+ "vfs::do_file_open of an Overlay's directory whose Layer refuses its open must answer \
3201
+ what the Layer answered"
1751
3202
  );
1752
3203
  }
1753
3204
 
1754
3205
  // @behavior FS-080
1755
3206
  #[test]
1756
3207
  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);
3208
+ let tmpfs: Tmpfs = [("file", "lower")].into_iter().collect();
3209
+ let lower = Wrap::new(Arc::new(tmpfs), Arc::new(Vanishes::default()));
3210
+ let overlay = at(over(Arc::new(lower), Arc::new(Tmpfs::new())));
1764
3211
  assert_eq!(
1765
- overlay.rename("file", "moved", Rename::Any),
3212
+ overlay.rename("file", &overlay, "moved"),
1766
3213
  Err(FilesystemError::NotFound),
1767
3214
  "a file renamed through Overlay::rename that its Layer loses during the call must \
1768
3215
  answer not found"
@@ -1774,148 +3221,134 @@ mod tests {
1774
3221
  // @behavior FS-078
1775
3222
  #[test]
1776
3223
  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);
3224
+ let overlay = at(over(
3225
+ tmpfs_with("dir", "lower"),
3226
+ tmpfs_with("upper/dir/sub/file", "upper"),
3227
+ ));
1780
3228
  assert_eq!(
1781
3229
  (
1782
- overlay.metadata("dir/sub").map(|metadata| metadata.file_type),
3230
+ overlay
3231
+ .vfs_lstat("dir/sub")
3232
+ .map(|metadata| metadata.file_type),
1783
3233
  read(&overlay, "dir/sub/file")
1784
3234
  ),
1785
- (Ok(FileType::Directory), b"upper".to_vec()),
1786
- "a directory looked up through Overlay::metadata above a lower Layer holding a file on \
3235
+ (Ok(FileType::Dir), b"upper".to_vec()),
3236
+ "a directory looked up through vfs_lstat above a lower Layer holding a file on \
1787
3237
  its path must read as a directory, as its files open"
1788
3238
  );
1789
3239
  }
1790
3240
 
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
3241
+ // @behavior FS-101
1795
3242
  #[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
- );
3243
+ fn a_file_opened_to_read_before_a_copy_up_reads_the_copy() {
3244
+ let (overlay, _) = overlay_on(Arc::new(Tmpfs::default()));
3245
+ let mut reader = open_to_read(&overlay, "dir/file");
3246
+ overwrite(&overlay, "dir/file", b"upper");
1808
3247
  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"
3248
+ read_from(reader.as_mut()),
3249
+ b"upper".to_vec(),
3250
+ "a file Overlay::open handed out to read before another open copied it up must \
3251
+ read what the copy holds"
1819
3252
  );
1820
3253
  }
1821
3254
 
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
3255
+ // @behavior FS-102
1830
3256
  #[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());
3257
+ fn a_file_opened_to_read_before_a_rename_reads_the_moved_copy() {
3258
+ let (overlay, _) = overlay_on(Arc::new(Tmpfs::default()));
3259
+ let mut reader = open_to_read(&overlay, "dir/file");
1834
3260
  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");
3261
+ .rename("dir/file", &overlay, "dir/moved")
3262
+ .expect("the rename must land");
3263
+ overwrite(&overlay, "dir/moved", b"moved");
1843
3264
  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"
3265
+ read_from(reader.as_mut()),
3266
+ b"moved".to_vec(),
3267
+ "a file Overlay::open handed out to read before Overlay::rename moved it must read \
3268
+ what the moved copy holds"
1848
3269
  );
1849
3270
  }
1850
3271
 
1851
- // @behavior FS-055
3272
+ // @behavior FS-103
1852
3273
  #[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());
3274
+ fn a_file_open_to_read_keeps_what_it_held_once_removed() {
3275
+ let (overlay, _) = overlay_on(Arc::new(Tmpfs::default()));
3276
+ let mut reader = open_to_read(&overlay, "dir/file");
3277
+ overwrite(&overlay, "dir/file", b"upper");
3278
+ overlay
3279
+ .vfs_unlink("dir/file")
3280
+ .expect("the removal must land");
3281
+ overwrite(&overlay, "dir/file", b"fresh");
3282
+ overlay
3283
+ .rename("dir/file", &overlay, "dir/moved")
3284
+ .expect("the rename must land");
1856
3285
  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"
3286
+ read_from(reader.as_mut()),
3287
+ b"upper".to_vec(),
3288
+ "a file Overlay::open handed out to read must keep reading what it held once \
3289
+ vfs_unlink took it away, whatever later stands at its path"
1869
3290
  );
1870
3291
  }
1871
3292
 
1872
- /// Counts each wait its Overlay's lock makes through it.
1873
- struct CountsWaits(AtomicUsize);
3293
+ /// A Tmpfs whose first open sleeps a while, flagging that it began.
3294
+ #[derive(Default)]
3295
+ struct SleepsOnOpen {
3296
+ opening: AtomicBool,
3297
+ }
1874
3298
 
1875
- impl Blocking for CountsWaits {
1876
- fn wait(&self, wait: &mut dyn FnMut()) {
1877
- self.0.fetch_add(1, Ordering::SeqCst);
1878
- wait();
3299
+ impl Hook for SleepsOnOpen {
3300
+ fn open(
3301
+ &self,
3302
+ inner: &dyn InodeOperations,
3303
+ dentry: &Dentry,
3304
+ how: &OpenHow,
3305
+ ) -> Result<Box<dyn FileOperations>, FilesystemError> {
3306
+ if !self.opening.swap(true, Ordering::SeqCst) {
3307
+ thread::sleep(Duration::from_millis(100));
3308
+ }
3309
+ inner.open(dentry, how)
1879
3310
  }
1880
3311
  }
1881
3312
 
3313
+ // The first copy up holds the file's lock while its lower open sleeps,
3314
+ // so the second, of the same file, finds the lock held and waits.
1882
3315
  // @behavior FS-039
1883
3316
  #[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);
3317
+ fn a_contended_copy_up_waits_through_the_registered_blocking() {
3318
+ let counts = crate::sched::testing::count_waits();
3319
+ let sleeps = Arc::new(SleepsOnOpen::default());
3320
+ let lower = Wrap::new(tmpfs_with("note", "lower"), sleeps.clone());
3321
+ let overlay = over(Arc::new(lower), Arc::new(Tmpfs::new()));
3322
+ let first = at(overlay);
3323
+ let second = first.clone();
3324
+ let copying = thread::spawn(move || write_over(&first, "note", b"first"));
3325
+ crate::sched::testing::wait_until("the first copy up opening its lower file", || {
3326
+ sleeps.opening.load(Ordering::SeqCst)
3327
+ });
3328
+ let waited =
3329
+ crate::sched::testing::within_max_wait(|| write_over(&second, "note", b"later"));
1896
3330
  assert_eq!(
1897
3331
  (
1898
- waiter.join().expect("the waiter must not panic"),
1899
- counts.0.load(Ordering::SeqCst)
3332
+ crate::sched::testing::join("the first copy up", copying),
3333
+ waited,
3334
+ counts.load(Ordering::SeqCst) > 0
1900
3335
  ),
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"
3336
+ (Ok(()), Ok(()), true),
3337
+ "a write through an Overlay copying up a file another thread is copying up must wait \
3338
+ through the Blocking given to sched::register_blocking, then land"
1904
3339
  );
1905
3340
  }
1906
3341
 
1907
- // Copying up makes the directory in the writable Layer, whose own
1908
- // identity differs, so only reporting the lower Layer's keeps it.
3342
+ // Copying up makes the directory in the upper, whose own identity
3343
+ // differs, so only reporting the lower Layer's keeps it.
1909
3344
  // @behavior FS-072
1910
3345
  #[test]
1911
3346
  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");
3347
+ let lower: Arc<dyn FileSystemType> =
3348
+ Arc::new([("dir/a.txt", "a")].into_iter().collect::<Tmpfs>());
3349
+ let overlay = at(over(lower, Arc::new(Tmpfs::new())));
3350
+ let identity = |overlay: &KernPath| {
3351
+ let metadata = overlay.vfs_lstat("dir").expect("the directory must stand");
1919
3352
  (metadata.dev, metadata.ino)
1920
3353
  };
1921
3354
  let before = identity(&overlay);
@@ -1923,33 +3356,114 @@ mod tests {
1923
3356
  assert_eq!(
1924
3357
  (identity(&overlay) == before, before.1 != 0),
1925
3358
  (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"
3359
+ "a directory of a lower Layer copied up by a write through an Overlay's open must \
3360
+ keep the identity vfs_lstat reported before"
3361
+ );
3362
+ }
3363
+
3364
+ // The upper Layer's directory changes after the lower one is made, so
3365
+ // the two report different times and only the upper's matches.
3366
+ // @behavior FS-117
3367
+ #[test]
3368
+ fn an_overlay_reports_a_merged_directory_as_overlayfs_does() {
3369
+ let lower: Arc<Tmpfs> = Arc::new([("dir/a.txt", "a")].into_iter().collect());
3370
+ let upper: Arc<Tmpfs> = Arc::new(Tmpfs::new());
3371
+ let overlay = at(over(lower.clone(), upper.clone()));
3372
+ std::thread::sleep(std::time::Duration::from_millis(10));
3373
+ write(&overlay, "dir/b.txt", b"b").expect("the upper must take a write");
3374
+ let merged = overlay.vfs_lstat("dir").expect("the directory must stand");
3375
+ let modified = |layer: &Arc<Tmpfs>, path: &str| {
3376
+ on(layer).vfs_lstat(path).expect("a Layer holds it").mtime
3377
+ };
3378
+ assert_eq!(
3379
+ (
3380
+ merged.nlink,
3381
+ merged.mtime == modified(&upper, "upper/dir"),
3382
+ merged.mtime == modified(&lower, "dir"),
3383
+ ),
3384
+ (1, true, false),
3385
+ "Overlay::getattr of a directory two Layers hold, written last in the upper, must \
3386
+ count one name and report the upper Layer's times"
1928
3387
  );
1929
3388
  }
1930
3389
 
1931
3390
  // @behavior FS-073
1932
3391
  #[test]
1933
3392
  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>| {
3393
+ let overlay = at(over(
3394
+ tmpfs_with("dir/lower", "x"),
3395
+ tmpfs_with("upper/dir/upper", "x"),
3396
+ ));
3397
+ let identity = |metadata: Result<Kstat, FilesystemError>| {
1945
3398
  let metadata = metadata.expect("the directory must stand");
1946
3399
  (metadata.dev, metadata.ino)
1947
3400
  };
1948
3401
  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"
3402
+ identity(overlay.vfs_lstat("dir")),
3403
+ identity(overlay.vfs_lstat("dir")),
3404
+ "a directory two Layers hold, looked up through vfs_lstat, must \
3405
+ report the identity vfs_lstat does"
1953
3406
  );
1954
3407
  }
3408
+
3409
+ /// A Layer whose `permission` refuses `mask` of what stands at `name`.
3410
+ struct RefusesOf {
3411
+ name: &'static str,
3412
+ mask: Mask,
3413
+ }
3414
+
3415
+ impl Hook for RefusesOf {
3416
+ fn permission(
3417
+ &self,
3418
+ inner: &dyn InodeOperations,
3419
+ dentry: &Dentry,
3420
+ mask: Mask,
3421
+ ) -> Result<(), FilesystemError> {
3422
+ if dentry.name() == self.name && mask.contains(self.mask) {
3423
+ return Err(FilesystemError::PermissionDenied);
3424
+ }
3425
+ inner.permission(dentry, mask)
3426
+ }
3427
+ }
3428
+
3429
+ type LayerCall = fn(&KernPath) -> Result<(), FilesystemError>;
3430
+
3431
+ // Only a Layer of the Host App's own answers `permission`, so each call is
3432
+ // made over a lower Layer refusing just the one ask that call makes of it.
3433
+ // @behavior FS-206
3434
+ #[test]
3435
+ fn an_overlay_asks_a_layers_permission_where_overlayfs_does() {
3436
+ let calls: [(&str, &str, Mask, LayerCall); 4] = [
3437
+ ("finding a name", "dir", Mask::EXEC, |view| {
3438
+ view.vfs_lstat("dir/file").map(drop)
3439
+ }),
3440
+ ("opening a file", "file", Mask::OPEN, |view| {
3441
+ let read = OpenHow {
3442
+ read: true,
3443
+ ..OpenHow::default()
3444
+ };
3445
+ view.open("dir/file", &read).map(drop)
3446
+ }),
3447
+ ("listing a directory", "dir", Mask::OPEN, |view| {
3448
+ view.open_dir("dir")?.iterate_dir().map(drop)
3449
+ }),
3450
+ ("copying a file up", "file", Mask::OPEN, |view| {
3451
+ write_over(view, "dir/file", b"y")
3452
+ }),
3453
+ ];
3454
+ for (call, name, mask, run) in calls {
3455
+ let lower = Wrap::new(
3456
+ tmpfs_with("dir/file", "x"),
3457
+ Arc::new(RefusesOf { name, mask }),
3458
+ );
3459
+ let view = at(over(Arc::new(lower), Arc::new(Tmpfs::new())));
3460
+ assert_eq!(
3461
+ run(&view),
3462
+ Err(FilesystemError::PermissionDenied),
3463
+ "{call} through an Overlay over a Layer whose permission refuses {mask:?} of \
3464
+ `{name}` must answer that refusal, as overlayfs asks inode_permission of the \
3465
+ real inode"
3466
+ );
3467
+ }
3468
+ }
1955
3469
  }