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,1412 @@
1
+ //! The names of one Filesystem a walk has reached, each kept by the
2
+ //! directory above it and its name there while something holds it, as
3
+ //! Linux's dentries are: a directory moved through the VFS takes every one
4
+ //! beneath it along, and one removed or replaced through it is dead. A name
5
+ //! found standing stays for the next walk, as Linux's dcache keeps it, up to a
6
+ //! bound; the walk asks `revalidate` before trusting it, so what changed behind
7
+ //! the runtime's back is found again.
8
+
9
+ use std::any::Any;
10
+ use std::num::NonZeroUsize;
11
+ use std::sync::{Arc, Condvar, Mutex, MutexGuard, OnceLock, PoisonError, Weak};
12
+ use std::thread::ThreadId;
13
+
14
+ use lru::LruCache;
15
+
16
+ use super::file::FileOperations;
17
+ use super::inode::{Inode, InodeOperations, Lookup};
18
+ use super::{FileType, FilesystemError, Kstat, OpenHow, SuperBlock};
19
+ use crate::hashtable::Hashtable;
20
+ use crate::locking::seqlock::SeqLock;
21
+ use crate::locking::RwSemaphoreGuard;
22
+ use crate::sched;
23
+
24
+ /// What a Filesystem answers about a name rather than an entry, as Linux's
25
+ /// `dentry_operations`: a name may stand for nothing, so no inode can answer
26
+ /// for it.
27
+ pub trait DentryOperations: Send + Sync {
28
+ /// Whether `dentry`, a name the runtime still holds in `dir`, names what
29
+ /// it recorded, as `d_revalidate` answers; `false` has the runtime drop
30
+ /// it and take the name afresh.
31
+ fn revalidate(
32
+ &self,
33
+ dir: &dyn InodeOperations,
34
+ dentry: &Dentry,
35
+ ) -> Result<bool, FilesystemError>;
36
+
37
+ /// Whether `dentry` goes once nothing holds it, rather than stay for the
38
+ /// next walk, as `d_delete` answers. A name whose `revalidate` cannot tell
39
+ /// a change must answer `true`, since it would be trusted when reached again.
40
+ fn delete(&self, _dentry: &Dentry) -> bool {
41
+ false
42
+ }
43
+ }
44
+
45
+ /// The most names a Filesystem keeps that nothing holds.
46
+ const MAX_CACHED_DENTRIES: NonZeroUsize = NonZeroUsize::new(1024).expect("the bound is not zero");
47
+
48
+ /// Every move of a dentry, as Linux's `rename_lock`, so a walk up its
49
+ /// parents answers where the dentry stood at one instant, and a walk learns of a
50
+ /// move landing meanwhile.
51
+ pub(super) static RENAME_LOCK: SeqLock = SeqLock::new();
52
+
53
+ /// One name of a Filesystem, and where it stands now. Only the VFS makes
54
+ /// one, so a Filesystem reads the name a call is about from it.
55
+ pub struct Dentry {
56
+ state: Mutex<State>,
57
+ /// This dentry, for whoever is handed it to keep, as Linux's `dget`
58
+ /// takes a reference to any dentry it is handed.
59
+ this: Weak<Dentry>,
60
+ /// The tables of its Filesystem instance a walk finds it in, as `d_sb`
61
+ /// reaches the instance a dentry belongs to.
62
+ hashtables: Arc<Hashtables>,
63
+ /// What the Filesystem keeps on the name, set once, as Linux's
64
+ /// `d_fsdata` holds a filesystem's own state for a dentry.
65
+ fsdata: OnceLock<Arc<dyn Any + Send + Sync>>,
66
+ /// What answers about this name, as `d_alloc` copies `s_d_op` from the
67
+ /// super block.
68
+ ops: Option<Arc<dyn DentryOperations>>,
69
+ /// Wakes the walks waiting while another looks this name up, as Linux's
70
+ /// `d_wait_lookup` waits for `DCACHE_PAR_LOOKUP` to clear.
71
+ wait: Condvar,
72
+ }
73
+
74
+ /// Where a walk asking for a lookup stands, as `d_alloc_parallel` answers.
75
+ enum InLookup {
76
+ /// A lookup already answered for the name.
77
+ Answered,
78
+ /// This walk asks, and ends the lookup once answered.
79
+ Begun,
80
+ /// This walk's own lookup of the name is under way, and asks again within it.
81
+ Within,
82
+ }
83
+
84
+ struct State {
85
+ /// The directory above, `None` where the dentry is a root, which stands as
86
+ /// its own parent, as Linux's `d_parent == self`, held without a cycle.
87
+ parent: Option<Arc<Dentry>>,
88
+ name: String,
89
+ /// What the name stands for once a lookup found it, `None` while the
90
+ /// dentry is negative.
91
+ inode: Option<Arc<Inode>>,
92
+ /// Where the lookup of the name stands, so a negative dentry says nothing
93
+ /// stands there rather than that nobody asked.
94
+ asked: LookupState,
95
+ /// Whether a walk finds it by its name, as Linux's `d_hash` links it into
96
+ /// `dentry_hashtable`; one still in lookup, or let go of, is not.
97
+ hashed: bool,
98
+ }
99
+
100
+ /// Where the lookup of a name stands, as `DCACHE_PAR_LOOKUP` marks a dentry a
101
+ /// walk is asking its Filesystem about and `d_add` ends that.
102
+ #[derive(Clone, Copy, PartialEq, Eq)]
103
+ enum LookupState {
104
+ /// No lookup answered for the name: none asked yet, or the one that did
105
+ /// ended without an answer here, as `d_lookup_done` leaves a dentry.
106
+ Unasked,
107
+ /// In lookup for the walk that made the dentry, which has yet to ask, as
108
+ /// `d_alloc_parallel` marks the dentry it makes.
109
+ Allocated(ThreadId),
110
+ /// The walk asking the Filesystem what stands here, while it asks.
111
+ Asking(ThreadId),
112
+ /// A lookup answered for the name, or a call made what stands there.
113
+ Answered,
114
+ }
115
+
116
+ impl Dentry {
117
+ fn new(
118
+ parent: Option<Arc<Dentry>>,
119
+ name: &str,
120
+ ops: Option<Arc<dyn DentryOperations>>,
121
+ hashtables: &Arc<Hashtables>,
122
+ ) -> Arc<Self> {
123
+ Arc::new_cyclic(|this| Self {
124
+ state: Mutex::new(State {
125
+ parent,
126
+ name: name.to_string(),
127
+ inode: None,
128
+ asked: LookupState::Unasked,
129
+ hashed: false,
130
+ }),
131
+ this: this.clone(),
132
+ hashtables: hashtables.clone(),
133
+ fsdata: OnceLock::new(),
134
+ ops,
135
+ wait: Condvar::new(),
136
+ })
137
+ }
138
+
139
+ /// What answers about this name, the same for every dentry of one
140
+ /// Filesystem.
141
+ pub fn ops(&self) -> Option<&Arc<dyn DentryOperations>> {
142
+ self.ops.as_ref()
143
+ }
144
+
145
+ /// The directory above, this dentry itself at a root, as Linux's
146
+ /// `dget_parent` takes it.
147
+ pub(crate) fn parent(&self) -> Arc<Dentry> {
148
+ self.parent_and_name().0
149
+ }
150
+
151
+ /// Whether this dentry is a root, standing as its own parent, as Linux's
152
+ /// `IS_ROOT` answers.
153
+ pub(crate) fn is_root(&self) -> bool {
154
+ self.state().parent.is_none()
155
+ }
156
+
157
+ /// The directory above and the name there, read together, so a move
158
+ /// landing meanwhile cannot pair one's parent with the other's name, as 9p
159
+ /// holds `rename_sem` to read both.
160
+ pub(crate) fn parent_and_name(&self) -> (Arc<Dentry>, String) {
161
+ let state = self.state();
162
+ let parent = state.parent.clone().unwrap_or_else(|| self.get());
163
+ (parent, state.name.clone())
164
+ }
165
+
166
+ /// This dentry, kept for as long as the caller holds it, as Linux's
167
+ /// `dget` does.
168
+ pub(crate) fn get(&self) -> Arc<Dentry> {
169
+ self.this
170
+ .upgrade()
171
+ .expect("a dentry handed out is held by whoever handed it")
172
+ }
173
+
174
+ fn state(&self) -> MutexGuard<'_, State> {
175
+ self.state.lock().unwrap_or_else(PoisonError::into_inner)
176
+ }
177
+
178
+ /// The name in the directory above, empty for the root.
179
+ pub fn name(&self) -> String {
180
+ self.state().name.clone()
181
+ }
182
+
183
+ /// Where this name stands now beneath the Filesystem's root, from a
184
+ /// leading `/` that alone names the root, as Linux's `dentry_path_raw`
185
+ /// answers.
186
+ pub fn path_raw(&self) -> String {
187
+ format!("/{}", self.names().join("/"))
188
+ }
189
+
190
+ /// Each name from the Filesystem's root down to this directory, as Linux's
191
+ /// `__dentry_path` walks up until `IS_ROOT`.
192
+ pub(crate) fn names(&self) -> Vec<String> {
193
+ RENAME_LOCK.read(|| {
194
+ let mut names = Vec::new();
195
+ let mut at = self.get();
196
+ while !at.is_root() {
197
+ let (parent, name) = at.parent_and_name();
198
+ names.push(name);
199
+ at = parent;
200
+ }
201
+ names.reverse();
202
+ names
203
+ })
204
+ }
205
+
206
+ /// Hold the directory this names shared, as
207
+ /// `inode_lock_shared(d_inode(dentry))`.
208
+ pub(crate) fn inode_lock_shared(&self) -> Result<RwSemaphoreGuard, FilesystemError> {
209
+ self.inode().ok_or(FilesystemError::NotFound)?.lock_shared()
210
+ }
211
+
212
+ /// Hold the directory this names exclusive, as
213
+ /// `inode_lock(d_inode(dentry))`.
214
+ pub(crate) fn inode_lock(&self) -> Result<RwSemaphoreGuard, FilesystemError> {
215
+ self.inode().ok_or(FilesystemError::NotFound)?.lock()
216
+ }
217
+
218
+ /// Whether the directory this names was removed or replaced, as
219
+ /// `IS_DEADDIR(d_inode(dentry))`.
220
+ pub(crate) fn is_deaddir(&self) -> bool {
221
+ self.inode().is_some_and(|inode| inode.is_deaddir())
222
+ }
223
+
224
+ /// Mark the directory this names removed or replaced.
225
+ pub(super) fn mark_dead(&self) {
226
+ if let Some(inode) = self.inode() {
227
+ inode.mark_dead();
228
+ }
229
+ }
230
+
231
+ /// What the name stands for, `None` while the dentry is negative, as
232
+ /// Linux's `d_inode` reads it.
233
+ pub fn inode(&self) -> Option<Arc<Inode>> {
234
+ self.state().inode.clone()
235
+ }
236
+
237
+ /// What answers for the entry this names, as `d_inode(dentry)->i_op`.
238
+ pub(crate) fn inode_ops(&self) -> Result<Arc<dyn InodeOperations>, FilesystemError> {
239
+ match self.inode() {
240
+ Some(inode) => Ok(inode.ops().clone()),
241
+ None => Err(FilesystemError::NotFound),
242
+ }
243
+ }
244
+
245
+ /// Record `inode` as what the name stands for, `None` where nothing
246
+ /// does, as a Filesystem's lookup hands Linux's `d_add` what it found.
247
+ /// A dentry already naming something keeps it, as Linux instantiates a
248
+ /// dentry once, so a later lookup never hides a change `d_revalidate`
249
+ /// must see.
250
+ pub fn add(&self, inode: Option<Arc<Inode>>) {
251
+ match inode {
252
+ Some(inode) => inode.add_hashed(self),
253
+ None => self.rehash(),
254
+ }
255
+ }
256
+
257
+ /// End the lookup of this name with an answer, and let a walk find it by
258
+ /// its name from now on, waking every walk waiting on it, as Linux's
259
+ /// `__d_add` takes it out of `in_lookup_hashtable` and `__d_rehash`es it.
260
+ pub(super) fn rehash(&self) {
261
+ {
262
+ let mut table = self.hashtables.dentry_hashtable();
263
+ let mut in_lookup = self.hashtables.in_lookup_hashtable();
264
+ let (parent, name) = self.parent_and_name();
265
+ let at = key(&parent, &name);
266
+ let this = self.get();
267
+ if in_lookup
268
+ .get(&at)
269
+ .is_some_and(|held| Arc::ptr_eq(&held, &this))
270
+ {
271
+ in_lookup.remove(&at);
272
+ }
273
+ let mut state = self.state();
274
+ state.asked = LookupState::Answered;
275
+ if !state.hashed {
276
+ table.insert(at, &this);
277
+ state.hashed = true;
278
+ }
279
+ }
280
+ self.wait.notify_all();
281
+ }
282
+
283
+ /// Whether a walk finds this dentry by its name, as Linux's `!d_unhashed`
284
+ /// answers.
285
+ pub(super) fn hashed(&self) -> bool {
286
+ self.state().hashed
287
+ }
288
+
289
+ /// Record `inode` on this dentry, which no walk finds by a name, as
290
+ /// `d_instantiate` hangs a root or a pseudo dentry on `i_dentry` under
291
+ /// `i_lock`.
292
+ pub(crate) fn instantiate(&self, inode: Arc<Inode>) {
293
+ let mut held = inode.dentry();
294
+ self.__instantiate(inode.clone(), &mut held);
295
+ }
296
+
297
+ /// Record `inode` here unless the dentry already names something, a
298
+ /// directory's `held` dentry being this one from then on, as
299
+ /// `__d_instantiate` hangs the dentry on `i_dentry`. The caller holds
300
+ /// `held`, the inode's lock on the dentry it keeps.
301
+ pub(super) fn __instantiate(&self, inode: Arc<Inode>, held: &mut Weak<Dentry>) {
302
+ let mut state = self.state();
303
+ if state.inode.is_none() {
304
+ if inode.kind() == FileType::Dir {
305
+ *held = self.this.clone();
306
+ }
307
+ state.inode = Some(inode);
308
+ }
309
+ }
310
+
311
+ /// What the Filesystem keeps on the name, set once, as Linux's
312
+ /// `d_fsdata` holds a filesystem's own state for a dentry.
313
+ pub fn fsdata(&self) -> &OnceLock<Arc<dyn Any + Send + Sync>> {
314
+ &self.fsdata
315
+ }
316
+
317
+ /// Whether this goes once nothing holds it, as `d_delete` answers; a name
318
+ /// no one answers for stays.
319
+ pub(crate) fn goes_when_unused(&self) -> bool {
320
+ self.ops.as_ref().is_some_and(|ops| ops.delete(self))
321
+ }
322
+
323
+ /// Whether both names stand for one entry, as hard links do, which Linux's
324
+ /// `vfs_rename` answers as having nothing to move, comparing `source ==
325
+ /// target`. A Filesystem naming no identity cannot tell.
326
+ pub(crate) fn names_the_entry_of(&self, other: &Dentry) -> bool {
327
+ let identity = |dentry: &Dentry| dentry.inode().and_then(|inode| inode.identity());
328
+ identity(self).is_some() && identity(self) == identity(other)
329
+ }
330
+
331
+ /// Whether a walk is still looking the name up, as Linux's `d_in_lookup`
332
+ /// tells a Filesystem's `atomic_open` the dentry it must look up first.
333
+ pub fn in_lookup(&self) -> bool {
334
+ matches!(
335
+ self.state().asked,
336
+ LookupState::Allocated(_) | LookupState::Asking(_)
337
+ )
338
+ }
339
+
340
+ /// Ask the directory `dir` of `sb` what stands here where no lookup answered
341
+ /// yet, and record it, as Linux's `lookup_fast` keeps a dentry, a negative
342
+ /// one too, and `__lookup_slow` asks `->lookup` for one just made. The
343
+ /// dentry to use in place of this one is handed back as `d_splice_alias`
344
+ /// hands it, none where this one records the answer.
345
+ pub(crate) fn lookup_inode(
346
+ &self,
347
+ sb: &SuperBlock,
348
+ dir: &Dentry,
349
+ ) -> Result<Option<Arc<Dentry>>, FilesystemError> {
350
+ let asked = self.ask_in_lookup(|| {
351
+ let found = dir.inode_ops()?.lookup(self)?;
352
+ self.splice_alias(sb, found)
353
+ })?;
354
+ Ok(asked.flatten())
355
+ }
356
+
357
+ /// What `ask` answers for this name where no lookup answered yet, none where
358
+ /// one did. The walk `d_alloc_parallel` made the dentry for asks, as it
359
+ /// holds the dentry in lookup, and `d_lookup_done` ends that.
360
+ pub(crate) fn ask_in_lookup<T>(
361
+ &self,
362
+ ask: impl FnOnce() -> Result<T, FilesystemError>,
363
+ ) -> Result<Option<T>, FilesystemError> {
364
+ let ends = match self.begin_lookup()? {
365
+ InLookup::Answered => return Ok(None),
366
+ InLookup::Begun => true,
367
+ InLookup::Within => false,
368
+ };
369
+ let asked = ask();
370
+ if ends {
371
+ self.lookup_done();
372
+ }
373
+ asked.map(Some)
374
+ }
375
+
376
+ /// Record what a lookup found at this name of `sb`, as Linux's
377
+ /// `d_splice_alias` does, handing back the dentry to use in place of this
378
+ /// one, none where this one records it. A directory already standing at
379
+ /// another dentry keeps that one, moved here, as a directory has one
380
+ /// dentry: a move that would put it beneath itself answers ELOOP, and one
381
+ /// whose locks another call holds answers ESTALE. An entry stored under
382
+ /// another spelling is recorded on that spelling's dentry, as `d_add_ci`
383
+ /// does.
384
+ pub(crate) fn splice_alias(
385
+ &self,
386
+ sb: &SuperBlock,
387
+ found: Lookup,
388
+ ) -> Result<Option<Arc<Dentry>>, FilesystemError> {
389
+ let inode = match found {
390
+ Lookup::Found(inode) => inode,
391
+ Lookup::Absent => {
392
+ self.add(None);
393
+ return Ok(None);
394
+ }
395
+ Lookup::Spelled { name, inode } => return self.add_ci(sb, &name, inode).map(Some),
396
+ };
397
+ match inode.find_alias_or_add(self) {
398
+ None => Ok(None),
399
+ Some(alias) => {
400
+ self.get().unalias(sb, &alias)?;
401
+ Ok(Some(alias))
402
+ }
403
+ }
404
+ }
405
+
406
+ /// The dentry of `name`, the spelling the directory above stores the
407
+ /// entry under, with `inode` spliced on it, as Linux's `d_add_ci` finds
408
+ /// that spelling's dentry, or makes one, rather than asking the
409
+ /// Filesystem again. One already held there is taken as it stands, as
410
+ /// `d_hash_and_lookup` asks no `d_revalidate`.
411
+ pub(crate) fn add_ci(
412
+ &self,
413
+ sb: &SuperBlock,
414
+ name: &str,
415
+ inode: Arc<Inode>,
416
+ ) -> Result<Arc<Dentry>, FilesystemError> {
417
+ let found = sb.dentries().alloc_parallel(&self.parent(), name)?;
418
+ let spliced = found.ask_in_lookup(|| found.splice_alias(sb, Lookup::Found(inode)))?;
419
+ Ok(spliced.flatten().unwrap_or(found))
420
+ }
421
+
422
+ /// `alias`, where a directory of `sb` stands, moved to this dentry's name,
423
+ /// taking its place, as Linux's `__d_unalias` moves it under `rename_lock`.
424
+ /// One that would stand beneath itself answers ELOOP, as `d_ancestor`
425
+ /// refuses it; so does a root, which only a Filesystem handing one inode
426
+ /// to two instances reaches. A move out of another directory takes that
427
+ /// directory's lock and the instance's `rename_mutex` without waiting,
428
+ /// since the caller holds the lock of the one it looks the name up in,
429
+ /// and answers ESTALE where another call holds either.
430
+ fn unalias(
431
+ self: &Arc<Self>,
432
+ sb: &SuperBlock,
433
+ alias: &Arc<Dentry>,
434
+ ) -> Result<(), FilesystemError> {
435
+ let (parent, name) = self.parent_and_name();
436
+ let mut table = self.hashtables.dentry_hashtable();
437
+ RENAME_LOCK.write(|| {
438
+ if d_ancestor(alias, self) || alias.is_root() {
439
+ return Err(FilesystemError::FilesystemLoop);
440
+ }
441
+ let (old_parent, old_name) = alias.parent_and_name();
442
+ let _held = match Arc::ptr_eq(&old_parent, &parent) {
443
+ true => None,
444
+ false => {
445
+ let stale = || FilesystemError::StaleNetworkFileHandle;
446
+ let rename = sb.rename_mutex().down_write_trylock().ok_or_else(stale)?;
447
+ let dir = old_parent
448
+ .inode()
449
+ .expect("a directory holding a dentry names its inode")
450
+ .trylock_shared();
451
+ Some((rename, dir.ok_or_else(stale)?))
452
+ }
453
+ };
454
+ let mut state = alias.state();
455
+ if state.hashed {
456
+ table.remove(&key(&old_parent, &old_name));
457
+ }
458
+ table.insert(key(&parent, &name), alias);
459
+ state.hashed = true;
460
+ state.parent = Some(parent.clone());
461
+ state.name = name.clone();
462
+ Ok(())
463
+ })
464
+ }
465
+
466
+ /// Begin the lookup the calling walk holds the dentry in lookup for, or one
467
+ /// nobody asked yet, once no other walk holds it. A Filesystem asking the
468
+ /// runtime about the name its own lookup is answering asks it again within
469
+ /// that lookup, as it would otherwise wait on itself.
470
+ fn begin_lookup(&self) -> Result<InLookup, FilesystemError> {
471
+ self.wait_lookup()?;
472
+ let mut state = self.state();
473
+ Ok(match state.asked {
474
+ LookupState::Asking(_) => InLookup::Within,
475
+ LookupState::Answered => InLookup::Answered,
476
+ LookupState::Allocated(_) | LookupState::Unasked => {
477
+ state.asked = LookupState::Asking(sched::current());
478
+ InLookup::Begun
479
+ }
480
+ })
481
+ }
482
+
483
+ /// Wait while another walk holds this name in lookup, until the walk's run
484
+ /// is to stop, as Linux's `d_wait_lookup` does, answering whether the
485
+ /// calling walk holds it in lookup itself.
486
+ fn wait_lookup(&self) -> Result<bool, FilesystemError> {
487
+ let current = sched::current();
488
+ loop {
489
+ let state = self.state();
490
+ match state.asked {
491
+ LookupState::Allocated(walk) | LookupState::Asking(walk) if walk == current => {
492
+ return Ok(true)
493
+ }
494
+ LookupState::Allocated(_) | LookupState::Asking(_)
495
+ if sched::fatal_signal_pending() =>
496
+ {
497
+ return Err(FilesystemError::Interrupted)
498
+ }
499
+ LookupState::Allocated(_) | LookupState::Asking(_) => {
500
+ drop(state);
501
+ sched::schedule(&mut || {
502
+ let state = self.state();
503
+ if matches!(
504
+ state.asked,
505
+ LookupState::Allocated(_) | LookupState::Asking(_)
506
+ ) {
507
+ drop(
508
+ self.wait
509
+ .wait_timeout(state, sched::sleep_slice())
510
+ .unwrap_or_else(PoisonError::into_inner),
511
+ );
512
+ }
513
+ });
514
+ }
515
+ LookupState::Unasked | LookupState::Answered => return Ok(false),
516
+ }
517
+ }
518
+ }
519
+
520
+ /// End this walk's lookup where no answer did, taking the dentry out of
521
+ /// `in_lookup_hashtable` and waking every walk waiting on it, as
522
+ /// `d_lookup_done` does; each of them looks the name up again.
523
+ fn lookup_done(&self) {
524
+ {
525
+ let mut in_lookup = self.hashtables.in_lookup_hashtable();
526
+ let (parent, name) = self.parent_and_name();
527
+ let at = key(&parent, &name);
528
+ let mut state = self.state();
529
+ if matches!(
530
+ state.asked,
531
+ LookupState::Allocated(_) | LookupState::Asking(_)
532
+ ) {
533
+ if in_lookup
534
+ .get(&at)
535
+ .is_some_and(|held| std::ptr::eq(held.as_ref(), self))
536
+ {
537
+ in_lookup.remove(&at);
538
+ }
539
+ state.asked = LookupState::Unasked;
540
+ }
541
+ }
542
+ self.wait.notify_all();
543
+ }
544
+
545
+ /// What kind of entry this records, `NotFound` where it records none, as
546
+ /// Linux's `d_is_*` read a dentry without asking its filesystem.
547
+ pub(crate) fn kind(&self) -> Result<FileType, FilesystemError> {
548
+ self.inode()
549
+ .map(|inode| inode.kind())
550
+ .ok_or(FilesystemError::NotFound)
551
+ }
552
+
553
+ /// What the entry this records reports of itself, `NotFound` where it
554
+ /// records none, as `vfs_getattr` asks the inode a dentry holds.
555
+ pub(crate) fn getattr(&self) -> Result<Kstat, FilesystemError> {
556
+ match self.inode() {
557
+ Some(inode) => inode.ops().getattr(self),
558
+ None => Err(FilesystemError::NotFound),
559
+ }
560
+ }
561
+
562
+ /// Open the file this records as `how` asks, as `vfs_open` finds
563
+ /// `f_op->open` on the inode a dentry holds.
564
+ pub(crate) fn open(&self, how: &OpenHow) -> Result<Box<dyn FileOperations>, FilesystemError> {
565
+ match self.inode() {
566
+ Some(inode) => inode.ops().open(self, how),
567
+ None => Err(FilesystemError::NotFound),
568
+ }
569
+ }
570
+
571
+ /// Where the link this records points, as `vfs_readlink` asks the inode a
572
+ /// dentry holds; anything else refuses as `readlinkat` does.
573
+ pub(crate) fn get_link(&self) -> Result<String, FilesystemError> {
574
+ match self.inode() {
575
+ Some(inode) => inode.ops().get_link(self),
576
+ None => Err(FilesystemError::NotFound),
577
+ }
578
+ }
579
+
580
+ /// Whether this dentry is `ancestor` or stands beneath it, as Linux's
581
+ /// `is_subdir` answers.
582
+ pub(crate) fn is_subdir(self: &Arc<Self>, ancestor: &Arc<Dentry>) -> bool {
583
+ Arc::ptr_eq(self, ancestor) || RENAME_LOCK.read(|| d_ancestor(ancestor, self))
584
+ }
585
+ }
586
+
587
+ /// Whether `ancestor` stands above `dentry`, as Linux's `d_ancestor` answers.
588
+ /// The caller holds `RENAME_LOCK`, so no move lands during the walk up.
589
+ fn d_ancestor(ancestor: &Arc<Dentry>, dentry: &Arc<Dentry>) -> bool {
590
+ let mut at = dentry.clone();
591
+ while !at.is_root() {
592
+ at = at.parent();
593
+ if Arc::ptr_eq(&at, ancestor) {
594
+ return true;
595
+ }
596
+ }
597
+ false
598
+ }
599
+
600
+ /// A dentry holds the one above it, and moves can stack them as deep as a
601
+ /// Filesystem holds directories, so the last reference lets go of each in a
602
+ /// loop, as Linux's `dput` walks up in one.
603
+ impl Drop for Dentry {
604
+ fn drop(&mut self) {
605
+ let parent = |dentry: &mut Dentry| {
606
+ dentry
607
+ .state
608
+ .get_mut()
609
+ .unwrap_or_else(PoisonError::into_inner)
610
+ .parent
611
+ .take()
612
+ };
613
+ let mut above = parent(self);
614
+ while let Some(mut dentry) = above.and_then(Arc::into_inner) {
615
+ above = parent(&mut dentry);
616
+ }
617
+ }
618
+ }
619
+
620
+ /// The directory above a name, by its address, and the name there.
621
+ type Key = (usize, String);
622
+
623
+ fn key(parent: &Arc<Dentry>, name: &str) -> Key {
624
+ (Arc::as_ptr(parent) as usize, name.to_string())
625
+ }
626
+
627
+ /// The dentries of one Filesystem a walk finds by the directory above and the
628
+ /// name there.
629
+ struct Hashtables {
630
+ /// Those a lookup answered, as Linux's `dentry_hashtable`.
631
+ dentry_hashtable: Mutex<Hashtable<Key, Dentry>>,
632
+ /// Those a walk is still looking up, as Linux's `in_lookup_hashtable`, so
633
+ /// any other walk reaching the name waits for that answer.
634
+ in_lookup_hashtable: Mutex<Hashtable<Key, Dentry>>,
635
+ }
636
+
637
+ impl Hashtables {
638
+ fn dentry_hashtable(&self) -> MutexGuard<'_, Hashtable<Key, Dentry>> {
639
+ self.dentry_hashtable
640
+ .lock()
641
+ .unwrap_or_else(PoisonError::into_inner)
642
+ }
643
+
644
+ /// Taken after `dentry_hashtable` where a call holds both.
645
+ fn in_lookup_hashtable(&self) -> MutexGuard<'_, Hashtable<Key, Dentry>> {
646
+ self.in_lookup_hashtable
647
+ .lock()
648
+ .unwrap_or_else(PoisonError::into_inner)
649
+ }
650
+ }
651
+
652
+ /// Every name of one Filesystem something still holds, by the directory
653
+ /// above it and its name there.
654
+ pub(crate) struct Dentries {
655
+ ops: Option<Arc<dyn DentryOperations>>,
656
+ hashtables: Arc<Hashtables>,
657
+ /// The names kept for the next walk, most recently found last, as Linux's
658
+ /// `d_lru`, each by its address.
659
+ lru: Mutex<LruCache<usize, Arc<Dentry>>>,
660
+ }
661
+
662
+ impl Dentries {
663
+ pub(crate) fn new(ops: Option<Arc<dyn DentryOperations>>) -> Self {
664
+ let hashtables = Arc::new(Hashtables {
665
+ dentry_hashtable: Mutex::default(),
666
+ in_lookup_hashtable: Mutex::default(),
667
+ });
668
+ Self {
669
+ ops,
670
+ hashtables,
671
+ lru: Mutex::new(LruCache::new(MAX_CACHED_DENTRIES)),
672
+ }
673
+ }
674
+
675
+ /// What every dentry of this table answers through.
676
+ pub(super) fn ops(&self) -> Option<&Arc<dyn DentryOperations>> {
677
+ self.ops.as_ref()
678
+ }
679
+
680
+ fn lru(&self) -> MutexGuard<'_, LruCache<usize, Arc<Dentry>>> {
681
+ self.lru.lock().unwrap_or_else(PoisonError::into_inner)
682
+ }
683
+
684
+ /// Keep `dentry`, which a lookup answered for, standing or not, for the next
685
+ /// walk, unless no walk finds it by its name any more or its Filesystem lets
686
+ /// it go, as `retain_dentry` adds a hashed dentry `d_delete` allows to the
687
+ /// LRU. What this pushes out goes once the table is let go of, as its
688
+ /// release may reach another Filesystem.
689
+ /// The check and the keeping run under the dentry's lock, which `unhash`
690
+ /// takes too, as both hold `d_lock`.
691
+ pub(crate) fn retain(&self, dentry: &Arc<Dentry>) {
692
+ if dentry.goes_when_unused() {
693
+ return;
694
+ }
695
+ let state = dentry.state();
696
+ if !state.hashed {
697
+ return;
698
+ }
699
+ let pushed_out = self
700
+ .lru()
701
+ .push(Arc::as_ptr(dentry) as usize, dentry.clone());
702
+ drop(state);
703
+ drop(pushed_out);
704
+ }
705
+
706
+ /// Have every dentry made from now on answer through `ops`, as
707
+ /// `set_default_d_op` sets `s_d_op` for each `d_alloc` after it.
708
+ pub(super) fn set_ops(&mut self, ops: Arc<dyn DentryOperations>) {
709
+ self.ops = Some(ops);
710
+ }
711
+
712
+ /// A root dentry recording `inode`, as `d_make_root` allocates one under no
713
+ /// name and instantiates it.
714
+ pub(super) fn make_root(&self, inode: Arc<Inode>) -> Arc<Dentry> {
715
+ let root = Dentry::new(None, "", self.ops.clone(), &self.hashtables);
716
+ root.instantiate(inode);
717
+ root
718
+ }
719
+
720
+ /// A dentry no walk reaches, a root of its own under no name, as
721
+ /// `d_alloc_pseudo` makes one for what is only ever open.
722
+ pub(crate) fn alloc_pseudo(&self) -> Arc<Dentry> {
723
+ Dentry::new(None, "", self.ops.clone(), &self.hashtables)
724
+ }
725
+
726
+ /// The dentry a lookup answered for `name` in `parent`, or a new one in
727
+ /// lookup for the calling walk, as Linux's `d_alloc_parallel` finds or
728
+ /// makes one. A walk finding the name in lookup by another waits for that
729
+ /// walk's answer, until its run is to stop, and takes the dentry where the
730
+ /// answer left it at the name, or looks again, as `d_alloc_parallel`
731
+ /// retries on a mismatch.
732
+ pub(crate) fn alloc_parallel(
733
+ &self,
734
+ parent: &Arc<Dentry>,
735
+ name: &str,
736
+ ) -> Result<Arc<Dentry>, FilesystemError> {
737
+ let at = key(parent, name);
738
+ loop {
739
+ let waited = {
740
+ let table = self.hashtables.dentry_hashtable();
741
+ if let Some(found) = table.get(&at) {
742
+ return Ok(found);
743
+ }
744
+ let mut in_lookup = self.hashtables.in_lookup_hashtable();
745
+ match in_lookup.get(&at) {
746
+ Some(waited) => waited,
747
+ None => {
748
+ let made = Dentry::new(
749
+ Some(parent.clone()),
750
+ name,
751
+ self.ops.clone(),
752
+ &self.hashtables,
753
+ );
754
+ made.state().asked = LookupState::Allocated(sched::current());
755
+ in_lookup.insert(at, &made);
756
+ return Ok(made);
757
+ }
758
+ }
759
+ };
760
+ if waited.wait_lookup()? {
761
+ return Ok(waited);
762
+ }
763
+ let (above, called) = waited.parent_and_name();
764
+ if waited.hashed() && Arc::ptr_eq(&above, parent) && called == name {
765
+ return Ok(waited);
766
+ }
767
+ }
768
+ }
769
+
770
+ /// The dentry a lookup answered for `name` in `parent`, as `d_lookup` finds
771
+ /// one.
772
+ pub(crate) fn lookup(&self, parent: &Arc<Dentry>, name: &str) -> Option<Arc<Dentry>> {
773
+ self.hashtables.dentry_hashtable().get(&key(parent, name))
774
+ }
775
+
776
+ /// Whether `dentry` was let go of from its name, as Linux's `d_unlinked`
777
+ /// answers; a root never is.
778
+ pub(crate) fn unlinked(&self, dentry: &Arc<Dentry>) -> bool {
779
+ !dentry.is_root() && !dentry.hashed()
780
+ }
781
+
782
+ /// Let go of `dentry`, which no longer names what stands at its name, and
783
+ /// of the names kept beneath it, as Linux's `d_invalidate` drops it and
784
+ /// shrinks what is below a positive one.
785
+ pub(crate) fn invalidate(&self, dentry: &Arc<Dentry>) {
786
+ if !dentry.hashed() {
787
+ return;
788
+ }
789
+ self.unhash(dentry);
790
+ if dentry.inode().is_some() {
791
+ self.shrink_dcache_parent(dentry);
792
+ }
793
+ }
794
+
795
+ /// Let go of the names kept for the next walk beneath `parent`, as Linux's
796
+ /// `shrink_dcache_parent` frees the unused dentries below one; one something
797
+ /// still holds stays with its holder. They are found through the LRU, the
798
+ /// only place keeping a dentry nothing else holds, rather than a list of
799
+ /// children.
800
+ pub(crate) fn shrink_dcache_parent(&self, parent: &Arc<Dentry>) {
801
+ let kept: Vec<Arc<Dentry>> = self.lru().iter().map(|(_, held)| held.clone()).collect();
802
+ // Decided before the LRU's lock is taken, as `retain` holds a name's lock
803
+ // while it takes the LRU's.
804
+ let beneath: Vec<usize> = kept
805
+ .iter()
806
+ .filter(|held| !Arc::ptr_eq(held, parent) && held.is_subdir(parent))
807
+ .map(|held| Arc::as_ptr(held) as usize)
808
+ .collect();
809
+ let let_go: Vec<_> = {
810
+ let mut lru = self.lru();
811
+ beneath.iter().filter_map(|at| lru.pop(at)).collect()
812
+ };
813
+ drop(let_go);
814
+ drop(kept);
815
+ }
816
+
817
+ /// Let go of `dentry` from its name, so the next lookup of the name makes
818
+ /// another and whoever holds this one keeps it, as Linux's `d_drop` unhashes
819
+ /// one for `d_invalidate` and for a `d_delete` of one still in use.
820
+ pub(crate) fn unhash(&self, dentry: &Arc<Dentry>) {
821
+ drop_hashed(&mut self.hashtables.dentry_hashtable(), dentry);
822
+ let kept = self.lru().pop(&(Arc::as_ptr(dentry) as usize));
823
+ drop(kept);
824
+ }
825
+
826
+ /// `dentry` moved to the name `target` stands at, taking every one beneath
827
+ /// it along, and `target` let go of, as Linux's `d_move` follows a rename.
828
+ /// A walk finding `dentry` by a name reaches it throughout, so it is never
829
+ /// taken for unlinked.
830
+ pub(crate) fn move_dentry(&self, dentry: &Arc<Dentry>, target: &Arc<Dentry>) {
831
+ self.unhash(target);
832
+ let mut table = self.hashtables.dentry_hashtable();
833
+ let (parent, name) = target.parent_and_name();
834
+ let (from, from_name) = dentry.parent_and_name();
835
+ RENAME_LOCK.write(|| {
836
+ let mut state = dentry.state();
837
+ if state.hashed {
838
+ table.remove(&key(&from, &from_name));
839
+ }
840
+ table.insert(key(&parent, &name), dentry);
841
+ state.hashed = true;
842
+ state.parent = Some(parent.clone());
843
+ state.name = name.clone();
844
+ });
845
+ }
846
+
847
+ /// `first` and `second` trade the names they stand at, as Linux's
848
+ /// `d_exchange` follows an exchange, so whoever holds either reaches
849
+ /// it where it went.
850
+ pub(crate) fn exchange(&self, first: &Arc<Dentry>, second: &Arc<Dentry>) {
851
+ let mut table = self.hashtables.dentry_hashtable();
852
+ let (first_at, second_at) = (first.parent_and_name(), second.parent_and_name());
853
+ RENAME_LOCK.write(|| {
854
+ let (mut one, mut other) = (first.state(), second.state());
855
+ for (state, (parent, name)) in [(&one, &first_at), (&other, &second_at)] {
856
+ if state.hashed {
857
+ table.remove(&key(parent, name));
858
+ }
859
+ }
860
+ table.insert(key(&second_at.0, &second_at.1), first);
861
+ table.insert(key(&first_at.0, &first_at.1), second);
862
+ std::mem::swap(&mut one.parent, &mut other.parent);
863
+ std::mem::swap(&mut one.name, &mut other.name);
864
+ (one.hashed, other.hashed) = (true, true);
865
+ });
866
+ }
867
+ }
868
+
869
+ /// Take `dentry` out of `table` where it is hashed, as Linux's `___d_drop`.
870
+ fn drop_hashed(table: &mut Hashtable<Key, Dentry>, dentry: &Arc<Dentry>) {
871
+ let (parent, name) = dentry.parent_and_name();
872
+ let mut state = dentry.state();
873
+ if state.hashed {
874
+ table.remove(&key(&parent, &name));
875
+ state.hashed = false;
876
+ }
877
+ }
878
+
879
+ #[cfg(test)]
880
+ mod tests {
881
+ use std::any::Any;
882
+ use std::sync::Arc;
883
+
884
+ use super::{Dentries, Dentry};
885
+ use crate::filesystem::{
886
+ FileOperations, FileType, Filesystem, FilesystemError, InodeOperations, Kstat, Lookup,
887
+ Tmpfs,
888
+ };
889
+ use crate::mount::{Access, Mount};
890
+ use crate::vfs::kern_path::{kern_path, KernPath};
891
+ use crate::vfs::{self, File, Finish, Identity, Inode, OpenHow, Path};
892
+
893
+ /// A root of `dentries` standing for a directory, as `d_make_root` makes one.
894
+ fn make_dir_root(dentries: &Dentries) -> Arc<Dentry> {
895
+ let ops: Arc<dyn InodeOperations> = Arc::new(Tmpfs::new());
896
+ dentries.make_root(Inode::new(&Kstat::new(FileType::Dir, 0), ops))
897
+ }
898
+
899
+ /// The dentry of `name` in `parent`, a lookup having answered that nothing
900
+ /// stands there, so a walk finds it by its name.
901
+ fn negative(dentries: &Dentries, parent: &Arc<Dentry>, name: &str) -> Arc<Dentry> {
902
+ let made = dentries
903
+ .alloc_parallel(parent, name)
904
+ .expect("no other walk holds the name");
905
+ made.add(None);
906
+ made
907
+ }
908
+
909
+ /// A Filesystem answering each call by the path its dentry stands at,
910
+ /// as one written in Ruby does, so a removed directory it opened
911
+ /// reaches whatever stands at that path later and keeps no death of
912
+ /// its own.
913
+ struct ByPath {
914
+ root: KernPath,
915
+ }
916
+
917
+ /// The path `dentry` stands at beneath a `ByPath`'s root, as a path-based
918
+ /// Filesystem builds its own from `dentry_path_raw`.
919
+ fn at(dentry: &Dentry) -> String {
920
+ dentry.path_raw()[1..].to_owned()
921
+ }
922
+
923
+ /// A name it recorded nothing for is looked up again, as a Ruby Filesystem's
924
+ /// `revalidate` answers, so one it made is found where the make left it.
925
+ impl super::DentryOperations for ByPath {
926
+ fn revalidate(
927
+ &self,
928
+ _dir: &dyn InodeOperations,
929
+ dentry: &Dentry,
930
+ ) -> Result<bool, FilesystemError> {
931
+ Ok(dentry.inode().is_some())
932
+ }
933
+ }
934
+
935
+ impl crate::vfs::FileSystemType for ByPath {
936
+ fn fill_super(self: Arc<Self>, sb: &mut Filesystem) -> Result<(), FilesystemError> {
937
+ sb.set_default_d_op(self.clone());
938
+ sb.d_make_root(Inode::new(&Kstat::new(FileType::Dir, 0), self));
939
+ Ok(())
940
+ }
941
+ }
942
+
943
+ impl InodeOperations for ByPath {
944
+ fn getattr(&self, dentry: &Dentry) -> Result<Kstat, FilesystemError> {
945
+ self.root.vfs_lstat(&at(dentry))
946
+ }
947
+
948
+ fn lookup(&self, dentry: &Dentry) -> Result<Lookup, FilesystemError> {
949
+ match self.root.vfs_lstat(&at(dentry)) {
950
+ Ok(found) => Ok(Lookup::Found(Inode::new(
951
+ &found,
952
+ Arc::new(Self {
953
+ root: self.root.clone(),
954
+ }),
955
+ ))),
956
+ Err(FilesystemError::NotFound) => Ok(Lookup::Absent),
957
+ Err(err) => Err(err),
958
+ }
959
+ }
960
+
961
+ fn open(
962
+ &self,
963
+ dentry: &Dentry,
964
+ how: &OpenHow,
965
+ ) -> Result<Box<dyn FileOperations>, FilesystemError> {
966
+ if dentry.kind() == Ok(FileType::Dir) {
967
+ return Ok(Box::new(ByPathDir(self.root.clone())));
968
+ }
969
+ self.root.open(&at(dentry), how)
970
+ }
971
+
972
+ fn atomic_open(&self, dentry: &Dentry, how: &OpenHow) -> Result<Finish, FilesystemError> {
973
+ if dentry.in_lookup() {
974
+ let found = self.lookup(dentry)?;
975
+ if !how.create || !matches!(found, Lookup::Absent) {
976
+ return Ok(Finish::Lookup(found));
977
+ }
978
+ }
979
+ if !how.create {
980
+ return Ok(Finish::NoOpen { created: false });
981
+ }
982
+ let file = self.root.create(&at(dentry), how)?;
983
+ Ok(Finish::Open {
984
+ file,
985
+ created: true,
986
+ })
987
+ }
988
+
989
+ fn mkdir(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
990
+ self.root.vfs_mkdir(&at(dentry))
991
+ }
992
+
993
+ fn rmdir(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
994
+ self.root.vfs_rmdir(&at(dentry))
995
+ }
996
+
997
+ fn unlink(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
998
+ self.root.vfs_unlink(&at(dentry))
999
+ }
1000
+
1001
+ fn rename(
1002
+ &self,
1003
+ from: &Dentry,
1004
+ to_dir: &dyn InodeOperations,
1005
+ to: &Dentry,
1006
+ _flag: Option<crate::vfs::RenameFlag>,
1007
+ ) -> Result<(), FilesystemError> {
1008
+ (to_dir as &dyn Any)
1009
+ .downcast_ref::<Self>()
1010
+ .ok_or(FilesystemError::CrossesDevices)?;
1011
+ self.root.rename(&at(from), &self.root, &at(to))
1012
+ }
1013
+ }
1014
+
1015
+ /// A directory of a `ByPath`, open.
1016
+ struct ByPathDir(KernPath);
1017
+
1018
+ impl FileOperations for ByPathDir {
1019
+ fn iterate_shared(
1020
+ &mut self,
1021
+ dentry: &Dentry,
1022
+ ctx: &mut vfs::DirContext<'_>,
1023
+ ) -> Result<(), FilesystemError> {
1024
+ let entries = match at(dentry).as_str() {
1025
+ "" => self.0.iterate_dir(),
1026
+ at => self.0.open_dir(at)?.iterate_dir(),
1027
+ }?;
1028
+ crate::filesystem::Rdir::default().iterate(dentry, ctx, || Ok(entries))
1029
+ }
1030
+ }
1031
+
1032
+ fn by_path(tmpfs: &Arc<Tmpfs>) -> Arc<Filesystem> {
1033
+ Filesystem::new(Arc::new(ByPath {
1034
+ root: kern_path(tmpfs.clone()),
1035
+ }))
1036
+ .expect("the instance must stand")
1037
+ }
1038
+
1039
+ fn root_of(filesystem: &Arc<Filesystem>) -> Path {
1040
+ let mount = Mount::new("/data", filesystem.clone(), Access::ReadWrite)
1041
+ .expect("/data is a guest path");
1042
+ Path::root(mount.mnt())
1043
+ }
1044
+
1045
+ fn open_dir(start: &Path, path: &str) -> Path {
1046
+ let how = OpenHow {
1047
+ read: true,
1048
+ directory: true,
1049
+ ..OpenHow::default()
1050
+ };
1051
+ match vfs::kern_path::filp_open(&Arc::default(), start, path, &how) {
1052
+ Ok(dir) => dir.path().clone(),
1053
+ Err(_) => panic!("{path} must open as a directory"),
1054
+ }
1055
+ }
1056
+
1057
+ // Linux's `path_init` reads `rename_lock` with `__read_seqcount_begin`,
1058
+ // which spins past a write under way, so a walk begun during a move is
1059
+ // never answered EAGAIN for that move.
1060
+ // @behavior RT-265
1061
+ #[test]
1062
+ fn a_walk_begun_during_a_move_begins_once_it_lands() {
1063
+ use crate::locking::seqlock::SeqLock;
1064
+ use crate::sched::testing::MAX_WAIT;
1065
+ use std::sync::mpsc;
1066
+ use std::thread;
1067
+
1068
+ let lock = Arc::new(SeqLock::new());
1069
+ let (entered, entering) = mpsc::channel();
1070
+ let (release, releasing) = mpsc::channel::<()>();
1071
+ let writer = {
1072
+ let lock = lock.clone();
1073
+ thread::spawn(move || {
1074
+ lock.write(|| {
1075
+ entered.send(()).ok();
1076
+ releasing.recv_timeout(MAX_WAIT).ok();
1077
+ })
1078
+ })
1079
+ };
1080
+ entering
1081
+ .recv_timeout(MAX_WAIT)
1082
+ .expect("the write must begin");
1083
+ let reader = {
1084
+ let lock = lock.clone();
1085
+ thread::spawn(move || lock.read_seqbegin())
1086
+ };
1087
+ thread::sleep(std::time::Duration::from_millis(50));
1088
+ let waited = !reader.is_finished();
1089
+ release.send(()).ok();
1090
+ crate::sched::testing::join("the write", writer);
1091
+ let begun = crate::sched::testing::join("the read", reader);
1092
+ assert_eq!(
1093
+ (waited, begun, lock.read_seqretry(begun)),
1094
+ (true, 2, false),
1095
+ "SeqLock::read_seqbegin during a write must wait for it to end and answer where the \
1096
+ writes stand after it, which no later read finds changed"
1097
+ );
1098
+ }
1099
+
1100
+ // `d_revalidate` compares what the dentry recorded first, so a later
1101
+ // lookup replacing it would hide the change it is there to see.
1102
+ #[test]
1103
+ fn a_dentry_keeps_what_a_lookup_first_found_there() {
1104
+ let ops: Arc<dyn InodeOperations> = Arc::new(Tmpfs::new());
1105
+ let (first, second) = (
1106
+ Kstat::new(FileType::Dir, 0).with_identity(1, 2),
1107
+ Kstat::new(FileType::Dir, 0).with_identity(1, 3),
1108
+ );
1109
+ let dentries = Dentries::new(None);
1110
+ let dentry = negative(&dentries, &make_dir_root(&dentries), "dir");
1111
+ dentry.add(Some(Inode::new(&first, ops.clone())));
1112
+ dentry.add(Some(Inode::new(&second, ops)));
1113
+ assert_eq!(
1114
+ dentry.inode().and_then(|inode| inode.identity()),
1115
+ Identity::of(&first),
1116
+ "Dentry::add after a negative lookup and then a positive one must keep the first \
1117
+ inode recorded"
1118
+ );
1119
+ }
1120
+
1121
+ // The Filesystem answers by path, so only the dentry the removal marked
1122
+ // keeps the make from landing in the directory made in its place.
1123
+ // @behavior RT-159
1124
+ #[test]
1125
+ fn a_directory_removed_through_the_vfs_makes_nothing_more_whatever_its_filesystem() {
1126
+ let tmpfs: Arc<Tmpfs> = Arc::new([("dir/keep", "x")].into_iter().collect());
1127
+ let root = root_of(&by_path(&tmpfs));
1128
+ let dir = open_dir(&root, "dir");
1129
+ vfs::filename_unlinkat(&Arc::default(), &root, "dir/keep").expect("the file must go");
1130
+ vfs::filename_rmdir(&Arc::default(), &root, "dir").expect("the directory must go");
1131
+ vfs::filename_mkdirat(&Arc::default(), &root, "dir")
1132
+ .expect("another directory must be made");
1133
+ vfs::filename_mkdirat(&Arc::default(), &root, "dir/other")
1134
+ .expect("an entry must be made in it");
1135
+ assert_eq!(
1136
+ (
1137
+ vfs::filename_mkdirat(&Arc::default(), &dir, "made"),
1138
+ File::dentry_open(
1139
+ dir.clone(),
1140
+ &OpenHow {
1141
+ read: true,
1142
+ ..OpenHow::default()
1143
+ },
1144
+ &Arc::default()
1145
+ )
1146
+ .expect("the directory must open")
1147
+ .iterate_dir(&mut vfs::DirContext::new(0, &mut |_| true)),
1148
+ kern_path(tmpfs.clone()).vfs_lstat("dir/made").err()
1149
+ ),
1150
+ (
1151
+ Err(FilesystemError::NotFound),
1152
+ Err(FilesystemError::NotFound),
1153
+ Some(FilesystemError::NotFound)
1154
+ ),
1155
+ "vfs::filename_mkdirat and File::iterate_dir through a directory vfs::filename_rmdir removed, over \
1156
+ a Filesystem answering by path, must each answer NotFound, as Linux asks \
1157
+ IS_DEADDIR, and make nothing in the directory made in its place"
1158
+ );
1159
+ }
1160
+
1161
+ // @behavior RT-160
1162
+ #[test]
1163
+ fn a_directory_removed_beneath_one_mount_is_removed_beneath_another_of_its_instance() {
1164
+ let tmpfs: Arc<Tmpfs> = Arc::new([("dir/keep", "x")].into_iter().collect());
1165
+ let instance = by_path(&tmpfs);
1166
+ let (first, second) = (root_of(&instance), root_of(&instance));
1167
+ let dir = open_dir(&first, "dir");
1168
+ vfs::filename_unlinkat(&Arc::default(), &second, "dir/keep").expect("the file must go");
1169
+ vfs::filename_rmdir(&Arc::default(), &second, "dir").expect("the directory must go");
1170
+ vfs::filename_mkdirat(&Arc::default(), &second, "dir")
1171
+ .expect("another directory must be made");
1172
+ assert_eq!(
1173
+ vfs::filename_mkdirat(&Arc::default(), &dir, "made"),
1174
+ Err(FilesystemError::NotFound),
1175
+ "vfs::filename_mkdirat through a directory opened beneath one Mount, once vfs::filename_rmdir \
1176
+ removed it beneath another Mount of the same Filesystem, must answer NotFound"
1177
+ );
1178
+ }
1179
+
1180
+ // Built directly, as a path cannot reach this deep. A depth a recursive
1181
+ // release cannot survive on this stack is what tells it from a loop.
1182
+ // @behavior RT-199
1183
+ #[test]
1184
+ fn a_dentry_goes_however_deep_it_stands() {
1185
+ let dentries = Dentries::new(None);
1186
+ let mut leaf = make_dir_root(&dentries);
1187
+ for _ in 0..200_000 {
1188
+ leaf = dentries
1189
+ .alloc_parallel(&leaf, "a")
1190
+ .expect("no other walk holds the name");
1191
+ }
1192
+ let released = std::thread::Builder::new()
1193
+ .stack_size(64 * 1024)
1194
+ .spawn(move || drop(leaf))
1195
+ .expect("the thread must start")
1196
+ .join();
1197
+ assert!(
1198
+ released.is_ok(),
1199
+ "a dentry 200000 directories deep, dropped on a 64 KiB stack, must go without \
1200
+ overflowing it"
1201
+ );
1202
+ }
1203
+
1204
+ // Only a name kept beneath the invalidated directory is let go of: a name
1205
+ // beside it stays for the next walk.
1206
+ #[test]
1207
+ fn a_directory_let_go_of_takes_the_names_kept_beneath_it() {
1208
+ let dentries = Dentries::new(None);
1209
+ let root = make_dir_root(&dentries);
1210
+ let ops: Arc<dyn InodeOperations> = Arc::new(Tmpfs::new());
1211
+ let dir = dentries
1212
+ .alloc_parallel(&root, "dir")
1213
+ .expect("no other walk holds the name");
1214
+ dir.add(Some(Inode::new(&Kstat::new(FileType::Dir, 0), ops)));
1215
+ let kept = |dentry: Arc<Dentry>| {
1216
+ dentries.retain(&dentry);
1217
+ Arc::downgrade(&dentry)
1218
+ };
1219
+ let beneath = kept(negative(
1220
+ &dentries,
1221
+ &negative(&dentries, &dir, "sub"),
1222
+ "leaf",
1223
+ ));
1224
+ let beside = kept(negative(&dentries, &root, "other"));
1225
+ dentries.invalidate(&dir);
1226
+ assert_eq!(
1227
+ (beneath.upgrade().is_some(), beside.upgrade().is_some()),
1228
+ (false, true),
1229
+ "Dentries::invalidate of a directory must let go of the names kept beneath it and \
1230
+ keep the others, as d_invalidate calls shrink_dcache_parent"
1231
+ );
1232
+ }
1233
+
1234
+ // A walk let go of the dentry while another still held it, so only
1235
+ // refusing the unhashed one keeps that walk's retain from keeping a dentry
1236
+ // no walk can find, whose Filesystem state may be stale.
1237
+ #[test]
1238
+ fn a_dentry_let_go_of_is_not_kept_for_the_next_walk() {
1239
+ let dentries = Dentries::new(None);
1240
+ let dentry = negative(&dentries, &make_dir_root(&dentries), "gone");
1241
+ dentries.unhash(&dentry);
1242
+ dentries.retain(&dentry);
1243
+ let held = Arc::downgrade(&dentry);
1244
+ drop(dentry);
1245
+ assert!(
1246
+ held.upgrade().is_none(),
1247
+ "Dentries::retain of a dentry Dentries::unhash let go of must not keep it, as \
1248
+ retain_dentry refuses an unhashed one"
1249
+ );
1250
+ }
1251
+
1252
+ #[test]
1253
+ fn the_root_stands_at_a_lone_slash() {
1254
+ let dentries = super::Dentries::new(None);
1255
+ assert_eq!(
1256
+ make_dir_root(&dentries).path_raw(),
1257
+ "/",
1258
+ "Dentry::path_raw of the root must answer a lone /, as dentry_path_raw answers"
1259
+ );
1260
+ }
1261
+
1262
+ #[test]
1263
+ fn an_exchange_swaps_two_dentries() {
1264
+ let dentries = super::Dentries::new(None);
1265
+ let root = make_dir_root(&dentries);
1266
+ let a = negative(&dentries, &root, "a");
1267
+ let b = negative(&dentries, &negative(&dentries, &root, "d"), "b");
1268
+ dentries.exchange(&a, &b);
1269
+ assert_eq!(
1270
+ (
1271
+ a.path_raw(),
1272
+ b.path_raw(),
1273
+ dentries
1274
+ .lookup(&root, "a")
1275
+ .is_some_and(|found| Arc::ptr_eq(&found, &b))
1276
+ ),
1277
+ ("/d/b".to_string(), "/a".to_string(), true),
1278
+ "Dentries::exchange must hand each dentry the other's name, and find each by its new \
1279
+ name"
1280
+ );
1281
+ }
1282
+
1283
+ /// Witnesses of a race, each run only by `rake crates:stress`: the race
1284
+ /// lands a few times in many tries, and only an optimized build makes
1285
+ /// that many in seconds.
1286
+ mod stress {
1287
+ use std::sync::atomic::{AtomicBool, Ordering};
1288
+ use std::sync::Arc;
1289
+ use std::thread;
1290
+
1291
+ use super::super::{Dentries, Dentry};
1292
+ use super::{make_dir_root, negative};
1293
+
1294
+ /// The dentry a rename onto `name` in `dir` replaces.
1295
+ fn target(dentries: &Dentries, dir: &Arc<Dentry>, name: &str) -> Arc<Dentry> {
1296
+ dentries
1297
+ .alloc_parallel(dir, name)
1298
+ .expect("no other walk holds the name")
1299
+ }
1300
+
1301
+ // `x` is named `z` only while `b` stands beneath `y`, so `z/b/leaf`
1302
+ // is a path `leaf` never stood at: only a read taking `b`'s parent
1303
+ // before the moves and `x`'s name after them answers it.
1304
+ // @behavior RT-172
1305
+ #[test]
1306
+ #[ignore = "a stress witness; run by rake crates:stress"]
1307
+ fn a_path_asked_while_directories_above_it_move_names_one_that_stood() {
1308
+ let dentries = Arc::new(Dentries::new(None));
1309
+ let root = make_dir_root(&dentries);
1310
+ let (x, y) = (
1311
+ negative(&dentries, &root, "x"),
1312
+ negative(&dentries, &root, "y"),
1313
+ );
1314
+ let b = negative(&dentries, &x, "b");
1315
+ let leaf = negative(&dentries, &b, "leaf");
1316
+ let stop = Arc::new(AtomicBool::new(false));
1317
+ let moving = {
1318
+ let (dentries, stop) = (dentries.clone(), stop.clone());
1319
+ thread::spawn(move || {
1320
+ while !stop.load(Ordering::SeqCst) {
1321
+ dentries.move_dentry(&b, &target(&dentries, &y, "b"));
1322
+ dentries.move_dentry(&x, &target(&dentries, &root, "z"));
1323
+ dentries.move_dentry(&x, &target(&dentries, &root, "x"));
1324
+ dentries.move_dentry(&b, &target(&dentries, &x, "b"));
1325
+ }
1326
+ })
1327
+ };
1328
+ let torn = (0..5_000_000)
1329
+ .filter(|_| leaf.path_raw().starts_with("/z/"))
1330
+ .count();
1331
+ stop.store(true, Ordering::SeqCst);
1332
+ moving.join().expect("the moves must not panic");
1333
+ assert_eq!(
1334
+ torn, 0,
1335
+ "Dentry::path_raw while Dentries::move_dentry moves the directories above it must \
1336
+ answer only a path the dentry stood at"
1337
+ );
1338
+ }
1339
+
1340
+ // `retain` holds the name's lock before the LRU's, so only deciding what
1341
+ // stands beneath the directory before taking the LRU's keeps the two
1342
+ // from each holding what the other waits on.
1343
+ // @behavior RT-331
1344
+ #[test]
1345
+ #[ignore = "a stress witness; run by rake crates:stress"]
1346
+ fn a_name_kept_while_the_names_beneath_its_directory_go_waits_on_neither() {
1347
+ let dentries = Arc::new(Dentries::new(None));
1348
+ let dir = negative(&dentries, &make_dir_root(&dentries), "dir");
1349
+ let leaf = negative(&dentries, &dir, "leaf");
1350
+ let stop = Arc::new(AtomicBool::new(false));
1351
+ let keeping = {
1352
+ let (dentries, stop, leaf) = (dentries.clone(), stop.clone(), leaf.clone());
1353
+ thread::spawn(move || {
1354
+ while !stop.load(Ordering::SeqCst) {
1355
+ dentries.retain(&leaf);
1356
+ }
1357
+ })
1358
+ };
1359
+ let shrinking = {
1360
+ let (dentries, stop) = (dentries.clone(), stop.clone());
1361
+ thread::spawn(move || {
1362
+ while !stop.load(Ordering::SeqCst) {
1363
+ dentries.shrink_dcache_parent(&dir);
1364
+ }
1365
+ })
1366
+ };
1367
+ thread::sleep(std::time::Duration::from_secs(2));
1368
+ stop.store(true, Ordering::SeqCst);
1369
+ crate::sched::testing::join(
1370
+ "Dentries::retain racing Dentries::shrink_dcache_parent",
1371
+ keeping,
1372
+ );
1373
+ crate::sched::testing::join(
1374
+ "Dentries::shrink_dcache_parent racing Dentries::retain",
1375
+ shrinking,
1376
+ );
1377
+ }
1378
+
1379
+ // Each move keeps the dentry in the table, so only a read of its parent
1380
+ // before the move and its name after it finds nothing at that key.
1381
+ // @behavior RT-228
1382
+ #[test]
1383
+ #[ignore = "a stress witness; run by rake crates:stress"]
1384
+ fn a_directory_moving_through_the_vfs_is_never_taken_for_unlinked() {
1385
+ let dentries = Arc::new(Dentries::new(None));
1386
+ let root = make_dir_root(&dentries);
1387
+ let (x, y) = (
1388
+ negative(&dentries, &root, "x"),
1389
+ negative(&dentries, &root, "y"),
1390
+ );
1391
+ let moved = negative(&dentries, &x, "a");
1392
+ let stop = Arc::new(AtomicBool::new(false));
1393
+ let moving = {
1394
+ let (dentries, stop, moved) = (dentries.clone(), stop.clone(), moved.clone());
1395
+ thread::spawn(move || {
1396
+ while !stop.load(Ordering::SeqCst) {
1397
+ dentries.move_dentry(&moved, &target(&dentries, &y, "b"));
1398
+ dentries.move_dentry(&moved, &target(&dentries, &x, "a"));
1399
+ }
1400
+ })
1401
+ };
1402
+ let unlinked = (0..5_000_000).filter(|_| dentries.unlinked(&moved)).count();
1403
+ stop.store(true, Ordering::SeqCst);
1404
+ moving.join().expect("the moves must not panic");
1405
+ assert_eq!(
1406
+ unlinked, 0,
1407
+ "Dentries::unlinked while Dentries::move_dentry moves the dentry must answer that \
1408
+ it still stands"
1409
+ );
1410
+ }
1411
+ }
1412
+ }