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,421 +1,302 @@
1
- //! The 10 `path_*` calls. Each resolves a path beneath a directory
2
- //! descriptor, asks its Mount what the guest may do there, and asks the
3
- //! Filesystem. Links and timestamps are no Filesystem's, so those calls
4
- //! are unsupported.
1
+ //! The 10 `path_*` calls. Each hands the call to the runtime's VFS, which
2
+ //! resolves the path, asks the Mount, and asks the Filesystem. What a Linux
3
+ //! syscall entry checks before the VFS stays here, such as the order of an
4
+ //! open's errors.
5
5
 
6
- use std::sync::Arc;
6
+ use kobako_runtime::filesystem::{FilesystemError, Iattr};
7
+ use kobako_runtime::vfs::{self, OpenHow, Start};
8
+ use wiggle::GuestPtr;
7
9
 
8
- use kobako_runtime::filesystem::{
9
- Access, FileType, Filesystem, FilesystemError, OpenOptions, Rename,
10
- };
11
- use kobako_runtime::mount::Mount;
12
-
13
- use super::args::{
14
- self, FDFLAGS_APPEND, FDFLAGS_NONBLOCK, FDFLAGS_SYNCHRONIZED, RIGHTS_FD_READ, RIGHTS_FD_WRITE,
15
- };
16
- use super::beneath::{resolve, ResolvedPath};
17
10
  use super::errno::{Answer, Errno};
18
- use super::fd::write_metadata;
11
+ use super::fd::to_filestat;
19
12
  use super::memory::GuestMemory;
20
- use crate::descriptors::{Descriptor, Descriptors, Identity, OpenDirectory, OpenFile};
21
-
22
- const LOOKUPFLAGS_SYMLINK_FOLLOW: u32 = 1 << 0;
23
- const OFLAGS_CREAT: u32 = 1 << 0;
24
- const OFLAGS_DIRECTORY: u32 = 1 << 1;
25
- const OFLAGS_EXCL: u32 = 1 << 2;
26
- const OFLAGS_TRUNC: u32 = 1 << 3;
13
+ use super::types;
14
+ use crate::descriptors::{Descriptor, Descriptors};
27
15
 
28
- /// The flags `path_open` takes besides the path itself.
29
- pub(crate) struct OpenFlags {
30
- pub(crate) dirflags: u32,
31
- pub(crate) oflags: u32,
32
- pub(crate) rights_base: u64,
33
- pub(crate) rights_inheriting: u64,
34
- pub(crate) fdflags: u32,
16
+ /// The arguments `path_open` takes besides where it starts and the path, as
17
+ /// Linux's `openat2` takes a `struct open_how`; they become the `OpenHow` the
18
+ /// VFS opens with.
19
+ pub(crate) struct PathOpen {
20
+ pub(crate) dirflags: types::Lookupflags,
21
+ pub(crate) oflags: types::Oflags,
22
+ pub(crate) rights_base: types::Rights,
23
+ pub(crate) fdflags: types::Fdflags,
35
24
  }
36
25
 
37
- /// The directory `fd` stands for; any other descriptor is `BADF`.
38
- fn directory(table: &Descriptors, fd: u32) -> Answer<&OpenDirectory> {
39
- match table.get(fd).ok_or(Errno::Badf)? {
40
- Descriptor::Directory(dir) => Ok(dir),
41
- Descriptor::Stdin(_) | Descriptor::Channel(_) | Descriptor::File(_) => {
42
- Err(Errno::Badf.into())
43
- }
44
- }
45
- }
26
+ /// How many times a call is made before a guest is answered EAGAIN, as cap-std
27
+ /// makes `openat2` four times: any move in the process answers a `..` walking
28
+ /// past it so, and nothing bounds how often one lands.
29
+ const MAX_ATTEMPTS: usize = 4;
46
30
 
47
- /// Whether the guest may reach `path` with `access`, as its Mount
48
- /// decides. A Mount that follows no link refuses one first.
49
- fn judge(mount: &Mount, path: &str, access: Access, follows_last: bool) -> Answer {
50
- if !mount.follows_links() {
51
- refuse_links(mount, path, follows_last)?;
31
+ /// What `call` answers once no move landed during its walk, made again at most
32
+ /// `MAX_ATTEMPTS` times in all, as cap-std retries `openat2` on EAGAIN.
33
+ pub(super) fn retry_eagain<T>(
34
+ mut call: impl FnMut() -> Result<T, FilesystemError>,
35
+ ) -> Result<T, FilesystemError> {
36
+ let mut answer = call();
37
+ for _ in 1..MAX_ATTEMPTS {
38
+ if !matches!(answer, Err(FilesystemError::WouldBlock)) {
39
+ break;
40
+ }
41
+ answer = call();
52
42
  }
53
- Ok(mount.check(path, access)?)
43
+ answer
54
44
  }
55
45
 
56
- /// Refuse a link standing on the way to `path`, and at its end when the
57
- /// call would follow it, before the Mount judges a path the link would
58
- /// lead elsewhere.
59
- fn refuse_links(mount: &Mount, path: &str, follows_last: bool) -> Answer {
60
- let ends = path.match_indices('/').map(|(end, _)| end);
61
- for end in ends.chain(follows_last.then_some(path.len())) {
62
- match mount.filesystem().symlink_metadata(&path[..end]) {
63
- Ok(metadata) if metadata.file_type == FileType::Symlink => {
64
- return Err(Errno::Loop.into())
65
- }
66
- Ok(_) => {}
67
- // What is missing holds no link; the call answers for it.
68
- Err(FilesystemError::NotFound | FilesystemError::NotADirectory) => break,
69
- Err(err) => return Err(err.into()),
46
+ /// Where `fd` stands, for a path to be walked from, once the guard answered for a
47
+ /// directory there, found when the VFS has taken the name, as Linux's
48
+ /// `path_init` takes the descriptor. What is no directory the walk itself
49
+ /// refuses, and a move the guard meets is one a walk meets, so `retry_eagain`
50
+ /// makes the call again.
51
+ fn start_of(table: &Descriptors, fd: u32) -> impl vfs::Start + '_ {
52
+ move || {
53
+ let file = &table.get(fd).ok_or(FilesystemError::BadDescriptor)?.file;
54
+ if file.is_directory() {
55
+ table.walk_guard().check(file.path())?;
70
56
  }
57
+ Ok(file.path().clone())
71
58
  }
72
- Ok(())
73
59
  }
74
60
 
75
- /// A path beneath `fd`, relative to its mount's root, for a call acting
76
- /// on what stands there rather than where a link leads: refused when it
77
- /// ends in `/` and names a file or a link.
78
- fn beneath<'a>(
79
- table: &'a Descriptors,
80
- mem: &GuestMemory,
81
- fd: u32,
82
- path: (u32, u32),
83
- ) -> Answer<(&'a OpenDirectory, String)> {
84
- let dir = directory(table, fd)?;
85
- let path = resolve(dir, &mem.path(path.0, path.1)?)?.check_directory(
86
- dir.mount.filesystem().as_ref(),
87
- <dyn Filesystem>::symlink_metadata,
88
- )?;
89
- Ok((dir, path))
90
- }
91
-
92
- /// A trailing `/` asks for the directory being made, so it is no
93
- /// refusal here, as `mkdir(2)` takes it.
94
61
  pub(crate) fn create_directory(
95
62
  table: &Descriptors,
96
- mem: &GuestMemory,
63
+ mem: &GuestMemory<'_, '_>,
97
64
  fd: u32,
98
- path: (u32, u32),
65
+ path: GuestPtr<str>,
99
66
  ) -> Answer {
100
- let dir = directory(table, fd)?;
101
- let path = resolve(dir, &mem.path(path.0, path.1)?)?.path;
102
- judge(&dir.mount, &path, Access::ReadWrite, false)?;
103
- Ok(dir.mount.filesystem().create_dir(&path)?)
67
+ let path = mem.path(path)?;
68
+ Ok(retry_eagain(|| {
69
+ vfs::filename_mkdirat(table.cred(), &start_of(table, fd), &path)
70
+ })?)
104
71
  }
105
72
 
106
73
  pub(crate) fn filestat_get(
107
74
  table: &Descriptors,
108
- mem: &mut GuestMemory,
75
+ mem: &GuestMemory<'_, '_>,
109
76
  fd: u32,
110
- flags: u32,
111
- path: (u32, u32),
112
- out: u32,
113
- ) -> Answer {
114
- args::flags(flags, args::LOOKUPFLAGS)?;
115
- let dir = directory(table, fd)?;
116
- let resolved = resolve(dir, &mem.path(path.0, path.1)?)?;
117
- // A trailing `/` resolves a link standing there, as Linux's `lstat` does.
118
- let follow = flags & LOOKUPFLAGS_SYMLINK_FOLLOW != 0 || resolved.names_directory;
119
- let resolved = if follow {
120
- resolved.follow_last(dir, false)?
121
- } else {
122
- resolved
123
- };
124
- let filesystem = dir.mount.filesystem();
125
- let path = resolved.check_directory(filesystem.as_ref(), <dyn Filesystem>::metadata)?;
126
- let metadata = if follow {
127
- filesystem.metadata(&path)?
128
- } else {
129
- filesystem.symlink_metadata(&path)?
130
- };
131
- write_metadata(mem, out, metadata)
77
+ flags: types::Lookupflags,
78
+ path: GuestPtr<str>,
79
+ ) -> Answer<types::Filestat> {
80
+ let path = mem.path(path)?;
81
+ let follow = flags.contains(types::Lookupflags::SYMLINK_FOLLOW);
82
+ let stat =
83
+ retry_eagain(|| vfs::vfs_fstatat(table.cred(), &start_of(table, fd), &path, follow))?;
84
+ Ok(to_filestat(table, stat))
132
85
  }
133
86
 
134
- /// A Filesystem keeps no timestamps of the guest's choosing.
135
87
  pub(crate) fn filestat_set_times(
136
88
  table: &Descriptors,
137
- mem: &GuestMemory,
89
+ mem: &GuestMemory<'_, '_>,
138
90
  fd: u32,
139
- flags: u32,
140
- path: (u32, u32),
141
- fst_flags: u32,
91
+ flags: types::Lookupflags,
92
+ path: GuestPtr<str>,
93
+ times: &Iattr,
142
94
  ) -> Answer {
143
- args::flags(flags, args::LOOKUPFLAGS)?;
144
- args::fstflags(fst_flags)?;
145
- beneath(table, mem, fd, path)?;
146
- Err(Errno::Notsup.into())
95
+ let path = mem.path(path)?;
96
+ let follow = flags.contains(types::Lookupflags::SYMLINK_FOLLOW);
97
+ Ok(retry_eagain(|| {
98
+ vfs::do_utimes_path(table.cred(), &start_of(table, fd), &path, follow, times)
99
+ })?)
147
100
  }
148
101
 
149
- /// A Filesystem holds no links, and answering as if one were made would
150
- /// leave the guest believing it was.
151
102
  pub(crate) fn link(
152
103
  table: &Descriptors,
153
- mem: &GuestMemory,
154
- old_fd: u32,
155
- old_flags: u32,
156
- old_path: (u32, u32),
157
- new_fd: u32,
158
- new_path: (u32, u32),
104
+ mem: &GuestMemory<'_, '_>,
105
+ old: (u32, GuestPtr<str>),
106
+ new: (u32, GuestPtr<str>),
107
+ flags: types::Lookupflags,
159
108
  ) -> Answer {
160
- args::flags(old_flags, args::LOOKUPFLAGS)?;
161
- directory(table, old_fd)?;
162
- directory(table, new_fd)?;
163
- mem.path(old_path.0, old_path.1)?;
164
- mem.path(new_path.0, new_path.1)?;
165
- Err(Errno::Notsup.into())
109
+ let (old_path, new_path) = (mem.path(old.1)?, mem.path(new.1)?);
110
+ let follow = flags.contains(types::Lookupflags::SYMLINK_FOLLOW);
111
+ Ok(retry_eagain(|| {
112
+ let (from, to) = (start_of(table, old.0), start_of(table, new.0));
113
+ vfs::filename_linkat(table.cred(), &from, &old_path, &to, &new_path, follow)
114
+ })?)
166
115
  }
167
116
 
168
117
  pub(crate) fn open(
169
118
  table: &mut Descriptors,
170
- mem: &mut GuestMemory,
119
+ mem: &GuestMemory<'_, '_>,
171
120
  fd: u32,
172
- path: (u32, u32),
173
- flags: &OpenFlags,
174
- out: u32,
175
- ) -> Answer {
176
- args::flags(flags.dirflags, args::LOOKUPFLAGS)?;
177
- args::flags(flags.oflags, args::OFLAGS)?;
178
- args::flags(flags.rights_base, args::RIGHTS)?;
179
- args::flags(flags.rights_inheriting, args::RIGHTS)?;
180
- args::flags(flags.fdflags, args::FDFLAGS)?;
181
- let path = mem.path(path.0, path.1)?;
182
- let dir = match table.get(fd).ok_or(Errno::Badf)? {
183
- Descriptor::Directory(dir) => dir,
184
- Descriptor::File(_) => return Err(Errno::Notdir.into()),
185
- Descriptor::Stdin(_) | Descriptor::Channel(_) => return Err(Errno::Badf.into()),
186
- };
187
- let oflag = |bit| flags.oflags & bit != 0;
188
- // A Filesystem makes no write durable on its own schedule, and every
189
- // call waits for its answer, so an open asking otherwise is
190
- // unsupported rather than quietly granted.
191
- if flags.fdflags & (FDFLAGS_SYNCHRONIZED | FDFLAGS_NONBLOCK) != 0 {
192
- return Err(Errno::Notsup.into());
193
- }
194
- if oflag(OFLAGS_DIRECTORY) && oflag(OFLAGS_CREAT | OFLAGS_EXCL | OFLAGS_TRUNC) {
195
- return Err(Errno::Inval.into());
196
- }
197
- let resolved = resolve(dir, &path)?;
198
- // A path ending in `/` asks for a directory, which cannot be created
199
- // as a file.
200
- if path.ends_with('/') && oflag(OFLAGS_CREAT) {
201
- return Err(Errno::Isdir.into());
202
- }
203
- // A trailing `/` resolves a link standing there, as Linux's `open` does.
204
- let follow = flags.dirflags & LOOKUPFLAGS_SYMLINK_FOLLOW != 0 || resolved.names_directory;
205
- let resolved = if follow {
206
- resolved.follow_last(dir, oflag(OFLAGS_CREAT))?
207
- } else {
208
- resolved
209
- };
210
- let path =
211
- resolved.check_directory(dir.mount.filesystem().as_ref(), <dyn Filesystem>::metadata)?;
212
- let mount = Arc::clone(&dir.mount);
213
- if table.is_full() {
214
- return Err(Errno::Mfile.into());
215
- }
216
-
217
- let read = flags.rights_base & RIGHTS_FD_READ != 0;
218
- let write = flags.rights_base & RIGHTS_FD_WRITE != 0;
219
- if !mount.follows_links() {
220
- refuse_links(&mount, &path, follow)?;
221
- }
222
- let looked_up = if follow {
223
- mount.filesystem().metadata(&path)
224
- } else {
225
- mount.filesystem().symlink_metadata(&path)
226
- };
227
- let (existing, identity) = match looked_up {
228
- Ok(metadata) => (Some(metadata.file_type), Identity::of(&metadata)),
229
- Err(FilesystemError::NotFound) => (None, None),
230
- Err(err) => return Err(err.into()),
231
- };
232
- // What stands at the path answers before any access is judged, as
233
- // Linux's `open` does on a read-only mount.
234
- let refusal = match existing {
235
- None if !oflag(OFLAGS_CREAT) => Some(Errno::Noent),
236
- Some(_) if oflag(OFLAGS_CREAT) && oflag(OFLAGS_EXCL) => Some(Errno::Exist),
237
- Some(FileType::Directory) if write || oflag(OFLAGS_TRUNC | OFLAGS_CREAT) => {
238
- Some(Errno::Isdir)
239
- }
240
- Some(FileType::Directory) | None => None,
241
- Some(_) if oflag(OFLAGS_DIRECTORY) => Some(Errno::Notdir),
242
- Some(_) => None,
243
- };
244
- if let Some(errno) = refusal {
245
- return Err(errno.into());
246
- }
247
- // Creating changes the Mount only where nothing stands yet, so the
248
- // Mount judges the open once, knowing what it does.
249
- let changes = write || oflag(OFLAGS_TRUNC) || (oflag(OFLAGS_CREAT) && existing.is_none());
250
- let access = if changes {
251
- Access::ReadWrite
252
- } else {
253
- Access::Read
254
- };
255
- mount.check(&path, access)?;
256
- let descriptor = match existing {
257
- Some(FileType::Directory) => Descriptor::Directory(OpenDirectory {
258
- _hold: mount.filesystem().hold_directory(&path)?,
259
- mount,
260
- path,
261
- identity,
262
- preopen: false,
263
- listing: None,
264
- }),
265
- Some(_) | None => {
266
- let mut options = OpenOptions::default();
267
- options.read = read;
268
- options.write = write;
269
- // A file already there is opened as it is, so a host that opens
270
- // only to create never asks for more than the guest.
271
- options.create = oflag(OFLAGS_CREAT) && existing.is_none();
272
- options.create_new = oflag(OFLAGS_CREAT) && oflag(OFLAGS_EXCL);
273
- options.truncate = oflag(OFLAGS_TRUNC);
274
- options.nofollow = !follow;
275
- Descriptor::File(OpenFile {
276
- file: mount.filesystem().open(&path, &options)?,
277
- position: 0,
278
- append: flags.fdflags & FDFLAGS_APPEND != 0,
279
- readable: read,
280
- writable: write,
281
- mount,
282
- })
283
- }
284
- };
285
- let opened = table.push(descriptor).ok_or(Errno::Mfile)?;
286
- mem.write_u32(out, opened)
121
+ path: GuestPtr<str>,
122
+ call: &PathOpen,
123
+ ) -> Answer<types::Fd> {
124
+ let oflags = call.oflags;
125
+ let mut how = OpenHow::default();
126
+ // wasi-libc asks `fd_read` and `fd_readdir` for `O_RDONLY` and neither for
127
+ // `O_SEARCH`, which glibc spells `O_PATH`; wasi-testsuite lists a
128
+ // directory opened with `fd_readdir` alone, and makes a file asking no
129
+ // right at all, which only an `O_RDONLY` open does.
130
+ how.create = oflags.contains(types::Oflags::CREAT);
131
+ how.exclusive = oflags.contains(types::Oflags::EXCL);
132
+ how.truncate = oflags.contains(types::Oflags::TRUNC);
133
+ how.write = call.rights_base.contains(types::Rights::FD_WRITE);
134
+ how.read = call
135
+ .rights_base
136
+ .intersects(types::Rights::FD_READ | types::Rights::FD_READDIR)
137
+ || (!how.write && (how.create || how.truncate));
138
+ how.directory = oflags.contains(types::Oflags::DIRECTORY);
139
+ how.follow = call.dirflags.contains(types::Lookupflags::SYMLINK_FOLLOW);
140
+ how.append = call.fdflags.contains(types::Fdflags::APPEND);
141
+ how.nonblock = call.fdflags.contains(types::Fdflags::NONBLOCK);
142
+ how.dsync = call.fdflags.contains(types::Fdflags::DSYNC);
143
+ how.sync = call
144
+ .fdflags
145
+ .intersects(types::Fdflags::RSYNC | types::Fdflags::SYNC);
146
+ // Linux's `do_sys_openat2` builds the flags, then reserves a descriptor
147
+ // before `do_file_open` answers for the name, so a full table answers
148
+ // whatever the path names.
149
+ let op = vfs::build_open_flags(&how)?;
150
+ let new = table.lowest_unused().ok_or(Errno::Mfile)?;
151
+ let path = mem.path(path)?;
152
+ let file = retry_eagain(|| vfs::do_file_open(table.cred(), &start_of(table, fd), &path, &op))?;
153
+ table.place(new, Descriptor::for_file(file));
154
+ Ok(new.into())
287
155
  }
288
156
 
289
- /// A Filesystem holds no links to read.
290
157
  pub(crate) fn readlink(
291
158
  table: &Descriptors,
292
- mem: &GuestMemory,
159
+ mem: &mut GuestMemory<'_, '_>,
293
160
  fd: u32,
294
- path: (u32, u32),
295
- ) -> Answer {
296
- beneath(table, mem, fd, path)?;
297
- Err(Errno::Notsup.into())
298
- }
299
-
300
- /// Refuse a guest path ending in `.` or `..` as `refusal`, since it names
301
- /// no entry a call may remove or move. Linux looks up what stands before
302
- /// it first, so a missing or non-directory parent answers as itself.
303
- fn refuse_dot_end(dir: &OpenDirectory, path: &str, refusal: Errno) -> Answer {
304
- let trimmed = path.trim_end_matches('/');
305
- let (parent, last) = trimmed.rsplit_once('/').unwrap_or(("", trimmed));
306
- if !matches!(last, "." | "..") {
307
- return Ok(());
308
- }
309
- if !parent.is_empty() {
310
- let parent = resolve(dir, parent)?.path;
311
- if dir.mount.filesystem().metadata(&parent)?.file_type != FileType::Directory {
312
- return Err(Errno::Notdir.into());
313
- }
161
+ path: GuestPtr<str>,
162
+ buf: GuestPtr<u8>,
163
+ buf_len: types::Size,
164
+ ) -> Answer<types::Size> {
165
+ if buf_len == 0 {
166
+ return Err(Errno::Inval.into());
314
167
  }
315
- Err(refusal.into())
168
+ let path = mem.path(path)?;
169
+ let target = retry_eagain(|| vfs::do_readlinkat(table.cred(), &start_of(table, fd), &path))?;
170
+ write_link(mem, &target, buf, buf_len)
316
171
  }
317
172
 
318
- /// A Mount's root is where the guest stands, not a directory it may
319
- /// remove or move; each call refuses it as Linux refuses its own root.
320
- fn refuse_root(path: &str, refusal: Errno) -> Answer {
321
- if path.is_empty() {
322
- return Err(refusal.into());
323
- }
324
- Ok(())
173
+ /// As much of where a link points as the buffer holds, as `readlink` cuts it
174
+ /// off there without saying so.
175
+ fn write_link(
176
+ mem: &mut GuestMemory<'_, '_>,
177
+ target: &str,
178
+ buf: GuestPtr<u8>,
179
+ buf_len: types::Size,
180
+ ) -> Answer<types::Size> {
181
+ let take = target.len().min(buf_len as usize);
182
+ mem.write_bytes(buf, &target.as_bytes()[..take])?;
183
+ Ok(take as u32)
325
184
  }
326
185
 
327
186
  pub(crate) fn remove_directory(
328
187
  table: &Descriptors,
329
- mem: &GuestMemory,
188
+ mem: &GuestMemory<'_, '_>,
330
189
  fd: u32,
331
- path: (u32, u32),
190
+ path: GuestPtr<str>,
332
191
  ) -> Answer {
333
- refuse_dot_end(
334
- directory(table, fd)?,
335
- &mem.path(path.0, path.1)?,
336
- Errno::Inval,
337
- )?;
338
- let (dir, path) = beneath(table, mem, fd, path)?;
339
- refuse_root(&path, Errno::Inval)?;
340
- judge(&dir.mount, &path, Access::ReadWrite, false)?;
341
- Ok(dir.mount.filesystem().remove_dir(&path)?)
192
+ let path = mem.path(path)?;
193
+ Ok(retry_eagain(|| {
194
+ vfs::filename_rmdir(table.cred(), &start_of(table, fd), &path)
195
+ })?)
342
196
  }
343
197
 
344
- /// A rename stays within one Mount; across two it is `XDEV`, as
345
- /// across two mounted devices. A directory moves only beneath a Mount
346
- /// that allows it, the Filesystem checking what it moves as it moves it.
347
198
  pub(crate) fn rename(
348
199
  table: &Descriptors,
349
- mem: &GuestMemory,
350
- old: (u32, (u32, u32)),
351
- new: (u32, (u32, u32)),
200
+ mem: &GuestMemory<'_, '_>,
201
+ old: (u32, GuestPtr<str>),
202
+ new: (u32, GuestPtr<str>),
352
203
  ) -> Answer {
353
- let from_dir = directory(table, old.0)?;
354
- let to_dir = directory(table, new.0)?;
355
- let (from, to) = (mem.path(old.1 .0, old.1 .1)?, mem.path(new.1 .0, new.1 .1)?);
356
- refuse_dot_end(from_dir, &from, Errno::Busy)?;
357
- refuse_dot_end(to_dir, &to, Errno::Busy)?;
358
- let from = resolve(from_dir, &from)?;
359
- let to = resolve(to_dir, &to)?;
360
- refuse_root(&from.path, Errno::Busy)?;
361
- refuse_root(&to.path, Errno::Busy)?;
362
- // A trailing `/` on either side asks that the source be a directory,
363
- // which `rename(2)` checks before it finds a rename onto its own path
364
- // leaves everything as it was. A rename moves a link rather than what
365
- // it points at, so each side is checked as it stands.
366
- let names_directory = from.names_directory || to.names_directory;
367
- let from = ResolvedPath {
368
- names_directory,
369
- ..from
370
- }
371
- .check_directory(
372
- from_dir.mount.filesystem().as_ref(),
373
- <dyn Filesystem>::symlink_metadata,
374
- )?;
375
- let to = match to {
376
- to if to.path == from => to.path,
377
- to => to.check_directory(
378
- to_dir.mount.filesystem().as_ref(),
379
- <dyn Filesystem>::symlink_metadata,
380
- )?,
381
- };
382
- if !Arc::ptr_eq(&from_dir.mount, &to_dir.mount) {
383
- return Err(Errno::Xdev.into());
384
- }
385
- judge(&from_dir.mount, &from, Access::ReadWrite, false)?;
386
- judge(&to_dir.mount, &to, Access::ReadWrite, false)?;
387
- let rename = match (from_dir.mount.moves_directories(), names_directory) {
388
- (false, _) => Rename::File,
389
- (true, true) => Rename::Directory,
390
- (true, false) => Rename::Any,
391
- };
392
- Ok(from_dir.mount.filesystem().rename(&from, &to, rename)?)
204
+ let (from_path, to_path) = (mem.path(old.1)?, mem.path(new.1)?);
205
+ Ok(retry_eagain(|| {
206
+ let (from, to) = (start_of(table, old.0), start_of(table, new.0));
207
+ vfs::filename_renameat2(table.cred(), &from, &from_path, &to, &to_path)
208
+ })?)
393
209
  }
394
210
 
395
- /// A Filesystem holds no links to make.
396
211
  pub(crate) fn symlink(
397
212
  table: &Descriptors,
398
- mem: &GuestMemory,
399
- old_path: (u32, u32),
213
+ mem: &GuestMemory<'_, '_>,
214
+ old_path: GuestPtr<str>,
400
215
  fd: u32,
401
- new_path: (u32, u32),
216
+ new_path: GuestPtr<str>,
402
217
  ) -> Answer {
403
- directory(table, fd)?;
404
- mem.path(old_path.0, old_path.1)?;
405
- mem.path(new_path.0, new_path.1)?;
406
- Err(Errno::Notsup.into())
218
+ let (target, path) = (mem.path(old_path)?, mem.path(new_path)?);
219
+ // wasi-testsuite's symlink_create has a guest make no link to an absolute
220
+ // path, where Linux would make one; a guest walks none of them anyway. It
221
+ // is refused once the start is found, where the link would be made.
222
+ let start = || -> Result<vfs::Path, FilesystemError> {
223
+ let start = start_of(table, fd).path_init()?;
224
+ match target.starts_with('/') {
225
+ true => Err(FilesystemError::NotPermitted),
226
+ false => Ok(start),
227
+ }
228
+ };
229
+ Ok(retry_eagain(|| {
230
+ vfs::filename_symlinkat(table.cred(), &target, &start, &path)
231
+ })?)
407
232
  }
408
233
 
409
234
  pub(crate) fn unlink_file(
410
235
  table: &Descriptors,
411
- mem: &GuestMemory,
236
+ mem: &GuestMemory<'_, '_>,
412
237
  fd: u32,
413
- path: (u32, u32),
238
+ path: GuestPtr<str>,
414
239
  ) -> Answer {
415
- // Linux's `unlink` follows no link at the end, so a trailing `/` is
416
- // answered by the name itself: no file removes as a directory.
417
- let (dir, path) = beneath(table, mem, fd, path)?;
418
- refuse_root(&path, Errno::Isdir)?;
419
- judge(&dir.mount, &path, Access::ReadWrite, false)?;
420
- Ok(dir.mount.filesystem().remove_file(&path)?)
240
+ let path = mem.path(path)?;
241
+ Ok(retry_eagain(|| {
242
+ vfs::filename_unlinkat(table.cred(), &start_of(table, fd), &path)
243
+ })?)
244
+ }
245
+
246
+ #[cfg(test)]
247
+ mod tests {
248
+ use super::*;
249
+
250
+ // Every Invocation holds to the hook refusing a readlink, so no guest call
251
+ // reaches this; only the encoding itself can witness it.
252
+ #[test]
253
+ fn a_link_is_cut_off_where_the_buffer_ends() {
254
+ let mut bytes = [0u8; 8];
255
+ let answered = {
256
+ let mut memory = wiggle::GuestMemory::Unshared(&mut bytes);
257
+ let mut mem = GuestMemory::new(&mut memory, 64);
258
+ write_link(&mut mem, "target", GuestPtr::new(2), 3)
259
+ .unwrap_or_else(|_| panic!("the link must write"))
260
+ };
261
+ assert_eq!(
262
+ (answered, &bytes),
263
+ (3, b"\0\0tar\0\0\0"),
264
+ "where a link points through path_readlink must fill the buffer and answer what fit"
265
+ );
266
+ }
267
+
268
+ /// What `retry_eagain` answers when the first `moves` calls each had a move
269
+ /// land in them, and how many calls it made.
270
+ fn retry_after_moves(moves: usize) -> (Result<(), FilesystemError>, usize) {
271
+ let mut made = 0;
272
+ let answer = retry_eagain(|| {
273
+ made += 1;
274
+ match made <= moves {
275
+ true => Err(FilesystemError::WouldBlock),
276
+ false => Ok(()),
277
+ }
278
+ });
279
+ (answer, made)
280
+ }
281
+
282
+ // A move lands during a walk only between two of its steps, which no guest
283
+ // call can time, so the retry itself witnesses it.
284
+ // @behavior RT-256
285
+ #[test]
286
+ fn a_path_call_a_move_landed_in_is_made_again_four_times_at_most() {
287
+ assert_eq!(
288
+ [
289
+ retry_after_moves(0),
290
+ retry_after_moves(3),
291
+ retry_after_moves(4)
292
+ ],
293
+ [
294
+ (Ok(()), 1),
295
+ (Ok(()), 4),
296
+ (Err(FilesystemError::WouldBlock), 4),
297
+ ],
298
+ "a path_* call answered EAGAIN must be made again until it answers otherwise, four \
299
+ times in all at most, as cap-std makes openat2"
300
+ );
301
+ }
421
302
  }