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,21 @@
|
|
|
1
|
+
//! Paths beneath a Filesystem's root, components joined by `/` and the
|
|
2
|
+
//! empty path being the root: the files a Host App seeds a Tmpfs with.
|
|
3
|
+
|
|
4
|
+
/// The last name of `path`, within its parent.
|
|
5
|
+
pub(crate) fn name(path: &str) -> &str {
|
|
6
|
+
path.rsplit_once('/').map_or(path, |(_, name)| name)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/// Every directory above `path`, outermost first, the root left out.
|
|
10
|
+
pub(crate) fn ancestors(path: &str) -> impl Iterator<Item = &str> {
|
|
11
|
+
path.match_indices('/').map(move |(end, _)| &path[..end])
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/// A path a Host App seeds a Filesystem with, in the form a runtime hands
|
|
15
|
+
/// over: no leading `/`, and no empty or `.` component.
|
|
16
|
+
pub(crate) fn normalize_seed(path: &str) -> String {
|
|
17
|
+
path.split('/')
|
|
18
|
+
.filter(|component| !component.is_empty() && *component != ".")
|
|
19
|
+
.collect::<Vec<_>>()
|
|
20
|
+
.join("/")
|
|
21
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
//! The in-memory pipes an invocation's standard streams run over, as Linux's
|
|
2
|
+
//! pipefs holds anonymous pipes. Any ABI a Wasm Driver encodes reads the Preamble
|
|
3
|
+
//! and writes a Channel through them, so none brings its own, and a pipe's two
|
|
4
|
+
//! ends are open files like any other.
|
|
5
|
+
|
|
6
|
+
use std::sync::{Arc, LazyLock, Mutex, PoisonError};
|
|
7
|
+
|
|
8
|
+
use crate::vfs::{
|
|
9
|
+
alloc_file_pseudo, Cred, Dentry, FMode, File, FileOperations, FileSystemType, FileType,
|
|
10
|
+
FilesystemError, Inode, InodeOperations, Kiocb, Kstat, OpenHow, PollMask, SuperBlock, VfsMount,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/// The one Filesystem every pipe stands on, as Linux's `pipe_mnt`. No path
|
|
14
|
+
/// reaches it: a pipe is only ever open.
|
|
15
|
+
static PIPE_MNT: LazyLock<Arc<VfsMount>> = LazyLock::new(|| {
|
|
16
|
+
let sb = SuperBlock::new(Arc::new(PipeFs)).expect("pipefs fills in nothing that can fail");
|
|
17
|
+
VfsMount::kern_mount(sb)
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
/// A pipe reports what it is and no length, as `get_pipe_inode` makes one.
|
|
21
|
+
struct PipeFs;
|
|
22
|
+
|
|
23
|
+
impl FileSystemType for PipeFs {
|
|
24
|
+
fn fill_super(self: Arc<Self>, sb: &mut SuperBlock) -> Result<(), FilesystemError> {
|
|
25
|
+
sb.d_make_root(Inode::new(&Kstat::new(FileType::Dir, 0), self));
|
|
26
|
+
Ok(())
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
impl InodeOperations for PipeFs {
|
|
31
|
+
fn getattr(&self, _dentry: &Dentry) -> Result<Kstat, FilesystemError> {
|
|
32
|
+
Ok(Kstat::new(FileType::Fifo, 0))
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/// What a pipe holds, as Linux's `pipe_inode_info`: what was written and not
|
|
37
|
+
/// yet read, up to a capacity.
|
|
38
|
+
pub struct PipeInodeInfo {
|
|
39
|
+
state: Mutex<PipeState>,
|
|
40
|
+
capacity: usize,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
struct PipeState {
|
|
44
|
+
bytes: Vec<u8>,
|
|
45
|
+
read: usize,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
impl PipeInodeInfo {
|
|
49
|
+
/// An empty pipe holding at most `capacity` bytes.
|
|
50
|
+
pub fn new(capacity: usize) -> Arc<Self> {
|
|
51
|
+
Self::with_bytes(Vec::new(), capacity)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// A pipe already holding `bytes`, as the Preamble's stream does, with no
|
|
55
|
+
/// room for more.
|
|
56
|
+
pub fn from_bytes(bytes: Vec<u8>) -> Arc<Self> {
|
|
57
|
+
let capacity = bytes.len();
|
|
58
|
+
Self::with_bytes(bytes, capacity)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
fn with_bytes(bytes: Vec<u8>, capacity: usize) -> Arc<Self> {
|
|
62
|
+
Arc::new(Self {
|
|
63
|
+
state: Mutex::new(PipeState { bytes, read: 0 }),
|
|
64
|
+
capacity,
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/// Everything written so far, as a Channel's capture reads it back.
|
|
69
|
+
pub fn contents(&self) -> Vec<u8> {
|
|
70
|
+
self.state().bytes.clone()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
fn state(&self) -> std::sync::MutexGuard<'_, PipeState> {
|
|
74
|
+
self.state.lock().unwrap_or_else(PoisonError::into_inner)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/// What answers for either end of a pipe, as Linux's `pipeanon_fops`.
|
|
79
|
+
struct AnonPipe(Arc<PipeInodeInfo>);
|
|
80
|
+
|
|
81
|
+
impl FileOperations for AnonPipe {
|
|
82
|
+
/// A pipe has no offsets, so a read takes what comes next. A drained pipe
|
|
83
|
+
/// reads nothing, as a closed writer's does, and so does a read into no
|
|
84
|
+
/// room, as `pipe_read` answers one.
|
|
85
|
+
fn read_iter(&mut self, _iocb: &mut Kiocb, buf: &mut [u8]) -> Result<usize, FilesystemError> {
|
|
86
|
+
let mut state = self.0.state();
|
|
87
|
+
let start = state.read;
|
|
88
|
+
let rest = &state.bytes[start..];
|
|
89
|
+
let take = rest.len().min(buf.len());
|
|
90
|
+
buf[..take].copy_from_slice(&rest[..take]);
|
|
91
|
+
state.read += take;
|
|
92
|
+
Ok(take)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/// A pipe has no offsets, so a write lands after the last. A write past
|
|
96
|
+
/// the capacity keeps what fit and answers a failure, which is how a
|
|
97
|
+
/// capture one past its limit learns it was truncated.
|
|
98
|
+
fn write_iter(&mut self, _iocb: &mut Kiocb, buf: &[u8]) -> Result<usize, FilesystemError> {
|
|
99
|
+
let mut state = self.0.state();
|
|
100
|
+
let take = buf.len().min(self.0.capacity - state.bytes.len());
|
|
101
|
+
state.bytes.extend_from_slice(&buf[..take]);
|
|
102
|
+
if take < buf.len() {
|
|
103
|
+
return Err(FilesystemError::Other("the pipe is full".to_string()));
|
|
104
|
+
}
|
|
105
|
+
Ok(take)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/// Each end is ready for what it was opened for, as `pipe_poll` reads
|
|
109
|
+
/// `f_mode`; nothing here waits for the other end.
|
|
110
|
+
fn poll(&self, mode: FMode) -> PollMask {
|
|
111
|
+
match mode.contains(FMode::READ) {
|
|
112
|
+
true => PollMask::IN,
|
|
113
|
+
false => PollMask::OUT,
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/// Neither end takes a position, as `create_pipe_files` calls
|
|
118
|
+
/// `stream_open` on both.
|
|
119
|
+
fn is_stream(&self) -> bool {
|
|
120
|
+
true
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/// The read and the write end of `pipe`, opened as `cred`, as Linux's
|
|
125
|
+
/// `create_pipe_files` opens both on one inode as the calling task. Neither
|
|
126
|
+
/// takes a position.
|
|
127
|
+
pub fn create_pipe_files(pipe: &Arc<PipeInodeInfo>, cred: &Arc<Cred>) -> (File, File) {
|
|
128
|
+
let inode = Inode::new(&Kstat::new(FileType::Fifo, 0), Arc::new(PipeFs));
|
|
129
|
+
let write = alloc_file_pseudo(
|
|
130
|
+
inode,
|
|
131
|
+
&PIPE_MNT,
|
|
132
|
+
&OpenHow {
|
|
133
|
+
write: true,
|
|
134
|
+
..OpenHow::default()
|
|
135
|
+
},
|
|
136
|
+
Box::new(AnonPipe(pipe.clone())),
|
|
137
|
+
cred,
|
|
138
|
+
);
|
|
139
|
+
let read = write.alloc_file_clone(
|
|
140
|
+
&OpenHow {
|
|
141
|
+
read: true,
|
|
142
|
+
..OpenHow::default()
|
|
143
|
+
},
|
|
144
|
+
Box::new(AnonPipe(pipe.clone())),
|
|
145
|
+
);
|
|
146
|
+
(read, write)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
#[cfg(test)]
|
|
150
|
+
mod tests {
|
|
151
|
+
use super::*;
|
|
152
|
+
|
|
153
|
+
#[test]
|
|
154
|
+
fn a_reader_reads_in_order_then_nothing() {
|
|
155
|
+
let (mut reader, _) =
|
|
156
|
+
create_pipe_files(&PipeInodeInfo::from_bytes(b"abc".to_vec()), &Arc::default());
|
|
157
|
+
let mut buf = [0; 2];
|
|
158
|
+
assert_eq!(
|
|
159
|
+
(
|
|
160
|
+
reader.read(&mut buf),
|
|
161
|
+
buf,
|
|
162
|
+
reader.read(&mut buf),
|
|
163
|
+
reader.read(&mut buf)
|
|
164
|
+
),
|
|
165
|
+
(Ok(2), *b"ab", Ok(1), Ok(0)),
|
|
166
|
+
"File::read of a pipe's read end holding three bytes in steps of two must take them \
|
|
167
|
+
in order, then nothing"
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// @behavior RT-217
|
|
172
|
+
#[test]
|
|
173
|
+
fn a_read_into_no_room_reads_nothing() {
|
|
174
|
+
let (mut holding, _) =
|
|
175
|
+
create_pipe_files(&PipeInodeInfo::from_bytes(b"a".to_vec()), &Arc::default());
|
|
176
|
+
let mut byte = [0];
|
|
177
|
+
assert_eq!(
|
|
178
|
+
(holding.read(&mut []), holding.read(&mut byte), byte),
|
|
179
|
+
(Ok(0), Ok(1), *b"a"),
|
|
180
|
+
"File::read of a pipe into no room must read nothing and leave its bytes for the next"
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
#[test]
|
|
185
|
+
fn a_writer_keeps_what_fits_and_fails_past_its_capacity() {
|
|
186
|
+
let pipe = PipeInodeInfo::new(3);
|
|
187
|
+
let (_, mut writer) = create_pipe_files(&pipe, &Arc::default());
|
|
188
|
+
assert_eq!(
|
|
189
|
+
(
|
|
190
|
+
writer.write(b"ab").ok(),
|
|
191
|
+
writer.write(b"cd").is_err(),
|
|
192
|
+
pipe.contents()
|
|
193
|
+
),
|
|
194
|
+
(Some(2), true, b"abc".to_vec()),
|
|
195
|
+
"File::write past a pipe's capacity must keep what fit and fail, and \
|
|
196
|
+
PipeInodeInfo::contents must read what was written"
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
#[test]
|
|
201
|
+
fn a_pipe_takes_no_position() {
|
|
202
|
+
let (reader, writer) = create_pipe_files(&PipeInodeInfo::new(1), &Arc::default());
|
|
203
|
+
for end in [reader, writer] {
|
|
204
|
+
assert!(
|
|
205
|
+
end.is_stream() && !end.mode().contains(FMode::LSEEK),
|
|
206
|
+
"create_pipe_files must open each end as a stream with no position"
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// @behavior RT-198
|
|
212
|
+
#[test]
|
|
213
|
+
fn a_pipe_reports_a_fifo() {
|
|
214
|
+
let (reader, _writer) = create_pipe_files(&PipeInodeInfo::new(1), &Arc::default());
|
|
215
|
+
assert_eq!(
|
|
216
|
+
reader.getattr().map(|metadata| metadata.file_type).ok(),
|
|
217
|
+
Some(FileType::Fifo),
|
|
218
|
+
"File::getattr of an end create_pipe_files made must report a FIFO"
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
//! What a Filesystem reading a whole directory at once holds while a listing
|
|
2
|
+
//! goes on, as 9p's `fid->rdir` holds what its server sent, so a caller reading in
|
|
3
|
+
//! small steps costs one listing, not one per step.
|
|
4
|
+
|
|
5
|
+
use std::sync::Arc;
|
|
6
|
+
|
|
7
|
+
use crate::vfs::{Cred, Dentry, DirContext, DirEntry, FileType, FilesystemError};
|
|
8
|
+
|
|
9
|
+
/// The entries a listing began with, `.` and `..` first, on the open directory
|
|
10
|
+
/// until it reaches its end, counted against who holds them.
|
|
11
|
+
#[derive(Default)]
|
|
12
|
+
pub struct Rdir(Option<Listing>);
|
|
13
|
+
|
|
14
|
+
struct Listing {
|
|
15
|
+
entries: Vec<DirEntry>,
|
|
16
|
+
/// Gives the entries' bytes back once the listing goes.
|
|
17
|
+
_charge: Charge,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/// Bytes a listing counts for as long as it holds them, as shmem pairs
|
|
21
|
+
/// `shmem_acct_size` with `shmem_unacct_size`.
|
|
22
|
+
struct Charge {
|
|
23
|
+
cred: Arc<Cred>,
|
|
24
|
+
bytes: usize,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
impl Charge {
|
|
28
|
+
fn account(cred: &Arc<Cred>, entries: &[DirEntry]) -> Result<Self, FilesystemError> {
|
|
29
|
+
let names: usize = entries.iter().map(|entry| entry.name.capacity()).sum();
|
|
30
|
+
let bytes = size_of_val(entries) + names;
|
|
31
|
+
cred.security_vm_enough_memory(bytes)?;
|
|
32
|
+
Ok(Self {
|
|
33
|
+
cred: cred.clone(),
|
|
34
|
+
bytes,
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
impl Drop for Charge {
|
|
40
|
+
fn drop(&mut self) {
|
|
41
|
+
self.cred.vm_unacct_memory(self.bytes);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
impl Rdir {
|
|
46
|
+
/// Hand `ctx` the directory from its position. A listing from the start
|
|
47
|
+
/// reads the directory through `read`, then stats it and what stands above
|
|
48
|
+
/// it for `.` and `..`, as 9p and FUSE take those from their server and
|
|
49
|
+
/// `ovl_cache_update` asks `vfs_getattr`; a failure of either answers before
|
|
50
|
+
/// any entry. A listing going on reads what that listing began with. Those
|
|
51
|
+
/// entries count against the caller from the moment they are read until the
|
|
52
|
+
/// listing ends, as `ovl_cache_get` and `v9fs_alloc_rdir_buf` allocate before
|
|
53
|
+
/// the first `dir_emit`: past what a security module allows, the listing
|
|
54
|
+
/// answers out of memory before it hands any entry.
|
|
55
|
+
pub fn iterate(
|
|
56
|
+
&mut self,
|
|
57
|
+
dentry: &Dentry,
|
|
58
|
+
ctx: &mut DirContext<'_>,
|
|
59
|
+
read: impl FnOnce() -> Result<Vec<DirEntry>, FilesystemError>,
|
|
60
|
+
) -> Result<(), FilesystemError> {
|
|
61
|
+
let listing = match self.0.take().filter(|_| ctx.pos() != 0) {
|
|
62
|
+
Some(held) => held,
|
|
63
|
+
None => {
|
|
64
|
+
let read = read()?;
|
|
65
|
+
let mut entries = dots(dentry)?;
|
|
66
|
+
entries.extend(read);
|
|
67
|
+
entries.shrink_to_fit();
|
|
68
|
+
let _charge = Charge::account(ctx.cred(), &entries)?;
|
|
69
|
+
Listing { entries, _charge }
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
if !emit_from(ctx, &listing.entries) {
|
|
73
|
+
self.0 = Some(listing);
|
|
74
|
+
}
|
|
75
|
+
Ok(())
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/// `.` and `..` as a stat of `dentry` and of what stands above it reports them,
|
|
80
|
+
/// `..` naming `dentry` itself at the root.
|
|
81
|
+
fn dots(dentry: &Dentry) -> Result<Vec<DirEntry>, FilesystemError> {
|
|
82
|
+
let dot = dentry.getattr()?;
|
|
83
|
+
let dotdot = dentry.parent().getattr()?;
|
|
84
|
+
Ok(vec![
|
|
85
|
+
DirEntry::new(".", FileType::Dir).with_identity(dot.dev, dot.ino),
|
|
86
|
+
DirEntry::new("..", FileType::Dir).with_identity(dotdot.dev, dotdot.ino),
|
|
87
|
+
])
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/// Hand `ctx` `entries` from where it stands, each at its index, answering
|
|
91
|
+
/// whether it took them all.
|
|
92
|
+
fn emit_from(ctx: &mut DirContext<'_>, entries: &[DirEntry]) -> bool {
|
|
93
|
+
let skip = usize::try_from(ctx.pos()).unwrap_or(usize::MAX);
|
|
94
|
+
entries.iter().skip(skip).all(|entry| {
|
|
95
|
+
let took = ctx.dir_emit(entry);
|
|
96
|
+
if took {
|
|
97
|
+
ctx.set_pos(ctx.pos() + 1);
|
|
98
|
+
}
|
|
99
|
+
took
|
|
100
|
+
})
|
|
101
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//! A file made with no name and named once whole, as tmpfs's
|
|
2
|
+
//! `shmem_tmpfile` and `linkat` let overlayfs copy a file up unseen, and
|
|
3
|
+
//! the times set on what it copies.
|
|
4
|
+
|
|
5
|
+
use super::super::{FilesystemError, Iattr};
|
|
6
|
+
use super::{lock, Content, Node, Tmpfs, TmpfsFile};
|
|
7
|
+
|
|
8
|
+
impl Tmpfs {
|
|
9
|
+
/// An empty file with no name yet, as `O_TMPFILE` opens one. Dropped
|
|
10
|
+
/// unnamed, it gives its room back.
|
|
11
|
+
pub(crate) fn tmpfile(&self) -> Result<TmpfsFile, FilesystemError> {
|
|
12
|
+
Ok(TmpfsFile {
|
|
13
|
+
data: Content::create(&self.tree)?,
|
|
14
|
+
})
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/// Name `file` `name` in this directory, as `simple_link` names a file
|
|
18
|
+
/// `O_TMPFILE` opened once `vfs_link` found the name free.
|
|
19
|
+
pub(crate) fn link(&self, file: &TmpfsFile, name: &str) -> Result<(), FilesystemError> {
|
|
20
|
+
self.dir.state().insert(name, Node::File(file.data.clone()));
|
|
21
|
+
Ok(())
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
impl TmpfsFile {
|
|
26
|
+
/// Set the times `attr` names on a file with no name yet, as
|
|
27
|
+
/// `shmem_setattr` takes them where overlayfs sets the temporary file's
|
|
28
|
+
/// times before naming it.
|
|
29
|
+
pub(crate) fn setattr(&self, attr: &Iattr) {
|
|
30
|
+
lock(&self.data).times.apply(attr);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
//! The whiteout a Tmpfs holds for an Overlay, and the moves that place
|
|
2
|
+
//! one, as tmpfs's `shmem_whiteout` makes a character device 0:0 and
|
|
3
|
+
//! `shmem_rename2` takes `RENAME_NOREPLACE`, `RENAME_EXCHANGE`, and
|
|
4
|
+
//! `RENAME_WHITEOUT`.
|
|
5
|
+
|
|
6
|
+
use std::sync::Arc;
|
|
7
|
+
|
|
8
|
+
use super::super::FilesystemError;
|
|
9
|
+
use super::{Node, Space, Tmpfs};
|
|
10
|
+
use crate::vfs::RenameFlag;
|
|
11
|
+
|
|
12
|
+
/// A whiteout, counting one inode for as long as it stands, as the
|
|
13
|
+
/// character device tmpfs makes for one does.
|
|
14
|
+
pub(super) struct Whiteout {
|
|
15
|
+
space: Arc<Space>,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
impl Drop for Whiteout {
|
|
19
|
+
fn drop(&mut self) {
|
|
20
|
+
self.space.inodes.give(1);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
impl Tmpfs {
|
|
25
|
+
fn new_whiteout(&self) -> Result<Node, FilesystemError> {
|
|
26
|
+
self.tree.space.inodes.take(1)?;
|
|
27
|
+
Ok(Node::Whiteout {
|
|
28
|
+
_inode: Arc::new(Whiteout {
|
|
29
|
+
space: self.tree.space.clone(),
|
|
30
|
+
}),
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/// Whether a whiteout stands at `name`, as overlayfs's
|
|
35
|
+
/// `ovl_is_whiteout` reads a character device 0:0.
|
|
36
|
+
pub(crate) fn is_whiteout(&self, name: &str) -> bool {
|
|
37
|
+
matches!(self.dir.get(name), Ok(Node::Whiteout { .. }))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// Make a whiteout at `name`, as `shmem_mknod` makes the character
|
|
41
|
+
/// device 0:0 `vfs_whiteout` asks for.
|
|
42
|
+
pub(crate) fn whiteout(&self, name: &str) -> Result<(), FilesystemError> {
|
|
43
|
+
let whiteout = self.new_whiteout()?;
|
|
44
|
+
self.dir.state().insert(name, whiteout);
|
|
45
|
+
Ok(())
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/// Move `from` to `to` in `target` as `shmem_rename2` does with
|
|
49
|
+
/// `flag`, the entry taking all it holds, so no name beneath it
|
|
50
|
+
/// changes. Without one, what stands at `to` goes as rename(2)
|
|
51
|
+
/// replaces it, once the VFS found the move fits what stands on each
|
|
52
|
+
/// side.
|
|
53
|
+
pub(super) fn rename_at(
|
|
54
|
+
&self,
|
|
55
|
+
from: &str,
|
|
56
|
+
target: &Tmpfs,
|
|
57
|
+
to: &str,
|
|
58
|
+
flag: Option<RenameFlag>,
|
|
59
|
+
) -> Result<(), FilesystemError> {
|
|
60
|
+
let (from_dir, to_dir) = (&self.dir, &target.dir);
|
|
61
|
+
let moving = from_dir.get(from)?;
|
|
62
|
+
let replaced = to_dir.get(to).ok();
|
|
63
|
+
if flag == Some(RenameFlag::Exchange) {
|
|
64
|
+
let replaced = replaced.ok_or(FilesystemError::NotFound)?;
|
|
65
|
+
from_dir.state().exchange(from, replaced);
|
|
66
|
+
to_dir.state().exchange(to, moving);
|
|
67
|
+
return Ok(());
|
|
68
|
+
}
|
|
69
|
+
if let Some(Node::Directory(dir)) = &replaced {
|
|
70
|
+
if !dir.state().entries.is_empty() {
|
|
71
|
+
return Err(FilesystemError::DirectoryNotEmpty);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
let left = match flag {
|
|
75
|
+
Some(RenameFlag::Whiteout) => Some(self.new_whiteout()?),
|
|
76
|
+
_ => None,
|
|
77
|
+
};
|
|
78
|
+
match left {
|
|
79
|
+
Some(left) => from_dir.state().exchange(from, left),
|
|
80
|
+
None => from_dir.state().take(from),
|
|
81
|
+
}
|
|
82
|
+
to_dir.state().insert(to, moving);
|
|
83
|
+
Ok(())
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//! A directory's extended attributes, as tmpfs keeps the `trusted.*`
|
|
2
|
+
//! ones overlayfs marks a directory opaque with.
|
|
3
|
+
|
|
4
|
+
use super::super::FilesystemError;
|
|
5
|
+
use super::Tmpfs;
|
|
6
|
+
|
|
7
|
+
impl Tmpfs {
|
|
8
|
+
/// The value of the attribute `name` on this directory, as `getxattr`
|
|
9
|
+
/// reads it, or `None` where it is not set.
|
|
10
|
+
pub(crate) fn getxattr(&self, name: &str) -> Option<Vec<u8>> {
|
|
11
|
+
self.dir.state().xattrs.get(name).cloned()
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/// Set the attribute `name` on this directory, as `setxattr` does.
|
|
15
|
+
pub(crate) fn setxattr(&self, name: &str, value: &[u8]) -> Result<(), FilesystemError> {
|
|
16
|
+
self.dir
|
|
17
|
+
.state()
|
|
18
|
+
.xattrs
|
|
19
|
+
.insert(name.to_string(), value.to_vec());
|
|
20
|
+
Ok(())
|
|
21
|
+
}
|
|
22
|
+
}
|