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,21 @@
1
+ //! Paths beneath a Filesystem's root, components joined by `/` and the
2
+ //! empty path being the root: the files a Host App seeds a Tmpfs with.
3
+
4
+ /// The last name of `path`, within its parent.
5
+ pub(crate) fn name(path: &str) -> &str {
6
+ path.rsplit_once('/').map_or(path, |(_, name)| name)
7
+ }
8
+
9
+ /// Every directory above `path`, outermost first, the root left out.
10
+ pub(crate) fn ancestors(path: &str) -> impl Iterator<Item = &str> {
11
+ path.match_indices('/').map(move |(end, _)| &path[..end])
12
+ }
13
+
14
+ /// A path a Host App seeds a Filesystem with, in the form a runtime hands
15
+ /// over: no leading `/`, and no empty or `.` component.
16
+ pub(crate) fn normalize_seed(path: &str) -> String {
17
+ path.split('/')
18
+ .filter(|component| !component.is_empty() && *component != ".")
19
+ .collect::<Vec<_>>()
20
+ .join("/")
21
+ }
@@ -0,0 +1,221 @@
1
+ //! The in-memory pipes an invocation's standard streams run over, as Linux's
2
+ //! pipefs holds anonymous pipes. Any ABI a Wasm Driver encodes reads the Preamble
3
+ //! and writes a Channel through them, so none brings its own, and a pipe's two
4
+ //! ends are open files like any other.
5
+
6
+ use std::sync::{Arc, LazyLock, Mutex, PoisonError};
7
+
8
+ use crate::vfs::{
9
+ alloc_file_pseudo, Cred, Dentry, FMode, File, FileOperations, FileSystemType, FileType,
10
+ FilesystemError, Inode, InodeOperations, Kiocb, Kstat, OpenHow, PollMask, SuperBlock, VfsMount,
11
+ };
12
+
13
+ /// The one Filesystem every pipe stands on, as Linux's `pipe_mnt`. No path
14
+ /// reaches it: a pipe is only ever open.
15
+ static PIPE_MNT: LazyLock<Arc<VfsMount>> = LazyLock::new(|| {
16
+ let sb = SuperBlock::new(Arc::new(PipeFs)).expect("pipefs fills in nothing that can fail");
17
+ VfsMount::kern_mount(sb)
18
+ });
19
+
20
+ /// A pipe reports what it is and no length, as `get_pipe_inode` makes one.
21
+ struct PipeFs;
22
+
23
+ impl FileSystemType for PipeFs {
24
+ fn fill_super(self: Arc<Self>, sb: &mut SuperBlock) -> Result<(), FilesystemError> {
25
+ sb.d_make_root(Inode::new(&Kstat::new(FileType::Dir, 0), self));
26
+ Ok(())
27
+ }
28
+ }
29
+
30
+ impl InodeOperations for PipeFs {
31
+ fn getattr(&self, _dentry: &Dentry) -> Result<Kstat, FilesystemError> {
32
+ Ok(Kstat::new(FileType::Fifo, 0))
33
+ }
34
+ }
35
+
36
+ /// What a pipe holds, as Linux's `pipe_inode_info`: what was written and not
37
+ /// yet read, up to a capacity.
38
+ pub struct PipeInodeInfo {
39
+ state: Mutex<PipeState>,
40
+ capacity: usize,
41
+ }
42
+
43
+ struct PipeState {
44
+ bytes: Vec<u8>,
45
+ read: usize,
46
+ }
47
+
48
+ impl PipeInodeInfo {
49
+ /// An empty pipe holding at most `capacity` bytes.
50
+ pub fn new(capacity: usize) -> Arc<Self> {
51
+ Self::with_bytes(Vec::new(), capacity)
52
+ }
53
+
54
+ /// A pipe already holding `bytes`, as the Preamble's stream does, with no
55
+ /// room for more.
56
+ pub fn from_bytes(bytes: Vec<u8>) -> Arc<Self> {
57
+ let capacity = bytes.len();
58
+ Self::with_bytes(bytes, capacity)
59
+ }
60
+
61
+ fn with_bytes(bytes: Vec<u8>, capacity: usize) -> Arc<Self> {
62
+ Arc::new(Self {
63
+ state: Mutex::new(PipeState { bytes, read: 0 }),
64
+ capacity,
65
+ })
66
+ }
67
+
68
+ /// Everything written so far, as a Channel's capture reads it back.
69
+ pub fn contents(&self) -> Vec<u8> {
70
+ self.state().bytes.clone()
71
+ }
72
+
73
+ fn state(&self) -> std::sync::MutexGuard<'_, PipeState> {
74
+ self.state.lock().unwrap_or_else(PoisonError::into_inner)
75
+ }
76
+ }
77
+
78
+ /// What answers for either end of a pipe, as Linux's `pipeanon_fops`.
79
+ struct AnonPipe(Arc<PipeInodeInfo>);
80
+
81
+ impl FileOperations for AnonPipe {
82
+ /// A pipe has no offsets, so a read takes what comes next. A drained pipe
83
+ /// reads nothing, as a closed writer's does, and so does a read into no
84
+ /// room, as `pipe_read` answers one.
85
+ fn read_iter(&mut self, _iocb: &mut Kiocb, buf: &mut [u8]) -> Result<usize, FilesystemError> {
86
+ let mut state = self.0.state();
87
+ let start = state.read;
88
+ let rest = &state.bytes[start..];
89
+ let take = rest.len().min(buf.len());
90
+ buf[..take].copy_from_slice(&rest[..take]);
91
+ state.read += take;
92
+ Ok(take)
93
+ }
94
+
95
+ /// A pipe has no offsets, so a write lands after the last. A write past
96
+ /// the capacity keeps what fit and answers a failure, which is how a
97
+ /// capture one past its limit learns it was truncated.
98
+ fn write_iter(&mut self, _iocb: &mut Kiocb, buf: &[u8]) -> Result<usize, FilesystemError> {
99
+ let mut state = self.0.state();
100
+ let take = buf.len().min(self.0.capacity - state.bytes.len());
101
+ state.bytes.extend_from_slice(&buf[..take]);
102
+ if take < buf.len() {
103
+ return Err(FilesystemError::Other("the pipe is full".to_string()));
104
+ }
105
+ Ok(take)
106
+ }
107
+
108
+ /// Each end is ready for what it was opened for, as `pipe_poll` reads
109
+ /// `f_mode`; nothing here waits for the other end.
110
+ fn poll(&self, mode: FMode) -> PollMask {
111
+ match mode.contains(FMode::READ) {
112
+ true => PollMask::IN,
113
+ false => PollMask::OUT,
114
+ }
115
+ }
116
+
117
+ /// Neither end takes a position, as `create_pipe_files` calls
118
+ /// `stream_open` on both.
119
+ fn is_stream(&self) -> bool {
120
+ true
121
+ }
122
+ }
123
+
124
+ /// The read and the write end of `pipe`, opened as `cred`, as Linux's
125
+ /// `create_pipe_files` opens both on one inode as the calling task. Neither
126
+ /// takes a position.
127
+ pub fn create_pipe_files(pipe: &Arc<PipeInodeInfo>, cred: &Arc<Cred>) -> (File, File) {
128
+ let inode = Inode::new(&Kstat::new(FileType::Fifo, 0), Arc::new(PipeFs));
129
+ let write = alloc_file_pseudo(
130
+ inode,
131
+ &PIPE_MNT,
132
+ &OpenHow {
133
+ write: true,
134
+ ..OpenHow::default()
135
+ },
136
+ Box::new(AnonPipe(pipe.clone())),
137
+ cred,
138
+ );
139
+ let read = write.alloc_file_clone(
140
+ &OpenHow {
141
+ read: true,
142
+ ..OpenHow::default()
143
+ },
144
+ Box::new(AnonPipe(pipe.clone())),
145
+ );
146
+ (read, write)
147
+ }
148
+
149
+ #[cfg(test)]
150
+ mod tests {
151
+ use super::*;
152
+
153
+ #[test]
154
+ fn a_reader_reads_in_order_then_nothing() {
155
+ let (mut reader, _) =
156
+ create_pipe_files(&PipeInodeInfo::from_bytes(b"abc".to_vec()), &Arc::default());
157
+ let mut buf = [0; 2];
158
+ assert_eq!(
159
+ (
160
+ reader.read(&mut buf),
161
+ buf,
162
+ reader.read(&mut buf),
163
+ reader.read(&mut buf)
164
+ ),
165
+ (Ok(2), *b"ab", Ok(1), Ok(0)),
166
+ "File::read of a pipe's read end holding three bytes in steps of two must take them \
167
+ in order, then nothing"
168
+ );
169
+ }
170
+
171
+ // @behavior RT-217
172
+ #[test]
173
+ fn a_read_into_no_room_reads_nothing() {
174
+ let (mut holding, _) =
175
+ create_pipe_files(&PipeInodeInfo::from_bytes(b"a".to_vec()), &Arc::default());
176
+ let mut byte = [0];
177
+ assert_eq!(
178
+ (holding.read(&mut []), holding.read(&mut byte), byte),
179
+ (Ok(0), Ok(1), *b"a"),
180
+ "File::read of a pipe into no room must read nothing and leave its bytes for the next"
181
+ );
182
+ }
183
+
184
+ #[test]
185
+ fn a_writer_keeps_what_fits_and_fails_past_its_capacity() {
186
+ let pipe = PipeInodeInfo::new(3);
187
+ let (_, mut writer) = create_pipe_files(&pipe, &Arc::default());
188
+ assert_eq!(
189
+ (
190
+ writer.write(b"ab").ok(),
191
+ writer.write(b"cd").is_err(),
192
+ pipe.contents()
193
+ ),
194
+ (Some(2), true, b"abc".to_vec()),
195
+ "File::write past a pipe's capacity must keep what fit and fail, and \
196
+ PipeInodeInfo::contents must read what was written"
197
+ );
198
+ }
199
+
200
+ #[test]
201
+ fn a_pipe_takes_no_position() {
202
+ let (reader, writer) = create_pipe_files(&PipeInodeInfo::new(1), &Arc::default());
203
+ for end in [reader, writer] {
204
+ assert!(
205
+ end.is_stream() && !end.mode().contains(FMode::LSEEK),
206
+ "create_pipe_files must open each end as a stream with no position"
207
+ );
208
+ }
209
+ }
210
+
211
+ // @behavior RT-198
212
+ #[test]
213
+ fn a_pipe_reports_a_fifo() {
214
+ let (reader, _writer) = create_pipe_files(&PipeInodeInfo::new(1), &Arc::default());
215
+ assert_eq!(
216
+ reader.getattr().map(|metadata| metadata.file_type).ok(),
217
+ Some(FileType::Fifo),
218
+ "File::getattr of an end create_pipe_files made must report a FIFO"
219
+ );
220
+ }
221
+ }
@@ -0,0 +1,101 @@
1
+ //! What a Filesystem reading a whole directory at once holds while a listing
2
+ //! goes on, as 9p's `fid->rdir` holds what its server sent, so a caller reading in
3
+ //! small steps costs one listing, not one per step.
4
+
5
+ use std::sync::Arc;
6
+
7
+ use crate::vfs::{Cred, Dentry, DirContext, DirEntry, FileType, FilesystemError};
8
+
9
+ /// The entries a listing began with, `.` and `..` first, on the open directory
10
+ /// until it reaches its end, counted against who holds them.
11
+ #[derive(Default)]
12
+ pub struct Rdir(Option<Listing>);
13
+
14
+ struct Listing {
15
+ entries: Vec<DirEntry>,
16
+ /// Gives the entries' bytes back once the listing goes.
17
+ _charge: Charge,
18
+ }
19
+
20
+ /// Bytes a listing counts for as long as it holds them, as shmem pairs
21
+ /// `shmem_acct_size` with `shmem_unacct_size`.
22
+ struct Charge {
23
+ cred: Arc<Cred>,
24
+ bytes: usize,
25
+ }
26
+
27
+ impl Charge {
28
+ fn account(cred: &Arc<Cred>, entries: &[DirEntry]) -> Result<Self, FilesystemError> {
29
+ let names: usize = entries.iter().map(|entry| entry.name.capacity()).sum();
30
+ let bytes = size_of_val(entries) + names;
31
+ cred.security_vm_enough_memory(bytes)?;
32
+ Ok(Self {
33
+ cred: cred.clone(),
34
+ bytes,
35
+ })
36
+ }
37
+ }
38
+
39
+ impl Drop for Charge {
40
+ fn drop(&mut self) {
41
+ self.cred.vm_unacct_memory(self.bytes);
42
+ }
43
+ }
44
+
45
+ impl Rdir {
46
+ /// Hand `ctx` the directory from its position. A listing from the start
47
+ /// reads the directory through `read`, then stats it and what stands above
48
+ /// it for `.` and `..`, as 9p and FUSE take those from their server and
49
+ /// `ovl_cache_update` asks `vfs_getattr`; a failure of either answers before
50
+ /// any entry. A listing going on reads what that listing began with. Those
51
+ /// entries count against the caller from the moment they are read until the
52
+ /// listing ends, as `ovl_cache_get` and `v9fs_alloc_rdir_buf` allocate before
53
+ /// the first `dir_emit`: past what a security module allows, the listing
54
+ /// answers out of memory before it hands any entry.
55
+ pub fn iterate(
56
+ &mut self,
57
+ dentry: &Dentry,
58
+ ctx: &mut DirContext<'_>,
59
+ read: impl FnOnce() -> Result<Vec<DirEntry>, FilesystemError>,
60
+ ) -> Result<(), FilesystemError> {
61
+ let listing = match self.0.take().filter(|_| ctx.pos() != 0) {
62
+ Some(held) => held,
63
+ None => {
64
+ let read = read()?;
65
+ let mut entries = dots(dentry)?;
66
+ entries.extend(read);
67
+ entries.shrink_to_fit();
68
+ let _charge = Charge::account(ctx.cred(), &entries)?;
69
+ Listing { entries, _charge }
70
+ }
71
+ };
72
+ if !emit_from(ctx, &listing.entries) {
73
+ self.0 = Some(listing);
74
+ }
75
+ Ok(())
76
+ }
77
+ }
78
+
79
+ /// `.` and `..` as a stat of `dentry` and of what stands above it reports them,
80
+ /// `..` naming `dentry` itself at the root.
81
+ fn dots(dentry: &Dentry) -> Result<Vec<DirEntry>, FilesystemError> {
82
+ let dot = dentry.getattr()?;
83
+ let dotdot = dentry.parent().getattr()?;
84
+ Ok(vec![
85
+ DirEntry::new(".", FileType::Dir).with_identity(dot.dev, dot.ino),
86
+ DirEntry::new("..", FileType::Dir).with_identity(dotdot.dev, dotdot.ino),
87
+ ])
88
+ }
89
+
90
+ /// Hand `ctx` `entries` from where it stands, each at its index, answering
91
+ /// whether it took them all.
92
+ fn emit_from(ctx: &mut DirContext<'_>, entries: &[DirEntry]) -> bool {
93
+ let skip = usize::try_from(ctx.pos()).unwrap_or(usize::MAX);
94
+ entries.iter().skip(skip).all(|entry| {
95
+ let took = ctx.dir_emit(entry);
96
+ if took {
97
+ ctx.set_pos(ctx.pos() + 1);
98
+ }
99
+ took
100
+ })
101
+ }
@@ -0,0 +1,32 @@
1
+ //! A file made with no name and named once whole, as tmpfs's
2
+ //! `shmem_tmpfile` and `linkat` let overlayfs copy a file up unseen, and
3
+ //! the times set on what it copies.
4
+
5
+ use super::super::{FilesystemError, Iattr};
6
+ use super::{lock, Content, Node, Tmpfs, TmpfsFile};
7
+
8
+ impl Tmpfs {
9
+ /// An empty file with no name yet, as `O_TMPFILE` opens one. Dropped
10
+ /// unnamed, it gives its room back.
11
+ pub(crate) fn tmpfile(&self) -> Result<TmpfsFile, FilesystemError> {
12
+ Ok(TmpfsFile {
13
+ data: Content::create(&self.tree)?,
14
+ })
15
+ }
16
+
17
+ /// Name `file` `name` in this directory, as `simple_link` names a file
18
+ /// `O_TMPFILE` opened once `vfs_link` found the name free.
19
+ pub(crate) fn link(&self, file: &TmpfsFile, name: &str) -> Result<(), FilesystemError> {
20
+ self.dir.state().insert(name, Node::File(file.data.clone()));
21
+ Ok(())
22
+ }
23
+ }
24
+
25
+ impl TmpfsFile {
26
+ /// Set the times `attr` names on a file with no name yet, as
27
+ /// `shmem_setattr` takes them where overlayfs sets the temporary file's
28
+ /// times before naming it.
29
+ pub(crate) fn setattr(&self, attr: &Iattr) {
30
+ lock(&self.data).times.apply(attr);
31
+ }
32
+ }
@@ -0,0 +1,85 @@
1
+ //! The whiteout a Tmpfs holds for an Overlay, and the moves that place
2
+ //! one, as tmpfs's `shmem_whiteout` makes a character device 0:0 and
3
+ //! `shmem_rename2` takes `RENAME_NOREPLACE`, `RENAME_EXCHANGE`, and
4
+ //! `RENAME_WHITEOUT`.
5
+
6
+ use std::sync::Arc;
7
+
8
+ use super::super::FilesystemError;
9
+ use super::{Node, Space, Tmpfs};
10
+ use crate::vfs::RenameFlag;
11
+
12
+ /// A whiteout, counting one inode for as long as it stands, as the
13
+ /// character device tmpfs makes for one does.
14
+ pub(super) struct Whiteout {
15
+ space: Arc<Space>,
16
+ }
17
+
18
+ impl Drop for Whiteout {
19
+ fn drop(&mut self) {
20
+ self.space.inodes.give(1);
21
+ }
22
+ }
23
+
24
+ impl Tmpfs {
25
+ fn new_whiteout(&self) -> Result<Node, FilesystemError> {
26
+ self.tree.space.inodes.take(1)?;
27
+ Ok(Node::Whiteout {
28
+ _inode: Arc::new(Whiteout {
29
+ space: self.tree.space.clone(),
30
+ }),
31
+ })
32
+ }
33
+
34
+ /// Whether a whiteout stands at `name`, as overlayfs's
35
+ /// `ovl_is_whiteout` reads a character device 0:0.
36
+ pub(crate) fn is_whiteout(&self, name: &str) -> bool {
37
+ matches!(self.dir.get(name), Ok(Node::Whiteout { .. }))
38
+ }
39
+
40
+ /// Make a whiteout at `name`, as `shmem_mknod` makes the character
41
+ /// device 0:0 `vfs_whiteout` asks for.
42
+ pub(crate) fn whiteout(&self, name: &str) -> Result<(), FilesystemError> {
43
+ let whiteout = self.new_whiteout()?;
44
+ self.dir.state().insert(name, whiteout);
45
+ Ok(())
46
+ }
47
+
48
+ /// Move `from` to `to` in `target` as `shmem_rename2` does with
49
+ /// `flag`, the entry taking all it holds, so no name beneath it
50
+ /// changes. Without one, what stands at `to` goes as rename(2)
51
+ /// replaces it, once the VFS found the move fits what stands on each
52
+ /// side.
53
+ pub(super) fn rename_at(
54
+ &self,
55
+ from: &str,
56
+ target: &Tmpfs,
57
+ to: &str,
58
+ flag: Option<RenameFlag>,
59
+ ) -> Result<(), FilesystemError> {
60
+ let (from_dir, to_dir) = (&self.dir, &target.dir);
61
+ let moving = from_dir.get(from)?;
62
+ let replaced = to_dir.get(to).ok();
63
+ if flag == Some(RenameFlag::Exchange) {
64
+ let replaced = replaced.ok_or(FilesystemError::NotFound)?;
65
+ from_dir.state().exchange(from, replaced);
66
+ to_dir.state().exchange(to, moving);
67
+ return Ok(());
68
+ }
69
+ if let Some(Node::Directory(dir)) = &replaced {
70
+ if !dir.state().entries.is_empty() {
71
+ return Err(FilesystemError::DirectoryNotEmpty);
72
+ }
73
+ }
74
+ let left = match flag {
75
+ Some(RenameFlag::Whiteout) => Some(self.new_whiteout()?),
76
+ _ => None,
77
+ };
78
+ match left {
79
+ Some(left) => from_dir.state().exchange(from, left),
80
+ None => from_dir.state().take(from),
81
+ }
82
+ to_dir.state().insert(to, moving);
83
+ Ok(())
84
+ }
85
+ }
@@ -0,0 +1,22 @@
1
+ //! A directory's extended attributes, as tmpfs keeps the `trusted.*`
2
+ //! ones overlayfs marks a directory opaque with.
3
+
4
+ use super::super::FilesystemError;
5
+ use super::Tmpfs;
6
+
7
+ impl Tmpfs {
8
+ /// The value of the attribute `name` on this directory, as `getxattr`
9
+ /// reads it, or `None` where it is not set.
10
+ pub(crate) fn getxattr(&self, name: &str) -> Option<Vec<u8>> {
11
+ self.dir.state().xattrs.get(name).cloned()
12
+ }
13
+
14
+ /// Set the attribute `name` on this directory, as `setxattr` does.
15
+ pub(crate) fn setxattr(&self, name: &str, value: &[u8]) -> Result<(), FilesystemError> {
16
+ self.dir
17
+ .state()
18
+ .xattrs
19
+ .insert(name.to_string(), value.to_vec());
20
+ Ok(())
21
+ }
22
+ }