kobako 0.28.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 (218) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +1 -1
  3. data/CHANGELOG.md +377 -0
  4. data/Cargo.lock +181 -154
  5. data/README.md +99 -459
  6. data/ROADMAP.md +7 -1
  7. data/SECURITY.md +1 -1
  8. data/crates/kobako-runtime/CHANGELOG.md +354 -0
  9. data/crates/kobako-runtime/Cargo.toml +38 -7
  10. data/crates/kobako-runtime/README.md +17 -8
  11. data/crates/kobako-runtime/src/dispatch.rs +9 -1
  12. data/crates/kobako-runtime/src/endpoint/buffer.rs +192 -0
  13. data/crates/kobako-runtime/src/endpoint/capture.rs +115 -0
  14. data/crates/kobako-runtime/src/endpoint/dispatch.rs +253 -0
  15. data/crates/kobako-runtime/src/endpoint/invoke.rs +194 -0
  16. data/crates/kobako-runtime/src/endpoint/memory_cap.rs +238 -0
  17. data/crates/kobako-runtime/src/endpoint.rs +166 -0
  18. data/crates/kobako-runtime/src/filesystem/bind/fid.rs +82 -0
  19. data/crates/kobako-runtime/src/filesystem/bind/lookup.rs +236 -0
  20. data/crates/kobako-runtime/src/filesystem/bind/walk.rs +249 -0
  21. data/crates/kobako-runtime/src/filesystem/bind.rs +2665 -0
  22. data/crates/kobako-runtime/src/filesystem/overlay/file.rs +89 -0
  23. data/crates/kobako-runtime/src/filesystem/overlay/inode.rs +106 -0
  24. data/crates/kobako-runtime/src/filesystem/overlay/path.rs +285 -0
  25. data/crates/kobako-runtime/src/filesystem/overlay/stack/copy_up.rs +135 -0
  26. data/crates/kobako-runtime/src/filesystem/overlay/stack/dir.rs +263 -0
  27. data/crates/kobako-runtime/src/filesystem/overlay/stack/namei.rs +135 -0
  28. data/crates/kobako-runtime/src/filesystem/overlay/stack/readdir.rs +57 -0
  29. data/crates/kobako-runtime/src/filesystem/overlay/stack.rs +301 -0
  30. data/crates/kobako-runtime/src/filesystem/overlay.rs +3469 -0
  31. data/crates/kobako-runtime/src/filesystem/path.rs +21 -0
  32. data/crates/kobako-runtime/src/filesystem/pipe.rs +221 -0
  33. data/crates/kobako-runtime/src/filesystem/rdir.rs +101 -0
  34. data/crates/kobako-runtime/src/filesystem/tmpfs/tmpfile.rs +32 -0
  35. data/crates/kobako-runtime/src/filesystem/tmpfs/whiteout.rs +85 -0
  36. data/crates/kobako-runtime/src/filesystem/tmpfs/xattr.rs +22 -0
  37. data/crates/kobako-runtime/src/filesystem/tmpfs.rs +1985 -0
  38. data/crates/kobako-runtime/src/filesystem/wrap.rs +224 -0
  39. data/crates/kobako-runtime/src/filesystem.rs +26 -0
  40. data/crates/kobako-runtime/src/grant.rs +20 -0
  41. data/crates/kobako-runtime/src/hashtable.rs +44 -0
  42. data/crates/kobako-runtime/src/lib.rs +16 -1
  43. data/crates/kobako-runtime/src/locking/seqlock.rs +66 -0
  44. data/crates/kobako-runtime/src/locking.rs +218 -0
  45. data/crates/kobako-runtime/src/mount.rs +105 -0
  46. data/crates/kobako-runtime/src/profile.rs +140 -4
  47. data/crates/kobako-runtime/src/runtime.rs +12 -0
  48. data/crates/kobako-runtime/src/sched.rs +210 -0
  49. data/crates/kobako-runtime/src/security.rs +696 -0
  50. data/crates/kobako-runtime/src/snapshot.rs +20 -5
  51. data/crates/kobako-runtime/src/vfs/attr.rs +162 -0
  52. data/crates/kobako-runtime/src/vfs/cred.rs +334 -0
  53. data/crates/kobako-runtime/src/vfs/dentry.rs +1412 -0
  54. data/crates/kobako-runtime/src/vfs/errno.rs +153 -0
  55. data/crates/kobako-runtime/src/vfs/file.rs +1915 -0
  56. data/crates/kobako-runtime/src/vfs/inode.rs +429 -0
  57. data/crates/kobako-runtime/src/vfs/kern_path.rs +217 -0
  58. data/crates/kobako-runtime/src/vfs/mount.rs +59 -0
  59. data/crates/kobako-runtime/src/vfs/namei.rs +5708 -0
  60. data/crates/kobako-runtime/src/vfs/openat2.rs +116 -0
  61. data/crates/kobako-runtime/src/vfs/path.rs +64 -0
  62. data/crates/kobako-runtime/src/vfs/readdir.rs +163 -0
  63. data/crates/kobako-runtime/src/vfs/stat.rs +157 -0
  64. data/crates/kobako-runtime/src/vfs/super_block.rs +122 -0
  65. data/crates/kobako-runtime/src/vfs/utimes.rs +289 -0
  66. data/crates/kobako-runtime/src/vfs.rs +60 -0
  67. data/crates/kobako-runtime/src/yielder.rs +11 -6
  68. data/crates/kobako-runtime/tests/linux_baseline/calls.rs +171 -0
  69. data/crates/kobako-runtime/tests/linux_baseline/concurrent.rs +276 -0
  70. data/crates/kobako-runtime/tests/linux_baseline/kobako.rs +323 -0
  71. data/crates/kobako-runtime/tests/linux_baseline/linux-baseline-expected.txt +16 -0
  72. data/crates/kobako-runtime/tests/linux_baseline/linux.rs +449 -0
  73. data/crates/kobako-runtime/tests/linux_baseline/main.rs +611 -0
  74. data/crates/kobako-runtime/tests/linux_baseline/permissions.rs +236 -0
  75. data/crates/kobako-transport/CHANGELOG.md +23 -0
  76. data/crates/kobako-transport/Cargo.toml +3 -3
  77. data/crates/kobako-transport/README.md +6 -5
  78. data/crates/kobako-transport/src/abi.rs +1 -1
  79. data/crates/kobako-transport/src/envelope/bytes.rs +33 -25
  80. data/crates/kobako-transport/src/envelope/call.rs +5 -5
  81. data/crates/kobako-transport/src/envelope/compile_reply.rs +2 -2
  82. data/crates/kobako-transport/src/envelope/error_record.rs +3 -3
  83. data/crates/kobako-transport/src/envelope/fault.rs +4 -3
  84. data/crates/kobako-transport/src/envelope/outcome.rs +3 -3
  85. data/crates/kobako-transport/src/envelope/preamble.rs +24 -24
  86. data/crates/kobako-transport/src/envelope/reply.rs +34 -16
  87. data/crates/kobako-transport/src/envelope/request.rs +19 -9
  88. data/crates/kobako-transport/src/envelope/run.rs +1 -1
  89. data/crates/kobako-transport/src/envelope/unsupported.rs +2 -2
  90. data/crates/kobako-transport/src/envelope.rs +2 -2
  91. data/crates/kobako-wasmtime/CHANGELOG.md +265 -0
  92. data/crates/kobako-wasmtime/Cargo.toml +25 -19
  93. data/crates/kobako-wasmtime/README.md +13 -7
  94. data/crates/kobako-wasmtime/src/abi.rs +75 -49
  95. data/crates/kobako-wasmtime/src/ambient.rs +64 -6
  96. data/crates/kobako-wasmtime/src/cache.rs +188 -77
  97. data/crates/kobako-wasmtime/src/config.rs +77 -5
  98. data/crates/kobako-wasmtime/src/descriptors.rs +200 -0
  99. data/crates/kobako-wasmtime/src/driver.rs +70 -135
  100. data/crates/kobako-wasmtime/src/exports.rs +5 -16
  101. data/crates/kobako-wasmtime/src/guest.rs +38 -0
  102. data/crates/kobako-wasmtime/src/imports.rs +52 -0
  103. data/crates/kobako-wasmtime/src/instance_pre.rs +18 -46
  104. data/crates/kobako-wasmtime/src/invocation.rs +43 -74
  105. data/crates/kobako-wasmtime/src/lib.rs +5 -4
  106. data/crates/kobako-wasmtime/src/limiter.rs +6 -230
  107. data/crates/kobako-wasmtime/src/preview1/differential/mount.rs +632 -0
  108. data/crates/kobako-wasmtime/src/preview1/differential.rs +486 -0
  109. data/crates/kobako-wasmtime/src/preview1/errno.rs +135 -0
  110. data/crates/kobako-wasmtime/src/preview1/fd.rs +1420 -0
  111. data/crates/kobako-wasmtime/src/preview1/memory.rs +172 -0
  112. data/crates/kobako-wasmtime/src/preview1/mount_view/wrap.rs +211 -0
  113. data/crates/kobako-wasmtime/src/preview1/mount_view.rs +3766 -0
  114. data/crates/kobako-wasmtime/src/preview1/path.rs +302 -0
  115. data/crates/kobako-wasmtime/src/preview1/poll.rs +381 -0
  116. data/crates/kobako-wasmtime/src/preview1/sock.rs +13 -0
  117. data/crates/kobako-wasmtime/src/preview1/testsuite-expected.txt +6 -0
  118. data/crates/kobako-wasmtime/src/preview1/testsuite.rs +157 -0
  119. data/crates/kobako-wasmtime/src/preview1.rs +512 -0
  120. data/crates/kobako-wasmtime/src/trap.rs +45 -25
  121. data/crates/kobako-wasmtime/src/wasi.rs +333 -0
  122. data/crates/kobako-wasmtime/tests/integration/compile_posture.rs +43 -0
  123. data/crates/kobako-wasmtime/tests/integration/guest_file.rs +1085 -0
  124. data/crates/kobako-wasmtime/tests/integration/main.rs +29 -0
  125. data/crates/kobako-wasmtime/tests/{preamble_flags.rs → integration/preamble_flags.rs} +4 -3
  126. data/crates/kobako-wasmtime/tests/{snippet_compile.rs → integration/snippet_compile.rs} +5 -4
  127. data/crates/kobako-wasmtime/tests/{support → integration/support}/mod.rs +9 -13
  128. data/crates/kobako-wasmtime/tests/integration/unanswered_call.rs +86 -0
  129. data/crates/kobako-wasmtime/tests/{unsupported_call.rs → integration/unsupported_call.rs} +2 -1
  130. data/crates/kobako-wasmtime/witx/typenames.witx +750 -0
  131. data/crates/kobako-wasmtime/witx/wasi_snapshot_preview1.witx +441 -0
  132. data/data/kobako.wasm +0 -0
  133. data/ext/kobako/Cargo.toml +4 -2
  134. data/ext/kobako/src/runtime/bridge.rs +154 -81
  135. data/ext/kobako/src/runtime/errors.rs +1 -1
  136. data/ext/kobako/src/runtime/escape.rs +67 -0
  137. data/ext/kobako/src/runtime/filesystem/grants.rs +107 -0
  138. data/ext/kobako/src/runtime/filesystem/ruby.rs +708 -0
  139. data/ext/kobako/src/runtime/filesystem.rs +208 -0
  140. data/ext/kobako/src/runtime/gvl.rs +49 -8
  141. data/ext/kobako/src/runtime/pinned_value.rs +31 -0
  142. data/ext/kobako/src/runtime/snapshot.rs +127 -0
  143. data/ext/kobako/src/runtime.rs +125 -199
  144. data/lib/kobako/catalog/extensions.rb +5 -5
  145. data/lib/kobako/catalog/flags.rb +2 -14
  146. data/lib/kobako/catalog/grants.rb +30 -0
  147. data/lib/kobako/catalog/handles.rb +6 -6
  148. data/lib/kobako/catalog/mount.rb +19 -0
  149. data/lib/kobako/catalog/services.rb +13 -2
  150. data/lib/kobako/catalog/snippets.rb +11 -15
  151. data/lib/kobako/catalog.rb +11 -0
  152. data/lib/kobako/codec/decoder.rb +10 -9
  153. data/lib/kobako/codec/ext_types.rb +4 -6
  154. data/lib/kobako/codec/handle_walk.rb +4 -6
  155. data/lib/kobako/codec/nesting.rb +7 -7
  156. data/lib/kobako/codec/state.rb +19 -3
  157. data/lib/kobako/codec/utils.rb +1 -1
  158. data/lib/kobako/codec/value_budget.rb +86 -0
  159. data/lib/kobako/codec.rb +7 -0
  160. data/lib/kobako/context.rb +40 -13
  161. data/lib/kobako/errors.rb +15 -14
  162. data/lib/kobako/filesystem.rb +69 -0
  163. data/lib/kobako/payload/arguments.rb +2 -2
  164. data/lib/kobako/pool.rb +6 -6
  165. data/lib/kobako/sandbox.rb +55 -27
  166. data/lib/kobako/sandbox_options.rb +39 -13
  167. data/lib/kobako/snippet/{binary.rb → bytecode.rb} +2 -2
  168. data/lib/kobako/snippet/source.rb +1 -1
  169. data/lib/kobako/snippet.rb +1 -1
  170. data/lib/kobako/transport/block_failure.rb +35 -0
  171. data/lib/kobako/transport/dispatcher.rb +33 -29
  172. data/lib/kobako/transport/exposure.rb +7 -9
  173. data/lib/kobako/transport/reflection.rb +79 -23
  174. data/lib/kobako/transport/run.rb +5 -5
  175. data/lib/kobako/transport/yielder.rb +9 -31
  176. data/lib/kobako/transport.rb +1 -0
  177. data/lib/kobako/version.rb +1 -1
  178. data/lib/kobako.rb +35 -0
  179. data/release-please-config.json +1 -0
  180. data/sig/kobako/catalog/extensions.rbs +3 -3
  181. data/sig/kobako/catalog/flags.rbs +0 -4
  182. data/sig/kobako/catalog/grants.rbs +13 -0
  183. data/sig/kobako/catalog/handles.rbs +3 -3
  184. data/sig/kobako/catalog/mount.rbs +24 -0
  185. data/sig/kobako/catalog/services.rbs +5 -1
  186. data/sig/kobako/catalog/snippets.rbs +4 -4
  187. data/sig/kobako/catalog.rbs +1 -0
  188. data/sig/kobako/codec/decoder.rbs +1 -1
  189. data/sig/kobako/codec/handle_walk.rbs +1 -1
  190. data/sig/kobako/codec/nesting.rbs +2 -2
  191. data/sig/kobako/codec/state.rbs +7 -1
  192. data/sig/kobako/codec/utils.rbs +1 -1
  193. data/sig/kobako/codec/value_budget.rbs +23 -0
  194. data/sig/kobako/codec.rbs +3 -2
  195. data/sig/kobako/context.rbs +14 -6
  196. data/sig/kobako/errors.rbs +4 -4
  197. data/sig/kobako/filesystem.rbs +93 -0
  198. data/sig/kobako/guest_yielder.rbs +9 -0
  199. data/sig/kobako/payload/arguments.rbs +1 -1
  200. data/sig/kobako/pool.rbs +3 -3
  201. data/sig/kobako/runtime.rbs +21 -49
  202. data/sig/kobako/sandbox.rbs +9 -5
  203. data/sig/kobako/sandbox_options.rbs +25 -3
  204. data/sig/kobako/snapshot.rbs +20 -0
  205. data/sig/kobako/snippet/bytecode.rbs +12 -0
  206. data/sig/kobako/transport/block_failure.rbs +13 -0
  207. data/sig/kobako/transport/dispatcher.rbs +4 -4
  208. data/sig/kobako/transport/exposure.rbs +2 -2
  209. data/sig/kobako/transport/reflection.rbs +14 -1
  210. data/sig/kobako/transport/run.rbs +3 -3
  211. data/sig/kobako/transport/yielder.rbs +3 -7
  212. data/sig/kobako/transport.rbs +3 -3
  213. metadata +116 -15
  214. data/crates/kobako-wasmtime/src/capture.rs +0 -96
  215. data/crates/kobako-wasmtime/src/dispatch.rs +0 -107
  216. data/crates/kobako-wasmtime/src/guest_mem.rs +0 -265
  217. data/crates/kobako-wasmtime/src/io.rs +0 -286
  218. data/sig/kobako/snippet/binary.rbs +0 -12
@@ -0,0 +1,3469 @@
1
+ //! `Overlay`: read-only lower Layers under an upper Tmpfs, stacked into
2
+ //! one Filesystem, as Linux overlayfs stacks a mount's layers.
3
+ //!
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;
15
+ mod stack;
16
+
17
+ use std::any::Any;
18
+ use std::sync::Arc;
19
+
20
+ use super::rdir::Rdir;
21
+ use super::{
22
+ DentryOperations, FileOperations, FileSystemType, FileType, Filesystem, FilesystemError, Iattr,
23
+ InodeOperations, Kstat, Lookup, OpenHow, RenameFlag, Tmpfs,
24
+ };
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.
44
+ #[derive(Clone)]
45
+ pub struct Overlay {
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
+ }
106
+ }
107
+
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),
122
+ }
123
+ }
124
+
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());
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
+ }))
156
+ }
157
+
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
+ })
173
+ }
174
+ }
175
+
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)
240
+ }
241
+ }
242
+
243
+ impl InodeOperations for OverlayLink {
244
+ fn getattr(&self, dentry: &Dentry) -> Result<Kstat, FilesystemError> {
245
+ self.0.real_getattr(dentry)
246
+ }
247
+
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()
252
+ }
253
+ }
254
+
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(())
275
+ }
276
+ }
277
+
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
+ }))
338
+ }
339
+
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(())
349
+ }
350
+
351
+ fn mkdir(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
352
+ self.fs.mkdir(&self.fs.parent(dentry)?, &dentry.name())
353
+ }
354
+
355
+ fn rmdir(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
356
+ self.fs.rmdir(&self.fs.parent(dentry)?, dentry)
357
+ }
358
+
359
+ fn unlink(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
360
+ self.fs.unlink(&self.fs.parent(dentry)?, dentry)
361
+ }
362
+
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)
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
+ }
389
+
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)
399
+ }
400
+
401
+ fn read_iter(
402
+ &mut self,
403
+ _iocb: &mut super::Kiocb,
404
+ _buf: &mut [u8],
405
+ ) -> Result<usize, FilesystemError> {
406
+ generic_read_dir()
407
+ }
408
+
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()))
416
+ }
417
+
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(())
423
+ }
424
+ }
425
+
426
+ #[cfg(test)]
427
+ mod tests {
428
+ use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
429
+ use std::sync::Mutex;
430
+ use std::thread;
431
+ use std::time::{Duration, Instant};
432
+
433
+ use super::super::tmpfs::Tmpfs;
434
+ use super::super::wrap::{Hook, Wrap};
435
+ use super::super::{FileType, Finish, Kiocb};
436
+ use super::*;
437
+ use crate::vfs::kern_path::{kern_path, KernPath};
438
+ use crate::vfs::Mask;
439
+
440
+ /// `filesystem` reached through the VFS.
441
+ fn at(filesystem: impl FileSystemType) -> KernPath {
442
+ kern_path(Arc::new(filesystem))
443
+ }
444
+
445
+ /// The Filesystem `filesystem` holds, reached through the VFS.
446
+ fn on<F: FileSystemType>(filesystem: &Arc<F>) -> KernPath {
447
+ kern_path(filesystem.clone())
448
+ }
449
+
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
+ }
462
+ }
463
+
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)
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
+ );
491
+ }
492
+
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
+ );
515
+ }
516
+
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
+ );
539
+ }
540
+
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> {
553
+ let mut buf = vec![0; 64];
554
+ let read = file
555
+ .read_iter(&mut Kiocb::at(0), &mut buf)
556
+ .expect("the file must read");
557
+ buf.truncate(read);
558
+ buf
559
+ }
560
+
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(
567
+ path,
568
+ &OpenHow {
569
+ write: true,
570
+ truncate: true,
571
+ ..OpenHow::default()
572
+ },
573
+ )?
574
+ .write_iter(&mut Kiocb::at(0), contents)
575
+ .map(drop)
576
+ }
577
+
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]) {
591
+ write(fs, path, contents).expect("the write must land");
592
+ }
593
+
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
600
+ .expect("the directory must list")
601
+ .into_iter()
602
+ .map(|entry| entry.name)
603
+ .collect();
604
+ names.sort();
605
+ names
606
+ }
607
+
608
+ fn tmpfs_with(path: &str, contents: &str) -> Arc<Tmpfs> {
609
+ Arc::new([(path, contents)].into_iter().collect())
610
+ }
611
+
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)
692
+ }
693
+
694
+ /// A host directory holding `target`, `dir/inner`, and the links `link`
695
+ /// to `target`, `dirlink` to `dir`, and `dangling` to nothing, removed
696
+ /// when it drops.
697
+ #[cfg(unix)]
698
+ struct LinkTree(std::path::PathBuf);
699
+
700
+ #[cfg(unix)]
701
+ impl LinkTree {
702
+ fn new(name: &str) -> Self {
703
+ let path =
704
+ std::env::temp_dir().join(format!("kobako-overlay-{name}-{}", std::process::id()));
705
+ let _ = std::fs::remove_dir_all(&path);
706
+ std::fs::create_dir_all(path.join("dir")).expect("the host directory must be created");
707
+ std::fs::write(path.join("target"), "lower").expect("the host file must be written");
708
+ std::fs::write(path.join("dir/inner"), "lower").expect("the host file must be written");
709
+ for (target, link) in [
710
+ ("target", "link"),
711
+ ("dir", "dirlink"),
712
+ ("missing", "dangling"),
713
+ ] {
714
+ std::os::unix::fs::symlink(target, path.join(link)).expect("the link must be made");
715
+ }
716
+ Self(path)
717
+ }
718
+
719
+ /// An empty upper over the host directory as the lower Layer.
720
+ fn overlay(&self) -> KernPath {
721
+ self.under(Arc::new(Tmpfs::new()))
722
+ }
723
+
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")
731
+ }
732
+ }
733
+
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
738
+ #[cfg(unix)]
739
+ #[test]
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");
746
+ assert_eq!(
747
+ (
748
+ kind(&overlay, "dirlink/inner"),
749
+ names(&overlay, "dirlink").len()
750
+ ),
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"
754
+ );
755
+ }
756
+
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.
766
+ // @behavior FS-100
767
+ #[cfg(unix)]
768
+ #[test]
769
+ fn an_overlay_opens_no_link_as_a_directory_where_asked_not_to_follow() {
770
+ let host = LinkTree::new("nofollow");
771
+ let overlay = host.overlay();
772
+ assert_eq!(
773
+ (
774
+ overlay.open_dir("dirlink").map(drop),
775
+ overlay.vfs_readlink("dirlink")
776
+ ),
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"
780
+ );
781
+ }
782
+
783
+ // @behavior FS-049
784
+ #[cfg(unix)]
785
+ #[test]
786
+ fn an_overlay_reports_a_link_its_layer_holds_as_a_link() {
787
+ let host = LinkTree::new("lstat");
788
+ let overlay = host.overlay();
789
+ let file_type = |path| kind(&overlay, path);
790
+ assert_eq!(
791
+ (file_type("link"), file_type("dangling")),
792
+ (Ok(FileType::Symlink), Ok(FileType::Symlink)),
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"
795
+ );
796
+ }
797
+
798
+ // A link to a directory follows to one, and a dangling link follows to
799
+ // nothing, so only judging each link itself lets both go.
800
+ // @behavior FS-050
801
+ #[cfg(unix)]
802
+ #[test]
803
+ fn an_overlay_removes_a_link_its_read_only_layer_holds_as_itself() {
804
+ let host = LinkTree::new("unlink");
805
+ let overlay = host.overlay();
806
+ let removed = (
807
+ overlay.vfs_rmdir("dirlink"),
808
+ overlay.vfs_unlink("dirlink"),
809
+ overlay.vfs_unlink("dangling"),
810
+ );
811
+ let gone = |path| overlay.vfs_lstat(path).err();
812
+ assert_eq!(
813
+ (
814
+ removed,
815
+ gone("dirlink"),
816
+ gone("dangling"),
817
+ host.0.join("dirlink").is_symlink()
818
+ ),
819
+ (
820
+ (Err(FilesystemError::NotADirectory), Ok(()), Ok(())),
821
+ Some(FilesystemError::NotFound),
822
+ Some(FilesystemError::NotFound),
823
+ true
824
+ ),
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, \
827
+ leaving the Layer as it was"
828
+ );
829
+ }
830
+
831
+ // A write would copy the FIFO up, which opens it, and a rename would
832
+ // move what no Layer above can hold, so only refusals keep either
833
+ // from waiting on a peer or landing nothing.
834
+ // @behavior FS-092
835
+ #[cfg(unix)]
836
+ #[test]
837
+ fn an_overlay_neither_copies_up_nor_moves_a_fifo_below_it() {
838
+ let host = LinkTree::new("fifo");
839
+ let made = std::process::Command::new("mkfifo")
840
+ .arg(host.0.join("pipe"))
841
+ .status()
842
+ .expect("mkfifo must run");
843
+ assert!(made.success(), "the FIFO must be made");
844
+ let overlay = host.overlay();
845
+ assert_eq!(
846
+ (
847
+ write(&overlay, "pipe", b"upper"),
848
+ overlay.rename("pipe", &overlay, "moved")
849
+ ),
850
+ (
851
+ Err(FilesystemError::PermissionDenied),
852
+ Err(FilesystemError::CrossesDevices)
853
+ ),
854
+ "a FIFO a read-only Bind Layer holds, written or renamed through an Overlay, must \
855
+ be refused as permission denied and as crossing devices"
856
+ );
857
+ }
858
+
859
+ // A FIFO is no directory, so each rename meets a kind it cannot
860
+ // replace; were it taken for neither, the directory made above would
861
+ // hide the FIFO, and the FIFO's rename would answer for its Layer.
862
+ // @behavior FS-095
863
+ #[cfg(unix)]
864
+ #[test]
865
+ fn an_overlay_replaces_neither_a_fifo_nor_a_directory_with_the_other() {
866
+ let host = LinkTree::new("fifo-kinds");
867
+ let made = std::process::Command::new("mkfifo")
868
+ .arg(host.0.join("pipe"))
869
+ .status()
870
+ .expect("mkfifo must run");
871
+ assert!(made.success(), "the FIFO must be made");
872
+ let overlay = host.overlay();
873
+ overlay
874
+ .vfs_mkdir("made")
875
+ .expect("a directory must be made in the upper");
876
+ let file_type = |path| kind(&overlay, path);
877
+ assert_eq!(
878
+ (
879
+ overlay.rename("pipe", &overlay, "dir"),
880
+ overlay.rename("made", &overlay, "pipe"),
881
+ file_type("pipe"),
882
+ file_type("made"),
883
+ ),
884
+ (
885
+ Err(FilesystemError::IsADirectory),
886
+ Err(FilesystemError::NotADirectory),
887
+ Ok(FileType::Fifo),
888
+ Ok(FileType::Dir)
889
+ ),
890
+ "a FIFO renamed onto a directory through an Overlay must be refused as a directory, \
891
+ and a directory renamed onto a FIFO as no directory, leaving both in place"
892
+ );
893
+ }
894
+
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.
1017
+ // @behavior FS-051
1018
+ #[cfg(unix)]
1019
+ #[test]
1020
+ fn an_overlay_refuses_to_move_a_link_its_read_only_layer_holds() {
1021
+ let host = LinkTree::new("through");
1022
+ let overlay = host.overlay();
1023
+ assert_eq!(
1024
+ (
1025
+ overlay.rename("link", &overlay, "moved"),
1026
+ read(&overlay, "target")
1027
+ ),
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"
1031
+ );
1032
+ }
1033
+
1034
+ // The lower Tmpfs holds a readable `dangling`, so only the upper
1035
+ // link standing at that path can keep the read from reaching it.
1036
+ // @behavior FS-053
1037
+ #[cfg(unix)]
1038
+ #[test]
1039
+ fn a_link_a_layer_holds_hides_what_the_layers_below_hold_there() {
1040
+ let host = LinkTree::new("shadow");
1041
+ let overlay = at(lowers(
1042
+ Arc::new(host.bind()),
1043
+ tmpfs_with("dangling", "lower"),
1044
+ ));
1045
+ assert_eq!(
1046
+ overlay
1047
+ .open(
1048
+ "dangling",
1049
+ &OpenHow {
1050
+ read: true,
1051
+ ..OpenHow::default()
1052
+ }
1053
+ )
1054
+ .map(drop),
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"
1058
+ );
1059
+ }
1060
+
1061
+ // The lower Tmpfs holds `dirlink/x` beside where the upper link points,
1062
+ // so only the link hiding everything beneath it keeps `x` out.
1063
+ // @behavior FS-057
1064
+ #[cfg(unix)]
1065
+ #[test]
1066
+ fn a_link_a_layer_holds_hides_what_the_layers_below_hold_beneath_it() {
1067
+ let host = LinkTree::new("shadow-beneath");
1068
+ let overlay = at(lowers(
1069
+ Arc::new(host.bind()),
1070
+ tmpfs_with("dirlink/x", "lower"),
1071
+ ));
1072
+ assert_eq!(
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"
1082
+ );
1083
+ }
1084
+
1085
+ // The lower directory stays in its Layer, so only the whiteout keeps
1086
+ // it from showing once the upper file goes.
1087
+ // @behavior FS-058
1088
+ #[test]
1089
+ fn removing_a_file_over_a_lower_directory_leaves_it_hidden() {
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");
1093
+ assert_eq!(
1094
+ (
1095
+ removed,
1096
+ overlay.vfs_lstat("f").err(),
1097
+ on(&lower).vfs_lstat("f/g").is_ok()
1098
+ ),
1099
+ (Ok(()), Some(FilesystemError::NotFound), true),
1100
+ "a file a higher Layer holds over a lower Layer's directory, removed through \
1101
+ vfs_unlink, must leave the path gone from the view and the lower \
1102
+ directory as it was"
1103
+ );
1104
+ }
1105
+
1106
+ // The upper's `dir` holds only the whiteout hiding `file`, so
1107
+ // only judging the view, not that copy, lets the move go.
1108
+ // @behavior FS-059
1109
+ #[test]
1110
+ fn a_directory_moves_onto_one_holding_only_markers() {
1111
+ let (overlay, _) = overlay_on(Arc::new(Tmpfs::new()));
1112
+ overlay
1113
+ .vfs_unlink("dir/file")
1114
+ .expect("the file must be removed");
1115
+ overlay
1116
+ .vfs_mkdir("new")
1117
+ .expect("the directory must be made");
1118
+ overwrite(&overlay, "new/made", b"made");
1119
+ let moved = overlay.rename("new", &overlay, "dir");
1120
+ assert_eq!(
1121
+ (moved, names(&overlay, "dir")),
1122
+ (Ok(()), vec!["made".to_string()]),
1123
+ "a directory renamed through Overlay::rename onto one the view shows empty, whose \
1124
+ upper copy holds only a whiteout, must take its place and show only its own entries"
1125
+ );
1126
+ }
1127
+
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.
1130
+ // @behavior FS-060
1131
+ #[test]
1132
+ fn a_directory_made_again_moves_as_the_uppers_own() {
1133
+ let (overlay, _) = overlay_on(Arc::new(Tmpfs::new()));
1134
+ overlay
1135
+ .vfs_unlink("dir/file")
1136
+ .expect("the file must be removed");
1137
+ overlay
1138
+ .vfs_rmdir("dir")
1139
+ .expect("the directory must be removed");
1140
+ overlay
1141
+ .vfs_mkdir("dir")
1142
+ .expect("the directory must be made again");
1143
+ let moved = overlay.rename("dir", &overlay, "moved");
1144
+ assert_eq!(
1145
+ (
1146
+ moved,
1147
+ overlay.vfs_lstat("dir").err(),
1148
+ overlay
1149
+ .open_dir("moved")
1150
+ .and_then(|dir| dir.iterate_dir())
1151
+ .map(|entries| entries.len())
1152
+ ),
1153
+ (Ok(()), Some(FilesystemError::NotFound), Ok(0)),
1154
+ "a directory made again through an Overlay where a lower Layer's one was removed, \
1155
+ renamed through Overlay::rename, must move and leave nothing of the lower one behind"
1156
+ );
1157
+ }
1158
+
1159
+ // @behavior FS-061
1160
+ #[test]
1161
+ fn an_overlay_refuses_a_file_moved_beneath_itself_as_no_directory() {
1162
+ let (overlay, _) = overlay_on(Arc::new(Tmpfs::new()));
1163
+ assert_eq!(
1164
+ overlay.rename("dir/file", &overlay, "dir/file/x"),
1165
+ Err(FilesystemError::NotADirectory),
1166
+ "a file renamed through Overlay::rename to a path beneath itself must be refused as \
1167
+ no directory, as rename(2) refuses it"
1168
+ );
1169
+ }
1170
+
1171
+ // The link points at nothing, so only the link itself can be what an
1172
+ // exclusive create or a new directory finds there.
1173
+ // @behavior FS-052
1174
+ #[cfg(unix)]
1175
+ #[test]
1176
+ fn an_overlay_finds_a_dangling_link_where_a_new_entry_would_go() {
1177
+ let host = LinkTree::new("dangling");
1178
+ let overlay = host.overlay();
1179
+ let exclusive = OpenHow {
1180
+ write: true,
1181
+ ..OpenHow::default()
1182
+ };
1183
+ assert_eq!(
1184
+ (
1185
+ overlay.create("dangling", &exclusive).map(drop),
1186
+ overlay.vfs_mkdir("dangling")
1187
+ ),
1188
+ (
1189
+ Err(FilesystemError::AlreadyExists),
1190
+ Err(FilesystemError::AlreadyExists)
1191
+ ),
1192
+ "an exclusive create and a create_dir at a dangling link a lower Bind Layer \
1193
+ holds, through an Overlay, must find it already there"
1194
+ );
1195
+ }
1196
+
1197
+ // @behavior FS-008
1198
+ #[test]
1199
+ fn an_overlay_reads_through_to_a_lower_layer() {
1200
+ let (overlay, _) = overlay_on(Arc::new(Tmpfs::new()));
1201
+ assert_eq!(
1202
+ read(&overlay, "dir/file"),
1203
+ b"lower",
1204
+ "a lower file read through Overlay::open must read back the lower content"
1205
+ );
1206
+ }
1207
+
1208
+ // @behavior FS-009
1209
+ #[test]
1210
+ fn a_write_to_a_lower_layers_file_lands_in_the_upper() {
1211
+ let upper = Arc::new(Tmpfs::new());
1212
+ let (overlay, _) = overlay_on(upper.clone());
1213
+ overwrite(&overlay, "dir/file", b"upper");
1214
+ assert_eq!(
1215
+ read(&on(&upper), "upper/dir/file"),
1216
+ b"upper",
1217
+ "a lower Layer's file written through Overlay::open must land in the upper"
1218
+ );
1219
+ }
1220
+
1221
+ // @behavior FS-010
1222
+ #[test]
1223
+ fn an_overlay_leaves_a_lower_layer_as_it_was() {
1224
+ let (overlay, lower) = overlay_on(Arc::new(Tmpfs::new()));
1225
+ overwrite(&overlay, "dir/file", b"upper");
1226
+ overlay
1227
+ .vfs_unlink("dir/file")
1228
+ .expect("the file must be removed");
1229
+ assert_eq!(
1230
+ read(&on(&lower), "dir/file"),
1231
+ b"lower",
1232
+ "a lower Layer's file written and removed through an Overlay must stay as it was"
1233
+ );
1234
+ }
1235
+
1236
+ // @behavior FS-011
1237
+ #[test]
1238
+ fn a_file_removed_through_an_overlay_is_gone_from_its_view() {
1239
+ let (overlay, _) = overlay_on(Arc::new(Tmpfs::new()));
1240
+ overlay
1241
+ .vfs_unlink("dir/file")
1242
+ .expect("the file must be removed");
1243
+ assert_eq!(
1244
+ overlay.vfs_lstat("dir/file"),
1245
+ Err(FilesystemError::NotFound),
1246
+ "a lower file removed through vfs_unlink must no longer be found there"
1247
+ );
1248
+ }
1249
+
1250
+ // @behavior FS-013
1251
+ #[test]
1252
+ fn an_upper_layer_hides_the_same_path_in_a_lower_one() {
1253
+ let overlay = at(lowers(
1254
+ tmpfs_with("note", "upper"),
1255
+ tmpfs_with("note", "lower"),
1256
+ ));
1257
+ assert_eq!(
1258
+ read(&overlay, "note"),
1259
+ b"upper",
1260
+ "a path both Layers hold read through Overlay::open must read the upper Layer's content"
1261
+ );
1262
+ }
1263
+
1264
+ // @behavior FS-014
1265
+ #[test]
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()));
1269
+ overwrite(&overlay, "note", b"changed");
1270
+ assert_eq!(
1271
+ read(&on(&upper), "upper/note"),
1272
+ b"changed",
1273
+ "the upper's file written through Overlay::open must change in place"
1274
+ );
1275
+ }
1276
+
1277
+ // @behavior FS-015
1278
+ #[test]
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");
1284
+ assert_eq!(
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"
1288
+ );
1289
+ }
1290
+
1291
+ // @behavior FS-016
1292
+ #[test]
1293
+ fn a_removal_leaves_a_whiteout_in_the_upper() {
1294
+ let upper = Arc::new(Tmpfs::new());
1295
+ let (overlay, _) = overlay_on(upper.clone());
1296
+ overlay
1297
+ .vfs_unlink("dir/file")
1298
+ .expect("the file must be removed");
1299
+ assert_eq!(
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"
1306
+ );
1307
+ }
1308
+
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
1313
+ #[test]
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();
1341
+ assert_eq!(
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"
1346
+ );
1347
+ }
1348
+
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
1353
+ #[test]
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)
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
+ );
1382
+ assert_eq!(
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"
1387
+ );
1388
+ }
1389
+
1390
+ // @behavior FS-017
1391
+ #[test]
1392
+ fn a_whiteout_lasts_as_long_as_the_upper() {
1393
+ let upper = Arc::new(Tmpfs::new());
1394
+ let (overlay, lower) = overlay_on(upper.clone());
1395
+ overlay
1396
+ .vfs_unlink("dir/file")
1397
+ .expect("the file must be removed");
1398
+ let rebuilt = at(over(lower, upper));
1399
+ assert_eq!(
1400
+ rebuilt.vfs_lstat("dir/file"),
1401
+ Err(FilesystemError::NotFound),
1402
+ "a lower file removed through an Overlay over a Tmpfs must stay gone from a new Overlay over the same Layers"
1403
+ );
1404
+ }
1405
+
1406
+ // @behavior FS-018
1407
+ #[test]
1408
+ fn a_directory_made_again_where_one_was_removed_shows_nothing_from_below() {
1409
+ let (overlay, _) = overlay_on(Arc::new(Tmpfs::new()));
1410
+ overlay
1411
+ .vfs_unlink("dir/file")
1412
+ .expect("the file must be removed");
1413
+ overlay
1414
+ .vfs_rmdir("dir")
1415
+ .expect("the directory must be removed");
1416
+ overlay
1417
+ .vfs_mkdir("dir")
1418
+ .expect("the directory must be made");
1419
+ assert_eq!(
1420
+ names(&overlay, "dir"),
1421
+ Vec::<String>::new(),
1422
+ "a directory made again through vfs_mkdir must list nothing from the lower Layer"
1423
+ );
1424
+ }
1425
+
1426
+ // @behavior FS-019
1427
+ #[test]
1428
+ fn an_overlay_lists_none_of_its_markers() {
1429
+ let (overlay, _) = overlay_on(Arc::new(Tmpfs::new()));
1430
+ overwrite(&overlay, "dir/new", b"made");
1431
+ overlay
1432
+ .vfs_unlink("dir/file")
1433
+ .expect("the file must be removed");
1434
+ assert_eq!(
1435
+ names(&overlay, "dir"),
1436
+ ["new"],
1437
+ "a directory holding a whiteout listed through Overlay::read_dir must not list the \
1438
+ whited-out name"
1439
+ );
1440
+ }
1441
+
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
1472
+ #[test]
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())));
1478
+ assert_eq!(
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"
1486
+ );
1487
+ }
1488
+
1489
+ // A whiteout is a device rather than a name, so no name is the
1490
+ // Overlay's to reserve.
1491
+ // @behavior FS-020
1492
+ #[test]
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");
1496
+ assert_eq!(
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"
1501
+ );
1502
+ }
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
+
1518
+ // @behavior FS-021
1519
+ #[test]
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);
1527
+ assert_eq!(
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"
1532
+ );
1533
+ }
1534
+
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
+ }
1550
+ }
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
1555
+ #[test]
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()));
1561
+ assert_eq!(
1562
+ (
1563
+ overlay.vfs_mkdir("long"),
1564
+ on(&upper)
1565
+ .vfs_lstat("upper/long")
1566
+ .map(|found| found.file_type)
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"
1571
+ );
1572
+ }
1573
+
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
+ );
1591
+ }
1592
+
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
+ }
1615
+
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
+ }
1646
+
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
+ }
1667
+
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
+ }
1680
+
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
+ }
1694
+
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
+ }
1806
+
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);
1830
+ }
1831
+ }
1832
+
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)
1846
+ }
1847
+ }
1848
+
1849
+ // The other call starts inside this one, between its emptiness check
1850
+ // and its removal, so only the Overlay's lock keeps the file it
1851
+ // creates from being removed with the directory.
1852
+ // @behavior FS-037
1853
+ #[test]
1854
+ fn a_change_through_an_overlay_waits_for_the_one_in_progress() {
1855
+ let tmpfs = tmpfs_with("dir/.keep", "");
1856
+ let interleave = Arc::new(Interleave::default());
1857
+ on(&tmpfs)
1858
+ .vfs_unlink("dir/.keep")
1859
+ .expect("the seed must go");
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
1874
+ .other
1875
+ .lock()
1876
+ .unwrap()
1877
+ .take()
1878
+ .expect("the other call must have started");
1879
+ crate::sched::testing::join("the other call", other);
1880
+ assert_eq!(
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"
1889
+ );
1890
+ }
1891
+
1892
+ // @behavior FS-038
1893
+ #[test]
1894
+ fn a_change_through_an_overlay_from_inside_one_answers_a_deadlock() {
1895
+ type Answers = (bool, Result<(), FilesystemError>);
1896
+
1897
+ struct Reenters {
1898
+ overlay: Mutex<Option<crate::vfs::Path>>,
1899
+ answer: Mutex<Option<Answers>>,
1900
+ }
1901
+
1902
+ impl Hook for Reenters {
1903
+ fn getattr(
1904
+ &self,
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
+ ));
1915
+ }
1916
+ inner.getattr(dentry)
1917
+ }
1918
+ }
1919
+
1920
+ let layer = Arc::new(Reenters {
1921
+ overlay: Mutex::new(None),
1922
+ answer: Mutex::new(None),
1923
+ });
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)
1938
+ .expect("the outer call must answer");
1939
+ let answers = layer.answer.lock().unwrap().take();
1940
+ assert_eq!(
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"
1945
+ );
1946
+ }
1947
+
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));
1956
+ let upper = Arc::new(upper);
1957
+ (
1958
+ at(over(tmpfs_with("dir/file", "lower"), upper.clone())),
1959
+ upper,
1960
+ )
1961
+ }
1962
+
1963
+ // The upper copy is all the room there is, so the whiteout cannot be
1964
+ // made; taking the copy away first would show the lower file again.
1965
+ // @behavior FS-040
1966
+ #[test]
1967
+ fn a_removal_that_cannot_leave_its_whiteout_removes_nothing() {
1968
+ let (overlay, _) = full_over_lower(&[("dir/file", "upper")], 2);
1969
+ assert_eq!(
1970
+ (overlay.vfs_unlink("dir/file"), read(&overlay, "dir/file")),
1971
+ (Err(FilesystemError::StorageFull), b"upper".to_vec()),
1972
+ "a file removed through vfs_unlink whose whiteout finds no room must stay as \
1973
+ it was"
1974
+ );
1975
+ }
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.
1979
+ // @behavior FS-041
1980
+ #[test]
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");
1991
+ assert_eq!(
1992
+ (overlay.vfs_mkdir("gone"), overlay.vfs_lstat("gone")),
1993
+ (
1994
+ Err(FilesystemError::StorageFull),
1995
+ Err(FilesystemError::NotFound)
1996
+ ),
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"
1999
+ );
2000
+ }
2001
+
2002
+ // The copy up takes the last room, so the whiteout over the source
2003
+ // cannot be made; moving first would leave the file in two places.
2004
+ // @behavior FS-042
2005
+ #[test]
2006
+ fn a_rename_that_cannot_leave_its_whiteout_moves_nothing() {
2007
+ let (overlay, _) = full_over_lower(&[], 2);
2008
+ assert_eq!(
2009
+ (
2010
+ overlay.rename("dir/file", &overlay, "dir/moved"),
2011
+ overlay.vfs_lstat("dir/moved"),
2012
+ read(&overlay, "dir/file")
2013
+ ),
2014
+ (
2015
+ Err(FilesystemError::StorageFull),
2016
+ Err(FilesystemError::NotFound),
2017
+ b"lower".to_vec()
2018
+ ),
2019
+ "a file renamed through Overlay::rename whose whiteout finds no room must stay where \
2020
+ it was"
2021
+ );
2022
+ }
2023
+
2024
+ // @behavior FS-043
2025
+ #[test]
2026
+ fn a_directory_a_lower_layer_holds_does_not_move_through_an_overlay() {
2027
+ let (overlay, _) = overlay_on(Arc::new(Tmpfs::new()));
2028
+ assert_eq!(
2029
+ (
2030
+ overlay.rename("dir", &overlay, "moved"),
2031
+ overlay.vfs_lstat("moved")
2032
+ ),
2033
+ (
2034
+ Err(FilesystemError::CrossesDevices),
2035
+ Err(FilesystemError::NotFound)
2036
+ ),
2037
+ "a directory a lower Layer holds renamed through Overlay::rename must be refused \
2038
+ as crossing devices, as overlayfs refuses it"
2039
+ );
2040
+ }
2041
+
2042
+ // The directory shows only from the lower Layer, so only a check of
2043
+ // the whole view finds it in the way.
2044
+ // @behavior FS-044
2045
+ #[test]
2046
+ fn a_rename_onto_what_an_overlay_shows_is_checked_against_the_view() {
2047
+ let upper = Arc::new(Tmpfs::new());
2048
+ let (overlay, _) = overlay_on(upper.clone());
2049
+ overwrite(&overlay, "note", b"x");
2050
+ overlay
2051
+ .vfs_mkdir("empty")
2052
+ .expect("the directory must be made");
2053
+ assert_eq!(
2054
+ (
2055
+ overlay.rename("note", &overlay, "dir"),
2056
+ overlay.rename("empty", &overlay, "dir"),
2057
+ read(&overlay, "dir/file")
2058
+ ),
2059
+ (
2060
+ Err(FilesystemError::IsADirectory),
2061
+ Err(FilesystemError::DirectoryNotEmpty),
2062
+ b"lower".to_vec()
2063
+ ),
2064
+ "a rename through Overlay::rename onto a directory only a lower Layer shows must \
2065
+ be refused as rename(2) refuses it, leaving that directory as it was"
2066
+ );
2067
+ }
2068
+
2069
+ // A directory moved over an empty one a lower Layer holds must not take
2070
+ // up what that Layer later shows there.
2071
+ // @behavior FS-045
2072
+ #[test]
2073
+ fn a_directory_moved_over_a_lower_one_is_marked_opaque() {
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()));
2080
+ overlay
2081
+ .vfs_mkdir("made")
2082
+ .expect("the directory must be made");
2083
+ overlay
2084
+ .rename("made", &overlay, "dir")
2085
+ .expect("the directory must move over the empty one");
2086
+ overwrite(&on(&lower), "dir/later", b"lower");
2087
+ assert_eq!(
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"
2299
+ );
2300
+ }
2301
+
2302
+ // The upper has room for the copy's entry but for less than
2303
+ // the lower file's bytes, so the copy fails part way.
2304
+ // @behavior FS-046
2305
+ #[test]
2306
+ fn a_copy_up_that_fails_part_way_leaves_nothing_behind() {
2307
+ let upper = Arc::new(Tmpfs::new().with_size(Some(2)).with_nr_inodes(Some(8)));
2308
+ let (overlay, _) = overlay_on(upper.clone());
2309
+ assert_eq!(
2310
+ (
2311
+ write_over(&overlay, "dir/file", b"x"),
2312
+ on(&upper).vfs_lstat("upper/dir/file"),
2313
+ read(&overlay, "dir/file")
2314
+ ),
2315
+ (
2316
+ Err(FilesystemError::StorageFull),
2317
+ Err(FilesystemError::NotFound),
2318
+ b"lower".to_vec()
2319
+ ),
2320
+ "a write through Overlay::open whose copy up runs out of room must leave no partial \
2321
+ copy hiding the lower file"
2322
+ );
2323
+ }
2324
+
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
+ }
2340
+ }
2341
+
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,
2347
+ }
2348
+
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)
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
+ }
2369
+
2370
+ impl FoldsCase {
2371
+ fn new(tmpfs: Tmpfs) -> Self {
2372
+ Self { root: at(tmpfs) }
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
+ }
2380
+
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))
2395
+ }
2396
+ }
2397
+
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(())
2402
+ }
2403
+ }
2404
+
2405
+ impl InodeOperations for FoldsCase {
2406
+ fn getattr(&self, dentry: &Dentry) -> Result<Kstat, FilesystemError> {
2407
+ self.root.vfs_lstat(&folded_path_of(dentry))
2408
+ }
2409
+
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),
2421
+ }
2422
+ }
2423
+
2424
+ fn open(
2425
+ &self,
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)
2433
+ }
2434
+
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
+ })
2450
+ }
2451
+
2452
+ fn mkdir(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
2453
+ self.root.vfs_mkdir(&folded_path_of(dentry))
2454
+ }
2455
+
2456
+ fn rmdir(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
2457
+ self.root.vfs_rmdir(&folded_path_of(dentry))
2458
+ }
2459
+
2460
+ fn unlink(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
2461
+ self.root.vfs_unlink(&folded_path_of(dentry))
2462
+ }
2463
+
2464
+ fn rename(
2465
+ &self,
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))
2473
+ }
2474
+ }
2475
+
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
+ }
2480
+
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
+ }
2513
+
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
+ );
2532
+ }
2533
+
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
+ }
2560
+
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>>>);
2565
+
2566
+ impl Hook for RunsAfterOpen {
2567
+ fn open(
2568
+ &self,
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));
2577
+ }
2578
+ opened
2579
+ }
2580
+ }
2581
+
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
+ );
2601
+ }
2602
+
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
+ }
2653
+
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
+ }
2702
+
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);
2706
+
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));
2715
+ }
2716
+ inner.getattr(dentry)
2717
+ }
2718
+ }
2719
+
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
+ );
2770
+ }
2771
+
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
+ );
2836
+ }
2837
+ }
2838
+
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
+ }
2856
+ }
2857
+ }
2858
+
2859
+ /// A lower Layer whose one entry, `pipe`, is a FIFO, counting each open.
2860
+ struct HoldsFifo(Arc<AtomicUsize>);
2861
+
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(())
2866
+ }
2867
+ }
2868
+
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
+ })
2875
+ }
2876
+
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
+ })
2885
+ }
2886
+
2887
+ fn open(
2888
+ &self,
2889
+ _dentry: &Dentry,
2890
+ _how: &OpenHow,
2891
+ ) -> Result<Box<dyn FileOperations>, FilesystemError> {
2892
+ self.0.fetch_add(1, Ordering::SeqCst);
2893
+ Err(FilesystemError::Interrupted)
2894
+ }
2895
+ }
2896
+
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
+ }
2931
+
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
+ }
2960
+
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
+ }
2977
+
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
+ }
3013
+
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
+ }
3065
+
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
+ }
3084
+
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
+ );
3105
+ }
3106
+
3107
+ // @behavior FS-083
3108
+ #[test]
3109
+ fn an_overlay_spells_a_path_as_the_layer_showing_it_stores_it() {
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");
3119
+ assert_eq!(
3120
+ (
3121
+ overlay.lookup("PRIVATE"),
3122
+ private.lookup("SECRET"),
3123
+ private.lookup("New")
3124
+ ),
3125
+ (
3126
+ Ok("private".to_string()),
3127
+ Ok("secret".to_string()),
3128
+ Ok("New".to_string())
3129
+ ),
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"
3132
+ );
3133
+ }
3134
+
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
3139
+ #[test]
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"
3202
+ );
3203
+ }
3204
+
3205
+ // @behavior FS-080
3206
+ #[test]
3207
+ fn a_file_gone_during_a_rename_answers_not_found() {
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())));
3211
+ assert_eq!(
3212
+ overlay.rename("file", &overlay, "moved"),
3213
+ Err(FilesystemError::NotFound),
3214
+ "a file renamed through Overlay::rename that its Layer loses during the call must \
3215
+ answer not found"
3216
+ );
3217
+ }
3218
+
3219
+ // The upper Layer's `dir/sub` stands where the lower Layer holds a
3220
+ // file `dir`, so the merge below it meets a file on the way.
3221
+ // @behavior FS-078
3222
+ #[test]
3223
+ fn a_directory_over_a_lower_file_on_its_way_reads_as_a_directory() {
3224
+ let overlay = at(over(
3225
+ tmpfs_with("dir", "lower"),
3226
+ tmpfs_with("upper/dir/sub/file", "upper"),
3227
+ ));
3228
+ assert_eq!(
3229
+ (
3230
+ overlay
3231
+ .vfs_lstat("dir/sub")
3232
+ .map(|metadata| metadata.file_type),
3233
+ read(&overlay, "dir/sub/file")
3234
+ ),
3235
+ (Ok(FileType::Dir), b"upper".to_vec()),
3236
+ "a directory looked up through vfs_lstat above a lower Layer holding a file on \
3237
+ its path must read as a directory, as its files open"
3238
+ );
3239
+ }
3240
+
3241
+ // @behavior FS-101
3242
+ #[test]
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");
3247
+ assert_eq!(
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"
3252
+ );
3253
+ }
3254
+
3255
+ // @behavior FS-102
3256
+ #[test]
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");
3260
+ overlay
3261
+ .rename("dir/file", &overlay, "dir/moved")
3262
+ .expect("the rename must land");
3263
+ overwrite(&overlay, "dir/moved", b"moved");
3264
+ assert_eq!(
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"
3269
+ );
3270
+ }
3271
+
3272
+ // @behavior FS-103
3273
+ #[test]
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");
3285
+ assert_eq!(
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"
3290
+ );
3291
+ }
3292
+
3293
+ /// A Tmpfs whose first open sleeps a while, flagging that it began.
3294
+ #[derive(Default)]
3295
+ struct SleepsOnOpen {
3296
+ opening: AtomicBool,
3297
+ }
3298
+
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)
3310
+ }
3311
+ }
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.
3315
+ // @behavior FS-039
3316
+ #[test]
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"));
3330
+ assert_eq!(
3331
+ (
3332
+ crate::sched::testing::join("the first copy up", copying),
3333
+ waited,
3334
+ counts.load(Ordering::SeqCst) > 0
3335
+ ),
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"
3339
+ );
3340
+ }
3341
+
3342
+ // Copying up makes the directory in the upper, whose own identity
3343
+ // differs, so only reporting the lower Layer's keeps it.
3344
+ // @behavior FS-072
3345
+ #[test]
3346
+ fn an_overlay_keeps_a_directorys_identity_when_a_write_copies_it_up() {
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");
3352
+ (metadata.dev, metadata.ino)
3353
+ };
3354
+ let before = identity(&overlay);
3355
+ overwrite(&overlay, "dir/b.txt", b"b");
3356
+ assert_eq!(
3357
+ (identity(&overlay) == before, before.1 != 0),
3358
+ (true, true),
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"
3387
+ );
3388
+ }
3389
+
3390
+ // @behavior FS-073
3391
+ #[test]
3392
+ fn an_overlay_names_a_directory_alike_whether_a_link_at_its_end_is_followed() {
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>| {
3398
+ let metadata = metadata.expect("the directory must stand");
3399
+ (metadata.dev, metadata.ino)
3400
+ };
3401
+ assert_eq!(
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"
3406
+ );
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
+ }
3469
+ }