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
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
//! A sequence's calls through the Linux the test runs on, syscall by syscall.
|
|
2
|
+
|
|
3
|
+
use std::collections::BTreeMap;
|
|
4
|
+
use std::ffi::{CStr, CString};
|
|
5
|
+
use std::fs;
|
|
6
|
+
use std::io;
|
|
7
|
+
use std::mem;
|
|
8
|
+
use std::os::unix::fs::MetadataExt;
|
|
9
|
+
use std::path::Path;
|
|
10
|
+
|
|
11
|
+
use crate::calls::{payload, Call, SLOTS, UTIME_SECONDS};
|
|
12
|
+
use crate::{describe, Snapshot, Times};
|
|
13
|
+
|
|
14
|
+
fn c_string(text: &str) -> CString {
|
|
15
|
+
CString::new(text).expect("no path of a sequence holds a NUL")
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/// The name of the errno the last syscall left, as kobako names a failure.
|
|
19
|
+
fn errno() -> String {
|
|
20
|
+
let code = io::Error::last_os_error().raw_os_error().unwrap_or(0);
|
|
21
|
+
let name = match code {
|
|
22
|
+
libc::ENOENT => "ENOENT",
|
|
23
|
+
libc::EEXIST => "EEXIST",
|
|
24
|
+
libc::ENOTDIR => "ENOTDIR",
|
|
25
|
+
libc::EISDIR => "EISDIR",
|
|
26
|
+
libc::ENOTEMPTY => "ENOTEMPTY",
|
|
27
|
+
libc::EACCES => "EACCES",
|
|
28
|
+
libc::EROFS => "EROFS",
|
|
29
|
+
libc::EINVAL => "EINVAL",
|
|
30
|
+
libc::ENOTSUP => "ENOTSUP",
|
|
31
|
+
libc::ENOSPC => "ENOSPC",
|
|
32
|
+
libc::EFBIG => "EFBIG",
|
|
33
|
+
libc::EXDEV => "EXDEV",
|
|
34
|
+
libc::ELOOP => "ELOOP",
|
|
35
|
+
libc::EPERM => "EPERM",
|
|
36
|
+
libc::EBADF => "EBADF",
|
|
37
|
+
libc::EBUSY => "EBUSY",
|
|
38
|
+
libc::ESPIPE => "ESPIPE",
|
|
39
|
+
libc::ENAMETOOLONG => "ENAMETOOLONG",
|
|
40
|
+
other => return format!("errno {other}"),
|
|
41
|
+
};
|
|
42
|
+
name.to_string()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
fn answer(result: i64, ok: impl FnOnce(i64) -> String) -> String {
|
|
46
|
+
match result {
|
|
47
|
+
failed if failed < 0 => errno(),
|
|
48
|
+
done => ok(done),
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
fn ok_or_errno(result: i64) -> String {
|
|
53
|
+
answer(result, |_| "ok".to_string())
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
fn bytes(buf: &[u8], read: i64) -> String {
|
|
57
|
+
format!("ok:{:?}", String::from_utf8_lossy(&buf[..read as usize]))
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
fn bytes_written(count: i64) -> String {
|
|
61
|
+
format!("ok:{count}")
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/// A `st_nlink`, whose width differs between architectures.
|
|
65
|
+
fn nlink(count: impl Into<u64>) -> u64 {
|
|
66
|
+
count.into()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
fn kind_of(mode: u32) -> &'static str {
|
|
70
|
+
match mode & libc::S_IFMT {
|
|
71
|
+
libc::S_IFREG => "reg",
|
|
72
|
+
libc::S_IFDIR => "dir",
|
|
73
|
+
libc::S_IFLNK => "lnk",
|
|
74
|
+
_ => "oth",
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/// Mount `source` at `target`, naming what stopped it, since nothing here is
|
|
79
|
+
/// worth holding if the filesystem the sequence runs on is not the real one.
|
|
80
|
+
pub fn mount(source: &str, target: &str, kind: Option<&str>, flags: u64, data: Option<&str>) {
|
|
81
|
+
let (source, target) = (c_string(source), c_string(target));
|
|
82
|
+
let kind = kind.map(c_string);
|
|
83
|
+
let data = data.map(c_string);
|
|
84
|
+
// SAFETY: each pointer is a live NUL-terminated string or null.
|
|
85
|
+
let result = unsafe {
|
|
86
|
+
libc::mount(
|
|
87
|
+
source.as_ptr(),
|
|
88
|
+
target.as_ptr(),
|
|
89
|
+
kind.as_ref().map_or(std::ptr::null(), |kind| kind.as_ptr()),
|
|
90
|
+
flags,
|
|
91
|
+
data.as_ref()
|
|
92
|
+
.map_or(std::ptr::null(), |data| data.as_ptr().cast()),
|
|
93
|
+
)
|
|
94
|
+
};
|
|
95
|
+
assert_eq!(
|
|
96
|
+
result,
|
|
97
|
+
0,
|
|
98
|
+
"mounting {target:?} answered {}; this test needs root and a mount namespace, which \
|
|
99
|
+
`rake crates:linux_baseline` runs it with",
|
|
100
|
+
errno()
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/// Let go of what is mounted at `target`, if anything is.
|
|
105
|
+
pub fn umount(target: &str) {
|
|
106
|
+
let target = c_string(target);
|
|
107
|
+
// SAFETY: `target` is a live NUL-terminated string.
|
|
108
|
+
unsafe { libc::umount(target.as_ptr()) };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/// Whether `path` stands on a tmpfs, whose answers this test holds a Tmpfs to.
|
|
112
|
+
pub fn on_tmpfs(path: &str) -> bool {
|
|
113
|
+
let path = c_string(path);
|
|
114
|
+
// SAFETY: `stat` is written before it is read, and only on success.
|
|
115
|
+
unsafe {
|
|
116
|
+
let mut stat: libc::statfs = mem::zeroed();
|
|
117
|
+
libc::statfs(path.as_ptr(), &mut stat) == 0 && stat.f_type == libc::TMPFS_MAGIC
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
pub struct Linux {
|
|
122
|
+
root: i32,
|
|
123
|
+
root_path: String,
|
|
124
|
+
slots: [Option<i32>; SLOTS],
|
|
125
|
+
merged: bool,
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
impl Linux {
|
|
129
|
+
/// The directory at `root_path`. `merged` leaves out what an Overlay and
|
|
130
|
+
/// overlayfs each count of a directory their own way.
|
|
131
|
+
pub fn new(root_path: &str, merged: bool) -> Self {
|
|
132
|
+
let path = c_string(root_path);
|
|
133
|
+
// SAFETY: `path` is a live NUL-terminated string.
|
|
134
|
+
let root = unsafe { libc::open(path.as_ptr(), libc::O_DIRECTORY | libc::O_RDONLY) };
|
|
135
|
+
assert!(root >= 0, "{root_path} must open: {}", errno());
|
|
136
|
+
Self {
|
|
137
|
+
root,
|
|
138
|
+
root_path: root_path.to_string(),
|
|
139
|
+
slots: [None; SLOTS],
|
|
140
|
+
merged,
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/// Another caller of the same directory, with descriptors of its own.
|
|
145
|
+
pub fn another(&self) -> Self {
|
|
146
|
+
Self::new(&self.root_path, self.merged)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
fn start(&self, base: Option<usize>) -> Result<i32, String> {
|
|
150
|
+
match base {
|
|
151
|
+
None => Ok(self.root),
|
|
152
|
+
Some(slot) => self.slots[slot].ok_or_else(|| "EBADF".to_string()),
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
fn fstat(fd: i32) -> Option<libc::stat> {
|
|
157
|
+
// SAFETY: `stat` is written before it is read, and only on success.
|
|
158
|
+
unsafe {
|
|
159
|
+
let mut stat: libc::stat = mem::zeroed();
|
|
160
|
+
(libc::fstat(fd, &mut stat) == 0).then_some(stat)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
fn is_directory(fd: i32) -> bool {
|
|
165
|
+
Self::fstat(fd).is_some_and(|stat| stat.st_mode & libc::S_IFMT == libc::S_IFDIR)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
fn stat(&self, stat: &libc::stat) -> String {
|
|
169
|
+
describe(
|
|
170
|
+
kind_of(stat.st_mode),
|
|
171
|
+
stat.st_size as u64,
|
|
172
|
+
nlink(stat.st_nlink),
|
|
173
|
+
self.merged,
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/// What `fd` lists from its start, kinds included, sorted.
|
|
178
|
+
fn readdir(fd: i32) -> String {
|
|
179
|
+
const DT_NAME_AT: usize = 19;
|
|
180
|
+
// SAFETY: `getdents64` writes at most `buf.len()` bytes, and each
|
|
181
|
+
// record it answers holds a NUL-terminated name within what it wrote.
|
|
182
|
+
unsafe {
|
|
183
|
+
if Self::is_directory(fd) {
|
|
184
|
+
libc::lseek(fd, 0, libc::SEEK_SET);
|
|
185
|
+
}
|
|
186
|
+
let mut names = Vec::new();
|
|
187
|
+
let mut buf = vec![0u8; 8192];
|
|
188
|
+
loop {
|
|
189
|
+
let read = libc::syscall(libc::SYS_getdents64, fd, buf.as_mut_ptr(), buf.len());
|
|
190
|
+
match read {
|
|
191
|
+
failed if failed < 0 => return errno(),
|
|
192
|
+
0 => break,
|
|
193
|
+
_ => {}
|
|
194
|
+
}
|
|
195
|
+
let mut at = 0;
|
|
196
|
+
while at < read as usize {
|
|
197
|
+
let length = u16::from_ne_bytes([buf[at + 16], buf[at + 17]]) as usize;
|
|
198
|
+
let kind = match buf[at + 18] {
|
|
199
|
+
libc::DT_REG => "reg",
|
|
200
|
+
libc::DT_DIR => "dir",
|
|
201
|
+
libc::DT_LNK => "lnk",
|
|
202
|
+
_ => "oth",
|
|
203
|
+
};
|
|
204
|
+
let name = CStr::from_ptr(buf.as_ptr().add(at + DT_NAME_AT).cast());
|
|
205
|
+
names.push(format!("{}:{kind}", name.to_string_lossy()));
|
|
206
|
+
at += length;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
names.sort();
|
|
210
|
+
format!("ok:{names:?}")
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
fn open(&mut self, open: &crate::calls::Open) -> String {
|
|
215
|
+
let at = match self.start(open.base) {
|
|
216
|
+
Ok(at) => at,
|
|
217
|
+
Err(answer) => return answer,
|
|
218
|
+
};
|
|
219
|
+
let mut flags = libc::O_CLOEXEC
|
|
220
|
+
| match (open.read || !open.write, open.write) {
|
|
221
|
+
(true, true) => libc::O_RDWR,
|
|
222
|
+
(false, true) => libc::O_WRONLY,
|
|
223
|
+
_ => libc::O_RDONLY,
|
|
224
|
+
};
|
|
225
|
+
for (asked, flag) in [
|
|
226
|
+
(open.create, libc::O_CREAT),
|
|
227
|
+
(open.exclusive, libc::O_EXCL),
|
|
228
|
+
(open.truncate, libc::O_TRUNC),
|
|
229
|
+
(open.append, libc::O_APPEND),
|
|
230
|
+
(open.directory, libc::O_DIRECTORY),
|
|
231
|
+
(open.nofollow, libc::O_NOFOLLOW),
|
|
232
|
+
] {
|
|
233
|
+
if asked {
|
|
234
|
+
flags |= flag;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
let path = c_string(&open.path);
|
|
238
|
+
// SAFETY: `path` is a live NUL-terminated string.
|
|
239
|
+
let fd = unsafe { libc::openat(at, path.as_ptr(), flags, 0o644) };
|
|
240
|
+
if fd < 0 {
|
|
241
|
+
return errno();
|
|
242
|
+
}
|
|
243
|
+
if let Some(old) = self.slots[open.slot].replace(fd) {
|
|
244
|
+
// SAFETY: the slot held the only reference to `old`.
|
|
245
|
+
unsafe { libc::close(old) };
|
|
246
|
+
}
|
|
247
|
+
"ok".to_string()
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
pub fn run(&mut self, call: &Call) -> String {
|
|
251
|
+
macro_rules! start {
|
|
252
|
+
($base:expr) => {
|
|
253
|
+
match self.start($base) {
|
|
254
|
+
Ok(at) => at,
|
|
255
|
+
Err(answer) => return answer,
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
macro_rules! fd {
|
|
260
|
+
($slot:expr) => {
|
|
261
|
+
match self.slots[$slot] {
|
|
262
|
+
Some(fd) => fd,
|
|
263
|
+
None => return "EBADF".to_string(),
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
// SAFETY: each path is a live NUL-terminated string, each buffer outlives
|
|
268
|
+
// the syscall it is handed to, with its length, and each `fd` is open.
|
|
269
|
+
unsafe {
|
|
270
|
+
match call {
|
|
271
|
+
Call::Open(open) => self.open(open),
|
|
272
|
+
Call::Close(slot) => {
|
|
273
|
+
let fd = fd!(*slot);
|
|
274
|
+
self.slots[*slot] = None;
|
|
275
|
+
ok_or_errno(libc::close(fd).into())
|
|
276
|
+
}
|
|
277
|
+
Call::Read(slot, len) => {
|
|
278
|
+
let mut buf = vec![0u8; *len];
|
|
279
|
+
let read = libc::read(fd!(*slot), buf.as_mut_ptr().cast(), *len);
|
|
280
|
+
answer(read as i64, |read| bytes(&buf, read))
|
|
281
|
+
}
|
|
282
|
+
Call::Write(slot, len) => {
|
|
283
|
+
let data = payload(*len);
|
|
284
|
+
let count = libc::write(fd!(*slot), data.as_ptr().cast(), data.len());
|
|
285
|
+
answer(count as i64, bytes_written)
|
|
286
|
+
}
|
|
287
|
+
Call::Pread(slot, len, pos) => {
|
|
288
|
+
let mut buf = vec![0u8; *len];
|
|
289
|
+
let read = libc::pread(fd!(*slot), buf.as_mut_ptr().cast(), *len, *pos as i64);
|
|
290
|
+
answer(read as i64, |read| bytes(&buf, read))
|
|
291
|
+
}
|
|
292
|
+
Call::Pwrite(slot, len, pos) => {
|
|
293
|
+
let data = payload(*len);
|
|
294
|
+
let count =
|
|
295
|
+
libc::pwrite(fd!(*slot), data.as_ptr().cast(), data.len(), *pos as i64);
|
|
296
|
+
answer(count as i64, bytes_written)
|
|
297
|
+
}
|
|
298
|
+
Call::Seek(slot, offset, whence) => {
|
|
299
|
+
let fd = fd!(*slot);
|
|
300
|
+
if Self::is_directory(fd) {
|
|
301
|
+
return "directory".to_string();
|
|
302
|
+
}
|
|
303
|
+
let whence = [libc::SEEK_SET, libc::SEEK_CUR, libc::SEEK_END][*whence as usize];
|
|
304
|
+
answer(libc::lseek(fd, *offset, whence), |pos| format!("ok:{pos}"))
|
|
305
|
+
}
|
|
306
|
+
Call::Truncate(slot, len) => {
|
|
307
|
+
ok_or_errno(libc::ftruncate(fd!(*slot), *len as i64).into())
|
|
308
|
+
}
|
|
309
|
+
Call::Fallocate(slot, offset, len) => {
|
|
310
|
+
ok_or_errno(libc::fallocate(fd!(*slot), 0, *offset as i64, *len as i64).into())
|
|
311
|
+
}
|
|
312
|
+
Call::Utimes { base, path, follow } => {
|
|
313
|
+
let times = [
|
|
314
|
+
libc::timespec {
|
|
315
|
+
tv_sec: 0,
|
|
316
|
+
tv_nsec: libc::UTIME_OMIT,
|
|
317
|
+
},
|
|
318
|
+
libc::timespec {
|
|
319
|
+
tv_sec: UTIME_SECONDS,
|
|
320
|
+
tv_nsec: 0,
|
|
321
|
+
},
|
|
322
|
+
];
|
|
323
|
+
let flags = match follow {
|
|
324
|
+
true => 0,
|
|
325
|
+
false => libc::AT_SYMLINK_NOFOLLOW,
|
|
326
|
+
};
|
|
327
|
+
let path = c_string(path);
|
|
328
|
+
ok_or_errno(
|
|
329
|
+
libc::utimensat(start!(*base), path.as_ptr(), times.as_ptr(), flags).into(),
|
|
330
|
+
)
|
|
331
|
+
}
|
|
332
|
+
Call::Fstat(slot) => match Self::fstat(fd!(*slot)) {
|
|
333
|
+
Some(stat) => self.stat(&stat),
|
|
334
|
+
None => errno(),
|
|
335
|
+
},
|
|
336
|
+
Call::Fsync(slot) => ok_or_errno(libc::fsync(fd!(*slot)).into()),
|
|
337
|
+
Call::Readdir(slot) => Self::readdir(fd!(*slot)),
|
|
338
|
+
Call::Stat { base, path, follow } => {
|
|
339
|
+
let mut stat: libc::stat = mem::zeroed();
|
|
340
|
+
let flags = if *follow {
|
|
341
|
+
0
|
|
342
|
+
} else {
|
|
343
|
+
libc::AT_SYMLINK_NOFOLLOW
|
|
344
|
+
};
|
|
345
|
+
let path = c_string(path);
|
|
346
|
+
match libc::fstatat(start!(*base), path.as_ptr(), &mut stat, flags) {
|
|
347
|
+
0 => self.stat(&stat),
|
|
348
|
+
_ => errno(),
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
Call::Mkdir(base, path) => {
|
|
352
|
+
ok_or_errno(libc::mkdirat(start!(*base), c_string(path).as_ptr(), 0o755).into())
|
|
353
|
+
}
|
|
354
|
+
Call::Rmdir(base, path) => ok_or_errno(
|
|
355
|
+
libc::unlinkat(start!(*base), c_string(path).as_ptr(), libc::AT_REMOVEDIR)
|
|
356
|
+
.into(),
|
|
357
|
+
),
|
|
358
|
+
Call::Unlink(base, path) => {
|
|
359
|
+
ok_or_errno(libc::unlinkat(start!(*base), c_string(path).as_ptr(), 0).into())
|
|
360
|
+
}
|
|
361
|
+
Call::Rename(base, from, to) => ok_or_errno(
|
|
362
|
+
libc::renameat(
|
|
363
|
+
start!(*base),
|
|
364
|
+
c_string(from).as_ptr(),
|
|
365
|
+
self.root,
|
|
366
|
+
c_string(to).as_ptr(),
|
|
367
|
+
)
|
|
368
|
+
.into(),
|
|
369
|
+
),
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/// Every entry beneath the root, with a file's content.
|
|
375
|
+
pub fn snapshot(&self) -> Snapshot {
|
|
376
|
+
let mut out = BTreeMap::new();
|
|
377
|
+
snapshot_into(Path::new(&self.root_path), "", self.merged, &mut out);
|
|
378
|
+
out
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/// When each entry beneath the root was last modified and changed.
|
|
382
|
+
pub fn times(&self) -> Times<(i64, i64)> {
|
|
383
|
+
fn walk(dir: &Path, prefix: &str, out: &mut Times<(i64, i64)>) {
|
|
384
|
+
let Ok(entries) = fs::read_dir(dir) else {
|
|
385
|
+
return;
|
|
386
|
+
};
|
|
387
|
+
for entry in entries.flatten() {
|
|
388
|
+
let full = format!("{prefix}{}", entry.file_name().to_string_lossy());
|
|
389
|
+
let metadata = fs::symlink_metadata(entry.path()).expect("the entry just listed");
|
|
390
|
+
out.insert(
|
|
391
|
+
full.clone(),
|
|
392
|
+
(
|
|
393
|
+
(metadata.mtime(), metadata.mtime_nsec()),
|
|
394
|
+
(metadata.ctime(), metadata.ctime_nsec()),
|
|
395
|
+
),
|
|
396
|
+
);
|
|
397
|
+
if metadata.is_dir() {
|
|
398
|
+
walk(&entry.path(), &format!("{full}/"), out);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
let root = Path::new(&self.root_path);
|
|
403
|
+
let metadata = fs::metadata(root).expect("the root stands");
|
|
404
|
+
let mut out = BTreeMap::from([(
|
|
405
|
+
".".to_string(),
|
|
406
|
+
(
|
|
407
|
+
(metadata.mtime(), metadata.mtime_nsec()),
|
|
408
|
+
(metadata.ctime(), metadata.ctime_nsec()),
|
|
409
|
+
),
|
|
410
|
+
)]);
|
|
411
|
+
walk(root, "", &mut out);
|
|
412
|
+
out
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
impl Drop for Linux {
|
|
417
|
+
fn drop(&mut self) {
|
|
418
|
+
for fd in self.slots.iter().flatten().chain([&self.root]) {
|
|
419
|
+
// SAFETY: this holds the only reference to each descriptor.
|
|
420
|
+
unsafe { libc::close(*fd) };
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/// Every entry beneath `dir`, as a Filesystem's snapshot reads one.
|
|
426
|
+
pub fn snapshot_into(dir: &Path, prefix: &str, merged: bool, out: &mut Snapshot) {
|
|
427
|
+
let Ok(entries) = fs::read_dir(dir) else {
|
|
428
|
+
return;
|
|
429
|
+
};
|
|
430
|
+
for entry in entries.flatten() {
|
|
431
|
+
let full = format!("{prefix}{}", entry.file_name().to_string_lossy());
|
|
432
|
+
let metadata = fs::symlink_metadata(entry.path()).expect("the entry just listed");
|
|
433
|
+
let mut seen = describe(
|
|
434
|
+
kind_of(metadata.mode()),
|
|
435
|
+
metadata.len(),
|
|
436
|
+
metadata.nlink(),
|
|
437
|
+
merged,
|
|
438
|
+
);
|
|
439
|
+
if metadata.is_dir() {
|
|
440
|
+
snapshot_into(&entry.path(), &format!("{full}/"), merged, out);
|
|
441
|
+
}
|
|
442
|
+
if metadata.is_file() {
|
|
443
|
+
let content = fs::read(entry.path()).unwrap_or_default();
|
|
444
|
+
let shown = &content[..content.len().min(64)];
|
|
445
|
+
seen += &format!(" {:?}", String::from_utf8_lossy(shown));
|
|
446
|
+
}
|
|
447
|
+
out.insert(full, seen);
|
|
448
|
+
}
|
|
449
|
+
}
|