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,486 @@
|
|
|
1
|
+
//! The differential probe: every call kobako answers, made on descriptors
|
|
2
|
+
//! 0 to 3 against kobako's linker and against plain wasmtime-wasi preview1,
|
|
3
|
+
//! must leave the same errno, the same guest memory, and the same captures.
|
|
4
|
+
//!
|
|
5
|
+
//! It guards each wasmtime-wasi upgrade: a change in what preview1 answers
|
|
6
|
+
//! shows up here rather than in a guest. The deliberate differences — a
|
|
7
|
+
//! clock wait under the hermetic rung — are witnessed in `poll`.
|
|
8
|
+
|
|
9
|
+
mod mount;
|
|
10
|
+
|
|
11
|
+
use std::sync::OnceLock;
|
|
12
|
+
|
|
13
|
+
use wasmtime::{Linker, Module, Store};
|
|
14
|
+
use wasmtime_wasi::p1::{self, WasiP1Ctx};
|
|
15
|
+
use wasmtime_wasi::p2::pipe::{MemoryInputPipe, MemoryOutputPipe};
|
|
16
|
+
use wasmtime_wasi::WasiCtxBuilder;
|
|
17
|
+
|
|
18
|
+
use crate::cache::load_engine;
|
|
19
|
+
use crate::config::Config;
|
|
20
|
+
use crate::imports;
|
|
21
|
+
use crate::invocation::Invocation;
|
|
22
|
+
use crate::trap::NO_TIMEOUT_EPOCH_DELTA;
|
|
23
|
+
use crate::wasi::Wasi;
|
|
24
|
+
use kobako_runtime::profile::Profile;
|
|
25
|
+
|
|
26
|
+
/// The Preamble bytes stdin carries after its length prefix.
|
|
27
|
+
const PREAMBLE: &[u8] = b"hello";
|
|
28
|
+
|
|
29
|
+
/// Every export takes a descriptor and answers the call's errno. Memory
|
|
30
|
+
/// holds an `iovec` at 64 over 512..528, an empty one at 72, a `ciovec` at 80 over "hello" at
|
|
31
|
+
/// 600, and the paths "a" and "b" at 700 and 702; results land at 128 and
|
|
32
|
+
/// 256, subscriptions are built at 800 and events written at 896.
|
|
33
|
+
const PROBE_WAT: &str = r#"
|
|
34
|
+
(module
|
|
35
|
+
(import "wasi_snapshot_preview1" "fd_advise" (func $fd_advise (param i32 i64 i64 i32) (result i32)))
|
|
36
|
+
(import "wasi_snapshot_preview1" "fd_allocate" (func $fd_allocate (param i32 i64 i64) (result i32)))
|
|
37
|
+
(import "wasi_snapshot_preview1" "fd_close" (func $fd_close (param i32) (result i32)))
|
|
38
|
+
(import "wasi_snapshot_preview1" "fd_datasync" (func $fd_datasync (param i32) (result i32)))
|
|
39
|
+
(import "wasi_snapshot_preview1" "fd_fdstat_get" (func $fd_fdstat_get (param i32 i32) (result i32)))
|
|
40
|
+
(import "wasi_snapshot_preview1" "fd_fdstat_set_flags" (func $fd_fdstat_set_flags (param i32 i32) (result i32)))
|
|
41
|
+
(import "wasi_snapshot_preview1" "fd_fdstat_set_rights" (func $fd_fdstat_set_rights (param i32 i64 i64) (result i32)))
|
|
42
|
+
(import "wasi_snapshot_preview1" "fd_filestat_get" (func $fd_filestat_get (param i32 i32) (result i32)))
|
|
43
|
+
(import "wasi_snapshot_preview1" "fd_filestat_set_size" (func $fd_filestat_set_size (param i32 i64) (result i32)))
|
|
44
|
+
(import "wasi_snapshot_preview1" "fd_filestat_set_times" (func $fd_filestat_set_times (param i32 i64 i64 i32) (result i32)))
|
|
45
|
+
(import "wasi_snapshot_preview1" "fd_pread" (func $fd_pread (param i32 i32 i32 i64 i32) (result i32)))
|
|
46
|
+
(import "wasi_snapshot_preview1" "fd_prestat_get" (func $fd_prestat_get (param i32 i32) (result i32)))
|
|
47
|
+
(import "wasi_snapshot_preview1" "fd_prestat_dir_name" (func $fd_prestat_dir_name (param i32 i32 i32) (result i32)))
|
|
48
|
+
(import "wasi_snapshot_preview1" "fd_pwrite" (func $fd_pwrite (param i32 i32 i32 i64 i32) (result i32)))
|
|
49
|
+
(import "wasi_snapshot_preview1" "fd_read" (func $fd_read (param i32 i32 i32 i32) (result i32)))
|
|
50
|
+
(import "wasi_snapshot_preview1" "fd_readdir" (func $fd_readdir (param i32 i32 i32 i64 i32) (result i32)))
|
|
51
|
+
(import "wasi_snapshot_preview1" "fd_renumber" (func $fd_renumber (param i32 i32) (result i32)))
|
|
52
|
+
(import "wasi_snapshot_preview1" "fd_seek" (func $fd_seek (param i32 i64 i32 i32) (result i32)))
|
|
53
|
+
(import "wasi_snapshot_preview1" "fd_sync" (func $fd_sync (param i32) (result i32)))
|
|
54
|
+
(import "wasi_snapshot_preview1" "fd_tell" (func $fd_tell (param i32 i32) (result i32)))
|
|
55
|
+
(import "wasi_snapshot_preview1" "fd_write" (func $fd_write (param i32 i32 i32 i32) (result i32)))
|
|
56
|
+
(import "wasi_snapshot_preview1" "path_create_directory" (func $path_create_directory (param i32 i32 i32) (result i32)))
|
|
57
|
+
(import "wasi_snapshot_preview1" "path_filestat_get" (func $path_filestat_get (param i32 i32 i32 i32 i32) (result i32)))
|
|
58
|
+
(import "wasi_snapshot_preview1" "path_filestat_set_times" (func $path_filestat_set_times (param i32 i32 i32 i32 i64 i64 i32) (result i32)))
|
|
59
|
+
(import "wasi_snapshot_preview1" "path_link" (func $path_link (param i32 i32 i32 i32 i32 i32 i32) (result i32)))
|
|
60
|
+
(import "wasi_snapshot_preview1" "path_open" (func $path_open (param i32 i32 i32 i32 i32 i64 i64 i32 i32) (result i32)))
|
|
61
|
+
(import "wasi_snapshot_preview1" "path_readlink" (func $path_readlink (param i32 i32 i32 i32 i32 i32) (result i32)))
|
|
62
|
+
(import "wasi_snapshot_preview1" "path_remove_directory" (func $path_remove_directory (param i32 i32 i32) (result i32)))
|
|
63
|
+
(import "wasi_snapshot_preview1" "path_rename" (func $path_rename (param i32 i32 i32 i32 i32 i32) (result i32)))
|
|
64
|
+
(import "wasi_snapshot_preview1" "path_symlink" (func $path_symlink (param i32 i32 i32 i32 i32) (result i32)))
|
|
65
|
+
(import "wasi_snapshot_preview1" "path_unlink_file" (func $path_unlink_file (param i32 i32 i32) (result i32)))
|
|
66
|
+
(import "wasi_snapshot_preview1" "poll_oneoff" (func $poll_oneoff (param i32 i32 i32 i32) (result i32)))
|
|
67
|
+
(import "wasi_snapshot_preview1" "sock_accept" (func $sock_accept (param i32 i32 i32) (result i32)))
|
|
68
|
+
(import "wasi_snapshot_preview1" "sock_recv" (func $sock_recv (param i32 i32 i32 i32 i32 i32) (result i32)))
|
|
69
|
+
(import "wasi_snapshot_preview1" "sock_send" (func $sock_send (param i32 i32 i32 i32 i32) (result i32)))
|
|
70
|
+
(import "wasi_snapshot_preview1" "sock_shutdown" (func $sock_shutdown (param i32 i32) (result i32)))
|
|
71
|
+
(memory (export "memory") 1)
|
|
72
|
+
(data (i32.const 64) "\00\02\00\00\10\00\00\00")
|
|
73
|
+
(data (i32.const 72) "\00\02\00\00\00\00\00\00")
|
|
74
|
+
(data (i32.const 80) "\58\02\00\00\05\00\00\00")
|
|
75
|
+
(data (i32.const 88) "\00\00\02\00\10\00\00\00")
|
|
76
|
+
(data (i32.const 600) "hello")
|
|
77
|
+
(data (i32.const 700) "a\00b")
|
|
78
|
+
|
|
79
|
+
;; One subscription at 800: userdata 7, the tag, and the descriptor.
|
|
80
|
+
(func $subscribe (param $tag i32) (param $fd i32)
|
|
81
|
+
(i64.store (i32.const 800) (i64.const 7))
|
|
82
|
+
(i32.store8 (i32.const 808) (local.get $tag))
|
|
83
|
+
(i32.store (i32.const 816) (local.get $fd)))
|
|
84
|
+
|
|
85
|
+
(func (export "fd_advise") (param i32) (result i32) (call $fd_advise (local.get 0) (i64.const 0) (i64.const 0) (i32.const 0)))
|
|
86
|
+
(func (export "fd_allocate") (param i32) (result i32) (call $fd_allocate (local.get 0) (i64.const 0) (i64.const 0)))
|
|
87
|
+
(func (export "fd_close") (param i32) (result i32) (call $fd_close (local.get 0)))
|
|
88
|
+
(func (export "fd_datasync") (param i32) (result i32) (call $fd_datasync (local.get 0)))
|
|
89
|
+
(func (export "fd_fdstat_get") (param i32) (result i32) (call $fd_fdstat_get (local.get 0) (i32.const 256)))
|
|
90
|
+
(func (export "fd_fdstat_set_flags") (param i32) (result i32) (call $fd_fdstat_set_flags (local.get 0) (i32.const 0)))
|
|
91
|
+
(func (export "fd_fdstat_set_rights") (param i32) (result i32) (call $fd_fdstat_set_rights (local.get 0) (i64.const 0) (i64.const 0)))
|
|
92
|
+
(func (export "fd_filestat_get") (param i32) (result i32) (call $fd_filestat_get (local.get 0) (i32.const 256)))
|
|
93
|
+
(func (export "fd_filestat_set_size") (param i32) (result i32) (call $fd_filestat_set_size (local.get 0) (i64.const 0)))
|
|
94
|
+
(func (export "fd_filestat_set_size_huge") (param i32) (result i32) (call $fd_filestat_set_size (local.get 0) (i64.const -1)))
|
|
95
|
+
(func (export "fd_filestat_set_times") (param i32) (result i32) (call $fd_filestat_set_times (local.get 0) (i64.const 0) (i64.const 0) (i32.const 0)))
|
|
96
|
+
(func (export "fd_pread") (param i32) (result i32) (call $fd_pread (local.get 0) (i32.const 64) (i32.const 1) (i64.const 0) (i32.const 128)))
|
|
97
|
+
(func (export "fd_prestat_get") (param i32) (result i32) (call $fd_prestat_get (local.get 0) (i32.const 256)))
|
|
98
|
+
(func (export "fd_prestat_dir_name") (param i32) (result i32) (call $fd_prestat_dir_name (local.get 0) (i32.const 512) (i32.const 16)))
|
|
99
|
+
(func (export "fd_pwrite") (param i32) (result i32) (call $fd_pwrite (local.get 0) (i32.const 80) (i32.const 1) (i64.const 0) (i32.const 128)))
|
|
100
|
+
(func (export "fd_read") (param i32) (result i32) (call $fd_read (local.get 0) (i32.const 64) (i32.const 1) (i32.const 128)))
|
|
101
|
+
(func (export "fd_readdir") (param i32) (result i32) (call $fd_readdir (local.get 0) (i32.const 512) (i32.const 64) (i64.const 0) (i32.const 128)))
|
|
102
|
+
(func (export "fd_renumber") (param i32) (result i32) (call $fd_renumber (local.get 0) (local.get 0)))
|
|
103
|
+
(func (export "fd_seek") (param i32) (result i32) (call $fd_seek (local.get 0) (i64.const 0) (i32.const 0) (i32.const 128)))
|
|
104
|
+
(func (export "fd_sync") (param i32) (result i32) (call $fd_sync (local.get 0)))
|
|
105
|
+
(func (export "fd_tell") (param i32) (result i32) (call $fd_tell (local.get 0) (i32.const 128)))
|
|
106
|
+
(func (export "fd_write") (param i32) (result i32) (call $fd_write (local.get 0) (i32.const 80) (i32.const 1) (i32.const 128)))
|
|
107
|
+
(func (export "path_create_directory") (param i32) (result i32) (call $path_create_directory (local.get 0) (i32.const 700) (i32.const 1)))
|
|
108
|
+
(func (export "path_filestat_get") (param i32) (result i32) (call $path_filestat_get (local.get 0) (i32.const 0) (i32.const 700) (i32.const 1) (i32.const 256)))
|
|
109
|
+
(func (export "path_filestat_set_times") (param i32) (result i32) (call $path_filestat_set_times (local.get 0) (i32.const 0) (i32.const 700) (i32.const 1) (i64.const 0) (i64.const 0) (i32.const 0)))
|
|
110
|
+
(func (export "path_link") (param i32) (result i32) (call $path_link (local.get 0) (i32.const 0) (i32.const 700) (i32.const 1) (local.get 0) (i32.const 702) (i32.const 1)))
|
|
111
|
+
(func (export "path_open") (param i32) (result i32) (call $path_open (local.get 0) (i32.const 0) (i32.const 700) (i32.const 1) (i32.const 0) (i64.const 0) (i64.const 0) (i32.const 0) (i32.const 128)))
|
|
112
|
+
(func (export "path_readlink") (param i32) (result i32) (call $path_readlink (local.get 0) (i32.const 700) (i32.const 1) (i32.const 512) (i32.const 16) (i32.const 128)))
|
|
113
|
+
(func (export "path_remove_directory") (param i32) (result i32) (call $path_remove_directory (local.get 0) (i32.const 700) (i32.const 1)))
|
|
114
|
+
(func (export "path_rename") (param i32) (result i32) (call $path_rename (local.get 0) (i32.const 700) (i32.const 1) (local.get 0) (i32.const 702) (i32.const 1)))
|
|
115
|
+
(func (export "path_symlink") (param i32) (result i32) (call $path_symlink (i32.const 700) (i32.const 1) (local.get 0) (i32.const 702) (i32.const 1)))
|
|
116
|
+
(func (export "path_unlink_file") (param i32) (result i32) (call $path_unlink_file (local.get 0) (i32.const 700) (i32.const 1)))
|
|
117
|
+
(func (export "poll_oneoff_read") (param i32) (result i32)
|
|
118
|
+
(call $subscribe (i32.const 1) (local.get 0))
|
|
119
|
+
(call $poll_oneoff (i32.const 800) (i32.const 896) (i32.const 1) (i32.const 128)))
|
|
120
|
+
(func (export "poll_oneoff_write") (param i32) (result i32)
|
|
121
|
+
(call $subscribe (i32.const 2) (local.get 0))
|
|
122
|
+
(call $poll_oneoff (i32.const 800) (i32.const 896) (i32.const 1) (i32.const 128)))
|
|
123
|
+
;; A lone relative wait of no time on the clock the descriptor numbers.
|
|
124
|
+
(func (export "poll_oneoff_clock") (param i32) (result i32)
|
|
125
|
+
(call $subscribe (i32.const 0) (local.get 0))
|
|
126
|
+
(call $poll_oneoff (i32.const 800) (i32.const 896) (i32.const 1) (i32.const 128)))
|
|
127
|
+
(func (export "poll_oneoff_none") (param i32) (result i32)
|
|
128
|
+
(call $poll_oneoff (i32.const 800) (i32.const 896) (i32.const 0) (i32.const 128)))
|
|
129
|
+
(func (export "sock_accept") (param i32) (result i32) (call $sock_accept (local.get 0) (i32.const 0) (i32.const 128)))
|
|
130
|
+
(func (export "sock_recv") (param i32) (result i32) (call $sock_recv (local.get 0) (i32.const 64) (i32.const 1) (i32.const 0) (i32.const 128) (i32.const 132)))
|
|
131
|
+
(func (export "sock_send") (param i32) (result i32) (call $sock_send (local.get 0) (i32.const 80) (i32.const 1) (i32.const 0) (i32.const 128)))
|
|
132
|
+
(func (export "sock_shutdown") (param i32) (result i32) (call $sock_shutdown (local.get 0) (i32.const 0)))
|
|
133
|
+
|
|
134
|
+
;; A buffer past the one page of memory, through the iovec at 88.
|
|
135
|
+
(func (export "fd_read_far") (param i32) (result i32) (call $fd_read (local.get 0) (i32.const 88) (i32.const 1) (i32.const 128)))
|
|
136
|
+
(func (export "fd_pread_far") (param i32) (result i32) (call $fd_pread (local.get 0) (i32.const 88) (i32.const 1) (i64.const 0) (i32.const 128)))
|
|
137
|
+
(func (export "fd_write_far") (param i32) (result i32) (call $fd_write (local.get 0) (i32.const 88) (i32.const 1) (i32.const 128)))
|
|
138
|
+
(func (export "fd_pwrite_far") (param i32) (result i32) (call $fd_pwrite (local.get 0) (i32.const 88) (i32.const 1) (i64.const 0) (i32.const 128)))
|
|
139
|
+
(func (export "fd_readdir_far") (param i32) (result i32) (call $fd_readdir (local.get 0) (i32.const 0x20000) (i32.const 0) (i64.const 0) (i32.const 128)))
|
|
140
|
+
(func (export "path_open_far") (param i32) (result i32) (call $path_open (local.get 0) (i32.const 0) (i32.const 0x20000) (i32.const 1) (i32.const 0) (i64.const 0) (i64.const 0) (i32.const 0) (i32.const 128)))
|
|
141
|
+
;; A path longer than one call's hostcall fuel.
|
|
142
|
+
(func (export "path_open_huge") (param i32) (result i32) (call $path_open (local.get 0) (i32.const 0) (i32.const 700) (i32.const 0x0c800000) (i32.const 0) (i64.const 0) (i64.const 0) (i32.const 0) (i32.const 128)))
|
|
143
|
+
;; Each argument one past what the witx defines.
|
|
144
|
+
(func (export "fd_advise_undefined") (param i32) (result i32) (call $fd_advise (local.get 0) (i64.const 0) (i64.const 0) (i32.const 6)))
|
|
145
|
+
(func (export "fd_seek_undefined") (param i32) (result i32) (call $fd_seek (local.get 0) (i64.const 0) (i32.const 3) (i32.const 128)))
|
|
146
|
+
(func (export "fd_fdstat_set_flags_undefined") (param i32) (result i32) (call $fd_fdstat_set_flags (local.get 0) (i32.const 0x20)))
|
|
147
|
+
(func (export "fd_fdstat_set_rights_undefined") (param i32) (result i32) (call $fd_fdstat_set_rights (local.get 0) (i64.const 0x40000000) (i64.const 0)))
|
|
148
|
+
(func (export "fd_filestat_set_times_undefined") (param i32) (result i32) (call $fd_filestat_set_times (local.get 0) (i64.const 0) (i64.const 0) (i32.const 0x10)))
|
|
149
|
+
(func (export "path_filestat_get_undefined") (param i32) (result i32) (call $path_filestat_get (local.get 0) (i32.const 2) (i32.const 700) (i32.const 1) (i32.const 256)))
|
|
150
|
+
(func (export "path_open_undefined") (param i32) (result i32) (call $path_open (local.get 0) (i32.const 0) (i32.const 700) (i32.const 1) (i32.const 0x10) (i64.const 0) (i64.const 0) (i32.const 0) (i32.const 128)))
|
|
151
|
+
(func (export "path_link_undefined") (param i32) (result i32) (call $path_link (local.get 0) (i32.const 2) (i32.const 700) (i32.const 1) (local.get 0) (i32.const 702) (i32.const 1)))
|
|
152
|
+
(func (export "sock_recv_undefined") (param i32) (result i32) (call $sock_recv (local.get 0) (i32.const 64) (i32.const 1) (i32.const 4) (i32.const 128) (i32.const 132)))
|
|
153
|
+
(func (export "sock_shutdown_undefined") (param i32) (result i32) (call $sock_shutdown (local.get 0) (i32.const 4)))
|
|
154
|
+
|
|
155
|
+
(func (export "fd_read_empty") (param i32) (result i32) (call $fd_read (local.get 0) (i32.const 72) (i32.const 1) (i32.const 128)))
|
|
156
|
+
(func (export "fd_write_empty") (param i32) (result i32) (call $fd_write (local.get 0) (i32.const 72) (i32.const 1) (i32.const 128)))
|
|
157
|
+
(func (export "close_twice") (param i32) (result i32)
|
|
158
|
+
(drop (call $fd_close (local.get 0)))
|
|
159
|
+
(call $fd_close (local.get 0)))
|
|
160
|
+
(func (export "renumber_onto_stdin") (param i32) (result i32)
|
|
161
|
+
(drop (call $fd_renumber (local.get 0) (i32.const 0)))
|
|
162
|
+
(call $fd_fdstat_get (i32.const 0) (i32.const 256)))
|
|
163
|
+
(func (export "read_twice") (param i32) (result i32)
|
|
164
|
+
(drop (call $fd_read (local.get 0) (i32.const 64) (i32.const 1) (i32.const 128)))
|
|
165
|
+
(call $fd_read (local.get 0) (i32.const 64) (i32.const 1) (i32.const 132)))
|
|
166
|
+
(func (export "write_twice") (param i32) (result i32)
|
|
167
|
+
(drop (call $fd_write (local.get 0) (i32.const 80) (i32.const 1) (i32.const 128)))
|
|
168
|
+
(call $fd_write (local.get 0) (i32.const 80) (i32.const 1) (i32.const 132))))
|
|
169
|
+
"#;
|
|
170
|
+
|
|
171
|
+
const CALLS: &[&str] = &[
|
|
172
|
+
"fd_advise",
|
|
173
|
+
"fd_allocate",
|
|
174
|
+
"fd_close",
|
|
175
|
+
"fd_datasync",
|
|
176
|
+
"fd_fdstat_get",
|
|
177
|
+
"fd_fdstat_set_flags",
|
|
178
|
+
"fd_fdstat_set_rights",
|
|
179
|
+
"fd_filestat_get",
|
|
180
|
+
"fd_filestat_set_size",
|
|
181
|
+
"fd_filestat_set_times",
|
|
182
|
+
"fd_pread",
|
|
183
|
+
"fd_prestat_get",
|
|
184
|
+
"fd_prestat_dir_name",
|
|
185
|
+
"fd_pwrite",
|
|
186
|
+
"fd_read",
|
|
187
|
+
"fd_readdir",
|
|
188
|
+
"fd_renumber",
|
|
189
|
+
"fd_seek",
|
|
190
|
+
"fd_sync",
|
|
191
|
+
"fd_tell",
|
|
192
|
+
"fd_write",
|
|
193
|
+
"path_create_directory",
|
|
194
|
+
"path_filestat_get",
|
|
195
|
+
"path_filestat_set_times",
|
|
196
|
+
"path_link",
|
|
197
|
+
"path_open",
|
|
198
|
+
"path_readlink",
|
|
199
|
+
"path_remove_directory",
|
|
200
|
+
"path_rename",
|
|
201
|
+
"path_symlink",
|
|
202
|
+
"path_unlink_file",
|
|
203
|
+
"poll_oneoff_read",
|
|
204
|
+
"poll_oneoff_write",
|
|
205
|
+
"poll_oneoff_clock",
|
|
206
|
+
"poll_oneoff_none",
|
|
207
|
+
"sock_accept",
|
|
208
|
+
"sock_recv",
|
|
209
|
+
"sock_send",
|
|
210
|
+
"sock_shutdown",
|
|
211
|
+
"fd_read_far",
|
|
212
|
+
"fd_pread_far",
|
|
213
|
+
"fd_write_far",
|
|
214
|
+
"fd_pwrite_far",
|
|
215
|
+
"fd_readdir_far",
|
|
216
|
+
"path_open_far",
|
|
217
|
+
"path_open_huge",
|
|
218
|
+
"fd_advise_undefined",
|
|
219
|
+
"fd_seek_undefined",
|
|
220
|
+
"fd_fdstat_set_flags_undefined",
|
|
221
|
+
"fd_fdstat_set_rights_undefined",
|
|
222
|
+
"fd_filestat_set_times_undefined",
|
|
223
|
+
"path_filestat_get_undefined",
|
|
224
|
+
"path_open_undefined",
|
|
225
|
+
"path_link_undefined",
|
|
226
|
+
"sock_recv_undefined",
|
|
227
|
+
"sock_shutdown_undefined",
|
|
228
|
+
"fd_read_empty",
|
|
229
|
+
"fd_write_empty",
|
|
230
|
+
"close_twice",
|
|
231
|
+
"renumber_onto_stdin",
|
|
232
|
+
"read_twice",
|
|
233
|
+
"write_twice",
|
|
234
|
+
];
|
|
235
|
+
|
|
236
|
+
/// What one call leaves behind; `None` for a call that trapped.
|
|
237
|
+
#[derive(Debug, PartialEq)]
|
|
238
|
+
struct Observation {
|
|
239
|
+
errno: Option<i32>,
|
|
240
|
+
memory: Vec<u8>,
|
|
241
|
+
stdout: Vec<u8>,
|
|
242
|
+
stderr: Vec<u8>,
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/// A capped Channel exercises the pipe filling mid-write.
|
|
246
|
+
const CHANNEL_LIMITS: [Option<usize>; 2] = [None, Some(3)];
|
|
247
|
+
|
|
248
|
+
fn stdin_bytes() -> Vec<u8> {
|
|
249
|
+
let mut bytes = (PREAMBLE.len() as u32).to_be_bytes().to_vec();
|
|
250
|
+
bytes.extend_from_slice(PREAMBLE);
|
|
251
|
+
bytes
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
fn observe<T>(
|
|
255
|
+
mut store: Store<T>,
|
|
256
|
+
linker: &Linker<T>,
|
|
257
|
+
call: &str,
|
|
258
|
+
fd: i32,
|
|
259
|
+
) -> (Observation, Store<T>) {
|
|
260
|
+
static MODULE: OnceLock<Module> = OnceLock::new();
|
|
261
|
+
let module = MODULE
|
|
262
|
+
.get_or_init(|| Module::new(store.engine(), PROBE_WAT).expect("probe module must compile"));
|
|
263
|
+
let instance = linker
|
|
264
|
+
.instantiate(&mut store, module)
|
|
265
|
+
.expect("probe module must instantiate");
|
|
266
|
+
let errno = instance
|
|
267
|
+
.get_typed_func::<i32, i32>(&mut store, call)
|
|
268
|
+
.expect("probe export must resolve")
|
|
269
|
+
.call(&mut store, fd)
|
|
270
|
+
.ok();
|
|
271
|
+
let memory = instance
|
|
272
|
+
.get_memory(&mut store, "memory")
|
|
273
|
+
.expect("probe exports its memory")
|
|
274
|
+
.data(&store)[..1024]
|
|
275
|
+
.to_vec();
|
|
276
|
+
let observation = Observation {
|
|
277
|
+
errno,
|
|
278
|
+
memory,
|
|
279
|
+
stdout: Vec::new(),
|
|
280
|
+
stderr: Vec::new(),
|
|
281
|
+
};
|
|
282
|
+
(observation, store)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
fn through_kobako(call: &str, fd: i32, limit: Option<usize>) -> Observation {
|
|
286
|
+
let engine = load_engine().expect("shared engine must be constructible");
|
|
287
|
+
let config = Config {
|
|
288
|
+
timeout: None,
|
|
289
|
+
setup_timeout: None,
|
|
290
|
+
memory_limit: None,
|
|
291
|
+
stdout_limit: limit,
|
|
292
|
+
stderr_limit: limit,
|
|
293
|
+
open_file_limit: None,
|
|
294
|
+
profile: Profile::Hermetic,
|
|
295
|
+
security: Vec::new(),
|
|
296
|
+
};
|
|
297
|
+
let wasi = Wasi::for_invocation(&config, stdin_bytes(), &[]).expect("WASI context must build");
|
|
298
|
+
let mut store = Store::new(engine, Invocation::new(None, wasi));
|
|
299
|
+
store.set_epoch_deadline(NO_TIMEOUT_EPOCH_DELTA);
|
|
300
|
+
let linker = imports::linker().expect("host imports must link");
|
|
301
|
+
let (mut observation, store) = observe(store, &linker, call, fd);
|
|
302
|
+
observation.stdout = store.data().wasi().stdout_bytes();
|
|
303
|
+
observation.stderr = store.data().wasi().stderr_bytes();
|
|
304
|
+
observation
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/// Plain wasmtime-wasi preview1 over the same stdin and pipes.
|
|
308
|
+
struct Oracle {
|
|
309
|
+
ctx: WasiP1Ctx,
|
|
310
|
+
stdout: MemoryOutputPipe,
|
|
311
|
+
stderr: MemoryOutputPipe,
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
fn through_wasmtime_wasi(call: &str, fd: i32, limit: Option<usize>) -> Observation {
|
|
315
|
+
let engine = load_engine().expect("shared engine must be constructible");
|
|
316
|
+
let stdout = MemoryOutputPipe::new(kobako_runtime::endpoint::pipe_capacity(limit));
|
|
317
|
+
let stderr = MemoryOutputPipe::new(kobako_runtime::endpoint::pipe_capacity(limit));
|
|
318
|
+
let ctx = WasiCtxBuilder::new()
|
|
319
|
+
.stdin(MemoryInputPipe::new(stdin_bytes()))
|
|
320
|
+
.stdout(stdout.clone())
|
|
321
|
+
.stderr(stderr.clone())
|
|
322
|
+
.build_p1();
|
|
323
|
+
let mut store = Store::new(
|
|
324
|
+
engine,
|
|
325
|
+
Oracle {
|
|
326
|
+
ctx,
|
|
327
|
+
stdout,
|
|
328
|
+
stderr,
|
|
329
|
+
},
|
|
330
|
+
);
|
|
331
|
+
store.set_epoch_deadline(NO_TIMEOUT_EPOCH_DELTA);
|
|
332
|
+
let mut linker = Linker::new(engine);
|
|
333
|
+
p1::add_to_linker_sync(&mut linker, |oracle: &mut Oracle| &mut oracle.ctx)
|
|
334
|
+
.expect("preview1 must link");
|
|
335
|
+
let (mut observation, store) = observe(store, &linker, call, fd);
|
|
336
|
+
observation.stdout = store.data().stdout.contents().to_vec();
|
|
337
|
+
observation.stderr = store.data().stderr.contents().to_vec();
|
|
338
|
+
observation
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Descriptor 3 is not open, so each call can only answer by the order its
|
|
342
|
+
// checks run in.
|
|
343
|
+
// @behavior RT-138
|
|
344
|
+
#[test]
|
|
345
|
+
fn an_argument_preview1_cannot_hold_answers_in_wasmtime_wasis_order() {
|
|
346
|
+
let errno = |call| through_kobako(call, 3, None).errno;
|
|
347
|
+
assert_eq!(
|
|
348
|
+
(
|
|
349
|
+
errno("fd_seek_undefined"),
|
|
350
|
+
errno("fd_read_far"),
|
|
351
|
+
errno("path_open_huge"),
|
|
352
|
+
),
|
|
353
|
+
(None, Some(8), Some(48)),
|
|
354
|
+
"through kobako's preview1 imports on a descriptor not open, an undefined whence must \
|
|
355
|
+
trap, a buffer past memory must answer EBADF before it is bounded, and a path past the \
|
|
356
|
+
call's fuel must answer ENOMEM before it is read, as wasmtime-wasi answers each"
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
#[test]
|
|
361
|
+
fn every_descriptor_call_answers_as_wasmtime_wasi_preview1() {
|
|
362
|
+
for limit in CHANNEL_LIMITS {
|
|
363
|
+
for call in CALLS {
|
|
364
|
+
for fd in 0..=3 {
|
|
365
|
+
let kobako = through_kobako(call, fd, limit);
|
|
366
|
+
if let Some(errno) = answered_as_linux(call, fd) {
|
|
367
|
+
assert_eq!(
|
|
368
|
+
kobako.errno,
|
|
369
|
+
Some(errno),
|
|
370
|
+
"{call} on descriptor {fd} through kobako's preview1 imports must answer \
|
|
371
|
+
as Linux does"
|
|
372
|
+
);
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
assert_eq!(
|
|
376
|
+
kobako,
|
|
377
|
+
through_wasmtime_wasi(call, fd, limit),
|
|
378
|
+
"{call} on descriptor {fd} with Channels capped at {limit:?} through \
|
|
379
|
+
kobako's preview1 imports must answer as wasmtime-wasi's preview1 does"
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/// Where kobako parts from wasmtime-wasi, the errno Linux answers, which
|
|
387
|
+
/// outranks it. For a standard stream it answers as for a pipe: an offset, a
|
|
388
|
+
/// sync, or a size is no pipe's, and a pipe is no directory to open beneath.
|
|
389
|
+
/// Linux also takes `O_APPEND` on a pipe, reports one link where wasmtime-wasi
|
|
390
|
+
/// reports none, checks neither path nor descriptor for a call setting no
|
|
391
|
+
/// time, as `utimensat` returns first, and reports a wait on a descriptor not
|
|
392
|
+
/// open in its event, as `poll` marks it `POLLNVAL`.
|
|
393
|
+
fn answered_as_linux(call: &str, fd: i32) -> Option<i32> {
|
|
394
|
+
const ERRNO_SUCCESS: i32 = 0;
|
|
395
|
+
const ERRNO_INVAL: i32 = 28;
|
|
396
|
+
const ERRNO_NOTDIR: i32 = 54;
|
|
397
|
+
const ERRNO_SPIPE: i32 = 70;
|
|
398
|
+
match (call, fd) {
|
|
399
|
+
("fd_filestat_set_times" | "path_filestat_set_times", 3) => Some(ERRNO_SUCCESS),
|
|
400
|
+
("poll_oneoff_read" | "poll_oneoff_write", 3) => Some(ERRNO_SUCCESS),
|
|
401
|
+
("fd_advise", 0..=2) => Some(ERRNO_SPIPE),
|
|
402
|
+
("fd_allocate" | "fd_sync" | "fd_datasync" | "fd_filestat_set_size", 0..=2) => {
|
|
403
|
+
Some(ERRNO_INVAL)
|
|
404
|
+
}
|
|
405
|
+
(
|
|
406
|
+
"fd_fdstat_set_flags"
|
|
407
|
+
| "fd_filestat_get"
|
|
408
|
+
| "fd_filestat_set_times"
|
|
409
|
+
| "path_filestat_set_times",
|
|
410
|
+
0..=2,
|
|
411
|
+
) => Some(ERRNO_SUCCESS),
|
|
412
|
+
("fd_read_empty", 0) => Some(ERRNO_SUCCESS),
|
|
413
|
+
("fd_readdir" | "fd_readdir_far", 0..=2) => Some(ERRNO_NOTDIR),
|
|
414
|
+
(
|
|
415
|
+
"path_create_directory"
|
|
416
|
+
| "path_filestat_get"
|
|
417
|
+
| "path_link"
|
|
418
|
+
| "path_open"
|
|
419
|
+
| "path_readlink"
|
|
420
|
+
| "path_remove_directory"
|
|
421
|
+
| "path_rename"
|
|
422
|
+
| "path_symlink"
|
|
423
|
+
| "path_unlink_file",
|
|
424
|
+
0..=2,
|
|
425
|
+
) => Some(ERRNO_NOTDIR),
|
|
426
|
+
("fd_pread" | "fd_pread_far", 1 | 2) => Some(ERRNO_SPIPE),
|
|
427
|
+
("fd_pwrite" | "fd_pwrite_far", 0) => Some(ERRNO_SPIPE),
|
|
428
|
+
_ => None,
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// wasmtime-wasi fails the whole wait; the witx gives each event its own error,
|
|
433
|
+
// and Linux's `do_pollfd` marks the descriptor `POLLNVAL`, which wasi-libc reads
|
|
434
|
+
// back from an event's EBADF.
|
|
435
|
+
// @behavior RT-295
|
|
436
|
+
#[test]
|
|
437
|
+
fn a_wait_on_a_descriptor_not_open_reports_it_in_its_event() {
|
|
438
|
+
let read = through_kobako("poll_oneoff_read", 3, None);
|
|
439
|
+
let write = through_kobako("poll_oneoff_write", 3, None);
|
|
440
|
+
let event = |seen: &Observation| {
|
|
441
|
+
(
|
|
442
|
+
seen.errno,
|
|
443
|
+
u64::from_le_bytes(seen.memory[896..904].try_into().expect("eight bytes")),
|
|
444
|
+
u16::from_le_bytes([seen.memory[904], seen.memory[905]]),
|
|
445
|
+
u32::from_le_bytes(seen.memory[128..132].try_into().expect("four bytes")),
|
|
446
|
+
)
|
|
447
|
+
};
|
|
448
|
+
assert_eq!(
|
|
449
|
+
(event(&read), event(&write)),
|
|
450
|
+
((Some(0), 7, 8, 1), (Some(0), 7, 8, 1)),
|
|
451
|
+
"poll_oneoff on descriptor 3, not open, through kobako's preview1 imports must succeed \
|
|
452
|
+
with one event whose error is EBADF, as Linux's poll marks it POLLNVAL"
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Linux's `ksys_ftruncate` reads the length as a signed `loff_t` and refuses a
|
|
457
|
+
// negative one before it looks for the descriptor.
|
|
458
|
+
// @behavior RT-296
|
|
459
|
+
#[test]
|
|
460
|
+
fn a_size_no_file_can_take_answers_before_the_descriptor_is_found() {
|
|
461
|
+
assert_eq!(
|
|
462
|
+
through_kobako("fd_filestat_set_size_huge", 3, None).errno,
|
|
463
|
+
Some(28),
|
|
464
|
+
"fd_filestat_set_size of 2^64 - 1 on descriptor 3, not open, through kobako's preview1 \
|
|
465
|
+
imports must answer EINVAL, as ftruncate checks the length before the descriptor"
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// wasmtime-wasi answers EBADF for the end a call cannot use; Linux reads
|
|
470
|
+
// `FMODE_PREAD` first, and a pipe has none on either end.
|
|
471
|
+
// @behavior RT-178
|
|
472
|
+
#[test]
|
|
473
|
+
fn a_standard_stream_takes_no_offset() {
|
|
474
|
+
let errno = |call, fd| through_kobako(call, fd, None).errno;
|
|
475
|
+
assert_eq!(
|
|
476
|
+
[
|
|
477
|
+
errno("fd_pread", 0),
|
|
478
|
+
errno("fd_pread", 1),
|
|
479
|
+
errno("fd_pwrite", 0),
|
|
480
|
+
errno("fd_pwrite", 1),
|
|
481
|
+
],
|
|
482
|
+
[Some(70), Some(70), Some(70), Some(70)],
|
|
483
|
+
"fd_pread and fd_pwrite on standard input and output through kobako's preview1 imports \
|
|
484
|
+
must answer ESPIPE, as Linux answers for a pipe's either end"
|
|
485
|
+
);
|
|
486
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
//! What a preview1 call answers when it does not succeed: an error code
|
|
2
|
+
//! the guest reads, or a trap that ends the invocation.
|
|
3
|
+
|
|
4
|
+
use kobako_runtime::filesystem::FilesystemError;
|
|
5
|
+
use wiggle::GuestError;
|
|
6
|
+
|
|
7
|
+
use super::types;
|
|
8
|
+
pub(crate) use super::types::Errno;
|
|
9
|
+
|
|
10
|
+
pub(crate) type Answer<T = ()> = Result<T, types::Error>;
|
|
11
|
+
|
|
12
|
+
/// The code a POSIX error name a Filesystem's failure carries stands for:
|
|
13
|
+
/// the witx case, `E` prefixed and upper-cased.
|
|
14
|
+
fn from_posix_name(name: &str) -> Option<Errno> {
|
|
15
|
+
Some(match name {
|
|
16
|
+
"EAGAIN" => Errno::Again,
|
|
17
|
+
"ENOENT" => Errno::Noent,
|
|
18
|
+
"EEXIST" => Errno::Exist,
|
|
19
|
+
"ENOTDIR" => Errno::Notdir,
|
|
20
|
+
"EISDIR" => Errno::Isdir,
|
|
21
|
+
"ENOTEMPTY" => Errno::Notempty,
|
|
22
|
+
"EACCES" => Errno::Acces,
|
|
23
|
+
"EROFS" => Errno::Rofs,
|
|
24
|
+
"EINVAL" => Errno::Inval,
|
|
25
|
+
"ENOTSUP" => Errno::Notsup,
|
|
26
|
+
"ENOSPC" => Errno::Nospc,
|
|
27
|
+
"EFBIG" => Errno::Fbig,
|
|
28
|
+
"EXDEV" => Errno::Xdev,
|
|
29
|
+
"EDEADLK" => Errno::Deadlk,
|
|
30
|
+
"ELOOP" => Errno::Loop,
|
|
31
|
+
"EPERM" => Errno::Perm,
|
|
32
|
+
"EBADF" => Errno::Badf,
|
|
33
|
+
"EBUSY" => Errno::Busy,
|
|
34
|
+
"EILSEQ" => Errno::Ilseq,
|
|
35
|
+
"EINTR" => Errno::Intr,
|
|
36
|
+
"EMFILE" => Errno::Mfile,
|
|
37
|
+
"ENAMETOOLONG" => Errno::Nametoolong,
|
|
38
|
+
"ENOMEM" => Errno::Nomem,
|
|
39
|
+
"ENOTSOCK" => Errno::Notsock,
|
|
40
|
+
"EOVERFLOW" => Errno::Overflow,
|
|
41
|
+
"ESPIPE" => Errno::Spipe,
|
|
42
|
+
"ENODEV" => Errno::Nodev,
|
|
43
|
+
"ESTALE" => Errno::Stale,
|
|
44
|
+
_ => return None,
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/// A pointer outside the memory or misaligned traps, as the witx
|
|
49
|
+
/// documentation requires; a value inside memory that no witx type holds is
|
|
50
|
+
/// `INVAL`, as in wasmtime-wasi.
|
|
51
|
+
impl From<GuestError> for types::Error {
|
|
52
|
+
fn from(error: GuestError) -> Self {
|
|
53
|
+
match error {
|
|
54
|
+
GuestError::InvalidFlagValue { .. } | GuestError::InvalidEnumValue { .. } => {
|
|
55
|
+
Errno::Inval.into()
|
|
56
|
+
}
|
|
57
|
+
GuestError::PtrOverflow
|
|
58
|
+
| GuestError::PtrOutOfBounds { .. }
|
|
59
|
+
| GuestError::PtrNotAligned { .. } => Self::trap(error.into()),
|
|
60
|
+
GuestError::InvalidUtf8 { .. } => Errno::Ilseq.into(),
|
|
61
|
+
GuestError::TryFromIntError { .. } => Errno::Overflow.into(),
|
|
62
|
+
GuestError::SliceLengthsDiffer => Errno::Fault.into(),
|
|
63
|
+
GuestError::InFunc { err, .. } => Self::from(*err),
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/// The code a call's failure reaches the guest as: the one its POSIX name
|
|
69
|
+
/// stands for, or `IO` for a failure without one. A path escaping its
|
|
70
|
+
/// descriptor is `NOTCAPABLE`, as preview1 names a capability the guest does
|
|
71
|
+
/// not hold, where Linux answers EXDEV.
|
|
72
|
+
impl From<FilesystemError> for types::Error {
|
|
73
|
+
fn from(error: FilesystemError) -> Self {
|
|
74
|
+
match error {
|
|
75
|
+
FilesystemError::Escape => Errno::Notcapable,
|
|
76
|
+
error => error
|
|
77
|
+
.posix_name()
|
|
78
|
+
.and_then(from_posix_name)
|
|
79
|
+
.unwrap_or(Errno::Io),
|
|
80
|
+
}
|
|
81
|
+
.into()
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
#[cfg(test)]
|
|
86
|
+
mod tests {
|
|
87
|
+
use super::*;
|
|
88
|
+
|
|
89
|
+
#[test]
|
|
90
|
+
fn each_filesystem_failure_reaches_the_guest_as_its_own_code() {
|
|
91
|
+
let cases = [
|
|
92
|
+
(FilesystemError::NotFound, 44),
|
|
93
|
+
(FilesystemError::AlreadyExists, 20),
|
|
94
|
+
(FilesystemError::NotADirectory, 54),
|
|
95
|
+
(FilesystemError::IsADirectory, 31),
|
|
96
|
+
(FilesystemError::DirectoryNotEmpty, 55),
|
|
97
|
+
(FilesystemError::PermissionDenied, 2),
|
|
98
|
+
(FilesystemError::ReadOnlyFilesystem, 69),
|
|
99
|
+
(FilesystemError::InvalidInput, 28),
|
|
100
|
+
(FilesystemError::Unsupported, 58),
|
|
101
|
+
(FilesystemError::StorageFull, 51),
|
|
102
|
+
(FilesystemError::FileTooLarge, 22),
|
|
103
|
+
(FilesystemError::CrossesDevices, 75),
|
|
104
|
+
(FilesystemError::Escape, 76),
|
|
105
|
+
(FilesystemError::Deadlock, 16),
|
|
106
|
+
(FilesystemError::FilesystemLoop, 32),
|
|
107
|
+
(FilesystemError::NotPermitted, 63),
|
|
108
|
+
(FilesystemError::BadDescriptor, 8),
|
|
109
|
+
(FilesystemError::ResourceBusy, 10),
|
|
110
|
+
(FilesystemError::IllegalSequence, 25),
|
|
111
|
+
(FilesystemError::Interrupted, 27),
|
|
112
|
+
(FilesystemError::WouldBlock, 6),
|
|
113
|
+
(FilesystemError::TooManyOpenFiles, 33),
|
|
114
|
+
(FilesystemError::InvalidFilename, 37),
|
|
115
|
+
(FilesystemError::OutOfMemory, 48),
|
|
116
|
+
(FilesystemError::NotASocket, 57),
|
|
117
|
+
(FilesystemError::ValueTooLarge, 61),
|
|
118
|
+
(FilesystemError::NotSeekable, 70),
|
|
119
|
+
(FilesystemError::NoSuchDevice, 43),
|
|
120
|
+
(FilesystemError::StaleNetworkFileHandle, 72),
|
|
121
|
+
(FilesystemError::Other("x".into()), 29),
|
|
122
|
+
];
|
|
123
|
+
for (error, code) in cases {
|
|
124
|
+
let label = format!("{error:?}");
|
|
125
|
+
let Ok(errno) = types::Error::from(error).downcast() else {
|
|
126
|
+
panic!("{label} must answer an error code, not a trap");
|
|
127
|
+
};
|
|
128
|
+
assert_eq!(
|
|
129
|
+
u16::from(errno),
|
|
130
|
+
code,
|
|
131
|
+
"{label} through types::Error::from must reach the guest as preview1 errno {code}"
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|