kobako 0.29.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 (201) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +1 -1
  3. data/CHANGELOG.md +217 -10
  4. data/Cargo.lock +139 -141
  5. data/README.md +96 -574
  6. data/ROADMAP.md +2 -1
  7. data/crates/kobako-runtime/CHANGELOG.md +269 -1
  8. data/crates/kobako-runtime/Cargo.toml +25 -15
  9. data/crates/kobako-runtime/README.md +7 -3
  10. data/crates/kobako-runtime/src/dispatch.rs +1 -1
  11. data/crates/kobako-runtime/src/endpoint/buffer.rs +192 -0
  12. data/crates/kobako-runtime/src/endpoint/capture.rs +115 -0
  13. data/crates/kobako-runtime/src/endpoint/dispatch.rs +253 -0
  14. data/crates/kobako-runtime/src/endpoint/invoke.rs +194 -0
  15. data/crates/kobako-runtime/src/endpoint/memory_cap.rs +238 -0
  16. data/crates/kobako-runtime/src/endpoint.rs +166 -0
  17. data/crates/kobako-runtime/src/filesystem/bind/fid.rs +82 -0
  18. data/crates/kobako-runtime/src/filesystem/{directory/spelling.rs → bind/lookup.rs} +91 -49
  19. data/crates/kobako-runtime/src/filesystem/bind/walk.rs +249 -0
  20. data/crates/kobako-runtime/src/filesystem/bind.rs +2665 -0
  21. data/crates/kobako-runtime/src/filesystem/overlay/file.rs +89 -0
  22. data/crates/kobako-runtime/src/filesystem/overlay/inode.rs +106 -0
  23. data/crates/kobako-runtime/src/filesystem/overlay/path.rs +285 -0
  24. data/crates/kobako-runtime/src/filesystem/overlay/stack/copy_up.rs +135 -0
  25. data/crates/kobako-runtime/src/filesystem/overlay/stack/dir.rs +263 -0
  26. data/crates/kobako-runtime/src/filesystem/overlay/stack/namei.rs +135 -0
  27. data/crates/kobako-runtime/src/filesystem/overlay/stack/readdir.rs +57 -0
  28. data/crates/kobako-runtime/src/filesystem/overlay/stack.rs +227 -734
  29. data/crates/kobako-runtime/src/filesystem/overlay.rs +2649 -1135
  30. data/crates/kobako-runtime/src/filesystem/path.rs +2 -41
  31. data/crates/kobako-runtime/src/filesystem/pipe.rs +221 -0
  32. data/crates/kobako-runtime/src/filesystem/rdir.rs +101 -0
  33. data/crates/kobako-runtime/src/filesystem/tmpfs/tmpfile.rs +32 -0
  34. data/crates/kobako-runtime/src/filesystem/tmpfs/whiteout.rs +85 -0
  35. data/crates/kobako-runtime/src/filesystem/tmpfs/xattr.rs +22 -0
  36. data/crates/kobako-runtime/src/filesystem/tmpfs.rs +1985 -0
  37. data/crates/kobako-runtime/src/filesystem/wrap.rs +224 -0
  38. data/crates/kobako-runtime/src/filesystem.rs +21 -387
  39. data/crates/kobako-runtime/src/hashtable.rs +44 -0
  40. data/crates/kobako-runtime/src/lib.rs +11 -6
  41. data/crates/kobako-runtime/src/locking/seqlock.rs +66 -0
  42. data/crates/kobako-runtime/src/locking.rs +218 -0
  43. data/crates/kobako-runtime/src/mount.rs +31 -104
  44. data/crates/kobako-runtime/src/profile.rs +137 -1
  45. data/crates/kobako-runtime/src/sched.rs +210 -0
  46. data/crates/kobako-runtime/src/security.rs +696 -0
  47. data/crates/kobako-runtime/src/vfs/attr.rs +162 -0
  48. data/crates/kobako-runtime/src/vfs/cred.rs +334 -0
  49. data/crates/kobako-runtime/src/vfs/dentry.rs +1412 -0
  50. data/crates/kobako-runtime/src/vfs/errno.rs +153 -0
  51. data/crates/kobako-runtime/src/vfs/file.rs +1915 -0
  52. data/crates/kobako-runtime/src/vfs/inode.rs +429 -0
  53. data/crates/kobako-runtime/src/vfs/kern_path.rs +217 -0
  54. data/crates/kobako-runtime/src/vfs/mount.rs +59 -0
  55. data/crates/kobako-runtime/src/vfs/namei.rs +5708 -0
  56. data/crates/kobako-runtime/src/vfs/openat2.rs +116 -0
  57. data/crates/kobako-runtime/src/vfs/path.rs +64 -0
  58. data/crates/kobako-runtime/src/vfs/readdir.rs +163 -0
  59. data/crates/kobako-runtime/src/vfs/stat.rs +157 -0
  60. data/crates/kobako-runtime/src/vfs/super_block.rs +122 -0
  61. data/crates/kobako-runtime/src/vfs/utimes.rs +289 -0
  62. data/crates/kobako-runtime/src/vfs.rs +60 -0
  63. data/crates/kobako-runtime/tests/linux_baseline/calls.rs +171 -0
  64. data/crates/kobako-runtime/tests/linux_baseline/concurrent.rs +276 -0
  65. data/crates/kobako-runtime/tests/linux_baseline/kobako.rs +323 -0
  66. data/crates/kobako-runtime/tests/linux_baseline/linux-baseline-expected.txt +16 -0
  67. data/crates/kobako-runtime/tests/linux_baseline/linux.rs +449 -0
  68. data/crates/kobako-runtime/tests/linux_baseline/main.rs +611 -0
  69. data/crates/kobako-runtime/tests/linux_baseline/permissions.rs +236 -0
  70. data/crates/kobako-transport/CHANGELOG.md +8 -2
  71. data/crates/kobako-transport/Cargo.toml +3 -3
  72. data/crates/kobako-transport/README.md +6 -5
  73. data/crates/kobako-transport/src/envelope/bytes.rs +33 -25
  74. data/crates/kobako-transport/src/envelope/call.rs +5 -5
  75. data/crates/kobako-transport/src/envelope/compile_reply.rs +2 -2
  76. data/crates/kobako-transport/src/envelope/error_record.rs +3 -3
  77. data/crates/kobako-transport/src/envelope/fault.rs +2 -2
  78. data/crates/kobako-transport/src/envelope/outcome.rs +3 -3
  79. data/crates/kobako-transport/src/envelope/preamble.rs +12 -12
  80. data/crates/kobako-transport/src/envelope/reply.rs +2 -2
  81. data/crates/kobako-transport/src/envelope/request.rs +2 -2
  82. data/crates/kobako-transport/src/envelope/run.rs +1 -1
  83. data/crates/kobako-transport/src/envelope/unsupported.rs +2 -2
  84. data/crates/kobako-transport/src/envelope.rs +2 -2
  85. data/crates/kobako-wasmtime/CHANGELOG.md +154 -8
  86. data/crates/kobako-wasmtime/Cargo.toml +15 -14
  87. data/crates/kobako-wasmtime/README.md +9 -5
  88. data/crates/kobako-wasmtime/src/abi.rs +12 -20
  89. data/crates/kobako-wasmtime/src/ambient.rs +1 -2
  90. data/crates/kobako-wasmtime/src/cache.rs +5 -5
  91. data/crates/kobako-wasmtime/src/config.rs +22 -7
  92. data/crates/kobako-wasmtime/src/descriptors.rs +133 -100
  93. data/crates/kobako-wasmtime/src/driver.rs +29 -96
  94. data/crates/kobako-wasmtime/src/exports.rs +5 -16
  95. data/crates/kobako-wasmtime/src/guest.rs +38 -0
  96. data/crates/kobako-wasmtime/src/imports.rs +14 -8
  97. data/crates/kobako-wasmtime/src/instance_pre.rs +3 -3
  98. data/crates/kobako-wasmtime/src/invocation.rs +26 -46
  99. data/crates/kobako-wasmtime/src/lib.rs +1 -4
  100. data/crates/kobako-wasmtime/src/limiter.rs +6 -230
  101. data/crates/kobako-wasmtime/src/preview1/differential/mount.rs +69 -46
  102. data/crates/kobako-wasmtime/src/preview1/differential.rs +121 -7
  103. data/crates/kobako-wasmtime/src/preview1/errno.rs +74 -206
  104. data/crates/kobako-wasmtime/src/preview1/fd.rs +1248 -500
  105. data/crates/kobako-wasmtime/src/preview1/memory.rs +117 -150
  106. data/crates/kobako-wasmtime/src/preview1/mount_view/wrap.rs +211 -0
  107. data/crates/kobako-wasmtime/src/preview1/mount_view.rs +1939 -672
  108. data/crates/kobako-wasmtime/src/preview1/path.rs +230 -349
  109. data/crates/kobako-wasmtime/src/preview1/poll.rs +148 -100
  110. data/crates/kobako-wasmtime/src/preview1/sock.rs +6 -23
  111. data/crates/kobako-wasmtime/src/preview1/testsuite-expected.txt +6 -0
  112. data/crates/kobako-wasmtime/src/preview1/testsuite.rs +157 -0
  113. data/crates/kobako-wasmtime/src/preview1.rs +464 -413
  114. data/crates/kobako-wasmtime/src/trap.rs +9 -9
  115. data/crates/kobako-wasmtime/src/wasi.rs +53 -37
  116. data/crates/kobako-wasmtime/tests/{guest_file.rs → integration/guest_file.rs} +232 -201
  117. data/crates/kobako-wasmtime/tests/integration/main.rs +29 -0
  118. data/crates/kobako-wasmtime/tests/{preamble_flags.rs → integration/preamble_flags.rs} +1 -1
  119. data/crates/kobako-wasmtime/tests/{snippet_compile.rs → integration/snippet_compile.rs} +1 -1
  120. data/crates/kobako-wasmtime/tests/{unanswered_call.rs → integration/unanswered_call.rs} +1 -1
  121. data/crates/kobako-wasmtime/tests/{unsupported_call.rs → integration/unsupported_call.rs} +1 -1
  122. data/crates/kobako-wasmtime/witx/typenames.witx +750 -0
  123. data/crates/kobako-wasmtime/witx/wasi_snapshot_preview1.witx +441 -0
  124. data/data/kobako.wasm +0 -0
  125. data/ext/kobako/Cargo.toml +1 -1
  126. data/ext/kobako/src/runtime/bridge.rs +1 -1
  127. data/ext/kobako/src/runtime/errors.rs +1 -1
  128. data/ext/kobako/src/runtime/escape.rs +28 -14
  129. data/ext/kobako/src/runtime/filesystem/grants.rs +21 -5
  130. data/ext/kobako/src/runtime/filesystem/ruby.rs +464 -156
  131. data/ext/kobako/src/runtime/filesystem.rs +109 -137
  132. data/ext/kobako/src/runtime/gvl.rs +43 -8
  133. data/ext/kobako/src/runtime/snapshot.rs +1 -1
  134. data/ext/kobako/src/runtime.rs +6 -1
  135. data/lib/kobako/catalog/extensions.rb +5 -5
  136. data/lib/kobako/catalog/flags.rb +2 -2
  137. data/lib/kobako/catalog/handles.rb +6 -6
  138. data/lib/kobako/catalog/mount.rb +2 -10
  139. data/lib/kobako/catalog/services.rb +2 -2
  140. data/lib/kobako/catalog/snippets.rb +7 -7
  141. data/lib/kobako/catalog.rb +1 -1
  142. data/lib/kobako/codec/decoder.rb +10 -10
  143. data/lib/kobako/codec/ext_types.rb +3 -5
  144. data/lib/kobako/codec/handle_walk.rb +4 -6
  145. data/lib/kobako/codec/nesting.rb +7 -7
  146. data/lib/kobako/codec/utils.rb +1 -1
  147. data/lib/kobako/codec/value_budget.rb +3 -3
  148. data/lib/kobako/context.rb +5 -5
  149. data/lib/kobako/filesystem.rb +44 -13
  150. data/lib/kobako/payload/arguments.rb +2 -2
  151. data/lib/kobako/pool.rb +4 -4
  152. data/lib/kobako/sandbox.rb +28 -28
  153. data/lib/kobako/sandbox_options.rb +2 -3
  154. data/lib/kobako/snippet/bytecode.rb +1 -1
  155. data/lib/kobako/snippet/source.rb +1 -1
  156. data/lib/kobako/transport/dispatcher.rb +5 -5
  157. data/lib/kobako/transport/exposure.rb +2 -2
  158. data/lib/kobako/transport/reflection.rb +7 -7
  159. data/lib/kobako/transport/run.rb +5 -5
  160. data/lib/kobako/transport/yielder.rb +1 -1
  161. data/lib/kobako/version.rb +1 -1
  162. data/lib/kobako.rb +34 -0
  163. data/release-please-config.json +1 -0
  164. data/sig/kobako/catalog/extensions.rbs +3 -3
  165. data/sig/kobako/catalog/handles.rbs +3 -3
  166. data/sig/kobako/catalog/mount.rbs +0 -4
  167. data/sig/kobako/catalog/services.rbs +1 -1
  168. data/sig/kobako/catalog/snippets.rbs +3 -3
  169. data/sig/kobako/catalog.rbs +1 -1
  170. data/sig/kobako/codec/decoder.rbs +1 -1
  171. data/sig/kobako/codec/handle_walk.rbs +1 -1
  172. data/sig/kobako/codec/nesting.rbs +2 -2
  173. data/sig/kobako/codec/utils.rbs +1 -1
  174. data/sig/kobako/codec/value_budget.rbs +1 -1
  175. data/sig/kobako/codec.rbs +1 -2
  176. data/sig/kobako/context.rbs +1 -1
  177. data/sig/kobako/filesystem.rbs +19 -14
  178. data/sig/kobako/guest_yielder.rbs +1 -1
  179. data/sig/kobako/payload/arguments.rbs +1 -1
  180. data/sig/kobako/pool.rbs +2 -2
  181. data/sig/kobako/runtime.rbs +1 -1
  182. data/sig/kobako/sandbox.rbs +3 -5
  183. data/sig/kobako/sandbox_options.rbs +1 -1
  184. data/sig/kobako/transport/dispatcher.rbs +2 -2
  185. data/sig/kobako/transport/exposure.rbs +1 -1
  186. data/sig/kobako/transport/reflection.rbs +2 -2
  187. data/sig/kobako/transport/run.rbs +3 -3
  188. data/sig/kobako/transport.rbs +1 -1
  189. metadata +67 -19
  190. data/crates/kobako-runtime/src/filesystem/directory.rs +0 -1082
  191. data/crates/kobako-runtime/src/filesystem/memory.rs +0 -1100
  192. data/crates/kobako-runtime/src/filesystem/overlay/exclusive.rs +0 -88
  193. data/crates/kobako-runtime/src/filesystem/temporary.rs +0 -167
  194. data/crates/kobako-wasmtime/src/capture.rs +0 -96
  195. data/crates/kobako-wasmtime/src/dispatch.rs +0 -125
  196. data/crates/kobako-wasmtime/src/guest_mem.rs +0 -271
  197. data/crates/kobako-wasmtime/src/io.rs +0 -89
  198. data/crates/kobako-wasmtime/src/preview1/args.rs +0 -60
  199. data/crates/kobako-wasmtime/src/preview1/beneath.rs +0 -188
  200. /data/crates/kobako-wasmtime/tests/{compile_posture.rs → integration/compile_posture.rs} +0 -0
  201. /data/crates/kobako-wasmtime/tests/{support → integration/support}/mod.rs +0 -0
@@ -1,88 +0,0 @@
1
- //! The lock an Overlay holds across each call, so a change that touches
2
- //! several Layers is never seen, nor interleaved with, half made, as the
3
- //! directory's lock overlayfs's changes run under.
4
-
5
- use std::sync::{Arc, Condvar, Mutex, MutexGuard};
6
- use std::thread::{self, ThreadId};
7
-
8
- use super::super::FilesystemError;
9
-
10
- /// How a thread waits for a lock another thread holds. A frontend whose
11
- /// threads share a lock of their own, as Ruby's threads share the GVL,
12
- /// gives that lock up for the wait, or a holder that needs it to finish
13
- /// never could.
14
- pub trait Blocking: Send + Sync {
15
- /// Run `wait`, which returns once the lock is taken.
16
- fn wait(&self, wait: &mut dyn FnMut());
17
- }
18
-
19
- /// Waiting with nothing else to give up.
20
- pub(super) struct Inline;
21
-
22
- impl Blocking for Inline {
23
- fn wait(&self, wait: &mut dyn FnMut()) {
24
- wait();
25
- }
26
- }
27
-
28
- /// Held by one thread at a time. The thread holding it asking again is
29
- /// refused as a deadlock, as `fcntl` refuses a lock that could never be
30
- /// granted.
31
- pub(super) struct Exclusive {
32
- holder: Mutex<Option<ThreadId>>,
33
- freed: Condvar,
34
- blocking: Arc<dyn Blocking>,
35
- }
36
-
37
- impl Exclusive {
38
- pub(super) fn new(blocking: Arc<dyn Blocking>) -> Self {
39
- Self {
40
- holder: Mutex::new(None),
41
- freed: Condvar::new(),
42
- blocking,
43
- }
44
- }
45
-
46
- pub(super) fn blocking(&self) -> Arc<dyn Blocking> {
47
- self.blocking.clone()
48
- }
49
-
50
- fn holder(&self) -> MutexGuard<'_, Option<ThreadId>> {
51
- self.holder
52
- .lock()
53
- .unwrap_or_else(|poisoned| poisoned.into_inner())
54
- }
55
-
56
- pub(super) fn hold(&self) -> Result<Held<'_>, FilesystemError> {
57
- let me = thread::current().id();
58
- let mut holder = self.holder();
59
- match *holder {
60
- Some(held) if held == me => return Err(FilesystemError::Deadlock),
61
- Some(_) => {
62
- drop(holder);
63
- self.blocking.wait(&mut || {
64
- let mut holder = self.holder();
65
- while holder.is_some() {
66
- holder = self
67
- .freed
68
- .wait(holder)
69
- .unwrap_or_else(|poisoned| poisoned.into_inner());
70
- }
71
- *holder = Some(me);
72
- });
73
- }
74
- None => *holder = Some(me),
75
- }
76
- Ok(Held(self))
77
- }
78
- }
79
-
80
- /// The lock while one call holds it, freed as the call returns.
81
- pub(super) struct Held<'a>(&'a Exclusive);
82
-
83
- impl Drop for Held<'_> {
84
- fn drop(&mut self) {
85
- *self.0.holder() = None;
86
- self.0.freed.notify_one();
87
- }
88
- }
@@ -1,167 +0,0 @@
1
- //! `Temporary`: a Filesystem held in memory that every Invocation starts
2
- //! empty.
3
-
4
- use std::sync::Arc;
5
-
6
- use super::memory::Memory;
7
- use super::{DirEntry, File, Filesystem, FilesystemError, Hold, Metadata, OpenOptions, Rename};
8
-
9
- /// Files and directories held in memory for one Invocation. The next one
10
- /// is handed a new, empty Temporary in its place, so nothing one writes
11
- /// reaches another. Each one holds as much as a Memory does.
12
- #[derive(Default)]
13
- pub struct Temporary {
14
- memory: Memory,
15
- }
16
-
17
- impl Temporary {
18
- pub fn new() -> Self {
19
- Self::default()
20
- }
21
-
22
- /// This Temporary, and every one handed out in its place, with its
23
- /// files holding at most `limit` bytes together, or without bound for
24
- /// `None`, as `Memory::with_limit` bounds a Memory.
25
- pub fn with_limit(self, limit: Option<usize>) -> Self {
26
- Self {
27
- memory: self.memory.with_limit(limit),
28
- }
29
- }
30
-
31
- /// This Temporary, and every one handed out in its place, holding at
32
- /// most `inodes` entries, as `Memory::with_inodes` bounds a Memory.
33
- pub fn with_inodes(self, inodes: Option<usize>) -> Self {
34
- Self {
35
- memory: self.memory.with_inodes(inodes),
36
- }
37
- }
38
- }
39
-
40
- impl Filesystem for Temporary {
41
- fn open(&self, path: &str, options: &OpenOptions) -> Result<Box<dyn File>, FilesystemError> {
42
- self.memory.open(path, options)
43
- }
44
-
45
- fn metadata(&self, path: &str) -> Result<Metadata, FilesystemError> {
46
- self.memory.metadata(path)
47
- }
48
-
49
- fn symlink_metadata(&self, path: &str) -> Result<Metadata, FilesystemError> {
50
- self.memory.symlink_metadata(path)
51
- }
52
-
53
- fn stored_path(&self, path: &str) -> Result<String, FilesystemError> {
54
- self.memory.stored_path(path)
55
- }
56
-
57
- fn canonicalize_beneath(&self, base: &str, path: &str) -> Result<String, FilesystemError> {
58
- self.memory.canonicalize_beneath(base, path)
59
- }
60
-
61
- fn for_policy(&self) -> Option<Arc<dyn Filesystem>> {
62
- self.memory.for_policy()
63
- }
64
-
65
- fn hold_directory(&self, path: &str) -> Result<Option<Hold>, FilesystemError> {
66
- self.memory.hold_directory(path)
67
- }
68
-
69
- fn read_dir(&self, path: &str) -> Result<Vec<DirEntry>, FilesystemError> {
70
- self.memory.read_dir(path)
71
- }
72
-
73
- fn create_dir(&self, path: &str) -> Result<(), FilesystemError> {
74
- self.memory.create_dir(path)
75
- }
76
-
77
- fn remove_dir(&self, path: &str) -> Result<(), FilesystemError> {
78
- self.memory.remove_dir(path)
79
- }
80
-
81
- fn remove_file(&self, path: &str) -> Result<(), FilesystemError> {
82
- self.memory.remove_file(path)
83
- }
84
-
85
- fn rename(&self, from: &str, to: &str, rename: Rename) -> Result<(), FilesystemError> {
86
- self.memory.rename(from, to, rename)
87
- }
88
-
89
- fn for_invocation(&self) -> Option<Arc<dyn Filesystem>> {
90
- Some(Arc::new(Self {
91
- memory: self.memory.to_empty(),
92
- }))
93
- }
94
- }
95
-
96
- #[cfg(test)]
97
- mod tests {
98
- use super::*;
99
-
100
- // @behavior FS-004
101
- #[test]
102
- fn a_temporary_starts_every_invocation_empty() {
103
- let temporary = Temporary::new();
104
- temporary
105
- .open(
106
- "scratch",
107
- &OpenOptions {
108
- write: true,
109
- create: true,
110
- ..OpenOptions::default()
111
- },
112
- )
113
- .expect("a new file must be created")
114
- .write_at(b"gone", 0)
115
- .expect("the write must land");
116
-
117
- let next = temporary
118
- .for_invocation()
119
- .expect("a Temporary must hand the next Invocation a Filesystem of its own");
120
- assert_eq!(
121
- next.read_dir("").expect("the root must list"),
122
- Vec::new(),
123
- "Temporary::for_invocation must hand the next Invocation a Filesystem holding no file"
124
- );
125
- }
126
-
127
- // @behavior FS-033
128
- #[test]
129
- fn a_temporary_keeps_its_inodes_in_every_invocation() {
130
- let next = Temporary::new()
131
- .with_inodes(Some(1))
132
- .for_invocation()
133
- .expect("a Temporary must hand the next Invocation a Filesystem of its own");
134
- assert_eq!(
135
- next.create_dir("dir"),
136
- Err(FilesystemError::StorageFull),
137
- "a directory past a Temporary's inodes in the Filesystem Temporary::for_invocation \
138
- hands the next Invocation must answer StorageFull"
139
- );
140
- }
141
-
142
- // @behavior FS-027
143
- #[test]
144
- fn a_temporary_keeps_its_limit_in_every_invocation() {
145
- let next = Temporary::new()
146
- .with_limit(Some(4))
147
- .for_invocation()
148
- .expect("a Temporary must hand the next Invocation a Filesystem of its own");
149
- let written = next
150
- .open(
151
- "scratch",
152
- &OpenOptions {
153
- write: true,
154
- create: true,
155
- ..OpenOptions::default()
156
- },
157
- )
158
- .expect("a new file must be created")
159
- .write_at(b"five!", 0);
160
- assert_eq!(
161
- written,
162
- Err(FilesystemError::StorageFull),
163
- "a write past a Temporary's limit in the Filesystem Temporary::for_invocation hands \
164
- the next Invocation must answer StorageFull"
165
- );
166
- }
167
- }
@@ -1,96 +0,0 @@
1
- //! Per-channel stdout / stderr capture sizing and clipping.
2
- //!
3
- //! Two pure helpers shared by the run path: one
4
- //! sizes the per-run `MemoryOutputPipe`, the other clips a captured
5
- //! snapshot back to the configured cap and reports whether the cap was
6
- //! exceeded. Kept channel-agnostic (a function of `cap`, not of which
7
- //! channel) so a regression that only breaks one channel cannot sneak
8
- //! through the test that pins them.
9
-
10
- /// One byte past the cap, so "wrote exactly cap" and "exceeded cap" stay
11
- /// distinguishable.
12
- pub(crate) fn pipe_capacity(cap: Option<usize>) -> usize {
13
- match cap {
14
- Some(c) => c.saturating_add(1),
15
- None => usize::MAX,
16
- }
17
- }
18
-
19
- /// Takes the snapshot by value and truncates in place, so the readback
20
- /// costs one copy out of the pipe rather than two.
21
- pub(crate) fn clip_capture(mut raw: Vec<u8>, cap: Option<usize>) -> (Vec<u8>, bool) {
22
- match cap {
23
- Some(c) if raw.len() > c => {
24
- raw.truncate(c);
25
- (raw, true)
26
- }
27
- _ => (raw, false),
28
- }
29
- }
30
-
31
- #[cfg(test)]
32
- mod tests {
33
- use super::{clip_capture, pipe_capacity};
34
-
35
- // @behavior S-108
36
- #[test]
37
- fn pipe_capacity_adds_one_when_cap_is_set() {
38
- assert_eq!(pipe_capacity(Some(5)), 6);
39
- assert_eq!(pipe_capacity(Some(0)), 1);
40
- }
41
-
42
- // @behavior S-109
43
- #[test]
44
- fn pipe_capacity_falls_back_to_usize_max_when_uncapped() {
45
- assert_eq!(pipe_capacity(None), usize::MAX);
46
- }
47
-
48
- // @behavior S-110
49
- #[test]
50
- fn pipe_capacity_saturates_at_usize_max() {
51
- assert_eq!(pipe_capacity(Some(usize::MAX)), usize::MAX);
52
- }
53
-
54
- // @behavior S-111
55
- #[test]
56
- fn clip_capture_returns_full_bytes_when_under_cap() {
57
- let (bytes, truncated) = clip_capture(b"abc".to_vec(), Some(5));
58
- assert_eq!(bytes, b"abc");
59
- assert!(!truncated);
60
- }
61
-
62
- // @behavior S-112
63
- #[test]
64
- fn clip_capture_does_not_flag_truncation_at_exactly_cap_bytes() {
65
- let (bytes, truncated) = clip_capture(b"abcde".to_vec(), Some(5));
66
- assert_eq!(bytes, b"abcde");
67
- assert!(!truncated);
68
- }
69
-
70
- // @behavior S-024
71
- #[test]
72
- fn clip_capture_clips_to_cap_and_flags_truncation_on_overflow() {
73
- // The pipe is sized `cap + 1`, so the snapshot can be at most
74
- // 6 bytes when `cap == 5`; that surface is what triggers the
75
- // truncation flag.
76
- let (bytes, truncated) = clip_capture(b"abcdef".to_vec(), Some(5));
77
- assert_eq!(bytes, b"abcde");
78
- assert!(truncated);
79
- }
80
-
81
- // @behavior S-033
82
- #[test]
83
- fn clip_capture_treats_none_as_uncapped() {
84
- let (bytes, truncated) = clip_capture(b"abcdef".to_vec(), None);
85
- assert_eq!(bytes, b"abcdef");
86
- assert!(!truncated);
87
- }
88
-
89
- // @behavior S-100 S-101
90
- #[test]
91
- fn clip_capture_handles_empty_input() {
92
- let (bytes, truncated) = clip_capture(Vec::new(), Some(5));
93
- assert_eq!(bytes, b"");
94
- assert!(!truncated);
95
- }
96
- }
@@ -1,125 +0,0 @@
1
- //! Host side of the `__kobako_upcall` import: hand the guest's Call to
2
- //! the bound `DispatchHandler` and write its Reply back into guest memory.
3
- //! A request under a tag the host does not serve is answered as
4
- //! unsupported, so a guest asking for a later operation can fall back.
5
- //!
6
- //! A failure lands on whoever is answerable for it, and always says why.
7
- //! A trap the guest raises during a callback into it ends the invocation
8
- //! as that trap. A Call the host could not answer is answered as an
9
- //! internal failure naming why, and an answer too large for a message as
10
- //! the Service's own failure. Only an answer that cannot be written into
11
- //! guest memory at all ends the invocation, as a trap naming why.
12
-
13
- use wasmtime::Caller;
14
-
15
- use kobako_runtime::error::Trap;
16
- use kobako_runtime::yielder::Yielder;
17
- use kobako_transport::abi::pack_ptr_len;
18
- use kobako_transport::envelope::{Fault, FaultKind, Reply, Unsupported, Upcall, YieldReply};
19
-
20
- use crate::guest_mem;
21
- use crate::invocation::Invocation;
22
-
23
- /// The wasmtime-backed `Yielder`: built per `__kobako_upcall` frame over
24
- /// that frame's `Caller`, so nested dispatch frames each carry their own and
25
- /// stack on the Rust call stack with no shared slot.
26
- struct CallerYielder<'a, 'c> {
27
- caller: &'a mut Caller<'c, Invocation>,
28
- frame: u32,
29
- }
30
-
31
- impl Yielder for CallerYielder<'_, '_> {
32
- fn frame(&self) -> u32 {
33
- self.frame
34
- }
35
-
36
- fn yield_to_block(&mut self, frame: u32, args: &[u8]) -> Result<YieldReply, Trap> {
37
- let bytes = guest_mem::drive_yield(self.caller, frame, args)
38
- .map_err(|msg| Trap::Other(msg.to_string()))?;
39
- YieldReply::decode(&bytes).map_err(|err| Trap::Other(err.to_string()))
40
- }
41
- }
42
-
43
- /// Answer one `__kobako_upcall` call with the packed `(ptr<<32)|len` of
44
- /// the Reply, or the trap a callback into the guest raised.
45
- pub(crate) fn handle(
46
- caller: &mut Caller<'_, Invocation>,
47
- req_ptr: i32,
48
- req_len: i32,
49
- ) -> wasmtime::Result<i64> {
50
- let reply = reply_to(caller, req_ptr, req_len)
51
- .unwrap_or_else(|reason| Reply::Fault(Fault::new(FaultKind::Internal, reason)));
52
- let written = write_reply(caller, &fit_in_message(reply).encode());
53
- if let Some(trap) = caller.data_mut().take_reentry_trap() {
54
- return Err(trap);
55
- }
56
- if let Some(trap) = caller.data().abandonment() {
57
- return Err(trap);
58
- }
59
- written.map_err(wasmtime::Error::msg)
60
- }
61
-
62
- /// An answer past the message cap is the Service's to change, as any other
63
- /// answer the host cannot write is.
64
- fn fit_in_message(reply: Reply) -> Reply {
65
- let bytes = reply.encode().len();
66
- match guest_mem::checked_payload_len(bytes) {
67
- Ok(_) => reply,
68
- Err(limit) => Reply::Fault(Fault::new(
69
- FaultKind::Runtime,
70
- format!("Sandbox could not write the Service's answer: {limit}"),
71
- )),
72
- }
73
- }
74
-
75
- fn reply_to(
76
- caller: &mut Caller<'_, Invocation>,
77
- req_ptr: i32,
78
- req_len: i32,
79
- ) -> Result<Reply, &'static str> {
80
- let req_bytes = guest_mem::read_request(caller, req_ptr, req_len)?;
81
- // The driver decodes the core envelope, here and in `CallerYielder`, so
82
- // the frontend never sees a frame; the payload inside it stays bytes
83
- // the whole way through.
84
- let call = match Upcall::decode(&req_bytes) {
85
- Ok(Upcall::Call(call)) => call,
86
- Ok(_) => {
87
- return Ok(Reply::Unsupported(Unsupported::new(
88
- "the host does not serve this request",
89
- )))
90
- }
91
- Err(_) => {
92
- return Err(
93
- "the guest sent a malformed Call envelope — please report this as a kobako bug",
94
- )
95
- }
96
- };
97
-
98
- // `Kobako::Sandbox` always installs the dispatch handler before
99
- // invoking the runtime, so reaching this branch indicates a misuse
100
- // rather than a normal control path.
101
- let handler = caller
102
- .data()
103
- .on_dispatch()
104
- .ok_or("a Sandbox callback fired outside an active invocation — please report this as a kobako bug")?;
105
-
106
- // Build a frame-scoped yielder over this Caller and hand it to the
107
- // handler. The borrow ends with the block, freeing the Caller for
108
- // `write_reply`; nested dispatch frames each build their own, so
109
- // the LIFO re-entry lives on the Rust stack — no shared slot.
110
- let frame = caller.data_mut().open_call();
111
- let reply = {
112
- let mut yielder = CallerYielder { caller, frame };
113
- handler.dispatch(call, &mut yielder)
114
- };
115
- caller.data_mut().close_call();
116
- reply.ok_or(
117
- "a Sandbox callback raised an exception instead of returning a fault — please report this as a kobako bug",
118
- )
119
- }
120
-
121
- fn write_reply(caller: &mut Caller<'_, Invocation>, bytes: &[u8]) -> Result<i64, &'static str> {
122
- let ptr = guest_mem::write_from_callback(caller, bytes)?;
123
- // The write has already held the length to the payload cap.
124
- Ok(pack_ptr_len(ptr, bytes.len() as u32) as i64)
125
- }