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
|
@@ -1,271 +0,0 @@
|
|
|
1
|
-
//! Guest linear-memory I/O for the ABI's buffer handoffs, whether the
|
|
2
|
-
//! invocation body drives one through its `Store` or a dispatch or yield
|
|
3
|
-
//! import drives one through its `Caller`. preview1 reads and writes its
|
|
4
|
-
//! arguments through its own `GuestMemory`.
|
|
5
|
-
//!
|
|
6
|
-
//! The two shared steps answer an error kind, and each caller words it,
|
|
7
|
-
//! because depth decides what the same problem means: before the export
|
|
8
|
-
//! call it is a `Trap` or an intact runtime, inside a callback it is a
|
|
9
|
-
//! wire failure the guest receives.
|
|
10
|
-
|
|
11
|
-
use wasmtime::{AsContext, AsContextMut, Caller, Memory};
|
|
12
|
-
|
|
13
|
-
use crate::exports::{Exports, RUNTIME_INCOMPATIBLE};
|
|
14
|
-
use crate::invocation::Invocation;
|
|
15
|
-
use kobako_transport::abi::{unpack_ptr_len, MAX_DISPATCH_PAYLOAD};
|
|
16
|
-
use kobako_transport::envelope::Downcall;
|
|
17
|
-
|
|
18
|
-
// The size-limit messages here and in `io` name the cap as 16 MiB, so
|
|
19
|
-
// moving the cap fails the build until they are reworded with it.
|
|
20
|
-
const _: () = assert!(MAX_DISPATCH_PAYLOAD == 16 << 20);
|
|
21
|
-
|
|
22
|
-
/// User-facing message for the "the loaded Wasm module is not a
|
|
23
|
-
/// Kobako-shaped runtime at all" failure mode, such as an instance with no
|
|
24
|
-
/// `memory` export.
|
|
25
|
-
pub(crate) const SANDBOX_RUNTIME_NOT_KOBAKO: &str =
|
|
26
|
-
"the loaded Wasm module is not a Kobako-compatible runtime";
|
|
27
|
-
|
|
28
|
-
/// Why a host write into guest memory failed.
|
|
29
|
-
pub(crate) enum WriteError {
|
|
30
|
-
TooLarge,
|
|
31
|
-
/// The allocator or the memory is missing or mistyped; carries the
|
|
32
|
-
/// message for it.
|
|
33
|
-
Incompatible(&'static str),
|
|
34
|
-
Trapped(wasmtime::Error),
|
|
35
|
-
/// The allocator ran and answered 0.
|
|
36
|
-
OutOfMemory,
|
|
37
|
-
OutOfBounds,
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/// Why a host read of a guest-named buffer failed.
|
|
41
|
-
pub(crate) enum ReadError {
|
|
42
|
-
TooLarge,
|
|
43
|
-
OutOfBounds,
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/// Allocate a guest buffer through `__kobako_alloc` and copy `bytes` into
|
|
47
|
-
/// it, answering the buffer's address.
|
|
48
|
-
pub(crate) fn alloc_and_write(
|
|
49
|
-
mut ctx: impl AsContextMut,
|
|
50
|
-
exports: &Exports,
|
|
51
|
-
bytes: &[u8],
|
|
52
|
-
) -> Result<u32, WriteError> {
|
|
53
|
-
let len = checked_payload_len(bytes.len()).map_err(|_| WriteError::TooLarge)?;
|
|
54
|
-
let alloc = exports
|
|
55
|
-
.alloc
|
|
56
|
-
.as_ref()
|
|
57
|
-
.ok_or(WriteError::Incompatible(RUNTIME_INCOMPATIBLE))?;
|
|
58
|
-
let memory = exports
|
|
59
|
-
.memory
|
|
60
|
-
.ok_or(WriteError::Incompatible(SANDBOX_RUNTIME_NOT_KOBAKO))?;
|
|
61
|
-
let ptr = alloc
|
|
62
|
-
.call(&mut ctx, len as u32)
|
|
63
|
-
.map_err(WriteError::Trapped)?;
|
|
64
|
-
if ptr == 0 {
|
|
65
|
-
return Err(WriteError::OutOfMemory);
|
|
66
|
-
}
|
|
67
|
-
memory
|
|
68
|
-
.write(&mut ctx, ptr as usize, bytes)
|
|
69
|
-
.map_err(|_| WriteError::OutOfBounds)?;
|
|
70
|
-
Ok(ptr)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/// Copy out the buffer the guest names; a length past the 16 MiB cap is
|
|
74
|
-
/// refused before memory is touched.
|
|
75
|
-
pub(crate) fn read_buffer(
|
|
76
|
-
ctx: impl AsContext,
|
|
77
|
-
memory: Memory,
|
|
78
|
-
ptr: usize,
|
|
79
|
-
len: usize,
|
|
80
|
-
) -> Result<Vec<u8>, ReadError> {
|
|
81
|
-
if len > MAX_DISPATCH_PAYLOAD {
|
|
82
|
-
return Err(ReadError::TooLarge);
|
|
83
|
-
}
|
|
84
|
-
let data = memory.data(&ctx);
|
|
85
|
-
let range = guest_buffer_range(ptr, len, data.len()).map_err(|_| ReadError::OutOfBounds)?;
|
|
86
|
-
Ok(data[range].to_vec())
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/// The handles of the instance a host import runs inside.
|
|
90
|
-
fn callback_exports(caller: &Caller<'_, Invocation>) -> Result<Exports, &'static str> {
|
|
91
|
-
caller.data().exports().ok_or(SANDBOX_RUNTIME_NOT_KOBAKO)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/// Keep a trap the guest raised during a callback into it for the dispatch
|
|
95
|
-
/// import to end the invocation with, and answer the callback's own reason.
|
|
96
|
-
fn keep_trap(
|
|
97
|
-
caller: &mut Caller<'_, Invocation>,
|
|
98
|
-
trap: wasmtime::Error,
|
|
99
|
-
reason: &'static str,
|
|
100
|
-
) -> &'static str {
|
|
101
|
-
caller.data_mut().record_reentry_trap(trap);
|
|
102
|
-
reason
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/// The request the guest hands the upcall import. Its `(ptr, len)` are
|
|
106
|
-
/// guest addresses, so they are read unsigned.
|
|
107
|
-
pub(crate) fn read_request(
|
|
108
|
-
caller: &mut Caller<'_, Invocation>,
|
|
109
|
-
req_ptr: i32,
|
|
110
|
-
req_len: i32,
|
|
111
|
-
) -> Result<Vec<u8>, &'static str> {
|
|
112
|
-
let memory = callback_exports(caller)?
|
|
113
|
-
.memory
|
|
114
|
-
.ok_or(SANDBOX_RUNTIME_NOT_KOBAKO)?;
|
|
115
|
-
read_buffer(
|
|
116
|
-
&*caller,
|
|
117
|
-
memory,
|
|
118
|
-
req_ptr as u32 as usize,
|
|
119
|
-
req_len as u32 as usize,
|
|
120
|
-
)
|
|
121
|
-
.map_err(|err| match err {
|
|
122
|
-
ReadError::TooLarge => "request payload exceeds the 16 MiB limit",
|
|
123
|
-
ReadError::OutOfBounds => "the Sandbox produced an out-of-bounds request",
|
|
124
|
-
})
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/// Write `bytes` into the guest from inside a callback. A guest that has
|
|
128
|
-
/// already trapped is not called again.
|
|
129
|
-
pub(crate) fn write_from_callback(
|
|
130
|
-
caller: &mut Caller<'_, Invocation>,
|
|
131
|
-
bytes: &[u8],
|
|
132
|
-
) -> Result<u32, &'static str> {
|
|
133
|
-
if caller.data().reentry_trapped() {
|
|
134
|
-
return Err("the Sandbox already trapped during this invocation");
|
|
135
|
-
}
|
|
136
|
-
let exports = callback_exports(caller)?;
|
|
137
|
-
alloc_and_write(&mut *caller, &exports, bytes).map_err(|err| match err {
|
|
138
|
-
WriteError::TooLarge => "payload exceeds the 16 MiB limit",
|
|
139
|
-
WriteError::Incompatible(message) => message,
|
|
140
|
-
WriteError::Trapped(trap) => keep_trap(
|
|
141
|
-
caller,
|
|
142
|
-
trap,
|
|
143
|
-
"the Sandbox trapped while allocating memory for the request",
|
|
144
|
-
),
|
|
145
|
-
WriteError::OutOfMemory => "the Sandbox ran out of memory while preparing the request",
|
|
146
|
-
WriteError::OutOfBounds => "could not write the request into the Sandbox's memory",
|
|
147
|
-
})
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/// Every host write boundary routes its length through here, so the
|
|
151
|
-
/// wire-violation reason is uniform.
|
|
152
|
-
pub(crate) fn checked_payload_len(len: usize) -> Result<i32, &'static str> {
|
|
153
|
-
if len > MAX_DISPATCH_PAYLOAD {
|
|
154
|
-
return Err("payload exceeds the 16 MiB limit");
|
|
155
|
-
}
|
|
156
|
-
// The cap above sits below `i32::MAX`, so this conversion cannot wrap.
|
|
157
|
-
i32::try_from(len).map_err(|_| "payload exceeds the 16 MiB limit")
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
pub(crate) fn guest_buffer_range(
|
|
161
|
-
ptr: usize,
|
|
162
|
-
len: usize,
|
|
163
|
-
mem_size: usize,
|
|
164
|
-
) -> Result<core::ops::Range<usize>, &'static str> {
|
|
165
|
-
let end = ptr.checked_add(len).ok_or("ptr + len overflow")?;
|
|
166
|
-
if end > mem_size {
|
|
167
|
-
return Err("range exceeds Sandbox memory size");
|
|
168
|
-
}
|
|
169
|
-
Ok(ptr..end)
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/// The `(ptr, len)` the guest packs for any answer it hands back, the
|
|
173
|
-
/// outcome and a block's result alike.
|
|
174
|
-
pub(crate) fn unpack_guest_buffer(packed: u64) -> (usize, usize) {
|
|
175
|
-
let (ptr, len) = unpack_ptr_len(packed);
|
|
176
|
-
(ptr as usize, len as usize)
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
pub(crate) fn drive_yield(
|
|
180
|
-
caller: &mut Caller<'_, Invocation>,
|
|
181
|
-
frame: u32,
|
|
182
|
-
args: &[u8],
|
|
183
|
-
) -> Result<Vec<u8>, &'static str> {
|
|
184
|
-
let request = Downcall::Yield {
|
|
185
|
-
frame,
|
|
186
|
-
args: args.to_vec(),
|
|
187
|
-
}
|
|
188
|
-
.encode();
|
|
189
|
-
let len_i32 = checked_payload_len(request.len())?;
|
|
190
|
-
let req_ptr = write_from_callback(caller, &request)? as i32;
|
|
191
|
-
|
|
192
|
-
let exports = callback_exports(caller)?;
|
|
193
|
-
let yield_fn = exports.downcall.ok_or(RUNTIME_INCOMPATIBLE)?;
|
|
194
|
-
let memory = exports.memory.ok_or(SANDBOX_RUNTIME_NOT_KOBAKO)?;
|
|
195
|
-
let packed = yield_fn
|
|
196
|
-
.call(&mut *caller, (req_ptr, len_i32))
|
|
197
|
-
.map_err(|trap| keep_trap(caller, trap, "the Sandbox trapped while invoking a block"))?;
|
|
198
|
-
let (reply_ptr, reply_len) = unpack_guest_buffer(packed);
|
|
199
|
-
if reply_len == 0 {
|
|
200
|
-
return Err("the Sandbox returned an empty block result");
|
|
201
|
-
}
|
|
202
|
-
read_buffer(&*caller, memory, reply_ptr, reply_len).map_err(|err| match err {
|
|
203
|
-
ReadError::TooLarge => "block result payload exceeds the 16 MiB limit",
|
|
204
|
-
ReadError::OutOfBounds => "the Sandbox returned an out-of-bounds block result",
|
|
205
|
-
})
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
#[cfg(test)]
|
|
209
|
-
mod tests {
|
|
210
|
-
use super::{
|
|
211
|
-
checked_payload_len, guest_buffer_range, unpack_guest_buffer, MAX_DISPATCH_PAYLOAD,
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
// @behavior WE-059
|
|
215
|
-
#[test]
|
|
216
|
-
fn checked_payload_len_accepts_zero_and_the_cap() {
|
|
217
|
-
assert_eq!(checked_payload_len(0), Ok(0));
|
|
218
|
-
assert_eq!(
|
|
219
|
-
checked_payload_len(MAX_DISPATCH_PAYLOAD),
|
|
220
|
-
Ok(MAX_DISPATCH_PAYLOAD as i32)
|
|
221
|
-
);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// @behavior WE-060
|
|
225
|
-
#[test]
|
|
226
|
-
fn checked_payload_len_rejects_past_the_cap() {
|
|
227
|
-
assert!(checked_payload_len(MAX_DISPATCH_PAYLOAD + 1).is_err());
|
|
228
|
-
assert!(checked_payload_len(usize::MAX).is_err());
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// @behavior WE-061
|
|
232
|
-
#[test]
|
|
233
|
-
fn guest_buffer_range_returns_half_open_range() {
|
|
234
|
-
assert_eq!(guest_buffer_range(10, 5, 100), Ok(10..15));
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// @behavior WE-062
|
|
238
|
-
#[test]
|
|
239
|
-
fn guest_buffer_range_accepts_zero_length_at_any_in_bounds_ptr() {
|
|
240
|
-
assert_eq!(guest_buffer_range(0, 0, 0), Ok(0..0));
|
|
241
|
-
assert_eq!(guest_buffer_range(42, 0, 100), Ok(42..42));
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// @behavior WE-063
|
|
245
|
-
#[test]
|
|
246
|
-
fn guest_buffer_range_rejects_ptr_plus_len_overflow() {
|
|
247
|
-
assert!(guest_buffer_range(usize::MAX, 1, usize::MAX).is_err());
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// @behavior WE-064
|
|
251
|
-
#[test]
|
|
252
|
-
fn guest_buffer_range_rejects_end_past_memory() {
|
|
253
|
-
assert!(guest_buffer_range(10, 100, 50).is_err());
|
|
254
|
-
assert_eq!(guest_buffer_range(0, 50, 50), Ok(0..50));
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// @behavior WE-001
|
|
258
|
-
#[test]
|
|
259
|
-
fn unpack_guest_buffer_extracts_high_ptr_low_len() {
|
|
260
|
-
assert_eq!(
|
|
261
|
-
unpack_guest_buffer(0xAABB_CCDD_1122_3344),
|
|
262
|
-
(0xAABB_CCDD, 0x1122_3344)
|
|
263
|
-
);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// @behavior WE-065
|
|
267
|
-
#[test]
|
|
268
|
-
fn unpack_guest_buffer_zero_decodes_to_zero_pair() {
|
|
269
|
-
assert_eq!(unpack_guest_buffer(0), (0, 0));
|
|
270
|
-
}
|
|
271
|
-
}
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
//! Per-invocation I/O between the host and the guest: it writes the
|
|
2
|
-
//! entry's downcall into a freshly allocated guest buffer and reads the
|
|
3
|
-
//! Outcome the downcall answered with.
|
|
4
|
-
|
|
5
|
-
use wasmtime::Store as WtStore;
|
|
6
|
-
|
|
7
|
-
use crate::exports::Exports;
|
|
8
|
-
use crate::guest_mem::{self, ReadError, WriteError};
|
|
9
|
-
use crate::invocation::Invocation;
|
|
10
|
-
use kobako_runtime::error::{InvokeError, SetupError, Trap};
|
|
11
|
-
use kobako_transport::envelope::{CompileReply, Outcome};
|
|
12
|
-
|
|
13
|
-
/// A missing or trapping allocator is an engine fault, a `Trap`; one that
|
|
14
|
-
/// runs but returns 0 leaves the runtime intact, so it is a `SetupError`.
|
|
15
|
-
pub(crate) fn write_request(
|
|
16
|
-
store: &mut WtStore<Invocation>,
|
|
17
|
-
exports: &Exports,
|
|
18
|
-
envelope: &[u8],
|
|
19
|
-
) -> Result<(i32, i32), InvokeError> {
|
|
20
|
-
let trap = |message: String| -> InvokeError { Trap::Other(message).into() };
|
|
21
|
-
let ptr =
|
|
22
|
-
guest_mem::alloc_and_write(&mut *store, exports, envelope).map_err(|err| match err {
|
|
23
|
-
WriteError::OutOfMemory => {
|
|
24
|
-
SetupError::Intact("could not allocate input buffer (out of memory)".to_string())
|
|
25
|
-
.into()
|
|
26
|
-
}
|
|
27
|
-
WriteError::TooLarge => trap("payload exceeds the 16 MiB limit".to_string()),
|
|
28
|
-
WriteError::Incompatible(message) => trap(message.to_string()),
|
|
29
|
-
WriteError::Trapped(e) => crate::trap::cap_trap(&e)
|
|
30
|
-
.unwrap_or_else(|| Trap::Other(format!("failed to allocate input buffer: {e}")))
|
|
31
|
-
.into(),
|
|
32
|
-
WriteError::OutOfBounds => {
|
|
33
|
-
trap("the input buffer lies outside the Sandbox's memory".to_string())
|
|
34
|
-
}
|
|
35
|
-
})?;
|
|
36
|
-
// The write has already held the length to the payload cap.
|
|
37
|
-
Ok((ptr as i32, envelope.len() as i32))
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/// A guest that answered without an Outcome the envelope can frame — an
|
|
41
|
-
/// empty answer included — has nothing left to attribute, so it is a
|
|
42
|
-
/// `Trap` like every other way of failing to read the result.
|
|
43
|
-
pub(crate) fn read_outcome(
|
|
44
|
-
store: &WtStore<Invocation>,
|
|
45
|
-
exports: &Exports,
|
|
46
|
-
packed: u64,
|
|
47
|
-
) -> Result<Outcome, Trap> {
|
|
48
|
-
let bytes = read_answer(store, exports, packed)?;
|
|
49
|
-
Outcome::decode(&bytes).map_err(|_| {
|
|
50
|
-
Trap::Other(
|
|
51
|
-
if bytes.is_empty() {
|
|
52
|
-
"Sandbox exited without producing a result"
|
|
53
|
-
} else {
|
|
54
|
-
"Sandbox produced an unrecognised result"
|
|
55
|
-
}
|
|
56
|
-
.to_string(),
|
|
57
|
-
)
|
|
58
|
-
})
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/// Held to the same 16 MiB bound as an Outcome; an answer that does not
|
|
62
|
-
/// frame is a `Trap` the caller treats like any other failed compile.
|
|
63
|
-
pub(crate) fn read_compile_reply(
|
|
64
|
-
store: &WtStore<Invocation>,
|
|
65
|
-
exports: &Exports,
|
|
66
|
-
packed: u64,
|
|
67
|
-
) -> Result<CompileReply, Trap> {
|
|
68
|
-
let bytes = read_answer(store, exports, packed)?;
|
|
69
|
-
CompileReply::decode(&bytes)
|
|
70
|
-
.map_err(|_| Trap::Other("Sandbox produced an unrecognised compile answer".to_string()))
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
fn read_answer(
|
|
74
|
-
store: &WtStore<Invocation>,
|
|
75
|
-
exports: &Exports,
|
|
76
|
-
packed: u64,
|
|
77
|
-
) -> Result<Vec<u8>, Trap> {
|
|
78
|
-
let mem = exports.require_memory()?;
|
|
79
|
-
let (ptr, len) = guest_mem::unpack_guest_buffer(packed);
|
|
80
|
-
guest_mem::read_buffer(store, mem, ptr, len).map_err(|err| {
|
|
81
|
-
Trap::Other(
|
|
82
|
-
match err {
|
|
83
|
-
ReadError::TooLarge => "result payload exceeds the 16 MiB limit",
|
|
84
|
-
ReadError::OutOfBounds => "the Sandbox result is out of bounds",
|
|
85
|
-
}
|
|
86
|
-
.to_string(),
|
|
87
|
-
)
|
|
88
|
-
})
|
|
89
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
//! The checks preview1 makes on an enum or flags argument before a call
|
|
2
|
-
//! looks at its descriptor: a value outside what the witx defines traps,
|
|
3
|
-
//! as wasmtime-wasi's generated bindings refuse to convert it.
|
|
4
|
-
|
|
5
|
-
use super::errno::{Answer, Errno, Failure};
|
|
6
|
-
|
|
7
|
-
/// The bits of `fdflags`.
|
|
8
|
-
pub(crate) const FDFLAGS: u64 = 0x1f;
|
|
9
|
-
/// The bits of `rights`.
|
|
10
|
-
pub(crate) const RIGHTS: u64 = (1 << 30) - 1;
|
|
11
|
-
/// The bits of `lookupflags`.
|
|
12
|
-
pub(crate) const LOOKUPFLAGS: u64 = 0x1;
|
|
13
|
-
/// The bits of `oflags`.
|
|
14
|
-
pub(crate) const OFLAGS: u64 = 0xf;
|
|
15
|
-
/// The bits of `riflags`.
|
|
16
|
-
pub(crate) const RIFLAGS: u64 = 0x3;
|
|
17
|
-
/// The bits of `sdflags`.
|
|
18
|
-
pub(crate) const SDFLAGS: u64 = 0x3;
|
|
19
|
-
|
|
20
|
-
pub(crate) const FDFLAGS_APPEND: u32 = 1 << 0;
|
|
21
|
-
pub(crate) const FDFLAGS_NONBLOCK: u32 = 1 << 2;
|
|
22
|
-
/// The `fdflags` asking each write to be synchronized: `dsync`, `rsync`,
|
|
23
|
-
/// and `sync`.
|
|
24
|
-
pub(crate) const FDFLAGS_SYNCHRONIZED: u32 = (1 << 1) | (1 << 3) | (1 << 4);
|
|
25
|
-
pub(crate) const RIGHTS_FD_READ: u64 = 1 << 1;
|
|
26
|
-
pub(crate) const RIGHTS_FD_WRITE: u64 = 1 << 6;
|
|
27
|
-
|
|
28
|
-
/// The highest `advice` variant.
|
|
29
|
-
pub(crate) const ADVICE_MAX: u32 = 5;
|
|
30
|
-
/// The highest `whence` variant.
|
|
31
|
-
pub(crate) const WHENCE_MAX: u32 = 2;
|
|
32
|
-
|
|
33
|
-
pub(crate) fn flags(value: impl Into<u64>, defined: u64) -> Answer {
|
|
34
|
-
if value.into() & !defined != 0 {
|
|
35
|
-
return Err(Failure::trap("flags value outside what the witx defines"));
|
|
36
|
-
}
|
|
37
|
-
Ok(())
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
pub(crate) fn variant(value: u32, max: u32) -> Answer {
|
|
41
|
-
if value > max {
|
|
42
|
-
return Err(Failure::trap("enum value outside what the witx defines"));
|
|
43
|
-
}
|
|
44
|
-
Ok(())
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/// `fstflags`, where setting a timestamp and setting it to now are
|
|
48
|
-
/// exclusive: a defined value asking both is `INVAL`.
|
|
49
|
-
pub(crate) fn fstflags(value: u32) -> Answer {
|
|
50
|
-
const ATIM: u32 = 1 << 0;
|
|
51
|
-
const ATIM_NOW: u32 = 1 << 1;
|
|
52
|
-
const MTIM: u32 = 1 << 2;
|
|
53
|
-
const MTIM_NOW: u32 = 1 << 3;
|
|
54
|
-
flags(value, 0xf)?;
|
|
55
|
-
let both = |set, now| value & set != 0 && value & now != 0;
|
|
56
|
-
if both(ATIM, ATIM_NOW) || both(MTIM, MTIM_NOW) {
|
|
57
|
-
return Err(Errno::Inval.into());
|
|
58
|
-
}
|
|
59
|
-
Ok(())
|
|
60
|
-
}
|
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
//! Resolving a guest path beneath a directory descriptor.
|
|
2
|
-
//!
|
|
3
|
-
//! The runtime is the boundary: a path never climbs above the directory
|
|
4
|
-
//! descriptor it is resolved beneath, as a capability never widens, and
|
|
5
|
-
//! the Mount is asked what the guest may do there before the Filesystem
|
|
6
|
-
//! is, so a Filesystem of a third party's need check neither.
|
|
7
|
-
//! A `..` steps out of what the Filesystem holds rather than out of the
|
|
8
|
-
//! path's text, as a host resolves it, so `missing/..` names nothing and
|
|
9
|
-
//! a `..` out of a link is refused rather than taken from its text. A link
|
|
10
|
-
//! is followed only within the directory the descriptor opened, as
|
|
11
|
-
//! wasmtime-wasi's cap-std `Dir` follows one.
|
|
12
|
-
|
|
13
|
-
use kobako_runtime::filesystem::{FileType, Filesystem, FilesystemError, Metadata};
|
|
14
|
-
|
|
15
|
-
use super::errno::{Answer, Errno};
|
|
16
|
-
use crate::descriptors::{Identity, OpenDirectory};
|
|
17
|
-
|
|
18
|
-
/// Linux's limits on a path, its terminating NUL counted, and on one name
|
|
19
|
-
/// within it.
|
|
20
|
-
pub(crate) const PATH_MAX: usize = 4096;
|
|
21
|
-
pub(crate) const NAME_MAX: usize = 255;
|
|
22
|
-
|
|
23
|
-
/// A guest path resolved beneath a directory: relative to the mount's
|
|
24
|
-
/// root, and whether it ended in `/` or `/.`, which only a directory
|
|
25
|
-
/// satisfies.
|
|
26
|
-
pub(crate) struct ResolvedPath {
|
|
27
|
-
pub(crate) path: String,
|
|
28
|
-
pub(crate) names_directory: bool,
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
impl ResolvedPath {
|
|
32
|
-
/// The path, refused as `NOTDIR` when it must name a directory and
|
|
33
|
-
/// `lookup` finds anything else there: `metadata` for a call that
|
|
34
|
-
/// follows a link at the end, `symlink_metadata` for one that acts on
|
|
35
|
-
/// the link itself.
|
|
36
|
-
pub(crate) fn check_directory(
|
|
37
|
-
self,
|
|
38
|
-
filesystem: &(dyn Filesystem + 'static),
|
|
39
|
-
lookup: fn(&(dyn Filesystem + 'static), &str) -> Result<Metadata, FilesystemError>,
|
|
40
|
-
) -> Answer<String> {
|
|
41
|
-
if self.names_directory {
|
|
42
|
-
match lookup(filesystem, &self.path) {
|
|
43
|
-
Ok(metadata) if metadata.file_type != FileType::Directory => {
|
|
44
|
-
return Err(Errno::Notdir.into())
|
|
45
|
-
}
|
|
46
|
-
Ok(_) | Err(FilesystemError::NotFound) => {}
|
|
47
|
-
Err(err) => return Err(err.into()),
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
Ok(self.path)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/// The path with a link standing at its end followed within the
|
|
54
|
-
/// directory `dir` opened, for a call following it. A link pointing
|
|
55
|
-
/// at nothing there is `LOOP` to a call that `creates`, since what it
|
|
56
|
-
/// would create could lie outside `dir`, and `NOENT` otherwise.
|
|
57
|
-
pub(crate) fn follow_last(self, dir: &OpenDirectory, creates: bool) -> Answer<Self> {
|
|
58
|
-
let Some(base) = bounded_base(dir) else {
|
|
59
|
-
return Ok(self);
|
|
60
|
-
};
|
|
61
|
-
let filesystem = dir.mount.filesystem();
|
|
62
|
-
let Some(rest) = beneath_base(base, &self.path) else {
|
|
63
|
-
return Ok(self);
|
|
64
|
-
};
|
|
65
|
-
match filesystem.symlink_metadata(&self.path) {
|
|
66
|
-
Ok(metadata) if metadata.file_type == FileType::Symlink => {}
|
|
67
|
-
_ => return Ok(self),
|
|
68
|
-
}
|
|
69
|
-
let path = match filesystem.canonicalize_beneath(base, rest) {
|
|
70
|
-
Ok(found) => join(base, &found),
|
|
71
|
-
Err(FilesystemError::NotFound) if creates => return Err(Errno::Loop.into()),
|
|
72
|
-
Err(err) => return Err(err.into()),
|
|
73
|
-
};
|
|
74
|
-
Ok(Self { path, ..self })
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/// The directory a descriptor's links are kept within, or `None` where
|
|
79
|
-
/// the Filesystem keeps them within the Mount's root already, or a Mount
|
|
80
|
-
/// follows no link at all.
|
|
81
|
-
fn bounded_base(dir: &OpenDirectory) -> Option<&str> {
|
|
82
|
-
(!dir.path.is_empty() && dir.mount.follows_links()).then_some(dir.path.as_str())
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/// What `path` names beneath `base`, or `None` for `base` itself.
|
|
86
|
-
fn beneath_base<'a>(base: &str, path: &'a str) -> Option<&'a str> {
|
|
87
|
-
path.strip_prefix(base)?.strip_prefix('/')
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
fn join(base: &str, path: &str) -> String {
|
|
91
|
-
if path.is_empty() {
|
|
92
|
-
base.to_string()
|
|
93
|
-
} else {
|
|
94
|
-
format!("{base}/{path}")
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/// `path` with the links on its way to its last name followed within the
|
|
99
|
-
/// directory `dir` opened, so none leads the call out of it. A way the
|
|
100
|
-
/// Filesystem finds nothing on stays as given, for the call to answer.
|
|
101
|
-
fn within(dir: &OpenDirectory, path: String) -> Answer<String> {
|
|
102
|
-
let Some(base) = bounded_base(dir) else {
|
|
103
|
-
return Ok(path);
|
|
104
|
-
};
|
|
105
|
-
let Some((way, name)) = beneath_base(base, &path).and_then(|rest| rest.rsplit_once('/')) else {
|
|
106
|
-
return Ok(path);
|
|
107
|
-
};
|
|
108
|
-
match dir.mount.filesystem().canonicalize_beneath(base, way) {
|
|
109
|
-
Ok(found) => Ok(join(&join(base, &found), name)),
|
|
110
|
-
Err(FilesystemError::NotFound | FilesystemError::NotADirectory) => Ok(path),
|
|
111
|
-
Err(err) => Err(err.into()),
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/// `path`, beneath `dir`. An empty path names nothing; an absolute path or
|
|
116
|
-
/// a `..` above `dir` itself is not the guest's to reach; a `..` or a final `.`
|
|
117
|
-
/// after a component that is missing, no directory, or a link is `NOENT`,
|
|
118
|
-
/// `NOTDIR`, or `LOOP`. A
|
|
119
|
-
/// name past `NAME_MAX`, or a resolved path the Filesystem could not be
|
|
120
|
-
/// handed within `PATH_MAX`, is `NAMETOOLONG`.
|
|
121
|
-
pub(crate) fn resolve(dir: &OpenDirectory, path: &str) -> Answer<ResolvedPath> {
|
|
122
|
-
confirm(dir)?;
|
|
123
|
-
if path.is_empty() {
|
|
124
|
-
return Err(Errno::Noent.into());
|
|
125
|
-
}
|
|
126
|
-
if path.starts_with('/') {
|
|
127
|
-
return Err(Errno::Notcapable.into());
|
|
128
|
-
}
|
|
129
|
-
let mut components: Vec<&str> = dir.path.split('/').filter(|c| !c.is_empty()).collect();
|
|
130
|
-
let opened = components.len();
|
|
131
|
-
let mut last = "";
|
|
132
|
-
for component in path.split('/') {
|
|
133
|
-
last = component;
|
|
134
|
-
match component {
|
|
135
|
-
"" | "." => {}
|
|
136
|
-
".." => {
|
|
137
|
-
if components.len() <= opened {
|
|
138
|
-
return Err(Errno::Notcapable.into());
|
|
139
|
-
}
|
|
140
|
-
step_out_of(dir.mount.filesystem().as_ref(), &components.join("/"))?;
|
|
141
|
-
components.pop();
|
|
142
|
-
}
|
|
143
|
-
component if component.len() > NAME_MAX => return Err(Errno::Nametoolong.into()),
|
|
144
|
-
component => components.push(component),
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
let path = components.join("/");
|
|
148
|
-
if path.len() >= PATH_MAX {
|
|
149
|
-
return Err(Errno::Nametoolong.into());
|
|
150
|
-
}
|
|
151
|
-
// A final `.` resolves through the name before it, as a `..` does.
|
|
152
|
-
if last == "." && components.len() > opened {
|
|
153
|
-
step_out_of(dir.mount.filesystem().as_ref(), &path)?;
|
|
154
|
-
}
|
|
155
|
-
Ok(ResolvedPath {
|
|
156
|
-
path: within(dir, path)?,
|
|
157
|
-
names_directory: matches!(last, "" | "."),
|
|
158
|
-
})
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/// `dir` still stands at its path, `NOENT` once another directory or
|
|
162
|
-
/// nothing does, as a POSIX descriptor never reaches a directory made in
|
|
163
|
-
/// its place. A directory opened with no identity follows its path alone.
|
|
164
|
-
pub(crate) fn confirm(dir: &OpenDirectory) -> Answer {
|
|
165
|
-
let Some(identity) = dir.identity else {
|
|
166
|
-
return Ok(());
|
|
167
|
-
};
|
|
168
|
-
match dir.mount.filesystem().metadata(&dir.path) {
|
|
169
|
-
Ok(found) if Identity::of(&found) == Some(identity) => Ok(()),
|
|
170
|
-
Ok(_) | Err(FilesystemError::NotFound | FilesystemError::NotADirectory) => {
|
|
171
|
-
Err(Errno::Noent.into())
|
|
172
|
-
}
|
|
173
|
-
Err(err) => Err(err.into()),
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/// A `..` leaves `path` only when it is a directory the Filesystem holds.
|
|
178
|
-
/// A host leaves a link by the parent of where it points, which no guest
|
|
179
|
-
/// is told, so a link there is refused as `LOOP`, as `openat2(2)`
|
|
180
|
-
/// refuses a link it may not follow.
|
|
181
|
-
fn step_out_of(filesystem: &dyn Filesystem, path: &str) -> Answer {
|
|
182
|
-
match filesystem.symlink_metadata(path) {
|
|
183
|
-
Ok(metadata) if metadata.file_type == FileType::Directory => Ok(()),
|
|
184
|
-
Ok(metadata) if metadata.file_type == FileType::Symlink => Err(Errno::Loop.into()),
|
|
185
|
-
Ok(_) => Err(Errno::Notdir.into()),
|
|
186
|
-
Err(err) => Err(err.into()),
|
|
187
|
-
}
|
|
188
|
-
}
|
|
File without changes
|
|
File without changes
|