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
@@ -15,7 +15,7 @@ use wasmtime_wasi::p1::{self, WasiP1Ctx};
15
15
  use wasmtime_wasi::p2::pipe::{MemoryInputPipe, MemoryOutputPipe};
16
16
  use wasmtime_wasi::WasiCtxBuilder;
17
17
 
18
- use crate::cache::shared_engine;
18
+ use crate::cache::load_engine;
19
19
  use crate::config::Config;
20
20
  use crate::imports;
21
21
  use crate::invocation::Invocation;
@@ -91,6 +91,7 @@ const PROBE_WAT: &str = r#"
91
91
  (func (export "fd_fdstat_set_rights") (param i32) (result i32) (call $fd_fdstat_set_rights (local.get 0) (i64.const 0) (i64.const 0)))
92
92
  (func (export "fd_filestat_get") (param i32) (result i32) (call $fd_filestat_get (local.get 0) (i32.const 256)))
93
93
  (func (export "fd_filestat_set_size") (param i32) (result i32) (call $fd_filestat_set_size (local.get 0) (i64.const 0)))
94
+ (func (export "fd_filestat_set_size_huge") (param i32) (result i32) (call $fd_filestat_set_size (local.get 0) (i64.const -1)))
94
95
  (func (export "fd_filestat_set_times") (param i32) (result i32) (call $fd_filestat_set_times (local.get 0) (i64.const 0) (i64.const 0) (i32.const 0)))
95
96
  (func (export "fd_pread") (param i32) (result i32) (call $fd_pread (local.get 0) (i32.const 64) (i32.const 1) (i64.const 0) (i32.const 128)))
96
97
  (func (export "fd_prestat_get") (param i32) (result i32) (call $fd_prestat_get (local.get 0) (i32.const 256)))
@@ -282,7 +283,7 @@ fn observe<T>(
282
283
  }
283
284
 
284
285
  fn through_kobako(call: &str, fd: i32, limit: Option<usize>) -> Observation {
285
- let engine = shared_engine().expect("shared engine must be constructible");
286
+ let engine = load_engine().expect("shared engine must be constructible");
286
287
  let config = Config {
287
288
  timeout: None,
288
289
  setup_timeout: None,
@@ -291,8 +292,9 @@ fn through_kobako(call: &str, fd: i32, limit: Option<usize>) -> Observation {
291
292
  stderr_limit: limit,
292
293
  open_file_limit: None,
293
294
  profile: Profile::Hermetic,
295
+ security: Vec::new(),
294
296
  };
295
- let wasi = Wasi::for_invocation(&config, PREAMBLE, &[]).expect("WASI context must build");
297
+ let wasi = Wasi::for_invocation(&config, stdin_bytes(), &[]).expect("WASI context must build");
296
298
  let mut store = Store::new(engine, Invocation::new(None, wasi));
297
299
  store.set_epoch_deadline(NO_TIMEOUT_EPOCH_DELTA);
298
300
  let linker = imports::linker().expect("host imports must link");
@@ -310,9 +312,9 @@ struct Oracle {
310
312
  }
311
313
 
312
314
  fn through_wasmtime_wasi(call: &str, fd: i32, limit: Option<usize>) -> Observation {
313
- let engine = shared_engine().expect("shared engine must be constructible");
314
- let stdout = MemoryOutputPipe::new(crate::capture::pipe_capacity(limit));
315
- let stderr = MemoryOutputPipe::new(crate::capture::pipe_capacity(limit));
315
+ let engine = load_engine().expect("shared engine must be constructible");
316
+ let stdout = MemoryOutputPipe::new(kobako_runtime::endpoint::pipe_capacity(limit));
317
+ let stderr = MemoryOutputPipe::new(kobako_runtime::endpoint::pipe_capacity(limit));
316
318
  let ctx = WasiCtxBuilder::new()
317
319
  .stdin(MemoryInputPipe::new(stdin_bytes()))
318
320
  .stdout(stdout.clone())
@@ -360,8 +362,18 @@ fn every_descriptor_call_answers_as_wasmtime_wasi_preview1() {
360
362
  for limit in CHANNEL_LIMITS {
361
363
  for call in CALLS {
362
364
  for fd in 0..=3 {
365
+ let kobako = through_kobako(call, fd, limit);
366
+ if let Some(errno) = answered_as_linux(call, fd) {
367
+ assert_eq!(
368
+ kobako.errno,
369
+ Some(errno),
370
+ "{call} on descriptor {fd} through kobako's preview1 imports must answer \
371
+ as Linux does"
372
+ );
373
+ continue;
374
+ }
363
375
  assert_eq!(
364
- through_kobako(call, fd, limit),
376
+ kobako,
365
377
  through_wasmtime_wasi(call, fd, limit),
366
378
  "{call} on descriptor {fd} with Channels capped at {limit:?} through \
367
379
  kobako's preview1 imports must answer as wasmtime-wasi's preview1 does"
@@ -370,3 +382,105 @@ fn every_descriptor_call_answers_as_wasmtime_wasi_preview1() {
370
382
  }
371
383
  }
372
384
  }
385
+
386
+ /// Where kobako parts from wasmtime-wasi, the errno Linux answers, which
387
+ /// outranks it. For a standard stream it answers as for a pipe: an offset, a
388
+ /// sync, or a size is no pipe's, and a pipe is no directory to open beneath.
389
+ /// Linux also takes `O_APPEND` on a pipe, reports one link where wasmtime-wasi
390
+ /// reports none, checks neither path nor descriptor for a call setting no
391
+ /// time, as `utimensat` returns first, and reports a wait on a descriptor not
392
+ /// open in its event, as `poll` marks it `POLLNVAL`.
393
+ fn answered_as_linux(call: &str, fd: i32) -> Option<i32> {
394
+ const ERRNO_SUCCESS: i32 = 0;
395
+ const ERRNO_INVAL: i32 = 28;
396
+ const ERRNO_NOTDIR: i32 = 54;
397
+ const ERRNO_SPIPE: i32 = 70;
398
+ match (call, fd) {
399
+ ("fd_filestat_set_times" | "path_filestat_set_times", 3) => Some(ERRNO_SUCCESS),
400
+ ("poll_oneoff_read" | "poll_oneoff_write", 3) => Some(ERRNO_SUCCESS),
401
+ ("fd_advise", 0..=2) => Some(ERRNO_SPIPE),
402
+ ("fd_allocate" | "fd_sync" | "fd_datasync" | "fd_filestat_set_size", 0..=2) => {
403
+ Some(ERRNO_INVAL)
404
+ }
405
+ (
406
+ "fd_fdstat_set_flags"
407
+ | "fd_filestat_get"
408
+ | "fd_filestat_set_times"
409
+ | "path_filestat_set_times",
410
+ 0..=2,
411
+ ) => Some(ERRNO_SUCCESS),
412
+ ("fd_read_empty", 0) => Some(ERRNO_SUCCESS),
413
+ ("fd_readdir" | "fd_readdir_far", 0..=2) => Some(ERRNO_NOTDIR),
414
+ (
415
+ "path_create_directory"
416
+ | "path_filestat_get"
417
+ | "path_link"
418
+ | "path_open"
419
+ | "path_readlink"
420
+ | "path_remove_directory"
421
+ | "path_rename"
422
+ | "path_symlink"
423
+ | "path_unlink_file",
424
+ 0..=2,
425
+ ) => Some(ERRNO_NOTDIR),
426
+ ("fd_pread" | "fd_pread_far", 1 | 2) => Some(ERRNO_SPIPE),
427
+ ("fd_pwrite" | "fd_pwrite_far", 0) => Some(ERRNO_SPIPE),
428
+ _ => None,
429
+ }
430
+ }
431
+
432
+ // wasmtime-wasi fails the whole wait; the witx gives each event its own error,
433
+ // and Linux's `do_pollfd` marks the descriptor `POLLNVAL`, which wasi-libc reads
434
+ // back from an event's EBADF.
435
+ // @behavior RT-295
436
+ #[test]
437
+ fn a_wait_on_a_descriptor_not_open_reports_it_in_its_event() {
438
+ let read = through_kobako("poll_oneoff_read", 3, None);
439
+ let write = through_kobako("poll_oneoff_write", 3, None);
440
+ let event = |seen: &Observation| {
441
+ (
442
+ seen.errno,
443
+ u64::from_le_bytes(seen.memory[896..904].try_into().expect("eight bytes")),
444
+ u16::from_le_bytes([seen.memory[904], seen.memory[905]]),
445
+ u32::from_le_bytes(seen.memory[128..132].try_into().expect("four bytes")),
446
+ )
447
+ };
448
+ assert_eq!(
449
+ (event(&read), event(&write)),
450
+ ((Some(0), 7, 8, 1), (Some(0), 7, 8, 1)),
451
+ "poll_oneoff on descriptor 3, not open, through kobako's preview1 imports must succeed \
452
+ with one event whose error is EBADF, as Linux's poll marks it POLLNVAL"
453
+ );
454
+ }
455
+
456
+ // Linux's `ksys_ftruncate` reads the length as a signed `loff_t` and refuses a
457
+ // negative one before it looks for the descriptor.
458
+ // @behavior RT-296
459
+ #[test]
460
+ fn a_size_no_file_can_take_answers_before_the_descriptor_is_found() {
461
+ assert_eq!(
462
+ through_kobako("fd_filestat_set_size_huge", 3, None).errno,
463
+ Some(28),
464
+ "fd_filestat_set_size of 2^64 - 1 on descriptor 3, not open, through kobako's preview1 \
465
+ imports must answer EINVAL, as ftruncate checks the length before the descriptor"
466
+ );
467
+ }
468
+
469
+ // wasmtime-wasi answers EBADF for the end a call cannot use; Linux reads
470
+ // `FMODE_PREAD` first, and a pipe has none on either end.
471
+ // @behavior RT-178
472
+ #[test]
473
+ fn a_standard_stream_takes_no_offset() {
474
+ let errno = |call, fd| through_kobako(call, fd, None).errno;
475
+ assert_eq!(
476
+ [
477
+ errno("fd_pread", 0),
478
+ errno("fd_pread", 1),
479
+ errno("fd_pwrite", 0),
480
+ errno("fd_pwrite", 1),
481
+ ],
482
+ [Some(70), Some(70), Some(70), Some(70)],
483
+ "fd_pread and fd_pwrite on standard input and output through kobako's preview1 imports \
484
+ must answer ESPIPE, as Linux answers for a pipe's either end"
485
+ );
486
+ }
@@ -2,223 +2,86 @@
2
2
  //! the guest reads, or a trap that ends the invocation.
3
3
 
4
4
  use kobako_runtime::filesystem::FilesystemError;
5
- use kobako_runtime::mount::Denial;
5
+ use wiggle::GuestError;
6
6
 
7
- /// One preview1 `errno`, numbered as the witx `$errno` enum numbers it.
8
- #[derive(Clone, Copy, Debug, PartialEq, Eq)]
9
- #[repr(u16)]
10
- pub(crate) enum Errno {
11
- TooBig = 1,
12
- Acces = 2,
13
- Addrinuse = 3,
14
- Addrnotavail = 4,
15
- Afnosupport = 5,
16
- Again = 6,
17
- Already = 7,
18
- Badf = 8,
19
- Badmsg = 9,
20
- Busy = 10,
21
- Canceled = 11,
22
- Child = 12,
23
- Connaborted = 13,
24
- Connrefused = 14,
25
- Connreset = 15,
26
- Deadlk = 16,
27
- Destaddrreq = 17,
28
- Dom = 18,
29
- Dquot = 19,
30
- Exist = 20,
31
- Fault = 21,
32
- Fbig = 22,
33
- Hostunreach = 23,
34
- Idrm = 24,
35
- Ilseq = 25,
36
- Inprogress = 26,
37
- Intr = 27,
38
- Inval = 28,
39
- Io = 29,
40
- Isconn = 30,
41
- Isdir = 31,
42
- Loop = 32,
43
- Mfile = 33,
44
- Mlink = 34,
45
- Msgsize = 35,
46
- Multihop = 36,
47
- Nametoolong = 37,
48
- Netdown = 38,
49
- Netreset = 39,
50
- Netunreach = 40,
51
- Nfile = 41,
52
- Nobufs = 42,
53
- Nodev = 43,
54
- Noent = 44,
55
- Noexec = 45,
56
- Nolck = 46,
57
- Nolink = 47,
58
- Nomem = 48,
59
- Nomsg = 49,
60
- Noprotoopt = 50,
61
- Nospc = 51,
62
- Nosys = 52,
63
- Notconn = 53,
64
- Notdir = 54,
65
- Notempty = 55,
66
- Notrecoverable = 56,
67
- Notsock = 57,
68
- Notsup = 58,
69
- Notty = 59,
70
- Nxio = 60,
71
- Overflow = 61,
72
- Ownerdead = 62,
73
- Perm = 63,
74
- Pipe = 64,
75
- Proto = 65,
76
- Protonosupport = 66,
77
- Prototype = 67,
78
- Range = 68,
79
- Rofs = 69,
80
- Spipe = 70,
81
- Srch = 71,
82
- Stale = 72,
83
- Timedout = 73,
84
- Txtbsy = 74,
85
- Xdev = 75,
86
- Notcapable = 76,
87
- }
88
-
89
- impl Errno {
90
- /// The code a POSIX error name stands for: the witx case, `E`
91
- /// prefixed and upper-cased. `ENOTCAPABLE` is WASI's own.
92
- fn from_posix_name(name: &str) -> Option<Self> {
93
- Some(match name {
94
- "E2BIG" => Self::TooBig,
95
- "EACCES" => Self::Acces,
96
- "EADDRINUSE" => Self::Addrinuse,
97
- "EADDRNOTAVAIL" => Self::Addrnotavail,
98
- "EAFNOSUPPORT" => Self::Afnosupport,
99
- "EAGAIN" => Self::Again,
100
- "EALREADY" => Self::Already,
101
- "EBADF" => Self::Badf,
102
- "EBADMSG" => Self::Badmsg,
103
- "EBUSY" => Self::Busy,
104
- "ECANCELED" => Self::Canceled,
105
- "ECHILD" => Self::Child,
106
- "ECONNABORTED" => Self::Connaborted,
107
- "ECONNREFUSED" => Self::Connrefused,
108
- "ECONNRESET" => Self::Connreset,
109
- "EDEADLK" => Self::Deadlk,
110
- "EDESTADDRREQ" => Self::Destaddrreq,
111
- "EDOM" => Self::Dom,
112
- "EDQUOT" => Self::Dquot,
113
- "EEXIST" => Self::Exist,
114
- "EFAULT" => Self::Fault,
115
- "EFBIG" => Self::Fbig,
116
- "EHOSTUNREACH" => Self::Hostunreach,
117
- "EIDRM" => Self::Idrm,
118
- "EILSEQ" => Self::Ilseq,
119
- "EINPROGRESS" => Self::Inprogress,
120
- "EINTR" => Self::Intr,
121
- "EINVAL" => Self::Inval,
122
- "EIO" => Self::Io,
123
- "EISCONN" => Self::Isconn,
124
- "EISDIR" => Self::Isdir,
125
- "ELOOP" => Self::Loop,
126
- "EMFILE" => Self::Mfile,
127
- "EMLINK" => Self::Mlink,
128
- "EMSGSIZE" => Self::Msgsize,
129
- "EMULTIHOP" => Self::Multihop,
130
- "ENAMETOOLONG" => Self::Nametoolong,
131
- "ENETDOWN" => Self::Netdown,
132
- "ENETRESET" => Self::Netreset,
133
- "ENETUNREACH" => Self::Netunreach,
134
- "ENFILE" => Self::Nfile,
135
- "ENOBUFS" => Self::Nobufs,
136
- "ENODEV" => Self::Nodev,
137
- "ENOENT" => Self::Noent,
138
- "ENOEXEC" => Self::Noexec,
139
- "ENOLCK" => Self::Nolck,
140
- "ENOLINK" => Self::Nolink,
141
- "ENOMEM" => Self::Nomem,
142
- "ENOMSG" => Self::Nomsg,
143
- "ENOPROTOOPT" => Self::Noprotoopt,
144
- "ENOSPC" => Self::Nospc,
145
- "ENOSYS" => Self::Nosys,
146
- "ENOTCONN" => Self::Notconn,
147
- "ENOTDIR" => Self::Notdir,
148
- "ENOTEMPTY" => Self::Notempty,
149
- "ENOTRECOVERABLE" => Self::Notrecoverable,
150
- "ENOTSOCK" => Self::Notsock,
151
- "ENOTSUP" => Self::Notsup,
152
- "ENOTTY" => Self::Notty,
153
- "ENXIO" => Self::Nxio,
154
- "EOVERFLOW" => Self::Overflow,
155
- "EOWNERDEAD" => Self::Ownerdead,
156
- "EPERM" => Self::Perm,
157
- "EPIPE" => Self::Pipe,
158
- "EPROTO" => Self::Proto,
159
- "EPROTONOSUPPORT" => Self::Protonosupport,
160
- "EPROTOTYPE" => Self::Prototype,
161
- "ERANGE" => Self::Range,
162
- "EROFS" => Self::Rofs,
163
- "ESPIPE" => Self::Spipe,
164
- "ESRCH" => Self::Srch,
165
- "ESTALE" => Self::Stale,
166
- "ETIMEDOUT" => Self::Timedout,
167
- "ETXTBSY" => Self::Txtbsy,
168
- "EXDEV" => Self::Xdev,
169
- "ENOTCAPABLE" => Self::Notcapable,
170
- _ => return None,
171
- })
172
- }
173
- }
7
+ use super::types;
8
+ pub(crate) use super::types::Errno;
174
9
 
175
- /// A call that did not succeed. preview1 traps rather than answering
176
- /// for a pointer outside guest memory or an argument outside what the
177
- /// witx defines, as wasmtime-wasi does.
178
- pub(crate) enum Failure {
179
- Errno(Errno),
180
- Trap(wasmtime::Error),
181
- }
10
+ pub(crate) type Answer<T = ()> = Result<T, types::Error>;
182
11
 
183
- impl Failure {
184
- /// A trap saying what the guest handed over that preview1 cannot hold.
185
- pub(crate) fn trap(message: &'static str) -> Self {
186
- Self::Trap(wasmtime::Error::msg(message))
187
- }
12
+ /// The code a POSIX error name a Filesystem's failure carries stands for:
13
+ /// the witx case, `E` prefixed and upper-cased.
14
+ fn from_posix_name(name: &str) -> Option<Errno> {
15
+ Some(match name {
16
+ "EAGAIN" => Errno::Again,
17
+ "ENOENT" => Errno::Noent,
18
+ "EEXIST" => Errno::Exist,
19
+ "ENOTDIR" => Errno::Notdir,
20
+ "EISDIR" => Errno::Isdir,
21
+ "ENOTEMPTY" => Errno::Notempty,
22
+ "EACCES" => Errno::Acces,
23
+ "EROFS" => Errno::Rofs,
24
+ "EINVAL" => Errno::Inval,
25
+ "ENOTSUP" => Errno::Notsup,
26
+ "ENOSPC" => Errno::Nospc,
27
+ "EFBIG" => Errno::Fbig,
28
+ "EXDEV" => Errno::Xdev,
29
+ "EDEADLK" => Errno::Deadlk,
30
+ "ELOOP" => Errno::Loop,
31
+ "EPERM" => Errno::Perm,
32
+ "EBADF" => Errno::Badf,
33
+ "EBUSY" => Errno::Busy,
34
+ "EILSEQ" => Errno::Ilseq,
35
+ "EINTR" => Errno::Intr,
36
+ "EMFILE" => Errno::Mfile,
37
+ "ENAMETOOLONG" => Errno::Nametoolong,
38
+ "ENOMEM" => Errno::Nomem,
39
+ "ENOTSOCK" => Errno::Notsock,
40
+ "EOVERFLOW" => Errno::Overflow,
41
+ "ESPIPE" => Errno::Spipe,
42
+ "ENODEV" => Errno::Nodev,
43
+ "ESTALE" => Errno::Stale,
44
+ _ => return None,
45
+ })
188
46
  }
189
47
 
190
- impl From<Errno> for Failure {
191
- fn from(errno: Errno) -> Self {
192
- Self::Errno(errno)
48
+ /// A pointer outside the memory or misaligned traps, as the witx
49
+ /// documentation requires; a value inside memory that no witx type holds is
50
+ /// `INVAL`, as in wasmtime-wasi.
51
+ impl From<GuestError> for types::Error {
52
+ fn from(error: GuestError) -> Self {
53
+ match error {
54
+ GuestError::InvalidFlagValue { .. } | GuestError::InvalidEnumValue { .. } => {
55
+ Errno::Inval.into()
56
+ }
57
+ GuestError::PtrOverflow
58
+ | GuestError::PtrOutOfBounds { .. }
59
+ | GuestError::PtrNotAligned { .. } => Self::trap(error.into()),
60
+ GuestError::InvalidUtf8 { .. } => Errno::Ilseq.into(),
61
+ GuestError::TryFromIntError { .. } => Errno::Overflow.into(),
62
+ GuestError::SliceLengthsDiffer => Errno::Fault.into(),
63
+ GuestError::InFunc { err, .. } => Self::from(*err),
64
+ }
193
65
  }
194
66
  }
195
67
 
196
- /// The code a Filesystem's failure reaches the guest as: the one its
197
- /// POSIX name stands for, or `IO` for a failure without one.
198
- impl From<FilesystemError> for Failure {
68
+ /// The code a call's failure reaches the guest as: the one its POSIX name
69
+ /// stands for, or `IO` for a failure without one. A path escaping its
70
+ /// descriptor is `NOTCAPABLE`, as preview1 names a capability the guest does
71
+ /// not hold, where Linux answers EXDEV.
72
+ impl From<FilesystemError> for types::Error {
199
73
  fn from(error: FilesystemError) -> Self {
200
- Self::Errno(
201
- error
74
+ match error {
75
+ FilesystemError::Escape => Errno::Notcapable,
76
+ error => error
202
77
  .posix_name()
203
- .and_then(Errno::from_posix_name)
78
+ .and_then(from_posix_name)
204
79
  .unwrap_or(Errno::Io),
205
- )
206
- }
207
- }
208
-
209
- /// A change beneath a read-only Mount is `ROFS`; a Policy's refusal is
210
- /// `ACCES`.
211
- impl From<Denial> for Failure {
212
- fn from(denial: Denial) -> Self {
213
- Self::Errno(match denial {
214
- Denial::ReadOnly => Errno::Rofs,
215
- Denial::Refused => Errno::Acces,
216
- })
80
+ }
81
+ .into()
217
82
  }
218
83
  }
219
84
 
220
- pub(crate) type Answer<T = ()> = Result<T, Failure>;
221
-
222
85
  #[cfg(test)]
223
86
  mod tests {
224
87
  use super::*;
@@ -238,6 +101,7 @@ mod tests {
238
101
  (FilesystemError::StorageFull, 51),
239
102
  (FilesystemError::FileTooLarge, 22),
240
103
  (FilesystemError::CrossesDevices, 75),
104
+ (FilesystemError::Escape, 76),
241
105
  (FilesystemError::Deadlock, 16),
242
106
  (FilesystemError::FilesystemLoop, 32),
243
107
  (FilesystemError::NotPermitted, 63),
@@ -245,22 +109,26 @@ mod tests {
245
109
  (FilesystemError::ResourceBusy, 10),
246
110
  (FilesystemError::IllegalSequence, 25),
247
111
  (FilesystemError::Interrupted, 27),
112
+ (FilesystemError::WouldBlock, 6),
248
113
  (FilesystemError::TooManyOpenFiles, 33),
249
114
  (FilesystemError::InvalidFilename, 37),
250
115
  (FilesystemError::OutOfMemory, 48),
251
116
  (FilesystemError::NotASocket, 57),
252
117
  (FilesystemError::ValueTooLarge, 61),
253
118
  (FilesystemError::NotSeekable, 70),
119
+ (FilesystemError::NoSuchDevice, 43),
120
+ (FilesystemError::StaleNetworkFileHandle, 72),
254
121
  (FilesystemError::Other("x".into()), 29),
255
122
  ];
256
123
  for (error, code) in cases {
257
124
  let label = format!("{error:?}");
258
- let Failure::Errno(errno) = Failure::from(error) else {
125
+ let Ok(errno) = types::Error::from(error).downcast() else {
259
126
  panic!("{label} must answer an error code, not a trap");
260
127
  };
261
128
  assert_eq!(
262
- errno as u16, code,
263
- "{label} through Failure::from must reach the guest as preview1 errno {code}"
129
+ u16::from(errno),
130
+ code,
131
+ "{label} through types::Error::from must reach the guest as preview1 errno {code}"
264
132
  );
265
133
  }
266
134
  }