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,276 @@
1
+ //! Holds calls racing on one shared instance to the Linux they align with. No
2
+ //! interleaving repeats, so answers are compared as sets: what kobako answers a
3
+ //! kind of call, Linux must have answered it too over the same sequences. What
4
+ //! stands once every call returned is held to rules any interleaving keeps.
5
+
6
+ use std::collections::BTreeSet;
7
+ use std::sync::atomic::{AtomicU64, Ordering};
8
+ use std::sync::Barrier;
9
+ use std::thread;
10
+
11
+ use crate::calls::{Call, Rng, SLOTS};
12
+ use crate::kobako::Kobako;
13
+ use crate::linux::Linux;
14
+ use crate::Snapshot;
15
+
16
+ /// How many threads race, each with descriptors of its own.
17
+ pub const THREADS: usize = 4;
18
+
19
+ /// What a sequence races over: plain names, so a path names one entry whichever
20
+ /// descriptor or link would otherwise lead there.
21
+ const PATHS: [&str; 12] = [
22
+ "a", "b", "c", "d", "d/f", "d/g", "d/e", "d/e/h", "m", "m/s", "m/s/t", "m/x",
23
+ ];
24
+
25
+ /// One side of the comparison, one instance per racing thread.
26
+ pub trait Side: Send {
27
+ fn run(&mut self, call: &Call) -> String;
28
+ fn snapshot(&self) -> Snapshot;
29
+ }
30
+
31
+ impl Side for Kobako {
32
+ fn run(&mut self, call: &Call) -> String {
33
+ Kobako::run(self, call)
34
+ }
35
+ fn snapshot(&self) -> Snapshot {
36
+ Kobako::snapshot(self)
37
+ }
38
+ }
39
+
40
+ impl Side for Linux {
41
+ fn run(&mut self, call: &Call) -> String {
42
+ Linux::run(self, call)
43
+ }
44
+ fn snapshot(&self) -> Snapshot {
45
+ Linux::snapshot(self)
46
+ }
47
+ }
48
+
49
+ /// One call as it happened, between two stamps of one clock all threads share.
50
+ struct Event {
51
+ call: Call,
52
+ answer: String,
53
+ began: u64,
54
+ ended: u64,
55
+ }
56
+
57
+ impl Event {
58
+ fn succeeded(&self) -> bool {
59
+ self.answer.starts_with("ok")
60
+ }
61
+
62
+ /// The name this call removed.
63
+ fn removed(&self) -> Option<&str> {
64
+ match &self.call {
65
+ Call::Unlink(_, path) | Call::Rmdir(_, path) if self.succeeded() => Some(path),
66
+ _ => None,
67
+ }
68
+ }
69
+
70
+ /// Whether this call may have put an entry at or beneath `name`.
71
+ fn may_make(&self, name: &str) -> bool {
72
+ let related = |path: &str| {
73
+ path == name
74
+ || path.starts_with(&format!("{name}/"))
75
+ || name.starts_with(&format!("{path}/"))
76
+ };
77
+ if !self.succeeded() {
78
+ return false;
79
+ }
80
+ match &self.call {
81
+ Call::Open(open) => open.create && related(&open.path),
82
+ Call::Mkdir(_, path) => related(path),
83
+ Call::Rename(_, from, to) => related(from) || related(to),
84
+ _ => false,
85
+ }
86
+ }
87
+ }
88
+
89
+ /// A call and its answer by what it says of the call, not its data: a read is
90
+ /// only `ok`, a stat only the kind it found, and what an open asked is kept where
91
+ /// it decides the answer.
92
+ fn class(event: &Event) -> String {
93
+ let answer = first_word(&event.answer);
94
+ let asked = match &event.call {
95
+ Call::Open(open) => format!(" create={} exclusive={}", open.create, open.exclusive),
96
+ _ => String::new(),
97
+ };
98
+ format!("{}{asked} {answer}", event.call.kind())
99
+ }
100
+
101
+ /// One thread's sequence.
102
+ fn sequence(seed: u64, thread: usize, longest: u64) -> Vec<Call> {
103
+ let paths = PATHS.map(str::to_string);
104
+ let mut rng = Rng::new(seed.wrapping_mul(31).wrapping_add(thread as u64 + 1));
105
+ let length = 1 + rng.random_below(longest);
106
+ (0..length)
107
+ .map(|_| {
108
+ let mut call = rng.call(&paths);
109
+ match &mut call {
110
+ Call::Open(open) => open.base = None,
111
+ Call::Stat { base, .. }
112
+ | Call::Mkdir(base, _)
113
+ | Call::Rmdir(base, _)
114
+ | Call::Unlink(base, _)
115
+ | Call::Rename(base, ..) => *base = None,
116
+ _ => {}
117
+ }
118
+ call
119
+ })
120
+ .collect()
121
+ }
122
+
123
+ /// The kind each descriptor reported when it opened, so a thread can tell one that
124
+ /// came to report another.
125
+ #[derive(Default)]
126
+ struct HeldKinds {
127
+ kinds: [Option<String>; SLOTS],
128
+ changed: BTreeSet<String>,
129
+ }
130
+
131
+ impl HeldKinds {
132
+ /// Keep what `call`, answered `answer`, says of the descriptors, asking `side`
133
+ /// what one just opened is.
134
+ fn note<S: Side>(&mut self, side: &mut S, call: &Call, answer: &str) {
135
+ match call {
136
+ Call::Open(open) if answer.starts_with("ok") => {
137
+ let kind = side.run(&Call::Fstat(open.slot));
138
+ self.kinds[open.slot] = Some(first_word(&kind).to_string());
139
+ }
140
+ Call::Close(slot) => self.kinds[*slot] = None,
141
+ Call::Fstat(slot) => {
142
+ if let Some(kind) = &self.kinds[*slot] {
143
+ if kind != first_word(answer) {
144
+ self.changed
145
+ .insert(format!("a {kind} descriptor came to report {answer}"));
146
+ }
147
+ }
148
+ }
149
+ _ => {}
150
+ }
151
+ }
152
+ }
153
+
154
+ fn first_word(answer: &str) -> &str {
155
+ answer.split([':', ' ']).next().unwrap_or("")
156
+ }
157
+
158
+ /// Race `sequences` over one side, one thread each, answering what each call did,
159
+ /// the thread's side to look at afterwards, and what a held descriptor changed to.
160
+ fn race<S: Side>(sides: Vec<S>, sequences: &[Vec<Call>]) -> (Vec<S>, Vec<Event>, BTreeSet<String>) {
161
+ let clock = AtomicU64::new(0);
162
+ let start = Barrier::new(sides.len());
163
+ let (mut left, mut events, mut changed) = (Vec::new(), Vec::new(), BTreeSet::new());
164
+ thread::scope(|scope| {
165
+ let threads: Vec<_> = sides
166
+ .into_iter()
167
+ .zip(sequences)
168
+ .map(|(mut side, calls)| {
169
+ let (clock, start) = (&clock, &start);
170
+ scope.spawn(move || {
171
+ start.wait();
172
+ let mut events = Vec::with_capacity(calls.len());
173
+ let mut held = HeldKinds::default();
174
+ for call in calls {
175
+ let began = clock.fetch_add(1, Ordering::SeqCst);
176
+ let answer = side.run(call);
177
+ let ended = clock.fetch_add(1, Ordering::SeqCst);
178
+ held.note(&mut side, call, &answer);
179
+ events.push(Event {
180
+ call: call.clone(),
181
+ answer,
182
+ began,
183
+ ended,
184
+ });
185
+ }
186
+ for slot in 0..SLOTS {
187
+ let call = Call::Fstat(slot);
188
+ let answer = side.run(&call);
189
+ held.note(&mut side, &call, &answer);
190
+ }
191
+ (side, events, held.changed)
192
+ })
193
+ })
194
+ .collect();
195
+ for thread in threads {
196
+ let (side, done, held) = thread.join().expect("a racing thread must not panic");
197
+ left.push(side);
198
+ events.extend(done);
199
+ changed.extend(held);
200
+ }
201
+ });
202
+ (left, events, changed)
203
+ }
204
+
205
+ /// What no interleaving allows of what stands once every call returned: an entry
206
+ /// its own stat refuses, a descriptor that no longer answers, and a name removed
207
+ /// that stands with nothing having made it since the removal began.
208
+ fn broken_rules<S: Side>(sides: &mut [S], events: &[Event]) -> BTreeSet<String> {
209
+ let mut broken = BTreeSet::new();
210
+ for side in sides.iter_mut() {
211
+ for slot in 0..SLOTS {
212
+ let answer = side.run(&Call::Fstat(slot));
213
+ if answer.starts_with('E') && answer != "EBADF" {
214
+ broken.insert(format!("a held descriptor answers {answer} at rest"));
215
+ }
216
+ }
217
+ }
218
+ let tree = sides[0].snapshot();
219
+ for (path, seen) in &tree {
220
+ if seen.starts_with('E') || seen.contains("unreadable") {
221
+ broken.insert(format!("{path} is listed but answers {seen}"));
222
+ }
223
+ }
224
+ for removal in events {
225
+ let Some(name) = removal.removed() else {
226
+ continue;
227
+ };
228
+ let remade = events
229
+ .iter()
230
+ .any(|event| event.ended >= removal.began && event.may_make(name));
231
+ let stands = tree
232
+ .keys()
233
+ .find(|path| *path == name || path.starts_with(&format!("{name}/")));
234
+ if let (false, Some(path)) = (remade, stands) {
235
+ broken.insert(format!(
236
+ "{path} stands after its removal, with nothing making it"
237
+ ));
238
+ }
239
+ }
240
+ broken
241
+ }
242
+
243
+ /// What one setup found over its rounds.
244
+ #[derive(Default)]
245
+ pub struct Found {
246
+ pub kobako: BTreeSet<String>,
247
+ pub linux: BTreeSet<String>,
248
+ pub broken_by_kobako: BTreeSet<String>,
249
+ pub broken_by_linux: BTreeSet<String>,
250
+ }
251
+
252
+ /// Race one round's sequences through a side of each.
253
+ pub fn round(
254
+ seed: u64,
255
+ longest: u64,
256
+ kobako: Vec<Kobako>,
257
+ linux: Vec<Linux>,
258
+ found: &mut Found,
259
+ ) -> (Vec<Kobako>, Vec<Linux>) {
260
+ let sequences: Vec<Vec<Call>> = (0..kobako.len())
261
+ .map(|thread| sequence(seed, thread, longest))
262
+ .collect();
263
+ let (mut kobako, ours, changed) = race(kobako, &sequences);
264
+ found.broken_by_kobako.extend(changed);
265
+ found
266
+ .broken_by_kobako
267
+ .extend(broken_rules(&mut kobako, &ours));
268
+ found.kobako.extend(ours.iter().map(class));
269
+ let (mut linux, theirs, changed) = race(linux, &sequences);
270
+ found.broken_by_linux.extend(changed);
271
+ found
272
+ .broken_by_linux
273
+ .extend(broken_rules(&mut linux, &theirs));
274
+ found.linux.extend(theirs.iter().map(class));
275
+ (kobako, linux)
276
+ }
@@ -0,0 +1,323 @@
1
+ //! A sequence's calls through kobako's VFS.
2
+
3
+ use std::collections::BTreeMap;
4
+ use std::fmt::Debug;
5
+ use std::sync::Arc;
6
+ use std::time::{Duration, SystemTime, UNIX_EPOCH};
7
+
8
+ use kobako_runtime::filesystem::{Filesystem, FilesystemError, Iattr, SetTime};
9
+ use kobako_runtime::mount::{Access, Mount, Policy};
10
+ use kobako_runtime::security::{prepare_creds_within, restrict_self};
11
+ use kobako_runtime::vfs::{
12
+ self, Cred, DirContext, DirEmit, File, FileType, OpenFlags, OpenHow, VfsMount, Whence,
13
+ };
14
+
15
+ use crate::calls::{payload, Call, SLOTS, UTIME_SECONDS};
16
+ use crate::{describe, Snapshot, Times};
17
+
18
+ fn errno(error: &FilesystemError) -> String {
19
+ error
20
+ .posix_name()
21
+ .map_or_else(|| format!("EIO({error:?})"), str::to_string)
22
+ }
23
+
24
+ fn answer<T>(result: Result<T, FilesystemError>, ok: impl FnOnce(T) -> String) -> String {
25
+ result.map_or_else(|error| errno(&error), ok)
26
+ }
27
+
28
+ fn ok_or_errno<T: Debug>(result: Result<T, FilesystemError>) -> String {
29
+ answer(result, |_| "ok".to_string())
30
+ }
31
+
32
+ fn kind_name(kind: FileType) -> &'static str {
33
+ match kind {
34
+ FileType::RegFile => "reg",
35
+ FileType::Dir => "dir",
36
+ FileType::Symlink => "lnk",
37
+ _ => "oth",
38
+ }
39
+ }
40
+
41
+ fn directory_flags() -> OpenFlags {
42
+ let mut how = OpenHow::default();
43
+ how.read = true;
44
+ how.directory = true;
45
+ vfs::build_open_flags(&how).expect("a directory read means an open")
46
+ }
47
+
48
+ /// The names `file` lists from its start, kinds included, `.` and `..` among
49
+ /// them, as the Linux side seeks a directory back before `getdents64`.
50
+ fn entries(file: &mut File) -> Result<Vec<(String, FileType)>, FilesystemError> {
51
+ if file.is_directory() {
52
+ file.llseek(0, Whence::Set)?;
53
+ }
54
+ let mut names = Vec::new();
55
+ let mut actor = |emit: &DirEmit<'_>| {
56
+ names.push((emit.name.to_string(), emit.file_type));
57
+ true
58
+ };
59
+ file.iterate_dir(&mut DirContext::new(0, &mut actor))?;
60
+ Ok(names)
61
+ }
62
+
63
+ pub struct Kobako {
64
+ cred: Arc<Cred>,
65
+ mnt: Arc<VfsMount>,
66
+ root: File,
67
+ slots: Vec<Option<File>>,
68
+ merged: bool,
69
+ }
70
+
71
+ impl Kobako {
72
+ /// The root of `filesystem` mounted with `access`. `merged` leaves out what
73
+ /// an Overlay and overlayfs each count of a directory their own way.
74
+ pub fn new(filesystem: Arc<Filesystem>, access: Access, merged: bool) -> Self {
75
+ let mount = Mount::new("/baseline", filesystem, access).expect("/baseline is a guest path");
76
+ let cred = restrict_self(&prepare_creds_within(&[], None), [&mount]);
77
+ let root = File::root(mount.mnt(), &cred)
78
+ .unwrap_or_else(|error| panic!("the root must open: {error:?}"));
79
+ Self {
80
+ cred,
81
+ mnt: mount.mnt().clone(),
82
+ root,
83
+ slots: std::iter::repeat_with(|| None).take(SLOTS).collect(),
84
+ merged,
85
+ }
86
+ }
87
+
88
+ /// The root of `filesystem` mounted to change, beneath `policy`.
89
+ pub fn with_policy(filesystem: Arc<Filesystem>, policy: Arc<dyn Policy>) -> Self {
90
+ let mount = Mount::new("/baseline", filesystem, Access::ReadWrite)
91
+ .expect("/baseline is a guest path")
92
+ .with_policy(policy);
93
+ let cred = restrict_self(&prepare_creds_within(&[], None), [&mount]);
94
+ let root = File::root(mount.mnt(), &cred)
95
+ .unwrap_or_else(|error| panic!("the root must open: {error:?}"));
96
+ Self {
97
+ cred,
98
+ mnt: mount.mnt().clone(),
99
+ root,
100
+ slots: std::iter::repeat_with(|| None).take(SLOTS).collect(),
101
+ merged: false,
102
+ }
103
+ }
104
+
105
+ /// Another caller of the same mount, with descriptors of its own.
106
+ pub fn another(&self) -> Self {
107
+ let root = File::root(&self.mnt, &self.cred)
108
+ .unwrap_or_else(|error| panic!("the root must open: {error:?}"));
109
+ Self {
110
+ cred: self.cred.clone(),
111
+ mnt: self.mnt.clone(),
112
+ root,
113
+ slots: std::iter::repeat_with(|| None).take(SLOTS).collect(),
114
+ merged: self.merged,
115
+ }
116
+ }
117
+
118
+ fn start(&self, base: Option<usize>) -> Result<&vfs::Path, String> {
119
+ match base {
120
+ None => Ok(self.root.path()),
121
+ Some(slot) => match &self.slots[slot] {
122
+ None => Err("EBADF".to_string()),
123
+ Some(file) => Ok(file.path()),
124
+ },
125
+ }
126
+ }
127
+
128
+ fn stat(&self, stat: vfs::Kstat) -> String {
129
+ describe(
130
+ kind_name(stat.file_type),
131
+ stat.size,
132
+ stat.nlink,
133
+ self.merged,
134
+ )
135
+ }
136
+
137
+ pub fn run(&mut self, call: &Call) -> String {
138
+ macro_rules! start {
139
+ ($base:expr) => {
140
+ match self.start($base) {
141
+ Ok(path) => path,
142
+ Err(answer) => return answer,
143
+ }
144
+ };
145
+ }
146
+ macro_rules! file {
147
+ ($slot:expr) => {
148
+ match self.slots[$slot].as_mut() {
149
+ Some(file) => file,
150
+ None => return "EBADF".to_string(),
151
+ }
152
+ };
153
+ }
154
+ match call {
155
+ Call::Open(open) => {
156
+ let mut how = OpenHow::default();
157
+ how.read = open.read || !open.write;
158
+ how.write = open.write;
159
+ how.create = open.create;
160
+ how.exclusive = open.exclusive;
161
+ how.truncate = open.truncate;
162
+ how.append = open.append;
163
+ how.directory = open.directory;
164
+ how.follow = !open.nofollow;
165
+ let start = start!(open.base);
166
+ let opened = vfs::build_open_flags(&how)
167
+ .and_then(|op| vfs::do_file_open(&self.cred, start, &open.path, &op));
168
+ answer(opened, |file| {
169
+ self.slots[open.slot] = Some(file);
170
+ "ok".to_string()
171
+ })
172
+ }
173
+ Call::Close(slot) => {
174
+ file!(*slot);
175
+ let file = self.slots[*slot].take().expect("the slot holds a file");
176
+ ok_or_errno(file.close())
177
+ }
178
+ Call::Read(slot, len) => {
179
+ let mut buf = vec![0; *len];
180
+ answer(file!(*slot).read(&mut buf), |read| bytes(&buf[..read]))
181
+ }
182
+ Call::Write(slot, len) => answer(file!(*slot).write(payload(*len)), bytes_written),
183
+ Call::Pread(slot, len, pos) => {
184
+ let mut buf = vec![0; *len];
185
+ answer(file!(*slot).pread(&mut buf, *pos), |read| {
186
+ bytes(&buf[..read])
187
+ })
188
+ }
189
+ Call::Pwrite(slot, len, pos) => {
190
+ answer(file!(*slot).pwrite(payload(*len), *pos), bytes_written)
191
+ }
192
+ Call::Seek(slot, offset, whence) => {
193
+ let file = file!(*slot);
194
+ if file.is_directory() {
195
+ return "directory".to_string();
196
+ }
197
+ let whence = [Whence::Set, Whence::Cur, Whence::End][*whence as usize];
198
+ answer(file.llseek(*offset, whence), |pos| format!("ok:{pos}"))
199
+ }
200
+ Call::Truncate(slot, len) => ok_or_errno(file!(*slot).truncate(*len)),
201
+ Call::Fallocate(slot, offset, len) => {
202
+ ok_or_errno(file!(*slot).fallocate(*offset, *len))
203
+ }
204
+ Call::Utimes { base, path, follow } => {
205
+ let time = UNIX_EPOCH + Duration::from_secs(UTIME_SECONDS as u64);
206
+ let times = Iattr::times(None, Some(SetTime::At(time)));
207
+ ok_or_errno(vfs::do_utimes_path(
208
+ &self.cred,
209
+ start!(*base),
210
+ path,
211
+ *follow,
212
+ &times,
213
+ ))
214
+ }
215
+ Call::Fstat(slot) => {
216
+ let got = file!(*slot).getattr();
217
+ answer(got, |metadata| self.stat(metadata))
218
+ }
219
+ Call::Fsync(slot) => ok_or_errno(file!(*slot).fsync(false)),
220
+ Call::Readdir(slot) => answer(entries(file!(*slot)), |names| {
221
+ let mut names: Vec<_> = names
222
+ .into_iter()
223
+ .map(|(name, kind)| format!("{name}:{}", kind_name(kind)))
224
+ .collect();
225
+ names.sort();
226
+ format!("ok:{names:?}")
227
+ }),
228
+ Call::Stat { base, path, follow } => {
229
+ let got = vfs::vfs_fstatat(&self.cred, start!(*base), path, *follow);
230
+ answer(got, |metadata| self.stat(metadata))
231
+ }
232
+ Call::Mkdir(base, path) => {
233
+ ok_or_errno(vfs::filename_mkdirat(&self.cred, start!(*base), path))
234
+ }
235
+ Call::Rmdir(base, path) => {
236
+ ok_or_errno(vfs::filename_rmdir(&self.cred, start!(*base), path))
237
+ }
238
+ Call::Unlink(base, path) => {
239
+ ok_or_errno(vfs::filename_unlinkat(&self.cred, start!(*base), path))
240
+ }
241
+ Call::Rename(base, from, to) => ok_or_errno(vfs::filename_renameat2(
242
+ &self.cred,
243
+ start!(*base),
244
+ from,
245
+ self.root.path(),
246
+ to,
247
+ )),
248
+ }
249
+ }
250
+
251
+ /// Visit every entry beneath the root, directories before what they hold.
252
+ fn walk(&self, dir: &vfs::Path, prefix: &str, visit: &mut dyn FnMut(&vfs::Path, &str, &str)) {
253
+ let Ok(mut opened) = vfs::do_file_open(&self.cred, dir, ".", &directory_flags()) else {
254
+ return;
255
+ };
256
+ for (name, kind) in entries(&mut opened).unwrap_or_default() {
257
+ if name == "." || name == ".." {
258
+ continue;
259
+ }
260
+ let full = format!("{prefix}{name}");
261
+ visit(dir, &name, &full);
262
+ if kind == FileType::Dir {
263
+ if let Ok(sub) = vfs::do_file_open(&self.cred, dir, &name, &directory_flags()) {
264
+ self.walk(sub.path(), &format!("{full}/"), visit);
265
+ }
266
+ }
267
+ }
268
+ }
269
+
270
+ /// Every entry beneath the root, with a file's content.
271
+ pub fn snapshot(&self) -> Snapshot {
272
+ let mut out = BTreeMap::new();
273
+ self.walk(self.root.path(), "", &mut |dir, name, full| {
274
+ let seen = match vfs::vfs_fstatat(&self.cred, dir, name, false) {
275
+ Err(error) => errno(&error),
276
+ Ok(metadata) => {
277
+ let kind = metadata.file_type;
278
+ let mut seen = self.stat(metadata);
279
+ if kind == FileType::RegFile {
280
+ seen += &self.content(dir, name);
281
+ }
282
+ seen
283
+ }
284
+ };
285
+ out.insert(full.to_string(), seen);
286
+ });
287
+ out
288
+ }
289
+
290
+ fn content(&self, dir: &vfs::Path, name: &str) -> String {
291
+ let mut how = OpenHow::default();
292
+ how.read = true;
293
+ let op = vfs::build_open_flags(&how).expect("a read means an open");
294
+ let Ok(mut file) = vfs::do_file_open(&self.cred, dir, name, &op) else {
295
+ return " unreadable".to_string();
296
+ };
297
+ let mut buf = [0; 64];
298
+ let read = file.read(&mut buf).unwrap_or(0);
299
+ format!(" {:?}", String::from_utf8_lossy(&buf[..read]))
300
+ }
301
+
302
+ /// When each entry beneath the root was last modified and changed.
303
+ pub fn times(&self) -> Times<Option<SystemTime>> {
304
+ let mut out = BTreeMap::new();
305
+ if let Ok(stat) = vfs::vfs_fstatat(&self.cred, self.root.path(), ".", false) {
306
+ out.insert(".".to_string(), (stat.mtime, stat.ctime));
307
+ }
308
+ self.walk(self.root.path(), "", &mut |dir, name, full| {
309
+ if let Ok(stat) = vfs::vfs_fstatat(&self.cred, dir, name, false) {
310
+ out.insert(full.to_string(), (stat.mtime, stat.ctime));
311
+ }
312
+ });
313
+ out
314
+ }
315
+ }
316
+
317
+ fn bytes(read: &[u8]) -> String {
318
+ format!("ok:{:?}", String::from_utf8_lossy(read))
319
+ }
320
+
321
+ fn bytes_written(count: usize) -> String {
322
+ format!("ok:{count}")
323
+ }
@@ -0,0 +1,16 @@
1
+ # The differences from Linux known so far, one per line: the setup, then the
2
+ # class a failure of tests/linux_baseline names, with what causes it after a `#`.
3
+ # A line records a difference and settles nothing about it.
4
+ overlay Rename from=g kobako=EXDEV linux=ok # a lower Layer's link is not copied up
5
+ overlay Rename from=l kobako=EXDEV linux=ok # a lower Layer's link is not copied up
6
+ overlay Rename from=n kobako=EXDEV linux=ok # a lower Layer's link is not copied up
7
+ bind Utimes kobako=ENOTSUP linux=ok # a link keeps its own times
8
+ overlay Utimes kobako=ENOTSUP linux=ok # a link keeps its own times
9
+ policy Rename from=d kobako=EXDEV linux=ENOTDIR # a Policy answers for one path, not a tree
10
+ policy Rename from=d kobako=EXDEV linux=ok # a Policy answers for one path, not a tree
11
+ policy Rename from=n kobako=EXDEV linux=ENOTDIR # a Policy answers for one path, not a tree
12
+ policy Rename from=n kobako=EXDEV linux=ok # a Policy answers for one path, not a tree
13
+ policy Utimes kobako=EACCES linux=ok # a Policy judges a time as a change, where Landlock leaves it
14
+ another-user Open kobako=EACCES linux=ok # a directory only readable or only searched: spelling a name and reading through it take more
15
+ another-user Rename from=nor kobako=EACCES linux=ok # spelling a name in a directory only searched takes reading it
16
+ another-user Stat kobako=EACCES linux=reg # spelling a name in a directory only searched takes reading it