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
data/ext/kobako/src/runtime.rs
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
//!
|
|
3
3
|
//! The Ruby-visible classes are
|
|
4
4
|
//!
|
|
5
|
-
//! Kobako::Runtime
|
|
6
|
-
//! Kobako::
|
|
5
|
+
//! Kobako::Runtime — wraps a `kobako_wasmtime::Driver`
|
|
6
|
+
//! Kobako::Snapshot — one invocation's completion + captures + usage
|
|
7
7
|
//!
|
|
8
8
|
//! `Kobako::Runtime` is constructed via `Kobako::Runtime.from_path(path,
|
|
9
|
-
//!
|
|
9
|
+
//! **options)`, the options keyed by the `Kobako::SandboxOptions` members. Every
|
|
10
10
|
//! invocation (`#eval` / `#run`) takes that run's dispatch handler as a call
|
|
11
11
|
//! argument, instantiates a fresh instance, and returns a `Snapshot` — the
|
|
12
12
|
//! whole per-invocation result — so the Runtime holds no per-invocation
|
|
@@ -19,11 +19,16 @@
|
|
|
19
19
|
|
|
20
20
|
mod bridge;
|
|
21
21
|
mod errors;
|
|
22
|
+
mod escape;
|
|
23
|
+
mod filesystem;
|
|
22
24
|
mod gvl;
|
|
25
|
+
mod pinned_value;
|
|
26
|
+
mod snapshot;
|
|
23
27
|
|
|
24
28
|
use magnus::{
|
|
25
|
-
function, method, prelude::*,
|
|
26
|
-
Error as MagnusError,
|
|
29
|
+
function, method, prelude::*, scan_args::get_kwargs, typed_data::DataTypeFunctions,
|
|
30
|
+
value::Opaque, Error as MagnusError, RArray, RHash, RModule, RString, Ruby, Symbol, TypedData,
|
|
31
|
+
Value,
|
|
27
32
|
};
|
|
28
33
|
|
|
29
34
|
use std::path::Path;
|
|
@@ -33,15 +38,10 @@ use std::time::Duration;
|
|
|
33
38
|
use kobako_runtime::dispatch::DispatchHandler;
|
|
34
39
|
use kobako_runtime::profile::Profile;
|
|
35
40
|
use kobako_runtime::runtime::{Entry, Runtime as ContractRuntime};
|
|
36
|
-
use
|
|
37
|
-
use kobako_transport::envelope::{Bindings, Flag, Outcome, Preamble, Snippet, Snippets};
|
|
41
|
+
use kobako_transport::envelope::{Bindings, Flag, Preamble, Snippet, Snippets};
|
|
38
42
|
use kobako_wasmtime::{Config, Driver};
|
|
39
43
|
|
|
40
|
-
|
|
41
|
-
/// backtrace, and the names the invocation could have used in place of the
|
|
42
|
-
/// one it named. Named so the Ruby side reads them positionally against one
|
|
43
|
-
/// shape, matching the `Kobako::Outcome::panic_fields` alias.
|
|
44
|
-
type PanicFields = (String, String, String, Vec<String>, Vec<String>);
|
|
44
|
+
use snapshot::Snapshot;
|
|
45
45
|
|
|
46
46
|
/// The one place the boundary borrows an RString's bytes: the borrow does
|
|
47
47
|
/// not outlive this call, so no Ruby allocation can move the RString
|
|
@@ -53,7 +53,7 @@ fn rstring_to_vec(s: RString) -> Vec<u8> {
|
|
|
53
53
|
|
|
54
54
|
/// `kind` arrives as a Symbol so the registry never names the wire's
|
|
55
55
|
/// discriminant; an off-ladder kind raises rather than defaulting.
|
|
56
|
-
fn
|
|
56
|
+
fn to_snippets(ruby: &Ruby, entries: RArray) -> Result<Snippets, MagnusError> {
|
|
57
57
|
let mut snippets = Snippets {
|
|
58
58
|
entries: Vec::with_capacity(entries.len()),
|
|
59
59
|
};
|
|
@@ -80,16 +80,16 @@ fn snippets_from(ruby: &Ruby, entries: RArray) -> Result<Snippets, MagnusError>
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
/// One owner for the Preamble every guest entry reads, so an invocation
|
|
83
|
-
/// and a compile cannot drift in what they send.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
paths:
|
|
87
|
-
|
|
88
|
-
flags: RArray,
|
|
89
|
-
) -> Result<Preamble, MagnusError> {
|
|
83
|
+
/// and a compile cannot drift in what they send. An invocation's setup
|
|
84
|
+
/// and a compile's preamble both open with its paths, snippets, and flags.
|
|
85
|
+
fn to_preamble(ruby: &Ruby, parts: RArray) -> Result<Preamble, MagnusError> {
|
|
86
|
+
let paths: RArray = parts.entry(0)?;
|
|
87
|
+
let flags: RArray = parts.entry(2)?;
|
|
90
88
|
let mut preamble = Preamble::default();
|
|
91
|
-
preamble.bindings = Bindings {
|
|
92
|
-
|
|
89
|
+
preamble.bindings = Bindings {
|
|
90
|
+
paths: paths.to_vec()?,
|
|
91
|
+
};
|
|
92
|
+
preamble.snippets = to_snippets(ruby, parts.entry(1)?)?;
|
|
93
93
|
preamble.flags = flags
|
|
94
94
|
.to_vec::<(String, String, bool)>()?
|
|
95
95
|
.into_iter()
|
|
@@ -111,27 +111,32 @@ pub fn init(ruby: &Ruby, kobako: RModule) -> Result<(), MagnusError> {
|
|
|
111
111
|
// directly into those classes through the constructors and mappers
|
|
112
112
|
// in `runtime/errors.rs` — no intermediate hierarchy is registered.
|
|
113
113
|
|
|
114
|
+
// This extension is the only frontend in its process, so the first
|
|
115
|
+
// registration is the one that stands.
|
|
116
|
+
let _ = kobako_runtime::sched::register_blocking(Box::new(gvl::GvlFreeWait));
|
|
117
|
+
|
|
114
118
|
let runtime = kobako.define_class("Runtime", ruby.class_object())?;
|
|
115
|
-
runtime.define_singleton_method("from_path", function!(Runtime::from_path,
|
|
116
|
-
runtime.define_method("eval", method!(Runtime::eval,
|
|
117
|
-
runtime.define_method("run", method!(Runtime::run,
|
|
119
|
+
runtime.define_singleton_method("from_path", function!(Runtime::from_path, 2))?;
|
|
120
|
+
runtime.define_method("eval", method!(Runtime::eval, 3))?;
|
|
121
|
+
runtime.define_method("run", method!(Runtime::run, 4))?;
|
|
118
122
|
runtime.define_method("profile", method!(Runtime::profile, 0))?;
|
|
119
|
-
runtime.define_method("compile", method!(Runtime::compile,
|
|
123
|
+
runtime.define_method("compile", method!(Runtime::compile, 1))?;
|
|
124
|
+
runtime.define_singleton_method("validate_mount", function!(filesystem::validate_mount, 3))?;
|
|
120
125
|
// The guest re-enters for a block yield through a frame-scoped
|
|
121
|
-
// `Kobako::
|
|
122
|
-
//
|
|
123
|
-
bridge::register(
|
|
126
|
+
// `Kobako::GuestYielder` the dispatcher hands the Proc, not a method on
|
|
127
|
+
// Runtime.
|
|
128
|
+
bridge::register(kobako)?;
|
|
129
|
+
filesystem::register(ruby, kobako)?;
|
|
124
130
|
|
|
125
131
|
// Snapshot — the per-invocation result object each entry point returns.
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
snapshot.define_method("stderr_truncated?", method!(Snapshot::stderr_truncated, 0))?;
|
|
132
|
+
snapshot::register(ruby, kobako)?;
|
|
133
|
+
|
|
134
|
+
// A wrapped class is otherwise looked up the first time an object is
|
|
135
|
+
// wrapped, through a Ruby call that a pending interrupt would turn into
|
|
136
|
+
// a panic; a signal arriving during a process's first run waits for
|
|
137
|
+
// exactly that moment.
|
|
138
|
+
Runtime::class(ruby);
|
|
139
|
+
Snapshot::class(ruby);
|
|
135
140
|
|
|
136
141
|
Ok(())
|
|
137
142
|
}
|
|
@@ -157,34 +162,61 @@ impl DataTypeFunctions for Runtime {}
|
|
|
157
162
|
|
|
158
163
|
impl Runtime {
|
|
159
164
|
/// The only Ruby-facing constructor, so Engine and Module are never
|
|
160
|
-
/// visible to Ruby.
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
timeout_seconds: Option<f64>,
|
|
164
|
-
memory_limit: Option<usize>,
|
|
165
|
-
stdout_limit: Option<usize>,
|
|
166
|
-
stderr_limit: Option<usize>,
|
|
167
|
-
profile: Symbol,
|
|
168
|
-
gvl: Symbol,
|
|
169
|
-
) -> Result<Self, MagnusError> {
|
|
165
|
+
/// visible to Ruby. Every option is a required keyword: the defaults
|
|
166
|
+
/// belong to `Kobako::SandboxOptions`, which names the keys.
|
|
167
|
+
fn from_path(path: String, options: RHash) -> Result<Self, MagnusError> {
|
|
170
168
|
let ruby = Ruby::get().expect("Ruby thread");
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
169
|
+
type Options = (
|
|
170
|
+
Option<f64>,
|
|
171
|
+
Option<f64>,
|
|
172
|
+
Option<usize>,
|
|
173
|
+
Option<usize>,
|
|
174
|
+
Option<usize>,
|
|
175
|
+
Option<usize>,
|
|
176
|
+
Symbol,
|
|
177
|
+
Symbol,
|
|
178
|
+
);
|
|
179
|
+
let options = get_kwargs::<_, Options, (), ()>(
|
|
180
|
+
options,
|
|
181
|
+
&[
|
|
182
|
+
"timeout",
|
|
183
|
+
"setup_timeout",
|
|
184
|
+
"memory_limit",
|
|
185
|
+
"stdout_limit",
|
|
186
|
+
"stderr_limit",
|
|
187
|
+
"open_file_limit",
|
|
188
|
+
"profile",
|
|
189
|
+
"gvl",
|
|
190
|
+
],
|
|
191
|
+
&[],
|
|
192
|
+
)?;
|
|
193
|
+
let (
|
|
194
|
+
timeout_seconds,
|
|
195
|
+
setup_timeout_seconds,
|
|
196
|
+
memory_limit,
|
|
197
|
+
stdout_limit,
|
|
198
|
+
stderr_limit,
|
|
199
|
+
open_file_limit,
|
|
200
|
+
profile,
|
|
201
|
+
gvl,
|
|
202
|
+
) = options.required;
|
|
203
|
+
// An invalid cap argument is a Host App programming error and
|
|
204
|
+
// raises `ArgumentError`, outside the construction-failure
|
|
205
|
+
// `SetupError` branch. `SandboxOptions` is the primary guard (it
|
|
206
|
+
// never lets a bad timeout reach here); this is defence-in-depth
|
|
207
|
+
// for direct `from_path` calls.
|
|
208
|
+
let to_duration = |seconds: Option<f64>, name: &str| match seconds {
|
|
209
|
+
None => Ok(None),
|
|
210
|
+
Some(secs) if secs.is_finite() && secs > 0.0 => Ok(Some(Duration::from_secs_f64(secs))),
|
|
211
|
+
Some(secs) => Err(MagnusError::new(
|
|
212
|
+
ruby.exception_arg_error(),
|
|
213
|
+
format!("{name} must be > 0 and finite, got {secs} seconds"),
|
|
214
|
+
)),
|
|
185
215
|
};
|
|
216
|
+
let timeout = to_duration(timeout_seconds, "timeout")?;
|
|
217
|
+
let setup_timeout = to_duration(setup_timeout_seconds, "setup_timeout")?;
|
|
186
218
|
// Fail closed on an off-ladder rung: an unrecognized posture must
|
|
187
|
-
// never fall back to a
|
|
219
|
+
// never fall back to a weaker posture. Same defence in depth as the
|
|
188
220
|
// timeout guard above — `SandboxOptions` is the primary validator.
|
|
189
221
|
let profile = match profile.name()?.as_ref() {
|
|
190
222
|
"hermetic" => Profile::Hermetic,
|
|
@@ -210,17 +242,16 @@ impl Runtime {
|
|
|
210
242
|
}
|
|
211
243
|
};
|
|
212
244
|
|
|
213
|
-
let
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
.map_err(|e| errors::setup_to_magnus(&ruby, e))?;
|
|
245
|
+
let mut config = Config::default();
|
|
246
|
+
config.timeout = timeout;
|
|
247
|
+
config.setup_timeout = setup_timeout;
|
|
248
|
+
config.memory_limit = memory_limit;
|
|
249
|
+
config.stdout_limit = stdout_limit;
|
|
250
|
+
config.stderr_limit = stderr_limit;
|
|
251
|
+
config.open_file_limit = open_file_limit;
|
|
252
|
+
config.profile = profile;
|
|
253
|
+
let driver =
|
|
254
|
+
Driver::new(Path::new(&path), config).map_err(|e| errors::setup_to_magnus(&ruby, e))?;
|
|
224
255
|
Ok(Self {
|
|
225
256
|
driver,
|
|
226
257
|
release_gvl,
|
|
@@ -233,40 +264,32 @@ impl Runtime {
|
|
|
233
264
|
// success or trap alike. Only a could-not-start fault (a missing export
|
|
234
265
|
// or a fault before the export call) raises directly, as the class
|
|
235
266
|
// `errors::to_magnus` gives its cause, since it yields no `Snapshot`.
|
|
267
|
+
//
|
|
268
|
+
// `setup` is the invocation's `[paths, snippets, flags, mounts]`: the
|
|
269
|
+
// Preamble's three parts, then the Mounts as
|
|
270
|
+
// `filesystem::to_grants` reads them.
|
|
236
271
|
// -----------------------------------------------------------------
|
|
237
272
|
|
|
238
273
|
fn eval(
|
|
239
274
|
&self,
|
|
240
275
|
dispatch: Value,
|
|
241
|
-
|
|
242
|
-
snippets: RArray,
|
|
243
|
-
flags: RArray,
|
|
276
|
+
setup: RArray,
|
|
244
277
|
source: RString,
|
|
245
278
|
) -> Result<Snapshot, MagnusError> {
|
|
246
279
|
let source = rstring_to_vec(source);
|
|
247
|
-
self.invoke(
|
|
248
|
-
dispatch,
|
|
249
|
-
paths,
|
|
250
|
-
snippets,
|
|
251
|
-
flags,
|
|
252
|
-
Entry::Eval { source: &source },
|
|
253
|
-
)
|
|
280
|
+
self.invoke(dispatch, setup, Entry::Eval { source: &source })
|
|
254
281
|
}
|
|
255
282
|
|
|
256
283
|
fn run(
|
|
257
284
|
&self,
|
|
258
285
|
dispatch: Value,
|
|
259
|
-
|
|
260
|
-
snippets: RArray,
|
|
261
|
-
flags: RArray,
|
|
286
|
+
setup: RArray,
|
|
262
287
|
entrypoint: String,
|
|
263
288
|
payload: RString,
|
|
264
289
|
) -> Result<Snapshot, MagnusError> {
|
|
265
290
|
self.invoke(
|
|
266
291
|
dispatch,
|
|
267
|
-
|
|
268
|
-
snippets,
|
|
269
|
-
flags,
|
|
292
|
+
setup,
|
|
270
293
|
Entry::Run {
|
|
271
294
|
entrypoint: &entrypoint,
|
|
272
295
|
payload: rstring_to_vec(payload),
|
|
@@ -274,14 +297,12 @@ impl Runtime {
|
|
|
274
297
|
)
|
|
275
298
|
}
|
|
276
299
|
|
|
277
|
-
/// One owner for the wiring both verbs share, so a Preamble
|
|
278
|
-
/// change cannot drift between them.
|
|
300
|
+
/// One owner for the wiring both verbs share, so a Preamble, a Grant,
|
|
301
|
+
/// or a handler change cannot drift between them.
|
|
279
302
|
fn invoke(
|
|
280
303
|
&self,
|
|
281
304
|
dispatch: Value,
|
|
282
|
-
|
|
283
|
-
snippets: RArray,
|
|
284
|
-
flags: RArray,
|
|
305
|
+
setup: RArray,
|
|
285
306
|
entry: Entry<'_>,
|
|
286
307
|
) -> Result<Snapshot, MagnusError> {
|
|
287
308
|
let ruby = Ruby::get().expect("Ruby thread");
|
|
@@ -290,15 +311,17 @@ impl Runtime {
|
|
|
290
311
|
Entry::Run { .. } => "run",
|
|
291
312
|
};
|
|
292
313
|
let handler = build_handler(dispatch);
|
|
293
|
-
let preamble =
|
|
314
|
+
let preamble = to_preamble(&ruby, setup)?;
|
|
315
|
+
let (grants, _pins) = filesystem::to_grants(&ruby, setup.entry(3)?)?;
|
|
294
316
|
// Release the GVL around the guest span iff this Sandbox asks for it;
|
|
295
317
|
// the closure touches no Ruby VALUE (the driver is magnus-free, and a
|
|
296
|
-
// guest→host dispatch re-acquires the GVL
|
|
318
|
+
// guest→host dispatch, or a mount's block, re-acquires the GVL).
|
|
297
319
|
let result = gvl::region(self.release_gvl, || {
|
|
298
|
-
self.driver.invoke(entry, &preamble, handler)
|
|
320
|
+
self.driver.invoke(entry, &preamble, handler, &grants)
|
|
299
321
|
});
|
|
322
|
+
escape::resume()?;
|
|
300
323
|
result
|
|
301
|
-
.map(|snapshot| Snapshot(snapshot, verb))
|
|
324
|
+
.map(|snapshot| Snapshot::new(snapshot, verb))
|
|
302
325
|
.map_err(|e| errors::to_magnus(&ruby, e))
|
|
303
326
|
}
|
|
304
327
|
|
|
@@ -308,15 +331,11 @@ impl Runtime {
|
|
|
308
331
|
/// invocation sends, `paths` included, so the driver can tell whether
|
|
309
332
|
/// the bytecode still fits it; no dispatch handler is bound, since
|
|
310
333
|
/// compiling runs no script.
|
|
311
|
-
fn compile(
|
|
312
|
-
&self,
|
|
313
|
-
paths: RArray,
|
|
314
|
-
snippets: RArray,
|
|
315
|
-
flags: RArray,
|
|
316
|
-
) -> Result<Option<RArray>, MagnusError> {
|
|
334
|
+
fn compile(&self, preamble: RArray) -> Result<Option<RArray>, MagnusError> {
|
|
317
335
|
let ruby = Ruby::get().expect("Ruby thread");
|
|
318
|
-
let preamble =
|
|
336
|
+
let preamble = to_preamble(&ruby, preamble)?;
|
|
319
337
|
let bytecodes = gvl::region(self.release_gvl, || self.driver.compile(&preamble));
|
|
338
|
+
escape::resume()?;
|
|
320
339
|
Ok(bytecodes.map(|bytecodes| {
|
|
321
340
|
ruby.ary_from_iter(
|
|
322
341
|
bytecodes
|
|
@@ -349,96 +368,3 @@ fn build_handler(dispatch: Value) -> Option<Arc<dyn DispatchHandler>> {
|
|
|
349
368
|
as Arc<dyn DispatchHandler>,
|
|
350
369
|
)
|
|
351
370
|
}
|
|
352
|
-
|
|
353
|
-
/// One invocation's result at the Ruby boundary — the whole `Snapshot` the
|
|
354
|
-
/// driver produced, exposed as `Kobako::Runtime::Snapshot`. Usage and the
|
|
355
|
-
/// two output captures are present on every outcome, so the trap path
|
|
356
|
-
/// carries them just like the value path; the completion is read as either
|
|
357
|
-
/// the Outcome (`#outcome`) or a trap (`#trap_error`). The verb names the
|
|
358
|
-
/// invocation, so an unsupported one says which operation is lacking.
|
|
359
|
-
#[derive(TypedData)]
|
|
360
|
-
#[magnus(class = "Kobako::Runtime::Snapshot", free_immediately, size)]
|
|
361
|
-
struct Snapshot(RuntimeSnapshot, &'static str);
|
|
362
|
-
|
|
363
|
-
impl DataTypeFunctions for Snapshot {}
|
|
364
|
-
|
|
365
|
-
impl Snapshot {
|
|
366
|
-
/// Already split off the core envelope, so the Ruby side maps a failure
|
|
367
|
-
/// onto its error taxonomy without decoding a payload byte: the ok
|
|
368
|
-
/// arm's payload, or a Panic's fields beside an empty payload. A trap
|
|
369
|
-
/// has no Outcome; `#trap_error` is the discriminator there.
|
|
370
|
-
fn outcome(&self) -> Result<(RString, Option<PanicFields>), MagnusError> {
|
|
371
|
-
let ruby = Ruby::get().expect("Ruby thread");
|
|
372
|
-
match &self.0.completion {
|
|
373
|
-
Completion::Outcome(Outcome::Ok(value)) => Ok((ruby.str_from_slice(value), None)),
|
|
374
|
-
Completion::Outcome(Outcome::Panic(panic)) => Ok((
|
|
375
|
-
ruby.str_from_slice(&[]),
|
|
376
|
-
Some((
|
|
377
|
-
panic.origin.name().to_owned(),
|
|
378
|
-
panic.error.name.clone(),
|
|
379
|
-
panic.error.message.clone(),
|
|
380
|
-
panic.error.backtrace.clone(),
|
|
381
|
-
panic.available.clone(),
|
|
382
|
-
)),
|
|
383
|
-
)),
|
|
384
|
-
// The Guest Binary answered rather than broke, so it reads as
|
|
385
|
-
// the Sandbox's failure, worded by the host.
|
|
386
|
-
Completion::Outcome(Outcome::Unsupported(_)) => Ok((
|
|
387
|
-
ruby.str_from_slice(&[]),
|
|
388
|
-
Some((
|
|
389
|
-
"sandbox".to_owned(),
|
|
390
|
-
"Kobako::SandboxError".to_owned(),
|
|
391
|
-
format!(
|
|
392
|
-
"the Sandbox runtime does not support #{}; \
|
|
393
|
-
rebuild it against the kobako version in use",
|
|
394
|
-
self.1
|
|
395
|
-
),
|
|
396
|
-
Vec::new(),
|
|
397
|
-
Vec::new(),
|
|
398
|
-
)),
|
|
399
|
-
)),
|
|
400
|
-
Completion::Trap(_) => Err(MagnusError::new(
|
|
401
|
-
ruby.exception_runtime_error(),
|
|
402
|
-
"a trapped run has no outcome",
|
|
403
|
-
)),
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
/// Unraised, so the Sandbox layer can attach the run's Execution before
|
|
408
|
-
/// raising it.
|
|
409
|
-
fn trap_error(&self) -> Result<Option<Exception>, MagnusError> {
|
|
410
|
-
let Completion::Trap(trap) = &self.0.completion else {
|
|
411
|
-
return Ok(None);
|
|
412
|
-
};
|
|
413
|
-
let ruby = Ruby::get().expect("Ruby thread");
|
|
414
|
-
errors::trap_class(&ruby, trap)
|
|
415
|
-
.new_instance((trap.to_string(),))
|
|
416
|
-
.map(Some)
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
fn wall_time(&self) -> f64 {
|
|
420
|
-
self.0.usage.wall_time
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
fn memory_peak(&self) -> usize {
|
|
424
|
-
self.0.usage.memory_peak
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
fn stdout(&self) -> RString {
|
|
428
|
-
let ruby = Ruby::get().expect("Ruby thread");
|
|
429
|
-
ruby.str_from_slice(&self.0.stdout.bytes)
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
fn stdout_truncated(&self) -> bool {
|
|
433
|
-
self.0.stdout.truncated
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
fn stderr(&self) -> RString {
|
|
437
|
-
let ruby = Ruby::get().expect("Ruby thread");
|
|
438
|
-
ruby.str_from_slice(&self.0.stderr.bytes)
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
fn stderr_truncated(&self) -> bool {
|
|
442
|
-
self.0.stderr.truncated
|
|
443
|
-
}
|
|
444
|
-
}
|
|
@@ -16,10 +16,10 @@ module Kobako
|
|
|
16
16
|
# The Extension readers are duck-typed, so a malformed +name+ or
|
|
17
17
|
# +backend.path+ surfaces through the registry it routes to.
|
|
18
18
|
def install(extension, snippets:, services:)
|
|
19
|
-
validate
|
|
19
|
+
validate(extension)
|
|
20
20
|
snippets.register(code: extension.source, name: extension.name)
|
|
21
21
|
backend = extension.backend
|
|
22
|
-
services.bind(backend.path,
|
|
22
|
+
services.bind(backend.path, object_of(backend)) if backend
|
|
23
23
|
@entries << extension
|
|
24
24
|
self
|
|
25
25
|
end
|
|
@@ -27,7 +27,7 @@ module Kobako
|
|
|
27
27
|
# The asserted flag flips only on success, so a check that failed
|
|
28
28
|
# re-runs on the next invocation rather than silently passing a broken
|
|
29
29
|
# Sandbox.
|
|
30
|
-
def assert_dependencies
|
|
30
|
+
def assert_dependencies
|
|
31
31
|
return self if @asserted
|
|
32
32
|
|
|
33
33
|
extension, dependency = unmet_dependency
|
|
@@ -52,7 +52,7 @@ module Kobako
|
|
|
52
52
|
|
|
53
53
|
private
|
|
54
54
|
|
|
55
|
-
def
|
|
55
|
+
def object_of(backend) = backend.object.nil? ? Kobako::Unresolved : backend.object
|
|
56
56
|
|
|
57
57
|
def resolve_backend(extension, resolved, by_provider)
|
|
58
58
|
backend = extension.backend
|
|
@@ -64,7 +64,7 @@ module Kobako
|
|
|
64
64
|
resolved[backend.path.to_s] = by_provider.fetch(provider) { by_provider[provider] = provider.call }
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
-
def validate
|
|
67
|
+
def validate(extension)
|
|
68
68
|
source = extension.source
|
|
69
69
|
raise ArgumentError, "Extension #source must be a String, got #{source.class}" unless source.is_a?(String)
|
|
70
70
|
|
data/lib/kobako/catalog/flags.rb
CHANGED
|
@@ -18,24 +18,12 @@ module Kobako
|
|
|
18
18
|
end
|
|
19
19
|
raise ArgumentError, "flag value must be a String, got #{value.class}" unless value.is_a?(String)
|
|
20
20
|
|
|
21
|
-
key =
|
|
21
|
+
key = Catalog.to_utf8(name.to_s, "flag name")
|
|
22
22
|
raise ArgumentError, "flag #{key.inspect} already set" if @entries.key?(key)
|
|
23
23
|
|
|
24
|
-
@entries[key] = [
|
|
24
|
+
@entries[key] = [Catalog.to_utf8(value, "flag value"), !advisory]
|
|
25
25
|
nil
|
|
26
26
|
end
|
|
27
|
-
|
|
28
|
-
private
|
|
29
|
-
|
|
30
|
-
# The native side carries a flag only as UTF-8 text, so text read as
|
|
31
|
-
# bytes is re-tagged, and bytes that are not UTF-8 are refused here
|
|
32
|
-
# rather than failing every invocation.
|
|
33
|
-
def utf8!(text, what)
|
|
34
|
-
utf8 = text.dup.force_encoding(Encoding::UTF_8)
|
|
35
|
-
raise ArgumentError, "#{what} must be UTF-8 text" unless utf8.valid_encoding?
|
|
36
|
-
|
|
37
|
-
utf8
|
|
38
|
-
end
|
|
39
27
|
end
|
|
40
28
|
end
|
|
41
29
|
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kobako
|
|
4
|
+
module Catalog
|
|
5
|
+
class Grants # :nodoc:
|
|
6
|
+
def initialize
|
|
7
|
+
@entries = {} # : Hash[String, Mount]
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Each Grant decides what it cannot stand beside.
|
|
11
|
+
def register(grant)
|
|
12
|
+
reason = @entries.each_value.lazy.filter_map { |other| grant.conflict(other) }.first
|
|
13
|
+
raise ArgumentError, reason if reason
|
|
14
|
+
|
|
15
|
+
@entries[grant.key] = grant
|
|
16
|
+
nil
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def include?(grant)
|
|
20
|
+
@entries.key?(grant.key)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Each Grant as the native side reads it, an override standing in for
|
|
24
|
+
# the Grant it shares a key with.
|
|
25
|
+
def entries(overrides = {})
|
|
26
|
+
@entries.merge(overrides).values.map(&:to_native)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -23,8 +23,8 @@ module Kobako
|
|
|
23
23
|
# call made through this Handle is authorized against the reference
|
|
24
24
|
# the guest was given.
|
|
25
25
|
def alloc(object)
|
|
26
|
-
reject_unwrappable
|
|
27
|
-
ensure_capacity
|
|
26
|
+
reject_unwrappable(object)
|
|
27
|
+
ensure_capacity
|
|
28
28
|
id = @next_id
|
|
29
29
|
@entries[id] = Kobako::Transport::Exposure.of(object, @surfaces)
|
|
30
30
|
@next_id = id + 1
|
|
@@ -36,7 +36,7 @@ module Kobako
|
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
def exposure(id)
|
|
39
|
-
require_bound
|
|
39
|
+
require_bound(id)
|
|
40
40
|
@entries[id]
|
|
41
41
|
end
|
|
42
42
|
|
|
@@ -61,7 +61,7 @@ module Kobako
|
|
|
61
61
|
# a surface the floor reads as ordinary Service behaviour. Raising here
|
|
62
62
|
# keeps the rule at the single mint point, so it holds on both the
|
|
63
63
|
# Service-return and the +#run+ host→guest auto-wrap paths.
|
|
64
|
-
def reject_unwrappable
|
|
64
|
+
def reject_unwrappable(object)
|
|
65
65
|
case object
|
|
66
66
|
when Binding, Method, UnboundMethod, Module, Delegator
|
|
67
67
|
# Delegator < BasicObject exposes no static +#class+, so name the
|
|
@@ -71,7 +71,7 @@ module Kobako
|
|
|
71
71
|
end
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
-
def ensure_capacity
|
|
74
|
+
def ensure_capacity
|
|
75
75
|
cap = Kobako::Handle::MAX_ID
|
|
76
76
|
return unless @next_id > cap
|
|
77
77
|
|
|
@@ -80,7 +80,7 @@ module Kobako
|
|
|
80
80
|
"in a single invocation (limit #{cap})"
|
|
81
81
|
end
|
|
82
82
|
|
|
83
|
-
def require_bound
|
|
83
|
+
def require_bound(id)
|
|
84
84
|
return if @entries.key?(id)
|
|
85
85
|
|
|
86
86
|
raise SandboxError, "unknown Handle id: #{id.inspect}"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kobako
|
|
4
|
+
module Catalog
|
|
5
|
+
class Mount < Data.define(:at, :access, :filesystem, :policy) # :nodoc:
|
|
6
|
+
# The path a Context replaces this Mount at.
|
|
7
|
+
def key = at
|
|
8
|
+
|
|
9
|
+
# Why +other+ cannot be mounted beside this Mount, or +nil+: a path
|
|
10
|
+
# means one Mount, as Docker refuses a duplicate mount point.
|
|
11
|
+
def conflict(other)
|
|
12
|
+
"path #{at.inspect} is already mounted" if other.at == at
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# The Mount as the native side reads it.
|
|
16
|
+
def to_native = [at, access, filesystem, policy]
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -7,13 +7,20 @@ require_relative "../transport/exposure"
|
|
|
7
7
|
module Kobako
|
|
8
8
|
module Catalog
|
|
9
9
|
class Services # :nodoc:
|
|
10
|
+
# The Guest Binary keeps this namespace for constants it may add later.
|
|
11
|
+
RESERVED_NAMESPACE = "Kobako"
|
|
12
|
+
|
|
10
13
|
def initialize
|
|
11
14
|
@bindings = {} # : Hash[String, Kobako::Transport::Exposure]
|
|
12
15
|
end
|
|
13
16
|
|
|
14
17
|
# The binding records +object+'s Exposure as it stands now.
|
|
15
18
|
def bind(path, object)
|
|
16
|
-
path_str = validate_path
|
|
19
|
+
path_str = validate_path(path)
|
|
20
|
+
if reserved?(path_str)
|
|
21
|
+
raise ArgumentError,
|
|
22
|
+
"Service path #{path_str} is in the reserved #{RESERVED_NAMESPACE} namespace"
|
|
23
|
+
end
|
|
17
24
|
raise ArgumentError, "Service path #{path_str} conflicts with an existing binding" if collision?(path_str)
|
|
18
25
|
|
|
19
26
|
@bindings[path_str] = Kobako::Transport::Exposure.of(object)
|
|
@@ -39,7 +46,7 @@ module Kobako
|
|
|
39
46
|
|
|
40
47
|
private
|
|
41
48
|
|
|
42
|
-
def validate_path
|
|
49
|
+
def validate_path(path)
|
|
43
50
|
path_str = path.to_s
|
|
44
51
|
segments = path_str.split("::", -1)
|
|
45
52
|
return path_str if !segments.empty? && segments.all? { |seg| CONSTANT_NAME.match?(seg) }
|
|
@@ -48,6 +55,10 @@ module Kobako
|
|
|
48
55
|
"bind path must be constant-form segments joined by '::' (got #{path.inspect})"
|
|
49
56
|
end
|
|
50
57
|
|
|
58
|
+
def reserved?(path)
|
|
59
|
+
path.split("::").first == RESERVED_NAMESPACE
|
|
60
|
+
end
|
|
61
|
+
|
|
51
62
|
# Keeps a name from being both a bound Service and a grouping prefix.
|
|
52
63
|
def collision?(path)
|
|
53
64
|
@bindings.each_key.any? do |existing|
|