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
|
@@ -7,7 +7,7 @@ module Kobako
|
|
|
7
7
|
module Catalog
|
|
8
8
|
class Snippets # :nodoc:
|
|
9
9
|
def initialize
|
|
10
|
-
@entries = [] # : Array[Kobako::Snippet::Source | Kobako::Snippet::
|
|
10
|
+
@entries = [] # : Array[Kobako::Snippet::Source | Kobako::Snippet::Bytecode]
|
|
11
11
|
@compiled = {} # : Hash[Kobako::Snippet::Source, String]
|
|
12
12
|
@compile_tried = false
|
|
13
13
|
end
|
|
@@ -40,21 +40,17 @@ module Kobako
|
|
|
40
40
|
if binary
|
|
41
41
|
raise ArgumentError, "cannot combine binary: with code: / name:" if code || name
|
|
42
42
|
|
|
43
|
-
register_binary
|
|
43
|
+
register_binary(binary)
|
|
44
44
|
else
|
|
45
|
-
register_source
|
|
45
|
+
register_source(code, name)
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
private
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def register_source!(code, name)
|
|
55
|
-
code, name = ensure_source_args!(code, name)
|
|
56
|
-
body = code.dup.force_encoding(Encoding::UTF_8)
|
|
57
|
-
raise ArgumentError, "code must be UTF-8 text" unless body.valid_encoding?
|
|
51
|
+
def register_source(code, name)
|
|
52
|
+
code, name = ensure_source_args(code, name)
|
|
53
|
+
body = Catalog.to_utf8(code, "code")
|
|
58
54
|
|
|
59
55
|
name_sym = normalize_name(name)
|
|
60
56
|
if @entries.any? { |e| e.is_a?(Snippet::Source) && e.name == name_sym }
|
|
@@ -67,7 +63,7 @@ module Kobako
|
|
|
67
63
|
|
|
68
64
|
# The +code:+ type check runs first so an explicit +code: nil+ reads
|
|
69
65
|
# as a type error rather than a missing keyword.
|
|
70
|
-
def ensure_source_args
|
|
66
|
+
def ensure_source_args(code, name)
|
|
71
67
|
raise ArgumentError, "missing keyword: code: + name:, or binary:" if code.nil? && name.nil?
|
|
72
68
|
raise ArgumentError, "code must be a String, got #{code.class}" unless code.is_a?(String)
|
|
73
69
|
raise ArgumentError, "missing keyword: name:" if name.nil?
|
|
@@ -77,10 +73,10 @@ module Kobako
|
|
|
77
73
|
|
|
78
74
|
# Kept as bytes whatever encoding the caller tagged them with, since
|
|
79
75
|
# the guest reads them as bytecode rather than text.
|
|
80
|
-
def register_binary
|
|
76
|
+
def register_binary(bytes)
|
|
81
77
|
raise ArgumentError, "binary must be a String, got #{bytes.class}" unless bytes.is_a?(String)
|
|
82
78
|
|
|
83
|
-
@entries << Snippet::
|
|
79
|
+
@entries << Snippet::Bytecode.new(body: bytes.dup.force_encoding(Encoding::ASCII_8BIT))
|
|
84
80
|
nil
|
|
85
81
|
end
|
|
86
82
|
|
|
@@ -90,9 +86,9 @@ module Kobako
|
|
|
90
86
|
case entry
|
|
91
87
|
when Snippet::Source
|
|
92
88
|
compiled = @compiled[entry]
|
|
93
|
-
compiled ? [Snippet::
|
|
89
|
+
compiled ? [Snippet::Bytecode::KIND, nil, compiled] : [Snippet::Source::KIND, entry.name.to_s, entry.body]
|
|
94
90
|
else
|
|
95
|
-
[Snippet::
|
|
91
|
+
[Snippet::Bytecode::KIND, nil, entry.body]
|
|
96
92
|
end
|
|
97
93
|
end
|
|
98
94
|
|
data/lib/kobako/catalog.rb
CHANGED
|
@@ -5,8 +5,19 @@ require_relative "catalog/services"
|
|
|
5
5
|
require_relative "catalog/snippets"
|
|
6
6
|
require_relative "catalog/flags"
|
|
7
7
|
require_relative "catalog/extensions"
|
|
8
|
+
require_relative "catalog/mount"
|
|
9
|
+
require_relative "catalog/grants"
|
|
8
10
|
|
|
9
11
|
module Kobako
|
|
10
12
|
module Catalog # :nodoc:
|
|
13
|
+
# The native side carries a registered text only as UTF-8, so text read
|
|
14
|
+
# as bytes is re-tagged, and bytes that are not UTF-8 are refused at
|
|
15
|
+
# registration rather than failing every invocation.
|
|
16
|
+
def self.to_utf8(text, what)
|
|
17
|
+
utf8 = text.dup.force_encoding(Encoding::UTF_8)
|
|
18
|
+
raise ArgumentError, "#{what} must be UTF-8 text" unless utf8.valid_encoding?
|
|
19
|
+
|
|
20
|
+
utf8
|
|
21
|
+
end
|
|
11
22
|
end
|
|
12
23
|
end
|
data/lib/kobako/codec/decoder.rb
CHANGED
|
@@ -13,8 +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(bytes, State.current.value_budget)
|
|
16
17
|
value = FACTORY.load(bytes.b)
|
|
17
|
-
validate_utf8
|
|
18
|
+
validate_utf8(value)
|
|
18
19
|
block_given? ? yield(value) : value
|
|
19
20
|
# msgpack gem raises the format/type errors below; +ArgumentError+
|
|
20
21
|
# covers a yielded block's Value Object invariants — a wire violation
|
|
@@ -29,23 +30,23 @@ module Kobako
|
|
|
29
30
|
raise TruncatedInputError, e.message
|
|
30
31
|
end
|
|
31
32
|
|
|
32
|
-
# +str+ family payloads must be UTF-8
|
|
33
|
-
#
|
|
33
|
+
# +str+ family payloads must be UTF-8, since a sender carries bytes
|
|
34
|
+
# that are not UTF-8 as +bin+. The msgpack gem
|
|
34
35
|
# returns UTF-8-tagged Strings for
|
|
35
36
|
# str family but does not validate the bytes; +bin+ family decodes
|
|
36
37
|
# to ASCII-8BIT. Walk the tree once and reject invalid UTF-8 in any
|
|
37
|
-
# str-typed leaf via Utils.assert_utf8
|
|
38
|
+
# str-typed leaf via Utils.assert_utf8.
|
|
38
39
|
class << self
|
|
39
40
|
private
|
|
40
41
|
|
|
41
|
-
def validate_utf8
|
|
42
|
+
def validate_utf8(value)
|
|
42
43
|
case value
|
|
43
|
-
when String then Utils.assert_utf8
|
|
44
|
-
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) }
|
|
45
46
|
when Hash
|
|
46
47
|
value.each do |key, val|
|
|
47
|
-
validate_utf8
|
|
48
|
-
validate_utf8
|
|
48
|
+
validate_utf8(key)
|
|
49
|
+
validate_utf8(val)
|
|
49
50
|
end
|
|
50
51
|
end
|
|
51
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
|
|
|
@@ -63,7 +61,7 @@ module Kobako
|
|
|
63
61
|
# shape. The sighting is recorded so a Handle-free decode can skip the
|
|
64
62
|
# downstream resolution walk.
|
|
65
63
|
def unpack_handle(payload, state)
|
|
66
|
-
state.record_handle
|
|
64
|
+
state.record_handle
|
|
67
65
|
bytes = payload.b
|
|
68
66
|
raise InvalidTypeError, "Handle payload must be 4 bytes, got #{bytes.bytesize}" unless bytes.bytesize == 4
|
|
69
67
|
|
|
@@ -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/state.rb
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module Kobako
|
|
4
4
|
module Codec
|
|
5
|
-
# Whether a Capability Handle crossed the decode in flight,
|
|
6
|
-
# thread so plain instance
|
|
5
|
+
# Whether a Capability Handle crossed the decode in flight, and the
|
|
6
|
+
# value budget a decode is held to, kept per thread so plain instance
|
|
7
|
+
# variables stay sound.
|
|
7
8
|
class State # :nodoc:
|
|
8
9
|
# Thread-local slot holding the calling thread's State.
|
|
9
10
|
STATE_KEY = :__kobako_codec_state__
|
|
@@ -18,6 +19,21 @@ module Kobako
|
|
|
18
19
|
|
|
19
20
|
def initialize
|
|
20
21
|
@carried_handle = false
|
|
22
|
+
@value_budget = nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# The most values a decode on this thread may build, or nil for no
|
|
26
|
+
# budget.
|
|
27
|
+
attr_reader :value_budget
|
|
28
|
+
|
|
29
|
+
# A nested invocation on the same thread restores the outer one's
|
|
30
|
+
# budget when it returns.
|
|
31
|
+
def within_value_budget(budget)
|
|
32
|
+
outer = @value_budget
|
|
33
|
+
@value_budget = budget
|
|
34
|
+
yield
|
|
35
|
+
ensure
|
|
36
|
+
@value_budget = outer
|
|
21
37
|
end
|
|
22
38
|
|
|
23
39
|
# ExtTypes#unpack_handle is the sole chokepoint every Handle passes
|
|
@@ -30,7 +46,7 @@ module Kobako
|
|
|
30
46
|
|
|
31
47
|
# Record that an ext 0x01 Capability Handle crossed the current
|
|
32
48
|
# decode; #track_handles reports it to the bracketing caller.
|
|
33
|
-
def record_handle
|
|
49
|
+
def record_handle
|
|
34
50
|
@carried_handle = true
|
|
35
51
|
end
|
|
36
52
|
end
|
data/lib/kobako/codec/utils.rb
CHANGED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "error"
|
|
4
|
+
|
|
5
|
+
module Kobako
|
|
6
|
+
module Codec
|
|
7
|
+
# Counts the values a payload holds before the decoder builds any, so a
|
|
8
|
+
# guest cannot make the host build more objects than it could hold in
|
|
9
|
+
# its own memory. Every value takes at
|
|
10
|
+
# least one byte, so a payload no longer than the budget needs no count.
|
|
11
|
+
#
|
|
12
|
+
# The count stops at the first byte it cannot frame and leaves that
|
|
13
|
+
# payload for the decoder to refuse in its own words.
|
|
14
|
+
module ValueBudget # :nodoc:
|
|
15
|
+
# Marker to the body width of a scalar with no length field.
|
|
16
|
+
SCALARS = {
|
|
17
|
+
0xc0 => 0, 0xc2 => 0, 0xc3 => 0, 0xca => 4, 0xcb => 8,
|
|
18
|
+
0xcc => 1, 0xcd => 2, 0xce => 4, 0xcf => 8,
|
|
19
|
+
0xd0 => 1, 0xd1 => 2, 0xd2 => 4, 0xd3 => 8,
|
|
20
|
+
0xd4 => 2, 0xd5 => 3, 0xd6 => 5, 0xd7 => 9, 0xd8 => 17
|
|
21
|
+
}.freeze
|
|
22
|
+
# Marker to a container's count width and the values each entry holds.
|
|
23
|
+
COUNTS = { 0xdc => [2, 1], 0xdd => [4, 1], 0xde => [2, 2], 0xdf => [4, 2] }.freeze
|
|
24
|
+
# Marker to a str, bin, or ext's length width and type tag width.
|
|
25
|
+
LENGTHS = {
|
|
26
|
+
0xc4 => [1, 0], 0xc5 => [2, 0], 0xc6 => [4, 0],
|
|
27
|
+
0xd9 => [1, 0], 0xda => [2, 0], 0xdb => [4, 0],
|
|
28
|
+
0xc7 => [1, 1], 0xc8 => [2, 1], 0xc9 => [4, 1]
|
|
29
|
+
}.freeze
|
|
30
|
+
UINT = { 1 => "C", 2 => "n", 4 => "N" }.freeze
|
|
31
|
+
private_constant :SCALARS, :COUNTS, :LENGTHS, :UINT
|
|
32
|
+
|
|
33
|
+
def self.assert_within(bytes, budget)
|
|
34
|
+
return if budget.nil? || bytes.bytesize <= budget
|
|
35
|
+
|
|
36
|
+
remaining = budget
|
|
37
|
+
pending = 1
|
|
38
|
+
pos = 0
|
|
39
|
+
while pending.positive?
|
|
40
|
+
pos, children = step(bytes, pos)
|
|
41
|
+
return if pos.nil?
|
|
42
|
+
raise InvalidTypeError, "payload holds more than #{budget} values" if (remaining -= 1).negative?
|
|
43
|
+
|
|
44
|
+
pending += children - 1
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# The position after the value at +pos+, nil for one the payload
|
|
49
|
+
# cannot frame, and the values it holds directly.
|
|
50
|
+
def self.step(bytes, pos)
|
|
51
|
+
marker = bytes.getbyte(pos) or return [nil, 0]
|
|
52
|
+
pos += 1
|
|
53
|
+
case marker
|
|
54
|
+
when 0x00..0x7f, 0xe0..0xff then [pos, 0]
|
|
55
|
+
when 0x80..0x8f then [pos, (marker & 0x0f) * 2]
|
|
56
|
+
when 0x90..0x9f then [pos, marker & 0x0f]
|
|
57
|
+
when 0xa0..0xbf then [pos + (marker & 0x1f), 0]
|
|
58
|
+
else step_framed(bytes, pos, marker)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def self.step_framed(bytes, pos, marker)
|
|
63
|
+
return [pos + SCALARS.fetch(marker), 0] if SCALARS.key?(marker)
|
|
64
|
+
|
|
65
|
+
width, per = COUNTS[marker]
|
|
66
|
+
return [pos + width, uint(bytes, pos, width) * per] if width && per
|
|
67
|
+
|
|
68
|
+
width, tag = LENGTHS[marker]
|
|
69
|
+
return [nil, 0] unless width && tag
|
|
70
|
+
|
|
71
|
+
[pos + width + tag + uint(bytes, pos, width), 0]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# A length cut short reads as zero, so the count runs off the end and
|
|
75
|
+
# stops there.
|
|
76
|
+
def self.uint(bytes, pos, width)
|
|
77
|
+
slice = bytes.byteslice(pos, width).to_s
|
|
78
|
+
return 0 if slice.bytesize < width
|
|
79
|
+
|
|
80
|
+
slice.unpack1(UINT.fetch(width)) # : Integer
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private_class_method :step, :step_framed, :uint
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
data/lib/kobako/codec.rb
CHANGED
|
@@ -5,6 +5,7 @@ require_relative "codec/utils"
|
|
|
5
5
|
require_relative "codec/handle_walk"
|
|
6
6
|
require_relative "codec/nesting"
|
|
7
7
|
require_relative "codec/state"
|
|
8
|
+
require_relative "codec/value_budget"
|
|
8
9
|
require_relative "codec/ext_types"
|
|
9
10
|
require_relative "codec/encoder"
|
|
10
11
|
require_relative "codec/decoder"
|
|
@@ -23,5 +24,11 @@ module Kobako
|
|
|
23
24
|
def self.track_handles(&block)
|
|
24
25
|
State.current.track_handles(&block)
|
|
25
26
|
end
|
|
27
|
+
|
|
28
|
+
# Holds every decode the block runs on this thread to +budget+ values,
|
|
29
|
+
# or to none when +budget+ is nil.
|
|
30
|
+
def self.within_value_budget(budget, &block)
|
|
31
|
+
State.current.within_value_budget(budget, &block)
|
|
32
|
+
end
|
|
26
33
|
end
|
|
27
34
|
end
|
data/lib/kobako/context.rb
CHANGED
|
@@ -17,12 +17,12 @@ module Kobako
|
|
|
17
17
|
#
|
|
18
18
|
# sandbox.eval("Req::Current.user_id") { |ctx| ctx.bind("Req::Current", request) }
|
|
19
19
|
class Context
|
|
20
|
-
def initialize(runtime:, services:,
|
|
20
|
+
def initialize(runtime:, services:, extensions:, grants:, preamble:) # :nodoc:
|
|
21
21
|
@runtime = runtime
|
|
22
22
|
@services = services
|
|
23
|
-
@snippets = snippets
|
|
24
|
-
@flags = flags
|
|
25
23
|
@extensions = extensions
|
|
24
|
+
@grants = grants
|
|
25
|
+
@preamble = preamble
|
|
26
26
|
@resolved = {} # : Hash[String, Transport::Exposure]
|
|
27
27
|
@overrides = {} # : Hash[String, Transport::Exposure]
|
|
28
28
|
@handles = Catalog::Handles.new
|
|
@@ -45,6 +45,26 @@ module Kobako
|
|
|
45
45
|
self
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
+
# Use +filesystem+ for the mount at +at+ during this invocation only,
|
|
49
|
+
# with its own +access+ and block, as Sandbox#mount takes them. Returns
|
|
50
|
+
# +self+.
|
|
51
|
+
#
|
|
52
|
+
# Raises TypeError for a path that is no String or something other than
|
|
53
|
+
# a Filesystem, and ArgumentError when nothing was mounted at +at+ on the
|
|
54
|
+
# Sandbox, for a malformed path or an unknown access, or when called
|
|
55
|
+
# after the block has returned.
|
|
56
|
+
def mount(at, filesystem, access: :read, &policy)
|
|
57
|
+
raise ArgumentError, "Kobako::Context is spent; ctx.mount is only valid inside the #eval / #run block" if @spent
|
|
58
|
+
|
|
59
|
+
Runtime.validate_mount(at, access, filesystem)
|
|
60
|
+
mount = Catalog::Mount.new(at: at, access: access, filesystem: filesystem, policy: policy)
|
|
61
|
+
raise ArgumentError, "cannot override unmounted path #{at.inspect}" unless @grants.include?(mount)
|
|
62
|
+
|
|
63
|
+
mounts = @mounts ||= {} # : Hash[String, Catalog::Mount]
|
|
64
|
+
mounts[mount.key] = mount
|
|
65
|
+
self
|
|
66
|
+
end
|
|
67
|
+
|
|
48
68
|
# A fillable left unfilled raises like an unbound path, so the guest's
|
|
49
69
|
# call fails closed instead of reaching the sentinel.
|
|
50
70
|
def lookup(path) # :nodoc:
|
|
@@ -57,21 +77,25 @@ module Kobako
|
|
|
57
77
|
|
|
58
78
|
def eval(code, &block) # :nodoc:
|
|
59
79
|
collect_overrides(&block) if block
|
|
60
|
-
invoke
|
|
61
|
-
@runtime.eval(dispatch_handler, @services.paths, @snippets.entries, @flags.entries, code.b)
|
|
62
|
-
end
|
|
80
|
+
invoke(:eval) { @runtime.eval(dispatch_handler, setup, code.b) }
|
|
63
81
|
end
|
|
64
82
|
|
|
65
83
|
def run(request, &block) # :nodoc:
|
|
66
84
|
collect_overrides(&block) if block
|
|
67
|
-
invoke
|
|
68
|
-
@runtime.run(dispatch_handler,
|
|
69
|
-
request.entrypoint.to_s, request.payload(@handles))
|
|
85
|
+
invoke(:run, entrypoint: request.entrypoint) do
|
|
86
|
+
@runtime.run(dispatch_handler, setup, request.entrypoint.to_s, request.encode_payload(@handles))
|
|
70
87
|
end
|
|
71
88
|
end
|
|
72
89
|
|
|
73
90
|
private
|
|
74
91
|
|
|
92
|
+
# What the Runtime builds the invocation from: the Preamble's parts,
|
|
93
|
+
# then this invocation's mounts.
|
|
94
|
+
def setup
|
|
95
|
+
paths, snippets, flags = @preamble
|
|
96
|
+
[paths, snippets, flags, @grants.entries(@mounts || {})]
|
|
97
|
+
end
|
|
98
|
+
|
|
75
99
|
# The Context is spent once the block returns, so a captured +ctx+ used
|
|
76
100
|
# later raises. A block that raises propagates before the guest drives,
|
|
77
101
|
# so the guest never runs and no Execution is produced.
|
|
@@ -83,17 +107,20 @@ module Kobako
|
|
|
83
107
|
|
|
84
108
|
# Handed to the Runtime as a call argument, so the Runtime holds no
|
|
85
109
|
# dispatch state and the Proc stays GC-rooted for the synchronous call.
|
|
110
|
+
# Built once per invocation, so every Call it answers shares one
|
|
111
|
+
# BlockFailure.
|
|
86
112
|
def dispatch_handler
|
|
113
|
+
failure = Transport::BlockFailure.new
|
|
87
114
|
lambda do |target, method_name, block_given, payload, guest_yielder|
|
|
88
115
|
call = Transport::Call.new(target: target, method_name: method_name,
|
|
89
116
|
block_given: block_given, payload: payload)
|
|
90
|
-
Transport::Dispatcher.dispatch(call, self, @handles, guest_yielder)
|
|
117
|
+
Transport::Dispatcher.dispatch(call, self, @handles, failure, guest_yielder)
|
|
91
118
|
end
|
|
92
119
|
end
|
|
93
120
|
|
|
94
121
|
# Every Snapshot carries usage and captures, trap or not, so they stay
|
|
95
122
|
# readable after a rescued trap.
|
|
96
|
-
def populate_observability
|
|
123
|
+
def populate_observability(snapshot)
|
|
97
124
|
@usage = Usage.new(wall_time: snapshot.wall_time, memory_peak: snapshot.memory_peak)
|
|
98
125
|
@stdout_capture = Capture.new(bytes: snapshot.stdout, truncated: snapshot.stdout_truncated?)
|
|
99
126
|
@stderr_capture = Capture.new(bytes: snapshot.stderr, truncated: snapshot.stderr_truncated?)
|
|
@@ -130,14 +157,14 @@ module Kobako
|
|
|
130
157
|
# that raises propagates unwrapped and leaves the guest unrun. A
|
|
131
158
|
# could-not-start fault ran no invocation at all, so it carries no
|
|
132
159
|
# Execution and gains only the verb prefix.
|
|
133
|
-
def invoke
|
|
160
|
+
def invoke(verb, entrypoint: nil)
|
|
134
161
|
@resolved = @extensions.resolve.transform_values { |object| Transport::Exposure.of(object) }
|
|
135
162
|
begin
|
|
136
163
|
snapshot = yield
|
|
137
164
|
rescue Kobako::TrapError => e
|
|
138
165
|
raise with_verb(e, verb)
|
|
139
166
|
end
|
|
140
|
-
populate_observability
|
|
167
|
+
populate_observability(snapshot)
|
|
141
168
|
trap = snapshot.trap_error
|
|
142
169
|
return settle_outcome(snapshot, verb, entrypoint) unless trap
|
|
143
170
|
|
data/lib/kobako/errors.rb
CHANGED
|
@@ -8,11 +8,7 @@ module Kobako
|
|
|
8
8
|
# The base of every error kobako raises.
|
|
9
9
|
class Error < StandardError; end
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
# rescue reads its captures and usage as a successful caller would.
|
|
13
|
-
# +execution+ is +nil+ when the failure came before the guest ran.
|
|
14
|
-
module CarriesExecution
|
|
15
|
-
# The Execution of the run that failed.
|
|
11
|
+
module Recorded # :nodoc:
|
|
16
12
|
attr_reader :execution
|
|
17
13
|
|
|
18
14
|
def with_execution(execution) # :nodoc:
|
|
@@ -23,8 +19,11 @@ module Kobako
|
|
|
23
19
|
|
|
24
20
|
# The engine stopped the guest: a trap, a cap reached, or an outcome that
|
|
25
21
|
# could not be read. The next invocation on the same Sandbox runs as usual.
|
|
22
|
+
# +execution+ is the failed run's Execution, so a rescue reads its captures
|
|
23
|
+
# and usage as a successful caller would; it is +nil+ when the failure came
|
|
24
|
+
# before the guest ran.
|
|
26
25
|
class TrapError < Error
|
|
27
|
-
include
|
|
26
|
+
include Recorded
|
|
28
27
|
end
|
|
29
28
|
|
|
30
29
|
# The invocation ran past its +timeout+.
|
|
@@ -41,10 +40,7 @@ module Kobako
|
|
|
41
40
|
# The Guest Binary is missing at +wasm_path+.
|
|
42
41
|
class ModuleNotBuiltError < SetupError; end
|
|
43
42
|
|
|
44
|
-
|
|
45
|
-
module Diagnosable
|
|
46
|
-
# Where the failure came from (<tt>"sandbox"</tt> or <tt>"service"</tt>),
|
|
47
|
-
# the name of the class that raised it, and its backtrace.
|
|
43
|
+
module Diagnosable # :nodoc:
|
|
48
44
|
attr_reader :origin, :klass, :backtrace_lines
|
|
49
45
|
|
|
50
46
|
def initialize(message, origin: nil, klass: nil, backtrace_lines: nil) # :nodoc:
|
|
@@ -57,15 +53,19 @@ module Kobako
|
|
|
57
53
|
|
|
58
54
|
# The guest failed: the script raised or did not compile, the Guest Binary
|
|
59
55
|
# could not start the run, or what it produced could not be read.
|
|
56
|
+
# +origin+ (<tt>"sandbox"</tt> or <tt>"service"</tt>), +klass+ and
|
|
57
|
+
# +backtrace_lines+ say where it came from, which class raised it, and its
|
|
58
|
+
# backtrace. +execution+ is the failed run's Execution, as on TrapError.
|
|
60
59
|
class SandboxError < Error
|
|
61
60
|
include Diagnosable
|
|
62
|
-
include
|
|
61
|
+
include Recorded
|
|
63
62
|
end
|
|
64
63
|
|
|
65
|
-
# A Service call failed and the guest left it unrescued.
|
|
64
|
+
# A Service call failed and the guest left it unrescued. It carries
|
|
65
|
+
# +origin+, +klass+, +backtrace_lines+ and +execution+ as SandboxError does.
|
|
66
66
|
class ServiceError < Error
|
|
67
67
|
include Diagnosable
|
|
68
|
-
include
|
|
68
|
+
include Recorded
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
# The call reached no Service method: nothing is bound there, the Handle
|
|
@@ -73,7 +73,8 @@ module Kobako
|
|
|
73
73
|
# same on purpose, so a target discloses nothing about what it defines.
|
|
74
74
|
class NoServiceError < ServiceError; end
|
|
75
75
|
|
|
76
|
-
# The
|
|
76
|
+
# The Service method refused its arguments: the call would not bind, or
|
|
77
|
+
# the method raised +ArgumentError+ over what it was given.
|
|
77
78
|
class ServiceArgumentError < ServiceError; end
|
|
78
79
|
|
|
79
80
|
# Raised at a Service's +yield+ when the guest block raised; rescue it as
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kobako
|
|
4
|
+
# The Filesystems a Sandbox can mount for the guest's +File+, each
|
|
5
|
+
# answered in Rust by the kind of the same name. Hand one to
|
|
6
|
+
# Sandbox#mount:
|
|
7
|
+
#
|
|
8
|
+
# sandbox.mount("/data", Kobako::Filesystem::Tmpfs.new("config.yml" => yaml))
|
|
9
|
+
#
|
|
10
|
+
# [<tt>Tmpfs.new(files = {}, size: 64 MiB, nr_inodes:)</tt>]
|
|
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; one that
|
|
13
|
+
# should start empty mounts a new Tmpfs through Context#mount.
|
|
14
|
+
# [<tt>Bind.new(path)</tt>]
|
|
15
|
+
# A host directory and everything beneath it. Nothing outside it is
|
|
16
|
+
# reachable, a symbolic link pointing out included.
|
|
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
|
+
#
|
|
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
|
+
# directories, its root among them; +nil+ lifts either bound. Without
|
|
26
|
+
# +nr_inodes+ it holds one for each 4 KiB of +size+, and the root. Past
|
|
27
|
+
# either, the guest raises Errno::ENOSPC.
|
|
28
|
+
#
|
|
29
|
+
# Any object answering +open+ and +stat+ mounts too, as a Filesystem
|
|
30
|
+
# written in Ruby; its methods take the names of +File+, +Dir+, and +IO+.
|
|
31
|
+
# Its +stat+ answers +directory?+, +size+, and +ino+, an +ino+ naming one
|
|
32
|
+
# directory for as long as the guest holds it open. Its names match
|
|
33
|
+
# exactly, as a mount's block judges them as given. A name may hold a
|
|
34
|
+
# <tt>\\</tt>, which the guest's paths never separate on, so one joined
|
|
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.
|
|
67
|
+
module Filesystem
|
|
68
|
+
end
|
|
69
|
+
end
|