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
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
//! Per-invocation I/O between the host and the guest: it frames what the guest
|
|
2
|
+
//! is handed, writes the entry's downcall into a freshly allocated guest buffer,
|
|
3
|
+
//! and reads the Outcome the downcall answered with.
|
|
4
|
+
|
|
5
|
+
use kobako_transport::abi::FRAME_LEN_SIZE;
|
|
6
|
+
use kobako_transport::envelope::{CompileReply, Downcall, Outcome, Preamble, Run, Snippet};
|
|
7
|
+
|
|
8
|
+
use super::buffer::{
|
|
9
|
+
alloc_and_write, checked_payload_len, read_buffer, unpack_guest_buffer, ReadError, WriteError,
|
|
10
|
+
};
|
|
11
|
+
use super::{GuestInstance, SANDBOX_RUNTIME_NOT_KOBAKO};
|
|
12
|
+
use crate::error::{InvokeError, SetupError, Trap};
|
|
13
|
+
use crate::runtime::Entry;
|
|
14
|
+
|
|
15
|
+
/// What the guest reads from stdin: the Preamble behind its length prefix. It
|
|
16
|
+
/// carries the same 16 MiB cap as a downcall request: past it, the prefix
|
|
17
|
+
/// would silently wrap.
|
|
18
|
+
pub fn stdin_content(preamble: &Preamble) -> Result<Vec<u8>, &'static str> {
|
|
19
|
+
let preamble = preamble.encode();
|
|
20
|
+
checked_payload_len(preamble.len())?;
|
|
21
|
+
let mut content = Vec::with_capacity(FRAME_LEN_SIZE + preamble.len());
|
|
22
|
+
content.extend_from_slice(&(preamble.len() as u32).to_be_bytes());
|
|
23
|
+
content.extend_from_slice(&preamble);
|
|
24
|
+
Ok(content)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/// The downcall `entry` is.
|
|
28
|
+
pub fn request_of(entry: Entry<'_>) -> Vec<u8> {
|
|
29
|
+
match entry {
|
|
30
|
+
Entry::Eval { source } => Downcall::Eval(source.to_vec()),
|
|
31
|
+
Entry::Run {
|
|
32
|
+
entrypoint,
|
|
33
|
+
payload,
|
|
34
|
+
} => Downcall::Run(Run {
|
|
35
|
+
entrypoint: entrypoint.to_owned(),
|
|
36
|
+
payload,
|
|
37
|
+
}),
|
|
38
|
+
}
|
|
39
|
+
.encode()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// The downcall that asks the guest to compile its Preamble.
|
|
43
|
+
pub fn compile_request() -> Vec<u8> {
|
|
44
|
+
Downcall::Compile.encode()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// Write a request into a guest buffer, answering where. A missing or trapping
|
|
48
|
+
/// allocator is an engine fault, a `Trap`; one that runs but returns 0 leaves
|
|
49
|
+
/// the runtime intact, so it is a `SetupError`.
|
|
50
|
+
pub fn write_request<G: GuestInstance>(
|
|
51
|
+
guest: &mut G,
|
|
52
|
+
envelope: &[u8],
|
|
53
|
+
) -> Result<(i32, i32), InvokeError> {
|
|
54
|
+
let trap = |message: String| -> InvokeError { Trap::Other(message).into() };
|
|
55
|
+
let ptr = alloc_and_write(guest, envelope).map_err(|err| match err {
|
|
56
|
+
WriteError::OutOfMemory => {
|
|
57
|
+
SetupError::Intact("could not allocate input buffer (out of memory)".to_string()).into()
|
|
58
|
+
}
|
|
59
|
+
WriteError::TooLarge => trap("payload exceeds the 16 MiB limit".to_string()),
|
|
60
|
+
WriteError::Incompatible(message) => trap(message.to_string()),
|
|
61
|
+
WriteError::Trapped(e) => G::cap_trap(&e)
|
|
62
|
+
.unwrap_or_else(|| Trap::Other(format!("failed to allocate input buffer: {e}")))
|
|
63
|
+
.into(),
|
|
64
|
+
WriteError::OutOfBounds => {
|
|
65
|
+
trap("the input buffer lies outside the Sandbox's memory".to_string())
|
|
66
|
+
}
|
|
67
|
+
})?;
|
|
68
|
+
// The write has already held the length to the payload cap.
|
|
69
|
+
Ok((ptr as i32, envelope.len() as i32))
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/// A guest that answered without an Outcome the envelope can frame — an
|
|
73
|
+
/// empty answer included — has nothing left to attribute, so it is a
|
|
74
|
+
/// `Trap` like every other way of failing to read the result.
|
|
75
|
+
pub fn read_outcome<G: GuestInstance>(guest: &mut G, packed: u64) -> Result<Outcome, Trap> {
|
|
76
|
+
let bytes = read_answer(guest, packed)?;
|
|
77
|
+
Outcome::decode(&bytes).map_err(|_| {
|
|
78
|
+
Trap::Other(
|
|
79
|
+
if bytes.is_empty() {
|
|
80
|
+
"Sandbox exited without producing a result"
|
|
81
|
+
} else {
|
|
82
|
+
"Sandbox produced an unrecognised result"
|
|
83
|
+
}
|
|
84
|
+
.to_string(),
|
|
85
|
+
)
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/// Held to the same 16 MiB bound as an Outcome; an answer that does not
|
|
90
|
+
/// frame is a `Trap` the caller treats like any other failed compile.
|
|
91
|
+
pub fn read_compile_reply<G: GuestInstance>(
|
|
92
|
+
guest: &mut G,
|
|
93
|
+
packed: u64,
|
|
94
|
+
) -> Result<CompileReply, Trap> {
|
|
95
|
+
let bytes = read_answer(guest, packed)?;
|
|
96
|
+
CompileReply::decode(&bytes)
|
|
97
|
+
.map_err(|_| Trap::Other("Sandbox produced an unrecognised compile answer".to_string()))
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
fn read_answer<G: GuestInstance>(guest: &mut G, packed: u64) -> Result<Vec<u8>, Trap> {
|
|
101
|
+
let memory = guest
|
|
102
|
+
.memory()
|
|
103
|
+
.ok_or_else(|| Trap::Other(SANDBOX_RUNTIME_NOT_KOBAKO.to_string()))?;
|
|
104
|
+
let (ptr, len) = unpack_guest_buffer(packed);
|
|
105
|
+
read_buffer(memory, ptr, len).map_err(|err| {
|
|
106
|
+
Trap::Other(
|
|
107
|
+
match err {
|
|
108
|
+
ReadError::TooLarge => "result payload exceeds the 16 MiB limit",
|
|
109
|
+
ReadError::OutOfBounds => "the Sandbox result is out of bounds",
|
|
110
|
+
}
|
|
111
|
+
.to_string(),
|
|
112
|
+
)
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/// What a compile answered, to replay in place of `preamble`'s source
|
|
117
|
+
/// snippets, or `None` where nothing can be replayed.
|
|
118
|
+
pub fn compiled_bytecodes(preamble: &Preamble, reply: CompileReply) -> Option<Vec<Vec<u8>>> {
|
|
119
|
+
match reply {
|
|
120
|
+
CompileReply::Ok(bytecodes) => {
|
|
121
|
+
one_per_source(preamble, bytecodes).filter(|bytecodes| replay_fits(preamble, bytecodes))
|
|
122
|
+
}
|
|
123
|
+
CompileReply::Error(_) | CompileReply::Unsupported(_) => None,
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/// Each invocation sends the Preamble with every bytecode in place of its
|
|
128
|
+
/// source, so an answer that would carry it past the payload cap would trap
|
|
129
|
+
/// every invocation the source alone fits.
|
|
130
|
+
fn replay_fits(preamble: &Preamble, bytecodes: &[Vec<u8>]) -> bool {
|
|
131
|
+
let mut replay = preamble.clone();
|
|
132
|
+
let sources = replay
|
|
133
|
+
.snippets
|
|
134
|
+
.entries
|
|
135
|
+
.iter_mut()
|
|
136
|
+
.filter(|entry| matches!(entry, Snippet::Source { .. }));
|
|
137
|
+
for (entry, body) in sources.zip(bytecodes) {
|
|
138
|
+
*entry = Snippet::Bytecode { body: body.clone() };
|
|
139
|
+
}
|
|
140
|
+
checked_payload_len(replay.encode().len()).is_ok()
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/// A frontend replays each bytecode in place of one source snippet, in
|
|
144
|
+
/// order, so an answer of any other length leaves nothing to replay.
|
|
145
|
+
fn one_per_source(preamble: &Preamble, bytecodes: Vec<Vec<u8>>) -> Option<Vec<Vec<u8>>> {
|
|
146
|
+
let sources = preamble
|
|
147
|
+
.snippets
|
|
148
|
+
.entries
|
|
149
|
+
.iter()
|
|
150
|
+
.filter(|entry| matches!(entry, Snippet::Source { .. }))
|
|
151
|
+
.count();
|
|
152
|
+
(bytecodes.len() == sources).then_some(bytecodes)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
#[cfg(test)]
|
|
156
|
+
mod tests {
|
|
157
|
+
use kobako_transport::envelope::Snippets;
|
|
158
|
+
|
|
159
|
+
use super::*;
|
|
160
|
+
|
|
161
|
+
/// One source between two bytecode snippets: a compile answers one
|
|
162
|
+
/// bytecode for it.
|
|
163
|
+
fn one_source() -> Preamble {
|
|
164
|
+
let mut preamble = Preamble::default();
|
|
165
|
+
preamble.snippets = Snippets {
|
|
166
|
+
entries: vec![
|
|
167
|
+
Snippet::Bytecode { body: vec![0xaa] },
|
|
168
|
+
Snippet::Source {
|
|
169
|
+
name: "Only".into(),
|
|
170
|
+
body: "1".into(),
|
|
171
|
+
},
|
|
172
|
+
Snippet::Bytecode { body: vec![0xbb] },
|
|
173
|
+
],
|
|
174
|
+
};
|
|
175
|
+
preamble
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// @behavior WE-089
|
|
179
|
+
#[test]
|
|
180
|
+
fn a_compiled_answer_of_another_length_leaves_nothing_compiled() {
|
|
181
|
+
let preamble = one_source();
|
|
182
|
+
|
|
183
|
+
assert_eq!(
|
|
184
|
+
[
|
|
185
|
+
one_per_source(&preamble, Vec::new()),
|
|
186
|
+
one_per_source(&preamble, vec![vec![0x01], vec![0x02]]),
|
|
187
|
+
one_per_source(&preamble, vec![vec![0x01]]),
|
|
188
|
+
],
|
|
189
|
+
[None, None, Some(vec![vec![0x01]])],
|
|
190
|
+
"a compiled answer through Driver#compile must be kept only when it carries one \
|
|
191
|
+
bytecode per source snippet"
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
//! The per-invocation memory cap: only the growth past the linear-memory size
|
|
2
|
+
//! captured at invocation entry is charged, so the image's initial allocation
|
|
3
|
+
//! is outside the budget.
|
|
4
|
+
|
|
5
|
+
/// The budget one invocation's memory grows against.
|
|
6
|
+
///
|
|
7
|
+
/// `memory_limit` bounds per-invocation growth (`None` disables it).
|
|
8
|
+
/// `baseline` is the linear-memory size captured at invocation entry by
|
|
9
|
+
/// `MemoryCap::activate`; only the growth past `baseline` is charged against
|
|
10
|
+
/// `memory_limit`, so the mruby image's initial allocation and any high-water
|
|
11
|
+
/// mark left by prior invocations on the same Sandbox do not consume the
|
|
12
|
+
/// budget. An Engine also asks about the module's declared initial allocation
|
|
13
|
+
/// at instantiation time, so the cap stays dormant until `MemoryCap::activate`
|
|
14
|
+
/// arms it for one invocation.
|
|
15
|
+
#[derive(Debug, Clone, Copy)]
|
|
16
|
+
pub struct MemoryCap {
|
|
17
|
+
memory_limit: Option<usize>,
|
|
18
|
+
baseline: usize,
|
|
19
|
+
cap_active: bool,
|
|
20
|
+
peak: usize,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
impl MemoryCap {
|
|
24
|
+
pub fn new(memory_limit: Option<usize>) -> Self {
|
|
25
|
+
Self {
|
|
26
|
+
memory_limit,
|
|
27
|
+
baseline: 0,
|
|
28
|
+
cap_active: false,
|
|
29
|
+
peak: 0,
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// The cap stays dormant until armed, because the module's declared
|
|
34
|
+
/// initial memory is allocated during instantiation and must pass.
|
|
35
|
+
pub fn activate(&mut self, baseline: usize) {
|
|
36
|
+
self.baseline = baseline;
|
|
37
|
+
self.cap_active = true;
|
|
38
|
+
self.peak = 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
pub fn deactivate(&mut self) {
|
|
42
|
+
self.cap_active = false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/// A grow the cap rejects never updates the peak, so the reported value
|
|
46
|
+
/// never exceeds `memory_limit`.
|
|
47
|
+
pub fn peak(&self) -> usize {
|
|
48
|
+
self.peak
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// Charge a growth to `desired` bytes, refusing one past `memory_limit`.
|
|
52
|
+
pub fn charge(&mut self, desired: usize) -> Result<(), MemoryLimitExceeded> {
|
|
53
|
+
if !self.cap_active {
|
|
54
|
+
return Ok(());
|
|
55
|
+
}
|
|
56
|
+
let delta = desired.saturating_sub(self.baseline);
|
|
57
|
+
if let Some(limit) = self.memory_limit {
|
|
58
|
+
if delta > limit {
|
|
59
|
+
return Err(MemoryLimitExceeded::new(desired, limit));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if delta > self.peak {
|
|
63
|
+
self.peak = delta;
|
|
64
|
+
}
|
|
65
|
+
Ok(())
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// A growth past the per-invocation memory cap. An Engine ends the invocation
|
|
70
|
+
/// with it, and a Wasm Driver finds it again to classify the failure as a
|
|
71
|
+
/// memory-limit `Trap`. Callers use the `Display` impl below — no field is
|
|
72
|
+
/// read directly — so the inner state stays private.
|
|
73
|
+
#[derive(Debug)]
|
|
74
|
+
pub struct MemoryLimitExceeded {
|
|
75
|
+
desired: usize,
|
|
76
|
+
limit: usize,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
impl MemoryLimitExceeded {
|
|
80
|
+
pub fn new(desired: usize, limit: usize) -> Self {
|
|
81
|
+
Self { desired, limit }
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
impl std::fmt::Display for MemoryLimitExceeded {
|
|
86
|
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
87
|
+
write!(
|
|
88
|
+
f,
|
|
89
|
+
"memory usage exceeded memory_limit: \
|
|
90
|
+
requested={} bytes, limit={} bytes",
|
|
91
|
+
self.desired, self.limit
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
impl std::error::Error for MemoryLimitExceeded {}
|
|
97
|
+
|
|
98
|
+
#[cfg(test)]
|
|
99
|
+
mod tests {
|
|
100
|
+
//! The pure delta arithmetic, so a regression that breaks the baseline
|
|
101
|
+
//! accounting (dropping the `baseline` subtraction, or letting `activate`
|
|
102
|
+
//! carry stale state across invocations) is caught without an Engine.
|
|
103
|
+
use super::MemoryCap;
|
|
104
|
+
|
|
105
|
+
fn assert_charged(cap: &mut MemoryCap, desired: usize, what: &str) {
|
|
106
|
+
assert!(
|
|
107
|
+
cap.charge(desired).is_ok(),
|
|
108
|
+
"{what} through MemoryCap::charge must be allowed"
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
fn assert_refused(cap: &mut MemoryCap, desired: usize, what: &str) {
|
|
113
|
+
assert!(
|
|
114
|
+
cap.charge(desired).is_err(),
|
|
115
|
+
"{what} through MemoryCap::charge must be refused"
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// @behavior S-113
|
|
120
|
+
#[test]
|
|
121
|
+
fn a_growth_before_the_cap_is_armed_is_not_bounded() {
|
|
122
|
+
let mut cap = MemoryCap::new(Some(1 << 20));
|
|
123
|
+
assert_charged(&mut cap, 100 << 20, "a 100 MiB growth before activate");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// @behavior S-114
|
|
127
|
+
#[test]
|
|
128
|
+
fn a_growth_within_the_limit_is_allowed() {
|
|
129
|
+
let mut cap = MemoryCap::new(Some(1 << 20));
|
|
130
|
+
cap.activate(2 << 20);
|
|
131
|
+
assert_charged(
|
|
132
|
+
&mut cap,
|
|
133
|
+
(2 << 20) + (1 << 19),
|
|
134
|
+
"a 512 KiB growth past a 2 MiB baseline under a 1 MiB limit",
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// @behavior S-121
|
|
139
|
+
#[test]
|
|
140
|
+
fn a_growth_past_the_limit_is_refused() {
|
|
141
|
+
let mut cap = MemoryCap::new(Some(1 << 20));
|
|
142
|
+
cap.activate(2 << 20);
|
|
143
|
+
assert_refused(
|
|
144
|
+
&mut cap,
|
|
145
|
+
4 << 20,
|
|
146
|
+
"a 2 MiB growth past a 2 MiB baseline under a 1 MiB limit",
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// @behavior S-008
|
|
151
|
+
#[test]
|
|
152
|
+
fn each_activation_charges_from_its_own_baseline() {
|
|
153
|
+
let mut cap = MemoryCap::new(Some(1 << 20));
|
|
154
|
+
cap.activate(2 << 20);
|
|
155
|
+
assert_charged(&mut cap, 3 << 20, "a 1 MiB growth in the first invocation");
|
|
156
|
+
cap.activate(3 << 20);
|
|
157
|
+
assert_charged(
|
|
158
|
+
&mut cap,
|
|
159
|
+
4 << 20,
|
|
160
|
+
"a 1 MiB growth in the second invocation, past the first one's residue",
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// @behavior S-009
|
|
165
|
+
#[test]
|
|
166
|
+
fn a_cap_without_a_limit_allows_any_growth() {
|
|
167
|
+
let mut cap = MemoryCap::new(None);
|
|
168
|
+
cap.activate(0);
|
|
169
|
+
assert_charged(&mut cap, 100 << 20, "a 100 MiB growth with no limit");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// @behavior S-116
|
|
173
|
+
#[test]
|
|
174
|
+
fn the_peak_is_zero_before_any_growth() {
|
|
175
|
+
let cap = MemoryCap::new(Some(1 << 20));
|
|
176
|
+
assert_eq!(
|
|
177
|
+
cap.peak(),
|
|
178
|
+
0,
|
|
179
|
+
"a new cap through MemoryCap::peak must report 0"
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// @behavior S-115
|
|
184
|
+
#[test]
|
|
185
|
+
fn the_peak_is_the_highest_growth_past_the_baseline() {
|
|
186
|
+
let mut cap = MemoryCap::new(Some(1 << 20));
|
|
187
|
+
cap.activate(2 << 20);
|
|
188
|
+
assert_charged(&mut cap, (2 << 20) + (1 << 18), "a 256 KiB growth");
|
|
189
|
+
assert_charged(&mut cap, (2 << 20) + (1 << 19), "a 512 KiB growth");
|
|
190
|
+
assert_charged(&mut cap, (2 << 20) + (1 << 17), "a 128 KiB growth");
|
|
191
|
+
assert_eq!(
|
|
192
|
+
cap.peak(),
|
|
193
|
+
1 << 19,
|
|
194
|
+
"growths of 256, 512, and 128 KiB through MemoryCap::peak must report 512 KiB"
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// @behavior S-062
|
|
199
|
+
#[test]
|
|
200
|
+
fn a_refused_growth_leaves_the_peak_alone() {
|
|
201
|
+
let mut cap = MemoryCap::new(Some(1 << 20));
|
|
202
|
+
cap.activate(2 << 20);
|
|
203
|
+
assert_charged(&mut cap, (2 << 20) + (1 << 19), "a 512 KiB growth");
|
|
204
|
+
assert_refused(&mut cap, 4 << 20, "a 2 MiB growth under a 1 MiB limit");
|
|
205
|
+
assert_eq!(
|
|
206
|
+
cap.peak(),
|
|
207
|
+
1 << 19,
|
|
208
|
+
"a refused 2 MiB growth through MemoryCap::peak must leave 512 KiB reported"
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// @behavior S-117
|
|
213
|
+
#[test]
|
|
214
|
+
fn each_activation_resets_the_peak() {
|
|
215
|
+
let mut cap = MemoryCap::new(Some(1 << 20));
|
|
216
|
+
cap.activate(2 << 20);
|
|
217
|
+
assert_charged(&mut cap, (2 << 20) + (1 << 19), "a 512 KiB growth");
|
|
218
|
+
cap.activate(3 << 20);
|
|
219
|
+
assert_eq!(
|
|
220
|
+
cap.peak(),
|
|
221
|
+
0,
|
|
222
|
+
"a second activate through MemoryCap::peak must report 0"
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// @behavior S-118
|
|
227
|
+
#[test]
|
|
228
|
+
fn a_cap_without_a_limit_still_reports_its_peak() {
|
|
229
|
+
let mut cap = MemoryCap::new(None);
|
|
230
|
+
cap.activate(1 << 20);
|
|
231
|
+
assert_charged(&mut cap, 5 << 20, "a 4 MiB growth with no limit");
|
|
232
|
+
assert_eq!(
|
|
233
|
+
cap.peak(),
|
|
234
|
+
4 << 20,
|
|
235
|
+
"a 4 MiB growth with no limit through MemoryCap::peak must report 4 MiB"
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
//! The host ABI endpoint: everything the kobako ABI asks of a host that no
|
|
2
|
+
//! Engine decides, so a Wasm Driver for any Engine implements `GuestInstance`
|
|
3
|
+
//! over its own memory and exports, and links `__kobako_upcall` to
|
|
4
|
+
//! `dispatch::handle`.
|
|
5
|
+
//!
|
|
6
|
+
//! Only this endpoint and its guest counterpart read or write an envelope's
|
|
7
|
+
//! bytes.
|
|
8
|
+
|
|
9
|
+
mod buffer;
|
|
10
|
+
mod capture;
|
|
11
|
+
mod dispatch;
|
|
12
|
+
mod invoke;
|
|
13
|
+
mod memory_cap;
|
|
14
|
+
|
|
15
|
+
use std::fmt::Display;
|
|
16
|
+
|
|
17
|
+
use kobako_transport::abi::ABI_VERSION;
|
|
18
|
+
|
|
19
|
+
use crate::error::{SetupError, Trap};
|
|
20
|
+
|
|
21
|
+
pub use capture::{clip_capture, pipe_capacity};
|
|
22
|
+
pub use dispatch::{handle, Calls, UpcallEnd, ABANDONED};
|
|
23
|
+
pub use invoke::{
|
|
24
|
+
compile_request, compiled_bytecodes, read_compile_reply, read_outcome, request_of,
|
|
25
|
+
stdin_content, write_request,
|
|
26
|
+
};
|
|
27
|
+
pub use memory_cap::{MemoryCap, MemoryLimitExceeded};
|
|
28
|
+
|
|
29
|
+
/// For a required guest export that is absent or mistyped, wherever it is
|
|
30
|
+
/// looked up. Names neither the export nor a frontend's artifact path;
|
|
31
|
+
/// what a caller can do is rebuild the runtime.
|
|
32
|
+
pub const RUNTIME_INCOMPATIBLE: &str =
|
|
33
|
+
"the Sandbox runtime is incompatible; rebuild it against the kobako version in use";
|
|
34
|
+
|
|
35
|
+
/// Accept a Guest Binary only when the ABI version it reports is the host's;
|
|
36
|
+
/// `None` means it exports none. Only rebuilding the artifact fixes either
|
|
37
|
+
/// refusal, so both are `Dead` and say so.
|
|
38
|
+
pub fn verify_abi_version(reported: Option<u32>) -> Result<(), SetupError> {
|
|
39
|
+
match reported {
|
|
40
|
+
None => Err(SetupError::Dead(format!(
|
|
41
|
+
"the Guest Binary does not export __kobako_abi_version; \
|
|
42
|
+
rebuild it against ABI version {ABI_VERSION}"
|
|
43
|
+
))),
|
|
44
|
+
Some(version) if version != ABI_VERSION => Err(SetupError::Dead(format!(
|
|
45
|
+
"the Guest Binary reports ABI version {version}, but this host \
|
|
46
|
+
implements ABI version {ABI_VERSION}; rebuild the Guest Binary \
|
|
47
|
+
against the host's version"
|
|
48
|
+
))),
|
|
49
|
+
Some(_) => Ok(()),
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/// For the "the loaded Wasm module is not a Kobako-shaped runtime at all"
|
|
54
|
+
/// failure mode, such as an instance with no `memory` export.
|
|
55
|
+
const SANDBOX_RUNTIME_NOT_KOBAKO: &str =
|
|
56
|
+
"the loaded Wasm module is not a Kobako-compatible runtime";
|
|
57
|
+
|
|
58
|
+
/// One guest instance as the endpoint drives it: what a Wasm Driver gives it of an
|
|
59
|
+
/// Engine, from the invocation body and from inside `__kobako_upcall` alike.
|
|
60
|
+
/// A method answering `None` names an export the module does not have.
|
|
61
|
+
pub trait GuestInstance {
|
|
62
|
+
/// What a call into the guest raises, kept as it is so an invocation ends
|
|
63
|
+
/// with its own kind.
|
|
64
|
+
type Fault: Display;
|
|
65
|
+
|
|
66
|
+
/// The state of the Calls open on this instance.
|
|
67
|
+
fn calls(&mut self) -> &mut Calls<Self::Fault>;
|
|
68
|
+
|
|
69
|
+
/// The guest's linear memory.
|
|
70
|
+
fn memory(&mut self) -> Option<&mut [u8]>;
|
|
71
|
+
|
|
72
|
+
/// Allocate `len` bytes through `__kobako_alloc`, answering the address,
|
|
73
|
+
/// or 0 where the guest has none to give.
|
|
74
|
+
fn alloc(&mut self, len: u32) -> Option<Result<u32, Self::Fault>>;
|
|
75
|
+
|
|
76
|
+
/// Call `__kobako_downcall` with a request at `ptr`, answering the packed
|
|
77
|
+
/// `(ptr, len)` of its answer.
|
|
78
|
+
fn downcall(&mut self, ptr: i32, len: i32) -> Option<Result<u64, Self::Fault>>;
|
|
79
|
+
|
|
80
|
+
/// The cap `fault` carries, if one of the invocation's caps fired, which
|
|
81
|
+
/// names itself rather than the step it fired during.
|
|
82
|
+
fn cap_trap(fault: &Self::Fault) -> Option<Trap>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
#[cfg(test)]
|
|
86
|
+
mod tests {
|
|
87
|
+
//! Witness that the endpoint needs nothing of an Engine: an instance made
|
|
88
|
+
//! of a plain buffer drives a whole request and upcall through it.
|
|
89
|
+
use kobako_transport::abi::unpack_ptr_len;
|
|
90
|
+
use kobako_transport::envelope::Reply;
|
|
91
|
+
|
|
92
|
+
use super::*;
|
|
93
|
+
|
|
94
|
+
/// An instance holding a buffer and a bump allocator.
|
|
95
|
+
struct InMemory {
|
|
96
|
+
memory: Vec<u8>,
|
|
97
|
+
next: u32,
|
|
98
|
+
calls: Calls<String>,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
impl GuestInstance for InMemory {
|
|
102
|
+
type Fault = String;
|
|
103
|
+
|
|
104
|
+
fn calls(&mut self) -> &mut Calls<String> {
|
|
105
|
+
&mut self.calls
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
fn memory(&mut self) -> Option<&mut [u8]> {
|
|
109
|
+
Some(&mut self.memory)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
fn alloc(&mut self, len: u32) -> Option<Result<u32, String>> {
|
|
113
|
+
let ptr = self.next;
|
|
114
|
+
self.next += len;
|
|
115
|
+
Some(Ok(ptr))
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
fn downcall(&mut self, _ptr: i32, _len: i32) -> Option<Result<u64, String>> {
|
|
119
|
+
None
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
fn cap_trap(_fault: &String) -> Option<Trap> {
|
|
123
|
+
None
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
fn instance() -> InMemory {
|
|
128
|
+
InMemory {
|
|
129
|
+
memory: vec![0; 1024],
|
|
130
|
+
next: 16,
|
|
131
|
+
calls: Calls::default(),
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// @behavior RT-191
|
|
136
|
+
#[test]
|
|
137
|
+
fn a_request_lands_in_guest_memory_through_any_instance() {
|
|
138
|
+
let mut guest = instance();
|
|
139
|
+
let answered = write_request(&mut guest, b"request").ok();
|
|
140
|
+
assert_eq!(
|
|
141
|
+
(answered, &guest.memory[16..23]),
|
|
142
|
+
(Some((16, 7)), &b"request"[..]),
|
|
143
|
+
"endpoint::write_request through an instance of a plain buffer must write the \
|
|
144
|
+
request at the address its allocator answers"
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// A tag the host does not serve needs no handler bound to be answered.
|
|
149
|
+
// @behavior RT-191
|
|
150
|
+
#[test]
|
|
151
|
+
fn an_upcall_answers_through_any_instance() {
|
|
152
|
+
let mut guest = instance();
|
|
153
|
+
let request = b"not a call";
|
|
154
|
+
guest.memory[..request.len()].copy_from_slice(request);
|
|
155
|
+
let packed = handle(&mut guest, 0, request.len() as i32)
|
|
156
|
+
.ok()
|
|
157
|
+
.expect("the upcall must answer");
|
|
158
|
+
let (ptr, len) = unpack_ptr_len(packed as u64);
|
|
159
|
+
let reply = Reply::decode(&guest.memory[ptr as usize..(ptr + len) as usize]);
|
|
160
|
+
assert!(
|
|
161
|
+
matches!(reply, Ok(Reply::Unsupported(_))),
|
|
162
|
+
"endpoint::handle through an instance of a plain buffer must write the answer to a \
|
|
163
|
+
request under an unserved tag back into guest memory"
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
//! The host directories a Bind keeps open, each a descriptor of the host's
|
|
2
|
+
//! own. A dentry holds the one above it, so keeping one for every dentry would
|
|
3
|
+
//! spend a descriptor per level of depth, which no table of a guest's counts.
|
|
4
|
+
//! A directory the guest holds open keeps its own; the rest are kept up to a
|
|
5
|
+
//! bound, the oldest let go and walked to again when asked, as 9p walks to a
|
|
6
|
+
//! dentry it holds no fid for.
|
|
7
|
+
|
|
8
|
+
use std::num::NonZeroUsize;
|
|
9
|
+
use std::sync::{Arc, Mutex, MutexGuard, PoisonError, Weak};
|
|
10
|
+
|
|
11
|
+
use cap_std::fs::Dir;
|
|
12
|
+
use lru::LruCache;
|
|
13
|
+
|
|
14
|
+
use crate::vfs::Dentry;
|
|
15
|
+
|
|
16
|
+
/// The most directories a Bind keeps open for walks.
|
|
17
|
+
const MAX_CACHED_FIDS: NonZeroUsize = NonZeroUsize::new(64).expect("the bound is not zero");
|
|
18
|
+
|
|
19
|
+
/// A directory, and the dentry it was walked to for.
|
|
20
|
+
type CachedFid = (Weak<Dentry>, Arc<Dir>);
|
|
21
|
+
|
|
22
|
+
/// Directories a Bind walked to, by the address of the dentry naming each.
|
|
23
|
+
/// Each entry holds that dentry's allocation, so no other can take its address.
|
|
24
|
+
pub(super) struct FidCache(Mutex<LruCache<usize, CachedFid>>);
|
|
25
|
+
|
|
26
|
+
impl Default for FidCache {
|
|
27
|
+
fn default() -> Self {
|
|
28
|
+
Self(Mutex::new(LruCache::new(MAX_CACHED_FIDS)))
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
fn key(dentry: &Dentry) -> usize {
|
|
33
|
+
std::ptr::from_ref(dentry) as usize
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
impl FidCache {
|
|
37
|
+
fn entries(&self) -> MutexGuard<'_, LruCache<usize, CachedFid>> {
|
|
38
|
+
self.0.lock().unwrap_or_else(PoisonError::into_inner)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// The directory kept for `dentry`, now the most recently asked.
|
|
42
|
+
pub(super) fn get(&self, dentry: &Dentry) -> Option<Arc<Dir>> {
|
|
43
|
+
self.entries().get(&key(dentry)).map(|(_, dir)| dir.clone())
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// Keep `dir` for `dentry`, letting go of the oldest past the bound.
|
|
47
|
+
pub(super) fn put(&self, dentry: &Dentry, dir: &Arc<Dir>) {
|
|
48
|
+
let entry = (Arc::downgrade(&dentry.get()), dir.clone());
|
|
49
|
+
self.entries().put(key(dentry), entry);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// How many directories are kept.
|
|
53
|
+
#[cfg(all(test, unix))]
|
|
54
|
+
pub(super) fn len(&self) -> usize {
|
|
55
|
+
self.entries().len()
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/// The host directory a guest holds open on a dentry, kept on it for as long
|
|
60
|
+
/// as the guest holds it, as 9p keeps a fid in `d_fsdata`.
|
|
61
|
+
#[derive(Default)]
|
|
62
|
+
pub(super) struct OpenDirFid(Mutex<Weak<Dir>>);
|
|
63
|
+
|
|
64
|
+
impl OpenDirFid {
|
|
65
|
+
/// The directory on `dentry`, while a guest holds it open.
|
|
66
|
+
pub(super) fn get(dentry: &Dentry) -> Option<Arc<Dir>> {
|
|
67
|
+
let kept = dentry.fsdata().get()?.clone().downcast::<Self>().ok()?;
|
|
68
|
+
let dir = kept.0.lock().unwrap_or_else(PoisonError::into_inner);
|
|
69
|
+
dir.upgrade()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/// `dir` is held open on `dentry` from now on.
|
|
73
|
+
pub(super) fn set(dentry: &Dentry, dir: &Arc<Dir>) {
|
|
74
|
+
let kept = dentry
|
|
75
|
+
.fsdata()
|
|
76
|
+
.get_or_init(|| Arc::new(Self::default()))
|
|
77
|
+
.clone();
|
|
78
|
+
if let Ok(kept) = kept.downcast::<Self>() {
|
|
79
|
+
*kept.0.lock().unwrap_or_else(PoisonError::into_inner) = Arc::downgrade(dir);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|