kobako 0.27.0 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +1 -1
  3. data/CHANGELOG.md +241 -0
  4. data/Cargo.lock +132 -126
  5. data/README.md +240 -147
  6. data/ROADMAP.md +28 -22
  7. data/SECURITY.md +2 -2
  8. data/crates/kobako-runtime/CHANGELOG.md +117 -0
  9. data/crates/kobako-runtime/Cargo.toml +27 -6
  10. data/crates/kobako-runtime/README.md +16 -10
  11. data/crates/kobako-runtime/src/dispatch.rs +8 -0
  12. data/crates/kobako-runtime/src/error.rs +8 -9
  13. data/crates/kobako-runtime/src/filesystem/directory/spelling.rs +194 -0
  14. data/crates/kobako-runtime/src/filesystem/directory.rs +1082 -0
  15. data/crates/kobako-runtime/src/filesystem/memory.rs +1100 -0
  16. data/crates/kobako-runtime/src/filesystem/overlay/exclusive.rs +88 -0
  17. data/crates/kobako-runtime/src/filesystem/overlay/stack.rs +808 -0
  18. data/crates/kobako-runtime/src/filesystem/overlay.rs +1955 -0
  19. data/crates/kobako-runtime/src/filesystem/path.rs +60 -0
  20. data/crates/kobako-runtime/src/filesystem/temporary.rs +167 -0
  21. data/crates/kobako-runtime/src/filesystem.rs +392 -0
  22. data/crates/kobako-runtime/src/grant.rs +20 -0
  23. data/crates/kobako-runtime/src/lib.rs +12 -2
  24. data/crates/kobako-runtime/src/mount.rs +178 -0
  25. data/crates/kobako-runtime/src/profile.rs +4 -4
  26. data/crates/kobako-runtime/src/runtime.rs +43 -17
  27. data/crates/kobako-runtime/src/snapshot.rs +33 -15
  28. data/crates/kobako-runtime/src/yielder.rs +14 -6
  29. data/crates/kobako-transport/CHANGELOG.md +45 -0
  30. data/crates/kobako-transport/Cargo.toml +1 -1
  31. data/crates/kobako-transport/README.md +21 -12
  32. data/crates/kobako-transport/src/abi.rs +11 -14
  33. data/crates/kobako-transport/src/envelope/bytes.rs +18 -6
  34. data/crates/kobako-transport/src/envelope/compile_reply.rs +139 -0
  35. data/crates/kobako-transport/src/envelope/fault.rs +2 -1
  36. data/crates/kobako-transport/src/envelope/outcome.rs +17 -16
  37. data/crates/kobako-transport/src/envelope/preamble.rs +513 -0
  38. data/crates/kobako-transport/src/envelope/reply.rs +103 -61
  39. data/crates/kobako-transport/src/envelope/request.rs +305 -0
  40. data/crates/kobako-transport/src/envelope/run.rs +1 -2
  41. data/crates/kobako-transport/src/envelope/tag.rs +49 -0
  42. data/crates/kobako-transport/src/envelope/unsupported.rs +105 -0
  43. data/crates/kobako-transport/src/envelope.rs +19 -12
  44. data/crates/kobako-transport/src/lib.rs +0 -2
  45. data/crates/kobako-wasmtime/CHANGELOG.md +163 -0
  46. data/crates/kobako-wasmtime/Cargo.toml +23 -16
  47. data/crates/kobako-wasmtime/README.md +6 -4
  48. data/crates/kobako-wasmtime/src/abi.rs +67 -36
  49. data/crates/kobako-wasmtime/src/ambient.rs +64 -5
  50. data/crates/kobako-wasmtime/src/cache.rs +204 -127
  51. data/crates/kobako-wasmtime/src/capture.rs +4 -11
  52. data/crates/kobako-wasmtime/src/config.rs +69 -15
  53. data/crates/kobako-wasmtime/src/descriptors.rs +167 -0
  54. data/crates/kobako-wasmtime/src/dispatch.rs +95 -87
  55. data/crates/kobako-wasmtime/src/driver.rs +174 -125
  56. data/crates/kobako-wasmtime/src/exports.rs +88 -34
  57. data/crates/kobako-wasmtime/src/guest_mem.rs +151 -138
  58. data/crates/kobako-wasmtime/src/imports.rs +46 -0
  59. data/crates/kobako-wasmtime/src/instance_pre.rs +19 -58
  60. data/crates/kobako-wasmtime/src/invocation.rs +84 -412
  61. data/crates/kobako-wasmtime/src/io.rs +89 -0
  62. data/crates/kobako-wasmtime/src/lib.rs +7 -24
  63. data/crates/kobako-wasmtime/src/limiter.rs +253 -0
  64. data/crates/kobako-wasmtime/src/preview1/args.rs +60 -0
  65. data/crates/kobako-wasmtime/src/preview1/beneath.rs +188 -0
  66. data/crates/kobako-wasmtime/src/preview1/differential/mount.rs +609 -0
  67. data/crates/kobako-wasmtime/src/preview1/differential.rs +372 -0
  68. data/crates/kobako-wasmtime/src/preview1/errno.rs +267 -0
  69. data/crates/kobako-wasmtime/src/preview1/fd.rs +672 -0
  70. data/crates/kobako-wasmtime/src/preview1/memory.rs +205 -0
  71. data/crates/kobako-wasmtime/src/preview1/mount_view.rs +2499 -0
  72. data/crates/kobako-wasmtime/src/preview1/path.rs +421 -0
  73. data/crates/kobako-wasmtime/src/preview1/poll.rs +333 -0
  74. data/crates/kobako-wasmtime/src/preview1/sock.rs +30 -0
  75. data/crates/kobako-wasmtime/src/preview1.rs +461 -0
  76. data/crates/kobako-wasmtime/src/trap.rs +66 -59
  77. data/crates/kobako-wasmtime/src/wasi.rs +317 -0
  78. data/crates/kobako-wasmtime/tests/compile_posture.rs +43 -0
  79. data/crates/kobako-wasmtime/tests/guest_file.rs +1054 -0
  80. data/crates/kobako-wasmtime/tests/preamble_flags.rs +70 -0
  81. data/crates/kobako-wasmtime/tests/snippet_compile.rs +95 -0
  82. data/crates/kobako-wasmtime/tests/support/mod.rs +30 -0
  83. data/crates/kobako-wasmtime/tests/unanswered_call.rs +86 -0
  84. data/crates/kobako-wasmtime/tests/unsupported_call.rs +51 -0
  85. data/data/kobako.wasm +0 -0
  86. data/ext/kobako/Cargo.toml +4 -2
  87. data/ext/kobako/src/runtime/bridge.rs +177 -113
  88. data/ext/kobako/src/runtime/errors.rs +26 -32
  89. data/ext/kobako/src/runtime/escape.rs +53 -0
  90. data/ext/kobako/src/runtime/filesystem/grants.rs +91 -0
  91. data/ext/kobako/src/runtime/filesystem/ruby.rs +400 -0
  92. data/ext/kobako/src/runtime/filesystem.rs +236 -0
  93. data/ext/kobako/src/runtime/gvl.rs +11 -13
  94. data/ext/kobako/src/runtime/pinned_value.rs +31 -0
  95. data/ext/kobako/src/runtime/snapshot.rs +127 -0
  96. data/ext/kobako/src/runtime.rs +190 -312
  97. data/lib/kobako/capture.rb +1 -4
  98. data/lib/kobako/catalog/extensions.rb +21 -59
  99. data/lib/kobako/catalog/flags.rb +29 -0
  100. data/lib/kobako/catalog/grants.rb +30 -0
  101. data/lib/kobako/catalog/handles.rb +7 -54
  102. data/lib/kobako/catalog/mount.rb +27 -0
  103. data/lib/kobako/catalog/services.rb +17 -60
  104. data/lib/kobako/catalog/snippets.rb +41 -69
  105. data/lib/kobako/catalog.rb +13 -10
  106. data/lib/kobako/codec/decoder.rb +10 -28
  107. data/lib/kobako/codec/encoder.rb +3 -22
  108. data/lib/kobako/codec/error.rb +6 -27
  109. data/lib/kobako/codec/ext_types.rb +72 -94
  110. data/lib/kobako/codec/handle_walk.rb +65 -130
  111. data/lib/kobako/codec/nesting.rb +3 -8
  112. data/lib/kobako/codec/state.rb +21 -11
  113. data/lib/kobako/codec/utils.rb +6 -34
  114. data/lib/kobako/codec/value_budget.rb +86 -0
  115. data/lib/kobako/codec.rb +10 -22
  116. data/lib/kobako/constant_name.rb +8 -0
  117. data/lib/kobako/context.rb +83 -117
  118. data/lib/kobako/errors.rb +57 -132
  119. data/lib/kobako/execution.rb +16 -21
  120. data/lib/kobako/extension.rb +23 -40
  121. data/lib/kobako/filesystem.rb +38 -0
  122. data/lib/kobako/handle.rb +7 -12
  123. data/lib/kobako/outcome.rb +51 -80
  124. data/lib/kobako/payload/arguments.rb +1 -16
  125. data/lib/kobako/payload.rb +4 -12
  126. data/lib/kobako/pool.rb +25 -55
  127. data/lib/kobako/runtime.rb +7 -21
  128. data/lib/kobako/sandbox.rb +128 -137
  129. data/lib/kobako/sandbox_options.rb +71 -90
  130. data/lib/kobako/snippet/bytecode.rb +11 -0
  131. data/lib/kobako/snippet/source.rb +1 -16
  132. data/lib/kobako/snippet.rb +2 -10
  133. data/lib/kobako/transport/block_failure.rb +35 -0
  134. data/lib/kobako/transport/call.rb +1 -14
  135. data/lib/kobako/transport/dispatcher.rb +127 -206
  136. data/lib/kobako/transport/error.rb +6 -20
  137. data/lib/kobako/transport/exposure.rb +10 -17
  138. data/lib/kobako/transport/reflection.rb +108 -63
  139. data/lib/kobako/transport/run.rb +17 -71
  140. data/lib/kobako/transport/yielder.rb +20 -94
  141. data/lib/kobako/transport.rb +4 -15
  142. data/lib/kobako/unresolved.rb +4 -11
  143. data/lib/kobako/usage.rb +2 -6
  144. data/lib/kobako/version.rb +1 -1
  145. data/lib/kobako.rb +1 -0
  146. data/sig/kobako/catalog/extensions.rbs +2 -2
  147. data/sig/kobako/catalog/flags.rbs +13 -0
  148. data/sig/kobako/catalog/grants.rbs +13 -0
  149. data/sig/kobako/catalog/mount.rbs +28 -0
  150. data/sig/kobako/catalog/services.rbs +3 -5
  151. data/sig/kobako/catalog/snippets.rbs +5 -5
  152. data/sig/kobako/catalog.rbs +1 -0
  153. data/sig/kobako/codec/ext_types.rbs +10 -8
  154. data/sig/kobako/codec/handle_walk.rbs +9 -7
  155. data/sig/kobako/codec/state.rbs +7 -1
  156. data/sig/kobako/codec/utils.rbs +2 -2
  157. data/sig/kobako/codec/value_budget.rbs +23 -0
  158. data/sig/kobako/codec.rbs +4 -2
  159. data/sig/kobako/constant_name.rbs +3 -0
  160. data/sig/kobako/context.rbs +15 -8
  161. data/sig/kobako/errors.rbs +10 -4
  162. data/sig/kobako/filesystem.rbs +88 -0
  163. data/sig/kobako/guest_yielder.rbs +9 -0
  164. data/sig/kobako/outcome.rbs +6 -8
  165. data/sig/kobako/pool.rbs +1 -1
  166. data/sig/kobako/runtime.rbs +21 -45
  167. data/sig/kobako/sandbox.rbs +11 -1
  168. data/sig/kobako/sandbox_options.rbs +25 -7
  169. data/sig/kobako/snapshot.rbs +20 -0
  170. data/sig/kobako/snippet/bytecode.rbs +12 -0
  171. data/sig/kobako/transport/block_failure.rbs +13 -0
  172. data/sig/kobako/transport/dispatcher.rbs +15 -19
  173. data/sig/kobako/transport/exposure.rbs +1 -1
  174. data/sig/kobako/transport/reflection.rbs +19 -4
  175. data/sig/kobako/transport/run.rbs +1 -3
  176. data/sig/kobako/transport/yielder.rbs +5 -9
  177. data/sig/kobako/transport.rbs +9 -8
  178. metadata +75 -10
  179. data/crates/kobako-transport/src/envelope/invocation_frames.rs +0 -241
  180. data/crates/kobako-wasmtime/src/frames.rs +0 -273
  181. data/lib/kobako/snippet/binary.rb +0 -26
  182. data/rust-toolchain.toml +0 -15
  183. data/sig/kobako/snippet/binary.rbs +0 -12
@@ -0,0 +1,461 @@
1
+ //! kobako's own answers to the 36 preview1 imports that read the
2
+ //! descriptor table: `fd_*`, `path_*`, `sock_*`, and `poll_oneoff`.
3
+ //!
4
+ //! wasmtime-wasi keeps its table private, so a descriptor kobako places
5
+ //! there would stay invisible to its calls. Answering every call that reads
6
+ //! a descriptor keeps the guest on one table, `crate::descriptors`, where
7
+ //! each Mount is a preopen. The other ten imports — arguments,
8
+ //! environment, clocks, process, scheduling, and randomness — stay
9
+ //! wasmtime-wasi's.
10
+
11
+ mod args;
12
+ mod beneath;
13
+ mod errno;
14
+ mod fd;
15
+ mod memory;
16
+ mod path;
17
+ mod poll;
18
+ mod sock;
19
+
20
+ #[cfg(test)]
21
+ mod differential;
22
+ #[cfg(test)]
23
+ mod mount_view;
24
+
25
+ use wasmtime::{Caller, Extern, Linker};
26
+
27
+ use crate::descriptors::Descriptors;
28
+ use crate::invocation::Invocation;
29
+ use errno::{Answer, Failure};
30
+ use memory::GuestMemory;
31
+ use path::OpenFlags;
32
+
33
+ const MODULE: &str = "wasi_snapshot_preview1";
34
+
35
+ /// Run one call against the calling instance's memory and invocation,
36
+ /// answering its `errno`, or the trap that ends the invocation.
37
+ fn answer(
38
+ caller: &mut Caller<'_, Invocation>,
39
+ call: impl FnOnce(&mut GuestMemory, &mut Invocation) -> Answer,
40
+ ) -> wasmtime::Result<i32> {
41
+ let memory = caller
42
+ .get_export("memory")
43
+ .and_then(Extern::into_memory)
44
+ .ok_or_else(|| wasmtime::Error::msg("a preview1 call needs the guest's `memory` export"))?;
45
+ let (bytes, invocation) = memory.data_and_store_mut(caller);
46
+ let answer = call(&mut GuestMemory::new(bytes), invocation);
47
+ if let Some(trap) = invocation.abandonment() {
48
+ return Err(trap);
49
+ }
50
+ match answer {
51
+ Ok(()) => Ok(0),
52
+ Err(Failure::Errno(errno)) => Ok(errno as i32),
53
+ Err(Failure::Trap(trap)) => Err(trap),
54
+ }
55
+ }
56
+
57
+ fn table(invocation: &mut Invocation) -> &mut Descriptors {
58
+ invocation.wasi_mut().descriptors_mut()
59
+ }
60
+
61
+ /// Define kobako's answers over wasmtime-wasi's for the same names.
62
+ pub(crate) fn shadow(linker: &mut Linker<Invocation>) -> wasmtime::Result<()> {
63
+ linker.allow_shadowing(true);
64
+ shadow_fd(linker)?;
65
+ shadow_path(linker)?;
66
+ shadow_poll_and_sock(linker)?;
67
+ linker.allow_shadowing(false);
68
+ Ok(())
69
+ }
70
+
71
+ fn shadow_fd(linker: &mut Linker<Invocation>) -> wasmtime::Result<()> {
72
+ type C<'a> = Caller<'a, Invocation>;
73
+ linker.func_wrap(
74
+ MODULE,
75
+ "fd_advise",
76
+ |mut c: C, fd: i32, offset: i64, len: i64, advice: i32| {
77
+ answer(&mut c, |_, inv| {
78
+ fd::advise(
79
+ table(inv),
80
+ fd as u32,
81
+ offset as u64,
82
+ len as u64,
83
+ advice as u32,
84
+ )
85
+ })
86
+ },
87
+ )?;
88
+ linker.func_wrap(
89
+ MODULE,
90
+ "fd_allocate",
91
+ |mut c: C, fd: i32, _: i64, _: i64| {
92
+ answer(&mut c, |_, inv| fd::allocate(table(inv), fd as u32))
93
+ },
94
+ )?;
95
+ linker.func_wrap(MODULE, "fd_close", |mut c: C, fd: i32| {
96
+ answer(&mut c, |_, inv| fd::close(table(inv), fd as u32))
97
+ })?;
98
+ // A Filesystem makes what was written durable in one step, so data
99
+ // and metadata sync alike.
100
+ for name in ["fd_datasync", "fd_sync"] {
101
+ linker.func_wrap(MODULE, name, |mut c: C, fd: i32| {
102
+ answer(&mut c, |_, inv| fd::sync(table(inv), fd as u32))
103
+ })?;
104
+ }
105
+ linker.func_wrap(MODULE, "fd_fdstat_get", |mut c: C, fd: i32, out: i32| {
106
+ answer(&mut c, |mem, inv| {
107
+ fd::fdstat_get(table(inv), mem, fd as u32, out as u32)
108
+ })
109
+ })?;
110
+ linker.func_wrap(
111
+ MODULE,
112
+ "fd_fdstat_set_flags",
113
+ |mut c: C, fd: i32, flags: i32| {
114
+ answer(&mut c, |_, inv| {
115
+ fd::fdstat_set_flags(table(inv), fd as u32, flags as u32)
116
+ })
117
+ },
118
+ )?;
119
+ linker.func_wrap(
120
+ MODULE,
121
+ "fd_fdstat_set_rights",
122
+ |mut c: C, fd: i32, base: i64, inheriting: i64| {
123
+ answer(&mut c, |_, inv| {
124
+ fd::fdstat_set_rights(table(inv), fd as u32, base as u64, inheriting as u64)
125
+ })
126
+ },
127
+ )?;
128
+ linker.func_wrap(MODULE, "fd_filestat_get", |mut c: C, fd: i32, out: i32| {
129
+ answer(&mut c, |mem, inv| {
130
+ fd::filestat_get(table(inv), mem, fd as u32, out as u32)
131
+ })
132
+ })?;
133
+ linker.func_wrap(
134
+ MODULE,
135
+ "fd_filestat_set_size",
136
+ |mut c: C, fd: i32, size: i64| {
137
+ answer(&mut c, |_, inv| {
138
+ fd::filestat_set_size(table(inv), fd as u32, size as u64)
139
+ })
140
+ },
141
+ )?;
142
+ linker.func_wrap(
143
+ MODULE,
144
+ "fd_filestat_set_times",
145
+ |mut c: C, fd: i32, _: i64, _: i64, fst_flags: i32| {
146
+ answer(&mut c, |_, inv| {
147
+ fd::filestat_set_times(table(inv), fd as u32, fst_flags as u32)
148
+ })
149
+ },
150
+ )?;
151
+ linker.func_wrap(
152
+ MODULE,
153
+ "fd_pread",
154
+ |mut c: C, fd: i32, iovs: i32, iovs_len: i32, offset: i64, nread: i32| {
155
+ answer(&mut c, |mem, inv| {
156
+ let iovs = (iovs as u32, iovs_len as u32);
157
+ fd::pread(
158
+ table(inv),
159
+ mem,
160
+ fd as u32,
161
+ iovs,
162
+ offset as u64,
163
+ nread as u32,
164
+ )
165
+ })
166
+ },
167
+ )?;
168
+ linker.func_wrap(MODULE, "fd_prestat_get", |mut c: C, fd: i32, out: i32| {
169
+ answer(&mut c, |mem, inv| {
170
+ fd::prestat_get(table(inv), mem, fd as u32, out as u32)
171
+ })
172
+ })?;
173
+ linker.func_wrap(
174
+ MODULE,
175
+ "fd_prestat_dir_name",
176
+ |mut c: C, fd: i32, path: i32, path_len: i32| {
177
+ answer(&mut c, |mem, inv| {
178
+ fd::prestat_dir_name(table(inv), mem, fd as u32, path as u32, path_len as u32)
179
+ })
180
+ },
181
+ )?;
182
+ linker.func_wrap(
183
+ MODULE,
184
+ "fd_pwrite",
185
+ |mut c: C, fd: i32, ciovs: i32, ciovs_len: i32, offset: i64, nwritten: i32| {
186
+ answer(&mut c, |mem, inv| {
187
+ let ciovs = (ciovs as u32, ciovs_len as u32);
188
+ fd::pwrite(
189
+ table(inv),
190
+ mem,
191
+ fd as u32,
192
+ ciovs,
193
+ offset as u64,
194
+ nwritten as u32,
195
+ )
196
+ })
197
+ },
198
+ )?;
199
+ linker.func_wrap(
200
+ MODULE,
201
+ "fd_read",
202
+ |mut c: C, fd: i32, iovs: i32, iovs_len: i32, nread: i32| {
203
+ answer(&mut c, |mem, inv| {
204
+ let iovs = (iovs as u32, iovs_len as u32);
205
+ fd::read(table(inv), mem, fd as u32, iovs, nread as u32)
206
+ })
207
+ },
208
+ )?;
209
+ linker.func_wrap(
210
+ MODULE,
211
+ "fd_readdir",
212
+ |mut c: C, fd: i32, buf: i32, buf_len: i32, cookie: i64, bufused: i32| {
213
+ answer(&mut c, |mem, inv| {
214
+ let buf = (buf as u32, buf_len as u32);
215
+ fd::readdir(
216
+ table(inv),
217
+ mem,
218
+ fd as u32,
219
+ buf,
220
+ cookie as u64,
221
+ bufused as u32,
222
+ )
223
+ })
224
+ },
225
+ )?;
226
+ linker.func_wrap(MODULE, "fd_renumber", |mut c: C, from: i32, to: i32| {
227
+ answer(&mut c, |_, inv| {
228
+ fd::renumber(table(inv), from as u32, to as u32)
229
+ })
230
+ })?;
231
+ linker.func_wrap(
232
+ MODULE,
233
+ "fd_seek",
234
+ |mut c: C, fd: i32, offset: i64, whence: i32, out: i32| {
235
+ answer(&mut c, |mem, inv| {
236
+ fd::seek(
237
+ table(inv),
238
+ mem,
239
+ fd as u32,
240
+ offset,
241
+ whence as u32,
242
+ out as u32,
243
+ )
244
+ })
245
+ },
246
+ )?;
247
+ linker.func_wrap(MODULE, "fd_tell", |mut c: C, fd: i32, out: i32| {
248
+ answer(&mut c, |mem, inv| {
249
+ fd::tell(table(inv), mem, fd as u32, out as u32)
250
+ })
251
+ })?;
252
+ linker.func_wrap(
253
+ MODULE,
254
+ "fd_write",
255
+ |mut c: C, fd: i32, ciovs: i32, ciovs_len: i32, nwritten: i32| {
256
+ answer(&mut c, |mem, inv| {
257
+ let ciovs = (ciovs as u32, ciovs_len as u32);
258
+ fd::write(table(inv), mem, fd as u32, ciovs, nwritten as u32)
259
+ })
260
+ },
261
+ )?;
262
+ Ok(())
263
+ }
264
+
265
+ fn shadow_path(linker: &mut Linker<Invocation>) -> wasmtime::Result<()> {
266
+ type C<'a> = Caller<'a, Invocation>;
267
+ linker.func_wrap(
268
+ MODULE,
269
+ "path_create_directory",
270
+ |mut c: C, fd: i32, path: i32, len: i32| {
271
+ answer(&mut c, |mem, inv| {
272
+ path::create_directory(table(inv), mem, fd as u32, (path as u32, len as u32))
273
+ })
274
+ },
275
+ )?;
276
+ linker.func_wrap(
277
+ MODULE,
278
+ "path_filestat_get",
279
+ |mut c: C, fd: i32, flags: i32, path: i32, len: i32, out: i32| {
280
+ answer(&mut c, |mem, inv| {
281
+ let path = (path as u32, len as u32);
282
+ path::filestat_get(table(inv), mem, fd as u32, flags as u32, path, out as u32)
283
+ })
284
+ },
285
+ )?;
286
+ linker.func_wrap(
287
+ MODULE,
288
+ "path_filestat_set_times",
289
+ |mut c: C, fd: i32, flags: i32, path: i32, len: i32, _: i64, _: i64, fst_flags: i32| {
290
+ answer(&mut c, |mem, inv| {
291
+ let path = (path as u32, len as u32);
292
+ path::filestat_set_times(
293
+ table(inv),
294
+ mem,
295
+ fd as u32,
296
+ flags as u32,
297
+ path,
298
+ fst_flags as u32,
299
+ )
300
+ })
301
+ },
302
+ )?;
303
+ linker.func_wrap(
304
+ MODULE,
305
+ "path_link",
306
+ |mut c: C,
307
+ old_fd: i32,
308
+ old_flags: i32,
309
+ old_path: i32,
310
+ old_len: i32,
311
+ new_fd: i32,
312
+ new_path: i32,
313
+ new_len: i32| {
314
+ answer(&mut c, |mem, inv| {
315
+ path::link(
316
+ table(inv),
317
+ mem,
318
+ old_fd as u32,
319
+ old_flags as u32,
320
+ (old_path as u32, old_len as u32),
321
+ new_fd as u32,
322
+ (new_path as u32, new_len as u32),
323
+ )
324
+ })
325
+ },
326
+ )?;
327
+ linker.func_wrap(
328
+ MODULE,
329
+ "path_open",
330
+ |mut c: C,
331
+ fd: i32,
332
+ dirflags: i32,
333
+ path: i32,
334
+ path_len: i32,
335
+ oflags: i32,
336
+ rights_base: i64,
337
+ rights_inheriting: i64,
338
+ fdflags: i32,
339
+ out: i32| {
340
+ let flags = OpenFlags {
341
+ dirflags: dirflags as u32,
342
+ oflags: oflags as u32,
343
+ rights_base: rights_base as u64,
344
+ rights_inheriting: rights_inheriting as u64,
345
+ fdflags: fdflags as u32,
346
+ };
347
+ answer(&mut c, |mem, inv| {
348
+ let path = (path as u32, path_len as u32);
349
+ path::open(table(inv), mem, fd as u32, path, &flags, out as u32)
350
+ })
351
+ },
352
+ )?;
353
+ linker.func_wrap(
354
+ MODULE,
355
+ "path_readlink",
356
+ |mut c: C, fd: i32, path: i32, len: i32, _: i32, _: i32, _: i32| {
357
+ answer(&mut c, |mem, inv| {
358
+ path::readlink(table(inv), mem, fd as u32, (path as u32, len as u32))
359
+ })
360
+ },
361
+ )?;
362
+ linker.func_wrap(
363
+ MODULE,
364
+ "path_remove_directory",
365
+ |mut c: C, fd: i32, path: i32, len: i32| {
366
+ answer(&mut c, |mem, inv| {
367
+ path::remove_directory(table(inv), mem, fd as u32, (path as u32, len as u32))
368
+ })
369
+ },
370
+ )?;
371
+ linker.func_wrap(
372
+ MODULE,
373
+ "path_rename",
374
+ |mut c: C, fd: i32, old: i32, old_len: i32, new_fd: i32, new: i32, new_len: i32| {
375
+ answer(&mut c, |mem, inv| {
376
+ path::rename(
377
+ table(inv),
378
+ mem,
379
+ (fd as u32, (old as u32, old_len as u32)),
380
+ (new_fd as u32, (new as u32, new_len as u32)),
381
+ )
382
+ })
383
+ },
384
+ )?;
385
+ linker.func_wrap(
386
+ MODULE,
387
+ "path_symlink",
388
+ |mut c: C, old: i32, old_len: i32, fd: i32, new: i32, new_len: i32| {
389
+ answer(&mut c, |mem, inv| {
390
+ path::symlink(
391
+ table(inv),
392
+ mem,
393
+ (old as u32, old_len as u32),
394
+ fd as u32,
395
+ (new as u32, new_len as u32),
396
+ )
397
+ })
398
+ },
399
+ )?;
400
+ linker.func_wrap(
401
+ MODULE,
402
+ "path_unlink_file",
403
+ |mut c: C, fd: i32, path: i32, len: i32| {
404
+ answer(&mut c, |mem, inv| {
405
+ path::unlink_file(table(inv), mem, fd as u32, (path as u32, len as u32))
406
+ })
407
+ },
408
+ )?;
409
+ Ok(())
410
+ }
411
+
412
+ fn shadow_poll_and_sock(linker: &mut Linker<Invocation>) -> wasmtime::Result<()> {
413
+ type C<'a> = Caller<'a, Invocation>;
414
+ linker.func_wrap(
415
+ MODULE,
416
+ "poll_oneoff",
417
+ |mut c: C, subs: i32, events: i32, count: i32, nevents: i32| {
418
+ answer(&mut c, |mem, inv| {
419
+ poll::poll_oneoff(
420
+ inv,
421
+ mem,
422
+ subs as u32,
423
+ events as u32,
424
+ count as u32,
425
+ nevents as u32,
426
+ )
427
+ })
428
+ },
429
+ )?;
430
+ linker.func_wrap(
431
+ MODULE,
432
+ "sock_accept",
433
+ |mut c: C, fd: i32, flags: i32, _: i32| {
434
+ answer(&mut c, |_, inv| {
435
+ sock::accept(table(inv), fd as u32, flags as u32)
436
+ })
437
+ },
438
+ )?;
439
+ linker.func_wrap(
440
+ MODULE,
441
+ "sock_recv",
442
+ |mut c: C, fd: i32, _: i32, _: i32, ri_flags: i32, _: i32, _: i32| {
443
+ answer(&mut c, |_, inv| {
444
+ sock::recv(table(inv), fd as u32, ri_flags as u32)
445
+ })
446
+ },
447
+ )?;
448
+ linker.func_wrap(
449
+ MODULE,
450
+ "sock_send",
451
+ |mut c: C, fd: i32, _: i32, _: i32, _: i32, _: i32| {
452
+ answer(&mut c, |_, inv| sock::send(table(inv), fd as u32))
453
+ },
454
+ )?;
455
+ linker.func_wrap(MODULE, "sock_shutdown", |mut c: C, fd: i32, how: i32| {
456
+ answer(&mut c, |_, inv| {
457
+ sock::shutdown(table(inv), fd as u32, how as u32)
458
+ })
459
+ })?;
460
+ Ok(())
461
+ }
@@ -5,17 +5,30 @@
5
5
  //! onto its own error surface, and hosts the epoch-deadline callback
6
6
  //! that raises the wall-clock `TimeoutTrap`. The classification is a
7
7
  //! pure function over the error's downcast chain so it can be exercised
8
- //! from `cargo test` without any frontend; the trap marker types
9
- //! themselves live in `crate::invocation` (where the limiter / callback
10
- //! construct them).
8
+ //! from `cargo test` without any frontend.
11
9
 
12
- use std::time::Instant;
10
+ use std::time::{Duration, Instant};
13
11
 
14
- use wasmtime::{StoreContextMut, UpdateDeadline};
12
+ use wasmtime::{Store, StoreContextMut, UpdateDeadline};
15
13
 
16
- use crate::invocation::{Invocation, MemoryLimitTrap, TimeoutTrap};
14
+ use crate::invocation::Invocation;
15
+ use crate::limiter::MemoryLimitTrap;
17
16
  use kobako_runtime::error::{SetupError, Trap};
18
17
 
18
+ /// Marker error returned from the epoch-deadline callback when the
19
+ /// wall-clock deadline is exceeded. Downcast from the wasmtime trap
20
+ /// error to classify the failure as a timeout `Trap`.
21
+ #[derive(Debug)]
22
+ pub(crate) struct TimeoutTrap;
23
+
24
+ impl std::fmt::Display for TimeoutTrap {
25
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26
+ write!(f, "wall-clock deadline exceeded")
27
+ }
28
+ }
29
+
30
+ impl std::error::Error for TimeoutTrap {}
31
+
19
32
  /// Epoch delta that keeps the deadline effectively unreachable when no
20
33
  /// wall-clock cap is configured. Half the epoch range rather than
21
34
  /// `u64::MAX`: wasmtime adds the delta to the engine's current epoch,
@@ -24,14 +37,9 @@ use kobako_runtime::error::{SetupError, Trap};
24
37
  /// overflow checks).
25
38
  pub(crate) const NO_TIMEOUT_EPOCH_DELTA: u64 = u64::MAX / 2;
26
39
 
27
- /// Epoch-deadline callback installed on every Store. Read the per-run
28
- /// wall-clock deadline from `Invocation` and trap with
29
- /// `TimeoutTrap` once the deadline has passed; otherwise extend the
30
- /// next check by one tick of the process-wide epoch ticker. When the
31
- /// deadline is `None` the callback should not fire under the normal
32
- /// `Driver` invoke flow because
33
- /// `NO_TIMEOUT_EPOCH_DELTA` is primed; returning the same long
34
- /// extension keeps the callback inert as a defence in depth.
40
+ /// With no deadline the primed `NO_TIMEOUT_EPOCH_DELTA` keeps this from
41
+ /// firing; returning the same long extension keeps it inert as a defence
42
+ /// in depth.
35
43
  pub(crate) fn epoch_deadline_callback(
36
44
  ctx: StoreContextMut<'_, Invocation>,
37
45
  ) -> wasmtime::Result<UpdateDeadline> {
@@ -42,39 +50,42 @@ pub(crate) fn epoch_deadline_callback(
42
50
  }
43
51
  }
44
52
 
45
- /// Classify a wasmtime call error into a neutral `Trap`. Pure function
46
- /// over the error's downcast chain, so the kind routing is exercisable
47
- /// from `cargo test` without any frontend. The ABI export symbol
48
- /// (`__kobako_eval` / `__kobako_run`) is deliberately omitted from the
49
- /// message — the Sandbox layer attaches the user-facing verb
50
- /// (`Sandbox#eval` / `Sandbox#run`) so the message reads in caller
51
- /// vocabulary rather than ABI vocabulary.
52
- ///
53
- /// For the configured-cap paths the trap's own `std::fmt::Display`
54
- /// carries the user-facing reason (`"wall-clock deadline exceeded"`,
55
- /// `"linear memory growth exceeded memory_limit: ..."`); the wasmtime
56
- /// outer wrapper would otherwise surface only the `"error while
57
- /// executing at wasm backtrace: ..."` framing, which is operator noise
58
- /// on a cap trap. For any other error the framing is kept but the
59
- /// chain's root cause is appended (see `other_trap_message`) so the
60
- /// real trap reason survives.
61
- pub(crate) fn trap_from(err: wasmtime::Error) -> Trap {
53
+ /// The message leaves out the ABI export symbol, so the Sandbox layer can
54
+ /// attach the caller's verb instead. A cap trap carries its own message,
55
+ /// since wasmtime's backtrace framing is noise there.
56
+ pub(crate) fn to_trap(err: wasmtime::Error) -> Trap {
57
+ cap_trap(&err).unwrap_or_else(|| Trap::Other(other_trap_message(&err)))
58
+ }
59
+
60
+ /// Arm a Store's wall-clock deadline `limit` from now, or none. Epoch
61
+ /// interruption is on engine-wide and a fresh Store's deadline has already
62
+ /// elapsed, so an unbounded Store names one it cannot reach.
63
+ pub(crate) fn arm_deadline(store: &mut Store<Invocation>, limit: Option<Duration>) {
64
+ store.epoch_deadline_callback(epoch_deadline_callback);
65
+ store
66
+ .data_mut()
67
+ .set_deadline(limit.map(|limit| Instant::now() + limit));
68
+ store.set_epoch_deadline(if limit.is_some() {
69
+ 1
70
+ } else {
71
+ NO_TIMEOUT_EPOCH_DELTA
72
+ });
73
+ }
74
+
75
+ /// The cap trap `err` carries, if a cap fired: a setup step that fails
76
+ /// for any other reason words its own message instead.
77
+ pub(crate) fn cap_trap(err: &wasmtime::Error) -> Option<Trap> {
62
78
  if let Some(t) = err.downcast_ref::<TimeoutTrap>() {
63
- Trap::Timeout(t.to_string())
64
- } else if let Some(t) = err.downcast_ref::<MemoryLimitTrap>() {
65
- Trap::MemoryLimit(t.to_string())
79
+ Some(Trap::Timeout(t.to_string()))
66
80
  } else {
67
- Trap::Other(other_trap_message(&err))
81
+ err.downcast_ref::<MemoryLimitTrap>()
82
+ .map(|t| Trap::MemoryLimit(t.to_string()))
68
83
  }
69
84
  }
70
85
 
71
- /// Compose the message for a non-cap trap. wasmtime's `Display` surfaces only
72
- /// the `"error while executing at wasm backtrace: ..."` framing; the actual
73
- /// trap reason (e.g. `"wasm trap: indirect call type mismatch"`) is the
74
- /// chain's root cause and would otherwise be dropped, making real guest
75
- /// faults undiagnosable. Append the root cause unless the framing already
76
- /// carries it. Pure so it can be exercised from `cargo test` without the
77
- /// magnus surface.
86
+ /// wasmtime's `Display` shows only the backtrace framing; the real trap
87
+ /// reason is the chain's root cause and would otherwise be dropped, leaving
88
+ /// a guest fault undiagnosable.
78
89
  fn other_trap_message(err: &wasmtime::Error) -> String {
79
90
  let display = format!("{}", err);
80
91
  let root = err.root_cause().to_string();
@@ -85,22 +96,18 @@ fn other_trap_message(err: &wasmtime::Error) -> String {
85
96
  }
86
97
  }
87
98
 
88
- /// Classify an instantiation error as a runtime-dead `SetupError`.
89
- /// Instantiation runs while the per-path template is built, before any
90
- /// invocation — every such failure is a construction setup fault, not a
91
- /// per-invocation cap outcome. Neither cap can fire there: the memory
92
- /// cap is dormant until an invocation arms it
93
- /// (`Invocation::arm_memory_cap`) and the probe Store names an epoch
94
- /// deadline it cannot reach, so the `trap_from` trap-class split does
95
- /// not apply here.
99
+ /// The template is built before any invocation, so no invocation cap can
100
+ /// fire: the memory cap is not yet armed, and the probe's own setup
101
+ /// deadline is told apart where it fires. Every failure here is a setup
102
+ /// fault, not a trap.
96
103
  pub(crate) fn instantiate_err(err: wasmtime::Error) -> SetupError {
97
104
  SetupError::Dead(format!("instantiate: {err}"))
98
105
  }
99
106
 
100
107
  #[cfg(test)]
101
108
  mod tests {
102
- use super::{other_trap_message, trap_from, NO_TIMEOUT_EPOCH_DELTA};
103
- use crate::invocation::{Invocation, MemoryLimitTrap, TimeoutTrap};
109
+ use super::{other_trap_message, to_trap, TimeoutTrap, NO_TIMEOUT_EPOCH_DELTA};
110
+ use crate::limiter::MemoryLimitTrap;
104
111
  use kobako_runtime::error::Trap;
105
112
 
106
113
  // The no-timeout priming delta is added to the engine's current
@@ -115,32 +122,32 @@ mod tests {
115
122
  fn no_timeout_delta_survives_a_ticked_engine_epoch() {
116
123
  let engine = crate::cache::shared_engine().expect("shared engine must be constructible");
117
124
  engine.increment_epoch();
118
- let mut store = wasmtime::Store::new(engine, Invocation::new(None));
125
+ let mut store = wasmtime::Store::new(engine, ());
119
126
  store.set_epoch_deadline(NO_TIMEOUT_EPOCH_DELTA);
120
127
  }
121
128
 
122
129
  // @behavior OC-032
123
130
  #[test]
124
- fn trap_from_routes_timeout_trap_to_timeout() {
131
+ fn to_trap_routes_timeout_trap_to_timeout() {
125
132
  let err = wasmtime::Error::new(TimeoutTrap);
126
133
  let expected = TimeoutTrap.to_string();
127
- assert!(matches!(trap_from(err), Trap::Timeout(msg) if msg == expected));
134
+ assert!(matches!(to_trap(err), Trap::Timeout(msg) if msg == expected));
128
135
  }
129
136
 
130
137
  // @behavior OC-033
131
138
  #[test]
132
- fn trap_from_routes_memory_limit_trap_to_memory_limit() {
139
+ fn to_trap_routes_memory_limit_trap_to_memory_limit() {
133
140
  let trap = MemoryLimitTrap::new(1 << 20, 1 << 19);
134
141
  let expected = trap.to_string();
135
142
  let err = wasmtime::Error::new(trap);
136
- assert!(matches!(trap_from(err), Trap::MemoryLimit(msg) if msg == expected));
143
+ assert!(matches!(to_trap(err), Trap::MemoryLimit(msg) if msg == expected));
137
144
  }
138
145
 
139
146
  // @behavior OC-028
140
147
  #[test]
141
- fn trap_from_falls_back_to_other_for_unknown_errors() {
148
+ fn to_trap_falls_back_to_other_for_unknown_errors() {
142
149
  let err = wasmtime::Error::msg("some other wasmtime fault");
143
- assert!(matches!(trap_from(err), Trap::Other(_)));
150
+ assert!(matches!(to_trap(err), Trap::Other(_)));
144
151
  }
145
152
 
146
153
  // A guest hard trap reaches the host as a wasmtime error whose Display is