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
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
//!
|
|
3
3
|
//! The surface where a wasm engine implementation and a host frontend
|
|
4
4
|
//! meet: the `Runtime` trait, the isolation `Profile` a runtime
|
|
5
|
-
//! declares, the Grants it presents to the guest and the
|
|
5
|
+
//! declares, the Grants it presents to the guest and the Filesystem a
|
|
6
6
|
//! Mount places, the neutral per-invocation value types, and the dispatch /
|
|
7
7
|
//! yield re-entry traits a frontend supplies. The messages these carry
|
|
8
8
|
//! are `kobako-transport`'s, which this crate takes as given.
|
|
@@ -13,27 +13,32 @@
|
|
|
13
13
|
//! so the engine stays swappable.
|
|
14
14
|
|
|
15
15
|
pub mod dispatch;
|
|
16
|
+
pub mod endpoint;
|
|
16
17
|
pub mod error;
|
|
17
18
|
pub mod filesystem;
|
|
18
19
|
pub mod grant;
|
|
20
|
+
mod hashtable;
|
|
21
|
+
mod locking;
|
|
19
22
|
pub mod mount;
|
|
20
23
|
pub mod profile;
|
|
21
24
|
pub mod runtime;
|
|
25
|
+
pub mod sched;
|
|
26
|
+
pub mod security;
|
|
22
27
|
pub mod snapshot;
|
|
28
|
+
pub mod vfs;
|
|
23
29
|
pub mod yielder;
|
|
24
30
|
|
|
25
31
|
// Every name an engine implementation writes into its own signatures, at
|
|
26
32
|
// the crate root: the module path repeats the crate name and adds nothing
|
|
27
33
|
// a reader of `kobako_runtime::Runtime` was missing.
|
|
28
34
|
pub use dispatch::DispatchHandler;
|
|
35
|
+
pub use endpoint::{Calls, GuestInstance};
|
|
29
36
|
pub use error::{InvokeError, SetupError, Trap};
|
|
30
|
-
pub use filesystem::{
|
|
31
|
-
Access, Blocking, DirEntry, Directory, File, FileType, Filesystem, FilesystemError, Hold,
|
|
32
|
-
Memory, Metadata, OpenOptions, Overlay, Rename, SeedError, Temporary,
|
|
33
|
-
};
|
|
37
|
+
pub use filesystem::{Bind, DirEntry, FileType, FilesystemError, Kstat, Overlay, SeedError, Tmpfs};
|
|
34
38
|
pub use grant::{Grant, GrantKind};
|
|
35
|
-
pub use mount::{
|
|
39
|
+
pub use mount::{Access, Mount, MountPathError, Policy};
|
|
36
40
|
pub use profile::Profile;
|
|
37
41
|
pub use runtime::{Entry, Runtime};
|
|
42
|
+
pub use sched::Blocking;
|
|
38
43
|
pub use snapshot::{Capture, Completion, Snapshot, Usage};
|
|
39
44
|
pub use yielder::Yielder;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
//! A lock its readers never wait on, as Linux's `seqlock_t`: a writer
|
|
2
|
+
//! counts each write, and a reader that a write overlapped reads again.
|
|
3
|
+
|
|
4
|
+
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
5
|
+
use std::sync::{Mutex, PoisonError};
|
|
6
|
+
|
|
7
|
+
pub(crate) struct SeqLock {
|
|
8
|
+
/// Holds one writer at a time, as `write_seqlock` takes the lock's
|
|
9
|
+
/// spinlock.
|
|
10
|
+
writer: Mutex<()>,
|
|
11
|
+
/// How many times a write began or ended, odd while one is under way.
|
|
12
|
+
sequence: AtomicUsize,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
impl SeqLock {
|
|
16
|
+
pub(crate) const fn new() -> Self {
|
|
17
|
+
Self {
|
|
18
|
+
writer: Mutex::new(()),
|
|
19
|
+
sequence: AtomicUsize::new(0),
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// Run `write` with every reader overlapping it told to read again, as
|
|
24
|
+
/// `write_seqlock` and `write_sequnlock` bracket a write.
|
|
25
|
+
pub(crate) fn write<T>(&self, write: impl FnOnce() -> T) -> T {
|
|
26
|
+
let _held = self.writer.lock().unwrap_or_else(PoisonError::into_inner);
|
|
27
|
+
self.sequence.fetch_add(1, Ordering::SeqCst);
|
|
28
|
+
let written = write();
|
|
29
|
+
self.sequence.fetch_add(1, Ordering::SeqCst);
|
|
30
|
+
written
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// Where the writes stand once none is under way, to ask `read_seqretry`
|
|
34
|
+
/// about later, as `__read_seqcount_begin` spins past an odd count. Holding
|
|
35
|
+
/// the writer's lock a moment waits out the one write it guards.
|
|
36
|
+
pub(crate) fn read_seqbegin(&self) -> usize {
|
|
37
|
+
loop {
|
|
38
|
+
let sequence = self.sequence.load(Ordering::SeqCst);
|
|
39
|
+
if sequence.is_multiple_of(2) {
|
|
40
|
+
return sequence;
|
|
41
|
+
}
|
|
42
|
+
drop(self.writer.lock().unwrap_or_else(PoisonError::into_inner));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// Whether a write began since `read_seqbegin` answered `begun`.
|
|
47
|
+
pub(crate) fn read_seqretry(&self, begun: usize) -> bool {
|
|
48
|
+
self.sequence.load(Ordering::SeqCst) != begun
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// What `read` answers with no write overlapping it, as
|
|
52
|
+
/// `read_seqbegin_or_lock` reads: first without waiting, then, once a
|
|
53
|
+
/// write overlapped, holding writers off, so writes one after another
|
|
54
|
+
/// never keep a reader reading again.
|
|
55
|
+
pub(crate) fn read<T>(&self, mut read: impl FnMut() -> T) -> T {
|
|
56
|
+
let begun = self.sequence.load(Ordering::SeqCst);
|
|
57
|
+
if begun.is_multiple_of(2) {
|
|
58
|
+
let answer = read();
|
|
59
|
+
if self.sequence.load(Ordering::SeqCst) == begun {
|
|
60
|
+
return answer;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
let _held = self.writer.lock().unwrap_or_else(PoisonError::into_inner);
|
|
64
|
+
read()
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
//! The lock a directory's calls run under, as Linux's `rw_semaphore` in
|
|
2
|
+
//! `kernel/locking`: shared while a name in it is looked up, exclusive while
|
|
3
|
+
//! one is made, removed, or moved. The other locks beneath the VFS sit
|
|
4
|
+
//! beside it.
|
|
5
|
+
|
|
6
|
+
pub(crate) mod seqlock;
|
|
7
|
+
|
|
8
|
+
use std::sync::{Arc, Condvar, Mutex, MutexGuard, PoisonError};
|
|
9
|
+
use std::thread::ThreadId;
|
|
10
|
+
|
|
11
|
+
use crate::sched;
|
|
12
|
+
|
|
13
|
+
/// Why a hold was not taken.
|
|
14
|
+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
15
|
+
pub(crate) enum DownError {
|
|
16
|
+
/// A wait no release could ever end, since the thread asking holds what it
|
|
17
|
+
/// asks for. A thread must never hang on itself, so it is answered, as
|
|
18
|
+
/// Linux's `rt_mutex` answers `-EDEADLK`.
|
|
19
|
+
Deadlock,
|
|
20
|
+
/// The wait's run was to stop, past its deadline or interrupted, as
|
|
21
|
+
/// `down_read_killable` answers `-EINTR` on a fatal signal.
|
|
22
|
+
Interrupted,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/// Held shared by any number of threads or exclusive by one. A thread
|
|
26
|
+
/// asking again for a hold it could only get once it let go of its own is
|
|
27
|
+
/// refused as a Deadlock; a thread already holding it shared may hold it
|
|
28
|
+
/// shared again, even past a waiting writer, which waits for that thread.
|
|
29
|
+
/// Every wait for it ends once its run is to stop, as each wait of Linux's
|
|
30
|
+
/// is `_killable`.
|
|
31
|
+
#[derive(Default)]
|
|
32
|
+
pub(crate) struct RwSemaphore {
|
|
33
|
+
holders: Mutex<Holders>,
|
|
34
|
+
freed: Condvar,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
#[derive(Default)]
|
|
38
|
+
struct Holders {
|
|
39
|
+
writer: Option<ThreadId>,
|
|
40
|
+
/// One entry for each shared hold, a thread holding it shared twice
|
|
41
|
+
/// standing twice.
|
|
42
|
+
readers: Vec<ThreadId>,
|
|
43
|
+
/// How many threads wait for a release, so one nobody waits for wakes
|
|
44
|
+
/// no one.
|
|
45
|
+
waiting: usize,
|
|
46
|
+
/// How many threads wait to hold it exclusive, which a thread arriving to hold
|
|
47
|
+
/// it shared goes behind, as `rwsem_down_read_slowpath` queues behind a waiting
|
|
48
|
+
/// writer, so readers one after another never keep a writer out.
|
|
49
|
+
writers_waiting: usize,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// The semaphore while one call holds it, freed as the call returns. It keeps
|
|
53
|
+
/// the semaphore alive itself, so whatever a lock was reached through may go
|
|
54
|
+
/// first.
|
|
55
|
+
pub(crate) struct RwSemaphoreGuard {
|
|
56
|
+
semaphore: Arc<RwSemaphore>,
|
|
57
|
+
holder: ThreadId,
|
|
58
|
+
exclusive: bool,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
impl RwSemaphore {
|
|
62
|
+
fn holders(&self) -> MutexGuard<'_, Holders> {
|
|
63
|
+
self.holders.lock().unwrap_or_else(PoisonError::into_inner)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/// Hold it shared, waiting while a writer holds it.
|
|
67
|
+
pub(crate) fn down_read(self: &Arc<Self>) -> Result<RwSemaphoreGuard, DownError> {
|
|
68
|
+
let current = sched::current();
|
|
69
|
+
self.down(current, false, |holders| match holders.writer {
|
|
70
|
+
Some(writer) if writer == current => Err(DownError::Deadlock),
|
|
71
|
+
Some(_) => Ok(false),
|
|
72
|
+
None if holders.writers_waiting > 0 && !holders.readers.contains(¤t) => Ok(false),
|
|
73
|
+
None => {
|
|
74
|
+
holders.readers.push(current);
|
|
75
|
+
Ok(true)
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/// Hold it exclusive, waiting while anyone holds it.
|
|
81
|
+
pub(crate) fn down_write(self: &Arc<Self>) -> Result<RwSemaphoreGuard, DownError> {
|
|
82
|
+
let current = sched::current();
|
|
83
|
+
self.down(current, true, |holders| {
|
|
84
|
+
if holders.writer == Some(current) || holders.readers.contains(¤t) {
|
|
85
|
+
return Err(DownError::Deadlock);
|
|
86
|
+
}
|
|
87
|
+
if holders.writer.is_some() || !holders.readers.is_empty() {
|
|
88
|
+
return Ok(false);
|
|
89
|
+
}
|
|
90
|
+
holders.writer = Some(current);
|
|
91
|
+
Ok(true)
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/// Hold it shared where nobody holds or waits to hold it exclusive, without
|
|
96
|
+
/// waiting, as `down_read_trylock` fails on a writer or a waiter.
|
|
97
|
+
pub(crate) fn down_read_trylock(self: &Arc<Self>) -> Option<RwSemaphoreGuard> {
|
|
98
|
+
let current = sched::current();
|
|
99
|
+
let mut holders = self.holders();
|
|
100
|
+
if holders.writer.is_some() || holders.writers_waiting > 0 {
|
|
101
|
+
return None;
|
|
102
|
+
}
|
|
103
|
+
holders.readers.push(current);
|
|
104
|
+
Some(self.guard(current, false))
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/// Hold it exclusive where nobody holds it, without waiting, as
|
|
108
|
+
/// `down_write_trylock`.
|
|
109
|
+
pub(crate) fn down_write_trylock(self: &Arc<Self>) -> Option<RwSemaphoreGuard> {
|
|
110
|
+
let current = sched::current();
|
|
111
|
+
let mut holders = self.holders();
|
|
112
|
+
if holders.writer.is_some() || !holders.readers.is_empty() {
|
|
113
|
+
return None;
|
|
114
|
+
}
|
|
115
|
+
holders.writer = Some(current);
|
|
116
|
+
Some(self.guard(current, true))
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
fn guard(self: &Arc<Self>, holder: ThreadId, exclusive: bool) -> RwSemaphoreGuard {
|
|
120
|
+
RwSemaphoreGuard {
|
|
121
|
+
semaphore: self.clone(),
|
|
122
|
+
holder,
|
|
123
|
+
exclusive,
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/// Whether a thread waits to hold it exclusive, for a test to see one queued.
|
|
128
|
+
#[cfg(test)]
|
|
129
|
+
pub(crate) fn writer_waits(&self) -> bool {
|
|
130
|
+
self.holders().writers_waiting > 0
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/// Take the hold `take` grants, or wait for it.
|
|
134
|
+
fn down(
|
|
135
|
+
self: &Arc<Self>,
|
|
136
|
+
holder: ThreadId,
|
|
137
|
+
exclusive: bool,
|
|
138
|
+
mut take: impl FnMut(&mut Holders) -> Result<bool, DownError>,
|
|
139
|
+
) -> Result<RwSemaphoreGuard, DownError> {
|
|
140
|
+
let mut holders = self.holders();
|
|
141
|
+
if !take(&mut holders)? {
|
|
142
|
+
drop(holders);
|
|
143
|
+
self.down_slowpath(exclusive, &mut take)?;
|
|
144
|
+
}
|
|
145
|
+
Ok(self.guard(holder, exclusive))
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/// Wait for a release and ask again, as `rwsem_down_read_slowpath` and
|
|
149
|
+
/// `rwsem_down_write_slowpath` call `schedule`, until the run is to stop, as
|
|
150
|
+
/// a killable wait looks at `fatal_signal_pending` each time it wakes.
|
|
151
|
+
fn down_slowpath(
|
|
152
|
+
&self,
|
|
153
|
+
exclusive: bool,
|
|
154
|
+
take: &mut impl FnMut(&mut Holders) -> Result<bool, DownError>,
|
|
155
|
+
) -> Result<(), DownError> {
|
|
156
|
+
let mut taken = Ok(());
|
|
157
|
+
sched::schedule(&mut || {
|
|
158
|
+
let mut holders = self.holders();
|
|
159
|
+
holders.writers_waiting += usize::from(exclusive);
|
|
160
|
+
loop {
|
|
161
|
+
match take(&mut holders) {
|
|
162
|
+
Ok(true) => break,
|
|
163
|
+
Ok(false) if sched::fatal_signal_pending() => {
|
|
164
|
+
taken = Err(DownError::Interrupted);
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
Ok(false) => {
|
|
168
|
+
holders.waiting += 1;
|
|
169
|
+
holders = self
|
|
170
|
+
.freed
|
|
171
|
+
.wait_timeout(holders, sched::sleep_slice())
|
|
172
|
+
.unwrap_or_else(PoisonError::into_inner)
|
|
173
|
+
.0;
|
|
174
|
+
holders.waiting -= 1;
|
|
175
|
+
}
|
|
176
|
+
Err(err) => {
|
|
177
|
+
taken = Err(err);
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
holders.writers_waiting -= usize::from(exclusive);
|
|
183
|
+
// A reader may have stood behind this writer, which now takes it or
|
|
184
|
+
// gave up; one that took it wakes them again once it lets go.
|
|
185
|
+
if holders.waiting > 0 && taken.is_err() {
|
|
186
|
+
self.freed.notify_all();
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
taken
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
impl RwSemaphoreGuard {
|
|
194
|
+
/// Whether this holds `semaphore`.
|
|
195
|
+
pub(crate) fn holds(&self, semaphore: &Arc<RwSemaphore>) -> bool {
|
|
196
|
+
Arc::ptr_eq(&self.semaphore, semaphore)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
impl Drop for RwSemaphoreGuard {
|
|
201
|
+
fn drop(&mut self) {
|
|
202
|
+
let mut holders = self.semaphore.holders();
|
|
203
|
+
if self.exclusive {
|
|
204
|
+
holders.writer = None;
|
|
205
|
+
} else if let Some(at) = holders
|
|
206
|
+
.readers
|
|
207
|
+
.iter()
|
|
208
|
+
.position(|reader| *reader == self.holder)
|
|
209
|
+
{
|
|
210
|
+
holders.readers.swap_remove(at);
|
|
211
|
+
}
|
|
212
|
+
let waiting = holders.waiting > 0;
|
|
213
|
+
drop(holders);
|
|
214
|
+
if waiting {
|
|
215
|
+
self.semaphore.freed.notify_all();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
@@ -1,38 +1,37 @@
|
|
|
1
1
|
//! A Mount: the Grant handing a Filesystem to the guest at an absolute
|
|
2
|
-
//! guest path, with the access it allows there
|
|
3
|
-
//!
|
|
4
|
-
//!
|
|
2
|
+
//! guest path, with the access it allows there and the Policy that may refuse
|
|
3
|
+
//! more, which the VFS asks through its Cred, so a Filesystem of a third
|
|
4
|
+
//! party's checks nothing.
|
|
5
5
|
|
|
6
6
|
use std::fmt;
|
|
7
7
|
use std::sync::Arc;
|
|
8
8
|
|
|
9
|
-
use crate::filesystem::
|
|
9
|
+
use crate::filesystem::Filesystem;
|
|
10
|
+
use crate::vfs::VfsMount;
|
|
10
11
|
|
|
11
|
-
///
|
|
12
|
-
///
|
|
12
|
+
/// What a Mount allows a guest beneath it, and what a Policy is asked to allow
|
|
13
|
+
/// for one path.
|
|
14
|
+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
15
|
+
pub enum Access {
|
|
16
|
+
Read,
|
|
17
|
+
ReadWrite,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/// The Host App's decision for one path a guest opens or changes, made after
|
|
21
|
+
/// a Mount's access allows it, so it can only refuse more.
|
|
13
22
|
pub trait Policy: Send + Sync {
|
|
14
23
|
/// Whether the guest may reach `path`, the absolute guest path, with
|
|
15
24
|
/// `access`.
|
|
16
25
|
fn allows(&self, path: &str, access: Access) -> bool;
|
|
17
26
|
}
|
|
18
27
|
|
|
19
|
-
///
|
|
20
|
-
|
|
21
|
-
pub enum Denial {
|
|
22
|
-
/// A change beneath a Mount allowing only reads.
|
|
23
|
-
ReadOnly,
|
|
24
|
-
/// The Mount's Policy said no.
|
|
25
|
-
Refused,
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/// A Filesystem at an absolute guest path. Its path bounds what a Policy
|
|
29
|
-
/// can allow either way.
|
|
28
|
+
/// A Filesystem instance at an absolute guest path. Its path bounds what a
|
|
29
|
+
/// Policy can allow either way.
|
|
30
30
|
#[derive(Clone)]
|
|
31
31
|
pub struct Mount {
|
|
32
32
|
at: String,
|
|
33
|
-
access: Access,
|
|
34
|
-
filesystem: Arc<dyn Filesystem>,
|
|
35
33
|
policy: Option<Arc<dyn Policy>>,
|
|
34
|
+
mnt: Arc<VfsMount>,
|
|
36
35
|
}
|
|
37
36
|
|
|
38
37
|
/// A path no Mount can stand at: one that is not an absolute guest path.
|
|
@@ -66,7 +65,7 @@ impl Mount {
|
|
|
66
65
|
/// guest path, which every path the Mount answers for is built on.
|
|
67
66
|
pub fn new(
|
|
68
67
|
at: impl Into<String>,
|
|
69
|
-
filesystem: Arc<
|
|
68
|
+
filesystem: Arc<Filesystem>,
|
|
70
69
|
access: Access,
|
|
71
70
|
) -> Result<Self, MountPathError> {
|
|
72
71
|
let at = at.into();
|
|
@@ -75,14 +74,13 @@ impl Mount {
|
|
|
75
74
|
}
|
|
76
75
|
Ok(Self {
|
|
77
76
|
at,
|
|
78
|
-
access,
|
|
79
|
-
filesystem,
|
|
77
|
+
mnt: VfsMount::new(filesystem, access == Access::Read),
|
|
80
78
|
policy: None,
|
|
81
79
|
})
|
|
82
80
|
}
|
|
83
81
|
|
|
84
|
-
/// This Mount with `policy`
|
|
85
|
-
/// access
|
|
82
|
+
/// This Mount with `policy` also judging each open and each change its
|
|
83
|
+
/// access allows; it can only refuse more, as an LSM can.
|
|
86
84
|
pub fn with_policy(self, policy: Arc<dyn Policy>) -> Self {
|
|
87
85
|
Self {
|
|
88
86
|
policy: Some(policy),
|
|
@@ -90,89 +88,18 @@ impl Mount {
|
|
|
90
88
|
}
|
|
91
89
|
}
|
|
92
90
|
|
|
93
|
-
/// The
|
|
94
|
-
pub fn
|
|
95
|
-
&self.
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/// The Filesystem an engine hands each call beneath this Mount.
|
|
99
|
-
pub fn filesystem(&self) -> &Arc<dyn Filesystem> {
|
|
100
|
-
&self.filesystem
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/// Whether a change beneath this Mount may be allowed at all: it was made
|
|
104
|
-
/// to be written, or a Policy decides each change in its access's place.
|
|
105
|
-
/// The Policy itself is not asked.
|
|
106
|
-
pub fn allows_writes(&self) -> bool {
|
|
107
|
-
self.policy.is_some() || self.access == Access::ReadWrite
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/// The absolute guest path `path`, relative to this Mount's root,
|
|
111
|
-
/// stands for.
|
|
112
|
-
pub fn guest_path(&self, path: &str) -> String {
|
|
113
|
-
match (self.at.as_str(), path) {
|
|
114
|
-
(at, "") => at.to_string(),
|
|
115
|
-
("/", path) => format!("/{path}"),
|
|
116
|
-
(at, path) => format!("{at}/{path}"),
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/// Whether the guest may reach `path`, relative to this Mount's root,
|
|
121
|
-
/// with `access`. A Policy judges the path as the Filesystem stores
|
|
122
|
-
/// its names, so a host folding case or Unicode form cannot hand it
|
|
123
|
-
/// one spelling of an entry it refuses by another; a Filesystem that
|
|
124
|
-
/// cannot spell it refuses.
|
|
125
|
-
pub fn check(&self, path: &str, access: Access) -> Result<(), Denial> {
|
|
126
|
-
match &self.policy {
|
|
127
|
-
Some(policy) => {
|
|
128
|
-
let stored = self
|
|
129
|
-
.filesystem
|
|
130
|
-
.stored_path(path)
|
|
131
|
-
.map_err(|_| Denial::Refused)?;
|
|
132
|
-
if policy.allows(&self.guest_path(&stored), access) {
|
|
133
|
-
Ok(())
|
|
134
|
-
} else {
|
|
135
|
-
Err(Denial::Refused)
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
None if access == Access::ReadWrite && self.access == Access::Read => {
|
|
139
|
-
Err(Denial::ReadOnly)
|
|
140
|
-
}
|
|
141
|
-
None => Ok(()),
|
|
142
|
-
}
|
|
91
|
+
/// The Filesystem as the VFS walks it.
|
|
92
|
+
pub fn mnt(&self) -> &Arc<VfsMount> {
|
|
93
|
+
&self.mnt
|
|
143
94
|
}
|
|
144
95
|
|
|
145
|
-
///
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
/// copies entry by entry instead, as Landlock refuses such a move.
|
|
149
|
-
pub fn moves_directories(&self) -> bool {
|
|
150
|
-
self.policy.is_none()
|
|
96
|
+
/// The Policy deciding beneath this Mount, once one was given.
|
|
97
|
+
pub(crate) fn policy(&self) -> Option<&Arc<dyn Policy>> {
|
|
98
|
+
self.policy.as_ref()
|
|
151
99
|
}
|
|
152
100
|
|
|
153
|
-
///
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
/// too many links.
|
|
157
|
-
pub fn follows_links(&self) -> bool {
|
|
158
|
-
self.policy.is_none()
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/// This Mount as a new Invocation starts from it, over the Filesystem
|
|
162
|
-
/// that Invocation receives; beneath a Policy, the one that follows no
|
|
163
|
-
/// link, so each call reaches the path the Policy judged.
|
|
164
|
-
pub fn for_invocation(&self) -> Self {
|
|
165
|
-
let filesystem = self
|
|
166
|
-
.filesystem
|
|
167
|
-
.for_invocation()
|
|
168
|
-
.unwrap_or_else(|| self.filesystem.clone());
|
|
169
|
-
let filesystem = match self.policy {
|
|
170
|
-
Some(_) => filesystem.for_policy().unwrap_or(filesystem),
|
|
171
|
-
None => filesystem,
|
|
172
|
-
};
|
|
173
|
-
Self {
|
|
174
|
-
filesystem,
|
|
175
|
-
..self.clone()
|
|
176
|
-
}
|
|
101
|
+
/// The absolute guest path the Filesystem appears at.
|
|
102
|
+
pub fn at(&self) -> &str {
|
|
103
|
+
&self.at
|
|
177
104
|
}
|
|
178
105
|
}
|
|
@@ -6,13 +6,20 @@
|
|
|
6
6
|
//! actually built, and the frontend refuses a declaration below the
|
|
7
7
|
//! request — so the request is also the floor.
|
|
8
8
|
|
|
9
|
+
use std::hash::{BuildHasher, RandomState};
|
|
10
|
+
use std::sync::LazyLock;
|
|
11
|
+
use std::time::{SystemTime, UNIX_EPOCH};
|
|
12
|
+
|
|
13
|
+
use crate::vfs::{FileType, Identity, Kstat};
|
|
14
|
+
|
|
9
15
|
/// The ordered isolation ladder a runtime builds one rung of.
|
|
10
16
|
///
|
|
11
17
|
/// `Hermetic` is the full ambient-denial posture: ambient time and
|
|
12
18
|
/// entropy denied at the WASI boundary, no ambient filesystem /
|
|
13
19
|
/// environment / network reachability, and no host import beyond the wire ABI's
|
|
14
20
|
/// `__kobako_upcall`. `Permissive` differs only in the ambient
|
|
15
|
-
/// authority it grants — live time and entropy at the WASI boundary
|
|
21
|
+
/// authority it grants — live time and entropy at the WASI boundary — and in
|
|
22
|
+
/// the times and inode numbers it shows of an entry. Ordering
|
|
16
23
|
/// follows strength (`Permissive < Hermetic`), so a floor check is a
|
|
17
24
|
/// plain comparison.
|
|
18
25
|
///
|
|
@@ -29,10 +36,139 @@ pub enum Profile {
|
|
|
29
36
|
Hermetic,
|
|
30
37
|
}
|
|
31
38
|
|
|
39
|
+
impl Profile {
|
|
40
|
+
/// The inode number a guest on this posture sees for `identity`. A
|
|
41
|
+
/// hermetic guest sees none, as a Filesystem's device number depends on
|
|
42
|
+
/// how many were made before it in the process. Any other sees a hash of
|
|
43
|
+
/// it under a key the process draws once, so it can tell entries apart
|
|
44
|
+
/// and cannot work back to a host's inode number.
|
|
45
|
+
pub fn guest_ino(self, identity: Option<Identity>) -> u64 {
|
|
46
|
+
static KEY: LazyLock<RandomState> = LazyLock::new(RandomState::new);
|
|
47
|
+
match (self, identity) {
|
|
48
|
+
(Self::Hermetic, _) | (_, None) => 0,
|
|
49
|
+
(Self::Permissive, Some(identity)) => KEY.hash_one(identity),
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/// `stat` as a guest on this posture sees it, whichever ABI a Wasm
|
|
54
|
+
/// Driver encodes it into. A Filesystem records the host's real time whatever
|
|
55
|
+
/// the invocation writing to it runs under, so one shared across
|
|
56
|
+
/// invocations keeps one record; a hermetic guest sees every time at the
|
|
57
|
+
/// Unix epoch, where its wall clock stands, so a time a write left behind
|
|
58
|
+
/// is no clock it can read. A link reports no size, so nothing of where it
|
|
59
|
+
/// points reaches the guest, and an entry is told apart only by what
|
|
60
|
+
/// `guest_ino` answers.
|
|
61
|
+
pub fn guest_kstat(self, stat: Kstat) -> Kstat {
|
|
62
|
+
let ino = self.guest_ino(Identity::of(&stat));
|
|
63
|
+
let masked = match self {
|
|
64
|
+
Self::Permissive => stat,
|
|
65
|
+
Self::Hermetic => {
|
|
66
|
+
let epoch = |time: Option<SystemTime>| time.map(|_| UNIX_EPOCH);
|
|
67
|
+
stat.with_times(epoch(stat.atime), epoch(stat.mtime), epoch(stat.ctime))
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
Kstat {
|
|
71
|
+
size: match masked.file_type {
|
|
72
|
+
FileType::Symlink => 0,
|
|
73
|
+
_ => masked.size,
|
|
74
|
+
},
|
|
75
|
+
..masked.with_identity(0, ino)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
32
80
|
#[cfg(test)]
|
|
33
81
|
mod tests {
|
|
34
82
|
use super::*;
|
|
35
83
|
|
|
84
|
+
// The Wasm Driver only encodes this, so another one shows the same, and a
|
|
85
|
+
// guest reads a link's size and a time here or nowhere.
|
|
86
|
+
// @behavior RT-173 RT-183
|
|
87
|
+
#[test]
|
|
88
|
+
fn a_guest_sees_no_size_for_a_link_and_only_what_its_posture_allows() {
|
|
89
|
+
let reported = Kstat::new(FileType::Symlink, 42)
|
|
90
|
+
.with_identity(7, 9)
|
|
91
|
+
.with_times(Some(SystemTime::now()), None, None);
|
|
92
|
+
let (hermetic, permissive) = (
|
|
93
|
+
Profile::Hermetic.guest_kstat(reported),
|
|
94
|
+
Profile::Permissive.guest_kstat(reported),
|
|
95
|
+
);
|
|
96
|
+
assert_eq!(
|
|
97
|
+
(hermetic.size, permissive.size, hermetic.ino, hermetic.atime),
|
|
98
|
+
(0, 0, 0, Some(UNIX_EPOCH)),
|
|
99
|
+
"Profile::guest_kstat of a link must report no size on any posture, and at the \
|
|
100
|
+
hermetic rung no inode number and every time at the Unix epoch"
|
|
101
|
+
);
|
|
102
|
+
assert!(
|
|
103
|
+
permissive.ino != 0 && permissive.ino != 9 && permissive.atime == reported.atime,
|
|
104
|
+
"Profile::guest_kstat at the permissive rung must hash the inode number and keep \
|
|
105
|
+
the time"
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// A device number depends on how many Filesystems the process made before,
|
|
110
|
+
// so the inode number a guest reads folds it in and the device stays out.
|
|
111
|
+
// @behavior RT-283
|
|
112
|
+
#[test]
|
|
113
|
+
fn a_guest_sees_one_device_for_every_entry() {
|
|
114
|
+
let reported = Kstat::new(FileType::RegFile, 1).with_identity(7, 9);
|
|
115
|
+
assert_eq!(
|
|
116
|
+
[Profile::Hermetic, Profile::Permissive]
|
|
117
|
+
.map(|profile| profile.guest_kstat(reported).dev),
|
|
118
|
+
[0, 0],
|
|
119
|
+
"Profile::guest_kstat of an entry a Filesystem reports on device 7 must report \
|
|
120
|
+
device 0 on either posture"
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// An unkeyed hash answers what anyone can compute from a host's numbers,
|
|
125
|
+
// so the hash a guest reads must differ from it.
|
|
126
|
+
// @behavior RT-194
|
|
127
|
+
#[test]
|
|
128
|
+
fn a_guest_cannot_compute_the_inode_number_it_reads() {
|
|
129
|
+
use std::collections::hash_map::DefaultHasher;
|
|
130
|
+
use std::hash::{Hash, Hasher};
|
|
131
|
+
|
|
132
|
+
let reported = Kstat::new(FileType::RegFile, 0).with_identity(7, 9);
|
|
133
|
+
let identity = Identity::of(&reported);
|
|
134
|
+
let mut unkeyed = DefaultHasher::new();
|
|
135
|
+
identity
|
|
136
|
+
.expect("an inode number names an identity")
|
|
137
|
+
.hash(&mut unkeyed);
|
|
138
|
+
let seen = Profile::Permissive.guest_ino(identity);
|
|
139
|
+
assert_eq!(
|
|
140
|
+
seen,
|
|
141
|
+
Profile::Permissive.guest_ino(identity),
|
|
142
|
+
"Profile::guest_ino of one identity must answer one number within a process"
|
|
143
|
+
);
|
|
144
|
+
assert_ne!(
|
|
145
|
+
seen,
|
|
146
|
+
unkeyed.finish(),
|
|
147
|
+
"Profile::guest_ino at the permissive rung must not answer a hash anyone can compute \
|
|
148
|
+
from the device and inode numbers"
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// A guest reads the count on any ABI, so the count starts in the runtime
|
|
153
|
+
// rather than in what a Wasm Driver encodes.
|
|
154
|
+
// @behavior RT-193
|
|
155
|
+
#[test]
|
|
156
|
+
fn an_entry_whose_filesystem_keeps_no_count_is_named_once() {
|
|
157
|
+
let reported = Kstat::new(FileType::RegFile, 0);
|
|
158
|
+
assert_eq!(
|
|
159
|
+
(
|
|
160
|
+
Profile::Hermetic.guest_kstat(reported).nlink,
|
|
161
|
+
Profile::Permissive.guest_kstat(reported).nlink,
|
|
162
|
+
Profile::Permissive
|
|
163
|
+
.guest_kstat(reported.with_nlink(3))
|
|
164
|
+
.nlink,
|
|
165
|
+
),
|
|
166
|
+
(1, 1, 3),
|
|
167
|
+
"Profile::guest_kstat of an entry reporting no count must show one name on each \
|
|
168
|
+
posture, and of one reporting three must show three"
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
36
172
|
// @behavior RT-014
|
|
37
173
|
#[test]
|
|
38
174
|
fn a_declaration_satisfies_any_floor_at_or_below_it() {
|