kobako 0.29.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +217 -10
- data/Cargo.lock +139 -141
- data/README.md +96 -574
- data/ROADMAP.md +2 -1
- data/crates/kobako-runtime/CHANGELOG.md +269 -1
- data/crates/kobako-runtime/Cargo.toml +25 -15
- data/crates/kobako-runtime/README.md +7 -3
- data/crates/kobako-runtime/src/dispatch.rs +1 -1
- data/crates/kobako-runtime/src/endpoint/buffer.rs +192 -0
- data/crates/kobako-runtime/src/endpoint/capture.rs +115 -0
- data/crates/kobako-runtime/src/endpoint/dispatch.rs +253 -0
- data/crates/kobako-runtime/src/endpoint/invoke.rs +194 -0
- data/crates/kobako-runtime/src/endpoint/memory_cap.rs +238 -0
- data/crates/kobako-runtime/src/endpoint.rs +166 -0
- data/crates/kobako-runtime/src/filesystem/bind/fid.rs +82 -0
- data/crates/kobako-runtime/src/filesystem/{directory/spelling.rs → bind/lookup.rs} +91 -49
- data/crates/kobako-runtime/src/filesystem/bind/walk.rs +249 -0
- data/crates/kobako-runtime/src/filesystem/bind.rs +2665 -0
- data/crates/kobako-runtime/src/filesystem/overlay/file.rs +89 -0
- data/crates/kobako-runtime/src/filesystem/overlay/inode.rs +106 -0
- data/crates/kobako-runtime/src/filesystem/overlay/path.rs +285 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/copy_up.rs +135 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/dir.rs +263 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/namei.rs +135 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/readdir.rs +57 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack.rs +227 -734
- data/crates/kobako-runtime/src/filesystem/overlay.rs +2649 -1135
- data/crates/kobako-runtime/src/filesystem/path.rs +2 -41
- data/crates/kobako-runtime/src/filesystem/pipe.rs +221 -0
- data/crates/kobako-runtime/src/filesystem/rdir.rs +101 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs/tmpfile.rs +32 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs/whiteout.rs +85 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs/xattr.rs +22 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs.rs +1985 -0
- data/crates/kobako-runtime/src/filesystem/wrap.rs +224 -0
- data/crates/kobako-runtime/src/filesystem.rs +21 -387
- data/crates/kobako-runtime/src/hashtable.rs +44 -0
- data/crates/kobako-runtime/src/lib.rs +11 -6
- data/crates/kobako-runtime/src/locking/seqlock.rs +66 -0
- data/crates/kobako-runtime/src/locking.rs +218 -0
- data/crates/kobako-runtime/src/mount.rs +31 -104
- data/crates/kobako-runtime/src/profile.rs +137 -1
- data/crates/kobako-runtime/src/sched.rs +210 -0
- data/crates/kobako-runtime/src/security.rs +696 -0
- data/crates/kobako-runtime/src/vfs/attr.rs +162 -0
- data/crates/kobako-runtime/src/vfs/cred.rs +334 -0
- data/crates/kobako-runtime/src/vfs/dentry.rs +1412 -0
- data/crates/kobako-runtime/src/vfs/errno.rs +153 -0
- data/crates/kobako-runtime/src/vfs/file.rs +1915 -0
- data/crates/kobako-runtime/src/vfs/inode.rs +429 -0
- data/crates/kobako-runtime/src/vfs/kern_path.rs +217 -0
- data/crates/kobako-runtime/src/vfs/mount.rs +59 -0
- data/crates/kobako-runtime/src/vfs/namei.rs +5708 -0
- data/crates/kobako-runtime/src/vfs/openat2.rs +116 -0
- data/crates/kobako-runtime/src/vfs/path.rs +64 -0
- data/crates/kobako-runtime/src/vfs/readdir.rs +163 -0
- data/crates/kobako-runtime/src/vfs/stat.rs +157 -0
- data/crates/kobako-runtime/src/vfs/super_block.rs +122 -0
- data/crates/kobako-runtime/src/vfs/utimes.rs +289 -0
- data/crates/kobako-runtime/src/vfs.rs +60 -0
- data/crates/kobako-runtime/tests/linux_baseline/calls.rs +171 -0
- data/crates/kobako-runtime/tests/linux_baseline/concurrent.rs +276 -0
- data/crates/kobako-runtime/tests/linux_baseline/kobako.rs +323 -0
- data/crates/kobako-runtime/tests/linux_baseline/linux-baseline-expected.txt +16 -0
- data/crates/kobako-runtime/tests/linux_baseline/linux.rs +449 -0
- data/crates/kobako-runtime/tests/linux_baseline/main.rs +611 -0
- data/crates/kobako-runtime/tests/linux_baseline/permissions.rs +236 -0
- data/crates/kobako-transport/CHANGELOG.md +8 -2
- data/crates/kobako-transport/Cargo.toml +3 -3
- data/crates/kobako-transport/README.md +6 -5
- data/crates/kobako-transport/src/envelope/bytes.rs +33 -25
- data/crates/kobako-transport/src/envelope/call.rs +5 -5
- data/crates/kobako-transport/src/envelope/compile_reply.rs +2 -2
- data/crates/kobako-transport/src/envelope/error_record.rs +3 -3
- data/crates/kobako-transport/src/envelope/fault.rs +2 -2
- data/crates/kobako-transport/src/envelope/outcome.rs +3 -3
- data/crates/kobako-transport/src/envelope/preamble.rs +12 -12
- data/crates/kobako-transport/src/envelope/reply.rs +2 -2
- data/crates/kobako-transport/src/envelope/request.rs +2 -2
- data/crates/kobako-transport/src/envelope/run.rs +1 -1
- data/crates/kobako-transport/src/envelope/unsupported.rs +2 -2
- data/crates/kobako-transport/src/envelope.rs +2 -2
- data/crates/kobako-wasmtime/CHANGELOG.md +154 -8
- data/crates/kobako-wasmtime/Cargo.toml +15 -14
- data/crates/kobako-wasmtime/README.md +9 -5
- data/crates/kobako-wasmtime/src/abi.rs +12 -20
- data/crates/kobako-wasmtime/src/ambient.rs +1 -2
- data/crates/kobako-wasmtime/src/cache.rs +5 -5
- data/crates/kobako-wasmtime/src/config.rs +22 -7
- data/crates/kobako-wasmtime/src/descriptors.rs +133 -100
- data/crates/kobako-wasmtime/src/driver.rs +29 -96
- data/crates/kobako-wasmtime/src/exports.rs +5 -16
- data/crates/kobako-wasmtime/src/guest.rs +38 -0
- data/crates/kobako-wasmtime/src/imports.rs +14 -8
- data/crates/kobako-wasmtime/src/instance_pre.rs +3 -3
- data/crates/kobako-wasmtime/src/invocation.rs +26 -46
- data/crates/kobako-wasmtime/src/lib.rs +1 -4
- data/crates/kobako-wasmtime/src/limiter.rs +6 -230
- data/crates/kobako-wasmtime/src/preview1/differential/mount.rs +69 -46
- data/crates/kobako-wasmtime/src/preview1/differential.rs +121 -7
- data/crates/kobako-wasmtime/src/preview1/errno.rs +74 -206
- data/crates/kobako-wasmtime/src/preview1/fd.rs +1248 -500
- data/crates/kobako-wasmtime/src/preview1/memory.rs +117 -150
- data/crates/kobako-wasmtime/src/preview1/mount_view/wrap.rs +211 -0
- data/crates/kobako-wasmtime/src/preview1/mount_view.rs +1939 -672
- data/crates/kobako-wasmtime/src/preview1/path.rs +230 -349
- data/crates/kobako-wasmtime/src/preview1/poll.rs +148 -100
- data/crates/kobako-wasmtime/src/preview1/sock.rs +6 -23
- data/crates/kobako-wasmtime/src/preview1/testsuite-expected.txt +6 -0
- data/crates/kobako-wasmtime/src/preview1/testsuite.rs +157 -0
- data/crates/kobako-wasmtime/src/preview1.rs +464 -413
- data/crates/kobako-wasmtime/src/trap.rs +9 -9
- data/crates/kobako-wasmtime/src/wasi.rs +53 -37
- data/crates/kobako-wasmtime/tests/{guest_file.rs → integration/guest_file.rs} +232 -201
- data/crates/kobako-wasmtime/tests/integration/main.rs +29 -0
- data/crates/kobako-wasmtime/tests/{preamble_flags.rs → integration/preamble_flags.rs} +1 -1
- data/crates/kobako-wasmtime/tests/{snippet_compile.rs → integration/snippet_compile.rs} +1 -1
- data/crates/kobako-wasmtime/tests/{unanswered_call.rs → integration/unanswered_call.rs} +1 -1
- data/crates/kobako-wasmtime/tests/{unsupported_call.rs → integration/unsupported_call.rs} +1 -1
- data/crates/kobako-wasmtime/witx/typenames.witx +750 -0
- data/crates/kobako-wasmtime/witx/wasi_snapshot_preview1.witx +441 -0
- data/data/kobako.wasm +0 -0
- data/ext/kobako/Cargo.toml +1 -1
- data/ext/kobako/src/runtime/bridge.rs +1 -1
- data/ext/kobako/src/runtime/errors.rs +1 -1
- data/ext/kobako/src/runtime/escape.rs +28 -14
- data/ext/kobako/src/runtime/filesystem/grants.rs +21 -5
- data/ext/kobako/src/runtime/filesystem/ruby.rs +464 -156
- data/ext/kobako/src/runtime/filesystem.rs +109 -137
- data/ext/kobako/src/runtime/gvl.rs +43 -8
- data/ext/kobako/src/runtime/snapshot.rs +1 -1
- data/ext/kobako/src/runtime.rs +6 -1
- data/lib/kobako/catalog/extensions.rb +5 -5
- data/lib/kobako/catalog/flags.rb +2 -2
- data/lib/kobako/catalog/handles.rb +6 -6
- data/lib/kobako/catalog/mount.rb +2 -10
- data/lib/kobako/catalog/services.rb +2 -2
- data/lib/kobako/catalog/snippets.rb +7 -7
- data/lib/kobako/catalog.rb +1 -1
- data/lib/kobako/codec/decoder.rb +10 -10
- data/lib/kobako/codec/ext_types.rb +3 -5
- data/lib/kobako/codec/handle_walk.rb +4 -6
- data/lib/kobako/codec/nesting.rb +7 -7
- data/lib/kobako/codec/utils.rb +1 -1
- data/lib/kobako/codec/value_budget.rb +3 -3
- data/lib/kobako/context.rb +5 -5
- data/lib/kobako/filesystem.rb +44 -13
- data/lib/kobako/payload/arguments.rb +2 -2
- data/lib/kobako/pool.rb +4 -4
- data/lib/kobako/sandbox.rb +28 -28
- data/lib/kobako/sandbox_options.rb +2 -3
- data/lib/kobako/snippet/bytecode.rb +1 -1
- data/lib/kobako/snippet/source.rb +1 -1
- data/lib/kobako/transport/dispatcher.rb +5 -5
- data/lib/kobako/transport/exposure.rb +2 -2
- data/lib/kobako/transport/reflection.rb +7 -7
- data/lib/kobako/transport/run.rb +5 -5
- data/lib/kobako/transport/yielder.rb +1 -1
- data/lib/kobako/version.rb +1 -1
- data/lib/kobako.rb +34 -0
- data/release-please-config.json +1 -0
- data/sig/kobako/catalog/extensions.rbs +3 -3
- data/sig/kobako/catalog/handles.rbs +3 -3
- data/sig/kobako/catalog/mount.rbs +0 -4
- data/sig/kobako/catalog/services.rbs +1 -1
- data/sig/kobako/catalog/snippets.rbs +3 -3
- data/sig/kobako/catalog.rbs +1 -1
- data/sig/kobako/codec/decoder.rbs +1 -1
- data/sig/kobako/codec/handle_walk.rbs +1 -1
- data/sig/kobako/codec/nesting.rbs +2 -2
- data/sig/kobako/codec/utils.rbs +1 -1
- data/sig/kobako/codec/value_budget.rbs +1 -1
- data/sig/kobako/codec.rbs +1 -2
- data/sig/kobako/context.rbs +1 -1
- data/sig/kobako/filesystem.rbs +19 -14
- data/sig/kobako/guest_yielder.rbs +1 -1
- data/sig/kobako/payload/arguments.rbs +1 -1
- data/sig/kobako/pool.rbs +2 -2
- data/sig/kobako/runtime.rbs +1 -1
- data/sig/kobako/sandbox.rbs +3 -5
- data/sig/kobako/sandbox_options.rbs +1 -1
- data/sig/kobako/transport/dispatcher.rbs +2 -2
- data/sig/kobako/transport/exposure.rbs +1 -1
- data/sig/kobako/transport/reflection.rbs +2 -2
- data/sig/kobako/transport/run.rbs +3 -3
- data/sig/kobako/transport.rbs +1 -1
- metadata +67 -19
- data/crates/kobako-runtime/src/filesystem/directory.rs +0 -1082
- data/crates/kobako-runtime/src/filesystem/memory.rs +0 -1100
- data/crates/kobako-runtime/src/filesystem/overlay/exclusive.rs +0 -88
- data/crates/kobako-runtime/src/filesystem/temporary.rs +0 -167
- data/crates/kobako-wasmtime/src/capture.rs +0 -96
- data/crates/kobako-wasmtime/src/dispatch.rs +0 -125
- data/crates/kobako-wasmtime/src/guest_mem.rs +0 -271
- data/crates/kobako-wasmtime/src/io.rs +0 -89
- data/crates/kobako-wasmtime/src/preview1/args.rs +0 -60
- data/crates/kobako-wasmtime/src/preview1/beneath.rs +0 -188
- /data/crates/kobako-wasmtime/tests/{compile_posture.rs → integration/compile_posture.rs} +0 -0
- /data/crates/kobako-wasmtime/tests/{support → integration/support}/mod.rs +0 -0
|
@@ -10,67 +10,51 @@
|
|
|
10
10
|
use std::thread;
|
|
11
11
|
use std::time::{Duration, Instant};
|
|
12
12
|
|
|
13
|
-
use
|
|
13
|
+
use kobako_runtime::vfs::PollMask;
|
|
14
|
+
use wiggle::{GuestPtr, GuestType};
|
|
15
|
+
|
|
16
|
+
use super::errno::{Answer, Errno};
|
|
17
|
+
use super::fd::file;
|
|
14
18
|
use super::memory::GuestMemory;
|
|
15
|
-
use
|
|
19
|
+
use super::types::{self, Clockid, Eventrwflags, Eventtype, Subclockflags, SubscriptionU};
|
|
16
20
|
use crate::invocation::Invocation;
|
|
17
21
|
use crate::trap::TimeoutTrap;
|
|
18
22
|
|
|
19
|
-
|
|
20
|
-
const EVENT_SIZE: u32 = 32;
|
|
21
|
-
|
|
22
|
-
const TAG_CLOCK: u8 = 0;
|
|
23
|
-
const TAG_FD_READ: u8 = 1;
|
|
24
|
-
const TAG_FD_WRITE: u8 = 2;
|
|
25
|
-
|
|
26
|
-
const CLOCK_REALTIME: u32 = 0;
|
|
27
|
-
const CLOCK_MONOTONIC: u32 = 1;
|
|
28
|
-
/// The highest `clockid` variant.
|
|
29
|
-
const CLOCK_MAX: u32 = 3;
|
|
30
|
-
const SUBCLOCKFLAGS_ABSTIME: u16 = 1;
|
|
31
|
-
|
|
32
|
-
/// One subscription as the guest wrote it.
|
|
23
|
+
/// One subscription as the guest wrote it, and the error its event reports.
|
|
33
24
|
struct Subscription {
|
|
34
25
|
userdata: u64,
|
|
35
26
|
kind: Kind,
|
|
27
|
+
error: Errno,
|
|
36
28
|
}
|
|
37
29
|
|
|
38
30
|
enum Kind {
|
|
39
31
|
/// A clock, as the nanoseconds left before it fires.
|
|
40
32
|
Clock(u64),
|
|
41
|
-
|
|
42
|
-
/// its position.
|
|
43
|
-
FdRead {
|
|
44
|
-
hangup: bool,
|
|
45
|
-
},
|
|
33
|
+
FdRead,
|
|
46
34
|
FdWrite,
|
|
47
35
|
}
|
|
48
36
|
|
|
49
|
-
const EVENTRWFLAGS_HANGUP: u16 = 1;
|
|
50
|
-
|
|
51
37
|
pub(crate) fn poll_oneoff(
|
|
52
38
|
invocation: &Invocation,
|
|
53
|
-
mem: &mut GuestMemory,
|
|
54
|
-
subs:
|
|
55
|
-
events:
|
|
39
|
+
mem: &mut GuestMemory<'_, '_>,
|
|
40
|
+
subs: GuestPtr<types::Subscription>,
|
|
41
|
+
events: GuestPtr<types::Event>,
|
|
56
42
|
count: u32,
|
|
57
|
-
|
|
58
|
-
) -> Answer {
|
|
43
|
+
) -> Answer<types::Size> {
|
|
59
44
|
if count == 0 {
|
|
60
45
|
return Err(Errno::Inval.into());
|
|
61
46
|
}
|
|
62
|
-
mem.consume(count,
|
|
63
|
-
mem.consume(count,
|
|
64
|
-
let subscriptions =
|
|
65
|
-
.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
})
|
|
47
|
+
mem.consume(count, types::Subscription::guest_size())?;
|
|
48
|
+
mem.consume(count, types::Event::guest_size())?;
|
|
49
|
+
let subscriptions = subs
|
|
50
|
+
.as_array(count)
|
|
51
|
+
.iter()
|
|
52
|
+
.map(|sub| read_subscription(invocation, mem, sub?))
|
|
69
53
|
.collect::<Answer<Vec<_>>>()?;
|
|
70
54
|
|
|
71
55
|
let clocks = subscriptions.iter().filter_map(|sub| match sub.kind {
|
|
72
56
|
Kind::Clock(left) => Some(left),
|
|
73
|
-
Kind::FdRead
|
|
57
|
+
Kind::FdRead | Kind::FdWrite => None,
|
|
74
58
|
});
|
|
75
59
|
let waited = if clocks.clone().count() == subscriptions.len() {
|
|
76
60
|
let soonest = clocks.min().unwrap_or(0);
|
|
@@ -84,71 +68,77 @@ pub(crate) fn poll_oneoff(
|
|
|
84
68
|
for sub in &subscriptions {
|
|
85
69
|
let (ready, nbytes) = match sub.kind {
|
|
86
70
|
Kind::Clock(left) => (left <= waited, 0),
|
|
87
|
-
Kind::FdRead
|
|
71
|
+
Kind::FdRead | Kind::FdWrite => (true, u64::from(sub.error == Errno::Success)),
|
|
88
72
|
};
|
|
89
73
|
if ready {
|
|
90
|
-
|
|
91
|
-
write_event(mem, event, sub, nbytes)?;
|
|
74
|
+
mem.write(events.add(written)?, to_event(sub, nbytes))?;
|
|
92
75
|
written += 1;
|
|
93
76
|
}
|
|
94
77
|
}
|
|
95
|
-
|
|
78
|
+
Ok(written)
|
|
96
79
|
}
|
|
97
80
|
|
|
98
|
-
fn read_subscription(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
},
|
|
115
|
-
_ => return Err(Errno::Inval.into()),
|
|
81
|
+
fn read_subscription(
|
|
82
|
+
invocation: &Invocation,
|
|
83
|
+
mem: &GuestMemory<'_, '_>,
|
|
84
|
+
sub: GuestPtr<types::Subscription>,
|
|
85
|
+
) -> Answer<Subscription> {
|
|
86
|
+
let sub = mem.read(sub)?;
|
|
87
|
+
let (kind, error) = match sub.u {
|
|
88
|
+
SubscriptionU::Clock(clock) => (read_clock(invocation, &clock)?, Errno::Success),
|
|
89
|
+
SubscriptionU::FdRead(subscribed) => (
|
|
90
|
+
Kind::FdRead,
|
|
91
|
+
poll_descriptor(invocation, subscribed, PollMask::IN)?,
|
|
92
|
+
),
|
|
93
|
+
SubscriptionU::FdWrite(subscribed) => (
|
|
94
|
+
Kind::FdWrite,
|
|
95
|
+
poll_descriptor(invocation, subscribed, PollMask::OUT)?,
|
|
96
|
+
),
|
|
116
97
|
};
|
|
117
|
-
Ok(Subscription {
|
|
98
|
+
Ok(Subscription {
|
|
99
|
+
userdata: sub.userdata,
|
|
100
|
+
kind,
|
|
101
|
+
error,
|
|
102
|
+
})
|
|
118
103
|
}
|
|
119
104
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
105
|
+
/// Hold a subscription to what its descriptor would find ready, answering the
|
|
106
|
+
/// error its event reports. A number no descriptor holds, or one standing only
|
|
107
|
+
/// for its path, is reported in its event, as Linux's `do_pollfd` marks it
|
|
108
|
+
/// `POLLNVAL` when `fdget` refuses it and the wait goes on. A
|
|
109
|
+
/// pipe's end is never ready for what the other end does, and nothing here
|
|
110
|
+
/// would wake a wait on it, so that is `BADF` rather than a wait that never
|
|
111
|
+
/// ends.
|
|
112
|
+
fn poll_descriptor(
|
|
113
|
+
invocation: &Invocation,
|
|
114
|
+
subscribed: types::SubscriptionFdReadwrite,
|
|
115
|
+
event: PollMask,
|
|
116
|
+
) -> Answer<Errno> {
|
|
117
|
+
let descriptors = invocation.wasi().descriptors();
|
|
118
|
+
let Ok(file) = file(descriptors, subscribed.file_descriptor.into()) else {
|
|
119
|
+
return Ok(Errno::Badf);
|
|
120
|
+
};
|
|
121
|
+
match file.poll().contains(event) {
|
|
122
|
+
true => Ok(Errno::Success),
|
|
123
|
+
false => Err(Errno::Badf.into()),
|
|
124
|
+
}
|
|
131
125
|
}
|
|
132
126
|
|
|
133
127
|
/// A clock, read against the guest's own clocks. Only the realtime and
|
|
134
128
|
/// monotonic clocks can be waited on, alone or among other
|
|
135
129
|
/// subscriptions, as in wasmtime-wasi.
|
|
136
|
-
fn read_clock(invocation: &Invocation,
|
|
137
|
-
let
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if id > CLOCK_MAX || flags & !SUBCLOCKFLAGS_ABSTIME != 0 {
|
|
141
|
-
return Err(Errno::Inval.into());
|
|
142
|
-
}
|
|
143
|
-
let absolute = flags & SUBCLOCKFLAGS_ABSTIME != 0;
|
|
130
|
+
fn read_clock(invocation: &Invocation, clock: &types::SubscriptionClock) -> Answer<Kind> {
|
|
131
|
+
let absolute = clock
|
|
132
|
+
.flags
|
|
133
|
+
.contains(Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME);
|
|
144
134
|
let clocks = invocation.wasi().clocks();
|
|
145
|
-
let now = match (id, absolute) {
|
|
146
|
-
(
|
|
147
|
-
(
|
|
148
|
-
(
|
|
135
|
+
let now = match (clock.id, absolute) {
|
|
136
|
+
(Clockid::Realtime | Clockid::Monotonic, false) => 0,
|
|
137
|
+
(Clockid::Monotonic, true) => clocks.monotonic_now(),
|
|
138
|
+
(Clockid::Realtime, true) => clocks.wall_now(),
|
|
149
139
|
_ => return Err(Errno::Inval.into()),
|
|
150
140
|
};
|
|
151
|
-
Ok(Kind::Clock(timeout.saturating_sub(now)))
|
|
141
|
+
Ok(Kind::Clock(clock.timeout.saturating_sub(now)))
|
|
152
142
|
}
|
|
153
143
|
|
|
154
144
|
/// Wait `nanos` of the guest's time: none on frozen clocks, and on live
|
|
@@ -163,24 +153,27 @@ fn wait(invocation: &Invocation, nanos: u64) -> Answer {
|
|
|
163
153
|
let left = deadline.saturating_duration_since(Instant::now());
|
|
164
154
|
if wanted >= left {
|
|
165
155
|
thread::sleep(left);
|
|
166
|
-
return Err(
|
|
156
|
+
return Err(types::Error::trap(wasmtime::Error::new(TimeoutTrap)));
|
|
167
157
|
}
|
|
168
158
|
}
|
|
169
159
|
thread::sleep(wanted);
|
|
170
160
|
Ok(())
|
|
171
161
|
}
|
|
172
162
|
|
|
173
|
-
fn
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
163
|
+
fn to_event(sub: &Subscription, nbytes: u64) -> types::Event {
|
|
164
|
+
types::Event {
|
|
165
|
+
userdata: sub.userdata,
|
|
166
|
+
error: sub.error,
|
|
167
|
+
type_: match sub.kind {
|
|
168
|
+
Kind::Clock(_) => Eventtype::Clock,
|
|
169
|
+
Kind::FdRead => Eventtype::FdRead,
|
|
170
|
+
Kind::FdWrite => Eventtype::FdWrite,
|
|
171
|
+
},
|
|
172
|
+
fd_readwrite: types::EventFdReadwrite {
|
|
173
|
+
nbytes,
|
|
174
|
+
flags: Eventrwflags::empty(),
|
|
175
|
+
},
|
|
176
|
+
}
|
|
184
177
|
}
|
|
185
178
|
|
|
186
179
|
#[cfg(test)]
|
|
@@ -192,7 +185,7 @@ mod tests {
|
|
|
192
185
|
|
|
193
186
|
use wasmtime::{Module, Store};
|
|
194
187
|
|
|
195
|
-
use crate::cache::
|
|
188
|
+
use crate::cache::load_engine;
|
|
196
189
|
use crate::config::Config;
|
|
197
190
|
use crate::imports;
|
|
198
191
|
use crate::invocation::Invocation;
|
|
@@ -221,7 +214,7 @@ mod tests {
|
|
|
221
214
|
profile: Profile,
|
|
222
215
|
deadline: Option<Duration>,
|
|
223
216
|
) -> (wasmtime::Result<i32>, Duration) {
|
|
224
|
-
let engine =
|
|
217
|
+
let engine = load_engine().expect("shared engine must be constructible");
|
|
225
218
|
let config = Config {
|
|
226
219
|
timeout: None,
|
|
227
220
|
setup_timeout: None,
|
|
@@ -230,8 +223,9 @@ mod tests {
|
|
|
230
223
|
stderr_limit: None,
|
|
231
224
|
open_file_limit: None,
|
|
232
225
|
profile,
|
|
226
|
+
security: Vec::new(),
|
|
233
227
|
};
|
|
234
|
-
let wasi = Wasi::for_invocation(&config,
|
|
228
|
+
let wasi = Wasi::for_invocation(&config, Vec::new(), &[]).expect("WASI context must build");
|
|
235
229
|
let mut store = Store::new(engine, Invocation::new(None, wasi));
|
|
236
230
|
store.set_epoch_deadline(NO_TIMEOUT_EPOCH_DELTA);
|
|
237
231
|
let linker = imports::linker().expect("host imports must link");
|
|
@@ -277,7 +271,7 @@ mod tests {
|
|
|
277
271
|
(i32.store (i32.const 16) (local.get $id))
|
|
278
272
|
(call $poll (i32.const 0) (i32.const 64) (i32.const 1) (i32.const 96))))
|
|
279
273
|
"#;
|
|
280
|
-
let engine =
|
|
274
|
+
let engine = load_engine().expect("shared engine must be constructible");
|
|
281
275
|
let config = Config {
|
|
282
276
|
timeout: None,
|
|
283
277
|
setup_timeout: None,
|
|
@@ -286,8 +280,9 @@ mod tests {
|
|
|
286
280
|
stderr_limit: None,
|
|
287
281
|
open_file_limit: None,
|
|
288
282
|
profile: Profile::Hermetic,
|
|
283
|
+
security: Vec::new(),
|
|
289
284
|
};
|
|
290
|
-
let wasi = Wasi::for_invocation(&config,
|
|
285
|
+
let wasi = Wasi::for_invocation(&config, Vec::new(), &[]).expect("WASI context must build");
|
|
291
286
|
let mut store = Store::new(engine, Invocation::new(None, wasi));
|
|
292
287
|
store.set_epoch_deadline(NO_TIMEOUT_EPOCH_DELTA);
|
|
293
288
|
let linker = imports::linker().expect("host imports must link");
|
|
@@ -314,6 +309,59 @@ mod tests {
|
|
|
314
309
|
|
|
315
310
|
const ERRNO_INVAL: i32 = 28;
|
|
316
311
|
|
|
312
|
+
/// What a lone wait on standard output answers for event `tag`, 1 to read
|
|
313
|
+
/// and 2 to write.
|
|
314
|
+
fn wait_on_stdout(tag: i32) -> i32 {
|
|
315
|
+
const WAIT_ON_FD_WAT: &str = r#"
|
|
316
|
+
(module
|
|
317
|
+
(import "wasi_snapshot_preview1" "poll_oneoff"
|
|
318
|
+
(func $poll (param i32 i32 i32 i32) (result i32)))
|
|
319
|
+
(memory (export "memory") 1)
|
|
320
|
+
(func (export "wait_on") (param $tag i32) (result i32)
|
|
321
|
+
(i32.store8 (i32.const 8) (local.get $tag))
|
|
322
|
+
(i32.store (i32.const 16) (i32.const 1))
|
|
323
|
+
(call $poll (i32.const 0) (i32.const 64) (i32.const 1) (i32.const 96))))
|
|
324
|
+
"#;
|
|
325
|
+
let engine = load_engine().expect("shared engine must be constructible");
|
|
326
|
+
let config = Config {
|
|
327
|
+
timeout: None,
|
|
328
|
+
setup_timeout: None,
|
|
329
|
+
memory_limit: None,
|
|
330
|
+
stdout_limit: None,
|
|
331
|
+
stderr_limit: None,
|
|
332
|
+
open_file_limit: None,
|
|
333
|
+
profile: Profile::Hermetic,
|
|
334
|
+
security: Vec::new(),
|
|
335
|
+
};
|
|
336
|
+
let wasi = Wasi::for_invocation(&config, Vec::new(), &[]).expect("WASI context must build");
|
|
337
|
+
let mut store = Store::new(engine, Invocation::new(None, wasi));
|
|
338
|
+
store.set_epoch_deadline(NO_TIMEOUT_EPOCH_DELTA);
|
|
339
|
+
let linker = imports::linker().expect("host imports must link");
|
|
340
|
+
let module = Module::new(engine, WAIT_ON_FD_WAT).expect("wait module must compile");
|
|
341
|
+
linker
|
|
342
|
+
.instantiate(&mut store, &module)
|
|
343
|
+
.expect("wait module must instantiate")
|
|
344
|
+
.get_typed_func::<i32, i32>(&mut store, "wait_on")
|
|
345
|
+
.expect("wait export must resolve")
|
|
346
|
+
.call(&mut store, tag)
|
|
347
|
+
.expect("the wait must not trap")
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const ERRNO_BADF: i32 = 8;
|
|
351
|
+
|
|
352
|
+
// Nothing ever makes standard output ready to read, so without the refusal
|
|
353
|
+
// the wait would only end with the invocation.
|
|
354
|
+
// @behavior RT-285
|
|
355
|
+
#[test]
|
|
356
|
+
fn a_wait_on_what_a_descriptor_never_does_answers_ebadf() {
|
|
357
|
+
assert_eq!(
|
|
358
|
+
[1, 2].map(wait_on_stdout),
|
|
359
|
+
[ERRNO_BADF, 0],
|
|
360
|
+
"poll_oneoff on standard output must answer EBADF waiting to read and be ready to \
|
|
361
|
+
write"
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
317
365
|
// @behavior RT-074
|
|
318
366
|
#[test]
|
|
319
367
|
fn a_permissive_wait_ends_at_the_deadline_as_a_timeout() {
|
|
@@ -1,30 +1,13 @@
|
|
|
1
1
|
//! The 4 `sock_*` calls. The table holds no socket, so every open
|
|
2
|
-
//! descriptor answers `NOTSOCK`, as wasmtime-wasi's preview1 does
|
|
2
|
+
//! descriptor answers `NOTSOCK`, as wasmtime-wasi's preview1 does, and one
|
|
3
|
+
//! standing only for its path `BADF`, as Linux's socket calls take theirs
|
|
4
|
+
//! through `fdget`.
|
|
3
5
|
|
|
4
|
-
use super::args;
|
|
5
6
|
use super::errno::{Answer, Errno};
|
|
7
|
+
use super::fd::file;
|
|
6
8
|
use crate::descriptors::Descriptors;
|
|
7
9
|
|
|
8
|
-
fn socket(table: &Descriptors, fd: u32) -> Answer {
|
|
9
|
-
table
|
|
10
|
+
pub(crate) fn socket<T>(table: &Descriptors, fd: u32) -> Answer<T> {
|
|
11
|
+
file(table, fd)?;
|
|
10
12
|
Err(Errno::Notsock.into())
|
|
11
13
|
}
|
|
12
|
-
|
|
13
|
-
pub(crate) fn accept(table: &Descriptors, fd: u32, flags: u32) -> Answer {
|
|
14
|
-
args::flags(flags, args::FDFLAGS)?;
|
|
15
|
-
socket(table, fd)
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
pub(crate) fn recv(table: &Descriptors, fd: u32, ri_flags: u32) -> Answer {
|
|
19
|
-
args::flags(ri_flags, args::RIFLAGS)?;
|
|
20
|
-
socket(table, fd)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
pub(crate) fn send(table: &Descriptors, fd: u32) -> Answer {
|
|
24
|
-
socket(table, fd)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
pub(crate) fn shutdown(table: &Descriptors, fd: u32, how: u32) -> Answer {
|
|
28
|
-
args::flags(how, args::SDFLAGS)?;
|
|
29
|
-
socket(table, fd)
|
|
30
|
-
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
nofollow_errors # no built-in Filesystem makes a link
|
|
2
|
+
path_exists # no built-in Filesystem makes a link
|
|
3
|
+
path_link # no built-in Filesystem makes a link
|
|
4
|
+
readlink # no built-in Filesystem makes a link
|
|
5
|
+
symlink_create # no built-in Filesystem makes a link
|
|
6
|
+
symlink_filestat # no built-in Filesystem makes a link
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
//! Runs wasi-testsuite's prebuilt preview1 tests against kobako's linker, over a
|
|
2
|
+
//! Bind of each test's own copy of the directory it is handed. The suite is
|
|
3
|
+
//! the second of what decides a preview1 answer, after the witx, so only the
|
|
4
|
+
//! calls kobako deliberately refuses may fail, and `testsuite-expected.txt`
|
|
5
|
+
//! names them.
|
|
6
|
+
//!
|
|
7
|
+
//! The suite is fetched by `rake crates:testsuite`, which pins its version, so
|
|
8
|
+
//! no gate reaches the network.
|
|
9
|
+
|
|
10
|
+
use std::collections::BTreeMap;
|
|
11
|
+
use std::fs;
|
|
12
|
+
use std::path::{Path, PathBuf};
|
|
13
|
+
use std::sync::Arc;
|
|
14
|
+
use std::time::Duration;
|
|
15
|
+
|
|
16
|
+
use kobako_runtime::filesystem::Bind;
|
|
17
|
+
use kobako_runtime::filesystem::Filesystem;
|
|
18
|
+
use kobako_runtime::grant::Grant;
|
|
19
|
+
use kobako_runtime::mount::{Access, Mount};
|
|
20
|
+
use kobako_runtime::profile::Profile;
|
|
21
|
+
use wasmtime::{Module, Store};
|
|
22
|
+
use wasmtime_wasi::I32Exit;
|
|
23
|
+
|
|
24
|
+
use crate::cache::load_engine;
|
|
25
|
+
use crate::config::Config;
|
|
26
|
+
use crate::imports;
|
|
27
|
+
use crate::invocation::Invocation;
|
|
28
|
+
use crate::trap::arm_deadline;
|
|
29
|
+
use crate::wasi::Wasi;
|
|
30
|
+
|
|
31
|
+
const SUITE: &str = concat!(
|
|
32
|
+
env!("CARGO_MANIFEST_DIR"),
|
|
33
|
+
"/../../tmp/wasi-testsuite/tests/rust/testsuite/wasm32-wasip1"
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
/// What may fail, one test per line, with why after a `#`.
|
|
37
|
+
const EXPECTED: &str = include_str!("testsuite-expected.txt");
|
|
38
|
+
|
|
39
|
+
fn copy_tree(from: &Path, to: &Path) {
|
|
40
|
+
fs::create_dir_all(to).expect("the copy must make its directory");
|
|
41
|
+
for entry in fs::read_dir(from).expect("the suite's directory must list") {
|
|
42
|
+
let entry = entry.expect("the suite's directory must list");
|
|
43
|
+
let target = to.join(entry.file_name());
|
|
44
|
+
match entry.file_type().expect("an entry has a type").is_dir() {
|
|
45
|
+
true => copy_tree(&entry.path(), &target),
|
|
46
|
+
false => {
|
|
47
|
+
fs::copy(entry.path(), target).expect("the suite's file must copy");
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/// What the test at `wasm` ended with: its exit code, or why it never reached
|
|
54
|
+
/// one.
|
|
55
|
+
fn run(wasm: &Path, root: &Path) -> Result<i32, String> {
|
|
56
|
+
let engine = load_engine().map_err(|err| err.to_string())?;
|
|
57
|
+
let bind = Bind::new(root).map_err(|err| err.to_string())?;
|
|
58
|
+
let filesystem = Filesystem::new(Arc::new(bind)).map_err(|err| format!("{err:?}"))?;
|
|
59
|
+
let mount = Mount::new("/", filesystem, Access::ReadWrite).map_err(|err| err.to_string())?;
|
|
60
|
+
let config = Config {
|
|
61
|
+
timeout: None,
|
|
62
|
+
setup_timeout: None,
|
|
63
|
+
memory_limit: None,
|
|
64
|
+
stdout_limit: None,
|
|
65
|
+
stderr_limit: None,
|
|
66
|
+
open_file_limit: None,
|
|
67
|
+
profile: Profile::Permissive,
|
|
68
|
+
security: Vec::new(),
|
|
69
|
+
};
|
|
70
|
+
let wasi = Wasi::for_invocation(&config, Vec::new(), &[Grant::Mount(mount)])
|
|
71
|
+
.map_err(|err| err.to_string())?;
|
|
72
|
+
let mut store = Store::new(engine, Invocation::new(None, wasi));
|
|
73
|
+
arm_deadline(&mut store, Some(Duration::from_secs(30)));
|
|
74
|
+
let module = Module::from_file(engine, wasm).map_err(|err| err.to_string())?;
|
|
75
|
+
let instance = imports::linker()
|
|
76
|
+
.map_err(|err| err.to_string())?
|
|
77
|
+
.instantiate(&mut store, &module)
|
|
78
|
+
.map_err(|err| err.to_string())?;
|
|
79
|
+
let start = instance
|
|
80
|
+
.get_typed_func::<(), ()>(&mut store, "_start")
|
|
81
|
+
.map_err(|err| err.to_string())?;
|
|
82
|
+
match start.call(&mut store, ()) {
|
|
83
|
+
Ok(()) => Ok(0),
|
|
84
|
+
Err(err) => match err.downcast_ref::<I32Exit>() {
|
|
85
|
+
Some(exit) => Ok(exit.0),
|
|
86
|
+
// A test aborts on its first failed assertion, which it wrote to
|
|
87
|
+
// stderr, so that says what the call answered.
|
|
88
|
+
None => Err(String::from_utf8_lossy(&store.data().wasi().stderr_bytes()).to_string()),
|
|
89
|
+
},
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
fn expected_failures() -> BTreeMap<String, String> {
|
|
94
|
+
EXPECTED
|
|
95
|
+
.lines()
|
|
96
|
+
.filter(|line| !line.trim().is_empty())
|
|
97
|
+
.map(|line| {
|
|
98
|
+
let (name, why) = line.split_once('#').unwrap_or((line, ""));
|
|
99
|
+
(name.trim().to_string(), why.trim().to_string())
|
|
100
|
+
})
|
|
101
|
+
.collect()
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// @behavior RT-192
|
|
105
|
+
#[test]
|
|
106
|
+
#[ignore = "run through `rake crates:testsuite`, which fetches the suite"]
|
|
107
|
+
fn only_what_kobako_refuses_fails_wasi_testsuite() {
|
|
108
|
+
let suite = PathBuf::from(SUITE);
|
|
109
|
+
assert!(
|
|
110
|
+
suite.exists(),
|
|
111
|
+
"wasi-testsuite is not at {SUITE}; run `rake crates:testsuite`"
|
|
112
|
+
);
|
|
113
|
+
let scratch = std::env::temp_dir().join(format!("kobako-testsuite-{}", std::process::id()));
|
|
114
|
+
let mut wasms: Vec<PathBuf> = fs::read_dir(&suite)
|
|
115
|
+
.expect("the suite must list")
|
|
116
|
+
.map(|entry| entry.expect("the suite must list").path())
|
|
117
|
+
.filter(|path| path.extension().is_some_and(|ext| ext == "wasm"))
|
|
118
|
+
.collect();
|
|
119
|
+
wasms.sort();
|
|
120
|
+
|
|
121
|
+
let mut failed = BTreeMap::new();
|
|
122
|
+
for wasm in &wasms {
|
|
123
|
+
let name = wasm
|
|
124
|
+
.file_stem()
|
|
125
|
+
.expect("a test has a name")
|
|
126
|
+
.to_string_lossy()
|
|
127
|
+
.to_string();
|
|
128
|
+
let root = scratch.join(&name);
|
|
129
|
+
copy_tree(&suite.join("fs-tests.dir"), &root);
|
|
130
|
+
match run(wasm, &root) {
|
|
131
|
+
Ok(0) => {}
|
|
132
|
+
Ok(code) => {
|
|
133
|
+
failed.insert(name, format!("exit {code}"));
|
|
134
|
+
}
|
|
135
|
+
Err(why) => {
|
|
136
|
+
failed.insert(name, why);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
let _ = fs::remove_dir_all(&scratch);
|
|
141
|
+
|
|
142
|
+
let expected = expected_failures();
|
|
143
|
+
let unexpected: Vec<_> = failed
|
|
144
|
+
.iter()
|
|
145
|
+
.filter(|(name, _)| !expected.contains_key(*name))
|
|
146
|
+
.collect();
|
|
147
|
+
let passing: Vec<_> = expected
|
|
148
|
+
.keys()
|
|
149
|
+
.filter(|name| !failed.contains_key(*name))
|
|
150
|
+
.collect();
|
|
151
|
+
assert!(
|
|
152
|
+
unexpected.is_empty() && passing.is_empty(),
|
|
153
|
+
"wasi-testsuite through kobako's preview1 imports must fail only what \
|
|
154
|
+
testsuite-expected.txt names; unexpected failures: {unexpected:#?}; now passing: \
|
|
155
|
+
{passing:?}"
|
|
156
|
+
);
|
|
157
|
+
}
|