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.
Files changed (201) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +1 -1
  3. data/CHANGELOG.md +217 -10
  4. data/Cargo.lock +139 -141
  5. data/README.md +96 -574
  6. data/ROADMAP.md +2 -1
  7. data/crates/kobako-runtime/CHANGELOG.md +269 -1
  8. data/crates/kobako-runtime/Cargo.toml +25 -15
  9. data/crates/kobako-runtime/README.md +7 -3
  10. data/crates/kobako-runtime/src/dispatch.rs +1 -1
  11. data/crates/kobako-runtime/src/endpoint/buffer.rs +192 -0
  12. data/crates/kobako-runtime/src/endpoint/capture.rs +115 -0
  13. data/crates/kobako-runtime/src/endpoint/dispatch.rs +253 -0
  14. data/crates/kobako-runtime/src/endpoint/invoke.rs +194 -0
  15. data/crates/kobako-runtime/src/endpoint/memory_cap.rs +238 -0
  16. data/crates/kobako-runtime/src/endpoint.rs +166 -0
  17. data/crates/kobako-runtime/src/filesystem/bind/fid.rs +82 -0
  18. data/crates/kobako-runtime/src/filesystem/{directory/spelling.rs → bind/lookup.rs} +91 -49
  19. data/crates/kobako-runtime/src/filesystem/bind/walk.rs +249 -0
  20. data/crates/kobako-runtime/src/filesystem/bind.rs +2665 -0
  21. data/crates/kobako-runtime/src/filesystem/overlay/file.rs +89 -0
  22. data/crates/kobako-runtime/src/filesystem/overlay/inode.rs +106 -0
  23. data/crates/kobako-runtime/src/filesystem/overlay/path.rs +285 -0
  24. data/crates/kobako-runtime/src/filesystem/overlay/stack/copy_up.rs +135 -0
  25. data/crates/kobako-runtime/src/filesystem/overlay/stack/dir.rs +263 -0
  26. data/crates/kobako-runtime/src/filesystem/overlay/stack/namei.rs +135 -0
  27. data/crates/kobako-runtime/src/filesystem/overlay/stack/readdir.rs +57 -0
  28. data/crates/kobako-runtime/src/filesystem/overlay/stack.rs +227 -734
  29. data/crates/kobako-runtime/src/filesystem/overlay.rs +2649 -1135
  30. data/crates/kobako-runtime/src/filesystem/path.rs +2 -41
  31. data/crates/kobako-runtime/src/filesystem/pipe.rs +221 -0
  32. data/crates/kobako-runtime/src/filesystem/rdir.rs +101 -0
  33. data/crates/kobako-runtime/src/filesystem/tmpfs/tmpfile.rs +32 -0
  34. data/crates/kobako-runtime/src/filesystem/tmpfs/whiteout.rs +85 -0
  35. data/crates/kobako-runtime/src/filesystem/tmpfs/xattr.rs +22 -0
  36. data/crates/kobako-runtime/src/filesystem/tmpfs.rs +1985 -0
  37. data/crates/kobako-runtime/src/filesystem/wrap.rs +224 -0
  38. data/crates/kobako-runtime/src/filesystem.rs +21 -387
  39. data/crates/kobako-runtime/src/hashtable.rs +44 -0
  40. data/crates/kobako-runtime/src/lib.rs +11 -6
  41. data/crates/kobako-runtime/src/locking/seqlock.rs +66 -0
  42. data/crates/kobako-runtime/src/locking.rs +218 -0
  43. data/crates/kobako-runtime/src/mount.rs +31 -104
  44. data/crates/kobako-runtime/src/profile.rs +137 -1
  45. data/crates/kobako-runtime/src/sched.rs +210 -0
  46. data/crates/kobako-runtime/src/security.rs +696 -0
  47. data/crates/kobako-runtime/src/vfs/attr.rs +162 -0
  48. data/crates/kobako-runtime/src/vfs/cred.rs +334 -0
  49. data/crates/kobako-runtime/src/vfs/dentry.rs +1412 -0
  50. data/crates/kobako-runtime/src/vfs/errno.rs +153 -0
  51. data/crates/kobako-runtime/src/vfs/file.rs +1915 -0
  52. data/crates/kobako-runtime/src/vfs/inode.rs +429 -0
  53. data/crates/kobako-runtime/src/vfs/kern_path.rs +217 -0
  54. data/crates/kobako-runtime/src/vfs/mount.rs +59 -0
  55. data/crates/kobako-runtime/src/vfs/namei.rs +5708 -0
  56. data/crates/kobako-runtime/src/vfs/openat2.rs +116 -0
  57. data/crates/kobako-runtime/src/vfs/path.rs +64 -0
  58. data/crates/kobako-runtime/src/vfs/readdir.rs +163 -0
  59. data/crates/kobako-runtime/src/vfs/stat.rs +157 -0
  60. data/crates/kobako-runtime/src/vfs/super_block.rs +122 -0
  61. data/crates/kobako-runtime/src/vfs/utimes.rs +289 -0
  62. data/crates/kobako-runtime/src/vfs.rs +60 -0
  63. data/crates/kobako-runtime/tests/linux_baseline/calls.rs +171 -0
  64. data/crates/kobako-runtime/tests/linux_baseline/concurrent.rs +276 -0
  65. data/crates/kobako-runtime/tests/linux_baseline/kobako.rs +323 -0
  66. data/crates/kobako-runtime/tests/linux_baseline/linux-baseline-expected.txt +16 -0
  67. data/crates/kobako-runtime/tests/linux_baseline/linux.rs +449 -0
  68. data/crates/kobako-runtime/tests/linux_baseline/main.rs +611 -0
  69. data/crates/kobako-runtime/tests/linux_baseline/permissions.rs +236 -0
  70. data/crates/kobako-transport/CHANGELOG.md +8 -2
  71. data/crates/kobako-transport/Cargo.toml +3 -3
  72. data/crates/kobako-transport/README.md +6 -5
  73. data/crates/kobako-transport/src/envelope/bytes.rs +33 -25
  74. data/crates/kobako-transport/src/envelope/call.rs +5 -5
  75. data/crates/kobako-transport/src/envelope/compile_reply.rs +2 -2
  76. data/crates/kobako-transport/src/envelope/error_record.rs +3 -3
  77. data/crates/kobako-transport/src/envelope/fault.rs +2 -2
  78. data/crates/kobako-transport/src/envelope/outcome.rs +3 -3
  79. data/crates/kobako-transport/src/envelope/preamble.rs +12 -12
  80. data/crates/kobako-transport/src/envelope/reply.rs +2 -2
  81. data/crates/kobako-transport/src/envelope/request.rs +2 -2
  82. data/crates/kobako-transport/src/envelope/run.rs +1 -1
  83. data/crates/kobako-transport/src/envelope/unsupported.rs +2 -2
  84. data/crates/kobako-transport/src/envelope.rs +2 -2
  85. data/crates/kobako-wasmtime/CHANGELOG.md +154 -8
  86. data/crates/kobako-wasmtime/Cargo.toml +15 -14
  87. data/crates/kobako-wasmtime/README.md +9 -5
  88. data/crates/kobako-wasmtime/src/abi.rs +12 -20
  89. data/crates/kobako-wasmtime/src/ambient.rs +1 -2
  90. data/crates/kobako-wasmtime/src/cache.rs +5 -5
  91. data/crates/kobako-wasmtime/src/config.rs +22 -7
  92. data/crates/kobako-wasmtime/src/descriptors.rs +133 -100
  93. data/crates/kobako-wasmtime/src/driver.rs +29 -96
  94. data/crates/kobako-wasmtime/src/exports.rs +5 -16
  95. data/crates/kobako-wasmtime/src/guest.rs +38 -0
  96. data/crates/kobako-wasmtime/src/imports.rs +14 -8
  97. data/crates/kobako-wasmtime/src/instance_pre.rs +3 -3
  98. data/crates/kobako-wasmtime/src/invocation.rs +26 -46
  99. data/crates/kobako-wasmtime/src/lib.rs +1 -4
  100. data/crates/kobako-wasmtime/src/limiter.rs +6 -230
  101. data/crates/kobako-wasmtime/src/preview1/differential/mount.rs +69 -46
  102. data/crates/kobako-wasmtime/src/preview1/differential.rs +121 -7
  103. data/crates/kobako-wasmtime/src/preview1/errno.rs +74 -206
  104. data/crates/kobako-wasmtime/src/preview1/fd.rs +1248 -500
  105. data/crates/kobako-wasmtime/src/preview1/memory.rs +117 -150
  106. data/crates/kobako-wasmtime/src/preview1/mount_view/wrap.rs +211 -0
  107. data/crates/kobako-wasmtime/src/preview1/mount_view.rs +1939 -672
  108. data/crates/kobako-wasmtime/src/preview1/path.rs +230 -349
  109. data/crates/kobako-wasmtime/src/preview1/poll.rs +148 -100
  110. data/crates/kobako-wasmtime/src/preview1/sock.rs +6 -23
  111. data/crates/kobako-wasmtime/src/preview1/testsuite-expected.txt +6 -0
  112. data/crates/kobako-wasmtime/src/preview1/testsuite.rs +157 -0
  113. data/crates/kobako-wasmtime/src/preview1.rs +464 -413
  114. data/crates/kobako-wasmtime/src/trap.rs +9 -9
  115. data/crates/kobako-wasmtime/src/wasi.rs +53 -37
  116. data/crates/kobako-wasmtime/tests/{guest_file.rs → integration/guest_file.rs} +232 -201
  117. data/crates/kobako-wasmtime/tests/integration/main.rs +29 -0
  118. data/crates/kobako-wasmtime/tests/{preamble_flags.rs → integration/preamble_flags.rs} +1 -1
  119. data/crates/kobako-wasmtime/tests/{snippet_compile.rs → integration/snippet_compile.rs} +1 -1
  120. data/crates/kobako-wasmtime/tests/{unanswered_call.rs → integration/unanswered_call.rs} +1 -1
  121. data/crates/kobako-wasmtime/tests/{unsupported_call.rs → integration/unsupported_call.rs} +1 -1
  122. data/crates/kobako-wasmtime/witx/typenames.witx +750 -0
  123. data/crates/kobako-wasmtime/witx/wasi_snapshot_preview1.witx +441 -0
  124. data/data/kobako.wasm +0 -0
  125. data/ext/kobako/Cargo.toml +1 -1
  126. data/ext/kobako/src/runtime/bridge.rs +1 -1
  127. data/ext/kobako/src/runtime/errors.rs +1 -1
  128. data/ext/kobako/src/runtime/escape.rs +28 -14
  129. data/ext/kobako/src/runtime/filesystem/grants.rs +21 -5
  130. data/ext/kobako/src/runtime/filesystem/ruby.rs +464 -156
  131. data/ext/kobako/src/runtime/filesystem.rs +109 -137
  132. data/ext/kobako/src/runtime/gvl.rs +43 -8
  133. data/ext/kobako/src/runtime/snapshot.rs +1 -1
  134. data/ext/kobako/src/runtime.rs +6 -1
  135. data/lib/kobako/catalog/extensions.rb +5 -5
  136. data/lib/kobako/catalog/flags.rb +2 -2
  137. data/lib/kobako/catalog/handles.rb +6 -6
  138. data/lib/kobako/catalog/mount.rb +2 -10
  139. data/lib/kobako/catalog/services.rb +2 -2
  140. data/lib/kobako/catalog/snippets.rb +7 -7
  141. data/lib/kobako/catalog.rb +1 -1
  142. data/lib/kobako/codec/decoder.rb +10 -10
  143. data/lib/kobako/codec/ext_types.rb +3 -5
  144. data/lib/kobako/codec/handle_walk.rb +4 -6
  145. data/lib/kobako/codec/nesting.rb +7 -7
  146. data/lib/kobako/codec/utils.rb +1 -1
  147. data/lib/kobako/codec/value_budget.rb +3 -3
  148. data/lib/kobako/context.rb +5 -5
  149. data/lib/kobako/filesystem.rb +44 -13
  150. data/lib/kobako/payload/arguments.rb +2 -2
  151. data/lib/kobako/pool.rb +4 -4
  152. data/lib/kobako/sandbox.rb +28 -28
  153. data/lib/kobako/sandbox_options.rb +2 -3
  154. data/lib/kobako/snippet/bytecode.rb +1 -1
  155. data/lib/kobako/snippet/source.rb +1 -1
  156. data/lib/kobako/transport/dispatcher.rb +5 -5
  157. data/lib/kobako/transport/exposure.rb +2 -2
  158. data/lib/kobako/transport/reflection.rb +7 -7
  159. data/lib/kobako/transport/run.rb +5 -5
  160. data/lib/kobako/transport/yielder.rb +1 -1
  161. data/lib/kobako/version.rb +1 -1
  162. data/lib/kobako.rb +34 -0
  163. data/release-please-config.json +1 -0
  164. data/sig/kobako/catalog/extensions.rbs +3 -3
  165. data/sig/kobako/catalog/handles.rbs +3 -3
  166. data/sig/kobako/catalog/mount.rbs +0 -4
  167. data/sig/kobako/catalog/services.rbs +1 -1
  168. data/sig/kobako/catalog/snippets.rbs +3 -3
  169. data/sig/kobako/catalog.rbs +1 -1
  170. data/sig/kobako/codec/decoder.rbs +1 -1
  171. data/sig/kobako/codec/handle_walk.rbs +1 -1
  172. data/sig/kobako/codec/nesting.rbs +2 -2
  173. data/sig/kobako/codec/utils.rbs +1 -1
  174. data/sig/kobako/codec/value_budget.rbs +1 -1
  175. data/sig/kobako/codec.rbs +1 -2
  176. data/sig/kobako/context.rbs +1 -1
  177. data/sig/kobako/filesystem.rbs +19 -14
  178. data/sig/kobako/guest_yielder.rbs +1 -1
  179. data/sig/kobako/payload/arguments.rbs +1 -1
  180. data/sig/kobako/pool.rbs +2 -2
  181. data/sig/kobako/runtime.rbs +1 -1
  182. data/sig/kobako/sandbox.rbs +3 -5
  183. data/sig/kobako/sandbox_options.rbs +1 -1
  184. data/sig/kobako/transport/dispatcher.rbs +2 -2
  185. data/sig/kobako/transport/exposure.rbs +1 -1
  186. data/sig/kobako/transport/reflection.rbs +2 -2
  187. data/sig/kobako/transport/run.rbs +3 -3
  188. data/sig/kobako/transport.rbs +1 -1
  189. metadata +67 -19
  190. data/crates/kobako-runtime/src/filesystem/directory.rs +0 -1082
  191. data/crates/kobako-runtime/src/filesystem/memory.rs +0 -1100
  192. data/crates/kobako-runtime/src/filesystem/overlay/exclusive.rs +0 -88
  193. data/crates/kobako-runtime/src/filesystem/temporary.rs +0 -167
  194. data/crates/kobako-wasmtime/src/capture.rs +0 -96
  195. data/crates/kobako-wasmtime/src/dispatch.rs +0 -125
  196. data/crates/kobako-wasmtime/src/guest_mem.rs +0 -271
  197. data/crates/kobako-wasmtime/src/io.rs +0 -89
  198. data/crates/kobako-wasmtime/src/preview1/args.rs +0 -60
  199. data/crates/kobako-wasmtime/src/preview1/beneath.rs +0 -188
  200. /data/crates/kobako-wasmtime/tests/{compile_posture.rs → integration/compile_posture.rs} +0 -0
  201. /data/crates/kobako-wasmtime/tests/{support → integration/support}/mod.rs +0 -0
@@ -1,32 +1,34 @@
1
- //! The guest's linear memory as one preview1 call sees it: little-endian
2
- //! fields at guest addresses. An address outside the memory, or one not
3
- //! aligned for what it points at, traps, as preview1 requires.
1
+ //! The guest's linear memory as one preview1 call reads what its generated
2
+ //! glue left as pointers. An address outside the memory traps, as preview1
3
+ //! requires.
4
4
 
5
5
  use std::cell::Cell;
6
- use std::ops::Range;
7
6
 
8
- use super::beneath::PATH_MAX;
9
- use super::errno::{Answer, Errno, Failure};
7
+ use wiggle::{GuestPtr, GuestType};
10
8
 
11
- fn out_of_bounds() -> Failure {
12
- Failure::trap("pointer out of bounds of guest memory")
13
- }
9
+ use super::errno::{Answer, Errno};
10
+ use super::types;
14
11
 
15
- /// The most of the guest's memory one call may have the host copy or
16
- /// walk, as wasmtime's default hostcall fuel bounds a preview1 call; past
17
- /// it the call answers NOMEM.
18
- const HOSTCALL_FUEL: u64 = 128 << 20;
12
+ /// A memory shared between threads hands out no slice to read or write, and
13
+ /// no Guest Binary declares one.
14
+ fn require_unshared<T>(slice: Option<T>) -> Answer<T> {
15
+ slice.ok_or_else(|| {
16
+ types::Error::trap(wasmtime::Error::msg(
17
+ "a preview1 call needs the guest's memory unshared",
18
+ ))
19
+ })
20
+ }
19
21
 
20
- pub(crate) struct GuestMemory<'a> {
21
- bytes: &'a mut [u8],
22
+ pub(crate) struct GuestMemory<'a, 'm> {
23
+ memory: &'a mut wiggle::GuestMemory<'m>,
22
24
  fuel: Cell<u64>,
23
25
  }
24
26
 
25
- impl<'a> GuestMemory<'a> {
26
- pub(crate) fn new(bytes: &'a mut [u8]) -> Self {
27
+ impl<'a, 'm> GuestMemory<'a, 'm> {
28
+ pub(crate) fn new(memory: &'a mut wiggle::GuestMemory<'m>, fuel: usize) -> Self {
27
29
  Self {
28
- bytes,
29
- fuel: Cell::new(HOSTCALL_FUEL),
30
+ memory,
31
+ fuel: Cell::new(fuel as u64),
30
32
  }
31
33
  }
32
34
 
@@ -39,129 +41,67 @@ impl<'a> GuestMemory<'a> {
39
41
  Ok(())
40
42
  }
41
43
 
42
- fn range(&self, ptr: u32, len: u32) -> Answer<Range<usize>> {
43
- let start = ptr as usize;
44
- match start.checked_add(len as usize) {
45
- Some(end) if end <= self.bytes.len() => Ok(start..end),
46
- _ => Err(out_of_bounds()),
47
- }
48
- }
49
-
50
- /// Hold `ptr` to the alignment of the struct or integer it points at.
51
- pub(crate) fn check_aligned(ptr: u32, align: u32) -> Answer {
52
- if !ptr.is_multiple_of(align) {
53
- return Err(Failure::trap("pointer not aligned for what it points at"));
54
- }
55
- Ok(())
56
- }
57
-
58
- /// The address of element `index` of an array of `size`-byte elements
59
- /// at `base`.
60
- pub(crate) fn element(base: u32, index: u32, size: u32) -> Answer<u32> {
61
- Self::field(base, index.checked_mul(size).ok_or_else(out_of_bounds)?)
62
- }
63
-
64
- /// The address `offset` bytes past `base`, which traps rather than
65
- /// wrapping past the end of the address space.
66
- pub(crate) fn field(base: u32, offset: u32) -> Answer<u32> {
67
- base.checked_add(offset).ok_or_else(out_of_bounds)
68
- }
69
-
70
- pub(crate) fn bytes(&self, ptr: u32, len: u32) -> Answer<&[u8]> {
71
- let range = self.range(ptr, len)?;
72
- Ok(&self.bytes[range])
73
- }
74
-
75
- pub(crate) fn bytes_mut(&mut self, ptr: u32, len: u32) -> Answer<&mut [u8]> {
76
- let range = self.range(ptr, len)?;
77
- Ok(&mut self.bytes[range])
78
- }
79
-
80
- fn read<const N: usize>(&self, ptr: u32) -> Answer<[u8; N]> {
81
- Self::check_aligned(ptr, N as u32)?;
82
- let mut out = [0; N];
83
- out.copy_from_slice(self.bytes(ptr, N as u32)?);
84
- Ok(out)
85
- }
86
-
87
- pub(crate) fn read_u8(&self, ptr: u32) -> Answer<u8> {
88
- self.read::<1>(ptr).map(|b| b[0])
89
- }
90
-
91
- pub(crate) fn read_u16(&self, ptr: u32) -> Answer<u16> {
92
- self.read(ptr).map(u16::from_le_bytes)
93
- }
94
-
95
- pub(crate) fn read_u32(&self, ptr: u32) -> Answer<u32> {
96
- self.read(ptr).map(u32::from_le_bytes)
97
- }
98
-
99
- pub(crate) fn read_u64(&self, ptr: u32) -> Answer<u64> {
100
- self.read(ptr).map(u64::from_le_bytes)
101
- }
102
-
103
- /// Copy `bytes` into guest memory at `ptr`, which needs no alignment.
104
- pub(crate) fn write(&mut self, ptr: u32, bytes: &[u8]) -> Answer {
105
- self.bytes_mut(ptr, bytes.len() as u32)?
106
- .copy_from_slice(bytes);
107
- Ok(())
108
- }
109
-
110
- fn write_aligned<const N: usize>(&mut self, ptr: u32, bytes: [u8; N]) -> Answer {
111
- Self::check_aligned(ptr, N as u32)?;
112
- self.write(ptr, &bytes)
44
+ pub(crate) fn bytes(&self, ptr: GuestPtr<[u8]>) -> Answer<&[u8]> {
45
+ require_unshared(self.memory.as_slice(ptr)?)
113
46
  }
114
47
 
115
- pub(crate) fn write_u8(&mut self, ptr: u32, value: u8) -> Answer {
116
- self.write(ptr, &[value])
48
+ pub(crate) fn bytes_mut(&mut self, ptr: GuestPtr<[u8]>) -> Answer<&mut [u8]> {
49
+ require_unshared(self.memory.as_slice_mut(ptr)?)
117
50
  }
118
51
 
119
- pub(crate) fn write_u16(&mut self, ptr: u32, value: u16) -> Answer {
120
- self.write_aligned(ptr, value.to_le_bytes())
52
+ pub(crate) fn read<T: GuestType>(&self, ptr: GuestPtr<T>) -> Answer<T> {
53
+ Ok(self.memory.read(ptr)?)
121
54
  }
122
55
 
123
- pub(crate) fn write_u32(&mut self, ptr: u32, value: u32) -> Answer {
124
- self.write_aligned(ptr, value.to_le_bytes())
56
+ pub(crate) fn write<T: GuestType>(&mut self, ptr: GuestPtr<T>, value: T) -> Answer {
57
+ Ok(self.memory.write(ptr, value)?)
125
58
  }
126
59
 
127
- pub(crate) fn write_u64(&mut self, ptr: u32, value: u64) -> Answer {
128
- self.write_aligned(ptr, value.to_le_bytes())
60
+ /// Copy `bytes` into guest memory at `ptr`.
61
+ pub(crate) fn write_bytes(&mut self, ptr: GuestPtr<u8>, bytes: &[u8]) -> Answer {
62
+ Ok(self
63
+ .memory
64
+ .copy_from_slice(bytes, ptr.as_array(bytes.len() as u32))?)
129
65
  }
130
66
 
131
67
  /// A guest path, which preview1 requires to be UTF-8, charged to the
132
- /// call's fuel before it is read, as wasmtime-wasi charges it. One
133
- /// that would not fit Linux's `PATH_MAX` with its terminating NUL is
134
- /// refused before it is copied, as Linux's `getname` refuses it, and
135
- /// one holding a NUL names nothing a host can hold, as cap-std refuses
136
- /// it.
137
- pub(crate) fn path(&self, ptr: u32, len: u32) -> Answer<String> {
138
- self.consume(len, 1)?;
139
- let bytes = self.bytes(ptr, len)?;
140
- if bytes.len() >= PATH_MAX {
141
- return Err(Errno::Nametoolong.into());
142
- }
143
- if bytes.contains(&0) {
144
- return Err(Errno::Inval.into());
145
- }
146
- String::from_utf8(bytes.to_vec()).map_err(|_| Errno::Ilseq.into())
147
- }
148
-
149
- /// The first non-empty buffer of an `iovec` / `ciovec` array, as
150
- /// `(ptr, len)`, or an empty one when every buffer is empty. Like
151
- /// wasmtime-wasi, kobako moves one buffer per call, and the buffer
152
- /// itself is bounded only once the call has found its descriptor.
153
- pub(crate) fn first_non_empty_buffer(&self, array: u32, count: u32) -> Answer<(u32, u32)> {
154
- self.consume(count, 8)?;
155
- for index in 0..count {
156
- let entry = Self::element(array, index, 8)?;
157
- let ptr = self.read_u32(entry)?;
158
- let len = self.read_u32(Self::field(entry, 4)?)?;
68
+ /// call's fuel before it is read, as wasmtime-wasi charges it.
69
+ pub(crate) fn path(&self, ptr: GuestPtr<str>) -> Answer<String> {
70
+ self.consume(ptr.len(), 1)?;
71
+ let bytes = self.memory.as_cow(ptr.as_bytes())?;
72
+ String::from_utf8(bytes.into_owned()).map_err(|_| Errno::Ilseq.into())
73
+ }
74
+
75
+ /// The first non-empty buffer of `iovs`, or an empty one when every
76
+ /// buffer is empty. Like wasmtime-wasi, kobako moves one buffer per call,
77
+ /// and the buffer itself is bounded only once the call has found its
78
+ /// descriptor.
79
+ pub(crate) fn first_non_empty_iovec(&self, iovs: types::IovecArray) -> Answer<GuestPtr<[u8]>> {
80
+ self.first_non_empty(iovs, |iov: types::Iovec| (iov.buf, iov.buf_len))
81
+ }
82
+
83
+ /// The first non-empty buffer of `ciovs`, as `first_non_empty_iovec`.
84
+ pub(crate) fn first_non_empty_ciovec(
85
+ &self,
86
+ ciovs: types::CiovecArray,
87
+ ) -> Answer<GuestPtr<[u8]>> {
88
+ self.first_non_empty(ciovs, |ciov: types::Ciovec| (ciov.buf, ciov.buf_len))
89
+ }
90
+
91
+ fn first_non_empty<T: GuestType>(
92
+ &self,
93
+ array: GuestPtr<[T]>,
94
+ buffer: impl Fn(T) -> (GuestPtr<u8>, u32),
95
+ ) -> Answer<GuestPtr<[u8]>> {
96
+ self.consume(array.len(), T::guest_size())?;
97
+ for entry in array.iter() {
98
+ let (ptr, len) = buffer(self.memory.read(entry?)?);
159
99
  if len != 0 {
160
100
  self.consume(len, 1)?;
161
- return Ok((ptr, len));
101
+ return Ok(ptr.as_array(len));
162
102
  }
163
103
  }
164
- Ok((0, 0))
104
+ Ok(GuestPtr::new((0, 0)))
165
105
  }
166
106
  }
167
107
 
@@ -169,37 +109,64 @@ impl<'a> GuestMemory<'a> {
169
109
  mod tests {
170
110
  use super::*;
171
111
 
112
+ /// The most of a guest's memory one call may have the host copy or walk,
113
+ /// which wasmtime hands each call by default.
114
+ const HOSTCALL_FUEL: usize = 128 << 20;
115
+
116
+ // Linux's `readv` would move all three buffers, so only the first with room
117
+ // being the one handed back shows the encoding moves one per call.
118
+ // @behavior RT-284
119
+ #[test]
120
+ fn a_read_or_write_moves_its_first_buffer_with_room() {
121
+ #[repr(align(8))]
122
+ struct AlignedBytes([u8; 64]);
123
+
124
+ // Three iovecs at 0: empty at 32, three bytes at 40, two at 48.
125
+ let mut bytes = AlignedBytes([0; 64]);
126
+ for (at, (buf, len)) in [(32u32, 0u32), (40, 3), (48, 2)].into_iter().enumerate() {
127
+ bytes.0[at * 8..at * 8 + 4].copy_from_slice(&buf.to_le_bytes());
128
+ bytes.0[at * 8 + 4..at * 8 + 8].copy_from_slice(&len.to_le_bytes());
129
+ }
130
+ let mut memory = wiggle::GuestMemory::Unshared(&mut bytes.0);
131
+ let taken = GuestMemory::new(&mut memory, HOSTCALL_FUEL)
132
+ .first_non_empty_iovec(GuestPtr::new((0, 3)))
133
+ .unwrap_or_else(|_| panic!("the iovecs must be read"));
134
+ assert_eq!(
135
+ (taken.offset_base(), taken.len()),
136
+ (40, 3),
137
+ "a preview1 read handed an empty buffer, then three bytes, then two, must move only \
138
+ the three bytes, as wasmtime-wasi does"
139
+ );
140
+ }
141
+
172
142
  // Neither the array nor the buffer exists in this small memory, so the
173
143
  // budget alone, spent before anything is read, refuses each.
174
144
  // @behavior RT-096
175
145
  #[test]
176
146
  fn a_call_past_its_hostcall_fuel_answers_nomem() {
177
- let mut array_bytes = [0u8; 64];
178
- let mut buffer_bytes = [0u8; 64];
179
- buffer_bytes[4..8].copy_from_slice(&(128u32 << 20).wrapping_add(1).to_le_bytes());
180
- assert!(
181
- matches!(
182
- (
183
- GuestMemory::new(&mut array_bytes).first_non_empty_buffer(0, (16 << 20) + 1),
184
- GuestMemory::new(&mut buffer_bytes).first_non_empty_buffer(0, 1),
185
- ),
186
- (
187
- Err(Failure::Errno(Errno::Nomem)),
188
- Err(Failure::Errno(Errno::Nomem))
189
- )
147
+ // A guest's memory starts aligned, and wiggle refuses an `iovec` at a
148
+ // host address that is not.
149
+ #[repr(align(8))]
150
+ struct AlignedBytes([u8; 64]);
151
+
152
+ let mut array_bytes = AlignedBytes([0; 64]);
153
+ let mut buffer_bytes = AlignedBytes([0; 64]);
154
+ buffer_bytes.0[4..8].copy_from_slice(&(128u32 << 20).wrapping_add(1).to_le_bytes());
155
+ let answer = |bytes: &mut [u8], count: u32| {
156
+ let mut memory = wiggle::GuestMemory::Unshared(bytes);
157
+ GuestMemory::new(&mut memory, HOSTCALL_FUEL)
158
+ .first_non_empty_iovec(GuestPtr::new((0, count)))
159
+ .err()
160
+ .and_then(|err| err.downcast().ok())
161
+ };
162
+ assert_eq!(
163
+ (
164
+ answer(&mut array_bytes.0, (16 << 20) + 1),
165
+ answer(&mut buffer_bytes.0, 1)
190
166
  ),
167
+ (Some(Errno::Nomem), Some(Errno::Nomem)),
191
168
  "an iovec array or a buffer past 128 MiB through one preview1 call must answer \
192
169
  NOMEM before the host copies any of it"
193
170
  );
194
171
  }
195
-
196
- // A guest memory of the full 4 GiB lets a field's address pass the end
197
- // of the address space, which only the arithmetic itself can refuse.
198
- #[test]
199
- fn a_field_past_the_address_space_traps_rather_than_wrapping() {
200
- assert!(
201
- matches!(GuestMemory::field(u32::MAX - 4, 8), Err(Failure::Trap(_))),
202
- "a field address past u32::MAX through GuestMemory::field must trap rather than wrap"
203
- );
204
- }
205
172
  }
@@ -0,0 +1,211 @@
1
+ //! A test's Filesystem over another: every call forwards to the entry it
2
+ //! wraps unless its `Hook` steps in, and every entry a lookup finds is
3
+ //! wrapped too, as a Filesystem wrapping another must. It makes a file
4
+ //! through `create` and leaves the open to the VFS, as overlayfs does, so an
5
+ //! open's lookup reaches the `Hook` as any lookup does. It hands the
6
+ //! Filesystem beneath the dentry it is handed, where overlayfs hands a real
7
+ //! one, so what that Filesystem makes is recorded unwrapped and later calls
8
+ //! on it reach that Filesystem past the `Hook`.
9
+
10
+ use std::any::Any;
11
+ use std::marker::PhantomData;
12
+ use std::sync::Arc;
13
+
14
+ use kobako_runtime::filesystem::{
15
+ DentryOperations, FileOperations, FileSystemType, Filesystem, FilesystemError, InodeOperations,
16
+ Kstat, Lookup, OpenHow, RenameFlag,
17
+ };
18
+ use kobako_runtime::vfs::{Dentry, Inode};
19
+
20
+ /// What a test changes of the Filesystem beneath it.
21
+ pub(super) trait Hook: Send + Sync + 'static {
22
+ fn getattr(
23
+ &self,
24
+ inner: &dyn InodeOperations,
25
+ dentry: &Dentry,
26
+ ) -> Result<Kstat, FilesystemError> {
27
+ inner.getattr(dentry)
28
+ }
29
+
30
+ fn lookup(
31
+ &self,
32
+ inner: &dyn InodeOperations,
33
+ dentry: &Dentry,
34
+ ) -> Result<Lookup, FilesystemError> {
35
+ inner.lookup(dentry)
36
+ }
37
+
38
+ fn fill_super(
39
+ &self,
40
+ fs: Arc<dyn FileSystemType>,
41
+ sb: &mut Filesystem,
42
+ ) -> Result<(), FilesystemError> {
43
+ fs.fill_super(sb)
44
+ }
45
+
46
+ fn open(
47
+ &self,
48
+ inner: &dyn InodeOperations,
49
+ dentry: &Dentry,
50
+ how: &OpenHow,
51
+ ) -> Result<Box<dyn FileOperations>, FilesystemError> {
52
+ inner.open(dentry, how)
53
+ }
54
+
55
+ fn mkdir(&self, inner: &dyn InodeOperations, dentry: &Dentry) -> Result<(), FilesystemError> {
56
+ inner.mkdir(dentry)
57
+ }
58
+
59
+ fn rmdir(&self, inner: &dyn InodeOperations, dentry: &Dentry) -> Result<(), FilesystemError> {
60
+ inner.rmdir(dentry)
61
+ }
62
+
63
+ fn unlink(&self, inner: &dyn InodeOperations, dentry: &Dentry) -> Result<(), FilesystemError> {
64
+ inner.unlink(dentry)
65
+ }
66
+
67
+ fn rename(
68
+ &self,
69
+ inner: &dyn InodeOperations,
70
+ from: &Dentry,
71
+ to_dir: &dyn InodeOperations,
72
+ to: &Dentry,
73
+ flag: Option<RenameFlag>,
74
+ ) -> Result<(), FilesystemError> {
75
+ inner.rename(from, to_dir, to, flag)
76
+ }
77
+ }
78
+
79
+ /// A Filesystem over the one `fs` makes, with `hook` stepping in.
80
+ pub(super) struct Wrap<H> {
81
+ fs: Arc<dyn FileSystemType>,
82
+ hook: Arc<H>,
83
+ }
84
+
85
+ impl<H: Hook> Wrap<H> {
86
+ pub(super) fn new(fs: Arc<dyn FileSystemType>, hook: Arc<H>) -> Arc<Self> {
87
+ Arc::new(Self { fs, hook })
88
+ }
89
+ }
90
+
91
+ impl<H: Hook> FileSystemType for Wrap<H> {
92
+ fn fill_super(self: Arc<Self>, sb: &mut Filesystem) -> Result<(), FilesystemError> {
93
+ self.hook.fill_super(self.fs.clone(), sb)?;
94
+ if let Some(ops) = sb.d_op().cloned() {
95
+ sb.set_default_d_op(Arc::new(WrapDentry::<H> {
96
+ ops,
97
+ hook: PhantomData,
98
+ }));
99
+ }
100
+ if let Some(root) = sb.root().and_then(|root| root.inode()) {
101
+ let wrapped = WrapInode {
102
+ inner: root.ops().clone(),
103
+ hook: self.hook.clone(),
104
+ };
105
+ sb.d_make_root(root.with_ops(Arc::new(wrapped)));
106
+ }
107
+ Ok(())
108
+ }
109
+ }
110
+
111
+ /// `inner`, an entry of the Filesystem beneath, with `hook` stepping in.
112
+ struct WrapInode<H> {
113
+ inner: Arc<dyn InodeOperations>,
114
+ hook: Arc<H>,
115
+ }
116
+
117
+ impl<H: Hook> WrapInode<H> {
118
+ /// `found` with the entry it names wrapped too.
119
+ fn wrap(&self, found: Lookup) -> Lookup {
120
+ let wrapped = |inode: Arc<Inode>| {
121
+ let ops = Self {
122
+ inner: inode.ops().clone(),
123
+ hook: self.hook.clone(),
124
+ };
125
+ inode.with_ops(Arc::new(ops))
126
+ };
127
+ match found {
128
+ Lookup::Found(inode) => Lookup::Found(wrapped(inode)),
129
+ Lookup::Spelled { name, inode } => Lookup::Spelled {
130
+ name,
131
+ inode: wrapped(inode),
132
+ },
133
+ Lookup::Absent => Lookup::Absent,
134
+ }
135
+ }
136
+ }
137
+
138
+ /// `ops`, handed the directory a `WrapInode<H>` wraps.
139
+ struct WrapDentry<H> {
140
+ ops: Arc<dyn DentryOperations>,
141
+ hook: PhantomData<H>,
142
+ }
143
+
144
+ impl<H: Hook> DentryOperations for WrapDentry<H> {
145
+ fn revalidate(
146
+ &self,
147
+ dir: &dyn InodeOperations,
148
+ dentry: &Dentry,
149
+ ) -> Result<bool, FilesystemError> {
150
+ let dir = (dir as &dyn Any)
151
+ .downcast_ref::<WrapInode<H>>()
152
+ .map_or(dir, |wrap| wrap.inner.as_ref());
153
+ self.ops.revalidate(dir, dentry)
154
+ }
155
+ }
156
+
157
+ impl<H: Hook> InodeOperations for WrapInode<H> {
158
+ fn getattr(&self, dentry: &Dentry) -> Result<Kstat, FilesystemError> {
159
+ self.hook.getattr(self.inner.as_ref(), dentry)
160
+ }
161
+
162
+ fn setattr(
163
+ &self,
164
+ dentry: &Dentry,
165
+ attr: &kobako_runtime::filesystem::Iattr,
166
+ ) -> Result<(), FilesystemError> {
167
+ self.inner.setattr(dentry, attr)
168
+ }
169
+
170
+ fn lookup(&self, dentry: &Dentry) -> Result<Lookup, FilesystemError> {
171
+ Ok(self.wrap(self.hook.lookup(self.inner.as_ref(), dentry)?))
172
+ }
173
+
174
+ fn open(
175
+ &self,
176
+ dentry: &Dentry,
177
+ how: &OpenHow,
178
+ ) -> Result<Box<dyn FileOperations>, FilesystemError> {
179
+ self.hook.open(self.inner.as_ref(), dentry, how)
180
+ }
181
+
182
+ fn create(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
183
+ self.inner.create(dentry)
184
+ }
185
+
186
+ fn mkdir(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
187
+ self.hook.mkdir(self.inner.as_ref(), dentry)
188
+ }
189
+
190
+ fn rmdir(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
191
+ self.hook.rmdir(self.inner.as_ref(), dentry)
192
+ }
193
+
194
+ fn unlink(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
195
+ self.hook.unlink(self.inner.as_ref(), dentry)
196
+ }
197
+
198
+ fn rename(
199
+ &self,
200
+ from: &Dentry,
201
+ to_dir: &dyn InodeOperations,
202
+ to: &Dentry,
203
+ flag: Option<RenameFlag>,
204
+ ) -> Result<(), FilesystemError> {
205
+ let target = (to_dir as &dyn Any)
206
+ .downcast_ref::<Self>()
207
+ .ok_or(FilesystemError::CrossesDevices)?;
208
+ self.hook
209
+ .rename(self.inner.as_ref(), from, target.inner.as_ref(), to, flag)
210
+ }
211
+ }