kobako 0.28.0 → 0.29.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.
Files changed (137) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +1 -1
  3. data/CHANGELOG.md +170 -0
  4. data/Cargo.lock +111 -82
  5. data/README.md +154 -36
  6. data/ROADMAP.md +5 -0
  7. data/SECURITY.md +1 -1
  8. data/crates/kobako-runtime/CHANGELOG.md +86 -0
  9. data/crates/kobako-runtime/Cargo.toml +27 -6
  10. data/crates/kobako-runtime/README.md +13 -8
  11. data/crates/kobako-runtime/src/dispatch.rs +8 -0
  12. data/crates/kobako-runtime/src/filesystem/directory/spelling.rs +194 -0
  13. data/crates/kobako-runtime/src/filesystem/directory.rs +1082 -0
  14. data/crates/kobako-runtime/src/filesystem/memory.rs +1100 -0
  15. data/crates/kobako-runtime/src/filesystem/overlay/exclusive.rs +88 -0
  16. data/crates/kobako-runtime/src/filesystem/overlay/stack.rs +808 -0
  17. data/crates/kobako-runtime/src/filesystem/overlay.rs +1955 -0
  18. data/crates/kobako-runtime/src/filesystem/path.rs +60 -0
  19. data/crates/kobako-runtime/src/filesystem/temporary.rs +167 -0
  20. data/crates/kobako-runtime/src/filesystem.rs +392 -0
  21. data/crates/kobako-runtime/src/grant.rs +20 -0
  22. data/crates/kobako-runtime/src/lib.rs +11 -1
  23. data/crates/kobako-runtime/src/mount.rs +178 -0
  24. data/crates/kobako-runtime/src/profile.rs +4 -4
  25. data/crates/kobako-runtime/src/runtime.rs +12 -0
  26. data/crates/kobako-runtime/src/snapshot.rs +20 -5
  27. data/crates/kobako-runtime/src/yielder.rs +11 -6
  28. data/crates/kobako-transport/CHANGELOG.md +17 -0
  29. data/crates/kobako-transport/Cargo.toml +1 -1
  30. data/crates/kobako-transport/README.md +1 -1
  31. data/crates/kobako-transport/src/abi.rs +1 -1
  32. data/crates/kobako-transport/src/envelope/fault.rs +2 -1
  33. data/crates/kobako-transport/src/envelope/preamble.rs +12 -12
  34. data/crates/kobako-transport/src/envelope/reply.rs +32 -14
  35. data/crates/kobako-transport/src/envelope/request.rs +18 -8
  36. data/crates/kobako-wasmtime/CHANGELOG.md +119 -0
  37. data/crates/kobako-wasmtime/Cargo.toml +21 -16
  38. data/crates/kobako-wasmtime/README.md +5 -3
  39. data/crates/kobako-wasmtime/src/abi.rs +66 -32
  40. data/crates/kobako-wasmtime/src/ambient.rs +63 -4
  41. data/crates/kobako-wasmtime/src/cache.rs +185 -74
  42. data/crates/kobako-wasmtime/src/config.rs +58 -1
  43. data/crates/kobako-wasmtime/src/descriptors.rs +167 -0
  44. data/crates/kobako-wasmtime/src/dispatch.rs +47 -29
  45. data/crates/kobako-wasmtime/src/driver.rs +46 -44
  46. data/crates/kobako-wasmtime/src/guest_mem.rs +13 -7
  47. data/crates/kobako-wasmtime/src/imports.rs +46 -0
  48. data/crates/kobako-wasmtime/src/instance_pre.rs +17 -45
  49. data/crates/kobako-wasmtime/src/invocation.rs +35 -46
  50. data/crates/kobako-wasmtime/src/io.rs +5 -202
  51. data/crates/kobako-wasmtime/src/lib.rs +4 -0
  52. data/crates/kobako-wasmtime/src/preview1/args.rs +60 -0
  53. data/crates/kobako-wasmtime/src/preview1/beneath.rs +188 -0
  54. data/crates/kobako-wasmtime/src/preview1/differential/mount.rs +609 -0
  55. data/crates/kobako-wasmtime/src/preview1/differential.rs +372 -0
  56. data/crates/kobako-wasmtime/src/preview1/errno.rs +267 -0
  57. data/crates/kobako-wasmtime/src/preview1/fd.rs +672 -0
  58. data/crates/kobako-wasmtime/src/preview1/memory.rs +205 -0
  59. data/crates/kobako-wasmtime/src/preview1/mount_view.rs +2499 -0
  60. data/crates/kobako-wasmtime/src/preview1/path.rs +421 -0
  61. data/crates/kobako-wasmtime/src/preview1/poll.rs +333 -0
  62. data/crates/kobako-wasmtime/src/preview1/sock.rs +30 -0
  63. data/crates/kobako-wasmtime/src/preview1.rs +461 -0
  64. data/crates/kobako-wasmtime/src/trap.rs +39 -19
  65. data/crates/kobako-wasmtime/src/wasi.rs +317 -0
  66. data/crates/kobako-wasmtime/tests/compile_posture.rs +43 -0
  67. data/crates/kobako-wasmtime/tests/guest_file.rs +1054 -0
  68. data/crates/kobako-wasmtime/tests/preamble_flags.rs +3 -2
  69. data/crates/kobako-wasmtime/tests/snippet_compile.rs +4 -3
  70. data/crates/kobako-wasmtime/tests/support/mod.rs +9 -13
  71. data/crates/kobako-wasmtime/tests/unanswered_call.rs +86 -0
  72. data/crates/kobako-wasmtime/tests/unsupported_call.rs +1 -0
  73. data/data/kobako.wasm +0 -0
  74. data/ext/kobako/Cargo.toml +4 -2
  75. data/ext/kobako/src/runtime/bridge.rs +154 -81
  76. data/ext/kobako/src/runtime/escape.rs +53 -0
  77. data/ext/kobako/src/runtime/filesystem/grants.rs +91 -0
  78. data/ext/kobako/src/runtime/filesystem/ruby.rs +400 -0
  79. data/ext/kobako/src/runtime/filesystem.rs +236 -0
  80. data/ext/kobako/src/runtime/gvl.rs +7 -1
  81. data/ext/kobako/src/runtime/pinned_value.rs +31 -0
  82. data/ext/kobako/src/runtime/snapshot.rs +127 -0
  83. data/ext/kobako/src/runtime.rs +120 -199
  84. data/lib/kobako/catalog/flags.rb +2 -14
  85. data/lib/kobako/catalog/grants.rb +30 -0
  86. data/lib/kobako/catalog/mount.rb +27 -0
  87. data/lib/kobako/catalog/services.rb +11 -0
  88. data/lib/kobako/catalog/snippets.rb +5 -9
  89. data/lib/kobako/catalog.rb +11 -0
  90. data/lib/kobako/codec/decoder.rb +1 -0
  91. data/lib/kobako/codec/ext_types.rb +1 -1
  92. data/lib/kobako/codec/state.rb +19 -3
  93. data/lib/kobako/codec/value_budget.rb +86 -0
  94. data/lib/kobako/codec.rb +7 -0
  95. data/lib/kobako/context.rb +38 -11
  96. data/lib/kobako/errors.rb +15 -14
  97. data/lib/kobako/filesystem.rb +38 -0
  98. data/lib/kobako/pool.rb +2 -2
  99. data/lib/kobako/sandbox.rb +40 -12
  100. data/lib/kobako/sandbox_options.rb +39 -12
  101. data/lib/kobako/snippet/{binary.rb → bytecode.rb} +1 -1
  102. data/lib/kobako/snippet.rb +1 -1
  103. data/lib/kobako/transport/block_failure.rb +35 -0
  104. data/lib/kobako/transport/dispatcher.rb +30 -26
  105. data/lib/kobako/transport/exposure.rb +5 -7
  106. data/lib/kobako/transport/reflection.rb +79 -23
  107. data/lib/kobako/transport/yielder.rb +8 -30
  108. data/lib/kobako/transport.rb +1 -0
  109. data/lib/kobako/version.rb +1 -1
  110. data/lib/kobako.rb +1 -0
  111. data/sig/kobako/catalog/flags.rbs +0 -4
  112. data/sig/kobako/catalog/grants.rbs +13 -0
  113. data/sig/kobako/catalog/mount.rbs +28 -0
  114. data/sig/kobako/catalog/services.rbs +4 -0
  115. data/sig/kobako/catalog/snippets.rbs +1 -1
  116. data/sig/kobako/catalog.rbs +1 -0
  117. data/sig/kobako/codec/state.rbs +7 -1
  118. data/sig/kobako/codec/value_budget.rbs +23 -0
  119. data/sig/kobako/codec.rbs +2 -0
  120. data/sig/kobako/context.rbs +14 -6
  121. data/sig/kobako/errors.rbs +4 -4
  122. data/sig/kobako/filesystem.rbs +88 -0
  123. data/sig/kobako/guest_yielder.rbs +9 -0
  124. data/sig/kobako/pool.rbs +1 -1
  125. data/sig/kobako/runtime.rbs +21 -49
  126. data/sig/kobako/sandbox.rbs +7 -1
  127. data/sig/kobako/sandbox_options.rbs +24 -2
  128. data/sig/kobako/snapshot.rbs +20 -0
  129. data/sig/kobako/snippet/bytecode.rbs +12 -0
  130. data/sig/kobako/transport/block_failure.rbs +13 -0
  131. data/sig/kobako/transport/dispatcher.rbs +3 -3
  132. data/sig/kobako/transport/exposure.rbs +1 -1
  133. data/sig/kobako/transport/reflection.rbs +14 -1
  134. data/sig/kobako/transport/yielder.rbs +3 -7
  135. data/sig/kobako/transport.rbs +2 -2
  136. metadata +60 -7
  137. data/sig/kobako/snippet/binary.rbs +0 -12
@@ -0,0 +1,194 @@
1
+ //! How a host directory spells a name it holds. A host that folds case or
2
+ //! Unicode form reaches one entry by many names, so a Policy judging the
3
+ //! name as asked would see a spelling the host never stored.
4
+ //!
5
+ //! macOS answers the stored name for any spelling. Elsewhere a listing
6
+ //! finds the entry by its inode, skipped on a Linux filesystem known to
7
+ //! match names exactly; without an inode, a spelling the listing lacks is
8
+ //! refused.
9
+
10
+ use std::io;
11
+
12
+ use cap_std::fs::Dir;
13
+
14
+ /// The name `parent` stores for the entry `name` reaches, a link reached
15
+ /// as itself, or `None` when nothing is there.
16
+ pub(super) fn stored_name(parent: &Dir, name: &str) -> io::Result<Option<String>> {
17
+ #[cfg(target_os = "macos")]
18
+ {
19
+ macos::stored_name(parent, name)
20
+ }
21
+ #[cfg(not(target_os = "macos"))]
22
+ {
23
+ #[cfg(target_os = "linux")]
24
+ if linux::matches_exactly(parent) {
25
+ return match parent.symlink_metadata(name) {
26
+ Ok(_) => Ok(Some(name.to_string())),
27
+ Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
28
+ Err(err) => Err(err),
29
+ };
30
+ }
31
+ stored_name_by_listing(parent, name)
32
+ }
33
+ }
34
+
35
+ /// The name a listing of `parent` holds for `name`: itself when listed,
36
+ /// else the entry sharing its inode.
37
+ #[cfg(not(target_os = "macos"))]
38
+ fn stored_name_by_listing(parent: &Dir, name: &str) -> io::Result<Option<String>> {
39
+ let found = match parent.symlink_metadata(name) {
40
+ Ok(found) => found,
41
+ Err(err) if err.kind() == io::ErrorKind::NotFound => return Ok(None),
42
+ Err(err) => return Err(err),
43
+ };
44
+ let mut names = Vec::new();
45
+ for entry in parent.entries()? {
46
+ let entry = entry?;
47
+ if entry.file_name() == name {
48
+ return Ok(Some(name.to_string()));
49
+ }
50
+ names.push(entry);
51
+ }
52
+ #[cfg(unix)]
53
+ {
54
+ use cap_std::fs::MetadataExt;
55
+ use std::os::unix::fs::DirEntryExt;
56
+ for entry in names {
57
+ if entry.ino() == found.ino() {
58
+ return entry
59
+ .file_name()
60
+ .into_string()
61
+ .map(Some)
62
+ .map_err(|_| io::ErrorKind::InvalidData.into());
63
+ }
64
+ }
65
+ }
66
+ #[cfg(not(unix))]
67
+ let _ = (found, names);
68
+ Err(io::ErrorKind::PermissionDenied.into())
69
+ }
70
+
71
+ #[cfg(target_os = "macos")]
72
+ mod macos {
73
+ use std::ffi::CString;
74
+ use std::io;
75
+ use std::os::fd::AsRawFd;
76
+
77
+ use cap_std::fs::Dir;
78
+
79
+ /// The reply `getattrlist(2)` writes for `ATTR_CMN_NAME`: its length,
80
+ /// then a reference to the name stored behind it.
81
+ #[repr(C, packed(4))]
82
+ struct NameReply {
83
+ length: u32,
84
+ name: libc::attrreference_t,
85
+ data: [u8; 1024],
86
+ }
87
+
88
+ pub(super) fn stored_name(parent: &Dir, name: &str) -> io::Result<Option<String>> {
89
+ let path = CString::new(name).map_err(|_| io::Error::from(io::ErrorKind::InvalidInput))?;
90
+ let mut request = libc::attrlist {
91
+ bitmapcount: libc::ATTR_BIT_MAP_COUNT,
92
+ reserved: 0,
93
+ commonattr: libc::ATTR_CMN_NAME,
94
+ volattr: 0,
95
+ dirattr: 0,
96
+ fileattr: 0,
97
+ forkattr: 0,
98
+ };
99
+ let mut reply = NameReply {
100
+ length: 0,
101
+ name: libc::attrreference_t {
102
+ attr_dataoffset: 0,
103
+ attr_length: 0,
104
+ },
105
+ data: [0; 1024],
106
+ };
107
+ // SAFETY: every pointer names a live, correctly sized buffer, and
108
+ // the reply buffer's size bounds what the call writes.
109
+ let answered = unsafe {
110
+ libc::getattrlistat(
111
+ parent.as_raw_fd(),
112
+ path.as_ptr(),
113
+ (&raw mut request).cast(),
114
+ (&raw mut reply).cast(),
115
+ size_of::<NameReply>(),
116
+ libc::FSOPT_NOFOLLOW as libc::c_ulong,
117
+ )
118
+ };
119
+ if answered != 0 {
120
+ let err = io::Error::last_os_error();
121
+ return match err.kind() {
122
+ io::ErrorKind::NotFound => Ok(None),
123
+ _ => Err(err),
124
+ };
125
+ }
126
+ // The reference counts from itself, and `data` follows it; the
127
+ // length counts the terminating NUL.
128
+ let start = usize::try_from(reply.name.attr_dataoffset)
129
+ .ok()
130
+ .and_then(|offset| offset.checked_sub(size_of::<libc::attrreference_t>()));
131
+ let len = (reply.name.attr_length as usize).checked_sub(1);
132
+ let stored = start
133
+ .zip(len)
134
+ .and_then(|(start, len)| reply.data.get(start..start.checked_add(len)?))
135
+ .ok_or(io::ErrorKind::InvalidData)?;
136
+ String::from_utf8(stored.to_vec())
137
+ .map(Some)
138
+ .map_err(|_| io::ErrorKind::InvalidData.into())
139
+ }
140
+ }
141
+
142
+ #[cfg(target_os = "linux")]
143
+ mod linux {
144
+ use cap_std::fs::Dir;
145
+ use rustix::fs::{fstatfs, ioctl_getflags};
146
+
147
+ /// The 32-bit magic numbers of filesystems matching names byte for
148
+ /// byte unless a directory is marked to fold case: ext4, Btrfs, tmpfs,
149
+ /// overlayfs, and F2FS. XFS is left to the listing, as a volume made
150
+ /// ASCII case-insensitive marks no directory.
151
+ const EXACT: [u32; 5] = [0xEF53, 0x9123_683E, 0x0102_1994, 0x794C_7630, 0xF2F5_2010];
152
+
153
+ /// The inode flag marking a directory whose names fold case, from
154
+ /// Linux's `FS_CASEFOLD_FL`.
155
+ const CASEFOLD: u32 = 0x4000_0000;
156
+
157
+ pub(super) fn matches_exactly(parent: &Dir) -> bool {
158
+ let exact = fstatfs(parent).is_ok_and(|stats| EXACT.contains(&(stats.f_type as u32)));
159
+ exact && ioctl_getflags(parent).is_ok_and(|flags| flags.bits() & CASEFOLD == 0)
160
+ }
161
+
162
+ #[cfg(test)]
163
+ mod tests {
164
+ use cap_std::ambient_authority;
165
+
166
+ use super::super::stored_name_by_listing;
167
+ use super::Dir;
168
+
169
+ // A Linux host whose filesystem is not known to match exactly,
170
+ // XFS among them, spells by listing; the host here matches
171
+ // exactly, so the listing must answer as the lookup would.
172
+ // @behavior FS-081
173
+ #[test]
174
+ fn a_listing_spells_names_it_matches_exactly_as_given() {
175
+ let path = std::env::temp_dir().join(format!("kobako-listed-{}", std::process::id()));
176
+ let _ = std::fs::remove_dir_all(&path);
177
+ std::fs::create_dir_all(&path).expect("the host directory must be created");
178
+ std::fs::write(path.join("file"), "x").expect("the host file must be written");
179
+ let dir = Dir::open_ambient_dir(&path, ambient_authority())
180
+ .expect("the host directory must open");
181
+ let answers = (
182
+ stored_name_by_listing(&dir, "file").ok(),
183
+ stored_name_by_listing(&dir, "missing").ok(),
184
+ );
185
+ let _ = std::fs::remove_dir_all(&path);
186
+ assert_eq!(
187
+ answers,
188
+ (Some(Some("file".to_string())), Some(None)),
189
+ "a name the host holds, spelled by listing its directory, must come back as \
190
+ given, and a name it lacks as none"
191
+ );
192
+ }
193
+ }
194
+ }