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
data/lib/kobako/codec/decoder.rb
CHANGED
|
@@ -13,9 +13,9 @@ module Kobako
|
|
|
13
13
|
# from the decoded payload reports a broken invariant as
|
|
14
14
|
# InvalidTypeError without its own Utils.with_boundary.
|
|
15
15
|
def self.decode(bytes)
|
|
16
|
-
ValueBudget.assert_within
|
|
16
|
+
ValueBudget.assert_within(bytes, State.current.value_budget)
|
|
17
17
|
value = FACTORY.load(bytes.b)
|
|
18
|
-
validate_utf8
|
|
18
|
+
validate_utf8(value)
|
|
19
19
|
block_given? ? yield(value) : value
|
|
20
20
|
# msgpack gem raises the format/type errors below; +ArgumentError+
|
|
21
21
|
# covers a yielded block's Value Object invariants — a wire violation
|
|
@@ -30,23 +30,23 @@ module Kobako
|
|
|
30
30
|
raise TruncatedInputError, e.message
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
-
# +str+ family payloads must be UTF-8
|
|
34
|
-
#
|
|
33
|
+
# +str+ family payloads must be UTF-8, since a sender carries bytes
|
|
34
|
+
# that are not UTF-8 as +bin+. The msgpack gem
|
|
35
35
|
# returns UTF-8-tagged Strings for
|
|
36
36
|
# str family but does not validate the bytes; +bin+ family decodes
|
|
37
37
|
# to ASCII-8BIT. Walk the tree once and reject invalid UTF-8 in any
|
|
38
|
-
# str-typed leaf via Utils.assert_utf8
|
|
38
|
+
# str-typed leaf via Utils.assert_utf8.
|
|
39
39
|
class << self
|
|
40
40
|
private
|
|
41
41
|
|
|
42
|
-
def validate_utf8
|
|
42
|
+
def validate_utf8(value)
|
|
43
43
|
case value
|
|
44
|
-
when String then Utils.assert_utf8
|
|
45
|
-
when Array then value.each { |v| validate_utf8
|
|
44
|
+
when String then Utils.assert_utf8(value, "str payload") if value.encoding == Encoding::UTF_8
|
|
45
|
+
when Array then value.each { |v| validate_utf8(v) }
|
|
46
46
|
when Hash
|
|
47
47
|
value.each do |key, val|
|
|
48
|
-
validate_utf8
|
|
49
|
-
validate_utf8
|
|
48
|
+
validate_utf8(key)
|
|
49
|
+
validate_utf8(val)
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
end
|
|
@@ -10,13 +10,11 @@ require_relative "../handle"
|
|
|
10
10
|
module Kobako
|
|
11
11
|
module Codec # :nodoc:
|
|
12
12
|
module ExtTypes # :nodoc:
|
|
13
|
-
# MessagePack ext type code reserved for Symbol
|
|
14
|
-
# (docs/wire/payload-msgpack.md § ext 0x00 — Symbol). Module-private —
|
|
13
|
+
# MessagePack ext type code reserved for Symbol. Module-private —
|
|
15
14
|
# mirrors +codec::EXT_SYMBOL+ on the
|
|
16
15
|
# Rust side.
|
|
17
16
|
EXT_SYMBOL = 0x00
|
|
18
|
-
# MessagePack ext type code reserved for Capability Handle
|
|
19
|
-
# (docs/wire/payload-msgpack.md § ext 0x01 — Handle). Module-private —
|
|
17
|
+
# MessagePack ext type code reserved for Capability Handle. Module-private —
|
|
20
18
|
# mirrors +codec::EXT_HANDLE+ on the
|
|
21
19
|
# Rust side.
|
|
22
20
|
EXT_HANDLE = 0x01
|
|
@@ -51,7 +49,7 @@ module Kobako
|
|
|
51
49
|
# unpacker would otherwise apply to invalid bytes.
|
|
52
50
|
def unpack_symbol(payload)
|
|
53
51
|
name = payload.b.force_encoding(Encoding::UTF_8)
|
|
54
|
-
Utils.assert_utf8
|
|
52
|
+
Utils.assert_utf8(name, "Symbol payload")
|
|
55
53
|
name.to_sym
|
|
56
54
|
end
|
|
57
55
|
|
|
@@ -8,8 +8,7 @@ module Kobako
|
|
|
8
8
|
module HandleWalk # :nodoc:
|
|
9
9
|
# Inclusive Integer range the msgpack gem encodes without raising
|
|
10
10
|
# +RangeError+ at encode time — signed +int 64+ minimum through
|
|
11
|
-
# unsigned +uint 64+ maximum
|
|
12
|
-
# Range).
|
|
11
|
+
# unsigned +uint 64+ maximum, the range every wire Integer fits.
|
|
13
12
|
# Anchored as a +Range+ so #primitive_type? stays a single
|
|
14
13
|
# dispatch line. This is the codec's encode domain — not to
|
|
15
14
|
# be confused with the Handle id range, which lives on
|
|
@@ -17,8 +16,7 @@ module Kobako
|
|
|
17
16
|
# represents a different concept entirely.
|
|
18
17
|
MSGPACK_INT_RANGE = (-(2**63)..((2**64) - 1))
|
|
19
18
|
|
|
20
|
-
# Whether +value+ falls in the codec's type set
|
|
21
|
-
# (docs/wire/payload-msgpack.md § Type Mapping). The +depth+ bound
|
|
19
|
+
# Whether +value+ falls in the codec's closed type set. The +depth+ bound
|
|
22
20
|
# makes a self-referential container —
|
|
23
21
|
# reachable as a cyclic Hash key on the +#run+ argument path — read as
|
|
24
22
|
# non-representable rather than looping.
|
|
@@ -32,7 +30,7 @@ module Kobako
|
|
|
32
30
|
# than crossing as a token the guest→host restore walk could not
|
|
33
31
|
# round-trip.
|
|
34
32
|
def self.deep_wrap(value, handles, depth = 0)
|
|
35
|
-
guard_nesting
|
|
33
|
+
guard_nesting(depth)
|
|
36
34
|
case value
|
|
37
35
|
when ::Array then value.map { |element| deep_wrap(element, handles, depth + 1) }
|
|
38
36
|
when ::Hash then deep_wrap_hash(value, handles, depth)
|
|
@@ -64,7 +62,7 @@ module Kobako
|
|
|
64
62
|
# A reference cycle necessarily trips the nesting bound, so a +#run+
|
|
65
63
|
# argument fails as a clean +Kobako::SandboxError+ rather than an
|
|
66
64
|
# unbounded host recursion.
|
|
67
|
-
def guard_nesting
|
|
65
|
+
def guard_nesting(depth)
|
|
68
66
|
return unless depth > MAX_NESTING_DEPTH
|
|
69
67
|
|
|
70
68
|
raise Kobako::SandboxError,
|
data/lib/kobako/codec/nesting.rb
CHANGED
|
@@ -9,12 +9,12 @@ module Kobako
|
|
|
9
9
|
module Nesting # :nodoc:
|
|
10
10
|
# Raise InvalidTypeError when +value+ nests past MAX_NESTING_DEPTH; a
|
|
11
11
|
# value nested exactly to the bound passes.
|
|
12
|
-
def self.assert_within_bound
|
|
12
|
+
def self.assert_within_bound(value, depth = 0)
|
|
13
13
|
case value
|
|
14
|
-
when ::Array then assert_members_within_bound
|
|
14
|
+
when ::Array then assert_members_within_bound(value, depth)
|
|
15
15
|
when ::Hash
|
|
16
|
-
assert_members_within_bound
|
|
17
|
-
assert_members_within_bound
|
|
16
|
+
assert_members_within_bound(value.keys, depth)
|
|
17
|
+
assert_members_within_bound(value.values, depth)
|
|
18
18
|
end
|
|
19
19
|
end
|
|
20
20
|
|
|
@@ -22,7 +22,7 @@ module Kobako
|
|
|
22
22
|
# level deeper. A member is matched by class rather than asked about
|
|
23
23
|
# itself — it may be a BasicObject — and only one that could be a
|
|
24
24
|
# container is walked into.
|
|
25
|
-
def self.assert_members_within_bound
|
|
25
|
+
def self.assert_members_within_bound(members, depth)
|
|
26
26
|
return if members.empty?
|
|
27
27
|
if depth >= MAX_NESTING_DEPTH
|
|
28
28
|
raise InvalidTypeError,
|
|
@@ -30,9 +30,9 @@ module Kobako
|
|
|
30
30
|
end
|
|
31
31
|
return unless members.any?(::Enumerable)
|
|
32
32
|
|
|
33
|
-
members.grep(::Enumerable) { |member| assert_within_bound
|
|
33
|
+
members.grep(::Enumerable) { |member| assert_within_bound(member, depth + 1) }
|
|
34
34
|
end
|
|
35
|
-
private_class_method :assert_members_within_bound
|
|
35
|
+
private_class_method :assert_members_within_bound
|
|
36
36
|
end
|
|
37
37
|
end
|
|
38
38
|
end
|
data/lib/kobako/codec/utils.rb
CHANGED
|
@@ -5,8 +5,8 @@ require_relative "error"
|
|
|
5
5
|
module Kobako
|
|
6
6
|
module Codec
|
|
7
7
|
# Counts the values a payload holds before the decoder builds any, so a
|
|
8
|
-
# guest cannot make the host build more objects than
|
|
9
|
-
#
|
|
8
|
+
# guest cannot make the host build more objects than it could hold in
|
|
9
|
+
# its own memory. Every value takes at
|
|
10
10
|
# least one byte, so a payload no longer than the budget needs no count.
|
|
11
11
|
#
|
|
12
12
|
# The count stops at the first byte it cannot frame and leaves that
|
|
@@ -30,7 +30,7 @@ module Kobako
|
|
|
30
30
|
UINT = { 1 => "C", 2 => "n", 4 => "N" }.freeze
|
|
31
31
|
private_constant :SCALARS, :COUNTS, :LENGTHS, :UINT
|
|
32
32
|
|
|
33
|
-
def self.assert_within
|
|
33
|
+
def self.assert_within(bytes, budget)
|
|
34
34
|
return if budget.nil? || bytes.bytesize <= budget
|
|
35
35
|
|
|
36
36
|
remaining = budget
|
data/lib/kobako/context.rb
CHANGED
|
@@ -56,7 +56,7 @@ module Kobako
|
|
|
56
56
|
def mount(at, filesystem, access: :read, &policy)
|
|
57
57
|
raise ArgumentError, "Kobako::Context is spent; ctx.mount is only valid inside the #eval / #run block" if @spent
|
|
58
58
|
|
|
59
|
-
Runtime.validate_mount
|
|
59
|
+
Runtime.validate_mount(at, access, filesystem)
|
|
60
60
|
mount = Catalog::Mount.new(at: at, access: access, filesystem: filesystem, policy: policy)
|
|
61
61
|
raise ArgumentError, "cannot override unmounted path #{at.inspect}" unless @grants.include?(mount)
|
|
62
62
|
|
|
@@ -77,13 +77,13 @@ module Kobako
|
|
|
77
77
|
|
|
78
78
|
def eval(code, &block) # :nodoc:
|
|
79
79
|
collect_overrides(&block) if block
|
|
80
|
-
invoke
|
|
80
|
+
invoke(:eval) { @runtime.eval(dispatch_handler, setup, code.b) }
|
|
81
81
|
end
|
|
82
82
|
|
|
83
83
|
def run(request, &block) # :nodoc:
|
|
84
84
|
collect_overrides(&block) if block
|
|
85
|
-
invoke
|
|
86
|
-
@runtime.run(dispatch_handler, setup, request.entrypoint.to_s, request.
|
|
85
|
+
invoke(:run, entrypoint: request.entrypoint) do
|
|
86
|
+
@runtime.run(dispatch_handler, setup, request.entrypoint.to_s, request.encode_payload(@handles))
|
|
87
87
|
end
|
|
88
88
|
end
|
|
89
89
|
|
|
@@ -157,7 +157,7 @@ module Kobako
|
|
|
157
157
|
# that raises propagates unwrapped and leaves the guest unrun. A
|
|
158
158
|
# could-not-start fault ran no invocation at all, so it carries no
|
|
159
159
|
# Execution and gains only the verb prefix.
|
|
160
|
-
def invoke
|
|
160
|
+
def invoke(verb, entrypoint: nil)
|
|
161
161
|
@resolved = @extensions.resolve.transform_values { |object| Transport::Exposure.of(object) }
|
|
162
162
|
begin
|
|
163
163
|
snapshot = yield
|
data/lib/kobako/filesystem.rb
CHANGED
|
@@ -5,25 +5,25 @@ module Kobako
|
|
|
5
5
|
# answered in Rust by the kind of the same name. Hand one to
|
|
6
6
|
# Sandbox#mount:
|
|
7
7
|
#
|
|
8
|
-
# sandbox.mount("/data", Kobako::Filesystem::
|
|
8
|
+
# sandbox.mount("/data", Kobako::Filesystem::Tmpfs.new("config.yml" => yaml))
|
|
9
9
|
#
|
|
10
|
-
# [<tt>
|
|
10
|
+
# [<tt>Tmpfs.new(files = {}, size: 64 MiB, nr_inodes:)</tt>]
|
|
11
11
|
# Files held in memory for as long as the object lives, seeded from a
|
|
12
|
-
# Hash of path to contents. Every invocation shares them
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
# [<tt>Directory.new(path)</tt>]
|
|
12
|
+
# Hash of path to contents. Every invocation shares them; one that
|
|
13
|
+
# should start empty mounts a new Tmpfs through Context#mount.
|
|
14
|
+
# [<tt>Bind.new(path)</tt>]
|
|
16
15
|
# A host directory and everything beneath it. Nothing outside it is
|
|
17
16
|
# reachable, a symbolic link pointing out included.
|
|
18
|
-
# [<tt>Overlay.new
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
17
|
+
# [<tt>Overlay.new(lower: [filesystem, ...], upper: tmpfs)</tt>]
|
|
18
|
+
# Read-only lower Layers, topmost first, under an upper Tmpfs that
|
|
19
|
+
# takes every change, as Linux's overlayfs stacks them. Without
|
|
20
|
+
# +upper+ the view is read-only and needs two lower Layers. It takes
|
|
21
|
+
# at most 500 lower Layers and stacks at most two Overlays deep.
|
|
22
22
|
#
|
|
23
|
-
# A
|
|
24
|
-
# at most +
|
|
23
|
+
# A Tmpfs is bounded as a tmpfs mount is: its files hold
|
|
24
|
+
# at most +size+ bytes together, and it holds at most +nr_inodes+ files and
|
|
25
25
|
# directories, its root among them; +nil+ lifts either bound. Without
|
|
26
|
-
# +
|
|
26
|
+
# +nr_inodes+ it holds one for each 4 KiB of +size+, and the root. Past
|
|
27
27
|
# either, the guest raises Errno::ENOSPC.
|
|
28
28
|
#
|
|
29
29
|
# Any object answering +open+ and +stat+ mounts too, as a Filesystem
|
|
@@ -33,6 +33,37 @@ module Kobako
|
|
|
33
33
|
# exactly, as a mount's block judges them as given. A name may hold a
|
|
34
34
|
# <tt>\\</tt>, which the guest's paths never separate on, so one joined
|
|
35
35
|
# to a Windows host path is the Filesystem's to refuse.
|
|
36
|
+
#
|
|
37
|
+
# Each method beyond +open+ and +stat+ is optional, and a change it lacks
|
|
38
|
+
# raises <tt>Errno::EROFS</tt> in the guest:
|
|
39
|
+
#
|
|
40
|
+
# [<tt>children(path)</tt>]
|
|
41
|
+
# Entry names, as Dir.children gives them.
|
|
42
|
+
# [<tt>mkdir(path)</tt>, <tt>rmdir(path)</tt>, <tt>unlink(path)</tt>, <tt>rename(from, to)</tt>]
|
|
43
|
+
# The changes a writable mount passes on.
|
|
44
|
+
#
|
|
45
|
+
# A +stat+ may also answer +dev+, else the Filesystem stands for one
|
|
46
|
+
# device, and +ftype+, which says a FIFO, a socket, or a device is one
|
|
47
|
+
# so the guest's open is refused without asking +open+.
|
|
48
|
+
#
|
|
49
|
+
# The file +open+ answers takes the guest's reads and writes, each named
|
|
50
|
+
# as +IO+ names it:
|
|
51
|
+
#
|
|
52
|
+
# [<tt>pread(length, offset)</tt>, +size+]
|
|
53
|
+
# Required: the bytes there, +nil+ or EOFError at the end, and the
|
|
54
|
+
# length.
|
|
55
|
+
# [<tt>pwrite(data, offset)</tt>, <tt>truncate(length)</tt>]
|
|
56
|
+
# The bytes written, and nothing; without them a write raises
|
|
57
|
+
# <tt>Errno::EROFS</tt>.
|
|
58
|
+
# [<tt>append(data)</tt>]
|
|
59
|
+
# Writes all of +data+ at the end in one step, so another writer's
|
|
60
|
+
# append never lands over it, and answers where the file then ends;
|
|
61
|
+
# without it an append raises <tt>Errno::ENOTSUP</tt>.
|
|
62
|
+
# [+fsync+, +close+]
|
|
63
|
+
# Nothing; without them there is nothing to flush or release.
|
|
64
|
+
# [+stat+]
|
|
65
|
+
# What <tt>stat(path)</tt> answers, for the file's times and count of
|
|
66
|
+
# names; without it the file has one name and no time.
|
|
36
67
|
module Filesystem
|
|
37
68
|
end
|
|
38
69
|
end
|
|
@@ -8,7 +8,7 @@ module Kobako
|
|
|
8
8
|
def initialize(args: [], kwargs: {})
|
|
9
9
|
raise ArgumentError, "payload args must be Array" unless args.is_a?(Array)
|
|
10
10
|
|
|
11
|
-
validate_kwargs
|
|
11
|
+
validate_kwargs(kwargs)
|
|
12
12
|
super
|
|
13
13
|
end
|
|
14
14
|
|
|
@@ -32,7 +32,7 @@ module Kobako
|
|
|
32
32
|
|
|
33
33
|
private
|
|
34
34
|
|
|
35
|
-
def validate_kwargs
|
|
35
|
+
def validate_kwargs(kwargs)
|
|
36
36
|
raise ArgumentError, "payload kwargs must be Hash" unless kwargs.is_a?(Hash)
|
|
37
37
|
|
|
38
38
|
kwargs.each_key do |key|
|
data/lib/kobako/pool.rb
CHANGED
|
@@ -20,7 +20,7 @@ module Kobako
|
|
|
20
20
|
# Raises ArgumentError when +slots+ is not a positive Integer or
|
|
21
21
|
# +checkout_timeout+ is not a positive finite number.
|
|
22
22
|
def initialize(slots:, checkout_timeout: DEFAULT_CHECKOUT_TIMEOUT_SECONDS, **sandbox_options, &setup)
|
|
23
|
-
validate_slots
|
|
23
|
+
validate_slots(slots)
|
|
24
24
|
@slots = slots
|
|
25
25
|
@checkout_timeout = normalize_checkout_timeout(checkout_timeout)
|
|
26
26
|
@sandbox_options = sandbox_options
|
|
@@ -69,13 +69,13 @@ module Kobako
|
|
|
69
69
|
return [:build, nil]
|
|
70
70
|
end
|
|
71
71
|
|
|
72
|
-
await_slot
|
|
72
|
+
await_slot(deadline)
|
|
73
73
|
[:retry, nil]
|
|
74
74
|
end
|
|
75
75
|
end
|
|
76
76
|
|
|
77
77
|
# Must run while holding +@mutex+.
|
|
78
|
-
def await_slot
|
|
78
|
+
def await_slot(deadline)
|
|
79
79
|
remaining = deadline && (deadline - monotonic_now)
|
|
80
80
|
if remaining && remaining <= 0
|
|
81
81
|
raise PoolTimeoutError,
|
|
@@ -117,7 +117,7 @@ module Kobako
|
|
|
117
117
|
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
118
118
|
end
|
|
119
119
|
|
|
120
|
-
def validate_slots
|
|
120
|
+
def validate_slots(slots)
|
|
121
121
|
return if slots.is_a?(Integer) && slots.positive?
|
|
122
122
|
|
|
123
123
|
raise ArgumentError, "slots must be a positive Integer, got #{slots.inspect}"
|
data/lib/kobako/sandbox.rb
CHANGED
|
@@ -43,7 +43,7 @@ module Kobako
|
|
|
43
43
|
@extensions = Catalog::Extensions.new
|
|
44
44
|
@grants = Catalog::Grants.new
|
|
45
45
|
@sealed = false
|
|
46
|
-
@runtime = build_runtime
|
|
46
|
+
@runtime = build_runtime
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
# Make +object+ reachable from the guest as the constant at +path+, a
|
|
@@ -62,7 +62,7 @@ module Kobako
|
|
|
62
62
|
# +Kobako+ namespace, a path that collides with an existing binding, or a
|
|
63
63
|
# call after the first invocation.
|
|
64
64
|
def bind(path, object = Unresolved)
|
|
65
|
-
refuse_once_invoked
|
|
65
|
+
refuse_once_invoked("bind")
|
|
66
66
|
@services.bind(path, object)
|
|
67
67
|
self
|
|
68
68
|
end
|
|
@@ -74,7 +74,7 @@ module Kobako
|
|
|
74
74
|
# Raises ArgumentError for a malformed Extension, a call after the first
|
|
75
75
|
# invocation, or, at the first invocation, a missing dependency.
|
|
76
76
|
def install(*extensions)
|
|
77
|
-
refuse_once_invoked
|
|
77
|
+
refuse_once_invoked("install")
|
|
78
78
|
|
|
79
79
|
extensions.each { |extension| @extensions.install(extension, snippets: @snippets, services: @services) }
|
|
80
80
|
self
|
|
@@ -85,18 +85,22 @@ module Kobako
|
|
|
85
85
|
# +:read_write+; a write beneath a read-only mount fails as
|
|
86
86
|
# <tt>Errno::EROFS</tt> in the guest. Returns +self+.
|
|
87
87
|
#
|
|
88
|
-
# A given block
|
|
89
|
-
#
|
|
90
|
-
#
|
|
88
|
+
# A given block judges each open and each change +access+ allows, asked
|
|
89
|
+
# with the guest path and +:read+ or +:read_write+, and can only refuse
|
|
90
|
+
# more; an open that may create asks +:read_write+ first, then what it
|
|
91
|
+
# opens for. A falsy answer,
|
|
91
92
|
# or a StandardError, fails that call as <tt>Errno::EACCES</tt> in the
|
|
92
93
|
# guest and leaves the script running.
|
|
93
94
|
#
|
|
95
|
+
# A path may sit beneath another mount, and the guest reaches the
|
|
96
|
+
# innermost one.
|
|
97
|
+
#
|
|
94
98
|
# Raises TypeError for a path that is no String or something other than
|
|
95
|
-
# a Filesystem, and ArgumentError for a malformed
|
|
96
|
-
#
|
|
99
|
+
# a Filesystem, and ArgumentError for a malformed or duplicate path, an
|
|
100
|
+
# unknown access, or a call after the first invocation.
|
|
97
101
|
def mount(at, filesystem, access: :read, &policy)
|
|
98
|
-
refuse_once_invoked
|
|
99
|
-
Runtime.validate_mount
|
|
102
|
+
refuse_once_invoked("mount")
|
|
103
|
+
Runtime.validate_mount(at, access, filesystem)
|
|
100
104
|
@grants.register(Catalog::Mount.new(at: at, access: access, filesystem: filesystem, policy: policy))
|
|
101
105
|
self
|
|
102
106
|
end
|
|
@@ -114,7 +118,7 @@ module Kobako
|
|
|
114
118
|
# after the first invocation. A snippet that fails to load fails every
|
|
115
119
|
# invocation with SandboxError, or BytecodeError for bytecode.
|
|
116
120
|
def preload(code: nil, name: nil, binary: nil)
|
|
117
|
-
refuse_once_invoked
|
|
121
|
+
refuse_once_invoked("preload")
|
|
118
122
|
|
|
119
123
|
@snippets.register(code: code, name: name, binary: binary)
|
|
120
124
|
self
|
|
@@ -130,7 +134,7 @@ module Kobako
|
|
|
130
134
|
# Raises ArgumentError for a malformed or duplicate flag, or a call after
|
|
131
135
|
# the first invocation.
|
|
132
136
|
def flag(name, value = "", advisory: false)
|
|
133
|
-
refuse_once_invoked
|
|
137
|
+
refuse_once_invoked("flag")
|
|
134
138
|
|
|
135
139
|
@flags.register(name, value, advisory: advisory)
|
|
136
140
|
self
|
|
@@ -145,7 +149,7 @@ module Kobako
|
|
|
145
149
|
# Handle or a non-Symbol keyword. A failed run raises as #eval does.
|
|
146
150
|
def run(target, *args, **kwargs, &block)
|
|
147
151
|
request = Transport::Run.new(entrypoint: target, args: args, kwargs: kwargs)
|
|
148
|
-
Codec.within_value_budget(@options.value_budget) {
|
|
152
|
+
Codec.within_value_budget(@options.value_budget) { begin_invocation.run(request, &block) }
|
|
149
153
|
end
|
|
150
154
|
|
|
151
155
|
# Evaluate +code+, mruby source as a String, and return the Execution,
|
|
@@ -160,27 +164,19 @@ module Kobako
|
|
|
160
164
|
def eval(code, &block)
|
|
161
165
|
raise SandboxError, "code must be a String, got #{code.class}" unless code.is_a?(String)
|
|
162
166
|
|
|
163
|
-
Codec.within_value_budget(@options.value_budget) {
|
|
167
|
+
Codec.within_value_budget(@options.value_budget) { begin_invocation.eval(code, &block) }
|
|
164
168
|
end
|
|
165
169
|
|
|
166
170
|
private
|
|
167
171
|
|
|
168
172
|
# A runtime whose declared posture falls below the request never runs
|
|
169
173
|
# guest code.
|
|
170
|
-
def build_runtime
|
|
174
|
+
def build_runtime
|
|
171
175
|
runtime = Kobako::Runtime.from_path(@wasm_path, **@options.to_h)
|
|
172
|
-
@options.enforce_floor
|
|
176
|
+
@options.enforce_floor(runtime.profile)
|
|
173
177
|
runtime
|
|
174
178
|
end
|
|
175
179
|
|
|
176
|
-
# Per-invocation state lives on the Context, so nothing an invocation
|
|
177
|
-
# does is written back onto the shared config.
|
|
178
|
-
def new_invocation
|
|
179
|
-
begin_invocation!
|
|
180
|
-
Context.new(runtime: @runtime, services: @services, extensions: @extensions, grants: @grants,
|
|
181
|
-
preamble: preamble(@snippets.entries))
|
|
182
|
-
end
|
|
183
|
-
|
|
184
180
|
# The one place the Preamble's parts are put together, for a compile
|
|
185
181
|
# and for every invocation alike.
|
|
186
182
|
def preamble(snippets)
|
|
@@ -189,17 +185,21 @@ module Kobako
|
|
|
189
185
|
|
|
190
186
|
# Registration closes at the first invocation, so every invocation ships
|
|
191
187
|
# the same Services, snippets, flags, Extensions and mounts.
|
|
192
|
-
def refuse_once_invoked
|
|
188
|
+
def refuse_once_invoked(verb)
|
|
193
189
|
raise ArgumentError, "cannot #{verb} after first Sandbox invocation" if @sealed
|
|
194
190
|
end
|
|
195
191
|
|
|
196
192
|
# Sealing before the dependency check keeps a Sandbox whose check failed
|
|
197
193
|
# closed to further registration. The sealed snippets are compiled once,
|
|
198
|
-
# before the first invocation replays them.
|
|
199
|
-
|
|
194
|
+
# before the first invocation replays them. Per-invocation state lives on
|
|
195
|
+
# the Context it answers, so nothing an invocation does is written back
|
|
196
|
+
# onto the shared config.
|
|
197
|
+
def begin_invocation
|
|
200
198
|
@sealed = true
|
|
201
|
-
@extensions.assert_dependencies
|
|
199
|
+
@extensions.assert_dependencies
|
|
202
200
|
@snippets.compile { |entries| @runtime.compile(preamble(entries)) }
|
|
201
|
+
Context.new(runtime: @runtime, services: @services, extensions: @extensions, grants: @grants,
|
|
202
|
+
preamble: preamble(@snippets.entries))
|
|
203
203
|
end
|
|
204
204
|
end
|
|
205
205
|
end
|
|
@@ -74,8 +74,7 @@ module Kobako
|
|
|
74
74
|
end
|
|
75
75
|
|
|
76
76
|
# The least guest memory one value occupies, so a guest payload may hold
|
|
77
|
-
# one value for each of these bytes of +memory_limit
|
|
78
|
-
# (docs/wire/payload-msgpack.md § Value Budget).
|
|
77
|
+
# one value for each of these bytes of +memory_limit+.
|
|
79
78
|
VALUE_BYTES = 16
|
|
80
79
|
private_constant :VALUE_BYTES
|
|
81
80
|
|
|
@@ -85,7 +84,7 @@ module Kobako
|
|
|
85
84
|
|
|
86
85
|
# Both fallbacks fail closed: an unknown declaration ranks below every
|
|
87
86
|
# request, and an unknown request refuses every declaration.
|
|
88
|
-
def enforce_floor
|
|
87
|
+
def enforce_floor(declared) # :nodoc:
|
|
89
88
|
return if (PROFILES.index(declared) || -1) >= (PROFILES.index(profile) || PROFILES.size)
|
|
90
89
|
|
|
91
90
|
raise Kobako::SetupError, "runtime declares isolation profile #{declared.inspect}, " \
|
|
@@ -4,7 +4,7 @@ module Kobako
|
|
|
4
4
|
module Snippet
|
|
5
5
|
class Bytecode < Data.define(:body) # :nodoc:
|
|
6
6
|
# Names the snippet form the guest replays this entry as. The wire's
|
|
7
|
-
# discriminant byte is assigned by the
|
|
7
|
+
# discriminant byte is assigned by the Envelope, not here.
|
|
8
8
|
KIND = :bytecode
|
|
9
9
|
end
|
|
10
10
|
end
|
|
@@ -4,7 +4,7 @@ module Kobako
|
|
|
4
4
|
module Snippet
|
|
5
5
|
class Source < Data.define(:name, :body) # :nodoc:
|
|
6
6
|
# Names the snippet form the guest replays this entry as. The wire's
|
|
7
|
-
# discriminant byte is assigned by the
|
|
7
|
+
# discriminant byte is assigned by the Envelope, not here.
|
|
8
8
|
KIND = :source
|
|
9
9
|
end
|
|
10
10
|
end
|
|
@@ -60,7 +60,7 @@ module Kobako
|
|
|
60
60
|
# stays uncaught and traps the invocation rather than being masked as a
|
|
61
61
|
# rescuable fault.
|
|
62
62
|
rescue StandardError => e
|
|
63
|
-
[:fault, *
|
|
63
|
+
[:fault, *fault_of(e, failure)] # : [Symbol, String, String?]
|
|
64
64
|
ensure
|
|
65
65
|
yielder&.invalidate
|
|
66
66
|
end
|
|
@@ -97,7 +97,7 @@ module Kobako
|
|
|
97
97
|
# The guest's own block failing is not the Service's to report at
|
|
98
98
|
# all, so the Yielder that raised it is asked first — it recognises
|
|
99
99
|
# its own by identity and words the failure the guest's way.
|
|
100
|
-
def
|
|
100
|
+
def fault_of(error, failure)
|
|
101
101
|
block_failure = failure.fault_text(error)
|
|
102
102
|
return fault("block", block_failure) if block_failure
|
|
103
103
|
|
|
@@ -111,7 +111,7 @@ module Kobako
|
|
|
111
111
|
# unsplatted for methods that take no keywords.
|
|
112
112
|
def invoke(exposure, method, args, kwargs, yielder)
|
|
113
113
|
name = method.to_sym
|
|
114
|
-
reject_unreachable
|
|
114
|
+
reject_unreachable(exposure, name)
|
|
115
115
|
target = exposure.object
|
|
116
116
|
block = yielder&.to_proc
|
|
117
117
|
if kwargs.empty?
|
|
@@ -124,7 +124,7 @@ module Kobako
|
|
|
124
124
|
# Both the ambient-surface floor and the reference's Exposure answer
|
|
125
125
|
# through Reflection, so a rejected name discloses nothing about
|
|
126
126
|
# which of the two refused.
|
|
127
|
-
def reject_unreachable
|
|
127
|
+
def reject_unreachable(exposure, name)
|
|
128
128
|
reason = Reflection.refusal(exposure, name)
|
|
129
129
|
raise UndefinedTargetError, reason if reason
|
|
130
130
|
end
|
|
@@ -163,7 +163,7 @@ module Kobako
|
|
|
163
163
|
# Service can change — so it is named here, where the direction is
|
|
164
164
|
# known, instead of falling to the boundary's codec floor.
|
|
165
165
|
def encode_ok(value, handles)
|
|
166
|
-
Kobako::Codec::Nesting.assert_within_bound
|
|
166
|
+
Kobako::Codec::Nesting.assert_within_bound(value)
|
|
167
167
|
Kobako::Codec::Encoder.encode(value)
|
|
168
168
|
rescue Kobako::Codec::UnsupportedTypeError
|
|
169
169
|
encode_ok(handles.alloc(value), handles)
|
|
@@ -32,7 +32,7 @@ module Kobako
|
|
|
32
32
|
# once (a Handle table), so two objects of one class enumerate it once.
|
|
33
33
|
def self.of(object, surfaces = {})
|
|
34
34
|
klass = CLASS_OF.bind_call(object)
|
|
35
|
-
names = narrows_itself?(object, klass) ? nil :
|
|
35
|
+
names = narrows_itself?(object, klass) ? nil : resolve_default_names(object, klass, surfaces)
|
|
36
36
|
new(object: object, names: names)
|
|
37
37
|
end
|
|
38
38
|
|
|
@@ -59,7 +59,7 @@ module Kobako
|
|
|
59
59
|
|
|
60
60
|
# A callable keeps its callable names; a class-level surface or a
|
|
61
61
|
# forwarder has no surface of its own to expose.
|
|
62
|
-
def
|
|
62
|
+
def resolve_default_names(object, klass, surfaces)
|
|
63
63
|
case object
|
|
64
64
|
when Proc, Method then CALLABLE
|
|
65
65
|
when Module, Delegator then NOTHING
|
|
@@ -77,20 +77,20 @@ module Kobako
|
|
|
77
77
|
# A name with no method never reaches +instance_method+, which would
|
|
78
78
|
# keep a guest-chosen name as a Symbol for the life of the process.
|
|
79
79
|
def ambient_refusal(target, name)
|
|
80
|
-
return
|
|
80
|
+
return service_refusal(name) if name == :method_missing
|
|
81
81
|
|
|
82
82
|
klass = lookup_class(target)
|
|
83
|
-
return
|
|
83
|
+
return public_method_refusal(target, name, klass) if klass.public_method_defined?(name)
|
|
84
84
|
return "no public method #{name.inspect} on target" unless target.respond_to?(name)
|
|
85
|
-
return
|
|
85
|
+
return service_refusal(name) if klass.method_defined?(name) || klass.private_method_defined?(name)
|
|
86
86
|
|
|
87
87
|
owner_refusal(target, name, klass)
|
|
88
88
|
end
|
|
89
89
|
|
|
90
|
-
def
|
|
90
|
+
def public_method_refusal(target, name, klass)
|
|
91
91
|
method = klass.instance_method(name)
|
|
92
92
|
owner_refusal(target, name, method.owner) ||
|
|
93
|
-
(authored_for?(target, name, method, klass) ? nil :
|
|
93
|
+
(authored_for?(target, name, method, klass) ? nil : service_refusal(name))
|
|
94
94
|
end
|
|
95
95
|
|
|
96
96
|
# A bound callable keeps its callable names and a record its member
|
|
@@ -102,7 +102,7 @@ module Kobako
|
|
|
102
102
|
(klass <= Struct || klass <= Data) && target.members.include?(name)
|
|
103
103
|
end
|
|
104
104
|
|
|
105
|
-
def
|
|
105
|
+
def service_refusal(name)
|
|
106
106
|
"method #{name.inspect} is not a Service method"
|
|
107
107
|
end
|
|
108
108
|
|
|
@@ -110,7 +110,7 @@ module Kobako
|
|
|
110
110
|
return nil unless ambient_owner?(owner, target)
|
|
111
111
|
return nil if GADGET_OWNERS.include?(owner) && CALLABLE_ALLOW.include?(name)
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
service_refusal(name)
|
|
114
114
|
end
|
|
115
115
|
|
|
116
116
|
# The class a method lookup on +target+ starts at. A plain object's
|