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,486 @@
1
+ //! The differential probe: every call kobako answers, made on descriptors
2
+ //! 0 to 3 against kobako's linker and against plain wasmtime-wasi preview1,
3
+ //! must leave the same errno, the same guest memory, and the same captures.
4
+ //!
5
+ //! It guards each wasmtime-wasi upgrade: a change in what preview1 answers
6
+ //! shows up here rather than in a guest. The deliberate differences — a
7
+ //! clock wait under the hermetic rung — are witnessed in `poll`.
8
+
9
+ mod mount;
10
+
11
+ use std::sync::OnceLock;
12
+
13
+ use wasmtime::{Linker, Module, Store};
14
+ use wasmtime_wasi::p1::{self, WasiP1Ctx};
15
+ use wasmtime_wasi::p2::pipe::{MemoryInputPipe, MemoryOutputPipe};
16
+ use wasmtime_wasi::WasiCtxBuilder;
17
+
18
+ use crate::cache::load_engine;
19
+ use crate::config::Config;
20
+ use crate::imports;
21
+ use crate::invocation::Invocation;
22
+ use crate::trap::NO_TIMEOUT_EPOCH_DELTA;
23
+ use crate::wasi::Wasi;
24
+ use kobako_runtime::profile::Profile;
25
+
26
+ /// The Preamble bytes stdin carries after its length prefix.
27
+ const PREAMBLE: &[u8] = b"hello";
28
+
29
+ /// Every export takes a descriptor and answers the call's errno. Memory
30
+ /// holds an `iovec` at 64 over 512..528, an empty one at 72, a `ciovec` at 80 over "hello" at
31
+ /// 600, and the paths "a" and "b" at 700 and 702; results land at 128 and
32
+ /// 256, subscriptions are built at 800 and events written at 896.
33
+ const PROBE_WAT: &str = r#"
34
+ (module
35
+ (import "wasi_snapshot_preview1" "fd_advise" (func $fd_advise (param i32 i64 i64 i32) (result i32)))
36
+ (import "wasi_snapshot_preview1" "fd_allocate" (func $fd_allocate (param i32 i64 i64) (result i32)))
37
+ (import "wasi_snapshot_preview1" "fd_close" (func $fd_close (param i32) (result i32)))
38
+ (import "wasi_snapshot_preview1" "fd_datasync" (func $fd_datasync (param i32) (result i32)))
39
+ (import "wasi_snapshot_preview1" "fd_fdstat_get" (func $fd_fdstat_get (param i32 i32) (result i32)))
40
+ (import "wasi_snapshot_preview1" "fd_fdstat_set_flags" (func $fd_fdstat_set_flags (param i32 i32) (result i32)))
41
+ (import "wasi_snapshot_preview1" "fd_fdstat_set_rights" (func $fd_fdstat_set_rights (param i32 i64 i64) (result i32)))
42
+ (import "wasi_snapshot_preview1" "fd_filestat_get" (func $fd_filestat_get (param i32 i32) (result i32)))
43
+ (import "wasi_snapshot_preview1" "fd_filestat_set_size" (func $fd_filestat_set_size (param i32 i64) (result i32)))
44
+ (import "wasi_snapshot_preview1" "fd_filestat_set_times" (func $fd_filestat_set_times (param i32 i64 i64 i32) (result i32)))
45
+ (import "wasi_snapshot_preview1" "fd_pread" (func $fd_pread (param i32 i32 i32 i64 i32) (result i32)))
46
+ (import "wasi_snapshot_preview1" "fd_prestat_get" (func $fd_prestat_get (param i32 i32) (result i32)))
47
+ (import "wasi_snapshot_preview1" "fd_prestat_dir_name" (func $fd_prestat_dir_name (param i32 i32 i32) (result i32)))
48
+ (import "wasi_snapshot_preview1" "fd_pwrite" (func $fd_pwrite (param i32 i32 i32 i64 i32) (result i32)))
49
+ (import "wasi_snapshot_preview1" "fd_read" (func $fd_read (param i32 i32 i32 i32) (result i32)))
50
+ (import "wasi_snapshot_preview1" "fd_readdir" (func $fd_readdir (param i32 i32 i32 i64 i32) (result i32)))
51
+ (import "wasi_snapshot_preview1" "fd_renumber" (func $fd_renumber (param i32 i32) (result i32)))
52
+ (import "wasi_snapshot_preview1" "fd_seek" (func $fd_seek (param i32 i64 i32 i32) (result i32)))
53
+ (import "wasi_snapshot_preview1" "fd_sync" (func $fd_sync (param i32) (result i32)))
54
+ (import "wasi_snapshot_preview1" "fd_tell" (func $fd_tell (param i32 i32) (result i32)))
55
+ (import "wasi_snapshot_preview1" "fd_write" (func $fd_write (param i32 i32 i32 i32) (result i32)))
56
+ (import "wasi_snapshot_preview1" "path_create_directory" (func $path_create_directory (param i32 i32 i32) (result i32)))
57
+ (import "wasi_snapshot_preview1" "path_filestat_get" (func $path_filestat_get (param i32 i32 i32 i32 i32) (result i32)))
58
+ (import "wasi_snapshot_preview1" "path_filestat_set_times" (func $path_filestat_set_times (param i32 i32 i32 i32 i64 i64 i32) (result i32)))
59
+ (import "wasi_snapshot_preview1" "path_link" (func $path_link (param i32 i32 i32 i32 i32 i32 i32) (result i32)))
60
+ (import "wasi_snapshot_preview1" "path_open" (func $path_open (param i32 i32 i32 i32 i32 i64 i64 i32 i32) (result i32)))
61
+ (import "wasi_snapshot_preview1" "path_readlink" (func $path_readlink (param i32 i32 i32 i32 i32 i32) (result i32)))
62
+ (import "wasi_snapshot_preview1" "path_remove_directory" (func $path_remove_directory (param i32 i32 i32) (result i32)))
63
+ (import "wasi_snapshot_preview1" "path_rename" (func $path_rename (param i32 i32 i32 i32 i32 i32) (result i32)))
64
+ (import "wasi_snapshot_preview1" "path_symlink" (func $path_symlink (param i32 i32 i32 i32 i32) (result i32)))
65
+ (import "wasi_snapshot_preview1" "path_unlink_file" (func $path_unlink_file (param i32 i32 i32) (result i32)))
66
+ (import "wasi_snapshot_preview1" "poll_oneoff" (func $poll_oneoff (param i32 i32 i32 i32) (result i32)))
67
+ (import "wasi_snapshot_preview1" "sock_accept" (func $sock_accept (param i32 i32 i32) (result i32)))
68
+ (import "wasi_snapshot_preview1" "sock_recv" (func $sock_recv (param i32 i32 i32 i32 i32 i32) (result i32)))
69
+ (import "wasi_snapshot_preview1" "sock_send" (func $sock_send (param i32 i32 i32 i32 i32) (result i32)))
70
+ (import "wasi_snapshot_preview1" "sock_shutdown" (func $sock_shutdown (param i32 i32) (result i32)))
71
+ (memory (export "memory") 1)
72
+ (data (i32.const 64) "\00\02\00\00\10\00\00\00")
73
+ (data (i32.const 72) "\00\02\00\00\00\00\00\00")
74
+ (data (i32.const 80) "\58\02\00\00\05\00\00\00")
75
+ (data (i32.const 88) "\00\00\02\00\10\00\00\00")
76
+ (data (i32.const 600) "hello")
77
+ (data (i32.const 700) "a\00b")
78
+
79
+ ;; One subscription at 800: userdata 7, the tag, and the descriptor.
80
+ (func $subscribe (param $tag i32) (param $fd i32)
81
+ (i64.store (i32.const 800) (i64.const 7))
82
+ (i32.store8 (i32.const 808) (local.get $tag))
83
+ (i32.store (i32.const 816) (local.get $fd)))
84
+
85
+ (func (export "fd_advise") (param i32) (result i32) (call $fd_advise (local.get 0) (i64.const 0) (i64.const 0) (i32.const 0)))
86
+ (func (export "fd_allocate") (param i32) (result i32) (call $fd_allocate (local.get 0) (i64.const 0) (i64.const 0)))
87
+ (func (export "fd_close") (param i32) (result i32) (call $fd_close (local.get 0)))
88
+ (func (export "fd_datasync") (param i32) (result i32) (call $fd_datasync (local.get 0)))
89
+ (func (export "fd_fdstat_get") (param i32) (result i32) (call $fd_fdstat_get (local.get 0) (i32.const 256)))
90
+ (func (export "fd_fdstat_set_flags") (param i32) (result i32) (call $fd_fdstat_set_flags (local.get 0) (i32.const 0)))
91
+ (func (export "fd_fdstat_set_rights") (param i32) (result i32) (call $fd_fdstat_set_rights (local.get 0) (i64.const 0) (i64.const 0)))
92
+ (func (export "fd_filestat_get") (param i32) (result i32) (call $fd_filestat_get (local.get 0) (i32.const 256)))
93
+ (func (export "fd_filestat_set_size") (param i32) (result i32) (call $fd_filestat_set_size (local.get 0) (i64.const 0)))
94
+ (func (export "fd_filestat_set_size_huge") (param i32) (result i32) (call $fd_filestat_set_size (local.get 0) (i64.const -1)))
95
+ (func (export "fd_filestat_set_times") (param i32) (result i32) (call $fd_filestat_set_times (local.get 0) (i64.const 0) (i64.const 0) (i32.const 0)))
96
+ (func (export "fd_pread") (param i32) (result i32) (call $fd_pread (local.get 0) (i32.const 64) (i32.const 1) (i64.const 0) (i32.const 128)))
97
+ (func (export "fd_prestat_get") (param i32) (result i32) (call $fd_prestat_get (local.get 0) (i32.const 256)))
98
+ (func (export "fd_prestat_dir_name") (param i32) (result i32) (call $fd_prestat_dir_name (local.get 0) (i32.const 512) (i32.const 16)))
99
+ (func (export "fd_pwrite") (param i32) (result i32) (call $fd_pwrite (local.get 0) (i32.const 80) (i32.const 1) (i64.const 0) (i32.const 128)))
100
+ (func (export "fd_read") (param i32) (result i32) (call $fd_read (local.get 0) (i32.const 64) (i32.const 1) (i32.const 128)))
101
+ (func (export "fd_readdir") (param i32) (result i32) (call $fd_readdir (local.get 0) (i32.const 512) (i32.const 64) (i64.const 0) (i32.const 128)))
102
+ (func (export "fd_renumber") (param i32) (result i32) (call $fd_renumber (local.get 0) (local.get 0)))
103
+ (func (export "fd_seek") (param i32) (result i32) (call $fd_seek (local.get 0) (i64.const 0) (i32.const 0) (i32.const 128)))
104
+ (func (export "fd_sync") (param i32) (result i32) (call $fd_sync (local.get 0)))
105
+ (func (export "fd_tell") (param i32) (result i32) (call $fd_tell (local.get 0) (i32.const 128)))
106
+ (func (export "fd_write") (param i32) (result i32) (call $fd_write (local.get 0) (i32.const 80) (i32.const 1) (i32.const 128)))
107
+ (func (export "path_create_directory") (param i32) (result i32) (call $path_create_directory (local.get 0) (i32.const 700) (i32.const 1)))
108
+ (func (export "path_filestat_get") (param i32) (result i32) (call $path_filestat_get (local.get 0) (i32.const 0) (i32.const 700) (i32.const 1) (i32.const 256)))
109
+ (func (export "path_filestat_set_times") (param i32) (result i32) (call $path_filestat_set_times (local.get 0) (i32.const 0) (i32.const 700) (i32.const 1) (i64.const 0) (i64.const 0) (i32.const 0)))
110
+ (func (export "path_link") (param i32) (result i32) (call $path_link (local.get 0) (i32.const 0) (i32.const 700) (i32.const 1) (local.get 0) (i32.const 702) (i32.const 1)))
111
+ (func (export "path_open") (param i32) (result i32) (call $path_open (local.get 0) (i32.const 0) (i32.const 700) (i32.const 1) (i32.const 0) (i64.const 0) (i64.const 0) (i32.const 0) (i32.const 128)))
112
+ (func (export "path_readlink") (param i32) (result i32) (call $path_readlink (local.get 0) (i32.const 700) (i32.const 1) (i32.const 512) (i32.const 16) (i32.const 128)))
113
+ (func (export "path_remove_directory") (param i32) (result i32) (call $path_remove_directory (local.get 0) (i32.const 700) (i32.const 1)))
114
+ (func (export "path_rename") (param i32) (result i32) (call $path_rename (local.get 0) (i32.const 700) (i32.const 1) (local.get 0) (i32.const 702) (i32.const 1)))
115
+ (func (export "path_symlink") (param i32) (result i32) (call $path_symlink (i32.const 700) (i32.const 1) (local.get 0) (i32.const 702) (i32.const 1)))
116
+ (func (export "path_unlink_file") (param i32) (result i32) (call $path_unlink_file (local.get 0) (i32.const 700) (i32.const 1)))
117
+ (func (export "poll_oneoff_read") (param i32) (result i32)
118
+ (call $subscribe (i32.const 1) (local.get 0))
119
+ (call $poll_oneoff (i32.const 800) (i32.const 896) (i32.const 1) (i32.const 128)))
120
+ (func (export "poll_oneoff_write") (param i32) (result i32)
121
+ (call $subscribe (i32.const 2) (local.get 0))
122
+ (call $poll_oneoff (i32.const 800) (i32.const 896) (i32.const 1) (i32.const 128)))
123
+ ;; A lone relative wait of no time on the clock the descriptor numbers.
124
+ (func (export "poll_oneoff_clock") (param i32) (result i32)
125
+ (call $subscribe (i32.const 0) (local.get 0))
126
+ (call $poll_oneoff (i32.const 800) (i32.const 896) (i32.const 1) (i32.const 128)))
127
+ (func (export "poll_oneoff_none") (param i32) (result i32)
128
+ (call $poll_oneoff (i32.const 800) (i32.const 896) (i32.const 0) (i32.const 128)))
129
+ (func (export "sock_accept") (param i32) (result i32) (call $sock_accept (local.get 0) (i32.const 0) (i32.const 128)))
130
+ (func (export "sock_recv") (param i32) (result i32) (call $sock_recv (local.get 0) (i32.const 64) (i32.const 1) (i32.const 0) (i32.const 128) (i32.const 132)))
131
+ (func (export "sock_send") (param i32) (result i32) (call $sock_send (local.get 0) (i32.const 80) (i32.const 1) (i32.const 0) (i32.const 128)))
132
+ (func (export "sock_shutdown") (param i32) (result i32) (call $sock_shutdown (local.get 0) (i32.const 0)))
133
+
134
+ ;; A buffer past the one page of memory, through the iovec at 88.
135
+ (func (export "fd_read_far") (param i32) (result i32) (call $fd_read (local.get 0) (i32.const 88) (i32.const 1) (i32.const 128)))
136
+ (func (export "fd_pread_far") (param i32) (result i32) (call $fd_pread (local.get 0) (i32.const 88) (i32.const 1) (i64.const 0) (i32.const 128)))
137
+ (func (export "fd_write_far") (param i32) (result i32) (call $fd_write (local.get 0) (i32.const 88) (i32.const 1) (i32.const 128)))
138
+ (func (export "fd_pwrite_far") (param i32) (result i32) (call $fd_pwrite (local.get 0) (i32.const 88) (i32.const 1) (i64.const 0) (i32.const 128)))
139
+ (func (export "fd_readdir_far") (param i32) (result i32) (call $fd_readdir (local.get 0) (i32.const 0x20000) (i32.const 0) (i64.const 0) (i32.const 128)))
140
+ (func (export "path_open_far") (param i32) (result i32) (call $path_open (local.get 0) (i32.const 0) (i32.const 0x20000) (i32.const 1) (i32.const 0) (i64.const 0) (i64.const 0) (i32.const 0) (i32.const 128)))
141
+ ;; A path longer than one call's hostcall fuel.
142
+ (func (export "path_open_huge") (param i32) (result i32) (call $path_open (local.get 0) (i32.const 0) (i32.const 700) (i32.const 0x0c800000) (i32.const 0) (i64.const 0) (i64.const 0) (i32.const 0) (i32.const 128)))
143
+ ;; Each argument one past what the witx defines.
144
+ (func (export "fd_advise_undefined") (param i32) (result i32) (call $fd_advise (local.get 0) (i64.const 0) (i64.const 0) (i32.const 6)))
145
+ (func (export "fd_seek_undefined") (param i32) (result i32) (call $fd_seek (local.get 0) (i64.const 0) (i32.const 3) (i32.const 128)))
146
+ (func (export "fd_fdstat_set_flags_undefined") (param i32) (result i32) (call $fd_fdstat_set_flags (local.get 0) (i32.const 0x20)))
147
+ (func (export "fd_fdstat_set_rights_undefined") (param i32) (result i32) (call $fd_fdstat_set_rights (local.get 0) (i64.const 0x40000000) (i64.const 0)))
148
+ (func (export "fd_filestat_set_times_undefined") (param i32) (result i32) (call $fd_filestat_set_times (local.get 0) (i64.const 0) (i64.const 0) (i32.const 0x10)))
149
+ (func (export "path_filestat_get_undefined") (param i32) (result i32) (call $path_filestat_get (local.get 0) (i32.const 2) (i32.const 700) (i32.const 1) (i32.const 256)))
150
+ (func (export "path_open_undefined") (param i32) (result i32) (call $path_open (local.get 0) (i32.const 0) (i32.const 700) (i32.const 1) (i32.const 0x10) (i64.const 0) (i64.const 0) (i32.const 0) (i32.const 128)))
151
+ (func (export "path_link_undefined") (param i32) (result i32) (call $path_link (local.get 0) (i32.const 2) (i32.const 700) (i32.const 1) (local.get 0) (i32.const 702) (i32.const 1)))
152
+ (func (export "sock_recv_undefined") (param i32) (result i32) (call $sock_recv (local.get 0) (i32.const 64) (i32.const 1) (i32.const 4) (i32.const 128) (i32.const 132)))
153
+ (func (export "sock_shutdown_undefined") (param i32) (result i32) (call $sock_shutdown (local.get 0) (i32.const 4)))
154
+
155
+ (func (export "fd_read_empty") (param i32) (result i32) (call $fd_read (local.get 0) (i32.const 72) (i32.const 1) (i32.const 128)))
156
+ (func (export "fd_write_empty") (param i32) (result i32) (call $fd_write (local.get 0) (i32.const 72) (i32.const 1) (i32.const 128)))
157
+ (func (export "close_twice") (param i32) (result i32)
158
+ (drop (call $fd_close (local.get 0)))
159
+ (call $fd_close (local.get 0)))
160
+ (func (export "renumber_onto_stdin") (param i32) (result i32)
161
+ (drop (call $fd_renumber (local.get 0) (i32.const 0)))
162
+ (call $fd_fdstat_get (i32.const 0) (i32.const 256)))
163
+ (func (export "read_twice") (param i32) (result i32)
164
+ (drop (call $fd_read (local.get 0) (i32.const 64) (i32.const 1) (i32.const 128)))
165
+ (call $fd_read (local.get 0) (i32.const 64) (i32.const 1) (i32.const 132)))
166
+ (func (export "write_twice") (param i32) (result i32)
167
+ (drop (call $fd_write (local.get 0) (i32.const 80) (i32.const 1) (i32.const 128)))
168
+ (call $fd_write (local.get 0) (i32.const 80) (i32.const 1) (i32.const 132))))
169
+ "#;
170
+
171
+ const CALLS: &[&str] = &[
172
+ "fd_advise",
173
+ "fd_allocate",
174
+ "fd_close",
175
+ "fd_datasync",
176
+ "fd_fdstat_get",
177
+ "fd_fdstat_set_flags",
178
+ "fd_fdstat_set_rights",
179
+ "fd_filestat_get",
180
+ "fd_filestat_set_size",
181
+ "fd_filestat_set_times",
182
+ "fd_pread",
183
+ "fd_prestat_get",
184
+ "fd_prestat_dir_name",
185
+ "fd_pwrite",
186
+ "fd_read",
187
+ "fd_readdir",
188
+ "fd_renumber",
189
+ "fd_seek",
190
+ "fd_sync",
191
+ "fd_tell",
192
+ "fd_write",
193
+ "path_create_directory",
194
+ "path_filestat_get",
195
+ "path_filestat_set_times",
196
+ "path_link",
197
+ "path_open",
198
+ "path_readlink",
199
+ "path_remove_directory",
200
+ "path_rename",
201
+ "path_symlink",
202
+ "path_unlink_file",
203
+ "poll_oneoff_read",
204
+ "poll_oneoff_write",
205
+ "poll_oneoff_clock",
206
+ "poll_oneoff_none",
207
+ "sock_accept",
208
+ "sock_recv",
209
+ "sock_send",
210
+ "sock_shutdown",
211
+ "fd_read_far",
212
+ "fd_pread_far",
213
+ "fd_write_far",
214
+ "fd_pwrite_far",
215
+ "fd_readdir_far",
216
+ "path_open_far",
217
+ "path_open_huge",
218
+ "fd_advise_undefined",
219
+ "fd_seek_undefined",
220
+ "fd_fdstat_set_flags_undefined",
221
+ "fd_fdstat_set_rights_undefined",
222
+ "fd_filestat_set_times_undefined",
223
+ "path_filestat_get_undefined",
224
+ "path_open_undefined",
225
+ "path_link_undefined",
226
+ "sock_recv_undefined",
227
+ "sock_shutdown_undefined",
228
+ "fd_read_empty",
229
+ "fd_write_empty",
230
+ "close_twice",
231
+ "renumber_onto_stdin",
232
+ "read_twice",
233
+ "write_twice",
234
+ ];
235
+
236
+ /// What one call leaves behind; `None` for a call that trapped.
237
+ #[derive(Debug, PartialEq)]
238
+ struct Observation {
239
+ errno: Option<i32>,
240
+ memory: Vec<u8>,
241
+ stdout: Vec<u8>,
242
+ stderr: Vec<u8>,
243
+ }
244
+
245
+ /// A capped Channel exercises the pipe filling mid-write.
246
+ const CHANNEL_LIMITS: [Option<usize>; 2] = [None, Some(3)];
247
+
248
+ fn stdin_bytes() -> Vec<u8> {
249
+ let mut bytes = (PREAMBLE.len() as u32).to_be_bytes().to_vec();
250
+ bytes.extend_from_slice(PREAMBLE);
251
+ bytes
252
+ }
253
+
254
+ fn observe<T>(
255
+ mut store: Store<T>,
256
+ linker: &Linker<T>,
257
+ call: &str,
258
+ fd: i32,
259
+ ) -> (Observation, Store<T>) {
260
+ static MODULE: OnceLock<Module> = OnceLock::new();
261
+ let module = MODULE
262
+ .get_or_init(|| Module::new(store.engine(), PROBE_WAT).expect("probe module must compile"));
263
+ let instance = linker
264
+ .instantiate(&mut store, module)
265
+ .expect("probe module must instantiate");
266
+ let errno = instance
267
+ .get_typed_func::<i32, i32>(&mut store, call)
268
+ .expect("probe export must resolve")
269
+ .call(&mut store, fd)
270
+ .ok();
271
+ let memory = instance
272
+ .get_memory(&mut store, "memory")
273
+ .expect("probe exports its memory")
274
+ .data(&store)[..1024]
275
+ .to_vec();
276
+ let observation = Observation {
277
+ errno,
278
+ memory,
279
+ stdout: Vec::new(),
280
+ stderr: Vec::new(),
281
+ };
282
+ (observation, store)
283
+ }
284
+
285
+ fn through_kobako(call: &str, fd: i32, limit: Option<usize>) -> Observation {
286
+ let engine = load_engine().expect("shared engine must be constructible");
287
+ let config = Config {
288
+ timeout: None,
289
+ setup_timeout: None,
290
+ memory_limit: None,
291
+ stdout_limit: limit,
292
+ stderr_limit: limit,
293
+ open_file_limit: None,
294
+ profile: Profile::Hermetic,
295
+ security: Vec::new(),
296
+ };
297
+ let wasi = Wasi::for_invocation(&config, stdin_bytes(), &[]).expect("WASI context must build");
298
+ let mut store = Store::new(engine, Invocation::new(None, wasi));
299
+ store.set_epoch_deadline(NO_TIMEOUT_EPOCH_DELTA);
300
+ let linker = imports::linker().expect("host imports must link");
301
+ let (mut observation, store) = observe(store, &linker, call, fd);
302
+ observation.stdout = store.data().wasi().stdout_bytes();
303
+ observation.stderr = store.data().wasi().stderr_bytes();
304
+ observation
305
+ }
306
+
307
+ /// Plain wasmtime-wasi preview1 over the same stdin and pipes.
308
+ struct Oracle {
309
+ ctx: WasiP1Ctx,
310
+ stdout: MemoryOutputPipe,
311
+ stderr: MemoryOutputPipe,
312
+ }
313
+
314
+ fn through_wasmtime_wasi(call: &str, fd: i32, limit: Option<usize>) -> Observation {
315
+ let engine = load_engine().expect("shared engine must be constructible");
316
+ let stdout = MemoryOutputPipe::new(kobako_runtime::endpoint::pipe_capacity(limit));
317
+ let stderr = MemoryOutputPipe::new(kobako_runtime::endpoint::pipe_capacity(limit));
318
+ let ctx = WasiCtxBuilder::new()
319
+ .stdin(MemoryInputPipe::new(stdin_bytes()))
320
+ .stdout(stdout.clone())
321
+ .stderr(stderr.clone())
322
+ .build_p1();
323
+ let mut store = Store::new(
324
+ engine,
325
+ Oracle {
326
+ ctx,
327
+ stdout,
328
+ stderr,
329
+ },
330
+ );
331
+ store.set_epoch_deadline(NO_TIMEOUT_EPOCH_DELTA);
332
+ let mut linker = Linker::new(engine);
333
+ p1::add_to_linker_sync(&mut linker, |oracle: &mut Oracle| &mut oracle.ctx)
334
+ .expect("preview1 must link");
335
+ let (mut observation, store) = observe(store, &linker, call, fd);
336
+ observation.stdout = store.data().stdout.contents().to_vec();
337
+ observation.stderr = store.data().stderr.contents().to_vec();
338
+ observation
339
+ }
340
+
341
+ // Descriptor 3 is not open, so each call can only answer by the order its
342
+ // checks run in.
343
+ // @behavior RT-138
344
+ #[test]
345
+ fn an_argument_preview1_cannot_hold_answers_in_wasmtime_wasis_order() {
346
+ let errno = |call| through_kobako(call, 3, None).errno;
347
+ assert_eq!(
348
+ (
349
+ errno("fd_seek_undefined"),
350
+ errno("fd_read_far"),
351
+ errno("path_open_huge"),
352
+ ),
353
+ (None, Some(8), Some(48)),
354
+ "through kobako's preview1 imports on a descriptor not open, an undefined whence must \
355
+ trap, a buffer past memory must answer EBADF before it is bounded, and a path past the \
356
+ call's fuel must answer ENOMEM before it is read, as wasmtime-wasi answers each"
357
+ );
358
+ }
359
+
360
+ #[test]
361
+ fn every_descriptor_call_answers_as_wasmtime_wasi_preview1() {
362
+ for limit in CHANNEL_LIMITS {
363
+ for call in CALLS {
364
+ for fd in 0..=3 {
365
+ let kobako = through_kobako(call, fd, limit);
366
+ if let Some(errno) = answered_as_linux(call, fd) {
367
+ assert_eq!(
368
+ kobako.errno,
369
+ Some(errno),
370
+ "{call} on descriptor {fd} through kobako's preview1 imports must answer \
371
+ as Linux does"
372
+ );
373
+ continue;
374
+ }
375
+ assert_eq!(
376
+ kobako,
377
+ through_wasmtime_wasi(call, fd, limit),
378
+ "{call} on descriptor {fd} with Channels capped at {limit:?} through \
379
+ kobako's preview1 imports must answer as wasmtime-wasi's preview1 does"
380
+ );
381
+ }
382
+ }
383
+ }
384
+ }
385
+
386
+ /// Where kobako parts from wasmtime-wasi, the errno Linux answers, which
387
+ /// outranks it. For a standard stream it answers as for a pipe: an offset, a
388
+ /// sync, or a size is no pipe's, and a pipe is no directory to open beneath.
389
+ /// Linux also takes `O_APPEND` on a pipe, reports one link where wasmtime-wasi
390
+ /// reports none, checks neither path nor descriptor for a call setting no
391
+ /// time, as `utimensat` returns first, and reports a wait on a descriptor not
392
+ /// open in its event, as `poll` marks it `POLLNVAL`.
393
+ fn answered_as_linux(call: &str, fd: i32) -> Option<i32> {
394
+ const ERRNO_SUCCESS: i32 = 0;
395
+ const ERRNO_INVAL: i32 = 28;
396
+ const ERRNO_NOTDIR: i32 = 54;
397
+ const ERRNO_SPIPE: i32 = 70;
398
+ match (call, fd) {
399
+ ("fd_filestat_set_times" | "path_filestat_set_times", 3) => Some(ERRNO_SUCCESS),
400
+ ("poll_oneoff_read" | "poll_oneoff_write", 3) => Some(ERRNO_SUCCESS),
401
+ ("fd_advise", 0..=2) => Some(ERRNO_SPIPE),
402
+ ("fd_allocate" | "fd_sync" | "fd_datasync" | "fd_filestat_set_size", 0..=2) => {
403
+ Some(ERRNO_INVAL)
404
+ }
405
+ (
406
+ "fd_fdstat_set_flags"
407
+ | "fd_filestat_get"
408
+ | "fd_filestat_set_times"
409
+ | "path_filestat_set_times",
410
+ 0..=2,
411
+ ) => Some(ERRNO_SUCCESS),
412
+ ("fd_read_empty", 0) => Some(ERRNO_SUCCESS),
413
+ ("fd_readdir" | "fd_readdir_far", 0..=2) => Some(ERRNO_NOTDIR),
414
+ (
415
+ "path_create_directory"
416
+ | "path_filestat_get"
417
+ | "path_link"
418
+ | "path_open"
419
+ | "path_readlink"
420
+ | "path_remove_directory"
421
+ | "path_rename"
422
+ | "path_symlink"
423
+ | "path_unlink_file",
424
+ 0..=2,
425
+ ) => Some(ERRNO_NOTDIR),
426
+ ("fd_pread" | "fd_pread_far", 1 | 2) => Some(ERRNO_SPIPE),
427
+ ("fd_pwrite" | "fd_pwrite_far", 0) => Some(ERRNO_SPIPE),
428
+ _ => None,
429
+ }
430
+ }
431
+
432
+ // wasmtime-wasi fails the whole wait; the witx gives each event its own error,
433
+ // and Linux's `do_pollfd` marks the descriptor `POLLNVAL`, which wasi-libc reads
434
+ // back from an event's EBADF.
435
+ // @behavior RT-295
436
+ #[test]
437
+ fn a_wait_on_a_descriptor_not_open_reports_it_in_its_event() {
438
+ let read = through_kobako("poll_oneoff_read", 3, None);
439
+ let write = through_kobako("poll_oneoff_write", 3, None);
440
+ let event = |seen: &Observation| {
441
+ (
442
+ seen.errno,
443
+ u64::from_le_bytes(seen.memory[896..904].try_into().expect("eight bytes")),
444
+ u16::from_le_bytes([seen.memory[904], seen.memory[905]]),
445
+ u32::from_le_bytes(seen.memory[128..132].try_into().expect("four bytes")),
446
+ )
447
+ };
448
+ assert_eq!(
449
+ (event(&read), event(&write)),
450
+ ((Some(0), 7, 8, 1), (Some(0), 7, 8, 1)),
451
+ "poll_oneoff on descriptor 3, not open, through kobako's preview1 imports must succeed \
452
+ with one event whose error is EBADF, as Linux's poll marks it POLLNVAL"
453
+ );
454
+ }
455
+
456
+ // Linux's `ksys_ftruncate` reads the length as a signed `loff_t` and refuses a
457
+ // negative one before it looks for the descriptor.
458
+ // @behavior RT-296
459
+ #[test]
460
+ fn a_size_no_file_can_take_answers_before_the_descriptor_is_found() {
461
+ assert_eq!(
462
+ through_kobako("fd_filestat_set_size_huge", 3, None).errno,
463
+ Some(28),
464
+ "fd_filestat_set_size of 2^64 - 1 on descriptor 3, not open, through kobako's preview1 \
465
+ imports must answer EINVAL, as ftruncate checks the length before the descriptor"
466
+ );
467
+ }
468
+
469
+ // wasmtime-wasi answers EBADF for the end a call cannot use; Linux reads
470
+ // `FMODE_PREAD` first, and a pipe has none on either end.
471
+ // @behavior RT-178
472
+ #[test]
473
+ fn a_standard_stream_takes_no_offset() {
474
+ let errno = |call, fd| through_kobako(call, fd, None).errno;
475
+ assert_eq!(
476
+ [
477
+ errno("fd_pread", 0),
478
+ errno("fd_pread", 1),
479
+ errno("fd_pwrite", 0),
480
+ errno("fd_pwrite", 1),
481
+ ],
482
+ [Some(70), Some(70), Some(70), Some(70)],
483
+ "fd_pread and fd_pwrite on standard input and output through kobako's preview1 imports \
484
+ must answer ESPIPE, as Linux answers for a pipe's either end"
485
+ );
486
+ }
@@ -0,0 +1,135 @@
1
+ //! What a preview1 call answers when it does not succeed: an error code
2
+ //! the guest reads, or a trap that ends the invocation.
3
+
4
+ use kobako_runtime::filesystem::FilesystemError;
5
+ use wiggle::GuestError;
6
+
7
+ use super::types;
8
+ pub(crate) use super::types::Errno;
9
+
10
+ pub(crate) type Answer<T = ()> = Result<T, types::Error>;
11
+
12
+ /// The code a POSIX error name a Filesystem's failure carries stands for:
13
+ /// the witx case, `E` prefixed and upper-cased.
14
+ fn from_posix_name(name: &str) -> Option<Errno> {
15
+ Some(match name {
16
+ "EAGAIN" => Errno::Again,
17
+ "ENOENT" => Errno::Noent,
18
+ "EEXIST" => Errno::Exist,
19
+ "ENOTDIR" => Errno::Notdir,
20
+ "EISDIR" => Errno::Isdir,
21
+ "ENOTEMPTY" => Errno::Notempty,
22
+ "EACCES" => Errno::Acces,
23
+ "EROFS" => Errno::Rofs,
24
+ "EINVAL" => Errno::Inval,
25
+ "ENOTSUP" => Errno::Notsup,
26
+ "ENOSPC" => Errno::Nospc,
27
+ "EFBIG" => Errno::Fbig,
28
+ "EXDEV" => Errno::Xdev,
29
+ "EDEADLK" => Errno::Deadlk,
30
+ "ELOOP" => Errno::Loop,
31
+ "EPERM" => Errno::Perm,
32
+ "EBADF" => Errno::Badf,
33
+ "EBUSY" => Errno::Busy,
34
+ "EILSEQ" => Errno::Ilseq,
35
+ "EINTR" => Errno::Intr,
36
+ "EMFILE" => Errno::Mfile,
37
+ "ENAMETOOLONG" => Errno::Nametoolong,
38
+ "ENOMEM" => Errno::Nomem,
39
+ "ENOTSOCK" => Errno::Notsock,
40
+ "EOVERFLOW" => Errno::Overflow,
41
+ "ESPIPE" => Errno::Spipe,
42
+ "ENODEV" => Errno::Nodev,
43
+ "ESTALE" => Errno::Stale,
44
+ _ => return None,
45
+ })
46
+ }
47
+
48
+ /// A pointer outside the memory or misaligned traps, as the witx
49
+ /// documentation requires; a value inside memory that no witx type holds is
50
+ /// `INVAL`, as in wasmtime-wasi.
51
+ impl From<GuestError> for types::Error {
52
+ fn from(error: GuestError) -> Self {
53
+ match error {
54
+ GuestError::InvalidFlagValue { .. } | GuestError::InvalidEnumValue { .. } => {
55
+ Errno::Inval.into()
56
+ }
57
+ GuestError::PtrOverflow
58
+ | GuestError::PtrOutOfBounds { .. }
59
+ | GuestError::PtrNotAligned { .. } => Self::trap(error.into()),
60
+ GuestError::InvalidUtf8 { .. } => Errno::Ilseq.into(),
61
+ GuestError::TryFromIntError { .. } => Errno::Overflow.into(),
62
+ GuestError::SliceLengthsDiffer => Errno::Fault.into(),
63
+ GuestError::InFunc { err, .. } => Self::from(*err),
64
+ }
65
+ }
66
+ }
67
+
68
+ /// The code a call's failure reaches the guest as: the one its POSIX name
69
+ /// stands for, or `IO` for a failure without one. A path escaping its
70
+ /// descriptor is `NOTCAPABLE`, as preview1 names a capability the guest does
71
+ /// not hold, where Linux answers EXDEV.
72
+ impl From<FilesystemError> for types::Error {
73
+ fn from(error: FilesystemError) -> Self {
74
+ match error {
75
+ FilesystemError::Escape => Errno::Notcapable,
76
+ error => error
77
+ .posix_name()
78
+ .and_then(from_posix_name)
79
+ .unwrap_or(Errno::Io),
80
+ }
81
+ .into()
82
+ }
83
+ }
84
+
85
+ #[cfg(test)]
86
+ mod tests {
87
+ use super::*;
88
+
89
+ #[test]
90
+ fn each_filesystem_failure_reaches_the_guest_as_its_own_code() {
91
+ let cases = [
92
+ (FilesystemError::NotFound, 44),
93
+ (FilesystemError::AlreadyExists, 20),
94
+ (FilesystemError::NotADirectory, 54),
95
+ (FilesystemError::IsADirectory, 31),
96
+ (FilesystemError::DirectoryNotEmpty, 55),
97
+ (FilesystemError::PermissionDenied, 2),
98
+ (FilesystemError::ReadOnlyFilesystem, 69),
99
+ (FilesystemError::InvalidInput, 28),
100
+ (FilesystemError::Unsupported, 58),
101
+ (FilesystemError::StorageFull, 51),
102
+ (FilesystemError::FileTooLarge, 22),
103
+ (FilesystemError::CrossesDevices, 75),
104
+ (FilesystemError::Escape, 76),
105
+ (FilesystemError::Deadlock, 16),
106
+ (FilesystemError::FilesystemLoop, 32),
107
+ (FilesystemError::NotPermitted, 63),
108
+ (FilesystemError::BadDescriptor, 8),
109
+ (FilesystemError::ResourceBusy, 10),
110
+ (FilesystemError::IllegalSequence, 25),
111
+ (FilesystemError::Interrupted, 27),
112
+ (FilesystemError::WouldBlock, 6),
113
+ (FilesystemError::TooManyOpenFiles, 33),
114
+ (FilesystemError::InvalidFilename, 37),
115
+ (FilesystemError::OutOfMemory, 48),
116
+ (FilesystemError::NotASocket, 57),
117
+ (FilesystemError::ValueTooLarge, 61),
118
+ (FilesystemError::NotSeekable, 70),
119
+ (FilesystemError::NoSuchDevice, 43),
120
+ (FilesystemError::StaleNetworkFileHandle, 72),
121
+ (FilesystemError::Other("x".into()), 29),
122
+ ];
123
+ for (error, code) in cases {
124
+ let label = format!("{error:?}");
125
+ let Ok(errno) = types::Error::from(error).downcast() else {
126
+ panic!("{label} must answer an error code, not a trap");
127
+ };
128
+ assert_eq!(
129
+ u16::from(errno),
130
+ code,
131
+ "{label} through types::Error::from must reach the guest as preview1 errno {code}"
132
+ );
133
+ }
134
+ }
135
+ }