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.
- checksums.yaml +4 -4
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +217 -10
- data/Cargo.lock +139 -141
- data/README.md +96 -574
- data/ROADMAP.md +2 -1
- data/crates/kobako-runtime/CHANGELOG.md +269 -1
- data/crates/kobako-runtime/Cargo.toml +25 -15
- data/crates/kobako-runtime/README.md +7 -3
- data/crates/kobako-runtime/src/dispatch.rs +1 -1
- data/crates/kobako-runtime/src/endpoint/buffer.rs +192 -0
- data/crates/kobako-runtime/src/endpoint/capture.rs +115 -0
- data/crates/kobako-runtime/src/endpoint/dispatch.rs +253 -0
- data/crates/kobako-runtime/src/endpoint/invoke.rs +194 -0
- data/crates/kobako-runtime/src/endpoint/memory_cap.rs +238 -0
- data/crates/kobako-runtime/src/endpoint.rs +166 -0
- data/crates/kobako-runtime/src/filesystem/bind/fid.rs +82 -0
- data/crates/kobako-runtime/src/filesystem/{directory/spelling.rs → bind/lookup.rs} +91 -49
- data/crates/kobako-runtime/src/filesystem/bind/walk.rs +249 -0
- data/crates/kobako-runtime/src/filesystem/bind.rs +2665 -0
- data/crates/kobako-runtime/src/filesystem/overlay/file.rs +89 -0
- data/crates/kobako-runtime/src/filesystem/overlay/inode.rs +106 -0
- data/crates/kobako-runtime/src/filesystem/overlay/path.rs +285 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/copy_up.rs +135 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/dir.rs +263 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/namei.rs +135 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/readdir.rs +57 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack.rs +227 -734
- data/crates/kobako-runtime/src/filesystem/overlay.rs +2649 -1135
- data/crates/kobako-runtime/src/filesystem/path.rs +2 -41
- data/crates/kobako-runtime/src/filesystem/pipe.rs +221 -0
- data/crates/kobako-runtime/src/filesystem/rdir.rs +101 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs/tmpfile.rs +32 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs/whiteout.rs +85 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs/xattr.rs +22 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs.rs +1985 -0
- data/crates/kobako-runtime/src/filesystem/wrap.rs +224 -0
- data/crates/kobako-runtime/src/filesystem.rs +21 -387
- data/crates/kobako-runtime/src/hashtable.rs +44 -0
- data/crates/kobako-runtime/src/lib.rs +11 -6
- data/crates/kobako-runtime/src/locking/seqlock.rs +66 -0
- data/crates/kobako-runtime/src/locking.rs +218 -0
- data/crates/kobako-runtime/src/mount.rs +31 -104
- data/crates/kobako-runtime/src/profile.rs +137 -1
- data/crates/kobako-runtime/src/sched.rs +210 -0
- data/crates/kobako-runtime/src/security.rs +696 -0
- data/crates/kobako-runtime/src/vfs/attr.rs +162 -0
- data/crates/kobako-runtime/src/vfs/cred.rs +334 -0
- data/crates/kobako-runtime/src/vfs/dentry.rs +1412 -0
- data/crates/kobako-runtime/src/vfs/errno.rs +153 -0
- data/crates/kobako-runtime/src/vfs/file.rs +1915 -0
- data/crates/kobako-runtime/src/vfs/inode.rs +429 -0
- data/crates/kobako-runtime/src/vfs/kern_path.rs +217 -0
- data/crates/kobako-runtime/src/vfs/mount.rs +59 -0
- data/crates/kobako-runtime/src/vfs/namei.rs +5708 -0
- data/crates/kobako-runtime/src/vfs/openat2.rs +116 -0
- data/crates/kobako-runtime/src/vfs/path.rs +64 -0
- data/crates/kobako-runtime/src/vfs/readdir.rs +163 -0
- data/crates/kobako-runtime/src/vfs/stat.rs +157 -0
- data/crates/kobako-runtime/src/vfs/super_block.rs +122 -0
- data/crates/kobako-runtime/src/vfs/utimes.rs +289 -0
- data/crates/kobako-runtime/src/vfs.rs +60 -0
- data/crates/kobako-runtime/tests/linux_baseline/calls.rs +171 -0
- data/crates/kobako-runtime/tests/linux_baseline/concurrent.rs +276 -0
- data/crates/kobako-runtime/tests/linux_baseline/kobako.rs +323 -0
- data/crates/kobako-runtime/tests/linux_baseline/linux-baseline-expected.txt +16 -0
- data/crates/kobako-runtime/tests/linux_baseline/linux.rs +449 -0
- data/crates/kobako-runtime/tests/linux_baseline/main.rs +611 -0
- data/crates/kobako-runtime/tests/linux_baseline/permissions.rs +236 -0
- data/crates/kobako-transport/CHANGELOG.md +8 -2
- data/crates/kobako-transport/Cargo.toml +3 -3
- data/crates/kobako-transport/README.md +6 -5
- data/crates/kobako-transport/src/envelope/bytes.rs +33 -25
- data/crates/kobako-transport/src/envelope/call.rs +5 -5
- data/crates/kobako-transport/src/envelope/compile_reply.rs +2 -2
- data/crates/kobako-transport/src/envelope/error_record.rs +3 -3
- data/crates/kobako-transport/src/envelope/fault.rs +2 -2
- data/crates/kobako-transport/src/envelope/outcome.rs +3 -3
- data/crates/kobako-transport/src/envelope/preamble.rs +12 -12
- data/crates/kobako-transport/src/envelope/reply.rs +2 -2
- data/crates/kobako-transport/src/envelope/request.rs +2 -2
- data/crates/kobako-transport/src/envelope/run.rs +1 -1
- data/crates/kobako-transport/src/envelope/unsupported.rs +2 -2
- data/crates/kobako-transport/src/envelope.rs +2 -2
- data/crates/kobako-wasmtime/CHANGELOG.md +154 -8
- data/crates/kobako-wasmtime/Cargo.toml +15 -14
- data/crates/kobako-wasmtime/README.md +9 -5
- data/crates/kobako-wasmtime/src/abi.rs +12 -20
- data/crates/kobako-wasmtime/src/ambient.rs +1 -2
- data/crates/kobako-wasmtime/src/cache.rs +5 -5
- data/crates/kobako-wasmtime/src/config.rs +22 -7
- data/crates/kobako-wasmtime/src/descriptors.rs +133 -100
- data/crates/kobako-wasmtime/src/driver.rs +29 -96
- data/crates/kobako-wasmtime/src/exports.rs +5 -16
- data/crates/kobako-wasmtime/src/guest.rs +38 -0
- data/crates/kobako-wasmtime/src/imports.rs +14 -8
- data/crates/kobako-wasmtime/src/instance_pre.rs +3 -3
- data/crates/kobako-wasmtime/src/invocation.rs +26 -46
- data/crates/kobako-wasmtime/src/lib.rs +1 -4
- data/crates/kobako-wasmtime/src/limiter.rs +6 -230
- data/crates/kobako-wasmtime/src/preview1/differential/mount.rs +69 -46
- data/crates/kobako-wasmtime/src/preview1/differential.rs +121 -7
- data/crates/kobako-wasmtime/src/preview1/errno.rs +74 -206
- data/crates/kobako-wasmtime/src/preview1/fd.rs +1248 -500
- data/crates/kobako-wasmtime/src/preview1/memory.rs +117 -150
- data/crates/kobako-wasmtime/src/preview1/mount_view/wrap.rs +211 -0
- data/crates/kobako-wasmtime/src/preview1/mount_view.rs +1939 -672
- data/crates/kobako-wasmtime/src/preview1/path.rs +230 -349
- data/crates/kobako-wasmtime/src/preview1/poll.rs +148 -100
- data/crates/kobako-wasmtime/src/preview1/sock.rs +6 -23
- data/crates/kobako-wasmtime/src/preview1/testsuite-expected.txt +6 -0
- data/crates/kobako-wasmtime/src/preview1/testsuite.rs +157 -0
- data/crates/kobako-wasmtime/src/preview1.rs +464 -413
- data/crates/kobako-wasmtime/src/trap.rs +9 -9
- data/crates/kobako-wasmtime/src/wasi.rs +53 -37
- data/crates/kobako-wasmtime/tests/{guest_file.rs → integration/guest_file.rs} +232 -201
- data/crates/kobako-wasmtime/tests/integration/main.rs +29 -0
- data/crates/kobako-wasmtime/tests/{preamble_flags.rs → integration/preamble_flags.rs} +1 -1
- data/crates/kobako-wasmtime/tests/{snippet_compile.rs → integration/snippet_compile.rs} +1 -1
- data/crates/kobako-wasmtime/tests/{unanswered_call.rs → integration/unanswered_call.rs} +1 -1
- data/crates/kobako-wasmtime/tests/{unsupported_call.rs → integration/unsupported_call.rs} +1 -1
- data/crates/kobako-wasmtime/witx/typenames.witx +750 -0
- data/crates/kobako-wasmtime/witx/wasi_snapshot_preview1.witx +441 -0
- data/data/kobako.wasm +0 -0
- data/ext/kobako/Cargo.toml +1 -1
- data/ext/kobako/src/runtime/bridge.rs +1 -1
- data/ext/kobako/src/runtime/errors.rs +1 -1
- data/ext/kobako/src/runtime/escape.rs +28 -14
- data/ext/kobako/src/runtime/filesystem/grants.rs +21 -5
- data/ext/kobako/src/runtime/filesystem/ruby.rs +464 -156
- data/ext/kobako/src/runtime/filesystem.rs +109 -137
- data/ext/kobako/src/runtime/gvl.rs +43 -8
- data/ext/kobako/src/runtime/snapshot.rs +1 -1
- data/ext/kobako/src/runtime.rs +6 -1
- data/lib/kobako/catalog/extensions.rb +5 -5
- data/lib/kobako/catalog/flags.rb +2 -2
- data/lib/kobako/catalog/handles.rb +6 -6
- data/lib/kobako/catalog/mount.rb +2 -10
- data/lib/kobako/catalog/services.rb +2 -2
- data/lib/kobako/catalog/snippets.rb +7 -7
- data/lib/kobako/catalog.rb +1 -1
- data/lib/kobako/codec/decoder.rb +10 -10
- data/lib/kobako/codec/ext_types.rb +3 -5
- data/lib/kobako/codec/handle_walk.rb +4 -6
- data/lib/kobako/codec/nesting.rb +7 -7
- data/lib/kobako/codec/utils.rb +1 -1
- data/lib/kobako/codec/value_budget.rb +3 -3
- data/lib/kobako/context.rb +5 -5
- data/lib/kobako/filesystem.rb +44 -13
- data/lib/kobako/payload/arguments.rb +2 -2
- data/lib/kobako/pool.rb +4 -4
- data/lib/kobako/sandbox.rb +28 -28
- data/lib/kobako/sandbox_options.rb +2 -3
- data/lib/kobako/snippet/bytecode.rb +1 -1
- data/lib/kobako/snippet/source.rb +1 -1
- data/lib/kobako/transport/dispatcher.rb +5 -5
- data/lib/kobako/transport/exposure.rb +2 -2
- data/lib/kobako/transport/reflection.rb +7 -7
- data/lib/kobako/transport/run.rb +5 -5
- data/lib/kobako/transport/yielder.rb +1 -1
- data/lib/kobako/version.rb +1 -1
- data/lib/kobako.rb +34 -0
- data/release-please-config.json +1 -0
- data/sig/kobako/catalog/extensions.rbs +3 -3
- data/sig/kobako/catalog/handles.rbs +3 -3
- data/sig/kobako/catalog/mount.rbs +0 -4
- data/sig/kobako/catalog/services.rbs +1 -1
- data/sig/kobako/catalog/snippets.rbs +3 -3
- data/sig/kobako/catalog.rbs +1 -1
- data/sig/kobako/codec/decoder.rbs +1 -1
- data/sig/kobako/codec/handle_walk.rbs +1 -1
- data/sig/kobako/codec/nesting.rbs +2 -2
- data/sig/kobako/codec/utils.rbs +1 -1
- data/sig/kobako/codec/value_budget.rbs +1 -1
- data/sig/kobako/codec.rbs +1 -2
- data/sig/kobako/context.rbs +1 -1
- data/sig/kobako/filesystem.rbs +19 -14
- data/sig/kobako/guest_yielder.rbs +1 -1
- data/sig/kobako/payload/arguments.rbs +1 -1
- data/sig/kobako/pool.rbs +2 -2
- data/sig/kobako/runtime.rbs +1 -1
- data/sig/kobako/sandbox.rbs +3 -5
- data/sig/kobako/sandbox_options.rbs +1 -1
- data/sig/kobako/transport/dispatcher.rbs +2 -2
- data/sig/kobako/transport/exposure.rbs +1 -1
- data/sig/kobako/transport/reflection.rbs +2 -2
- data/sig/kobako/transport/run.rbs +3 -3
- data/sig/kobako/transport.rbs +1 -1
- metadata +67 -19
- data/crates/kobako-runtime/src/filesystem/directory.rs +0 -1082
- data/crates/kobako-runtime/src/filesystem/memory.rs +0 -1100
- data/crates/kobako-runtime/src/filesystem/overlay/exclusive.rs +0 -88
- data/crates/kobako-runtime/src/filesystem/temporary.rs +0 -167
- data/crates/kobako-wasmtime/src/capture.rs +0 -96
- data/crates/kobako-wasmtime/src/dispatch.rs +0 -125
- data/crates/kobako-wasmtime/src/guest_mem.rs +0 -271
- data/crates/kobako-wasmtime/src/io.rs +0 -89
- data/crates/kobako-wasmtime/src/preview1/args.rs +0 -60
- data/crates/kobako-wasmtime/src/preview1/beneath.rs +0 -188
- /data/crates/kobako-wasmtime/tests/{compile_posture.rs → integration/compile_posture.rs} +0 -0
- /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
|
-
}
|