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
@@ -10,67 +10,51 @@
10
10
  use std::thread;
11
11
  use std::time::{Duration, Instant};
12
12
 
13
- use super::errno::{Answer, Errno, Failure};
13
+ use kobako_runtime::vfs::PollMask;
14
+ use wiggle::{GuestPtr, GuestType};
15
+
16
+ use super::errno::{Answer, Errno};
17
+ use super::fd::file;
14
18
  use super::memory::GuestMemory;
15
- use crate::descriptors::Descriptor;
19
+ use super::types::{self, Clockid, Eventrwflags, Eventtype, Subclockflags, SubscriptionU};
16
20
  use crate::invocation::Invocation;
17
21
  use crate::trap::TimeoutTrap;
18
22
 
19
- const SUBSCRIPTION_SIZE: u32 = 48;
20
- const EVENT_SIZE: u32 = 32;
21
-
22
- const TAG_CLOCK: u8 = 0;
23
- const TAG_FD_READ: u8 = 1;
24
- const TAG_FD_WRITE: u8 = 2;
25
-
26
- const CLOCK_REALTIME: u32 = 0;
27
- const CLOCK_MONOTONIC: u32 = 1;
28
- /// The highest `clockid` variant.
29
- const CLOCK_MAX: u32 = 3;
30
- const SUBCLOCKFLAGS_ABSTIME: u16 = 1;
31
-
32
- /// One subscription as the guest wrote it.
23
+ /// One subscription as the guest wrote it, and the error its event reports.
33
24
  struct Subscription {
34
25
  userdata: u64,
35
26
  kind: Kind,
27
+ error: Errno,
36
28
  }
37
29
 
38
30
  enum Kind {
39
31
  /// A clock, as the nanoseconds left before it fires.
40
32
  Clock(u64),
41
- /// A descriptor to read; `hangup` when a file has nothing left past
42
- /// its position.
43
- FdRead {
44
- hangup: bool,
45
- },
33
+ FdRead,
46
34
  FdWrite,
47
35
  }
48
36
 
49
- const EVENTRWFLAGS_HANGUP: u16 = 1;
50
-
51
37
  pub(crate) fn poll_oneoff(
52
38
  invocation: &Invocation,
53
- mem: &mut GuestMemory,
54
- subs: u32,
55
- events: u32,
39
+ mem: &mut GuestMemory<'_, '_>,
40
+ subs: GuestPtr<types::Subscription>,
41
+ events: GuestPtr<types::Event>,
56
42
  count: u32,
57
- nevents: u32,
58
- ) -> Answer {
43
+ ) -> Answer<types::Size> {
59
44
  if count == 0 {
60
45
  return Err(Errno::Inval.into());
61
46
  }
62
- mem.consume(count, SUBSCRIPTION_SIZE)?;
63
- mem.consume(count, EVENT_SIZE)?;
64
- let subscriptions = (0..count)
65
- .map(|index| {
66
- let sub = GuestMemory::element(subs, index, SUBSCRIPTION_SIZE)?;
67
- read_subscription(invocation, mem, sub)
68
- })
47
+ mem.consume(count, types::Subscription::guest_size())?;
48
+ mem.consume(count, types::Event::guest_size())?;
49
+ let subscriptions = subs
50
+ .as_array(count)
51
+ .iter()
52
+ .map(|sub| read_subscription(invocation, mem, sub?))
69
53
  .collect::<Answer<Vec<_>>>()?;
70
54
 
71
55
  let clocks = subscriptions.iter().filter_map(|sub| match sub.kind {
72
56
  Kind::Clock(left) => Some(left),
73
- Kind::FdRead { .. } | Kind::FdWrite => None,
57
+ Kind::FdRead | Kind::FdWrite => None,
74
58
  });
75
59
  let waited = if clocks.clone().count() == subscriptions.len() {
76
60
  let soonest = clocks.min().unwrap_or(0);
@@ -84,71 +68,77 @@ pub(crate) fn poll_oneoff(
84
68
  for sub in &subscriptions {
85
69
  let (ready, nbytes) = match sub.kind {
86
70
  Kind::Clock(left) => (left <= waited, 0),
87
- Kind::FdRead { .. } | Kind::FdWrite => (true, 1),
71
+ Kind::FdRead | Kind::FdWrite => (true, u64::from(sub.error == Errno::Success)),
88
72
  };
89
73
  if ready {
90
- let event = GuestMemory::element(events, written, EVENT_SIZE)?;
91
- write_event(mem, event, sub, nbytes)?;
74
+ mem.write(events.add(written)?, to_event(sub, nbytes))?;
92
75
  written += 1;
93
76
  }
94
77
  }
95
- mem.write_u32(nevents, written)
78
+ Ok(written)
96
79
  }
97
80
 
98
- fn read_subscription(invocation: &Invocation, mem: &GuestMemory, sub: u32) -> Answer<Subscription> {
99
- let userdata = mem.read_u64(sub)?;
100
- let kind = match mem.read_u8(GuestMemory::field(sub, 8)?)? {
101
- TAG_CLOCK => read_clock(invocation, mem, sub)?,
102
- TAG_FD_READ => match descriptor(invocation, mem, sub)? {
103
- Descriptor::Stdin(_) => Kind::FdRead { hangup: false },
104
- Descriptor::File(file) => Kind::FdRead {
105
- hangup: file.file.metadata()?.len <= file.position,
106
- },
107
- Descriptor::Channel(_) | Descriptor::Directory(_) => return Err(Errno::Badf.into()),
108
- },
109
- TAG_FD_WRITE => match descriptor(invocation, mem, sub)? {
110
- Descriptor::Channel(_) => Kind::FdWrite,
111
- Descriptor::File(file) if file.mount.allows_writes() => Kind::FdWrite,
112
- Descriptor::File(_) => return Err(Errno::Perm.into()),
113
- Descriptor::Stdin(_) | Descriptor::Directory(_) => return Err(Errno::Badf.into()),
114
- },
115
- _ => return Err(Errno::Inval.into()),
81
+ fn read_subscription(
82
+ invocation: &Invocation,
83
+ mem: &GuestMemory<'_, '_>,
84
+ sub: GuestPtr<types::Subscription>,
85
+ ) -> Answer<Subscription> {
86
+ let sub = mem.read(sub)?;
87
+ let (kind, error) = match sub.u {
88
+ SubscriptionU::Clock(clock) => (read_clock(invocation, &clock)?, Errno::Success),
89
+ SubscriptionU::FdRead(subscribed) => (
90
+ Kind::FdRead,
91
+ poll_descriptor(invocation, subscribed, PollMask::IN)?,
92
+ ),
93
+ SubscriptionU::FdWrite(subscribed) => (
94
+ Kind::FdWrite,
95
+ poll_descriptor(invocation, subscribed, PollMask::OUT)?,
96
+ ),
116
97
  };
117
- Ok(Subscription { userdata, kind })
98
+ Ok(Subscription {
99
+ userdata: sub.userdata,
100
+ kind,
101
+ error,
102
+ })
118
103
  }
119
104
 
120
- fn descriptor<'a>(
121
- invocation: &'a Invocation,
122
- mem: &GuestMemory,
123
- sub: u32,
124
- ) -> Answer<&'a Descriptor> {
125
- let fd = mem.read_u32(GuestMemory::field(sub, 16)?)?;
126
- invocation
127
- .wasi()
128
- .descriptors()
129
- .get(fd)
130
- .ok_or_else(|| Errno::Badf.into())
105
+ /// Hold a subscription to what its descriptor would find ready, answering the
106
+ /// error its event reports. A number no descriptor holds, or one standing only
107
+ /// for its path, is reported in its event, as Linux's `do_pollfd` marks it
108
+ /// `POLLNVAL` when `fdget` refuses it and the wait goes on. A
109
+ /// pipe's end is never ready for what the other end does, and nothing here
110
+ /// would wake a wait on it, so that is `BADF` rather than a wait that never
111
+ /// ends.
112
+ fn poll_descriptor(
113
+ invocation: &Invocation,
114
+ subscribed: types::SubscriptionFdReadwrite,
115
+ event: PollMask,
116
+ ) -> Answer<Errno> {
117
+ let descriptors = invocation.wasi().descriptors();
118
+ let Ok(file) = file(descriptors, subscribed.file_descriptor.into()) else {
119
+ return Ok(Errno::Badf);
120
+ };
121
+ match file.poll().contains(event) {
122
+ true => Ok(Errno::Success),
123
+ false => Err(Errno::Badf.into()),
124
+ }
131
125
  }
132
126
 
133
127
  /// A clock, read against the guest's own clocks. Only the realtime and
134
128
  /// monotonic clocks can be waited on, alone or among other
135
129
  /// subscriptions, as in wasmtime-wasi.
136
- fn read_clock(invocation: &Invocation, mem: &GuestMemory, sub: u32) -> Answer<Kind> {
137
- let id = mem.read_u32(GuestMemory::field(sub, 16)?)?;
138
- let timeout = mem.read_u64(GuestMemory::field(sub, 24)?)?;
139
- let flags = mem.read_u16(GuestMemory::field(sub, 40)?)?;
140
- if id > CLOCK_MAX || flags & !SUBCLOCKFLAGS_ABSTIME != 0 {
141
- return Err(Errno::Inval.into());
142
- }
143
- let absolute = flags & SUBCLOCKFLAGS_ABSTIME != 0;
130
+ fn read_clock(invocation: &Invocation, clock: &types::SubscriptionClock) -> Answer<Kind> {
131
+ let absolute = clock
132
+ .flags
133
+ .contains(Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME);
144
134
  let clocks = invocation.wasi().clocks();
145
- let now = match (id, absolute) {
146
- (CLOCK_REALTIME | CLOCK_MONOTONIC, false) => 0,
147
- (CLOCK_MONOTONIC, true) => clocks.monotonic_now(),
148
- (CLOCK_REALTIME, true) => clocks.wall_now(),
135
+ let now = match (clock.id, absolute) {
136
+ (Clockid::Realtime | Clockid::Monotonic, false) => 0,
137
+ (Clockid::Monotonic, true) => clocks.monotonic_now(),
138
+ (Clockid::Realtime, true) => clocks.wall_now(),
149
139
  _ => return Err(Errno::Inval.into()),
150
140
  };
151
- Ok(Kind::Clock(timeout.saturating_sub(now)))
141
+ Ok(Kind::Clock(clock.timeout.saturating_sub(now)))
152
142
  }
153
143
 
154
144
  /// Wait `nanos` of the guest's time: none on frozen clocks, and on live
@@ -163,24 +153,27 @@ fn wait(invocation: &Invocation, nanos: u64) -> Answer {
163
153
  let left = deadline.saturating_duration_since(Instant::now());
164
154
  if wanted >= left {
165
155
  thread::sleep(left);
166
- return Err(Failure::Trap(wasmtime::Error::new(TimeoutTrap)));
156
+ return Err(types::Error::trap(wasmtime::Error::new(TimeoutTrap)));
167
157
  }
168
158
  }
169
159
  thread::sleep(wanted);
170
160
  Ok(())
171
161
  }
172
162
 
173
- fn write_event(mem: &mut GuestMemory, event: u32, sub: &Subscription, nbytes: u64) -> Answer {
174
- let (kind, flags) = match sub.kind {
175
- Kind::Clock(_) => (TAG_CLOCK, 0),
176
- Kind::FdRead { hangup } => (TAG_FD_READ, if hangup { EVENTRWFLAGS_HANGUP } else { 0 }),
177
- Kind::FdWrite => (TAG_FD_WRITE, 0),
178
- };
179
- mem.write_u64(event, sub.userdata)?;
180
- mem.write_u16(GuestMemory::field(event, 8)?, 0)?;
181
- mem.write_u8(GuestMemory::field(event, 10)?, kind)?;
182
- mem.write_u64(GuestMemory::field(event, 16)?, nbytes)?;
183
- mem.write_u16(GuestMemory::field(event, 24)?, flags)
163
+ fn to_event(sub: &Subscription, nbytes: u64) -> types::Event {
164
+ types::Event {
165
+ userdata: sub.userdata,
166
+ error: sub.error,
167
+ type_: match sub.kind {
168
+ Kind::Clock(_) => Eventtype::Clock,
169
+ Kind::FdRead => Eventtype::FdRead,
170
+ Kind::FdWrite => Eventtype::FdWrite,
171
+ },
172
+ fd_readwrite: types::EventFdReadwrite {
173
+ nbytes,
174
+ flags: Eventrwflags::empty(),
175
+ },
176
+ }
184
177
  }
185
178
 
186
179
  #[cfg(test)]
@@ -192,7 +185,7 @@ mod tests {
192
185
 
193
186
  use wasmtime::{Module, Store};
194
187
 
195
- use crate::cache::shared_engine;
188
+ use crate::cache::load_engine;
196
189
  use crate::config::Config;
197
190
  use crate::imports;
198
191
  use crate::invocation::Invocation;
@@ -221,7 +214,7 @@ mod tests {
221
214
  profile: Profile,
222
215
  deadline: Option<Duration>,
223
216
  ) -> (wasmtime::Result<i32>, Duration) {
224
- let engine = shared_engine().expect("shared engine must be constructible");
217
+ let engine = load_engine().expect("shared engine must be constructible");
225
218
  let config = Config {
226
219
  timeout: None,
227
220
  setup_timeout: None,
@@ -230,8 +223,9 @@ mod tests {
230
223
  stderr_limit: None,
231
224
  open_file_limit: None,
232
225
  profile,
226
+ security: Vec::new(),
233
227
  };
234
- let wasi = Wasi::for_invocation(&config, &[], &[]).expect("WASI context must build");
228
+ let wasi = Wasi::for_invocation(&config, Vec::new(), &[]).expect("WASI context must build");
235
229
  let mut store = Store::new(engine, Invocation::new(None, wasi));
236
230
  store.set_epoch_deadline(NO_TIMEOUT_EPOCH_DELTA);
237
231
  let linker = imports::linker().expect("host imports must link");
@@ -277,7 +271,7 @@ mod tests {
277
271
  (i32.store (i32.const 16) (local.get $id))
278
272
  (call $poll (i32.const 0) (i32.const 64) (i32.const 1) (i32.const 96))))
279
273
  "#;
280
- let engine = shared_engine().expect("shared engine must be constructible");
274
+ let engine = load_engine().expect("shared engine must be constructible");
281
275
  let config = Config {
282
276
  timeout: None,
283
277
  setup_timeout: None,
@@ -286,8 +280,9 @@ mod tests {
286
280
  stderr_limit: None,
287
281
  open_file_limit: None,
288
282
  profile: Profile::Hermetic,
283
+ security: Vec::new(),
289
284
  };
290
- let wasi = Wasi::for_invocation(&config, &[], &[]).expect("WASI context must build");
285
+ let wasi = Wasi::for_invocation(&config, Vec::new(), &[]).expect("WASI context must build");
291
286
  let mut store = Store::new(engine, Invocation::new(None, wasi));
292
287
  store.set_epoch_deadline(NO_TIMEOUT_EPOCH_DELTA);
293
288
  let linker = imports::linker().expect("host imports must link");
@@ -314,6 +309,59 @@ mod tests {
314
309
 
315
310
  const ERRNO_INVAL: i32 = 28;
316
311
 
312
+ /// What a lone wait on standard output answers for event `tag`, 1 to read
313
+ /// and 2 to write.
314
+ fn wait_on_stdout(tag: i32) -> i32 {
315
+ const WAIT_ON_FD_WAT: &str = r#"
316
+ (module
317
+ (import "wasi_snapshot_preview1" "poll_oneoff"
318
+ (func $poll (param i32 i32 i32 i32) (result i32)))
319
+ (memory (export "memory") 1)
320
+ (func (export "wait_on") (param $tag i32) (result i32)
321
+ (i32.store8 (i32.const 8) (local.get $tag))
322
+ (i32.store (i32.const 16) (i32.const 1))
323
+ (call $poll (i32.const 0) (i32.const 64) (i32.const 1) (i32.const 96))))
324
+ "#;
325
+ let engine = load_engine().expect("shared engine must be constructible");
326
+ let config = Config {
327
+ timeout: None,
328
+ setup_timeout: None,
329
+ memory_limit: None,
330
+ stdout_limit: None,
331
+ stderr_limit: None,
332
+ open_file_limit: None,
333
+ profile: Profile::Hermetic,
334
+ security: Vec::new(),
335
+ };
336
+ let wasi = Wasi::for_invocation(&config, Vec::new(), &[]).expect("WASI context must build");
337
+ let mut store = Store::new(engine, Invocation::new(None, wasi));
338
+ store.set_epoch_deadline(NO_TIMEOUT_EPOCH_DELTA);
339
+ let linker = imports::linker().expect("host imports must link");
340
+ let module = Module::new(engine, WAIT_ON_FD_WAT).expect("wait module must compile");
341
+ linker
342
+ .instantiate(&mut store, &module)
343
+ .expect("wait module must instantiate")
344
+ .get_typed_func::<i32, i32>(&mut store, "wait_on")
345
+ .expect("wait export must resolve")
346
+ .call(&mut store, tag)
347
+ .expect("the wait must not trap")
348
+ }
349
+
350
+ const ERRNO_BADF: i32 = 8;
351
+
352
+ // Nothing ever makes standard output ready to read, so without the refusal
353
+ // the wait would only end with the invocation.
354
+ // @behavior RT-285
355
+ #[test]
356
+ fn a_wait_on_what_a_descriptor_never_does_answers_ebadf() {
357
+ assert_eq!(
358
+ [1, 2].map(wait_on_stdout),
359
+ [ERRNO_BADF, 0],
360
+ "poll_oneoff on standard output must answer EBADF waiting to read and be ready to \
361
+ write"
362
+ );
363
+ }
364
+
317
365
  // @behavior RT-074
318
366
  #[test]
319
367
  fn a_permissive_wait_ends_at_the_deadline_as_a_timeout() {
@@ -1,30 +1,13 @@
1
1
  //! The 4 `sock_*` calls. The table holds no socket, so every open
2
- //! descriptor answers `NOTSOCK`, as wasmtime-wasi's preview1 does.
2
+ //! descriptor answers `NOTSOCK`, as wasmtime-wasi's preview1 does, and one
3
+ //! standing only for its path `BADF`, as Linux's socket calls take theirs
4
+ //! through `fdget`.
3
5
 
4
- use super::args;
5
6
  use super::errno::{Answer, Errno};
7
+ use super::fd::file;
6
8
  use crate::descriptors::Descriptors;
7
9
 
8
- fn socket(table: &Descriptors, fd: u32) -> Answer {
9
- table.get(fd).ok_or(Errno::Badf)?;
10
+ pub(crate) fn socket<T>(table: &Descriptors, fd: u32) -> Answer<T> {
11
+ file(table, fd)?;
10
12
  Err(Errno::Notsock.into())
11
13
  }
12
-
13
- pub(crate) fn accept(table: &Descriptors, fd: u32, flags: u32) -> Answer {
14
- args::flags(flags, args::FDFLAGS)?;
15
- socket(table, fd)
16
- }
17
-
18
- pub(crate) fn recv(table: &Descriptors, fd: u32, ri_flags: u32) -> Answer {
19
- args::flags(ri_flags, args::RIFLAGS)?;
20
- socket(table, fd)
21
- }
22
-
23
- pub(crate) fn send(table: &Descriptors, fd: u32) -> Answer {
24
- socket(table, fd)
25
- }
26
-
27
- pub(crate) fn shutdown(table: &Descriptors, fd: u32, how: u32) -> Answer {
28
- args::flags(how, args::SDFLAGS)?;
29
- socket(table, fd)
30
- }
@@ -0,0 +1,6 @@
1
+ nofollow_errors # no built-in Filesystem makes a link
2
+ path_exists # no built-in Filesystem makes a link
3
+ path_link # no built-in Filesystem makes a link
4
+ readlink # no built-in Filesystem makes a link
5
+ symlink_create # no built-in Filesystem makes a link
6
+ symlink_filestat # no built-in Filesystem makes a link
@@ -0,0 +1,157 @@
1
+ //! Runs wasi-testsuite's prebuilt preview1 tests against kobako's linker, over a
2
+ //! Bind of each test's own copy of the directory it is handed. The suite is
3
+ //! the second of what decides a preview1 answer, after the witx, so only the
4
+ //! calls kobako deliberately refuses may fail, and `testsuite-expected.txt`
5
+ //! names them.
6
+ //!
7
+ //! The suite is fetched by `rake crates:testsuite`, which pins its version, so
8
+ //! no gate reaches the network.
9
+
10
+ use std::collections::BTreeMap;
11
+ use std::fs;
12
+ use std::path::{Path, PathBuf};
13
+ use std::sync::Arc;
14
+ use std::time::Duration;
15
+
16
+ use kobako_runtime::filesystem::Bind;
17
+ use kobako_runtime::filesystem::Filesystem;
18
+ use kobako_runtime::grant::Grant;
19
+ use kobako_runtime::mount::{Access, Mount};
20
+ use kobako_runtime::profile::Profile;
21
+ use wasmtime::{Module, Store};
22
+ use wasmtime_wasi::I32Exit;
23
+
24
+ use crate::cache::load_engine;
25
+ use crate::config::Config;
26
+ use crate::imports;
27
+ use crate::invocation::Invocation;
28
+ use crate::trap::arm_deadline;
29
+ use crate::wasi::Wasi;
30
+
31
+ const SUITE: &str = concat!(
32
+ env!("CARGO_MANIFEST_DIR"),
33
+ "/../../tmp/wasi-testsuite/tests/rust/testsuite/wasm32-wasip1"
34
+ );
35
+
36
+ /// What may fail, one test per line, with why after a `#`.
37
+ const EXPECTED: &str = include_str!("testsuite-expected.txt");
38
+
39
+ fn copy_tree(from: &Path, to: &Path) {
40
+ fs::create_dir_all(to).expect("the copy must make its directory");
41
+ for entry in fs::read_dir(from).expect("the suite's directory must list") {
42
+ let entry = entry.expect("the suite's directory must list");
43
+ let target = to.join(entry.file_name());
44
+ match entry.file_type().expect("an entry has a type").is_dir() {
45
+ true => copy_tree(&entry.path(), &target),
46
+ false => {
47
+ fs::copy(entry.path(), target).expect("the suite's file must copy");
48
+ }
49
+ }
50
+ }
51
+ }
52
+
53
+ /// What the test at `wasm` ended with: its exit code, or why it never reached
54
+ /// one.
55
+ fn run(wasm: &Path, root: &Path) -> Result<i32, String> {
56
+ let engine = load_engine().map_err(|err| err.to_string())?;
57
+ let bind = Bind::new(root).map_err(|err| err.to_string())?;
58
+ let filesystem = Filesystem::new(Arc::new(bind)).map_err(|err| format!("{err:?}"))?;
59
+ let mount = Mount::new("/", filesystem, Access::ReadWrite).map_err(|err| err.to_string())?;
60
+ let config = Config {
61
+ timeout: None,
62
+ setup_timeout: None,
63
+ memory_limit: None,
64
+ stdout_limit: None,
65
+ stderr_limit: None,
66
+ open_file_limit: None,
67
+ profile: Profile::Permissive,
68
+ security: Vec::new(),
69
+ };
70
+ let wasi = Wasi::for_invocation(&config, Vec::new(), &[Grant::Mount(mount)])
71
+ .map_err(|err| err.to_string())?;
72
+ let mut store = Store::new(engine, Invocation::new(None, wasi));
73
+ arm_deadline(&mut store, Some(Duration::from_secs(30)));
74
+ let module = Module::from_file(engine, wasm).map_err(|err| err.to_string())?;
75
+ let instance = imports::linker()
76
+ .map_err(|err| err.to_string())?
77
+ .instantiate(&mut store, &module)
78
+ .map_err(|err| err.to_string())?;
79
+ let start = instance
80
+ .get_typed_func::<(), ()>(&mut store, "_start")
81
+ .map_err(|err| err.to_string())?;
82
+ match start.call(&mut store, ()) {
83
+ Ok(()) => Ok(0),
84
+ Err(err) => match err.downcast_ref::<I32Exit>() {
85
+ Some(exit) => Ok(exit.0),
86
+ // A test aborts on its first failed assertion, which it wrote to
87
+ // stderr, so that says what the call answered.
88
+ None => Err(String::from_utf8_lossy(&store.data().wasi().stderr_bytes()).to_string()),
89
+ },
90
+ }
91
+ }
92
+
93
+ fn expected_failures() -> BTreeMap<String, String> {
94
+ EXPECTED
95
+ .lines()
96
+ .filter(|line| !line.trim().is_empty())
97
+ .map(|line| {
98
+ let (name, why) = line.split_once('#').unwrap_or((line, ""));
99
+ (name.trim().to_string(), why.trim().to_string())
100
+ })
101
+ .collect()
102
+ }
103
+
104
+ // @behavior RT-192
105
+ #[test]
106
+ #[ignore = "run through `rake crates:testsuite`, which fetches the suite"]
107
+ fn only_what_kobako_refuses_fails_wasi_testsuite() {
108
+ let suite = PathBuf::from(SUITE);
109
+ assert!(
110
+ suite.exists(),
111
+ "wasi-testsuite is not at {SUITE}; run `rake crates:testsuite`"
112
+ );
113
+ let scratch = std::env::temp_dir().join(format!("kobako-testsuite-{}", std::process::id()));
114
+ let mut wasms: Vec<PathBuf> = fs::read_dir(&suite)
115
+ .expect("the suite must list")
116
+ .map(|entry| entry.expect("the suite must list").path())
117
+ .filter(|path| path.extension().is_some_and(|ext| ext == "wasm"))
118
+ .collect();
119
+ wasms.sort();
120
+
121
+ let mut failed = BTreeMap::new();
122
+ for wasm in &wasms {
123
+ let name = wasm
124
+ .file_stem()
125
+ .expect("a test has a name")
126
+ .to_string_lossy()
127
+ .to_string();
128
+ let root = scratch.join(&name);
129
+ copy_tree(&suite.join("fs-tests.dir"), &root);
130
+ match run(wasm, &root) {
131
+ Ok(0) => {}
132
+ Ok(code) => {
133
+ failed.insert(name, format!("exit {code}"));
134
+ }
135
+ Err(why) => {
136
+ failed.insert(name, why);
137
+ }
138
+ }
139
+ }
140
+ let _ = fs::remove_dir_all(&scratch);
141
+
142
+ let expected = expected_failures();
143
+ let unexpected: Vec<_> = failed
144
+ .iter()
145
+ .filter(|(name, _)| !expected.contains_key(*name))
146
+ .collect();
147
+ let passing: Vec<_> = expected
148
+ .keys()
149
+ .filter(|name| !failed.contains_key(*name))
150
+ .collect();
151
+ assert!(
152
+ unexpected.is_empty() && passing.is_empty(),
153
+ "wasi-testsuite through kobako's preview1 imports must fail only what \
154
+ testsuite-expected.txt names; unexpected failures: {unexpected:#?}; now passing: \
155
+ {passing:?}"
156
+ );
157
+ }