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,491 +1,453 @@
1
1
  //! The 21 `fd_*` calls over kobako's descriptor table.
2
2
  //!
3
- //! For stdio each answers what wasmtime-wasi's preview1 answers for the
4
- //! same descriptor; the differential probe in `preview1` holds that. For a
5
- //! directory or a file beneath a Mount, the Filesystem answers
6
- //! and the descriptor's position and rights stay here.
7
-
8
- use bytes::Bytes;
9
- use kobako_runtime::filesystem::{FileType, Metadata};
10
- use wasmtime_wasi::p2::pipe::{MemoryInputPipe, MemoryOutputPipe};
11
- use wasmtime_wasi::p2::{InputStream, OutputStream, StreamError};
12
-
13
- use super::args::{
14
- self, FDFLAGS_APPEND, FDFLAGS_NONBLOCK, FDFLAGS_SYNCHRONIZED, RIGHTS_FD_READ, RIGHTS_FD_WRITE,
15
- };
16
- use super::beneath::confirm;
3
+ //! The runtime's open file decides each answer, so this keeps only what
4
+ //! preview1 itself defines: which file a number stands for, its rights, a
5
+ //! preopen's name, and what wasi-testsuite asks of a directory's position.
6
+ //! It also keeps what a Linux syscall entry checks before the VFS: which
7
+ //! descriptor a call takes, the order of its errors, and the caller's buffer.
8
+
9
+ use std::time::{Duration, SystemTime, UNIX_EPOCH};
10
+
11
+ use kobako_runtime::filesystem::{FileType, FilesystemError, Iattr, Kstat, SetTime};
12
+ use kobako_runtime::profile::Profile;
13
+ use kobako_runtime::vfs::{self, verify_dirent_name, DirContext, DirEmit, File, Whence};
14
+ use wiggle::{GuestMemory as WitxMemory, GuestPtr, GuestType};
15
+
17
16
  use super::errno::{Answer, Errno};
18
17
  use super::memory::GuestMemory;
19
- use crate::descriptors::{Descriptor, Descriptors, OpenFile};
20
-
21
- /// `fdstat` / `filestat` filetypes.
22
- const FILETYPE_UNKNOWN: u8 = 0;
23
- const FILETYPE_DIRECTORY: u8 = 3;
24
- const FILETYPE_REGULAR_FILE: u8 = 4;
25
- const FILETYPE_SYMBOLIC_LINK: u8 = 7;
26
-
27
- const RIGHTS_FD_DATASYNC: u64 = 1 << 0;
28
- const RIGHTS_FD_SEEK: u64 = 1 << 2;
29
- const RIGHTS_FD_FDSTAT_SET_FLAGS: u64 = 1 << 3;
30
- const RIGHTS_FD_SYNC: u64 = 1 << 4;
31
- const RIGHTS_FD_TELL: u64 = 1 << 5;
32
- const RIGHTS_FD_ADVISE: u64 = 1 << 7;
33
- const RIGHTS_FD_ALLOCATE: u64 = 1 << 8;
34
- const RIGHTS_FD_READDIR: u64 = 1 << 14;
35
- const RIGHTS_PATH_FILESTAT_SET_SIZE: u64 = 1 << 19;
36
- const RIGHTS_FD_FILESTAT_GET: u64 = 1 << 21;
37
- const RIGHTS_FD_FILESTAT_SET_SIZE: u64 = 1 << 22;
38
- const RIGHTS_FD_FILESTAT_SET_TIMES: u64 = 1 << 23;
39
- const RIGHTS_POLL_FD_READWRITE: u64 = 1 << 27;
18
+ use super::path::retry_eagain;
19
+ use super::types::{self, Fdflags, Filetype, Fstflags, Rights};
20
+ use crate::descriptors::{Descriptor, Descriptors};
40
21
 
41
22
  /// What a preopen allows itself: every path call, as wasmtime-wasi hands
42
23
  /// most userlands.
43
- const PREOPEN_RIGHTS: u64 = (1 << 9)
44
- | (1 << 10)
45
- | (1 << 11)
46
- | (1 << 12)
47
- | (1 << 13)
48
- | RIGHTS_FD_READDIR
49
- | (1 << 15)
50
- | (1 << 16)
51
- | (1 << 17)
52
- | (1 << 18)
53
- | (1 << 20)
54
- | RIGHTS_FD_FILESTAT_GET
55
- | RIGHTS_FD_FILESTAT_SET_TIMES
56
- | (1 << 24)
57
- | (1 << 25)
58
- | (1 << 26);
59
-
60
- /// What a file opened beneath a preopen may hold. Reading and writing are
61
- /// both inherited whatever the Mount's access, since wasi-libc intersects
62
- /// them before asking: a read-only Mount answers a write itself, as `ROFS`.
63
- const PREOPEN_INHERITING: u64 = PREOPEN_RIGHTS
64
- | RIGHTS_FD_DATASYNC
65
- | RIGHTS_FD_READ
66
- | RIGHTS_FD_SEEK
67
- | RIGHTS_FD_FDSTAT_SET_FLAGS
68
- | RIGHTS_FD_SYNC
69
- | RIGHTS_FD_TELL
70
- | RIGHTS_FD_WRITE
71
- | RIGHTS_FD_ADVISE
72
- | RIGHTS_FD_ALLOCATE
73
- | RIGHTS_FD_FILESTAT_SET_SIZE
74
- | RIGHTS_POLL_FD_READWRITE;
75
-
76
- /// The most bytes one write moves into a capture pipe at a time, the
77
- /// preview2 `blocking-write-and-flush` limit wasmtime-wasi writes under.
78
- const WRITE_CHUNK: usize = 4096;
79
-
80
- /// The size of one `dirent` header ahead of its name.
81
- const DIRENT_SIZE: u32 = 24;
24
+ fn preopen_rights() -> Rights {
25
+ Rights::PATH_CREATE_DIRECTORY
26
+ | Rights::PATH_CREATE_FILE
27
+ | Rights::PATH_LINK_SOURCE
28
+ | Rights::PATH_LINK_TARGET
29
+ | Rights::PATH_OPEN
30
+ | Rights::FD_READDIR
31
+ | Rights::PATH_READLINK
32
+ | Rights::PATH_RENAME_SOURCE
33
+ | Rights::PATH_RENAME_TARGET
34
+ | Rights::PATH_FILESTAT_GET
35
+ | Rights::PATH_FILESTAT_SET_TIMES
36
+ | Rights::FD_FILESTAT_GET
37
+ | Rights::FD_FILESTAT_SET_TIMES
38
+ | Rights::PATH_SYMLINK
39
+ | Rights::PATH_REMOVE_DIRECTORY
40
+ | Rights::PATH_UNLINK_FILE
41
+ }
82
42
 
83
- fn descriptor(table: &Descriptors, fd: u32) -> Answer<&Descriptor> {
84
- table.get(fd).ok_or_else(|| Errno::Badf.into())
43
+ /// What a file opened beneath a directory may hold. Reading and writing are
44
+ /// both inherited whatever the Mount's access or the directory's own mode,
45
+ /// since wasi-libc intersects them before asking and Linux takes an open's
46
+ /// mode from its flags alone: a read-only Mount answers a write itself, as
47
+ /// `ROFS`.
48
+ fn directory_rights_inheriting() -> Rights {
49
+ preopen_rights()
50
+ | Rights::FD_DATASYNC
51
+ | Rights::FD_READ
52
+ | Rights::FD_SEEK
53
+ | Rights::FD_FDSTAT_SET_FLAGS
54
+ | Rights::FD_SYNC
55
+ | Rights::FD_TELL
56
+ | Rights::FD_WRITE
57
+ | Rights::FD_ADVISE
58
+ | Rights::FD_ALLOCATE
59
+ | Rights::FD_FILESTAT_SET_SIZE
60
+ | Rights::POLL_FD_READWRITE
85
61
  }
86
62
 
87
- fn descriptor_mut(table: &mut Descriptors, fd: u32) -> Answer<&mut Descriptor> {
88
- table.get_mut(fd).ok_or_else(|| Errno::Badf.into())
63
+ /// What `fd` stands for, a file that stands only for its path included, as
64
+ /// `fdget_raw` takes one.
65
+ fn descriptor(table: &Descriptors, fd: u32) -> Answer<&Descriptor> {
66
+ table.get(fd).ok_or_else(|| Errno::Badf.into())
89
67
  }
90
68
 
91
- /// The open file `fd` stands for; any other descriptor is `BADF`.
92
- fn file(table: &mut Descriptors, fd: u32) -> Answer<&mut OpenFile> {
93
- match descriptor_mut(table, fd)? {
94
- Descriptor::File(file) => Ok(file),
95
- Descriptor::Stdin(_) | Descriptor::Channel(_) | Descriptor::Directory(_) => {
96
- Err(Errno::Badf.into())
97
- }
69
+ /// The open file `fd` stands for, refusing one that stands only for its path,
70
+ /// as `fdget` does.
71
+ pub(crate) fn file(table: &Descriptors, fd: u32) -> Answer<&File> {
72
+ match table.get(fd) {
73
+ Some(descriptor) if !descriptor.file.is_path() => Ok(&descriptor.file),
74
+ _ => Err(Errno::Badf.into()),
98
75
  }
99
76
  }
100
77
 
101
- /// A call that moves a stream position: a file has one, stdio is a pipe
102
- /// with none, and a directory is no stream at all.
103
- fn seekable(table: &mut Descriptors, fd: u32) -> Answer<&mut OpenFile> {
104
- match descriptor_mut(table, fd)? {
105
- Descriptor::File(file) => Ok(file),
106
- Descriptor::Stdin(_) | Descriptor::Channel(_) => Err(Errno::Spipe.into()),
107
- Descriptor::Directory(_) => Err(Errno::Badf.into()),
78
+ /// The open file `fd` stands for, to change, refusing one that stands only for
79
+ /// its path, as `fdget` does.
80
+ fn file_mut(table: &mut Descriptors, fd: u32) -> Answer<&mut File> {
81
+ match table.get_mut(fd) {
82
+ Some(descriptor) if !descriptor.file.is_path() => Ok(&mut descriptor.file),
83
+ _ => Err(Errno::Badf.into()),
108
84
  }
109
85
  }
110
86
 
111
- /// An offset or size `pread`, `pwrite`, or `ftruncate` names, which Linux
112
- /// reads as a signed `off_t` and refuses once negative.
113
- fn refuse_negative(offset: u64) -> Answer<u64> {
114
- if offset > i64::MAX as u64 {
115
- return Err(Errno::Inval.into());
87
+ /// The open file `fd` stands for, in a call about its position. wasi-testsuite
88
+ /// has a directory refuse one, where Linux would seek it.
89
+ fn seekable_file(table: &mut Descriptors, fd: u32) -> Answer<&mut File> {
90
+ match file_mut(table, fd)? {
91
+ file if file.is_directory() => Err(Errno::Badf.into()),
92
+ file => Ok(file),
116
93
  }
117
- Ok(offset)
118
94
  }
119
95
 
120
- /// A read of `len` bytes from `offset`, refused as Linux refuses one whose
121
- /// end would pass the largest signed offset.
122
- fn refuse_reading_past_largest_offset(offset: u64, len: u32) -> Answer<u64> {
123
- if offset.saturating_add(u64::from(len)) > i64::MAX as u64 {
124
- return Err(Errno::Inval.into());
96
+ /// A call any file or directory answers, which preview1 refuses for
97
+ /// stdio.
98
+ fn refuse_stream(table: &Descriptors, fd: u32) -> Answer {
99
+ match descriptor(table, fd)?.file.is_stream() {
100
+ true => Err(Errno::Badf.into()),
101
+ false => Ok(()),
125
102
  }
126
- Ok(offset)
127
103
  }
128
104
 
129
- /// The part of `buf` a write at `offset` lands, as Linux cuts a write
130
- /// short at the largest signed offset and refuses one starting there as
131
- /// too large.
132
- fn trim_to_largest_offset(buf: &[u8], offset: u64) -> Answer<&[u8]> {
133
- let room = (i64::MAX as u64).saturating_sub(offset);
134
- if room == 0 && !buf.is_empty() {
135
- return Err(Errno::Fbig.into());
105
+ /// `file_type` as a preview1 `filetype`, which has no code for a FIFO and two
106
+ /// for the one kind `st_mode` names a socket.
107
+ fn to_filetype(file_type: FileType) -> Filetype {
108
+ match file_type {
109
+ FileType::Dir => Filetype::Directory,
110
+ FileType::Symlink => Filetype::SymbolicLink,
111
+ FileType::RegFile => Filetype::RegularFile,
112
+ FileType::Sock => Filetype::SocketStream,
113
+ FileType::Blkdev => Filetype::BlockDevice,
114
+ FileType::Chrdev => Filetype::CharacterDevice,
115
+ _ => Filetype::Unknown,
136
116
  }
137
- Ok(&buf[..buf.len().min(usize::try_from(room).unwrap_or(usize::MAX))])
138
117
  }
139
118
 
140
- /// A call any file or directory answers, which preview1 refuses for
141
- /// stdio.
142
- fn file_or_directory(table: &Descriptors, fd: u32) -> Answer {
143
- match descriptor(table, fd)? {
144
- Descriptor::Directory(_) | Descriptor::File(_) => Ok(()),
145
- Descriptor::Stdin(_) | Descriptor::Channel(_) => Err(Errno::Badf.into()),
119
+ /// A `filestat` for what a Filesystem reports a path names, as the guest
120
+ /// holding `table` sees it, at time zero where it keeps no time.
121
+ pub(crate) fn to_filestat(table: &Descriptors, stat: Kstat) -> types::Filestat {
122
+ let stat = table.profile().guest_kstat(stat);
123
+ types::Filestat {
124
+ dev: stat.dev,
125
+ ino: stat.ino,
126
+ filetype: to_filetype(stat.file_type),
127
+ nlink: stat.nlink,
128
+ size: stat.size,
129
+ atim: to_timestamp(stat.atime),
130
+ mtim: to_timestamp(stat.mtime),
131
+ ctim: to_timestamp(stat.ctime),
146
132
  }
147
133
  }
148
134
 
149
- /// A `filestat` of `filetype`, named `nlink` times and `size` bytes long.
150
- fn write_filestat(mem: &mut GuestMemory, out: u32, filetype: u8, nlink: u64, size: u64) -> Answer {
151
- GuestMemory::check_aligned(out, 8)?;
152
- mem.write(out, &[0; 64])?;
153
- mem.write_u8(GuestMemory::field(out, 16)?, filetype)?;
154
- mem.write_u64(GuestMemory::field(out, 24)?, nlink)?;
155
- mem.write_u64(GuestMemory::field(out, 32)?, size)
135
+ /// `time` as a preview1 `timestamp`, nanoseconds since the Unix epoch;
136
+ /// zero for none, and the largest timestamp for a time past it.
137
+ fn to_timestamp(time: Option<SystemTime>) -> u64 {
138
+ time.and_then(|time| time.duration_since(UNIX_EPOCH).ok())
139
+ .map_or(0, |since| {
140
+ u64::try_from(since.as_nanos()).unwrap_or(u64::MAX)
141
+ })
156
142
  }
157
143
 
158
- fn to_filetype(file_type: FileType) -> u8 {
159
- match file_type {
160
- FileType::Directory => FILETYPE_DIRECTORY,
161
- FileType::Symlink => FILETYPE_SYMBOLIC_LINK,
162
- FileType::File => FILETYPE_REGULAR_FILE,
163
- _ => FILETYPE_UNKNOWN,
144
+ /// Set the times `flags` ask through `call`, answering before it looks at the
145
+ /// name or the descriptor where they ask none, as Linux's `utimensat` does
146
+ /// for both. Setting a timestamp and setting it to now are exclusive: asking
147
+ /// both is `INVAL`.
148
+ pub(crate) fn utimensat(
149
+ atim: types::Timestamp,
150
+ mtim: types::Timestamp,
151
+ flags: Fstflags,
152
+ call: impl FnOnce(&Iattr) -> Answer,
153
+ ) -> Answer {
154
+ match to_iattr(atim, mtim, flags)? {
155
+ Some(times) => call(&times),
156
+ None => Ok(()),
164
157
  }
165
158
  }
166
159
 
167
- /// A `filestat` for what a Filesystem reports a path names, named once
168
- /// since `Metadata` carries no count of names.
169
- pub(crate) fn write_metadata(mem: &mut GuestMemory, out: u32, metadata: Metadata) -> Answer {
170
- write_filestat(mem, out, to_filetype(metadata.file_type), 1, metadata.len)
160
+ /// The times `flags` ask to set, `None` where they ask none.
161
+ fn to_iattr(
162
+ atim: types::Timestamp,
163
+ mtim: types::Timestamp,
164
+ flags: Fstflags,
165
+ ) -> Answer<Option<Iattr<'static>>> {
166
+ let time = |at, set, now| match (flags.contains(set), flags.contains(now)) {
167
+ (true, true) => Err(Errno::Inval),
168
+ (true, false) => Ok(Some(SetTime::At(UNIX_EPOCH + Duration::from_nanos(at)))),
169
+ (false, true) => Ok(Some(SetTime::Now)),
170
+ (false, false) => Ok(None),
171
+ };
172
+ let atime = time(atim, Fstflags::ATIM, Fstflags::ATIM_NOW)?;
173
+ let mtime = time(mtim, Fstflags::MTIM, Fstflags::MTIM_NOW)?;
174
+ Ok((atime.is_some() || mtime.is_some()).then(|| Iattr::times(atime, mtime)))
171
175
  }
172
176
 
173
- /// The hints preview1 offers about a file. A Filesystem that ignores them
174
- /// is still correct, so they succeed over a range Linux takes.
175
- pub(crate) fn advise(
176
- table: &mut Descriptors,
177
- fd: u32,
178
- offset: u64,
179
- len: u64,
180
- advice: u32,
181
- ) -> Answer {
182
- args::variant(advice, args::ADVICE_MAX)?;
183
- file(table, fd)?;
184
- refuse_negative(offset)?;
185
- refuse_negative(len).map(drop)
177
+ pub(crate) fn advise(table: &mut Descriptors, fd: u32, offset: u64, len: u64) -> Answer {
178
+ Ok(file_mut(table, fd)?.fadvise(offset, len)?)
186
179
  }
187
180
 
188
- /// A Filesystem has no room to reserve ahead of a write, so allocating it
189
- /// is unsupported rather than answered as done.
190
- pub(crate) fn allocate(table: &mut Descriptors, fd: u32) -> Answer {
191
- file(table, fd)?;
192
- Err(Errno::Notsup.into())
181
+ pub(crate) fn allocate(table: &mut Descriptors, fd: u32, offset: u64, len: u64) -> Answer {
182
+ Ok(file_mut(table, fd)?.fallocate(offset, len)?)
193
183
  }
194
184
 
195
185
  pub(crate) fn close(table: &mut Descriptors, fd: u32) -> Answer {
196
- match table.remove(fd).ok_or(Errno::Badf)? {
197
- Descriptor::File(open) => Ok(open.file.close()?),
198
- _ => Ok(()),
199
- }
186
+ Ok(table.remove(fd).ok_or(Errno::Badf)?.file.close()?)
200
187
  }
201
188
 
202
- pub(crate) fn sync(table: &mut Descriptors, fd: u32) -> Answer {
203
- Ok(file(table, fd)?.file.sync()?)
189
+ pub(crate) fn sync(table: &mut Descriptors, fd: u32, datasync: bool) -> Answer {
190
+ Ok(file_mut(table, fd)?.fsync(datasync)?)
204
191
  }
205
192
 
206
- pub(crate) fn fdstat_get(table: &Descriptors, mem: &mut GuestMemory, fd: u32, out: u32) -> Answer {
207
- let (filetype, flags, base, inheriting) = match descriptor(table, fd)? {
208
- Descriptor::Stdin(_) => (FILETYPE_UNKNOWN, 0, RIGHTS_FD_READ, RIGHTS_FD_READ),
209
- Descriptor::Channel(_) => (FILETYPE_UNKNOWN, 0, RIGHTS_FD_WRITE, RIGHTS_FD_WRITE),
210
- Descriptor::Directory(dir) if dir.preopen => {
211
- (FILETYPE_DIRECTORY, 0, PREOPEN_RIGHTS, PREOPEN_INHERITING)
193
+ pub(crate) fn fdstat_get(table: &Descriptors, fd: u32) -> Answer<types::Fdstat> {
194
+ let descriptor = descriptor(table, fd)?;
195
+ let file = &descriptor.file;
196
+ let mut fs_flags = Fdflags::empty();
197
+ fs_flags.set(Fdflags::APPEND, file.append());
198
+ fs_flags.set(Fdflags::NONBLOCK, file.nonblock());
199
+ fs_flags.set(Fdflags::DSYNC, file.dsync());
200
+ fs_flags.set(Fdflags::SYNC, file.sync());
201
+ let (fs_rights_base, fs_rights_inheriting) = if file.is_stream() {
202
+ let mut rights = Rights::empty();
203
+ rights.set(Rights::FD_READ, file.readable());
204
+ rights.set(Rights::FD_WRITE, file.writable());
205
+ (rights, rights)
206
+ } else if descriptor.preopen_path.is_some() {
207
+ (preopen_rights(), directory_rights_inheriting())
208
+ } else if file.is_directory() {
209
+ let mut rights = Rights::all()
210
+ - Rights::FD_WRITE
211
+ - Rights::FD_SEEK
212
+ - Rights::FD_FILESTAT_SET_SIZE
213
+ - Rights::PATH_FILESTAT_SET_SIZE;
214
+ if !file.readable() {
215
+ rights -= Rights::FD_READ | Rights::FD_READDIR;
212
216
  }
213
- Descriptor::Directory(_) => {
214
- let rights = args::RIGHTS
215
- & !RIGHTS_FD_WRITE
216
- & !RIGHTS_FD_SEEK
217
- & !RIGHTS_FD_FILESTAT_SET_SIZE
218
- & !RIGHTS_PATH_FILESTAT_SET_SIZE;
219
- (FILETYPE_DIRECTORY, 0, rights, rights)
217
+ (rights, directory_rights_inheriting())
218
+ } else {
219
+ let mut rights = Rights::all();
220
+ if !file.readable() {
221
+ rights -= Rights::FD_READ | Rights::FD_READDIR;
220
222
  }
221
- Descriptor::File(file) => {
222
- let mut rights = args::RIGHTS;
223
- if !file.readable {
224
- rights &= !RIGHTS_FD_READ & !RIGHTS_FD_READDIR;
225
- }
226
- if !file.writable {
227
- rights &= !RIGHTS_FD_WRITE;
228
- }
229
- let flags = if file.append {
230
- FDFLAGS_APPEND as u16
231
- } else {
232
- 0
233
- };
234
- (FILETYPE_REGULAR_FILE, flags, rights, rights)
223
+ if !file.writable() {
224
+ rights -= Rights::FD_WRITE;
235
225
  }
226
+ (rights, rights)
236
227
  };
237
- GuestMemory::check_aligned(out, 8)?;
238
- mem.write_u8(out, filetype)?;
239
- mem.write_u16(GuestMemory::field(out, 2)?, flags)?;
240
- mem.write_u64(GuestMemory::field(out, 8)?, base)?;
241
- mem.write_u64(GuestMemory::field(out, 16)?, inheriting)
242
- }
243
-
244
- /// Only appending can change once a file is open; the sync flags are
245
- /// `INVAL`, as in wasmtime-wasi, and a file never stops blocking, so
246
- /// asking it to is unsupported.
247
- pub(crate) fn fdstat_set_flags(table: &mut Descriptors, fd: u32, flags: u32) -> Answer {
248
- args::flags(flags, args::FDFLAGS)?;
249
- let file = file(table, fd)?;
250
- if flags & FDFLAGS_SYNCHRONIZED != 0 {
251
- return Err(Errno::Inval.into());
252
- }
253
- if flags & FDFLAGS_NONBLOCK != 0 {
254
- return Err(Errno::Notsup.into());
255
- }
256
- file.append = flags & FDFLAGS_APPEND != 0;
228
+ Ok(types::Fdstat {
229
+ fs_filetype: to_filetype(file.kind()),
230
+ fs_flags,
231
+ fs_rights_base,
232
+ fs_rights_inheriting,
233
+ })
234
+ }
235
+
236
+ pub(crate) fn fdstat_set_flags(table: &mut Descriptors, fd: u32, flags: Fdflags) -> Answer {
237
+ file_mut(table, fd)?.setfl(
238
+ flags.contains(Fdflags::APPEND),
239
+ flags.contains(Fdflags::NONBLOCK),
240
+ );
257
241
  Ok(())
258
242
  }
259
243
 
260
244
  /// Rights only narrow at open, so a later change is unsupported.
261
- pub(crate) fn fdstat_set_rights(
262
- table: &Descriptors,
263
- fd: u32,
264
- base: u64,
265
- inheriting: u64,
266
- ) -> Answer {
267
- args::flags(base, args::RIGHTS)?;
268
- args::flags(inheriting, args::RIGHTS)?;
269
- file_or_directory(table, fd)?;
245
+ pub(crate) fn fdstat_set_rights(table: &Descriptors, fd: u32) -> Answer {
246
+ refuse_stream(table, fd)?;
270
247
  Err(Errno::Notsup.into())
271
248
  }
272
249
 
273
- pub(crate) fn filestat_get(
274
- table: &Descriptors,
275
- mem: &mut GuestMemory,
276
- fd: u32,
277
- out: u32,
278
- ) -> Answer {
279
- match descriptor(table, fd)? {
280
- Descriptor::Stdin(_) | Descriptor::Channel(_) => {
281
- write_filestat(mem, out, FILETYPE_UNKNOWN, 0, 0)
282
- }
283
- Descriptor::Directory(dir) => {
284
- confirm(dir)?;
285
- write_metadata(mem, out, dir.mount.filesystem().metadata(&dir.path)?)
286
- }
287
- Descriptor::File(file) => write_metadata(mem, out, file.file.metadata()?),
288
- }
250
+ pub(crate) fn filestat_get(table: &Descriptors, fd: u32) -> Answer<types::Filestat> {
251
+ Ok(to_filestat(table, descriptor(table, fd)?.file.getattr()?))
289
252
  }
290
253
 
291
- /// A Mount that refuses the write answers first, as wasmtime-wasi answers
292
- /// by its preopen; a file opened only to read then answers as Linux's
293
- /// `ftruncate` does.
254
+ /// A size Linux's signed `loff_t` reads as negative answers before the
255
+ /// descriptor is looked for, as `ksys_ftruncate` checks it before `fdget`.
294
256
  pub(crate) fn filestat_set_size(table: &mut Descriptors, fd: u32, size: u64) -> Answer {
295
- let file = file(table, fd)?;
296
- if !file.mount.allows_writes() {
297
- return Err(Errno::Perm.into());
298
- }
299
- if !file.writable {
257
+ if i64::try_from(size).is_err() {
300
258
  return Err(Errno::Inval.into());
301
259
  }
302
- Ok(file.file.set_len(refuse_negative(size)?)?)
260
+ Ok(file_mut(table, fd)?.truncate(size)?)
303
261
  }
304
262
 
305
- /// A Filesystem keeps no timestamps of the guest's choosing, so setting
306
- /// them is unsupported rather than silently ignored.
307
- pub(crate) fn filestat_set_times(table: &Descriptors, fd: u32, fst_flags: u32) -> Answer {
308
- args::fstflags(fst_flags)?;
309
- file_or_directory(table, fd)?;
310
- Err(Errno::Notsup.into())
263
+ pub(crate) fn filestat_set_times(table: &Descriptors, fd: u32, times: &Iattr) -> Answer {
264
+ let file = file(table, fd)?;
265
+ Ok(retry_eagain(|| {
266
+ table.walk_guard().check(file.path())?;
267
+ vfs::do_utimes_fd(table.cred(), file.path(), times)
268
+ })?)
311
269
  }
312
270
 
313
271
  pub(crate) fn pread(
314
272
  table: &mut Descriptors,
315
- mem: &mut GuestMemory,
273
+ mem: &mut GuestMemory<'_, '_>,
316
274
  fd: u32,
317
- iovs: (u32, u32),
275
+ iovs: types::IovecArray,
318
276
  offset: u64,
319
- nread: u32,
320
- ) -> Answer {
321
- let (ptr, len) = mem.first_non_empty_buffer(iovs.0, iovs.1)?;
322
- let file = match descriptor_mut(table, fd)? {
323
- Descriptor::Stdin(_) => return Err(Errno::Spipe.into()),
324
- Descriptor::Channel(_) | Descriptor::Directory(_) => return Err(Errno::Badf.into()),
325
- Descriptor::File(file) => file,
326
- };
327
- if !file.readable {
328
- return Err(Errno::Badf.into());
329
- }
330
- let offset = refuse_reading_past_largest_offset(refuse_negative(offset)?, len)?;
331
- let read = file.file.read_at(mem.bytes_mut(ptr, len)?, offset)?;
332
- mem.write_u32(nread, read as u32)
277
+ ) -> Answer<types::Size> {
278
+ let buf = mem.first_non_empty_iovec(iovs)?;
279
+ let file = file_mut(table, fd)?;
280
+ file.may_pread(offset)?;
281
+ Ok(file.pread(mem.bytes_mut(buf)?, offset)? as u32)
333
282
  }
334
283
 
335
- pub(crate) fn prestat_get(table: &Descriptors, mem: &mut GuestMemory, fd: u32, out: u32) -> Answer {
336
- match descriptor(table, fd)? {
337
- Descriptor::Directory(dir) if dir.preopen => {
338
- GuestMemory::check_aligned(out, 4)?;
339
- mem.write_u8(out, 0)?;
340
- mem.write_u32(GuestMemory::field(out, 4)?, dir.mount.at().len() as u32)
341
- }
342
- _ => Err(Errno::Badf.into()),
284
+ pub(crate) fn prestat_get(table: &Descriptors, fd: u32) -> Answer<types::Prestat> {
285
+ match &descriptor(table, fd)?.preopen_path {
286
+ Some(at) => Ok(types::Prestat::Dir(types::PrestatDir {
287
+ pr_name_len: at.len() as u32,
288
+ })),
289
+ None => Err(Errno::Badf.into()),
343
290
  }
344
291
  }
345
292
 
346
293
  pub(crate) fn prestat_dir_name(
347
294
  table: &Descriptors,
348
- mem: &mut GuestMemory,
295
+ mem: &mut GuestMemory<'_, '_>,
349
296
  fd: u32,
350
- path: u32,
297
+ path: GuestPtr<u8>,
351
298
  path_len: u32,
352
299
  ) -> Answer {
353
- match descriptor(table, fd)? {
354
- Descriptor::Directory(dir) if dir.preopen => {
355
- let name = dir.mount.at().as_bytes();
300
+ match &descriptor(table, fd)?.preopen_path {
301
+ Some(at) => {
302
+ let name = at.as_bytes();
356
303
  if name.len() > path_len as usize {
357
304
  return Err(Errno::Nametoolong.into());
358
305
  }
359
- mem.write(path, name)
306
+ mem.write_bytes(path, name)
360
307
  }
361
- _ => Err(Errno::Notdir.into()),
308
+ None => Err(Errno::Notdir.into()),
362
309
  }
363
310
  }
364
311
 
365
312
  pub(crate) fn pwrite(
366
313
  table: &mut Descriptors,
367
- mem: &mut GuestMemory,
314
+ mem: &mut GuestMemory<'_, '_>,
368
315
  fd: u32,
369
- ciovs: (u32, u32),
316
+ ciovs: types::CiovecArray,
370
317
  offset: u64,
371
- nwritten: u32,
372
- ) -> Answer {
373
- let (ptr, len) = mem.first_non_empty_buffer(ciovs.0, ciovs.1)?;
374
- let file = match descriptor_mut(table, fd)? {
375
- Descriptor::Channel(_) => return Err(Errno::Spipe.into()),
376
- Descriptor::Stdin(_) | Descriptor::Directory(_) => return Err(Errno::Badf.into()),
377
- Descriptor::File(file) => file,
378
- };
379
- // The buffer is bounded before the access is checked, as wasmtime-wasi
380
- // bounds it before the host refuses the write.
381
- let buf = mem.bytes(ptr, len)?;
382
- if !file.writable {
383
- return Err(Errno::Badf.into());
384
- }
385
- let offset = refuse_negative(offset)?;
386
- // Linux's `pwrite` ignores the offset of a file opened to append.
387
- let written = if file.append {
388
- file.file.append(buf)?.1
389
- } else {
390
- file.file
391
- .write_at(trim_to_largest_offset(buf, offset)?, offset)?
392
- };
393
- mem.write_u32(nwritten, written as u32)
318
+ ) -> Answer<types::Size> {
319
+ let buf = mem.first_non_empty_ciovec(ciovs)?;
320
+ let file = file_mut(table, fd)?;
321
+ file.may_pwrite(offset)?;
322
+ Ok(file.pwrite(mem.bytes(buf)?, offset)? as u32)
394
323
  }
395
324
 
396
325
  pub(crate) fn read(
397
326
  table: &mut Descriptors,
398
- mem: &mut GuestMemory,
327
+ mem: &mut GuestMemory<'_, '_>,
399
328
  fd: u32,
400
- iovs: (u32, u32),
401
- nread: u32,
402
- ) -> Answer {
403
- let (ptr, len) = mem.first_non_empty_buffer(iovs.0, iovs.1)?;
404
- let read = match descriptor_mut(table, fd)? {
405
- Descriptor::Stdin(pipe) => {
406
- // Bounded before stdin is drained, so a trap takes no bytes.
407
- mem.bytes(ptr, len)?;
408
- let bytes = read_stdin(pipe, len as usize)?;
409
- mem.write(ptr, &bytes)?;
410
- bytes.len()
411
- }
412
- Descriptor::File(file) => {
413
- // Bounded before the access is checked, as wasmtime-wasi bounds
414
- // it before the host refuses the read.
415
- let buf = mem.bytes_mut(ptr, len)?;
416
- if !file.readable {
417
- return Err(Errno::Badf.into());
418
- }
419
- let at = refuse_reading_past_largest_offset(file.position, len)?;
420
- let read = file.file.read_at(buf, at)?;
421
- file.position += read as u64;
422
- read
329
+ iovs: types::IovecArray,
330
+ ) -> Answer<types::Size> {
331
+ let buf = mem.first_non_empty_iovec(iovs)?;
332
+ let file = file_mut(table, fd)?;
333
+ file.may_read()?;
334
+ // Bounded before the file is asked, as a trap takes no bytes from a pipe.
335
+ Ok(file.read(mem.bytes_mut(buf)?)? as u32)
336
+ }
337
+
338
+ /// The `dirent` header ahead of one entry's name, its `d_next` left for the
339
+ /// listing to fill in once it knows it.
340
+ fn to_dirent_header(emit: &DirEmit<'_>, d_ino: u64) -> Answer<Vec<u8>> {
341
+ let mut header = vec![0; types::Dirent::guest_size() as usize];
342
+ WitxMemory::Unshared(&mut header).write(
343
+ GuestPtr::new(0),
344
+ types::Dirent {
345
+ d_next: 0,
346
+ d_ino,
347
+ d_namlen: emit.name.len() as u32,
348
+ d_type: to_filetype(emit.file_type),
349
+ },
350
+ )?;
351
+ Ok(header)
352
+ }
353
+
354
+ /// One entry as a preview1 `dirent` at `used` bytes into the buffer, cut off
355
+ /// where it ends, answering where its header stands when it fit whole.
356
+ fn write_dirent(
357
+ mem: &mut GuestMemory<'_, '_>,
358
+ buf: (GuestPtr<u8>, u32),
359
+ used: &mut u32,
360
+ profile: Profile,
361
+ emit: &DirEmit<'_>,
362
+ ) -> Answer<Option<u32>> {
363
+ let (start, capacity) = buf;
364
+ let at = *used;
365
+ let header = to_dirent_header(emit, profile.guest_ino(emit.identity))?;
366
+ let mut whole = true;
367
+ for part in [&header[..], emit.name.as_bytes()] {
368
+ let room = (capacity - *used) as usize;
369
+ let take = part.len().min(room);
370
+ if take > 0 {
371
+ mem.write_bytes(start.add(*used)?, &part[..take])?;
423
372
  }
424
- Descriptor::Channel(_) | Descriptor::Directory(_) => return Err(Errno::Badf.into()),
425
- };
426
- mem.write_u32(nread, read as u32)
373
+ *used += take as u32;
374
+ whole &= take == part.len();
375
+ }
376
+ Ok(whole.then_some(at))
427
377
  }
428
378
 
429
- /// A directory's entries as preview1 `dirent`s, `.` and `..` first, from
430
- /// the `cookie`th on, cut off where the buffer ends. A listing from the
431
- /// start reads the directory; one going on reads the entries that listing
432
- /// began with, so a guest reading in small steps costs one listing, not one
433
- /// per step.
379
+ /// Where a listing goes on from past the entry whose header stands at `at`,
380
+ /// as `filldir64` writes `prev->d_off` once the next entry or the listing's end
381
+ /// says, `d_next` leading each header.
382
+ fn write_d_next(
383
+ mem: &mut GuestMemory<'_, '_>,
384
+ start: GuestPtr<u8>,
385
+ at: u32,
386
+ d_next: u64,
387
+ ) -> Answer {
388
+ mem.write_bytes(start.add(at)?, &d_next.to_le_bytes())?;
389
+ Ok(())
390
+ }
391
+
392
+ /// A directory's entries from the `cookie`th on, cut off where the buffer
393
+ /// ends.
434
394
  pub(crate) fn readdir(
435
395
  table: &mut Descriptors,
436
- mem: &mut GuestMemory,
396
+ mem: &mut GuestMemory<'_, '_>,
437
397
  fd: u32,
438
- buf: (u32, u32),
398
+ start: GuestPtr<u8>,
399
+ capacity: u32,
439
400
  cookie: u64,
440
- bufused: u32,
441
- ) -> Answer {
442
- let dir = match descriptor_mut(table, fd)? {
443
- Descriptor::Directory(dir) => dir,
444
- _ => return Err(Errno::Badf.into()),
445
- };
446
- let skip = usize::try_from(cookie).map_err(|_| Errno::Overflow)?;
447
- confirm(dir)?;
448
- let listing = match dir.listing.take() {
449
- Some(listing) if cookie != 0 => listing,
450
- _ => dir.mount.filesystem().read_dir(&dir.path)?,
451
- };
452
- let entries = [(".", FILETYPE_DIRECTORY), ("..", FILETYPE_DIRECTORY)]
453
- .into_iter()
454
- .chain(
455
- listing
456
- .iter()
457
- .map(|entry| (entry.name.as_str(), to_filetype(entry.file_type))),
458
- );
459
- let (start, capacity) = buf;
401
+ ) -> Answer<types::Size> {
402
+ let profile = table.profile();
403
+ let dir = file_mut(table, fd)?;
404
+ // preview1 names where a listing goes on by its cookie, as wasi-libc's
405
+ // `seekdir` moves a directory's position before `getdents`; a listing going
406
+ // on from where the last one stopped needs no seek.
407
+ if cookie != dir.pos() {
408
+ let cookie = i64::try_from(cookie).map_err(|_| FilesystemError::InvalidInput)?;
409
+ dir.llseek(cookie, Whence::Set)?;
410
+ }
460
411
  let mut used: u32 = 0;
461
- let mut full = false;
462
- 'entries: for (index, (name, filetype)) in entries.enumerate().skip(skip) {
463
- let mut dirent = [0u8; DIRENT_SIZE as usize];
464
- dirent[0..8].copy_from_slice(&(index as u64 + 1).to_le_bytes());
465
- dirent[16..20].copy_from_slice(&(name.len() as u32).to_le_bytes());
466
- dirent[20] = filetype;
467
- for part in [&dirent[..], name.as_bytes()] {
468
- let room = (capacity - used) as usize;
469
- let take = part.len().min(room);
470
- if take > 0 {
471
- mem.write(GuestMemory::field(start, used)?, &part[..take])?;
412
+ let mut failed = None;
413
+ let mut last_whole = None;
414
+ let mut actor = |emit: &DirEmit<'_>| {
415
+ let written = verify_dirent_name(emit.name)
416
+ .map_err(Into::into)
417
+ .and_then(|()| match last_whole.take() {
418
+ Some(at) => write_d_next(mem, start, at, emit.offset),
419
+ None => Ok(()),
420
+ })
421
+ .and_then(|()| write_dirent(mem, (start, capacity), &mut used, profile, emit));
422
+ match written {
423
+ Ok(whole) => {
424
+ last_whole = whole;
425
+ whole.is_some()
472
426
  }
473
- used += take as u32;
474
- if used == capacity {
475
- full = true;
476
- break 'entries;
427
+ Err(error) => {
428
+ failed = Some(error);
429
+ false
477
430
  }
478
431
  }
432
+ };
433
+ let listed = dir.iterate_dir(&mut DirContext::new(0, &mut actor));
434
+ // The last entry written whole goes on from where the listing stopped,
435
+ // as `getdents64` sets the last `d_off` from `f_pos`.
436
+ if let Some(at) = last_whole {
437
+ write_d_next(mem, start, at, dir.pos())?;
479
438
  }
480
- if full {
481
- dir.listing = Some(listing);
439
+ // What was listed answers before what stopped the listing, which the next
440
+ // call meets first, as Linux's `getdents64` answers the bytes it wrote over
441
+ // `iterate_dir`'s error and `buf.error`.
442
+ match listed.err().map(Into::into).or(failed) {
443
+ Some(error) if used == 0 => Err(error),
444
+ _ => Ok(used),
482
445
  }
483
- mem.write_u32(bufused, used)
484
446
  }
485
447
 
486
- /// preview1 renumbering closes `to` as `fd_close` does, answering a close
487
- /// its Filesystem refuses, and moves `from` onto its number; both must
488
- /// already be open.
448
+ /// preview1 renumbering moves `from` onto `to`, both already open, then
449
+ /// closes what stood at `to`, ignoring how its close answers, as Linux's
450
+ /// `do_dup2` installs the file before `filp_close` of the one it replaced.
489
451
  pub(crate) fn renumber(table: &mut Descriptors, from: u32, to: u32) -> Answer {
490
452
  if table.get(from).is_none() || table.get(to).is_none() {
491
453
  return Err(Errno::Badf.into());
@@ -493,99 +455,41 @@ pub(crate) fn renumber(table: &mut Descriptors, from: u32, to: u32) -> Answer {
493
455
  if from == to {
494
456
  return Ok(());
495
457
  }
496
- close(table, to)?;
497
- let moved = table.remove(from).ok_or(Errno::Badf)?;
458
+ let moved = table.remove(from).expect("found open above");
459
+ let replaced = table.remove(to).expect("found open above");
498
460
  table.place(to, moved);
461
+ let _ = replaced.file.close();
499
462
  Ok(())
500
463
  }
501
464
 
502
465
  pub(crate) fn seek(
503
466
  table: &mut Descriptors,
504
- mem: &mut GuestMemory,
505
467
  fd: u32,
506
468
  offset: i64,
507
- whence: u32,
508
- out: u32,
509
- ) -> Answer {
510
- args::variant(whence, args::WHENCE_MAX)?;
511
- let file = seekable(table, fd)?;
512
- let base = match whence {
513
- 0 => 0,
514
- 1 => file.position,
515
- _ => file.file.metadata()?.len,
469
+ whence: types::Whence,
470
+ ) -> Answer<u64> {
471
+ let whence = match whence {
472
+ types::Whence::Set => Whence::Set,
473
+ types::Whence::Cur => Whence::Cur,
474
+ types::Whence::End => Whence::End,
516
475
  };
517
- // Linux holds an offset in a signed 64-bit `off_t`, past which a seek
518
- // is `OVERFLOW` rather than a position a write could wrap around.
519
- let position = base.checked_add_signed(offset).ok_or(Errno::Inval)?;
520
- if position > i64::MAX as u64 {
521
- return Err(Errno::Overflow.into());
522
- }
523
- file.position = position;
524
- mem.write_u64(out, position)
476
+ Ok(seekable_file(table, fd)?.llseek(offset, whence)?)
525
477
  }
526
478
 
527
- pub(crate) fn tell(table: &mut Descriptors, mem: &mut GuestMemory, fd: u32, out: u32) -> Answer {
528
- let position = seekable(table, fd)?.position;
529
- mem.write_u64(out, position)
479
+ pub(crate) fn tell(table: &mut Descriptors, fd: u32) -> Answer<u64> {
480
+ Ok(seekable_file(table, fd)?.llseek(0, Whence::Cur)?)
530
481
  }
531
482
 
532
483
  pub(crate) fn write(
533
484
  table: &mut Descriptors,
534
- mem: &mut GuestMemory,
485
+ mem: &mut GuestMemory<'_, '_>,
535
486
  fd: u32,
536
- ciovs: (u32, u32),
537
- nwritten: u32,
538
- ) -> Answer {
539
- let (ptr, len) = mem.first_non_empty_buffer(ciovs.0, ciovs.1)?;
540
- let written = match descriptor_mut(table, fd)? {
541
- Descriptor::Channel(pipe) => write_channel(pipe, mem.bytes(ptr, len)?)?,
542
- Descriptor::File(file) => {
543
- // Bounded before the access is checked, as in `pwrite`.
544
- let buf = mem.bytes(ptr, len)?;
545
- if !file.writable {
546
- return Err(Errno::Badf.into());
547
- }
548
- let (at, written) = if file.append {
549
- file.file.append(buf)?
550
- } else {
551
- let buf = trim_to_largest_offset(buf, file.position)?;
552
- (file.position, file.file.write_at(buf, file.position)?)
553
- };
554
- file.position = at.checked_add(written as u64).ok_or(Errno::Fbig)?;
555
- written
556
- }
557
- Descriptor::Stdin(_) | Descriptor::Directory(_) => return Err(Errno::Badf.into()),
558
- };
559
- mem.write_u32(nwritten, written as u32)
560
- }
561
-
562
- /// A zero-length read of a stdin that still holds bytes is `INTR`, and one
563
- /// of a drained stdin reads nothing, as wasmtime-wasi's blocking read
564
- /// answers them.
565
- fn read_stdin(pipe: &mut MemoryInputPipe, len: usize) -> Answer<Vec<u8>> {
566
- match pipe.read(len) {
567
- Ok(bytes) if bytes.is_empty() => Err(Errno::Intr.into()),
568
- Ok(bytes) => Ok(bytes.to_vec()),
569
- Err(StreamError::Closed) => Ok(Vec::new()),
570
- Err(_) => Err(Errno::Io.into()),
571
- }
572
- }
573
-
574
- /// Fill the capture pipe as far as it holds. A pipe that fills before the
575
- /// bytes run out answers `IO`, though what fit is kept: the cap one past
576
- /// the configured limit is how a capture learns it was truncated.
577
- fn write_channel(pipe: &mut MemoryOutputPipe, bytes: &[u8]) -> Answer<usize> {
578
- for chunk in bytes.chunks(WRITE_CHUNK) {
579
- let mut rest = chunk;
580
- while !rest.is_empty() {
581
- let permit = pipe.check_write().map_err(|_| Errno::Io)?;
582
- let (now, later) = rest.split_at(rest.len().min(permit));
583
- pipe.write(Bytes::copy_from_slice(now))
584
- .map_err(|_| Errno::Io)?;
585
- rest = later;
586
- }
587
- }
588
- Ok(bytes.len())
487
+ ciovs: types::CiovecArray,
488
+ ) -> Answer<types::Size> {
489
+ let buf = mem.first_non_empty_ciovec(ciovs)?;
490
+ let file = file_mut(table, fd)?;
491
+ file.may_write()?;
492
+ Ok(file.write(mem.bytes(buf)?)? as u32)
589
493
  }
590
494
 
591
495
  #[cfg(test)]
@@ -594,79 +498,923 @@ mod tests {
594
498
  //! does, so its Filesystem learns of the close and may refuse it.
595
499
  use std::sync::Arc;
596
500
 
597
- use kobako_runtime::filesystem::{Access, File, FilesystemError, Memory, Metadata};
598
- use kobako_runtime::mount::Mount;
599
- use wasmtime_wasi::p2::pipe::{MemoryInputPipe, MemoryOutputPipe};
501
+ use super::{to_filetype, Filetype};
502
+
503
+ // @behavior RT-195
504
+ #[test]
505
+ fn a_special_files_kind_reads_as_preview1_names_it() {
506
+ assert_eq!(
507
+ [
508
+ FileType::Sock,
509
+ FileType::Blkdev,
510
+ FileType::Chrdev,
511
+ FileType::Fifo,
512
+ FileType::Unknown,
513
+ ]
514
+ .map(to_filetype),
515
+ [
516
+ Filetype::SocketStream,
517
+ Filetype::BlockDevice,
518
+ Filetype::CharacterDevice,
519
+ Filetype::Unknown,
520
+ Filetype::Unknown,
521
+ ],
522
+ "to_filetype must name a socket as a stream, each device as itself, and a FIFO, which \
523
+ preview1 has no code for, as unknown"
524
+ );
525
+ }
526
+
527
+ use super::{
528
+ fdstat_get, fdstat_set_flags, filestat_set_size, filestat_set_times, readdir, renumber,
529
+ seek, sync, tell, utimensat, Answer, Errno, Fdflags, Fstflags,
530
+ };
531
+
532
+ /// `fd_filestat_set_times` of `fd` setting both times to zero as `flags`
533
+ /// ask, as the dispatcher answers it.
534
+ fn set_times(table: &Descriptors, fd: u32, flags: Fstflags) -> Answer {
535
+ utimensat(0, 0, flags, |times| filestat_set_times(table, fd, times))
536
+ }
537
+ use crate::descriptors::{Descriptor, Descriptors};
538
+ use crate::preview1::memory::GuestMemory;
539
+ use crate::preview1::path::{
540
+ create_directory, filestat_set_times as path_set_times, open as path_open, readlink,
541
+ rename, symlink, PathOpen,
542
+ };
543
+ use crate::preview1::types::{self, Rights, Whence};
544
+ use kobako_runtime::filesystem::{create_pipe_files, PipeInodeInfo};
545
+ use kobako_runtime::filesystem::{
546
+ FileOperations, FileType, Filesystem, FilesystemError, InodeOperations, Kstat, Lookup,
547
+ };
548
+ use kobako_runtime::mount::{Access, Mount};
549
+ use kobako_runtime::profile::Profile;
550
+ use kobako_runtime::security::prepare_creds_within;
551
+ use kobako_runtime::vfs::{self, Dentry, Inode, OpenHow, Path, PATH_MAX};
552
+ use wiggle::{GuestPtr, GuestType};
553
+
554
+ /// What `do_file_open` takes for an open `how` asks.
555
+ fn flags_of(how: &OpenHow) -> vfs::OpenFlags {
556
+ vfs::build_open_flags(how).expect("the flags must mean an open")
557
+ }
600
558
 
601
- use super::renumber;
602
- use crate::descriptors::{Descriptor, Descriptors, OpenFile};
603
- use crate::preview1::errno::{Errno, Failure};
559
+ /// A table holding the three standard streams and `mounts`, at most `limit`
560
+ /// descriptors open, as a Driver makes one.
561
+ fn table_of(mounts: Vec<Mount>, limit: Option<usize>) -> Result<Descriptors, FilesystemError> {
562
+ let cred = prepare_creds_within(&[], None);
563
+ let ends = |pipe| create_pipe_files(&pipe, &cred);
564
+ Descriptors::new(
565
+ ends(PipeInodeInfo::from_bytes(Vec::new())).0,
566
+ ends(PipeInodeInfo::new(1)).1,
567
+ ends(PipeInodeInfo::new(1)).1,
568
+ mounts,
569
+ limit,
570
+ cred.clone(),
571
+ Profile::Hermetic,
572
+ )
573
+ }
574
+
575
+ /// A table holding only the three standard streams.
576
+ fn standard_streams() -> Descriptors {
577
+ table_of(Vec::new(), None).expect("a table holding no Mount must make")
578
+ }
579
+
580
+ /// The code a call answered, if it answered one.
581
+ fn errno<T>(answer: Answer<T>) -> Option<Errno> {
582
+ answer.err().and_then(|err| err.downcast().ok())
583
+ }
604
584
 
605
- /// A file whose Filesystem refuses to release it.
585
+ // @behavior RT-177
586
+ #[test]
587
+ fn a_standard_stream_answers_as_a_pipe_does() {
588
+ let mut table = standard_streams();
589
+ assert_eq!(
590
+ [
591
+ errno(sync(&mut table, 1, false)),
592
+ errno(filestat_set_size(&mut table, 1, 0)),
593
+ errno(tell(&mut table, 0)),
594
+ errno(tell(&mut table, 1)),
595
+ ],
596
+ [
597
+ Some(Errno::Inval),
598
+ Some(Errno::Inval),
599
+ Some(Errno::Spipe),
600
+ Some(Errno::Spipe)
601
+ ],
602
+ "fd_sync and fd_filestat_set_size on a standard stream through preview1 must answer \
603
+ EINVAL, and a call about its position ESPIPE, as Linux answers for a pipe"
604
+ );
605
+ }
606
+
607
+ /// A file whose Filesystem refuses to close it.
606
608
  struct RefusesClose;
607
609
 
608
- impl File for RefusesClose {
609
- fn read_at(&mut self, _buf: &mut [u8], _offset: u64) -> Result<usize, FilesystemError> {
610
- Ok(0)
610
+ impl FileOperations for RefusesClose {
611
+ fn flush(&mut self) -> Result<(), FilesystemError> {
612
+ Err(FilesystemError::PermissionDenied)
611
613
  }
614
+ }
612
615
 
613
- fn write_at(&mut self, buf: &[u8], _offset: u64) -> Result<usize, FilesystemError> {
614
- Ok(buf.len())
616
+ /// A file its Filesystem opens with no position.
617
+ struct Stream;
618
+
619
+ impl FileOperations for Stream {
620
+ fn is_stream(&self) -> bool {
621
+ true
615
622
  }
623
+ }
616
624
 
617
- fn append(&mut self, buf: &[u8]) -> Result<(u64, usize), FilesystemError> {
618
- Ok((0, buf.len()))
625
+ /// A directory holding at every name an entry of the kind it names, whose
626
+ /// open answers what its function makes.
627
+ struct Holds(FileType, fn() -> Box<dyn FileOperations>);
628
+
629
+ /// A directory holding a file its Filesystem refuses to close.
630
+ const HOLDS_REFUSES_CLOSE: Holds = Holds(FileType::RegFile, || Box::new(RefusesClose));
631
+
632
+ impl kobako_runtime::filesystem::FileSystemType for Holds {
633
+ fn fill_super(self: Arc<Self>, sb: &mut Filesystem) -> Result<(), FilesystemError> {
634
+ sb.d_make_root(kobako_runtime::vfs::Inode::new(
635
+ &kobako_runtime::Kstat::new(kobako_runtime::FileType::Dir, 0),
636
+ self,
637
+ ));
638
+ Ok(())
619
639
  }
640
+ }
620
641
 
621
- fn metadata(&self) -> Result<Metadata, FilesystemError> {
622
- Err(FilesystemError::Unsupported)
642
+ impl InodeOperations for Holds {
643
+ fn getattr(&self, _dentry: &Dentry) -> Result<Kstat, FilesystemError> {
644
+ Ok(Kstat::new(FileType::Dir, 0))
623
645
  }
624
646
 
625
- fn set_len(&mut self, _len: u64) -> Result<(), FilesystemError> {
626
- Ok(())
647
+ fn lookup(&self, _dentry: &Dentry) -> Result<Lookup, FilesystemError> {
648
+ let found = Kstat::new(self.0, 0);
649
+ Ok(Lookup::Found(Inode::new(
650
+ &found,
651
+ Arc::new(Self(self.0, self.1)),
652
+ )))
627
653
  }
628
654
 
629
- fn close(self: Box<Self>) -> Result<(), FilesystemError> {
630
- Err(FilesystemError::PermissionDenied)
655
+ fn open(
656
+ &self,
657
+ _dentry: &Dentry,
658
+ _options: &OpenHow,
659
+ ) -> Result<Box<dyn FileOperations>, FilesystemError> {
660
+ Ok((self.1)())
631
661
  }
632
662
  }
633
663
 
664
+ /// The file `holds` holds, opened as `how` asks beneath a Mount that
665
+ /// writes when `how` does.
666
+ fn open_held(holds: Holds, how: &OpenHow) -> Descriptor {
667
+ let access = match how.write {
668
+ true => Access::ReadWrite,
669
+ false => Access::Read,
670
+ };
671
+ let mount = Mount::new(
672
+ "/data",
673
+ Filesystem::new(Arc::new(holds)).expect("the instance must stand"),
674
+ access,
675
+ )
676
+ .expect("/data is a guest path");
677
+ let start = Path::root(mount.mnt());
678
+ Descriptor::for_file(
679
+ vfs::do_file_open(
680
+ &prepare_creds_within(&[], None),
681
+ &start,
682
+ "file",
683
+ &flags_of(how),
684
+ )
685
+ .unwrap_or_else(|_| panic!("the file must open")),
686
+ )
687
+ }
688
+
634
689
  fn open_file() -> Descriptor {
635
- Descriptor::File(OpenFile {
636
- file: Box::new(RefusesClose),
637
- position: 0,
638
- append: false,
639
- readable: true,
640
- writable: false,
641
- mount: Arc::new(
642
- Mount::new("/data", Arc::new(Memory::new()), Access::Read)
643
- .expect("/data is a guest path"),
690
+ let mut how = OpenHow::default();
691
+ how.read = true;
692
+ open_held(HOLDS_REFUSES_CLOSE, &how)
693
+ }
694
+
695
+ // @behavior RT-176
696
+ #[test]
697
+ fn a_file_without_fsync_refuses_a_sync() {
698
+ let mut table = standard_streams();
699
+ let fd = table.push(open_file()).expect("a descriptor to sync");
700
+ assert_eq!(
701
+ errno(sync(&mut table, fd, false)),
702
+ Some(Errno::Inval),
703
+ "fd_sync on a file whose Filesystem has no fsync must answer EINVAL"
704
+ );
705
+ }
706
+
707
+ // @behavior RT-129
708
+ #[test]
709
+ fn a_file_set_not_to_block_reports_it() {
710
+ let mut table = standard_streams();
711
+ let fd = table.push(open_file()).expect("a descriptor to set");
712
+ let flags = |table: &Descriptors| {
713
+ fdstat_get(table, fd)
714
+ .unwrap_or_else(|_| panic!("the file must report its flags"))
715
+ .fs_flags
716
+ };
717
+ assert!(
718
+ fdstat_set_flags(&mut table, fd, Fdflags::NONBLOCK | Fdflags::SYNC).is_ok(),
719
+ "fd_fdstat_set_flags asking not to block and to sync must succeed, as Linux's setfl"
720
+ );
721
+ assert_eq!(
722
+ flags(&table),
723
+ Fdflags::NONBLOCK,
724
+ "fd_fdstat_get after fd_fdstat_set_flags must report not blocking and, as Linux's \
725
+ setfl ignores it, no sync"
726
+ );
727
+ }
728
+
729
+ // A pipe end is open one way only, so a stream open both ways comes from
730
+ // a Filesystem's open.
731
+ // @behavior RT-275
732
+ #[test]
733
+ fn a_stream_reports_what_it_was_opened_and_set_for() {
734
+ let mut table = standard_streams();
735
+ let mut how = OpenHow::default();
736
+ how.read = true;
737
+ how.write = true;
738
+ let fd = table
739
+ .push(open_held(
740
+ Holds(FileType::RegFile, || Box::new(Stream)),
741
+ &how,
742
+ ))
743
+ .expect("a descriptor to set");
744
+ assert!(
745
+ fdstat_set_flags(&mut table, fd, Fdflags::APPEND | Fdflags::NONBLOCK).is_ok(),
746
+ "fd_fdstat_set_flags asking a stream to append and not to block must succeed"
747
+ );
748
+ let stat = fdstat_get(&table, fd).unwrap_or_else(|_| panic!("the stream must report"));
749
+ assert_eq!(
750
+ (
751
+ stat.fs_flags,
752
+ stat.fs_rights_base
753
+ .contains(Rights::FD_READ | Rights::FD_WRITE)
644
754
  ),
645
- })
755
+ (Fdflags::APPEND | Fdflags::NONBLOCK, true),
756
+ "fd_fdstat_get on a stream open to read and write, set to append and not to block, \
757
+ must report both flags and both rights, as Linux's F_GETFL reports a pipe's"
758
+ );
646
759
  }
647
760
 
648
- // @behavior RT-114
761
+ // @behavior RT-276
762
+ #[test]
763
+ fn a_descriptor_reports_the_kind_its_file_is() {
764
+ let mut table = standard_streams();
765
+ let fd = table
766
+ .push(open_held(
767
+ Holds(FileType::Chrdev, || Box::new(RefusesClose)),
768
+ &OpenHow::default(),
769
+ ))
770
+ .expect("a descriptor to ask about");
771
+ assert_eq!(
772
+ fdstat_get(&table, fd)
773
+ .unwrap_or_else(|_| panic!("the device must report"))
774
+ .fs_filetype,
775
+ types::Filetype::CharacterDevice,
776
+ "fd_fdstat_get on a character device opened for its path alone must report a \
777
+ character device, as fd_filestat_get does"
778
+ );
779
+ }
780
+
781
+ // wasi-testsuite's dir_fd_op_failures names these answers and writes that
782
+ // they part from POSIX, where a directory seeks.
783
+ // @behavior RT-184
784
+ #[test]
785
+ fn a_directory_refuses_a_call_about_its_position() {
786
+ let mount = Mount::new(
787
+ "/data",
788
+ Filesystem::new(Arc::new(HOLDS_REFUSES_CLOSE)).expect("the instance must stand"),
789
+ Access::Read,
790
+ )
791
+ .expect("/data is a guest path");
792
+ let mut table =
793
+ table_of(vec![mount], None).unwrap_or_else(|_| panic!("the table must make"));
794
+ assert_eq!(
795
+ (
796
+ errno(tell(&mut table, 3)),
797
+ errno(seek(&mut table, 3, 0, Whence::Set)),
798
+ ),
799
+ (Some(Errno::Badf), Some(Errno::Badf)),
800
+ "fd_tell and fd_seek on a directory through preview1 must answer EBADF, as \
801
+ wasi-testsuite requires"
802
+ );
803
+ }
804
+
805
+ // @behavior RT-251
806
+ #[test]
807
+ fn a_descriptor_reports_how_far_it_was_opened_to_sync() {
808
+ let tmpfs: kobako_runtime::filesystem::Tmpfs = [("f", "x")].into_iter().collect();
809
+ let mount = Mount::new(
810
+ "/data",
811
+ Filesystem::new(Arc::new(tmpfs)).expect("the instance must stand"),
812
+ Access::ReadWrite,
813
+ )
814
+ .expect("/data is a guest path");
815
+ let root = Path::root(mount.mnt());
816
+ let mut table =
817
+ table_of(vec![mount], None).unwrap_or_else(|_| panic!("the table must make"));
818
+ let cred = table.cred().clone();
819
+ let mut reported = |dsync, sync| {
820
+ let mut how = OpenHow::default();
821
+ how.write = true;
822
+ how.dsync = dsync;
823
+ how.sync = sync;
824
+ let opened = vfs::do_file_open(&cred, &root, "f", &flags_of(&how))
825
+ .unwrap_or_else(|_| panic!("the file must open"));
826
+ let fd = table
827
+ .push(Descriptor::for_file(opened))
828
+ .unwrap_or_else(|| panic!("the table must have room"));
829
+ let Ok(stat) = fdstat_get(&table, fd) else {
830
+ panic!("the descriptor must report itself");
831
+ };
832
+ (
833
+ stat.fs_flags.contains(Fdflags::DSYNC),
834
+ stat.fs_flags.contains(Fdflags::SYNC),
835
+ )
836
+ };
837
+ assert_eq!(
838
+ (reported(true, false), reported(false, true)),
839
+ ((true, false), (true, true)),
840
+ "fd_fdstat_get must report DSYNC alone for a file opened O_DSYNC, and both for one \
841
+ opened O_SYNC, whose bits hold O_DSYNC's as Linux's do"
842
+ );
843
+ }
844
+
845
+ /// Allows every path, counting each it is asked about.
846
+ #[derive(Default)]
847
+ struct CountsAsks(std::sync::atomic::AtomicUsize);
848
+
849
+ impl kobako_runtime::mount::Policy for CountsAsks {
850
+ fn allows(&self, _path: &str, _access: Access) -> bool {
851
+ self.0.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
852
+ true
853
+ }
854
+ }
855
+
856
+ // The guard sits in the Driver rather than the VFS, so only this call shows
857
+ // that setting a descriptor's times asks it.
858
+ // @behavior RT-201
859
+ #[test]
860
+ fn setting_a_descriptors_times_asks_whether_it_still_stands() {
861
+ use std::sync::atomic::Ordering;
862
+ let policy = Arc::new(CountsAsks::default());
863
+ let tmpfs: kobako_runtime::filesystem::Tmpfs = [("f", "x")].into_iter().collect();
864
+ let mount = Mount::new(
865
+ "/data",
866
+ Filesystem::new(Arc::new(tmpfs)).expect("the instance must stand"),
867
+ Access::ReadWrite,
868
+ )
869
+ .expect("/data is a guest path")
870
+ .with_policy(policy.clone());
871
+ let root = Path::root(mount.mnt());
872
+ let mut table =
873
+ table_of(vec![mount], None).unwrap_or_else(|_| panic!("the table must make"));
874
+ let cred = table.cred().clone();
875
+ let mut read = OpenHow::default();
876
+ read.read = true;
877
+ let opened = vfs::do_file_open(&cred, &root, "f", &flags_of(&read))
878
+ .unwrap_or_else(|_| panic!("the file must open"));
879
+ let fd = table
880
+ .push(Descriptor::for_file(opened))
881
+ .unwrap_or_else(|| panic!("the table must have room"));
882
+ vfs::filename_unlinkat(&cred, &root, "f").unwrap_or_else(|_| panic!("the name must go"));
883
+ policy.0.store(0, Ordering::SeqCst);
884
+ assert_eq!(
885
+ (
886
+ errno(set_times(&table, fd, Fstflags::ATIM_NOW)),
887
+ policy.0.load(Ordering::SeqCst)
888
+ ),
889
+ (Some(Errno::Noent), 0),
890
+ "fd_filestat_set_times through a descriptor of a file whose name is gone, beneath a \
891
+ Policy, must answer ENOENT without asking the Policy about the path it stood at"
892
+ );
893
+ }
894
+
895
+ // @behavior RT-186 RT-187
896
+ #[test]
897
+ fn setting_a_descriptors_times_answers_as_a_paths_do() {
898
+ let mount = Mount::new(
899
+ "/data",
900
+ Filesystem::new(Arc::new(HOLDS_REFUSES_CLOSE)).expect("the instance must stand"),
901
+ Access::Read,
902
+ )
903
+ .expect("/data is a guest path");
904
+ let table = table_of(vec![mount], None).unwrap_or_else(|_| panic!("the table must make"));
905
+ assert_eq!(
906
+ (
907
+ errno(set_times(&table, 3, Fstflags::MTIM)),
908
+ errno(set_times(&table, 3, Fstflags::empty())),
909
+ errno(set_times(&table, 3, Fstflags::MTIM | Fstflags::MTIM_NOW)),
910
+ ),
911
+ (Some(Errno::Rofs), None, Some(Errno::Inval)),
912
+ "fd_filestat_set_times beneath a read-only Mount through preview1 must answer EROFS, \
913
+ a call asking no time must succeed, and one asking a time and now EINVAL"
914
+ );
915
+ }
916
+
917
+ // @behavior RT-185
918
+ #[test]
919
+ fn a_path_call_starting_from_a_file_is_not_a_directory() {
920
+ let mut table = standard_streams();
921
+ let fd = table.push(open_file()).expect("a descriptor to start from");
922
+ let mut bytes = *b"a";
923
+ let mut memory = wiggle::GuestMemory::Unshared(&mut bytes);
924
+ let mem = GuestMemory::new(&mut memory, 1);
925
+ assert_eq!(
926
+ errno(create_directory(&table, &mem, fd, GuestPtr::new((0, 1)))),
927
+ Some(Errno::Notdir),
928
+ "path_create_directory starting from a file through preview1 must answer ENOTDIR, as \
929
+ Linux refuses a walk starting there"
930
+ );
931
+ }
932
+
933
+ // Linux reads the flags and the path before it looks at where a walk starts,
934
+ // so a Driver checking the start first would hide each of these.
935
+ // @behavior RT-225 RT-230 RT-231 RT-232
936
+ #[test]
937
+ fn a_call_no_walk_can_take_answers_before_where_it_starts() {
938
+ let mut table = standard_streams();
939
+ let fd = table.push(open_file()).expect("a descriptor to start from");
940
+ let mut bytes = vec![b'a'; PATH_MAX];
941
+ let mut memory = wiggle::GuestMemory::Unshared(&mut bytes);
942
+ let mem = GuestMemory::new(&mut memory, 2 * PATH_MAX);
943
+ let call = PathOpen {
944
+ dirflags: types::Lookupflags::empty(),
945
+ oflags: types::Oflags::CREAT | types::Oflags::DIRECTORY,
946
+ rights_base: Rights::FD_READ,
947
+ fdflags: Fdflags::empty(),
948
+ };
949
+ let path = |len| GuestPtr::new((0, len));
950
+ assert_eq!(
951
+ (
952
+ errno(path_open(&mut table, &mem, fd, path(1), &call)),
953
+ errno(create_directory(&table, &mem, fd, path(0))),
954
+ errno(create_directory(&table, &mem, fd, path(PATH_MAX as u32))),
955
+ errno(utimensat(0, 0, Fstflags::empty(), |times| path_set_times(
956
+ &table,
957
+ &mem,
958
+ fd,
959
+ types::Lookupflags::empty(),
960
+ path(1),
961
+ times
962
+ ))),
963
+ ),
964
+ (
965
+ Some(Errno::Inval),
966
+ Some(Errno::Noent),
967
+ Some(Errno::Nametoolong),
968
+ None
969
+ ),
970
+ "path_open with O_CREAT and O_DIRECTORY, and path_create_directory of an empty and of \
971
+ an overlong path, starting from a file through preview1 must answer EINVAL, ENOENT, \
972
+ and ENAMETOOLONG, and path_filestat_set_times setting no time must succeed"
973
+ );
974
+ }
975
+
976
+ // Linux reads the flags and the name before `path_init` takes the
977
+ // descriptor, so a number naming nothing answers only once both are fine.
978
+ // @behavior RT-257 RT-258
979
+ #[test]
980
+ fn a_call_no_walk_can_take_answers_before_the_descriptor_it_names() {
981
+ let mut table = standard_streams();
982
+ let mut bytes = vec![b'a'; PATH_MAX];
983
+ let mut memory = wiggle::GuestMemory::Unshared(&mut bytes);
984
+ let mem = GuestMemory::new(&mut memory, 2 * PATH_MAX);
985
+ let call = |oflags| PathOpen {
986
+ dirflags: types::Lookupflags::empty(),
987
+ oflags,
988
+ rights_base: Rights::FD_READ,
989
+ fdflags: Fdflags::empty(),
990
+ };
991
+ let path = |len| GuestPtr::new((0, len));
992
+ let unopened = 99;
993
+ let create_dir = call(types::Oflags::CREAT | types::Oflags::DIRECTORY);
994
+ assert_eq!(
995
+ (
996
+ errno(path_open(&mut table, &mem, unopened, path(1), &create_dir)),
997
+ errno(create_directory(&table, &mem, unopened, path(0))),
998
+ errno(create_directory(
999
+ &table,
1000
+ &mem,
1001
+ unopened,
1002
+ path(PATH_MAX as u32)
1003
+ )),
1004
+ errno(create_directory(&table, &mem, unopened, path(1))),
1005
+ ),
1006
+ (
1007
+ Some(Errno::Inval),
1008
+ Some(Errno::Noent),
1009
+ Some(Errno::Nametoolong),
1010
+ Some(Errno::Badf)
1011
+ ),
1012
+ "path_open with O_CREAT and O_DIRECTORY, and path_create_directory of an empty and of \
1013
+ an overlong path, from a descriptor number naming nothing through preview1 must \
1014
+ answer EINVAL, ENOENT, and ENAMETOOLONG, and a path a walk can take EBADF"
1015
+ );
1016
+ }
1017
+
1018
+ // Standard output is a pipe's end, so a walk from it answers ENOTDIR; the
1019
+ // second path is wrong too, so only the order picks which answers.
1020
+ // @behavior RT-286
649
1021
  #[test]
650
- fn renumbering_onto_an_open_file_closes_it_first() {
651
- let mut table = Descriptors::new(
652
- MemoryInputPipe::new(Vec::new()),
653
- MemoryOutputPipe::new(1),
654
- MemoryOutputPipe::new(1),
655
- Vec::new(),
656
- None,
1022
+ fn a_call_naming_two_paths_answers_for_its_first_before_its_second() {
1023
+ let table = standard_streams();
1024
+ let mut bytes = vec![b'a'; PATH_MAX];
1025
+ let mut memory = wiggle::GuestMemory::Unshared(&mut bytes);
1026
+ let mem = GuestMemory::new(&mut memory, 2 * PATH_MAX);
1027
+ let path = |len| GuestPtr::new((0, len));
1028
+ let (stdout, unopened) = (1, 99);
1029
+ assert_eq!(
1030
+ (
1031
+ errno(rename(
1032
+ &table,
1033
+ &mem,
1034
+ (stdout, path(1)),
1035
+ (stdout, path(PATH_MAX as u32))
1036
+ )),
1037
+ errno(rename(&table, &mem, (stdout, path(1)), (unopened, path(1)))),
1038
+ ),
1039
+ (Some(Errno::Notdir), Some(Errno::Notdir)),
1040
+ "path_rename from standard output to a path past PATH_MAX, or to a descriptor \
1041
+ number naming nothing, through preview1 must answer ENOTDIR for the first path"
657
1042
  );
1043
+ }
1044
+
1045
+ // @behavior RT-287
1046
+ #[test]
1047
+ fn an_absolute_path_answers_before_the_descriptor_it_names() {
1048
+ let table = standard_streams();
1049
+ let mut bytes = b"/a".to_vec();
1050
+ let mut memory = wiggle::GuestMemory::Unshared(&mut bytes);
1051
+ let mem = GuestMemory::new(&mut memory, 16);
1052
+ assert_eq!(
1053
+ errno(create_directory(&table, &mem, 99, GuestPtr::new((0, 2)))),
1054
+ Some(Errno::Notcapable),
1055
+ "path_create_directory of an absolute path from a descriptor number naming nothing \
1056
+ through preview1 must answer ENOTCAPABLE, as path_init looks at no descriptor"
1057
+ );
1058
+ }
1059
+
1060
+ // @behavior RT-277
1061
+ #[test]
1062
+ fn a_readlink_into_no_room_is_invalid() {
1063
+ let table = standard_streams();
1064
+ let mut bytes = vec![0u8; 8];
1065
+ let mut memory = wiggle::GuestMemory::Unshared(&mut bytes);
1066
+ let mut mem = GuestMemory::new(&mut memory, 16);
1067
+ assert_eq!(
1068
+ errno(readlink(
1069
+ &table,
1070
+ &mut mem,
1071
+ 99,
1072
+ GuestPtr::new((0, 0)),
1073
+ GuestPtr::new(0),
1074
+ 0
1075
+ )),
1076
+ Some(Errno::Inval),
1077
+ "path_readlink into a buffer of no bytes, of an empty path from a descriptor number \
1078
+ naming nothing, through preview1 must answer EINVAL, as do_readlinkat refuses the \
1079
+ size first"
1080
+ );
1081
+ }
1082
+
1083
+ // Linux's `do_readlinkat` takes its name with `LOOKUP_EMPTY`, so an empty
1084
+ // path names the descriptor itself: one naming nothing answers as
1085
+ // `path_init` does, and a file, which is no link, as not found. The file
1086
+ // also shows the start need not be a directory.
1087
+ // @behavior RT-289
1088
+ #[test]
1089
+ fn a_readlink_of_an_empty_path_answers_for_its_descriptor() {
1090
+ let mut table = standard_streams();
1091
+ let fd = table.push(open_file()).expect("a descriptor to start from");
1092
+ let mut bytes = vec![0u8; 8];
1093
+ let mut memory = wiggle::GuestMemory::Unshared(&mut bytes);
1094
+ let mut mem = GuestMemory::new(&mut memory, 16);
1095
+ let mut empty_from = |start| {
1096
+ errno(readlink(
1097
+ &table,
1098
+ &mut mem,
1099
+ start,
1100
+ GuestPtr::new((0, 0)),
1101
+ GuestPtr::new(0),
1102
+ 8,
1103
+ ))
1104
+ };
1105
+ assert_eq!(
1106
+ (empty_from(99), empty_from(fd)),
1107
+ (Some(Errno::Badf), Some(Errno::Noent)),
1108
+ "path_readlink of an empty path through preview1 must answer EBADF from a descriptor \
1109
+ number naming nothing and ENOENT from a file, as do_readlinkat reads the descriptor \
1110
+ itself"
1111
+ );
1112
+ }
1113
+
1114
+ // Linux's `utimensat` answers a call asking no time before it takes the
1115
+ // name or the descriptor, so a number naming nothing never shows.
1116
+ // @behavior RT-271
1117
+ #[test]
1118
+ fn a_call_asking_no_time_succeeds_whatever_descriptor_it_names() {
1119
+ let table = standard_streams();
1120
+ let mut bytes = *b"a";
1121
+ let mut memory = wiggle::GuestMemory::Unshared(&mut bytes);
1122
+ let mem = GuestMemory::new(&mut memory, 1);
1123
+ let unopened = 99;
1124
+ assert_eq!(
1125
+ (
1126
+ errno(set_times(&table, unopened, Fstflags::empty())),
1127
+ errno(utimensat(0, 0, Fstflags::empty(), |times| path_set_times(
1128
+ &table,
1129
+ &mem,
1130
+ unopened,
1131
+ types::Lookupflags::empty(),
1132
+ GuestPtr::new((0, 1)),
1133
+ times
1134
+ ))),
1135
+ ),
1136
+ (None, None),
1137
+ "fd_filestat_set_times and path_filestat_set_times asking no time through preview1 \
1138
+ must succeed from a descriptor number naming nothing"
1139
+ );
1140
+ }
1141
+
1142
+ // Linux's `do_sys_openat2` reads the flags before `FD_ADD` reserves a
1143
+ // descriptor, and the name before the walk looks at where it starts.
1144
+ // @behavior RT-259
1145
+ #[test]
1146
+ fn a_full_table_answers_after_the_flags_and_before_the_start() {
1147
+ let mut table = table_of(Vec::new(), Some(3)).expect("a table holding no Mount must make");
1148
+ let mut bytes = *b"a";
1149
+ let mut memory = wiggle::GuestMemory::Unshared(&mut bytes);
1150
+ let mem = GuestMemory::new(&mut memory, 1);
1151
+ let call = |oflags| PathOpen {
1152
+ dirflags: types::Lookupflags::empty(),
1153
+ oflags,
1154
+ rights_base: Rights::FD_READ,
1155
+ fdflags: Fdflags::empty(),
1156
+ };
1157
+ let unopened = 99;
1158
+ let path = GuestPtr::new((0, 1));
1159
+ assert_eq!(
1160
+ (
1161
+ errno(path_open(
1162
+ &mut table,
1163
+ &mem,
1164
+ unopened,
1165
+ path,
1166
+ &call(types::Oflags::CREAT | types::Oflags::DIRECTORY)
1167
+ )),
1168
+ errno(path_open(
1169
+ &mut table,
1170
+ &mem,
1171
+ unopened,
1172
+ path,
1173
+ &call(types::Oflags::empty())
1174
+ )),
1175
+ ),
1176
+ (Some(Errno::Inval), Some(Errno::Mfile)),
1177
+ "path_open with no descriptor left through preview1 must answer EINVAL for O_CREAT \
1178
+ with O_DIRECTORY, and EMFILE from a descriptor number naming nothing"
1179
+ );
1180
+ }
1181
+
1182
+ /// A directory listing `ok` and then `bad`, whatever `bad` is.
1183
+ struct ListsNames(&'static str);
1184
+
1185
+ impl kobako_runtime::filesystem::FileSystemType for ListsNames {
1186
+ fn fill_super(self: Arc<Self>, sb: &mut Filesystem) -> Result<(), FilesystemError> {
1187
+ sb.d_make_root(kobako_runtime::vfs::Inode::new(
1188
+ &kobako_runtime::Kstat::new(kobako_runtime::FileType::Dir, 0),
1189
+ self,
1190
+ ));
1191
+ Ok(())
1192
+ }
1193
+ }
1194
+
1195
+ impl InodeOperations for ListsNames {
1196
+ fn getattr(&self, _dentry: &Dentry) -> Result<Kstat, FilesystemError> {
1197
+ Ok(Kstat::new(FileType::Dir, 0))
1198
+ }
1199
+
1200
+ fn open(
1201
+ &self,
1202
+ _dentry: &Dentry,
1203
+ _options: &OpenHow,
1204
+ ) -> Result<Box<dyn FileOperations>, FilesystemError> {
1205
+ Ok(Box::new(Self(self.0)))
1206
+ }
1207
+ }
1208
+
1209
+ impl FileOperations for ListsNames {
1210
+ fn iterate_shared(
1211
+ &mut self,
1212
+ dentry: &Dentry,
1213
+ ctx: &mut vfs::DirContext<'_>,
1214
+ ) -> Result<(), FilesystemError> {
1215
+ let entries = vec![
1216
+ vfs::DirEntry::new("ok", FileType::RegFile),
1217
+ vfs::DirEntry::new(self.0, FileType::RegFile),
1218
+ ];
1219
+ kobako_runtime::filesystem::Rdir::default().iterate(dentry, ctx, || Ok(entries))
1220
+ }
1221
+ }
1222
+
1223
+ /// Each whole `dirent` in `bytes`, as its name and its `d_next`.
1224
+ fn dirents(bytes: &[u8]) -> Vec<(String, u64)> {
1225
+ let header = types::Dirent::guest_size() as usize;
1226
+ let mut seen = Vec::new();
1227
+ let mut at = 0;
1228
+ while at + header <= bytes.len() {
1229
+ let field = |from: usize, len: usize| &bytes[at + from..at + from + len];
1230
+ let d_next = u64::from_le_bytes(field(0, 8).try_into().expect("eight bytes"));
1231
+ let namlen = u32::from_le_bytes(field(16, 4).try_into().expect("four bytes")) as usize;
1232
+ if at + header + namlen > bytes.len() {
1233
+ break;
1234
+ }
1235
+ let name = String::from_utf8_lossy(field(header, namlen)).into_owned();
1236
+ seen.push((name, d_next));
1237
+ at += header + namlen;
1238
+ }
1239
+ seen
1240
+ }
1241
+
1242
+ // The first buffer holds `.` and `..` exactly, so `..` learns where the
1243
+ // listing goes on only from the entry refused after it, as `filldir64`
1244
+ // writes `prev->d_off`, and the last entry of the second from where the
1245
+ // listing stopped, as `getdents64` sets it from `f_pos`.
1246
+ // @behavior RT-182
1247
+ #[test]
1248
+ fn each_listed_entry_names_where_the_listing_goes_on() {
1249
+ let mount = Mount::new(
1250
+ "/data",
1251
+ Filesystem::new(Arc::new(ListsNames("bb"))).expect("the instance must stand"),
1252
+ Access::Read,
1253
+ )
1254
+ .expect("/data is a guest path");
1255
+ let mut table =
1256
+ table_of(vec![mount], None).unwrap_or_else(|_| panic!("the table must make"));
1257
+ let header = types::Dirent::guest_size();
1258
+ let dots = 2 * header + 3;
1259
+ let mut bytes = vec![0u8; 256];
1260
+ let used = {
1261
+ let mut memory = wiggle::GuestMemory::Unshared(&mut bytes);
1262
+ let mut mem = GuestMemory::new(&mut memory, 256);
1263
+ let first = readdir(&mut table, &mut mem, 3, GuestPtr::new(0), dots, 0);
1264
+ let rest = readdir(&mut table, &mut mem, 3, GuestPtr::new(128), 128, 2);
1265
+ (first.ok(), rest.ok())
1266
+ };
1267
+ let entry = |name: &str, d_next| (name.to_string(), d_next);
1268
+ assert_eq!(
1269
+ (
1270
+ used,
1271
+ dirents(&bytes[..dots as usize]),
1272
+ dirents(&bytes[128..128 + 2 * (header as usize + 2)])
1273
+ ),
1274
+ (
1275
+ (Some(dots), Some(2 * (header + 2))),
1276
+ vec![entry(".", 1), entry("..", 2)],
1277
+ vec![entry("ok", 3), entry("bb", 4)],
1278
+ ),
1279
+ "fd_readdir through preview1 must give each whole entry the cookie a listing goes on \
1280
+ from past it, the one filling the buffer included, and the last the position the \
1281
+ listing stopped at"
1282
+ );
1283
+ }
1284
+
1285
+ // A built-in Filesystem cannot hold such a name, so only one that hands it
1286
+ // over shows the listing refuses it; Linux's `getdents64` answers what came
1287
+ // before it and leaves the name to the next call, whose cookie stands on it.
1288
+ // @behavior RT-212 RT-269
1289
+ #[test]
1290
+ fn a_listing_stops_at_a_name_no_directory_can_hold() {
1291
+ let listed = |bad: &'static str| {
1292
+ let mount = Mount::new(
1293
+ "/data",
1294
+ Filesystem::new(Arc::new(ListsNames(bad))).expect("the instance must stand"),
1295
+ Access::Read,
1296
+ )
1297
+ .expect("/data is a guest path");
1298
+ let mut table =
1299
+ table_of(vec![mount], None).unwrap_or_else(|_| panic!("the table must make"));
1300
+ let mut bytes = vec![0u8; 256];
1301
+ let (first, next) = {
1302
+ let mut memory = wiggle::GuestMemory::Unshared(&mut bytes);
1303
+ let mut mem = GuestMemory::new(&mut memory, 256);
1304
+ let buf = GuestPtr::new(0);
1305
+ let first = readdir(&mut table, &mut mem, 3, buf, 256, 0).ok();
1306
+ (first, errno(readdir(&mut table, &mut mem, 3, buf, 256, 3)))
1307
+ };
1308
+ let handed_over = bytes.windows(bad.len().max(1)).any(|w| w == bad.as_bytes());
1309
+ (
1310
+ first.is_some_and(|used| used > 0),
1311
+ next,
1312
+ handed_over && !bad.is_empty(),
1313
+ )
1314
+ };
1315
+ assert_eq!(
1316
+ (listed("../escape"), listed("")),
1317
+ ((true, Some(Errno::Io), false), (true, Some(Errno::Io), false)),
1318
+ "fd_readdir through preview1 of a Filesystem listing `ok` and then a name with a `/` or \
1319
+ an empty one must hand over what came before it, and answer EIO from where it stands, \
1320
+ handing over no such name"
1321
+ );
1322
+ }
1323
+
1324
+ /// A directory that would make any link it is asked to.
1325
+ struct MakesLinks;
1326
+
1327
+ impl kobako_runtime::filesystem::FileSystemType for MakesLinks {
1328
+ fn fill_super(self: Arc<Self>, sb: &mut Filesystem) -> Result<(), FilesystemError> {
1329
+ sb.d_make_root(kobako_runtime::vfs::Inode::new(
1330
+ &kobako_runtime::Kstat::new(kobako_runtime::FileType::Dir, 0),
1331
+ self,
1332
+ ));
1333
+ Ok(())
1334
+ }
1335
+ }
1336
+
1337
+ impl InodeOperations for MakesLinks {
1338
+ fn getattr(&self, _dentry: &Dentry) -> Result<Kstat, FilesystemError> {
1339
+ Ok(Kstat::new(FileType::Dir, 0))
1340
+ }
1341
+
1342
+ fn lookup(&self, _dentry: &Dentry) -> Result<Lookup, FilesystemError> {
1343
+ Ok(Lookup::Absent)
1344
+ }
1345
+
1346
+ fn symlink(&self, _dentry: &Dentry, _target: &str) -> Result<(), FilesystemError> {
1347
+ Ok(())
1348
+ }
1349
+ }
1350
+
1351
+ // No built-in Filesystem makes a link, so only one that would shows the
1352
+ // call stopped here.
1353
+ // @behavior RT-202
1354
+ #[test]
1355
+ fn a_link_to_an_absolute_path_is_not_made() {
1356
+ let mount = Mount::new(
1357
+ "/data",
1358
+ Filesystem::new(Arc::new(MakesLinks)).expect("the instance must stand"),
1359
+ Access::ReadWrite,
1360
+ )
1361
+ .expect("/data is a guest path");
1362
+ let table = table_of(vec![mount], None).unwrap_or_else(|_| panic!("the table must make"));
1363
+ let mut bytes = *b"/trelative";
1364
+ let mut memory = wiggle::GuestMemory::Unshared(&mut bytes);
1365
+ let mem = GuestMemory::new(&mut memory, 64);
1366
+ let (absolute, relative, name) = (
1367
+ GuestPtr::new((0, 2)),
1368
+ GuestPtr::new((1, 1)),
1369
+ GuestPtr::new((2, 8)),
1370
+ );
1371
+ assert_eq!(
1372
+ (
1373
+ errno(symlink(&table, &mem, absolute, 3, name)),
1374
+ errno(symlink(&table, &mem, relative, 3, name)),
1375
+ ),
1376
+ (Some(Errno::Perm), None),
1377
+ "path_symlink through preview1 to an absolute target must answer EPERM, as \
1378
+ wasi-testsuite requires, and to a relative one must reach the Filesystem"
1379
+ );
1380
+ }
1381
+
1382
+ // The replaced file refuses its close, so only a renumber that moves the
1383
+ // source before closing, and ignores that answer, succeeds.
1384
+ // @behavior RT-114
1385
+ #[test]
1386
+ fn renumbering_onto_an_open_file_replaces_it_whatever_its_close_answers() {
1387
+ let mut table = standard_streams();
658
1388
  let from = table.push(open_file()).expect("a descriptor to move");
659
1389
  let to = table.push(open_file()).expect("a descriptor to move onto");
660
- assert!(
661
- matches!(
662
- renumber(&mut table, from, to),
663
- Err(Failure::Errno(Errno::Acces))
1390
+ assert_eq!(
1391
+ (
1392
+ errno(renumber(&mut table, from, to)),
1393
+ table.get(from).is_none(),
1394
+ table.get(to).is_some()
664
1395
  ),
665
- "fd_renumber onto a file whose Filesystem refuses the close must answer that refusal"
1396
+ (None, true, true),
1397
+ "fd_renumber through preview1 onto a file whose Filesystem refuses the close must \
1398
+ succeed, the source now standing at the target and its own number free, as Linux's \
1399
+ do_dup2 ignores filp_close of the file it replaced"
666
1400
  );
667
- assert!(
668
- table.get(to).is_none() && table.get(from).is_some(),
669
- "a refused close through fd_renumber must leave the target closed and the source in place"
1401
+ }
1402
+
1403
+ // @behavior RT-309
1404
+ #[test]
1405
+ fn renumbering_onto_a_number_not_open_answers_ebadf_and_keeps_the_source() {
1406
+ let mut table = standard_streams();
1407
+ let from = table.push(open_file()).expect("a descriptor to move");
1408
+ let to = from + 1;
1409
+ assert_eq!(
1410
+ (
1411
+ errno(renumber(&mut table, from, to)),
1412
+ table.get(from).is_some(),
1413
+ table.get(to).is_none()
1414
+ ),
1415
+ (Some(Errno::Badf), true, true),
1416
+ "fd_renumber through preview1 onto a number nothing stands at must answer EBADF and \
1417
+ leave the source at its own number, as wasmtime-wasi replaces only an open one"
670
1418
  );
671
1419
  }
672
1420
  }