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.
- checksums.yaml +4 -4
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +377 -0
- data/Cargo.lock +181 -154
- data/README.md +99 -459
- data/ROADMAP.md +7 -1
- data/SECURITY.md +1 -1
- data/crates/kobako-runtime/CHANGELOG.md +354 -0
- data/crates/kobako-runtime/Cargo.toml +38 -7
- data/crates/kobako-runtime/README.md +17 -8
- data/crates/kobako-runtime/src/dispatch.rs +9 -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/bind/lookup.rs +236 -0
- 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 +301 -0
- data/crates/kobako-runtime/src/filesystem/overlay.rs +3469 -0
- data/crates/kobako-runtime/src/filesystem/path.rs +21 -0
- 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 +26 -0
- data/crates/kobako-runtime/src/grant.rs +20 -0
- data/crates/kobako-runtime/src/hashtable.rs +44 -0
- data/crates/kobako-runtime/src/lib.rs +16 -1
- 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 +105 -0
- data/crates/kobako-runtime/src/profile.rs +140 -4
- data/crates/kobako-runtime/src/runtime.rs +12 -0
- data/crates/kobako-runtime/src/sched.rs +210 -0
- data/crates/kobako-runtime/src/security.rs +696 -0
- data/crates/kobako-runtime/src/snapshot.rs +20 -5
- 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/src/yielder.rs +11 -6
- 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 +23 -0
- data/crates/kobako-transport/Cargo.toml +3 -3
- data/crates/kobako-transport/README.md +6 -5
- data/crates/kobako-transport/src/abi.rs +1 -1
- 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 +4 -3
- data/crates/kobako-transport/src/envelope/outcome.rs +3 -3
- data/crates/kobako-transport/src/envelope/preamble.rs +24 -24
- data/crates/kobako-transport/src/envelope/reply.rs +34 -16
- data/crates/kobako-transport/src/envelope/request.rs +19 -9
- 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 +265 -0
- data/crates/kobako-wasmtime/Cargo.toml +25 -19
- data/crates/kobako-wasmtime/README.md +13 -7
- data/crates/kobako-wasmtime/src/abi.rs +75 -49
- data/crates/kobako-wasmtime/src/ambient.rs +64 -6
- data/crates/kobako-wasmtime/src/cache.rs +188 -77
- data/crates/kobako-wasmtime/src/config.rs +77 -5
- data/crates/kobako-wasmtime/src/descriptors.rs +200 -0
- data/crates/kobako-wasmtime/src/driver.rs +70 -135
- 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 +52 -0
- data/crates/kobako-wasmtime/src/instance_pre.rs +18 -46
- data/crates/kobako-wasmtime/src/invocation.rs +43 -74
- data/crates/kobako-wasmtime/src/lib.rs +5 -4
- data/crates/kobako-wasmtime/src/limiter.rs +6 -230
- data/crates/kobako-wasmtime/src/preview1/differential/mount.rs +632 -0
- data/crates/kobako-wasmtime/src/preview1/differential.rs +486 -0
- data/crates/kobako-wasmtime/src/preview1/errno.rs +135 -0
- data/crates/kobako-wasmtime/src/preview1/fd.rs +1420 -0
- data/crates/kobako-wasmtime/src/preview1/memory.rs +172 -0
- data/crates/kobako-wasmtime/src/preview1/mount_view/wrap.rs +211 -0
- data/crates/kobako-wasmtime/src/preview1/mount_view.rs +3766 -0
- data/crates/kobako-wasmtime/src/preview1/path.rs +302 -0
- data/crates/kobako-wasmtime/src/preview1/poll.rs +381 -0
- data/crates/kobako-wasmtime/src/preview1/sock.rs +13 -0
- 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 +512 -0
- data/crates/kobako-wasmtime/src/trap.rs +45 -25
- data/crates/kobako-wasmtime/src/wasi.rs +333 -0
- data/crates/kobako-wasmtime/tests/integration/compile_posture.rs +43 -0
- data/crates/kobako-wasmtime/tests/integration/guest_file.rs +1085 -0
- data/crates/kobako-wasmtime/tests/integration/main.rs +29 -0
- data/crates/kobako-wasmtime/tests/{preamble_flags.rs → integration/preamble_flags.rs} +4 -3
- data/crates/kobako-wasmtime/tests/{snippet_compile.rs → integration/snippet_compile.rs} +5 -4
- data/crates/kobako-wasmtime/tests/{support → integration/support}/mod.rs +9 -13
- data/crates/kobako-wasmtime/tests/integration/unanswered_call.rs +86 -0
- data/crates/kobako-wasmtime/tests/{unsupported_call.rs → integration/unsupported_call.rs} +2 -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 +4 -2
- data/ext/kobako/src/runtime/bridge.rs +154 -81
- data/ext/kobako/src/runtime/errors.rs +1 -1
- data/ext/kobako/src/runtime/escape.rs +67 -0
- data/ext/kobako/src/runtime/filesystem/grants.rs +107 -0
- data/ext/kobako/src/runtime/filesystem/ruby.rs +708 -0
- data/ext/kobako/src/runtime/filesystem.rs +208 -0
- data/ext/kobako/src/runtime/gvl.rs +49 -8
- data/ext/kobako/src/runtime/pinned_value.rs +31 -0
- data/ext/kobako/src/runtime/snapshot.rs +127 -0
- data/ext/kobako/src/runtime.rs +125 -199
- data/lib/kobako/catalog/extensions.rb +5 -5
- data/lib/kobako/catalog/flags.rb +2 -14
- data/lib/kobako/catalog/grants.rb +30 -0
- data/lib/kobako/catalog/handles.rb +6 -6
- data/lib/kobako/catalog/mount.rb +19 -0
- data/lib/kobako/catalog/services.rb +13 -2
- data/lib/kobako/catalog/snippets.rb +11 -15
- data/lib/kobako/catalog.rb +11 -0
- data/lib/kobako/codec/decoder.rb +10 -9
- data/lib/kobako/codec/ext_types.rb +4 -6
- data/lib/kobako/codec/handle_walk.rb +4 -6
- data/lib/kobako/codec/nesting.rb +7 -7
- data/lib/kobako/codec/state.rb +19 -3
- data/lib/kobako/codec/utils.rb +1 -1
- data/lib/kobako/codec/value_budget.rb +86 -0
- data/lib/kobako/codec.rb +7 -0
- data/lib/kobako/context.rb +40 -13
- data/lib/kobako/errors.rb +15 -14
- data/lib/kobako/filesystem.rb +69 -0
- data/lib/kobako/payload/arguments.rb +2 -2
- data/lib/kobako/pool.rb +6 -6
- data/lib/kobako/sandbox.rb +55 -27
- data/lib/kobako/sandbox_options.rb +39 -13
- data/lib/kobako/snippet/{binary.rb → bytecode.rb} +2 -2
- data/lib/kobako/snippet/source.rb +1 -1
- data/lib/kobako/snippet.rb +1 -1
- data/lib/kobako/transport/block_failure.rb +35 -0
- data/lib/kobako/transport/dispatcher.rb +33 -29
- data/lib/kobako/transport/exposure.rb +7 -9
- data/lib/kobako/transport/reflection.rb +79 -23
- data/lib/kobako/transport/run.rb +5 -5
- data/lib/kobako/transport/yielder.rb +9 -31
- data/lib/kobako/transport.rb +1 -0
- data/lib/kobako/version.rb +1 -1
- data/lib/kobako.rb +35 -0
- data/release-please-config.json +1 -0
- data/sig/kobako/catalog/extensions.rbs +3 -3
- data/sig/kobako/catalog/flags.rbs +0 -4
- data/sig/kobako/catalog/grants.rbs +13 -0
- data/sig/kobako/catalog/handles.rbs +3 -3
- data/sig/kobako/catalog/mount.rbs +24 -0
- data/sig/kobako/catalog/services.rbs +5 -1
- data/sig/kobako/catalog/snippets.rbs +4 -4
- data/sig/kobako/catalog.rbs +1 -0
- 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/state.rbs +7 -1
- data/sig/kobako/codec/utils.rbs +1 -1
- data/sig/kobako/codec/value_budget.rbs +23 -0
- data/sig/kobako/codec.rbs +3 -2
- data/sig/kobako/context.rbs +14 -6
- data/sig/kobako/errors.rbs +4 -4
- data/sig/kobako/filesystem.rbs +93 -0
- data/sig/kobako/guest_yielder.rbs +9 -0
- data/sig/kobako/payload/arguments.rbs +1 -1
- data/sig/kobako/pool.rbs +3 -3
- data/sig/kobako/runtime.rbs +21 -49
- data/sig/kobako/sandbox.rbs +9 -5
- data/sig/kobako/sandbox_options.rbs +25 -3
- data/sig/kobako/snapshot.rbs +20 -0
- data/sig/kobako/snippet/bytecode.rbs +12 -0
- data/sig/kobako/transport/block_failure.rbs +13 -0
- data/sig/kobako/transport/dispatcher.rbs +4 -4
- data/sig/kobako/transport/exposure.rbs +2 -2
- data/sig/kobako/transport/reflection.rbs +14 -1
- data/sig/kobako/transport/run.rbs +3 -3
- data/sig/kobako/transport/yielder.rbs +3 -7
- data/sig/kobako/transport.rbs +3 -3
- metadata +116 -15
- data/crates/kobako-wasmtime/src/capture.rs +0 -96
- data/crates/kobako-wasmtime/src/dispatch.rs +0 -107
- data/crates/kobako-wasmtime/src/guest_mem.rs +0 -265
- data/crates/kobako-wasmtime/src/io.rs +0 -286
- data/sig/kobako/snippet/binary.rbs +0 -12
|
@@ -5,113 +5,162 @@
|
|
|
5
5
|
//! dispatch seam touches `magnus`. The wasm-side dispatch path
|
|
6
6
|
//! (`kobako_wasmtime`'s dispatch module) sees only the contract traits.
|
|
7
7
|
|
|
8
|
-
use core::cell::Cell;
|
|
8
|
+
use core::cell::{Cell, RefCell};
|
|
9
9
|
use core::ptr::NonNull;
|
|
10
|
+
use std::sync::Arc;
|
|
10
11
|
|
|
12
|
+
use magnus::rb_sys::AsRawValue;
|
|
11
13
|
use magnus::value::{Opaque, ReprValue};
|
|
12
|
-
use magnus::{
|
|
14
|
+
use magnus::{
|
|
15
|
+
method, prelude::*, Error as MagnusError, RModule, RString, Ruby, Symbol, TypedData, Value,
|
|
16
|
+
};
|
|
13
17
|
|
|
14
18
|
use kobako_runtime::dispatch::DispatchHandler;
|
|
15
19
|
use kobako_runtime::yielder::Yielder;
|
|
16
20
|
use kobako_transport::envelope::{Call, Fault, FaultKind, Reply, Target, YieldReply};
|
|
17
21
|
|
|
18
|
-
/// The names
|
|
19
|
-
/// Reply arm. Neutral Symbols rather than the envelope's tag bytes,
|
|
20
|
-
/// same way `Snapshot#outcome` names its arms: the
|
|
21
|
-
/// side's to read, so no byte value of it reaches Ruby.
|
|
22
|
+
/// The names each side of this seam branches on — one per Reply and
|
|
23
|
+
/// Yield Reply arm. Neutral Symbols rather than the envelope's tag bytes,
|
|
24
|
+
/// the same way `Snapshot#outcome` names its arms: the Envelope is
|
|
25
|
+
/// this side's to read, so no byte value of it reaches Ruby.
|
|
22
26
|
const ARM_OK: &str = "ok";
|
|
27
|
+
const ARM_FAULT: &str = "fault";
|
|
23
28
|
const ARM_BREAK: &str = "break";
|
|
24
29
|
const ARM_ERROR: &str = "error";
|
|
25
30
|
const ARM_UNSUPPORTED: &str = "unsupported";
|
|
26
31
|
|
|
27
|
-
///
|
|
28
|
-
///
|
|
29
|
-
pub(super) fn register(
|
|
32
|
+
/// Defines the class the `#[magnus::wrap]` name resolves to, and looks it
|
|
33
|
+
/// up once, before any object is wrapped.
|
|
34
|
+
pub(super) fn register(kobako: RModule) -> Result<(), MagnusError> {
|
|
30
35
|
let ruby = Ruby::get().expect("Ruby thread");
|
|
31
|
-
let class =
|
|
36
|
+
let class = kobako.define_class("GuestYielder", ruby.class_object())?;
|
|
32
37
|
class.define_method("call", method!(GuestYielder::call, 1))?;
|
|
38
|
+
GuestYielder::class(&ruby);
|
|
33
39
|
Ok(())
|
|
34
40
|
}
|
|
35
41
|
|
|
36
|
-
///
|
|
37
|
-
///
|
|
38
|
-
///
|
|
39
|
-
///
|
|
42
|
+
/// The Yielders one invocation's open Calls lent, innermost last. A yield
|
|
43
|
+
/// re-enters the guest only through the innermost, whatever Call's block
|
|
44
|
+
/// it runs, so no Yielder is reached while a later Call holds the guest.
|
|
45
|
+
///
|
|
46
|
+
/// This is the one place the host↔guest re-entry still costs raw
|
|
47
|
+
/// pointers: `magnus`' `funcall` sits between two Rust frames, so the
|
|
48
|
+
/// typed `&mut dyn Yielder` cannot cross it and is erased here. Each
|
|
49
|
+
/// pointer is pushed before its frame's `funcall` and popped after it, so
|
|
50
|
+
/// every pointer on the stack belongs to a frame still on the Rust stack.
|
|
51
|
+
#[derive(Default)]
|
|
52
|
+
struct OpenFrames(RefCell<Vec<NonNull<dyn Yielder>>>);
|
|
53
|
+
|
|
54
|
+
// SAFETY: the stack is pushed and popped by the dispatch frames on the
|
|
55
|
+
// fiber driving the invocation, and read only by a `GuestYielder` that
|
|
56
|
+
// refuses every other fiber, so it is never reached from another thread.
|
|
57
|
+
unsafe impl Send for OpenFrames {}
|
|
58
|
+
unsafe impl Sync for OpenFrames {}
|
|
59
|
+
|
|
60
|
+
impl OpenFrames {
|
|
61
|
+
/// Lend `yielder` for the frame `run` answers, popping it however
|
|
62
|
+
/// `run` returns.
|
|
63
|
+
fn lend<T>(&self, yielder: &mut dyn Yielder, run: impl FnOnce() -> T) -> T {
|
|
64
|
+
struct Pop<'a>(&'a OpenFrames);
|
|
65
|
+
impl Drop for Pop<'_> {
|
|
66
|
+
fn drop(&mut self) {
|
|
67
|
+
self.0 .0.borrow_mut().pop();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// SAFETY: the lifetime is erased only for as long as `Pop` keeps
|
|
71
|
+
// the pointer on the stack, which ends before `yielder` does.
|
|
72
|
+
let ptr: NonNull<dyn Yielder> = unsafe {
|
|
73
|
+
std::mem::transmute::<NonNull<dyn Yielder + '_>, NonNull<dyn Yielder + 'static>>(
|
|
74
|
+
NonNull::from(yielder),
|
|
75
|
+
)
|
|
76
|
+
};
|
|
77
|
+
self.0.borrow_mut().push(ptr);
|
|
78
|
+
let _pop = Pop(self);
|
|
79
|
+
run()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
fn innermost(&self) -> Option<NonNull<dyn Yielder>> {
|
|
83
|
+
self.0.borrow().last().copied()
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/// Frame-scoped Ruby handle that lets the dispatch `Proc` run the block
|
|
88
|
+
/// one Call carried. The bridge builds one per `__kobako_upcall` frame,
|
|
89
|
+
/// hands it to the `Proc` as the last argument, and `invalidate`s it the
|
|
40
90
|
/// instant the `Proc` returns. A guest block stashed and called after that
|
|
41
91
|
/// frame normally raises `LocalJumpError` at the Ruby
|
|
42
92
|
/// `Transport::Yielder` net — invalidated in the dispatcher's `ensure`,
|
|
43
93
|
/// which fires before this handle is reached. This inner invalidation is
|
|
44
|
-
/// the backstop behind that outer net
|
|
45
|
-
///
|
|
46
|
-
///
|
|
94
|
+
/// the backstop behind that outer net, so neither net is redundant.
|
|
95
|
+
///
|
|
96
|
+
/// The guest's Store is entered from the fiber that drives the
|
|
97
|
+
/// invocation, and only from it: another fiber runs on its own native
|
|
98
|
+
/// stack, which wasmtime's stack limit and the value budget never saw. A
|
|
99
|
+
/// block run from any other fiber, including one on another thread, is
|
|
100
|
+
/// refused before it reaches the guest.
|
|
47
101
|
///
|
|
48
|
-
///
|
|
49
|
-
///
|
|
50
|
-
///
|
|
51
|
-
|
|
52
|
-
/// `Value`**, so GC has nothing to trace through it — it needs no `mark`.
|
|
53
|
-
#[magnus::wrap(class = "Kobako::Runtime::GuestYielder", free_immediately, size)]
|
|
102
|
+
/// It holds **no Ruby `Value`**, so GC has nothing to trace through it —
|
|
103
|
+
/// it needs no `mark`. The driving fiber is kept as its raw identity,
|
|
104
|
+
/// compared only while the frame is live and so while that fiber is.
|
|
105
|
+
#[magnus::wrap(class = "Kobako::GuestYielder", free_immediately, size)]
|
|
54
106
|
struct GuestYielder {
|
|
55
|
-
|
|
107
|
+
frame: u32,
|
|
108
|
+
open: Arc<OpenFrames>,
|
|
109
|
+
live: Cell<bool>,
|
|
110
|
+
fiber: rb_sys::VALUE,
|
|
56
111
|
}
|
|
57
112
|
|
|
58
|
-
// SAFETY: magnus requires `Send + Sync` on wrapped types.
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
// never read from another thread.
|
|
113
|
+
// SAFETY: magnus requires `Send + Sync` on wrapped types. `call` refuses
|
|
114
|
+
// every fiber but the one driving the invocation, so the frames it reaches
|
|
115
|
+
// are only ever read on that fiber's thread.
|
|
62
116
|
unsafe impl Send for GuestYielder {}
|
|
63
117
|
unsafe impl Sync for GuestYielder {}
|
|
64
118
|
|
|
65
119
|
impl GuestYielder {
|
|
66
|
-
|
|
67
|
-
/// Safety contract for the caller: `invalidate` MUST run before the
|
|
68
|
-
/// borrow this pointer came from ends (i.e. before the dispatch frame
|
|
69
|
-
/// returns).
|
|
70
|
-
fn new(yielder: &mut dyn Yielder) -> Self {
|
|
71
|
-
let ptr = NonNull::from(yielder);
|
|
72
|
-
// Erase the borrow's lifetime to `'static`; the pointer is only
|
|
73
|
-
// ever dereferenced while it is still `Some` (i.e. `invalidate`
|
|
74
|
-
// has not run), so the referent is guaranteed live.
|
|
75
|
-
let ptr: NonNull<dyn Yielder> = unsafe {
|
|
76
|
-
std::mem::transmute::<NonNull<dyn Yielder + '_>, NonNull<dyn Yielder + 'static>>(ptr)
|
|
77
|
-
};
|
|
120
|
+
fn new(ruby: &Ruby, frame: u32, open: Arc<OpenFrames>) -> Self {
|
|
78
121
|
Self {
|
|
79
|
-
|
|
122
|
+
frame,
|
|
123
|
+
open,
|
|
124
|
+
live: Cell::new(true),
|
|
125
|
+
fiber: ruby.fiber_current().as_raw(),
|
|
80
126
|
}
|
|
81
127
|
}
|
|
82
128
|
|
|
83
129
|
/// Runs the instant the dispatch frame's `funcall` returns, so a guest
|
|
84
|
-
/// block stashed beyond its frame raises instead of
|
|
85
|
-
/// stack.
|
|
130
|
+
/// block stashed beyond its frame raises instead of re-entering.
|
|
86
131
|
fn invalidate(&self) {
|
|
87
|
-
self.
|
|
132
|
+
self.live.set(false);
|
|
88
133
|
}
|
|
89
134
|
|
|
90
135
|
/// The arm is named here off the decoded Yield Reply, so the host
|
|
91
136
|
/// `Transport::Yielder` decodes only a payload.
|
|
92
137
|
fn call(&self, args: RString) -> Result<(Symbol, RString, Option<String>), MagnusError> {
|
|
93
138
|
let ruby = Ruby::get().expect("Ruby handle unavailable in the block bridge");
|
|
94
|
-
let
|
|
139
|
+
let innermost = self.open.innermost().filter(|_| self.live.get());
|
|
140
|
+
let Some(mut ptr) = innermost else {
|
|
95
141
|
return Err(super::errors::trap_err(
|
|
96
142
|
&ruby,
|
|
97
143
|
"guest block invoked after the host dispatch frame returned",
|
|
98
144
|
));
|
|
99
145
|
};
|
|
146
|
+
if ruby.fiber_current().as_raw() != self.fiber {
|
|
147
|
+
return Err(MagnusError::new(
|
|
148
|
+
ruby.exception_thread_error(),
|
|
149
|
+
"guest block called from a thread or fiber other than its invocation's",
|
|
150
|
+
));
|
|
151
|
+
}
|
|
100
152
|
let bytes = super::rstring_to_vec(args);
|
|
101
|
-
// SAFETY:
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
// The borrow ends with this method.
|
|
153
|
+
// SAFETY: this frame is live, so the innermost frame — this one or
|
|
154
|
+
// a later Call — is still on the Rust stack with its Yielder lent.
|
|
155
|
+
// Only the innermost frame's Ruby code runs, so no other borrow of
|
|
156
|
+
// that Yielder is live; the borrow ends with this method.
|
|
106
157
|
let yielder: &mut dyn Yielder = unsafe { ptr.as_mut() };
|
|
107
158
|
let reply = yielder
|
|
108
|
-
.yield_to_block(&bytes)
|
|
159
|
+
.yield_to_block(self.frame, &bytes)
|
|
109
160
|
.map_err(|t| super::errors::trap_to_magnus(&ruby, t))?;
|
|
110
161
|
Ok(match reply {
|
|
111
162
|
YieldReply::Ok(body) => (ruby.to_symbol(ARM_OK), ruby.str_from_slice(&body), None),
|
|
112
|
-
YieldReply::Break(
|
|
113
|
-
(ruby.to_symbol(ARM_BREAK), ruby.str_from_slice(&body), None)
|
|
114
|
-
}
|
|
163
|
+
YieldReply::Break => (ruby.to_symbol(ARM_BREAK), ruby.str_from_slice(&[]), None),
|
|
115
164
|
YieldReply::Error(record) => (
|
|
116
165
|
ruby.to_symbol(ARM_ERROR),
|
|
117
166
|
ruby.str_new(&record.message),
|
|
@@ -136,19 +185,24 @@ impl GuestYielder {
|
|
|
136
185
|
/// `Opaque` copy of the same handle.
|
|
137
186
|
pub(super) struct RubyDispatchHandler {
|
|
138
187
|
on_dispatch: Opaque<Value>,
|
|
188
|
+
open: Arc<OpenFrames>,
|
|
139
189
|
}
|
|
140
190
|
|
|
141
191
|
impl RubyDispatchHandler {
|
|
142
192
|
pub(super) fn new(on_dispatch: Opaque<Value>) -> Self {
|
|
143
|
-
Self {
|
|
193
|
+
Self {
|
|
194
|
+
on_dispatch,
|
|
195
|
+
open: Arc::default(),
|
|
196
|
+
}
|
|
144
197
|
}
|
|
145
198
|
}
|
|
146
199
|
|
|
147
200
|
impl DispatchHandler for RubyDispatchHandler {
|
|
148
201
|
/// Ruby decodes only the payload, through the codec it already owns,
|
|
149
202
|
/// which keeps a large payload's strings shared with the buffer rather
|
|
150
|
-
/// than copied. The Proc folds every
|
|
151
|
-
/// fault arm, so
|
|
203
|
+
/// than copied. The Proc folds every `StandardError` into the Reply's
|
|
204
|
+
/// fault arm, so one escaping is a contract violation surfaced as
|
|
205
|
+
/// `None`.
|
|
152
206
|
fn dispatch(&self, call: Call<'_>, yielder: &mut dyn Yielder) -> Option<Reply> {
|
|
153
207
|
// The guest may be running GVL-free (`gvl: :release`); re-acquire the
|
|
154
208
|
// GVL for the whole callback before touching any Ruby VALUE. In hold
|
|
@@ -171,34 +225,53 @@ impl DispatchHandler for RubyDispatchHandler {
|
|
|
171
225
|
};
|
|
172
226
|
// Hand the Proc a frame-scoped yielder object as its last arg and
|
|
173
227
|
// invalidate it the instant the Proc returns, so a guest block that
|
|
174
|
-
// escapes the dispatch frame
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
let
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
228
|
+
// escapes the dispatch frame never re-enters the guest.
|
|
229
|
+
let guest_yielder =
|
|
230
|
+
ruby.obj_wrap(GuestYielder::new(&ruby, yielder.frame(), self.open.clone()));
|
|
231
|
+
let answer: Result<(Symbol, RString, Option<RString>), magnus::Error> =
|
|
232
|
+
self.open.lend(yielder, || {
|
|
233
|
+
proc_value.funcall(
|
|
234
|
+
"call",
|
|
235
|
+
(
|
|
236
|
+
target,
|
|
237
|
+
ruby.str_new(call.method),
|
|
238
|
+
call.block_given,
|
|
239
|
+
ruby.str_from_slice(call.payload),
|
|
240
|
+
guest_yielder,
|
|
241
|
+
),
|
|
242
|
+
)
|
|
243
|
+
});
|
|
189
244
|
guest_yielder.invalidate();
|
|
245
|
+
// The Proc rescues only StandardError, so what else escapes it
|
|
246
|
+
// is the host's own and is held for the host; a StandardError
|
|
247
|
+
// escaping breaks the Proc's contract.
|
|
248
|
+
let (arm, body, kind) = match answer {
|
|
249
|
+
Ok(answer) => answer,
|
|
250
|
+
Err(error) => {
|
|
251
|
+
let _ = super::escape::hold(&ruby, error);
|
|
252
|
+
return None;
|
|
253
|
+
}
|
|
254
|
+
};
|
|
190
255
|
// The ok arm carries payload bytes the codec owns; the fault
|
|
191
256
|
// arm carries the Fault's own two fields, which the envelope
|
|
192
257
|
// types — so Ruby names a category rather than encoding one.
|
|
193
|
-
// A
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
258
|
+
// A block exit carries nothing, since nothing of it crosses.
|
|
259
|
+
// An arm or category outside the closed set cannot come from
|
|
260
|
+
// the public API, so it fails the dispatch rather than
|
|
261
|
+
// degrading.
|
|
262
|
+
match arm.name().ok()?.as_ref() {
|
|
263
|
+
ARM_OK => Some(Reply::Ok(super::rstring_to_vec(body))),
|
|
264
|
+
ARM_BREAK => Some(Reply::Break),
|
|
265
|
+
ARM_FAULT => Some(Reply::Fault(Fault::new(
|
|
266
|
+
FaultKind::from_name(&kind?.to_string().ok()?)?,
|
|
267
|
+
body.to_string().ok()?,
|
|
268
|
+
))),
|
|
269
|
+
_ => None,
|
|
270
|
+
}
|
|
202
271
|
})
|
|
203
272
|
}
|
|
273
|
+
|
|
274
|
+
fn abandoned(&self) -> bool {
|
|
275
|
+
super::escape::pending()
|
|
276
|
+
}
|
|
204
277
|
}
|
|
@@ -58,7 +58,7 @@ pub(super) fn trap_class(ruby: &Ruby, trap: &Trap) -> ExceptionClass {
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
/// The verb prefix is left to `Kobako::Context#invoke
|
|
61
|
+
/// The verb prefix is left to `Kobako::Context#invoke`.
|
|
62
62
|
pub(super) fn trap_to_magnus(ruby: &Ruby, trap: Trap) -> MagnusError {
|
|
63
63
|
MagnusError::new(trap_class(ruby, &trap), trap.to_string())
|
|
64
64
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
//! What a Host App's callback raises that is not a `StandardError`:
|
|
2
|
+
//! `Interrupt`, `exit`, `throw`, `Timeout`, and any interrupt delivered to the
|
|
3
|
+
//! Thread while a run gives up the GVL. These belong to the host, so one is held
|
|
4
|
+
//! here instead of answering the guest with it, the invocation ends at its next
|
|
5
|
+
//! host call, and the run raises it again once control is back in Ruby.
|
|
6
|
+
//!
|
|
7
|
+
//! The slot is per thread, as each invocation runs on its caller's
|
|
8
|
+
//! thread. A nested invocation takes what its own run held before the
|
|
9
|
+
//! outer callback sees it, so the outer one holds it next.
|
|
10
|
+
|
|
11
|
+
use std::cell::RefCell;
|
|
12
|
+
|
|
13
|
+
use magnus::value::BoxValue;
|
|
14
|
+
use magnus::{Error as MagnusError, Ruby, Value};
|
|
15
|
+
|
|
16
|
+
thread_local! {
|
|
17
|
+
static ESCAPE: RefCell<Option<Escape>> = const { RefCell::new(None) };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/// The error and, when it carries one, its exception kept rooted while
|
|
21
|
+
/// the guest finishes unwinding.
|
|
22
|
+
struct Escape {
|
|
23
|
+
error: MagnusError,
|
|
24
|
+
_rooted: Option<BoxValue<Value>>,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/// Hold `error` unless it is a `StandardError`, which comes back for the
|
|
28
|
+
/// callback to answer the guest with.
|
|
29
|
+
pub(super) fn hold(ruby: &Ruby, error: MagnusError) -> Result<(), MagnusError> {
|
|
30
|
+
if error.is_kind_of(ruby.exception_standard_error()) {
|
|
31
|
+
return Err(error);
|
|
32
|
+
}
|
|
33
|
+
keep(error);
|
|
34
|
+
Ok(())
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/// Run what is pending on this Thread, holding what it raises, whatever its
|
|
38
|
+
/// class: no callback asked for it, so no guest is answered with it.
|
|
39
|
+
pub(super) fn hold_interrupts() {
|
|
40
|
+
let ruby = Ruby::get().expect("a Thread that was not given up holds the GVL");
|
|
41
|
+
if let Err(error) = ruby.thread_check_ints() {
|
|
42
|
+
keep(error);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// Keep `error` for the host; the first one kept wins.
|
|
47
|
+
fn keep(error: MagnusError) {
|
|
48
|
+
ESCAPE.with(|held| {
|
|
49
|
+
held.borrow_mut().get_or_insert_with(|| Escape {
|
|
50
|
+
_rooted: error.value().map(BoxValue::new),
|
|
51
|
+
error,
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/// Whether this thread holds an error for the host.
|
|
57
|
+
pub(super) fn pending() -> bool {
|
|
58
|
+
ESCAPE.with(|held| held.borrow().is_some())
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/// Raise what was held, once the run is back in Ruby.
|
|
62
|
+
pub(super) fn resume() -> Result<(), MagnusError> {
|
|
63
|
+
match ESCAPE.with(|held| held.borrow_mut().take()) {
|
|
64
|
+
Some(held) => Err(held.error),
|
|
65
|
+
None => Ok(()),
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
//! A Sandbox's Mounts as contract Grants: refused at `mount` time and
|
|
2
|
+
//! built afresh for every invocation by the same rules, each mount's
|
|
3
|
+
//! block standing in for its access.
|
|
4
|
+
|
|
5
|
+
use std::sync::Arc;
|
|
6
|
+
|
|
7
|
+
use magnus::value::{Opaque, ReprValue};
|
|
8
|
+
use magnus::{prelude::*, Error as MagnusError, RArray, RString, Ruby, Symbol, Value};
|
|
9
|
+
|
|
10
|
+
use kobako_runtime::grant::Grant;
|
|
11
|
+
use kobako_runtime::mount::{Access, Mount, Policy};
|
|
12
|
+
|
|
13
|
+
use super::to_filesystem;
|
|
14
|
+
use crate::runtime::pinned_value::PinnedValue;
|
|
15
|
+
use crate::runtime::{escape, gvl};
|
|
16
|
+
|
|
17
|
+
/// Refuse a Mount at `mount` time by the same rules an invocation builds
|
|
18
|
+
/// it with, so a mistake raises where it was made.
|
|
19
|
+
pub(in crate::runtime) fn validate_mount(
|
|
20
|
+
at: Value,
|
|
21
|
+
access: Value,
|
|
22
|
+
filesystem: Value,
|
|
23
|
+
) -> Result<(), MagnusError> {
|
|
24
|
+
to_mount(at, access, filesystem).map(drop)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/// The Mount a Sandbox's `[at, access, filesystem]` stands for: one owner
|
|
28
|
+
/// of the path, access, and Filesystem rules.
|
|
29
|
+
fn to_mount(at: Value, access: Value, filesystem: Value) -> Result<Mount, MagnusError> {
|
|
30
|
+
let ruby = Ruby::get().expect("Ruby thread");
|
|
31
|
+
let at = RString::try_convert(at)?.to_string()?;
|
|
32
|
+
let access = to_access(access)?;
|
|
33
|
+
Mount::new(at, to_filesystem(filesystem)?, access)
|
|
34
|
+
.map_err(|err| MagnusError::new(ruby.exception_arg_error(), err.to_string()))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
fn to_access(access: Value) -> Result<Access, MagnusError> {
|
|
38
|
+
let name = Symbol::from_value(access)
|
|
39
|
+
.map(|symbol| symbol.name())
|
|
40
|
+
.transpose()?;
|
|
41
|
+
match name.as_deref() {
|
|
42
|
+
Some("read") => Ok(Access::Read),
|
|
43
|
+
Some("read_write") => Ok(Access::ReadWrite),
|
|
44
|
+
_ => Err(MagnusError::new(
|
|
45
|
+
Ruby::get().expect("Ruby thread").exception_arg_error(),
|
|
46
|
+
format!(
|
|
47
|
+
"access must be :read or :read_write, got {}",
|
|
48
|
+
access.inspect()
|
|
49
|
+
),
|
|
50
|
+
)),
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// A mount's block, asked what its access allows. A block raising a
|
|
55
|
+
/// StandardError refuses, so the guest's open fails and its script goes
|
|
56
|
+
/// on; anything else it raises is held for the host.
|
|
57
|
+
struct RubyPolicy {
|
|
58
|
+
block: Opaque<Value>,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
impl Policy for RubyPolicy {
|
|
62
|
+
fn allows(&self, path: &str, access: Access) -> bool {
|
|
63
|
+
gvl::reenter(|| {
|
|
64
|
+
let ruby = Ruby::get().expect("Ruby handle unavailable in a mount's block");
|
|
65
|
+
let block: Value = ruby.get_inner(self.block);
|
|
66
|
+
let access = ruby.to_symbol(match access {
|
|
67
|
+
Access::Read => "read",
|
|
68
|
+
Access::ReadWrite => "read_write",
|
|
69
|
+
});
|
|
70
|
+
block
|
|
71
|
+
.funcall::<_, _, Value>("call", (ruby.str_new(path), access))
|
|
72
|
+
.map(|answer| answer.to_bool())
|
|
73
|
+
.unwrap_or_else(|error| {
|
|
74
|
+
let _ = escape::hold(&ruby, error);
|
|
75
|
+
false
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/// The Grants one invocation carries, from the Sandbox's Mounts as
|
|
82
|
+
/// `[at, access, filesystem, block]` entries, beside the pins keeping
|
|
83
|
+
/// each Mount's filesystem and block in place while the guest may reach
|
|
84
|
+
/// them; the caller holds both until the invocation returns.
|
|
85
|
+
pub(in crate::runtime) fn to_grants(
|
|
86
|
+
ruby: &Ruby,
|
|
87
|
+
mounts: RArray,
|
|
88
|
+
) -> Result<(Vec<Grant>, Vec<PinnedValue>), MagnusError> {
|
|
89
|
+
let mut grants = Vec::with_capacity(mounts.len());
|
|
90
|
+
let mut pins = Vec::with_capacity(mounts.len() * 2);
|
|
91
|
+
for index in 0..mounts.len() as isize {
|
|
92
|
+
let entry: RArray = mounts.entry(index)?;
|
|
93
|
+
let filesystem: Value = entry.entry(2)?;
|
|
94
|
+
pins.push(PinnedValue::new(ruby, filesystem));
|
|
95
|
+
let mount = to_mount(entry.entry(0)?, entry.entry(1)?, filesystem)?;
|
|
96
|
+
let block: Value = entry.entry(3)?;
|
|
97
|
+
grants.push(Grant::Mount(if block.is_nil() {
|
|
98
|
+
mount
|
|
99
|
+
} else {
|
|
100
|
+
pins.push(PinnedValue::new(ruby, block));
|
|
101
|
+
mount.with_policy(Arc::new(RubyPolicy {
|
|
102
|
+
block: Opaque::from(block),
|
|
103
|
+
}))
|
|
104
|
+
}));
|
|
105
|
+
}
|
|
106
|
+
Ok((grants, pins))
|
|
107
|
+
}
|