kobako 0.28.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 (218) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +1 -1
  3. data/CHANGELOG.md +377 -0
  4. data/Cargo.lock +181 -154
  5. data/README.md +99 -459
  6. data/ROADMAP.md +7 -1
  7. data/SECURITY.md +1 -1
  8. data/crates/kobako-runtime/CHANGELOG.md +354 -0
  9. data/crates/kobako-runtime/Cargo.toml +38 -7
  10. data/crates/kobako-runtime/README.md +17 -8
  11. data/crates/kobako-runtime/src/dispatch.rs +9 -1
  12. data/crates/kobako-runtime/src/endpoint/buffer.rs +192 -0
  13. data/crates/kobako-runtime/src/endpoint/capture.rs +115 -0
  14. data/crates/kobako-runtime/src/endpoint/dispatch.rs +253 -0
  15. data/crates/kobako-runtime/src/endpoint/invoke.rs +194 -0
  16. data/crates/kobako-runtime/src/endpoint/memory_cap.rs +238 -0
  17. data/crates/kobako-runtime/src/endpoint.rs +166 -0
  18. data/crates/kobako-runtime/src/filesystem/bind/fid.rs +82 -0
  19. data/crates/kobako-runtime/src/filesystem/bind/lookup.rs +236 -0
  20. data/crates/kobako-runtime/src/filesystem/bind/walk.rs +249 -0
  21. data/crates/kobako-runtime/src/filesystem/bind.rs +2665 -0
  22. data/crates/kobako-runtime/src/filesystem/overlay/file.rs +89 -0
  23. data/crates/kobako-runtime/src/filesystem/overlay/inode.rs +106 -0
  24. data/crates/kobako-runtime/src/filesystem/overlay/path.rs +285 -0
  25. data/crates/kobako-runtime/src/filesystem/overlay/stack/copy_up.rs +135 -0
  26. data/crates/kobako-runtime/src/filesystem/overlay/stack/dir.rs +263 -0
  27. data/crates/kobako-runtime/src/filesystem/overlay/stack/namei.rs +135 -0
  28. data/crates/kobako-runtime/src/filesystem/overlay/stack/readdir.rs +57 -0
  29. data/crates/kobako-runtime/src/filesystem/overlay/stack.rs +301 -0
  30. data/crates/kobako-runtime/src/filesystem/overlay.rs +3469 -0
  31. data/crates/kobako-runtime/src/filesystem/path.rs +21 -0
  32. data/crates/kobako-runtime/src/filesystem/pipe.rs +221 -0
  33. data/crates/kobako-runtime/src/filesystem/rdir.rs +101 -0
  34. data/crates/kobako-runtime/src/filesystem/tmpfs/tmpfile.rs +32 -0
  35. data/crates/kobako-runtime/src/filesystem/tmpfs/whiteout.rs +85 -0
  36. data/crates/kobako-runtime/src/filesystem/tmpfs/xattr.rs +22 -0
  37. data/crates/kobako-runtime/src/filesystem/tmpfs.rs +1985 -0
  38. data/crates/kobako-runtime/src/filesystem/wrap.rs +224 -0
  39. data/crates/kobako-runtime/src/filesystem.rs +26 -0
  40. data/crates/kobako-runtime/src/grant.rs +20 -0
  41. data/crates/kobako-runtime/src/hashtable.rs +44 -0
  42. data/crates/kobako-runtime/src/lib.rs +16 -1
  43. data/crates/kobako-runtime/src/locking/seqlock.rs +66 -0
  44. data/crates/kobako-runtime/src/locking.rs +218 -0
  45. data/crates/kobako-runtime/src/mount.rs +105 -0
  46. data/crates/kobako-runtime/src/profile.rs +140 -4
  47. data/crates/kobako-runtime/src/runtime.rs +12 -0
  48. data/crates/kobako-runtime/src/sched.rs +210 -0
  49. data/crates/kobako-runtime/src/security.rs +696 -0
  50. data/crates/kobako-runtime/src/snapshot.rs +20 -5
  51. data/crates/kobako-runtime/src/vfs/attr.rs +162 -0
  52. data/crates/kobako-runtime/src/vfs/cred.rs +334 -0
  53. data/crates/kobako-runtime/src/vfs/dentry.rs +1412 -0
  54. data/crates/kobako-runtime/src/vfs/errno.rs +153 -0
  55. data/crates/kobako-runtime/src/vfs/file.rs +1915 -0
  56. data/crates/kobako-runtime/src/vfs/inode.rs +429 -0
  57. data/crates/kobako-runtime/src/vfs/kern_path.rs +217 -0
  58. data/crates/kobako-runtime/src/vfs/mount.rs +59 -0
  59. data/crates/kobako-runtime/src/vfs/namei.rs +5708 -0
  60. data/crates/kobako-runtime/src/vfs/openat2.rs +116 -0
  61. data/crates/kobako-runtime/src/vfs/path.rs +64 -0
  62. data/crates/kobako-runtime/src/vfs/readdir.rs +163 -0
  63. data/crates/kobako-runtime/src/vfs/stat.rs +157 -0
  64. data/crates/kobako-runtime/src/vfs/super_block.rs +122 -0
  65. data/crates/kobako-runtime/src/vfs/utimes.rs +289 -0
  66. data/crates/kobako-runtime/src/vfs.rs +60 -0
  67. data/crates/kobako-runtime/src/yielder.rs +11 -6
  68. data/crates/kobako-runtime/tests/linux_baseline/calls.rs +171 -0
  69. data/crates/kobako-runtime/tests/linux_baseline/concurrent.rs +276 -0
  70. data/crates/kobako-runtime/tests/linux_baseline/kobako.rs +323 -0
  71. data/crates/kobako-runtime/tests/linux_baseline/linux-baseline-expected.txt +16 -0
  72. data/crates/kobako-runtime/tests/linux_baseline/linux.rs +449 -0
  73. data/crates/kobako-runtime/tests/linux_baseline/main.rs +611 -0
  74. data/crates/kobako-runtime/tests/linux_baseline/permissions.rs +236 -0
  75. data/crates/kobako-transport/CHANGELOG.md +23 -0
  76. data/crates/kobako-transport/Cargo.toml +3 -3
  77. data/crates/kobako-transport/README.md +6 -5
  78. data/crates/kobako-transport/src/abi.rs +1 -1
  79. data/crates/kobako-transport/src/envelope/bytes.rs +33 -25
  80. data/crates/kobako-transport/src/envelope/call.rs +5 -5
  81. data/crates/kobako-transport/src/envelope/compile_reply.rs +2 -2
  82. data/crates/kobako-transport/src/envelope/error_record.rs +3 -3
  83. data/crates/kobako-transport/src/envelope/fault.rs +4 -3
  84. data/crates/kobako-transport/src/envelope/outcome.rs +3 -3
  85. data/crates/kobako-transport/src/envelope/preamble.rs +24 -24
  86. data/crates/kobako-transport/src/envelope/reply.rs +34 -16
  87. data/crates/kobako-transport/src/envelope/request.rs +19 -9
  88. data/crates/kobako-transport/src/envelope/run.rs +1 -1
  89. data/crates/kobako-transport/src/envelope/unsupported.rs +2 -2
  90. data/crates/kobako-transport/src/envelope.rs +2 -2
  91. data/crates/kobako-wasmtime/CHANGELOG.md +265 -0
  92. data/crates/kobako-wasmtime/Cargo.toml +25 -19
  93. data/crates/kobako-wasmtime/README.md +13 -7
  94. data/crates/kobako-wasmtime/src/abi.rs +75 -49
  95. data/crates/kobako-wasmtime/src/ambient.rs +64 -6
  96. data/crates/kobako-wasmtime/src/cache.rs +188 -77
  97. data/crates/kobako-wasmtime/src/config.rs +77 -5
  98. data/crates/kobako-wasmtime/src/descriptors.rs +200 -0
  99. data/crates/kobako-wasmtime/src/driver.rs +70 -135
  100. data/crates/kobako-wasmtime/src/exports.rs +5 -16
  101. data/crates/kobako-wasmtime/src/guest.rs +38 -0
  102. data/crates/kobako-wasmtime/src/imports.rs +52 -0
  103. data/crates/kobako-wasmtime/src/instance_pre.rs +18 -46
  104. data/crates/kobako-wasmtime/src/invocation.rs +43 -74
  105. data/crates/kobako-wasmtime/src/lib.rs +5 -4
  106. data/crates/kobako-wasmtime/src/limiter.rs +6 -230
  107. data/crates/kobako-wasmtime/src/preview1/differential/mount.rs +632 -0
  108. data/crates/kobako-wasmtime/src/preview1/differential.rs +486 -0
  109. data/crates/kobako-wasmtime/src/preview1/errno.rs +135 -0
  110. data/crates/kobako-wasmtime/src/preview1/fd.rs +1420 -0
  111. data/crates/kobako-wasmtime/src/preview1/memory.rs +172 -0
  112. data/crates/kobako-wasmtime/src/preview1/mount_view/wrap.rs +211 -0
  113. data/crates/kobako-wasmtime/src/preview1/mount_view.rs +3766 -0
  114. data/crates/kobako-wasmtime/src/preview1/path.rs +302 -0
  115. data/crates/kobako-wasmtime/src/preview1/poll.rs +381 -0
  116. data/crates/kobako-wasmtime/src/preview1/sock.rs +13 -0
  117. data/crates/kobako-wasmtime/src/preview1/testsuite-expected.txt +6 -0
  118. data/crates/kobako-wasmtime/src/preview1/testsuite.rs +157 -0
  119. data/crates/kobako-wasmtime/src/preview1.rs +512 -0
  120. data/crates/kobako-wasmtime/src/trap.rs +45 -25
  121. data/crates/kobako-wasmtime/src/wasi.rs +333 -0
  122. data/crates/kobako-wasmtime/tests/integration/compile_posture.rs +43 -0
  123. data/crates/kobako-wasmtime/tests/integration/guest_file.rs +1085 -0
  124. data/crates/kobako-wasmtime/tests/integration/main.rs +29 -0
  125. data/crates/kobako-wasmtime/tests/{preamble_flags.rs → integration/preamble_flags.rs} +4 -3
  126. data/crates/kobako-wasmtime/tests/{snippet_compile.rs → integration/snippet_compile.rs} +5 -4
  127. data/crates/kobako-wasmtime/tests/{support → integration/support}/mod.rs +9 -13
  128. data/crates/kobako-wasmtime/tests/integration/unanswered_call.rs +86 -0
  129. data/crates/kobako-wasmtime/tests/{unsupported_call.rs → integration/unsupported_call.rs} +2 -1
  130. data/crates/kobako-wasmtime/witx/typenames.witx +750 -0
  131. data/crates/kobako-wasmtime/witx/wasi_snapshot_preview1.witx +441 -0
  132. data/data/kobako.wasm +0 -0
  133. data/ext/kobako/Cargo.toml +4 -2
  134. data/ext/kobako/src/runtime/bridge.rs +154 -81
  135. data/ext/kobako/src/runtime/errors.rs +1 -1
  136. data/ext/kobako/src/runtime/escape.rs +67 -0
  137. data/ext/kobako/src/runtime/filesystem/grants.rs +107 -0
  138. data/ext/kobako/src/runtime/filesystem/ruby.rs +708 -0
  139. data/ext/kobako/src/runtime/filesystem.rs +208 -0
  140. data/ext/kobako/src/runtime/gvl.rs +49 -8
  141. data/ext/kobako/src/runtime/pinned_value.rs +31 -0
  142. data/ext/kobako/src/runtime/snapshot.rs +127 -0
  143. data/ext/kobako/src/runtime.rs +125 -199
  144. data/lib/kobako/catalog/extensions.rb +5 -5
  145. data/lib/kobako/catalog/flags.rb +2 -14
  146. data/lib/kobako/catalog/grants.rb +30 -0
  147. data/lib/kobako/catalog/handles.rb +6 -6
  148. data/lib/kobako/catalog/mount.rb +19 -0
  149. data/lib/kobako/catalog/services.rb +13 -2
  150. data/lib/kobako/catalog/snippets.rb +11 -15
  151. data/lib/kobako/catalog.rb +11 -0
  152. data/lib/kobako/codec/decoder.rb +10 -9
  153. data/lib/kobako/codec/ext_types.rb +4 -6
  154. data/lib/kobako/codec/handle_walk.rb +4 -6
  155. data/lib/kobako/codec/nesting.rb +7 -7
  156. data/lib/kobako/codec/state.rb +19 -3
  157. data/lib/kobako/codec/utils.rb +1 -1
  158. data/lib/kobako/codec/value_budget.rb +86 -0
  159. data/lib/kobako/codec.rb +7 -0
  160. data/lib/kobako/context.rb +40 -13
  161. data/lib/kobako/errors.rb +15 -14
  162. data/lib/kobako/filesystem.rb +69 -0
  163. data/lib/kobako/payload/arguments.rb +2 -2
  164. data/lib/kobako/pool.rb +6 -6
  165. data/lib/kobako/sandbox.rb +55 -27
  166. data/lib/kobako/sandbox_options.rb +39 -13
  167. data/lib/kobako/snippet/{binary.rb → bytecode.rb} +2 -2
  168. data/lib/kobako/snippet/source.rb +1 -1
  169. data/lib/kobako/snippet.rb +1 -1
  170. data/lib/kobako/transport/block_failure.rb +35 -0
  171. data/lib/kobako/transport/dispatcher.rb +33 -29
  172. data/lib/kobako/transport/exposure.rb +7 -9
  173. data/lib/kobako/transport/reflection.rb +79 -23
  174. data/lib/kobako/transport/run.rb +5 -5
  175. data/lib/kobako/transport/yielder.rb +9 -31
  176. data/lib/kobako/transport.rb +1 -0
  177. data/lib/kobako/version.rb +1 -1
  178. data/lib/kobako.rb +35 -0
  179. data/release-please-config.json +1 -0
  180. data/sig/kobako/catalog/extensions.rbs +3 -3
  181. data/sig/kobako/catalog/flags.rbs +0 -4
  182. data/sig/kobako/catalog/grants.rbs +13 -0
  183. data/sig/kobako/catalog/handles.rbs +3 -3
  184. data/sig/kobako/catalog/mount.rbs +24 -0
  185. data/sig/kobako/catalog/services.rbs +5 -1
  186. data/sig/kobako/catalog/snippets.rbs +4 -4
  187. data/sig/kobako/catalog.rbs +1 -0
  188. data/sig/kobako/codec/decoder.rbs +1 -1
  189. data/sig/kobako/codec/handle_walk.rbs +1 -1
  190. data/sig/kobako/codec/nesting.rbs +2 -2
  191. data/sig/kobako/codec/state.rbs +7 -1
  192. data/sig/kobako/codec/utils.rbs +1 -1
  193. data/sig/kobako/codec/value_budget.rbs +23 -0
  194. data/sig/kobako/codec.rbs +3 -2
  195. data/sig/kobako/context.rbs +14 -6
  196. data/sig/kobako/errors.rbs +4 -4
  197. data/sig/kobako/filesystem.rbs +93 -0
  198. data/sig/kobako/guest_yielder.rbs +9 -0
  199. data/sig/kobako/payload/arguments.rbs +1 -1
  200. data/sig/kobako/pool.rbs +3 -3
  201. data/sig/kobako/runtime.rbs +21 -49
  202. data/sig/kobako/sandbox.rbs +9 -5
  203. data/sig/kobako/sandbox_options.rbs +25 -3
  204. data/sig/kobako/snapshot.rbs +20 -0
  205. data/sig/kobako/snippet/bytecode.rbs +12 -0
  206. data/sig/kobako/transport/block_failure.rbs +13 -0
  207. data/sig/kobako/transport/dispatcher.rbs +4 -4
  208. data/sig/kobako/transport/exposure.rbs +2 -2
  209. data/sig/kobako/transport/reflection.rbs +14 -1
  210. data/sig/kobako/transport/run.rbs +3 -3
  211. data/sig/kobako/transport/yielder.rbs +3 -7
  212. data/sig/kobako/transport.rbs +3 -3
  213. metadata +116 -15
  214. data/crates/kobako-wasmtime/src/capture.rs +0 -96
  215. data/crates/kobako-wasmtime/src/dispatch.rs +0 -107
  216. data/crates/kobako-wasmtime/src/guest_mem.rs +0 -265
  217. data/crates/kobako-wasmtime/src/io.rs +0 -286
  218. data/sig/kobako/snippet/binary.rbs +0 -12
@@ -0,0 +1,289 @@
1
+ //! Setting the times of an entry, as Linux's `utimensat` does. What answers is
2
+ //! the entry's own `setattr`, so a Filesystem decides whether it keeps a time
3
+ //! of the caller's choosing.
4
+
5
+ use std::sync::Arc;
6
+
7
+ use super::attr::notify_change;
8
+ use super::cred::{on_path, Cred};
9
+ use super::namei::{filename_lookup, retry_estale};
10
+ use super::path::{Path, Start};
11
+ use super::{FilesystemError, Iattr};
12
+
13
+ /// Set `times` on what the descriptor at `path` names, as Linux's `do_utimes_fd`.
14
+ pub fn do_utimes_fd(cred: &Arc<Cred>, path: &Path, times: &Iattr) -> Result<(), FilesystemError> {
15
+ vfs_utimes(cred, path, times)
16
+ }
17
+
18
+ /// Set `times` on what `path` names, as Linux's `vfs_utimes` asks the mount,
19
+ /// then the security hook, then `setattr` under the entry's lock. An entry
20
+ /// without `setattr` keeps no time to set, as this VFS holds none on an inode.
21
+ fn vfs_utimes(cred: &Arc<Cred>, path: &Path, times: &Iattr) -> Result<(), FilesystemError> {
22
+ path.mnt().mnt_want_write()?;
23
+ let dentry = path.dentry();
24
+ on_path(path.mnt(), dentry, |at| cred.security_path_utimes(at))?;
25
+ let held = dentry.inode_lock()?;
26
+ notify_change(dentry, &held, times)
27
+ }
28
+
29
+ /// Set `times` on what `path` beneath `start` names, a link at its end
30
+ /// followed where `follow` asks, as Linux's `do_utimes_path` walks to it first.
31
+ pub fn do_utimes_path(
32
+ cred: &Arc<Cred>,
33
+ start: &impl Start,
34
+ path: &str,
35
+ follow: bool,
36
+ times: &Iattr,
37
+ ) -> Result<(), FilesystemError> {
38
+ retry_estale(
39
+ || filename_lookup(cred, start, path, follow),
40
+ |nd| vfs_utimes(cred, &nd.path_to(nd.path()?), times),
41
+ )
42
+ }
43
+
44
+ #[cfg(test)]
45
+ mod tests {
46
+ use std::sync::Mutex;
47
+ use std::time::UNIX_EPOCH;
48
+
49
+ use super::*;
50
+ use crate::filesystem::{
51
+ FileSystemType, FileType, Filesystem, InodeOperations, Kstat, SetTime, Tmpfs,
52
+ };
53
+ use crate::mount::{Access as MountAccess, Mount, Policy};
54
+ use crate::security::{prepare_creds_within, restrict_self};
55
+ use crate::vfs::Dentry;
56
+
57
+ fn times() -> Iattr<'static> {
58
+ Iattr::times(Some(SetTime::Now), Some(SetTime::At(UNIX_EPOCH)))
59
+ }
60
+
61
+ fn mount_of(filesystem: Arc<dyn FileSystemType>, access: MountAccess) -> Mount {
62
+ let filesystem = Filesystem::new(filesystem).expect("the instance must stand");
63
+ Mount::new("/data", filesystem, access).expect("/data is a guest path")
64
+ }
65
+
66
+ fn tmpfs() -> Arc<Tmpfs> {
67
+ Arc::new([("file.txt", "x")].into_iter().collect())
68
+ }
69
+
70
+ // A thread waiting on a lock it holds answers a deadlock, so only a change
71
+ // that takes the entry's lock answers one here.
72
+ // @behavior RT-236
73
+ #[test]
74
+ fn a_change_to_an_entry_holds_its_lock() {
75
+ let mount = mount_of(tmpfs(), MountAccess::ReadWrite);
76
+ let root = Path::root(mount.mnt());
77
+ let _held = root.dentry().inode_lock().expect("the lock must be free");
78
+ assert_eq!(
79
+ vfs_utimes(&Arc::default(), &root, &times()),
80
+ Err(FilesystemError::Deadlock),
81
+ "vfs::vfs_utimes on an entry whose lock its own thread holds must answer EDEADLK"
82
+ );
83
+ }
84
+
85
+ // @behavior RT-236
86
+ #[test]
87
+ fn a_truncate_through_a_descriptor_holds_the_entry_lock() {
88
+ let mount = mount_of(tmpfs(), MountAccess::ReadWrite);
89
+ let write = crate::vfs::OpenHow {
90
+ write: true,
91
+ ..Default::default()
92
+ };
93
+ let mut file = crate::vfs::kern_path::filp_open(
94
+ &Arc::default(),
95
+ &Path::root(mount.mnt()),
96
+ "file.txt",
97
+ &write,
98
+ )
99
+ .expect("the file opens to write");
100
+ let dentry = file.path().dentry().clone();
101
+ let _held = dentry.inode_lock().expect("the lock must be free");
102
+ assert_eq!(
103
+ file.truncate(0),
104
+ Err(FilesystemError::Deadlock),
105
+ "vfs::File::truncate on a file whose lock its own thread holds must answer EDEADLK, \
106
+ as do_ftruncate holds the inode's lock around notify_change"
107
+ );
108
+ }
109
+
110
+ // @behavior RT-186
111
+ #[test]
112
+ fn setting_times_asks_the_mount_then_the_entry() {
113
+ let set = |access, path: &str| {
114
+ let mount = mount_of(tmpfs(), access);
115
+ do_utimes_path(
116
+ &Arc::default(),
117
+ &Path::root(mount.mnt()),
118
+ path,
119
+ false,
120
+ &times(),
121
+ )
122
+ };
123
+ assert_eq!(
124
+ (
125
+ set(MountAccess::ReadWrite, "missing"),
126
+ set(MountAccess::Read, "file.txt"),
127
+ set(MountAccess::ReadWrite, "file.txt"),
128
+ ),
129
+ (
130
+ Err(FilesystemError::NotFound),
131
+ Err(FilesystemError::ReadOnlyFilesystem),
132
+ Ok(()),
133
+ ),
134
+ "vfs::do_utimes_path must answer what is not there ENOENT, a read-only Mount EROFS, and \
135
+ hand a Tmpfs entry the times"
136
+ );
137
+ }
138
+
139
+ /// A directory answering every call but `setattr` by default.
140
+ struct NoSetattr;
141
+
142
+ impl FileSystemType for NoSetattr {
143
+ fn fill_super(self: Arc<Self>, sb: &mut Filesystem) -> Result<(), FilesystemError> {
144
+ sb.d_make_root(crate::vfs::Inode::new(
145
+ &crate::vfs::Kstat::new(crate::vfs::FileType::Dir, 0),
146
+ self,
147
+ ));
148
+ Ok(())
149
+ }
150
+ }
151
+
152
+ impl InodeOperations for NoSetattr {
153
+ fn getattr(&self, _dentry: &Dentry) -> Result<Kstat, FilesystemError> {
154
+ Ok(Kstat::new(FileType::Dir, 0))
155
+ }
156
+ }
157
+
158
+ // @behavior RT-186
159
+ #[test]
160
+ fn an_entry_without_setattr_answers_enotsup() {
161
+ let mount = mount_of(Arc::new(NoSetattr), MountAccess::ReadWrite);
162
+ assert_eq!(
163
+ vfs_utimes(&Arc::default(), &Path::root(mount.mnt()), &times()),
164
+ Err(FilesystemError::Unsupported),
165
+ "vfs::vfs_utimes on an entry without setattr must answer ENOTSUP"
166
+ );
167
+ }
168
+
169
+ /// A Filesystem keeping what it was last asked to set.
170
+ #[derive(Default)]
171
+ struct KeepsTimes(Mutex<Option<Iattr<'static>>>);
172
+
173
+ impl FileSystemType for KeepsTimes {
174
+ fn fill_super(self: Arc<Self>, sb: &mut Filesystem) -> Result<(), FilesystemError> {
175
+ sb.d_make_root(crate::vfs::Inode::new(
176
+ &crate::vfs::Kstat::new(crate::vfs::FileType::Dir, 0),
177
+ self,
178
+ ));
179
+ Ok(())
180
+ }
181
+ }
182
+
183
+ impl InodeOperations for KeepsTimes {
184
+ fn getattr(&self, _dentry: &Dentry) -> Result<Kstat, FilesystemError> {
185
+ Ok(Kstat::new(FileType::Dir, 0))
186
+ }
187
+
188
+ fn setattr(&self, _dentry: &Dentry, attr: &Iattr) -> Result<(), FilesystemError> {
189
+ *self.0.lock().unwrap() = Some(Iattr {
190
+ size: attr.size,
191
+ atime: attr.atime,
192
+ mtime: attr.mtime,
193
+ open: attr.open,
194
+ ctime: attr.ctime,
195
+ file: None,
196
+ });
197
+ Ok(())
198
+ }
199
+ }
200
+
201
+ // @behavior RT-186
202
+ #[test]
203
+ fn a_filesystem_with_setattr_answers_for_the_times_itself() {
204
+ let filesystem = Arc::new(KeepsTimes::default());
205
+ let mount = mount_of(filesystem.clone(), MountAccess::ReadWrite);
206
+ let answer = vfs_utimes(&Arc::default(), &Path::root(mount.mnt()), &times());
207
+ let handed = filesystem
208
+ .0
209
+ .lock()
210
+ .unwrap()
211
+ .map(|attr| (attr.atime, attr.mtime, attr.ctime.is_some()));
212
+ assert_eq!(
213
+ (answer, handed),
214
+ (Ok(()), Some((times().atime, times().mtime, true))),
215
+ "vfs::vfs_utimes on a Filesystem with setattr must hand it the times and the moment \
216
+ the change lands, and answer what it answers"
217
+ );
218
+ }
219
+
220
+ /// A Policy allowing everything, keeping each path it was asked about.
221
+ #[derive(Default)]
222
+ struct RecordsPaths(Mutex<Vec<String>>);
223
+
224
+ impl Policy for RecordsPaths {
225
+ fn allows(&self, path: &str, _access: MountAccess) -> bool {
226
+ self.0.lock().unwrap().push(path.to_string());
227
+ true
228
+ }
229
+ }
230
+
231
+ // A file's descriptor outlives its name, so only the walk's own check
232
+ // keeps a Policy from judging a path it no longer stands at.
233
+ // @behavior RT-201
234
+ #[test]
235
+ fn a_time_set_through_a_descriptor_is_judged_where_it_stands() {
236
+ let policy = Arc::new(RecordsPaths::default());
237
+ let mount = mount_of(tmpfs(), MountAccess::ReadWrite).with_policy(policy.clone());
238
+ let cred = restrict_self(&prepare_creds_within(&[], None), [&mount]);
239
+ let root = Path::root(mount.mnt());
240
+ let how = crate::vfs::OpenHow {
241
+ read: true,
242
+ ..Default::default()
243
+ };
244
+ let file = crate::vfs::kern_path::filp_open(&cred, &root, "file.txt", &how)
245
+ .unwrap_or_else(|_| panic!("the file must open"));
246
+ crate::vfs::filename_unlinkat(&cred, &root, "file.txt").expect("the name must go");
247
+ policy.0.lock().unwrap().clear();
248
+ assert_eq!(
249
+ (
250
+ crate::security::WalkGuard::new([&mount]).check(file.path()),
251
+ policy.0.lock().unwrap().clone()
252
+ ),
253
+ (Err(FilesystemError::NotFound), Vec::new()),
254
+ "WalkGuard::check of a descriptor of a file whose name is gone, beneath a Policy, \
255
+ must answer ENOENT without asking the Policy about the path it stood at"
256
+ );
257
+ }
258
+
259
+ struct RefusesAll;
260
+
261
+ impl Policy for RefusesAll {
262
+ fn allows(&self, _path: &str, _access: MountAccess) -> bool {
263
+ false
264
+ }
265
+ }
266
+
267
+ // A file opened to read reaches this with no write the Policy judged, so
268
+ // the call itself must ask.
269
+ // @behavior RT-186
270
+ #[test]
271
+ fn a_policy_is_asked_before_times_are_set() {
272
+ let filesystem = Arc::new(KeepsTimes::default());
273
+ let mount =
274
+ mount_of(filesystem.clone(), MountAccess::ReadWrite).with_policy(Arc::new(RefusesAll));
275
+ assert_eq!(
276
+ (
277
+ vfs_utimes(
278
+ &restrict_self(&prepare_creds_within(&[], None), [&mount]),
279
+ &Path::root(mount.mnt()),
280
+ &times()
281
+ ),
282
+ *filesystem.0.lock().unwrap(),
283
+ ),
284
+ (Err(FilesystemError::PermissionDenied), None),
285
+ "vfs::vfs_utimes beneath a Policy refusing the path must answer EACCES and leave the \
286
+ Filesystem unasked"
287
+ );
288
+ }
289
+ }
@@ -0,0 +1,60 @@
1
+ //! The calls made on paths beneath a mount, answered as the VFS above a
2
+ //! filesystem answers them, so an ABI above only carries each call's arguments
3
+ //! in and its answer out.
4
+ //!
5
+ //! A path never climbs above the directory it is resolved beneath, as a
6
+ //! capability never widens, and a read-only mount and the security hooks
7
+ //! answer each open and each change before the Filesystem is asked, so a
8
+ //! Filesystem of a third party's checks neither.
9
+
10
+ mod attr;
11
+ mod cred;
12
+ mod dentry;
13
+ mod errno;
14
+ mod file;
15
+ mod inode;
16
+ #[cfg(test)]
17
+ pub(crate) mod kern_path;
18
+ mod mount;
19
+ mod namei;
20
+ mod openat2;
21
+ mod path;
22
+ mod readdir;
23
+ mod stat;
24
+ mod super_block;
25
+ mod utimes;
26
+
27
+ pub(crate) use namei::{
28
+ __start_renaming, d_revalidate, inode_permission, lookup_one_unlocked, may_create_dentry,
29
+ start_creating, start_removing, vfs_create, vfs_mkdir, vfs_rename, vfs_rmdir, vfs_unlink,
30
+ };
31
+
32
+ pub(crate) use attr::notify_change;
33
+ pub use attr::{Iattr, SetTime};
34
+ pub(crate) use cred::VmCommitted;
35
+ pub use cred::{Cred, FileSecurity, SecurityOperations, SecurityPath};
36
+ pub use dentry::{Dentry, DentryOperations};
37
+ pub use errno::FilesystemError;
38
+ pub(crate) use file::alloc_file_pseudo;
39
+ pub use file::{
40
+ generic_file_llseek, generic_write_sync, FMode, File, FileOperations, Kiocb, PollMask, Whence,
41
+ };
42
+ pub use inode::{Finish, Identity, Inode, InodeOperations, Lookup, Mask, RenameFlag};
43
+ pub use mount::VfsMount;
44
+ pub use namei::{
45
+ do_file_open, filename_linkat, filename_mkdirat, filename_renameat2, filename_rmdir,
46
+ filename_symlinkat, filename_unlinkat,
47
+ };
48
+ pub use openat2::{build_open_flags, OpenFlags, OpenHow};
49
+ pub use path::{Path, Start};
50
+ pub use readdir::{
51
+ dir_emit_dots, generic_read_dir, verify_dirent_name, DirContext, DirEmit, DirEntry,
52
+ };
53
+ pub use stat::{do_readlinkat, vfs_fstatat, FileType, Kstat};
54
+ pub use super_block::{FileSystemType, SuperBlock};
55
+ pub use utimes::{do_utimes_fd, do_utimes_path};
56
+
57
+ /// Linux's limits on a path, its terminating NUL counted, and on one name
58
+ /// within it.
59
+ pub const PATH_MAX: usize = 4096;
60
+ pub const NAME_MAX: usize = 255;
@@ -15,11 +15,16 @@ use crate::error::Trap;
15
15
  /// Host-initiated re-entry into the in-flight guest instance to run a
16
16
  /// yielded block.
17
17
  ///
18
- /// `yield_to_block` ships `args` as a `yield` downcall and returns the
19
- /// decoded Yield Reply, `Unsupported` arm included, or a `Trap` — surfaced
20
- /// through the frontend's trap-error mapping — when the re-entry traps,
21
- /// the guest returns an empty or unframeable result, or a payload exceeds
22
- /// the 16 MiB cap.
18
+ /// Each open Call has a `frame`, counted from `0` at the outermost.
19
+ /// `yield_to_block` ships `args` as a `yield` downcall to the block of the
20
+ /// open Call `frame` names, and returns the decoded Yield Reply,
21
+ /// `Unsupported` arm included, or a `Trap` — surfaced through the
22
+ /// frontend's trap-error mapping — when the re-entry traps, the guest
23
+ /// returns an empty or unframeable result, or a payload exceeds the
24
+ /// 16 MiB cap.
23
25
  pub trait Yielder {
24
- fn yield_to_block(&mut self, args: &[u8]) -> Result<YieldReply, Trap>;
26
+ /// The frame of the Call this Yielder was lent for.
27
+ fn frame(&self) -> u32;
28
+
29
+ fn yield_to_block(&mut self, frame: u32, args: &[u8]) -> Result<YieldReply, Trap>;
25
30
  }
@@ -0,0 +1,171 @@
1
+ //! The calls a sequence is made of, drawn from a seed so a run repeats and an
2
+ //! older commit can be held to the same one.
3
+
4
+ /// How many descriptors a sequence holds at once.
5
+ pub const SLOTS: usize = 4;
6
+
7
+ /// The modified time a call sets, seconds past the Unix epoch.
8
+ pub const UTIME_SECONDS: i64 = 1;
9
+
10
+ /// One call, made alike through the VFS and through Linux. A path call starts
11
+ /// from the root, or from the descriptor `base` names.
12
+ #[derive(Clone, Debug)]
13
+ pub enum Call {
14
+ Open(Open),
15
+ Close(usize),
16
+ Read(usize, usize),
17
+ Write(usize, usize),
18
+ Pread(usize, usize, u64),
19
+ Pwrite(usize, usize, u64),
20
+ Seek(usize, i64, u8),
21
+ Truncate(usize, u64),
22
+ Fallocate(usize, u64, u64),
23
+ Fstat(usize),
24
+ Fsync(usize),
25
+ Readdir(usize),
26
+ Stat {
27
+ base: Option<usize>,
28
+ path: String,
29
+ follow: bool,
30
+ },
31
+ Utimes {
32
+ base: Option<usize>,
33
+ path: String,
34
+ follow: bool,
35
+ },
36
+ Mkdir(Option<usize>, String),
37
+ Rmdir(Option<usize>, String),
38
+ Unlink(Option<usize>, String),
39
+ Rename(Option<usize>, String, String),
40
+ }
41
+
42
+ /// What an open asks for, and the descriptor it lands in.
43
+ #[derive(Clone, Debug, Default)]
44
+ pub struct Open {
45
+ pub base: Option<usize>,
46
+ pub path: String,
47
+ pub read: bool,
48
+ pub write: bool,
49
+ pub create: bool,
50
+ pub exclusive: bool,
51
+ pub truncate: bool,
52
+ pub append: bool,
53
+ pub directory: bool,
54
+ pub nofollow: bool,
55
+ pub slot: usize,
56
+ }
57
+
58
+ impl Call {
59
+ /// The call's name, which a difference and a coverage count are kept by.
60
+ pub fn kind(&self) -> &'static str {
61
+ match self {
62
+ Self::Open(_) => "Open",
63
+ Self::Close(_) => "Close",
64
+ Self::Read(..) => "Read",
65
+ Self::Write(..) => "Write",
66
+ Self::Pread(..) => "Pread",
67
+ Self::Pwrite(..) => "Pwrite",
68
+ Self::Seek(..) => "Seek",
69
+ Self::Truncate(..) => "Truncate",
70
+ Self::Fallocate(..) => "Fallocate",
71
+ Self::Utimes { .. } => "Utimes",
72
+ Self::Fstat(_) => "Fstat",
73
+ Self::Fsync(_) => "Fsync",
74
+ Self::Readdir(_) => "Readdir",
75
+ Self::Stat { .. } => "Stat",
76
+ Self::Mkdir(..) => "Mkdir",
77
+ Self::Rmdir(..) => "Rmdir",
78
+ Self::Unlink(..) => "Unlink",
79
+ Self::Rename(..) => "Rename",
80
+ }
81
+ }
82
+ }
83
+
84
+ /// The bytes a write of `len` carries.
85
+ pub fn payload(len: usize) -> &'static [u8] {
86
+ &b"WXYZ"[..len.min(4)]
87
+ }
88
+
89
+ /// A small generator, so a seed names the same sequence on every toolchain.
90
+ pub struct Rng(u64);
91
+
92
+ impl Rng {
93
+ pub fn new(seed: u64) -> Self {
94
+ let mut rng = Self(seed.wrapping_mul(0x9E37_79B9_7F4A_7C15) | 1);
95
+ for _ in 0..4 {
96
+ rng.next();
97
+ }
98
+ rng
99
+ }
100
+
101
+ fn next(&mut self) -> u64 {
102
+ self.0 ^= self.0 << 13;
103
+ self.0 ^= self.0 >> 7;
104
+ self.0 ^= self.0 << 17;
105
+ self.0
106
+ }
107
+
108
+ pub fn random_below(&mut self, bound: u64) -> u64 {
109
+ self.next() % bound
110
+ }
111
+
112
+ fn random_ratio(&mut self, numerator: u64, denominator: u64) -> bool {
113
+ self.random_below(denominator) < numerator
114
+ }
115
+
116
+ fn choose_path(&mut self, paths: &[String]) -> String {
117
+ paths[self.random_below(paths.len() as u64) as usize].clone()
118
+ }
119
+
120
+ /// The next call of a sequence over `paths`.
121
+ pub fn call(&mut self, paths: &[String]) -> Call {
122
+ let slot = self.random_below(SLOTS as u64) as usize;
123
+ let base = self
124
+ .random_ratio(25, 100)
125
+ .then(|| self.random_below(SLOTS as u64) as usize);
126
+ match self.random_below(100) {
127
+ 0..=27 => Call::Open(Open {
128
+ base,
129
+ path: self.choose_path(paths),
130
+ read: self.random_ratio(70, 100),
131
+ write: self.random_ratio(45, 100),
132
+ create: self.random_ratio(30, 100),
133
+ exclusive: self.random_ratio(10, 100),
134
+ truncate: self.random_ratio(10, 100),
135
+ append: self.random_ratio(12, 100),
136
+ directory: self.random_ratio(12, 100),
137
+ nofollow: self.random_ratio(15, 100),
138
+ slot,
139
+ }),
140
+ 28..=32 => Call::Close(slot),
141
+ 33..=38 => Call::Read(slot, self.random_below(8) as usize),
142
+ 39..=45 => Call::Write(slot, self.random_below(4) as usize),
143
+ 46..=48 => Call::Pread(slot, self.random_below(8) as usize, self.random_below(10)),
144
+ 49..=51 => Call::Pwrite(slot, self.random_below(4) as usize, self.random_below(10)),
145
+ 52..=54 => Call::Seek(
146
+ slot,
147
+ self.random_below(14) as i64 - 4,
148
+ self.random_below(3) as u8,
149
+ ),
150
+ 55..=58 => Call::Truncate(slot, self.random_below(12)),
151
+ 59..=62 => Call::Fstat(slot),
152
+ 63..=64 => Call::Fsync(slot),
153
+ 65..=68 => Call::Readdir(slot),
154
+ 69..=73 => Call::Stat {
155
+ base,
156
+ path: self.choose_path(paths),
157
+ follow: self.random_ratio(50, 100),
158
+ },
159
+ 74..=79 => Call::Mkdir(base, self.choose_path(paths)),
160
+ 80..=85 => Call::Rmdir(base, self.choose_path(paths)),
161
+ 86..=89 => Call::Unlink(base, self.choose_path(paths)),
162
+ 90 => Call::Fallocate(slot, self.random_below(10), self.random_below(5)),
163
+ 91 => Call::Utimes {
164
+ base,
165
+ path: self.choose_path(paths),
166
+ follow: self.random_ratio(50, 100),
167
+ },
168
+ _ => Call::Rename(base, self.choose_path(paths), self.choose_path(paths)),
169
+ }
170
+ }
171
+ }