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,302 @@
|
|
|
1
|
+
//! The 10 `path_*` calls. Each hands the call to the runtime's VFS, which
|
|
2
|
+
//! resolves the path, asks the Mount, and asks the Filesystem. What a Linux
|
|
3
|
+
//! syscall entry checks before the VFS stays here, such as the order of an
|
|
4
|
+
//! open's errors.
|
|
5
|
+
|
|
6
|
+
use kobako_runtime::filesystem::{FilesystemError, Iattr};
|
|
7
|
+
use kobako_runtime::vfs::{self, OpenHow, Start};
|
|
8
|
+
use wiggle::GuestPtr;
|
|
9
|
+
|
|
10
|
+
use super::errno::{Answer, Errno};
|
|
11
|
+
use super::fd::to_filestat;
|
|
12
|
+
use super::memory::GuestMemory;
|
|
13
|
+
use super::types;
|
|
14
|
+
use crate::descriptors::{Descriptor, Descriptors};
|
|
15
|
+
|
|
16
|
+
/// The arguments `path_open` takes besides where it starts and the path, as
|
|
17
|
+
/// Linux's `openat2` takes a `struct open_how`; they become the `OpenHow` the
|
|
18
|
+
/// VFS opens with.
|
|
19
|
+
pub(crate) struct PathOpen {
|
|
20
|
+
pub(crate) dirflags: types::Lookupflags,
|
|
21
|
+
pub(crate) oflags: types::Oflags,
|
|
22
|
+
pub(crate) rights_base: types::Rights,
|
|
23
|
+
pub(crate) fdflags: types::Fdflags,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/// How many times a call is made before a guest is answered EAGAIN, as cap-std
|
|
27
|
+
/// makes `openat2` four times: any move in the process answers a `..` walking
|
|
28
|
+
/// past it so, and nothing bounds how often one lands.
|
|
29
|
+
const MAX_ATTEMPTS: usize = 4;
|
|
30
|
+
|
|
31
|
+
/// What `call` answers once no move landed during its walk, made again at most
|
|
32
|
+
/// `MAX_ATTEMPTS` times in all, as cap-std retries `openat2` on EAGAIN.
|
|
33
|
+
pub(super) fn retry_eagain<T>(
|
|
34
|
+
mut call: impl FnMut() -> Result<T, FilesystemError>,
|
|
35
|
+
) -> Result<T, FilesystemError> {
|
|
36
|
+
let mut answer = call();
|
|
37
|
+
for _ in 1..MAX_ATTEMPTS {
|
|
38
|
+
if !matches!(answer, Err(FilesystemError::WouldBlock)) {
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
answer = call();
|
|
42
|
+
}
|
|
43
|
+
answer
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// Where `fd` stands, for a path to be walked from, once the guard answered for a
|
|
47
|
+
/// directory there, found when the VFS has taken the name, as Linux's
|
|
48
|
+
/// `path_init` takes the descriptor. What is no directory the walk itself
|
|
49
|
+
/// refuses, and a move the guard meets is one a walk meets, so `retry_eagain`
|
|
50
|
+
/// makes the call again.
|
|
51
|
+
fn start_of(table: &Descriptors, fd: u32) -> impl vfs::Start + '_ {
|
|
52
|
+
move || {
|
|
53
|
+
let file = &table.get(fd).ok_or(FilesystemError::BadDescriptor)?.file;
|
|
54
|
+
if file.is_directory() {
|
|
55
|
+
table.walk_guard().check(file.path())?;
|
|
56
|
+
}
|
|
57
|
+
Ok(file.path().clone())
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
pub(crate) fn create_directory(
|
|
62
|
+
table: &Descriptors,
|
|
63
|
+
mem: &GuestMemory<'_, '_>,
|
|
64
|
+
fd: u32,
|
|
65
|
+
path: GuestPtr<str>,
|
|
66
|
+
) -> Answer {
|
|
67
|
+
let path = mem.path(path)?;
|
|
68
|
+
Ok(retry_eagain(|| {
|
|
69
|
+
vfs::filename_mkdirat(table.cred(), &start_of(table, fd), &path)
|
|
70
|
+
})?)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
pub(crate) fn filestat_get(
|
|
74
|
+
table: &Descriptors,
|
|
75
|
+
mem: &GuestMemory<'_, '_>,
|
|
76
|
+
fd: u32,
|
|
77
|
+
flags: types::Lookupflags,
|
|
78
|
+
path: GuestPtr<str>,
|
|
79
|
+
) -> Answer<types::Filestat> {
|
|
80
|
+
let path = mem.path(path)?;
|
|
81
|
+
let follow = flags.contains(types::Lookupflags::SYMLINK_FOLLOW);
|
|
82
|
+
let stat =
|
|
83
|
+
retry_eagain(|| vfs::vfs_fstatat(table.cred(), &start_of(table, fd), &path, follow))?;
|
|
84
|
+
Ok(to_filestat(table, stat))
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
pub(crate) fn filestat_set_times(
|
|
88
|
+
table: &Descriptors,
|
|
89
|
+
mem: &GuestMemory<'_, '_>,
|
|
90
|
+
fd: u32,
|
|
91
|
+
flags: types::Lookupflags,
|
|
92
|
+
path: GuestPtr<str>,
|
|
93
|
+
times: &Iattr,
|
|
94
|
+
) -> Answer {
|
|
95
|
+
let path = mem.path(path)?;
|
|
96
|
+
let follow = flags.contains(types::Lookupflags::SYMLINK_FOLLOW);
|
|
97
|
+
Ok(retry_eagain(|| {
|
|
98
|
+
vfs::do_utimes_path(table.cred(), &start_of(table, fd), &path, follow, times)
|
|
99
|
+
})?)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
pub(crate) fn link(
|
|
103
|
+
table: &Descriptors,
|
|
104
|
+
mem: &GuestMemory<'_, '_>,
|
|
105
|
+
old: (u32, GuestPtr<str>),
|
|
106
|
+
new: (u32, GuestPtr<str>),
|
|
107
|
+
flags: types::Lookupflags,
|
|
108
|
+
) -> Answer {
|
|
109
|
+
let (old_path, new_path) = (mem.path(old.1)?, mem.path(new.1)?);
|
|
110
|
+
let follow = flags.contains(types::Lookupflags::SYMLINK_FOLLOW);
|
|
111
|
+
Ok(retry_eagain(|| {
|
|
112
|
+
let (from, to) = (start_of(table, old.0), start_of(table, new.0));
|
|
113
|
+
vfs::filename_linkat(table.cred(), &from, &old_path, &to, &new_path, follow)
|
|
114
|
+
})?)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
pub(crate) fn open(
|
|
118
|
+
table: &mut Descriptors,
|
|
119
|
+
mem: &GuestMemory<'_, '_>,
|
|
120
|
+
fd: u32,
|
|
121
|
+
path: GuestPtr<str>,
|
|
122
|
+
call: &PathOpen,
|
|
123
|
+
) -> Answer<types::Fd> {
|
|
124
|
+
let oflags = call.oflags;
|
|
125
|
+
let mut how = OpenHow::default();
|
|
126
|
+
// wasi-libc asks `fd_read` and `fd_readdir` for `O_RDONLY` and neither for
|
|
127
|
+
// `O_SEARCH`, which glibc spells `O_PATH`; wasi-testsuite lists a
|
|
128
|
+
// directory opened with `fd_readdir` alone, and makes a file asking no
|
|
129
|
+
// right at all, which only an `O_RDONLY` open does.
|
|
130
|
+
how.create = oflags.contains(types::Oflags::CREAT);
|
|
131
|
+
how.exclusive = oflags.contains(types::Oflags::EXCL);
|
|
132
|
+
how.truncate = oflags.contains(types::Oflags::TRUNC);
|
|
133
|
+
how.write = call.rights_base.contains(types::Rights::FD_WRITE);
|
|
134
|
+
how.read = call
|
|
135
|
+
.rights_base
|
|
136
|
+
.intersects(types::Rights::FD_READ | types::Rights::FD_READDIR)
|
|
137
|
+
|| (!how.write && (how.create || how.truncate));
|
|
138
|
+
how.directory = oflags.contains(types::Oflags::DIRECTORY);
|
|
139
|
+
how.follow = call.dirflags.contains(types::Lookupflags::SYMLINK_FOLLOW);
|
|
140
|
+
how.append = call.fdflags.contains(types::Fdflags::APPEND);
|
|
141
|
+
how.nonblock = call.fdflags.contains(types::Fdflags::NONBLOCK);
|
|
142
|
+
how.dsync = call.fdflags.contains(types::Fdflags::DSYNC);
|
|
143
|
+
how.sync = call
|
|
144
|
+
.fdflags
|
|
145
|
+
.intersects(types::Fdflags::RSYNC | types::Fdflags::SYNC);
|
|
146
|
+
// Linux's `do_sys_openat2` builds the flags, then reserves a descriptor
|
|
147
|
+
// before `do_file_open` answers for the name, so a full table answers
|
|
148
|
+
// whatever the path names.
|
|
149
|
+
let op = vfs::build_open_flags(&how)?;
|
|
150
|
+
let new = table.lowest_unused().ok_or(Errno::Mfile)?;
|
|
151
|
+
let path = mem.path(path)?;
|
|
152
|
+
let file = retry_eagain(|| vfs::do_file_open(table.cred(), &start_of(table, fd), &path, &op))?;
|
|
153
|
+
table.place(new, Descriptor::for_file(file));
|
|
154
|
+
Ok(new.into())
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
pub(crate) fn readlink(
|
|
158
|
+
table: &Descriptors,
|
|
159
|
+
mem: &mut GuestMemory<'_, '_>,
|
|
160
|
+
fd: u32,
|
|
161
|
+
path: GuestPtr<str>,
|
|
162
|
+
buf: GuestPtr<u8>,
|
|
163
|
+
buf_len: types::Size,
|
|
164
|
+
) -> Answer<types::Size> {
|
|
165
|
+
if buf_len == 0 {
|
|
166
|
+
return Err(Errno::Inval.into());
|
|
167
|
+
}
|
|
168
|
+
let path = mem.path(path)?;
|
|
169
|
+
let target = retry_eagain(|| vfs::do_readlinkat(table.cred(), &start_of(table, fd), &path))?;
|
|
170
|
+
write_link(mem, &target, buf, buf_len)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/// As much of where a link points as the buffer holds, as `readlink` cuts it
|
|
174
|
+
/// off there without saying so.
|
|
175
|
+
fn write_link(
|
|
176
|
+
mem: &mut GuestMemory<'_, '_>,
|
|
177
|
+
target: &str,
|
|
178
|
+
buf: GuestPtr<u8>,
|
|
179
|
+
buf_len: types::Size,
|
|
180
|
+
) -> Answer<types::Size> {
|
|
181
|
+
let take = target.len().min(buf_len as usize);
|
|
182
|
+
mem.write_bytes(buf, &target.as_bytes()[..take])?;
|
|
183
|
+
Ok(take as u32)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
pub(crate) fn remove_directory(
|
|
187
|
+
table: &Descriptors,
|
|
188
|
+
mem: &GuestMemory<'_, '_>,
|
|
189
|
+
fd: u32,
|
|
190
|
+
path: GuestPtr<str>,
|
|
191
|
+
) -> Answer {
|
|
192
|
+
let path = mem.path(path)?;
|
|
193
|
+
Ok(retry_eagain(|| {
|
|
194
|
+
vfs::filename_rmdir(table.cred(), &start_of(table, fd), &path)
|
|
195
|
+
})?)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
pub(crate) fn rename(
|
|
199
|
+
table: &Descriptors,
|
|
200
|
+
mem: &GuestMemory<'_, '_>,
|
|
201
|
+
old: (u32, GuestPtr<str>),
|
|
202
|
+
new: (u32, GuestPtr<str>),
|
|
203
|
+
) -> Answer {
|
|
204
|
+
let (from_path, to_path) = (mem.path(old.1)?, mem.path(new.1)?);
|
|
205
|
+
Ok(retry_eagain(|| {
|
|
206
|
+
let (from, to) = (start_of(table, old.0), start_of(table, new.0));
|
|
207
|
+
vfs::filename_renameat2(table.cred(), &from, &from_path, &to, &to_path)
|
|
208
|
+
})?)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
pub(crate) fn symlink(
|
|
212
|
+
table: &Descriptors,
|
|
213
|
+
mem: &GuestMemory<'_, '_>,
|
|
214
|
+
old_path: GuestPtr<str>,
|
|
215
|
+
fd: u32,
|
|
216
|
+
new_path: GuestPtr<str>,
|
|
217
|
+
) -> Answer {
|
|
218
|
+
let (target, path) = (mem.path(old_path)?, mem.path(new_path)?);
|
|
219
|
+
// wasi-testsuite's symlink_create has a guest make no link to an absolute
|
|
220
|
+
// path, where Linux would make one; a guest walks none of them anyway. It
|
|
221
|
+
// is refused once the start is found, where the link would be made.
|
|
222
|
+
let start = || -> Result<vfs::Path, FilesystemError> {
|
|
223
|
+
let start = start_of(table, fd).path_init()?;
|
|
224
|
+
match target.starts_with('/') {
|
|
225
|
+
true => Err(FilesystemError::NotPermitted),
|
|
226
|
+
false => Ok(start),
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
Ok(retry_eagain(|| {
|
|
230
|
+
vfs::filename_symlinkat(table.cred(), &target, &start, &path)
|
|
231
|
+
})?)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
pub(crate) fn unlink_file(
|
|
235
|
+
table: &Descriptors,
|
|
236
|
+
mem: &GuestMemory<'_, '_>,
|
|
237
|
+
fd: u32,
|
|
238
|
+
path: GuestPtr<str>,
|
|
239
|
+
) -> Answer {
|
|
240
|
+
let path = mem.path(path)?;
|
|
241
|
+
Ok(retry_eagain(|| {
|
|
242
|
+
vfs::filename_unlinkat(table.cred(), &start_of(table, fd), &path)
|
|
243
|
+
})?)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
#[cfg(test)]
|
|
247
|
+
mod tests {
|
|
248
|
+
use super::*;
|
|
249
|
+
|
|
250
|
+
// Every Invocation holds to the hook refusing a readlink, so no guest call
|
|
251
|
+
// reaches this; only the encoding itself can witness it.
|
|
252
|
+
#[test]
|
|
253
|
+
fn a_link_is_cut_off_where_the_buffer_ends() {
|
|
254
|
+
let mut bytes = [0u8; 8];
|
|
255
|
+
let answered = {
|
|
256
|
+
let mut memory = wiggle::GuestMemory::Unshared(&mut bytes);
|
|
257
|
+
let mut mem = GuestMemory::new(&mut memory, 64);
|
|
258
|
+
write_link(&mut mem, "target", GuestPtr::new(2), 3)
|
|
259
|
+
.unwrap_or_else(|_| panic!("the link must write"))
|
|
260
|
+
};
|
|
261
|
+
assert_eq!(
|
|
262
|
+
(answered, &bytes),
|
|
263
|
+
(3, b"\0\0tar\0\0\0"),
|
|
264
|
+
"where a link points through path_readlink must fill the buffer and answer what fit"
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/// What `retry_eagain` answers when the first `moves` calls each had a move
|
|
269
|
+
/// land in them, and how many calls it made.
|
|
270
|
+
fn retry_after_moves(moves: usize) -> (Result<(), FilesystemError>, usize) {
|
|
271
|
+
let mut made = 0;
|
|
272
|
+
let answer = retry_eagain(|| {
|
|
273
|
+
made += 1;
|
|
274
|
+
match made <= moves {
|
|
275
|
+
true => Err(FilesystemError::WouldBlock),
|
|
276
|
+
false => Ok(()),
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
(answer, made)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// A move lands during a walk only between two of its steps, which no guest
|
|
283
|
+
// call can time, so the retry itself witnesses it.
|
|
284
|
+
// @behavior RT-256
|
|
285
|
+
#[test]
|
|
286
|
+
fn a_path_call_a_move_landed_in_is_made_again_four_times_at_most() {
|
|
287
|
+
assert_eq!(
|
|
288
|
+
[
|
|
289
|
+
retry_after_moves(0),
|
|
290
|
+
retry_after_moves(3),
|
|
291
|
+
retry_after_moves(4)
|
|
292
|
+
],
|
|
293
|
+
[
|
|
294
|
+
(Ok(()), 1),
|
|
295
|
+
(Ok(()), 4),
|
|
296
|
+
(Err(FilesystemError::WouldBlock), 4),
|
|
297
|
+
],
|
|
298
|
+
"a path_* call answered EAGAIN must be made again until it answers otherwise, four \
|
|
299
|
+
times in all at most, as cap-std makes openat2"
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
//! `poll_oneoff`, the guest's wait: for a clock, or for a descriptor to be
|
|
2
|
+
//! ready.
|
|
3
|
+
//!
|
|
4
|
+
//! Every descriptor kobako holds is ready at once, so only a clock ever
|
|
5
|
+
//! waits. A wait passes on the guest's own clocks: the hermetic rung's
|
|
6
|
+
//! never advance, so its waits end at once, and a permissive wait takes
|
|
7
|
+
//! host time but never past the invocation's deadline — the host is the
|
|
8
|
+
//! one waiting, where no epoch check can interrupt it.
|
|
9
|
+
|
|
10
|
+
use std::thread;
|
|
11
|
+
use std::time::{Duration, Instant};
|
|
12
|
+
|
|
13
|
+
use kobako_runtime::vfs::PollMask;
|
|
14
|
+
use wiggle::{GuestPtr, GuestType};
|
|
15
|
+
|
|
16
|
+
use super::errno::{Answer, Errno};
|
|
17
|
+
use super::fd::file;
|
|
18
|
+
use super::memory::GuestMemory;
|
|
19
|
+
use super::types::{self, Clockid, Eventrwflags, Eventtype, Subclockflags, SubscriptionU};
|
|
20
|
+
use crate::invocation::Invocation;
|
|
21
|
+
use crate::trap::TimeoutTrap;
|
|
22
|
+
|
|
23
|
+
/// One subscription as the guest wrote it, and the error its event reports.
|
|
24
|
+
struct Subscription {
|
|
25
|
+
userdata: u64,
|
|
26
|
+
kind: Kind,
|
|
27
|
+
error: Errno,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
enum Kind {
|
|
31
|
+
/// A clock, as the nanoseconds left before it fires.
|
|
32
|
+
Clock(u64),
|
|
33
|
+
FdRead,
|
|
34
|
+
FdWrite,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
pub(crate) fn poll_oneoff(
|
|
38
|
+
invocation: &Invocation,
|
|
39
|
+
mem: &mut GuestMemory<'_, '_>,
|
|
40
|
+
subs: GuestPtr<types::Subscription>,
|
|
41
|
+
events: GuestPtr<types::Event>,
|
|
42
|
+
count: u32,
|
|
43
|
+
) -> Answer<types::Size> {
|
|
44
|
+
if count == 0 {
|
|
45
|
+
return Err(Errno::Inval.into());
|
|
46
|
+
}
|
|
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?))
|
|
53
|
+
.collect::<Answer<Vec<_>>>()?;
|
|
54
|
+
|
|
55
|
+
let clocks = subscriptions.iter().filter_map(|sub| match sub.kind {
|
|
56
|
+
Kind::Clock(left) => Some(left),
|
|
57
|
+
Kind::FdRead | Kind::FdWrite => None,
|
|
58
|
+
});
|
|
59
|
+
let waited = if clocks.clone().count() == subscriptions.len() {
|
|
60
|
+
let soonest = clocks.min().unwrap_or(0);
|
|
61
|
+
wait(invocation, soonest)?;
|
|
62
|
+
soonest
|
|
63
|
+
} else {
|
|
64
|
+
0
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
let mut written = 0;
|
|
68
|
+
for sub in &subscriptions {
|
|
69
|
+
let (ready, nbytes) = match sub.kind {
|
|
70
|
+
Kind::Clock(left) => (left <= waited, 0),
|
|
71
|
+
Kind::FdRead | Kind::FdWrite => (true, u64::from(sub.error == Errno::Success)),
|
|
72
|
+
};
|
|
73
|
+
if ready {
|
|
74
|
+
mem.write(events.add(written)?, to_event(sub, nbytes))?;
|
|
75
|
+
written += 1;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
Ok(written)
|
|
79
|
+
}
|
|
80
|
+
|
|
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
|
+
),
|
|
97
|
+
};
|
|
98
|
+
Ok(Subscription {
|
|
99
|
+
userdata: sub.userdata,
|
|
100
|
+
kind,
|
|
101
|
+
error,
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
|
|
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
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/// A clock, read against the guest's own clocks. Only the realtime and
|
|
128
|
+
/// monotonic clocks can be waited on, alone or among other
|
|
129
|
+
/// subscriptions, as in wasmtime-wasi.
|
|
130
|
+
fn read_clock(invocation: &Invocation, clock: &types::SubscriptionClock) -> Answer<Kind> {
|
|
131
|
+
let absolute = clock
|
|
132
|
+
.flags
|
|
133
|
+
.contains(Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME);
|
|
134
|
+
let clocks = invocation.wasi().clocks();
|
|
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(),
|
|
139
|
+
_ => return Err(Errno::Inval.into()),
|
|
140
|
+
};
|
|
141
|
+
Ok(Kind::Clock(clock.timeout.saturating_sub(now)))
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/// Wait `nanos` of the guest's time: none on frozen clocks, and on live
|
|
145
|
+
/// ones no further than the invocation's deadline, which ends it as a
|
|
146
|
+
/// timeout.
|
|
147
|
+
fn wait(invocation: &Invocation, nanos: u64) -> Answer {
|
|
148
|
+
if nanos == 0 || !invocation.wasi().clocks().advances() {
|
|
149
|
+
return Ok(());
|
|
150
|
+
}
|
|
151
|
+
let wanted = Duration::from_nanos(nanos);
|
|
152
|
+
if let Some(deadline) = invocation.deadline() {
|
|
153
|
+
let left = deadline.saturating_duration_since(Instant::now());
|
|
154
|
+
if wanted >= left {
|
|
155
|
+
thread::sleep(left);
|
|
156
|
+
return Err(types::Error::trap(wasmtime::Error::new(TimeoutTrap)));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
thread::sleep(wanted);
|
|
160
|
+
Ok(())
|
|
161
|
+
}
|
|
162
|
+
|
|
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
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
#[cfg(test)]
|
|
180
|
+
mod tests {
|
|
181
|
+
//! Witness that a guest's clock wait passes on its posture's clocks:
|
|
182
|
+
//! at once on the frozen ones, and on the live ones no further than
|
|
183
|
+
//! the deadline.
|
|
184
|
+
use std::time::{Duration, Instant};
|
|
185
|
+
|
|
186
|
+
use wasmtime::{Module, Store};
|
|
187
|
+
|
|
188
|
+
use crate::cache::load_engine;
|
|
189
|
+
use crate::config::Config;
|
|
190
|
+
use crate::imports;
|
|
191
|
+
use crate::invocation::Invocation;
|
|
192
|
+
use crate::trap::{to_trap, NO_TIMEOUT_EPOCH_DELTA};
|
|
193
|
+
use crate::wasi::Wasi;
|
|
194
|
+
use kobako_runtime::error::Trap;
|
|
195
|
+
use kobako_runtime::profile::Profile;
|
|
196
|
+
|
|
197
|
+
/// One relative monotonic-clock subscription of ten seconds at 0,
|
|
198
|
+
/// its event written at 64 and the event count at 96.
|
|
199
|
+
const WAIT_WAT: &str = r#"
|
|
200
|
+
(module
|
|
201
|
+
(import "wasi_snapshot_preview1" "poll_oneoff"
|
|
202
|
+
(func $poll (param i32 i32 i32 i32) (result i32)))
|
|
203
|
+
(memory (export "memory") 1)
|
|
204
|
+
(func (export "wait_ten_seconds") (result i32)
|
|
205
|
+
(i32.store8 (i32.const 8) (i32.const 0))
|
|
206
|
+
(i32.store (i32.const 16) (i32.const 1))
|
|
207
|
+
(i64.store (i32.const 24) (i64.const 10000000000))
|
|
208
|
+
(call $poll (i32.const 0) (i32.const 64) (i32.const 1) (i32.const 96))))
|
|
209
|
+
"#;
|
|
210
|
+
|
|
211
|
+
/// Run the ten-second wait at `profile` under `deadline`, answering
|
|
212
|
+
/// how it ended and how long the host spent on it.
|
|
213
|
+
fn wait_ten_seconds(
|
|
214
|
+
profile: Profile,
|
|
215
|
+
deadline: Option<Duration>,
|
|
216
|
+
) -> (wasmtime::Result<i32>, Duration) {
|
|
217
|
+
let engine = load_engine().expect("shared engine must be constructible");
|
|
218
|
+
let config = Config {
|
|
219
|
+
timeout: None,
|
|
220
|
+
setup_timeout: None,
|
|
221
|
+
memory_limit: None,
|
|
222
|
+
stdout_limit: None,
|
|
223
|
+
stderr_limit: None,
|
|
224
|
+
open_file_limit: None,
|
|
225
|
+
profile,
|
|
226
|
+
security: Vec::new(),
|
|
227
|
+
};
|
|
228
|
+
let wasi = Wasi::for_invocation(&config, Vec::new(), &[]).expect("WASI context must build");
|
|
229
|
+
let mut store = Store::new(engine, Invocation::new(None, wasi));
|
|
230
|
+
store.set_epoch_deadline(NO_TIMEOUT_EPOCH_DELTA);
|
|
231
|
+
let linker = imports::linker().expect("host imports must link");
|
|
232
|
+
let module = Module::new(engine, WAIT_WAT).expect("wait module must compile");
|
|
233
|
+
let instance = linker
|
|
234
|
+
.instantiate(&mut store, &module)
|
|
235
|
+
.expect("wait module must instantiate");
|
|
236
|
+
let wait = instance
|
|
237
|
+
.get_typed_func::<(), i32>(&mut store, "wait_ten_seconds")
|
|
238
|
+
.expect("wait export must resolve");
|
|
239
|
+
let started = Instant::now();
|
|
240
|
+
store
|
|
241
|
+
.data_mut()
|
|
242
|
+
.set_deadline(deadline.map(|left| started + left));
|
|
243
|
+
let ended = wait.call(&mut store, ());
|
|
244
|
+
(ended, started.elapsed())
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// @behavior RT-073
|
|
248
|
+
#[test]
|
|
249
|
+
fn a_hermetic_wait_ends_without_the_host_waiting() {
|
|
250
|
+
let (ended, spent) = wait_ten_seconds(Profile::Hermetic, None);
|
|
251
|
+
assert_eq!(
|
|
252
|
+
ended.ok(),
|
|
253
|
+
Some(0),
|
|
254
|
+
"a ten-second clock wait through a hermetic guest's poll_oneoff must succeed"
|
|
255
|
+
);
|
|
256
|
+
assert!(
|
|
257
|
+
spent < Duration::from_secs(1),
|
|
258
|
+
"a ten-second clock wait through a hermetic guest's poll_oneoff must return at once, \
|
|
259
|
+
not after {spent:?}"
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/// What a lone relative wait of no time on clock `id` answers.
|
|
264
|
+
fn wait_alone_on(id: i32) -> i32 {
|
|
265
|
+
const WAIT_ON_WAT: &str = r#"
|
|
266
|
+
(module
|
|
267
|
+
(import "wasi_snapshot_preview1" "poll_oneoff"
|
|
268
|
+
(func $poll (param i32 i32 i32 i32) (result i32)))
|
|
269
|
+
(memory (export "memory") 1)
|
|
270
|
+
(func (export "wait_on") (param $id i32) (result i32)
|
|
271
|
+
(i32.store (i32.const 16) (local.get $id))
|
|
272
|
+
(call $poll (i32.const 0) (i32.const 64) (i32.const 1) (i32.const 96))))
|
|
273
|
+
"#;
|
|
274
|
+
let engine = load_engine().expect("shared engine must be constructible");
|
|
275
|
+
let config = Config {
|
|
276
|
+
timeout: None,
|
|
277
|
+
setup_timeout: None,
|
|
278
|
+
memory_limit: None,
|
|
279
|
+
stdout_limit: None,
|
|
280
|
+
stderr_limit: None,
|
|
281
|
+
open_file_limit: None,
|
|
282
|
+
profile: Profile::Hermetic,
|
|
283
|
+
security: Vec::new(),
|
|
284
|
+
};
|
|
285
|
+
let wasi = Wasi::for_invocation(&config, Vec::new(), &[]).expect("WASI context must build");
|
|
286
|
+
let mut store = Store::new(engine, Invocation::new(None, wasi));
|
|
287
|
+
store.set_epoch_deadline(NO_TIMEOUT_EPOCH_DELTA);
|
|
288
|
+
let linker = imports::linker().expect("host imports must link");
|
|
289
|
+
let module = Module::new(engine, WAIT_ON_WAT).expect("wait module must compile");
|
|
290
|
+
linker
|
|
291
|
+
.instantiate(&mut store, &module)
|
|
292
|
+
.expect("wait module must instantiate")
|
|
293
|
+
.get_typed_func::<i32, i32>(&mut store, "wait_on")
|
|
294
|
+
.expect("wait export must resolve")
|
|
295
|
+
.call(&mut store, id)
|
|
296
|
+
.expect("the wait must not trap")
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// @behavior RT-131
|
|
300
|
+
#[test]
|
|
301
|
+
fn a_lone_wait_on_a_cpu_time_clock_is_invalid() {
|
|
302
|
+
assert_eq!(
|
|
303
|
+
[0, 1, 2, 3].map(wait_alone_on),
|
|
304
|
+
[0, 0, ERRNO_INVAL, ERRNO_INVAL],
|
|
305
|
+
"a lone relative wait through poll_oneoff on the process or thread CPU-time clock \
|
|
306
|
+
must answer EINVAL as wasmtime-wasi does, while the realtime and monotonic clocks wait"
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const ERRNO_INVAL: i32 = 28;
|
|
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
|
+
|
|
365
|
+
// @behavior RT-074
|
|
366
|
+
#[test]
|
|
367
|
+
fn a_permissive_wait_ends_at_the_deadline_as_a_timeout() {
|
|
368
|
+
let (ended, spent) = wait_ten_seconds(Profile::Permissive, Some(Duration::from_millis(50)));
|
|
369
|
+
let trap = ended.expect_err("a wait past the deadline must not return to the guest");
|
|
370
|
+
assert!(
|
|
371
|
+
matches!(to_trap(trap), Trap::Timeout(_)),
|
|
372
|
+
"a clock wait past the deadline through a permissive guest's poll_oneoff must end \
|
|
373
|
+
as a timeout"
|
|
374
|
+
);
|
|
375
|
+
assert!(
|
|
376
|
+
spent >= Duration::from_millis(50) && spent < Duration::from_secs(5),
|
|
377
|
+
"a clock wait past the deadline through a permissive guest's poll_oneoff must end \
|
|
378
|
+
at the deadline, not after {spent:?}"
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//! The 4 `sock_*` calls. The table holds no socket, so every open
|
|
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`.
|
|
5
|
+
|
|
6
|
+
use super::errno::{Answer, Errno};
|
|
7
|
+
use super::fd::file;
|
|
8
|
+
use crate::descriptors::Descriptors;
|
|
9
|
+
|
|
10
|
+
pub(crate) fn socket<T>(table: &Descriptors, fd: u32) -> Answer<T> {
|
|
11
|
+
file(table, fd)?;
|
|
12
|
+
Err(Errno::Notsock.into())
|
|
13
|
+
}
|