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,429 @@
1
+ //! What a name stands for, apart from where it stands, as Linux's
2
+ //! `struct inode` is what a dentry is instantiated with.
3
+
4
+ use std::any::Any;
5
+ use std::ops::BitOr;
6
+ use std::sync::atomic::{AtomicBool, Ordering};
7
+ use std::sync::{Arc, Mutex, MutexGuard, OnceLock, PoisonError, Weak};
8
+
9
+ use super::dentry::Dentry;
10
+ use super::file::FileOperations;
11
+ use super::{FileType, FilesystemError, Iattr, Kstat, OpenHow};
12
+ use crate::locking::{RwSemaphore, RwSemaphoreGuard};
13
+
14
+ /// What an `atomic_open` did with the name it was asked to open, as Linux's
15
+ /// `->atomic_open` ends in `finish_open` or `finish_no_open`. `created` says
16
+ /// whether it made the file, as `FMODE_CREATED` does: one it found standing is
17
+ /// opened as any open of an entry that stood, and one it made asks for nothing
18
+ /// but the open.
19
+ #[non_exhaustive]
20
+ pub enum Finish {
21
+ /// It opened the file too, as `finish_open` does.
22
+ Open {
23
+ file: Box<dyn FileOperations>,
24
+ created: bool,
25
+ },
26
+ /// It found the entry stored under the other spelling `name` and opened
27
+ /// it, as `finish_open` is handed the dentry `d_add_ci` returned, for the
28
+ /// VFS to record `inode` on that spelling's dentry.
29
+ Spelled {
30
+ file: Box<dyn FileOperations>,
31
+ name: String,
32
+ inode: Arc<Inode>,
33
+ },
34
+ /// It left the open to the VFS, as `finish_no_open` hands the dentry back.
35
+ NoOpen { created: bool },
36
+ /// It looked the name up and left the rest to the VFS, as `finish_no_open`
37
+ /// hands back the dentry a lookup returned, for the VFS to record as it
38
+ /// records what `lookup` answers.
39
+ Lookup(Lookup),
40
+ }
41
+
42
+ /// What one name in a directory turned out to be, as `->lookup` answers.
43
+ pub enum Lookup {
44
+ /// The entry standing at the name, for the runtime to record on the
45
+ /// dentry, as `d_splice_alias` records it. A Filesystem naming one entry by
46
+ /// more than one name hands the same one, as `iget5_locked` finds it, and so
47
+ /// shares its lock.
48
+ Found(Arc<Inode>),
49
+ /// Nothing stands at the name, as a negative dentry records.
50
+ Absent,
51
+ /// The entry the name reaches, stored under the other spelling `name`, as
52
+ /// `d_add_ci` is handed the inode found and the case-exact name, so a name
53
+ /// a host reaches by more than one spelling has one dentry, one lock, and
54
+ /// one spelling a security hook judges.
55
+ Spelled { name: String, inode: Arc<Inode> },
56
+ }
57
+
58
+ /// What one entry of a Filesystem answers: a directory for the names in it,
59
+ /// a link for where it points, and any entry for itself, as Linux's
60
+ /// `inode_operations` hang from each inode. An operation an entry does not
61
+ /// have answers by default as Linux's VFS answers a NULL in its table. One
62
+ /// Filesystem may wrap another and answer by
63
+ /// delegating, wrapping each entry its `lookup` finds, so it sees every call
64
+ /// on the way through. It forwards every operation the one beneath keeps, or
65
+ /// the default answers in that one's place. A directory keeps its identity its
66
+ /// own while it is open, as an open descriptor keeps a host's inode from being
67
+ /// reused.
68
+ ///
69
+ /// The VFS walks a path one name at a time and follows a link
70
+ /// itself, as the VFS above a filesystem does, so a link never leads a call
71
+ /// out of the directory a descriptor opened. Every call hands the dentry it
72
+ /// is about; a Filesystem answering by path reads `Dentry::path_raw`, so a
73
+ /// directory moved through the runtime is asked at its new path. A call
74
+ /// making, removing, or moving a name runs under its directory's lock.
75
+ pub trait InodeOperations: Any + Send + Sync {
76
+ /// Whether a call may do what `mask` asks of this entry, which `dentry`
77
+ /// names, as `->permission` answers `inode_permission`. The VFS asks where
78
+ /// Linux does and holds no mode to judge by, so an entry without it allows
79
+ /// the call, and the backend answers once it is made, as FUSE leaves a
80
+ /// server to without `default_permissions`.
81
+ fn permission(&self, _dentry: &Dentry, _mask: Mask) -> Result<(), FilesystemError> {
82
+ Ok(())
83
+ }
84
+
85
+ /// What this entry, which `dentry` names, reports of itself, as
86
+ /// `getattr` is handed the path it answers for, even once its name is
87
+ /// gone and only an open file holds it, as `fstat` asks the same. A
88
+ /// Filesystem keeps what that answer takes on the `Inode`. A link reports
89
+ /// itself.
90
+ fn getattr(&self, dentry: &Dentry) -> Result<Kstat, FilesystemError>;
91
+
92
+ /// Apply `attr` to this entry, even once its name is gone and only an
93
+ /// open file holds it, as `ftruncate`, `open` with `O_TRUNC`, and
94
+ /// `utimensat` reach `->setattr`. One whose `open` already truncated
95
+ /// answers success where `attr.open` says so. An entry without it refuses a
96
+ /// size as invalid input and times as unsupported.
97
+ fn setattr(&self, _dentry: &Dentry, attr: &Iattr) -> Result<(), FilesystemError> {
98
+ match attr.size {
99
+ Some(_) => Err(FilesystemError::InvalidInput),
100
+ None => Err(FilesystemError::Unsupported),
101
+ }
102
+ }
103
+
104
+ /// What stands at the name `dentry` carries in this directory. The
105
+ /// runtime asks once for a dentry naming nothing yet, and an entry
106
+ /// without it is no directory to walk through. A directory found answers as
107
+ /// an `Inode::new` carrying its identity, or a Policy cannot tell it from
108
+ /// one made in its place.
109
+ fn lookup(&self, _dentry: &Dentry) -> Result<Lookup, FilesystemError> {
110
+ Err(FilesystemError::NotADirectory)
111
+ }
112
+
113
+ /// Where this link, which `dentry` names, points, as `get_link` answers,
114
+ /// for the VFS to follow. Anything else refuses as `readlinkat` does.
115
+ fn get_link(&self, _dentry: &Dentry) -> Result<String, FilesystemError> {
116
+ Err(FilesystemError::InvalidInput)
117
+ }
118
+
119
+ /// Open this entry, which `dentry` names, as `how` asks, as `f_op->open`
120
+ /// makes the open file reading its `f_flags`. The VFS empties a file an
121
+ /// open asks to truncate through `setattr` afterwards, and refuses a link
122
+ /// before it asks. An entry without it opens as Linux's opens with no
123
+ /// `f_op->open`, answering each call on the open file as `FileOperations`
124
+ /// does by default.
125
+ fn open(
126
+ &self,
127
+ _dentry: &Dentry,
128
+ _how: &OpenHow,
129
+ ) -> Result<Box<dyn FileOperations>, FilesystemError> {
130
+ Ok(Box::new(EmptyFops))
131
+ }
132
+
133
+ /// Make the file `dentry` names in this directory and record it on `dentry`,
134
+ /// as `->create` calls `d_instantiate`. A directory without it answers
135
+ /// EACCES, as Linux's VFS does.
136
+ fn create(&self, _dentry: &Dentry) -> Result<(), FilesystemError> {
137
+ Err(FilesystemError::PermissionDenied)
138
+ }
139
+
140
+ /// Look up, make, and open the last name of an open in one step, as
141
+ /// `->atomic_open` takes `open_flag` for a backend that does it at once. The
142
+ /// VFS asks it for a `dentry` still in lookup, which is looked up first, and
143
+ /// for a negative one an open may make; `how` asks to make a file only
144
+ /// where the open may, and for a directory where the name ends in a slash,
145
+ /// as Linux adds `O_DIRECTORY` for `LOOKUP_DIRECTORY`. A directory without it looks the name up, then makes
146
+ /// the file through `create` and leaves the open to the VFS, which asks
147
+ /// `may_open` and the security modules first, as Linux's `lookup_open` calls
148
+ /// `->lookup`, then `->create`, and `do_open` opens what it made.
149
+ fn atomic_open(&self, dentry: &Dentry, how: &OpenHow) -> Result<Finish, FilesystemError> {
150
+ if dentry.in_lookup() {
151
+ let found = self.lookup(dentry)?;
152
+ if !how.create || !matches!(found, Lookup::Absent) {
153
+ return Ok(Finish::Lookup(found));
154
+ }
155
+ }
156
+ if !how.create {
157
+ return Ok(Finish::NoOpen { created: false });
158
+ }
159
+ self.create(dentry)?;
160
+ Ok(Finish::NoOpen { created: true })
161
+ }
162
+
163
+ /// Make the directory `dentry` names in this one, recording it on `dentry`
164
+ /// where it can, as `->mkdir` calls `d_instantiate`. One that records nothing
165
+ /// answers for the name at the next walk, through its `revalidate` or by
166
+ /// keeping no name, as Linux leaves a negative dentry to its Filesystem. A
167
+ /// directory without it, or without any change below, answers EPERM, as
168
+ /// Linux's VFS does.
169
+ fn mkdir(&self, _dentry: &Dentry) -> Result<(), FilesystemError> {
170
+ Err(FilesystemError::NotPermitted)
171
+ }
172
+
173
+ /// Give the entry `old` names another name, the one `dentry` carries in
174
+ /// this directory. A directory without it answers EPERM, as Linux's VFS
175
+ /// does.
176
+ fn link(&self, _old: &Dentry, _dentry: &Dentry) -> Result<(), FilesystemError> {
177
+ Err(FilesystemError::NotPermitted)
178
+ }
179
+
180
+ /// Make the link `dentry` names in this directory, pointing at `target`.
181
+ /// A directory without it answers EPERM, as Linux's VFS does.
182
+ fn symlink(&self, _dentry: &Dentry, _target: &str) -> Result<(), FilesystemError> {
183
+ Err(FilesystemError::NotPermitted)
184
+ }
185
+
186
+ /// Remove the empty directory `dentry` names in this one. A directory
187
+ /// without it answers EPERM, as Linux's VFS does.
188
+ fn rmdir(&self, _dentry: &Dentry) -> Result<(), FilesystemError> {
189
+ Err(FilesystemError::NotPermitted)
190
+ }
191
+
192
+ /// Remove the file `dentry` names in this directory. A directory without
193
+ /// it answers EPERM, as Linux's VFS does.
194
+ fn unlink(&self, _dentry: &Dentry) -> Result<(), FilesystemError> {
195
+ Err(FilesystemError::NotPermitted)
196
+ }
197
+
198
+ /// Move the entry `from` names in this directory to `to` in `to_dir`,
199
+ /// refusing what rename(2) refuses at `to`. The runtime has already
200
+ /// checked what stands at `from` and never hands a move onto its own
201
+ /// name, as Linux's VFS does before `inode_operations.rename`. It hands
202
+ /// only a `to_dir` of the same mount, so no two instances meet here, as
203
+ /// `filename_renameat2` answers EXDEV first. One that takes no `flag`
204
+ /// refuses any as invalid input, as Linux's filesystems refuse a `renameat2`
205
+ /// flag they do not know. A directory without it answers EPERM, as
206
+ /// Linux's VFS does.
207
+ fn rename(
208
+ &self,
209
+ _from: &Dentry,
210
+ _to_dir: &dyn InodeOperations,
211
+ _to: &Dentry,
212
+ _flag: Option<RenameFlag>,
213
+ ) -> Result<(), FilesystemError> {
214
+ Err(FilesystemError::NotPermitted)
215
+ }
216
+ }
217
+
218
+ /// What a call asks of an entry, as Linux's `MAY_*` bits in the mask
219
+ /// `inode_permission` takes.
220
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
221
+ pub struct Mask(u8);
222
+
223
+ impl Mask {
224
+ /// Asking nothing, as an `acc_mode` of 0.
225
+ pub(crate) const NONE: Self = Self(0);
226
+ /// Passing through a directory, as `MAY_EXEC`.
227
+ pub const EXEC: Self = Self(1);
228
+ /// Changing it, as `MAY_WRITE`.
229
+ pub const WRITE: Self = Self(1 << 1);
230
+ /// Reading it, as `MAY_READ`.
231
+ pub const READ: Self = Self(1 << 2);
232
+ /// Opening it, as `MAY_OPEN`.
233
+ pub const OPEN: Self = Self(1 << 3);
234
+ /// Changing it only at its end, as `MAY_APPEND`, which comes with `WRITE`
235
+ /// so a module can tell an append from any other write.
236
+ pub const APPEND: Self = Self(1 << 4);
237
+
238
+ /// Whether this holds all of `mask`.
239
+ pub fn contains(self, mask: Self) -> bool {
240
+ self.0 & mask.0 == mask.0
241
+ }
242
+ }
243
+
244
+ impl BitOr for Mask {
245
+ type Output = Self;
246
+
247
+ fn bitor(self, other: Self) -> Self {
248
+ Self(self.0 | other.0)
249
+ }
250
+ }
251
+
252
+ /// What a move asks of its target, as `renameat2`'s flags.
253
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
254
+ #[non_exhaustive]
255
+ pub enum RenameFlag {
256
+ /// Refuse the move when anything stands at the target.
257
+ NoReplace,
258
+ /// Swap the source and the target, both of which must stand.
259
+ Exchange,
260
+ /// Leave a whiteout where the source stood.
261
+ Whiteout,
262
+ }
263
+
264
+ /// What an entry without `open` opens as, as Linux's `empty_fops` is a file with
265
+ /// no operations.
266
+ pub(super) struct EmptyFops;
267
+
268
+ impl FileOperations for EmptyFops {}
269
+
270
+ /// What an entry is, apart from where it stands: the device and inode
271
+ /// numbers its Filesystem names it by, as Linux reads `i_ino` and `s_dev`.
272
+ #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
273
+ pub struct Identity {
274
+ dev: u64,
275
+ ino: u64,
276
+ }
277
+
278
+ impl Identity {
279
+ /// The identity `stat` names, `None` where its inode of zero reports none.
280
+ pub fn of(stat: &Kstat) -> Option<Self> {
281
+ Self::from_numbers(stat.dev, stat.ino)
282
+ }
283
+
284
+ /// The identity `dev` and `ino` name, `None` where an inode of zero
285
+ /// reports none.
286
+ pub(crate) fn from_numbers(dev: u64, ino: u64) -> Option<Self> {
287
+ (ino != 0).then_some(Self { dev, ino })
288
+ }
289
+
290
+ /// `stat`, naming this identity.
291
+ pub fn onto(self, stat: Kstat) -> Kstat {
292
+ stat.with_identity(self.dev, self.ino)
293
+ }
294
+ }
295
+
296
+ /// One entry of a Filesystem, as a lookup finds it for the runtime to record
297
+ /// on the dentry naming it: what it is, and what answers for it, as Linux's
298
+ /// inode carries `i_mode`, `i_ino`, and `i_op`.
299
+ pub struct Inode {
300
+ /// What kind of entry it is.
301
+ kind: FileType,
302
+ /// `None` where the Filesystem names no inode number.
303
+ identity: Option<Identity>,
304
+ ops: Arc<dyn InodeOperations>,
305
+ /// What the Filesystem keeps on the entry once it needs it, set once,
306
+ /// as Linux's `i_private`, reached the same however its operations are
307
+ /// wrapped.
308
+ private: OnceLock<Arc<dyn Any + Send + Sync>>,
309
+ /// The lock calls on the entry run under, as Linux's `i_rwsem`.
310
+ rwsem: Arc<RwSemaphore>,
311
+ /// Whether the directory was removed or replaced through the VFS, as
312
+ /// Linux sets `S_DEAD` in `i_flags`.
313
+ dead: AtomicBool,
314
+ /// The dentry a directory stands at, as Linux keeps a directory's one
315
+ /// dentry in `i_dentry`.
316
+ dentry: Mutex<Weak<Dentry>>,
317
+ }
318
+
319
+ impl Inode {
320
+ /// The entry `stat` reports, answered for by `ops`.
321
+ pub fn new(stat: &Kstat, ops: Arc<dyn InodeOperations>) -> Arc<Self> {
322
+ Self::of_kind(stat.file_type, Identity::of(stat), ops)
323
+ }
324
+
325
+ /// This entry answered for by `ops`, as a Filesystem wrapping another
326
+ /// wraps each entry that one's lookup finds.
327
+ pub fn with_ops(&self, ops: Arc<dyn InodeOperations>) -> Arc<Self> {
328
+ Self::of_kind(self.kind, self.identity, ops)
329
+ }
330
+
331
+ fn of_kind(
332
+ kind: FileType,
333
+ identity: Option<Identity>,
334
+ ops: Arc<dyn InodeOperations>,
335
+ ) -> Arc<Self> {
336
+ Arc::new(Self {
337
+ kind,
338
+ identity,
339
+ ops,
340
+ private: OnceLock::new(),
341
+ rwsem: Arc::default(),
342
+ dead: AtomicBool::new(false),
343
+ dentry: Mutex::default(),
344
+ })
345
+ }
346
+
347
+ /// The dentry other than `dentry` this directory already stands at, as
348
+ /// `__d_find_dir_alias` finds it, or none with this entry recorded on
349
+ /// `dentry`, as `__d_add` records it. Both run under one lock, as
350
+ /// `d_splice_alias` holds `i_lock`, so two lookups of one directory never
351
+ /// each find none, and none finds `dentry` before a walk can.
352
+ pub(super) fn find_alias_or_add(self: &Arc<Self>, dentry: &Dentry) -> Option<Arc<Dentry>> {
353
+ let mut held = self.dentry();
354
+ if let Some(found) = held.upgrade() {
355
+ if !std::ptr::eq(found.as_ref(), dentry) {
356
+ return Some(found);
357
+ }
358
+ }
359
+ dentry.__instantiate(self.clone(), &mut held);
360
+ dentry.rehash();
361
+ None
362
+ }
363
+
364
+ /// Record this entry on `dentry` and let a walk find it by its name, as
365
+ /// `d_add` does under `i_lock`.
366
+ pub(super) fn add_hashed(self: &Arc<Self>, dentry: &Dentry) {
367
+ let mut held = self.dentry();
368
+ dentry.__instantiate(self.clone(), &mut held);
369
+ dentry.rehash();
370
+ }
371
+
372
+ pub(super) fn dentry(&self) -> MutexGuard<'_, Weak<Dentry>> {
373
+ self.dentry.lock().unwrap_or_else(PoisonError::into_inner)
374
+ }
375
+
376
+ /// Hold it shared where nothing holds it exclusive, without waiting, as
377
+ /// Linux's `inode_trylock_shared`.
378
+ pub(super) fn trylock_shared(&self) -> Option<RwSemaphoreGuard> {
379
+ self.rwsem.down_read_trylock()
380
+ }
381
+
382
+ /// Hold it shared, as Linux's `inode_lock_shared` holds the directory it
383
+ /// looks a name up in, so no change through the VFS lands between the
384
+ /// name's dentry and what the Filesystem answers for it.
385
+ pub(super) fn lock_shared(&self) -> Result<RwSemaphoreGuard, FilesystemError> {
386
+ Ok(self.rwsem.down_read()?)
387
+ }
388
+
389
+ /// Hold it exclusive, as Linux's `inode_lock` holds a directory while a
390
+ /// name in it is made, removed, or moved.
391
+ pub(super) fn lock(&self) -> Result<RwSemaphoreGuard, FilesystemError> {
392
+ Ok(self.rwsem.down_write()?)
393
+ }
394
+
395
+ /// Whether `held` holds its lock, as Linux's `inode_is_locked` asks.
396
+ pub(super) fn is_locked_by(&self, held: &RwSemaphoreGuard) -> bool {
397
+ held.holds(&self.rwsem)
398
+ }
399
+
400
+ /// Whether the directory was removed or replaced, as `IS_DEADDIR`.
401
+ pub(super) fn is_deaddir(&self) -> bool {
402
+ self.dead.load(Ordering::SeqCst)
403
+ }
404
+
405
+ /// The directory was removed or replaced, so nothing is made in it any
406
+ /// more.
407
+ pub(super) fn mark_dead(&self) {
408
+ self.dead.store(true, Ordering::SeqCst);
409
+ }
410
+
411
+ pub fn kind(&self) -> FileType {
412
+ self.kind
413
+ }
414
+
415
+ pub fn identity(&self) -> Option<Identity> {
416
+ self.identity
417
+ }
418
+
419
+ /// What answers for the entry, as Linux's `i_op`.
420
+ pub fn ops(&self) -> &Arc<dyn InodeOperations> {
421
+ &self.ops
422
+ }
423
+
424
+ /// What the Filesystem keeps on the entry, set once, as Linux's
425
+ /// `i_private`.
426
+ pub fn private(&self) -> &OnceLock<Arc<dyn Any + Send + Sync>> {
427
+ &self.private
428
+ }
429
+ }
@@ -0,0 +1,217 @@
1
+ //! A test's way to ask a Filesystem about a path, as kernel code reaches a
2
+ //! file by name through `kern_path`: the VFS resolves the path to a
3
+ //! dentry, as it does for every call, and the Filesystem is asked about
4
+ //! that dentry, a change going through the VFS's own `vfs_*` helpers as
5
+ //! kernel code's does, so a test sees what the Filesystem answers beneath
6
+ //! the VFS. It takes no directory's lock, so a test holding one can
7
+ //! still reach the Filesystem through it.
8
+
9
+ use std::sync::Arc;
10
+
11
+ use super::cred::Cred;
12
+ use super::dentry::Dentry;
13
+ use super::namei::{
14
+ atomic_open, do_file_open, filename_lookup, filename_parentat, lookup_one_qstr_excl,
15
+ may_create_dentry, vfs_mkdir, vfs_rename, vfs_rmdir, vfs_unlink, Nameidata, RenameData,
16
+ };
17
+ use super::path::{Path, Start};
18
+ use super::{
19
+ build_open_flags, DirEntry, File, FileOperations, FileSystemType, FileType, FilesystemError,
20
+ Iattr, Kstat, OpenHow, SuperBlock,
21
+ };
22
+ use crate::mount::{Access, Mount};
23
+
24
+ /// Open `path` beneath `start` for `cred` as `how` asks, as kernel code opens a
25
+ /// file by name through `filp_open`, which builds the flags `do_file_open`
26
+ /// takes.
27
+ pub(crate) fn filp_open(
28
+ cred: &Arc<Cred>,
29
+ start: &impl Start,
30
+ path: &str,
31
+ how: &OpenHow,
32
+ ) -> Result<File, FilesystemError> {
33
+ do_file_open(cred, start, path, &build_open_flags(how)?)
34
+ }
35
+
36
+ /// A directory of a Filesystem reached through the VFS.
37
+ #[derive(Clone)]
38
+ pub(crate) struct KernPath(Path);
39
+
40
+ /// The root of `filesystem`, mounted read-write.
41
+ pub(crate) fn kern_path(filesystem: Arc<dyn FileSystemType>) -> KernPath {
42
+ let filesystem = SuperBlock::new(filesystem).expect("the instance must stand");
43
+ let mount = Mount::new("/data", filesystem, Access::ReadWrite).expect("/data is a guest path");
44
+ KernPath(Path::root(mount.mnt()))
45
+ }
46
+
47
+ impl KernPath {
48
+ /// `path` resolved beneath this directory, a link at its end left as
49
+ /// it stands.
50
+ fn resolve(&self, path: &str) -> Result<Nameidata, FilesystemError> {
51
+ filename_lookup(&Arc::default(), &self.0, path, false)
52
+ }
53
+
54
+ /// The directory holding the last name of `path`, and that name's dentry,
55
+ /// whether or not anything stands there.
56
+ fn parent(&self, path: &str) -> Result<(Nameidata, Arc<Dentry>), FilesystemError> {
57
+ let nd = filename_parentat(&Arc::default(), &self.0, path)?;
58
+ let child = lookup_one_qstr_excl(nd.mnt.sb(), nd.dentry(), &nd.last)?;
59
+ Ok((nd, child))
60
+ }
61
+
62
+ pub(crate) fn open(
63
+ &self,
64
+ path: &str,
65
+ how: &OpenHow,
66
+ ) -> Result<Box<dyn FileOperations>, FilesystemError> {
67
+ let child = self.resolve(path)?.path()?;
68
+ // A link opens as itself nowhere, as `may_open` refuses one first.
69
+ if child.kind() == Ok(FileType::Symlink) {
70
+ return Err(FilesystemError::FilesystemLoop);
71
+ }
72
+ let file = child.open(how)?;
73
+ if how.truncate {
74
+ child
75
+ .inode()
76
+ .ok_or(FilesystemError::NotFound)?
77
+ .ops()
78
+ .setattr(&child, &Iattr::open_truncate())?;
79
+ }
80
+ Ok(file)
81
+ }
82
+
83
+ /// Open the file at `path`, made first where nothing stands.
84
+ pub(crate) fn open_or_create(
85
+ &self,
86
+ path: &str,
87
+ how: &OpenHow,
88
+ ) -> Result<Box<dyn FileOperations>, FilesystemError> {
89
+ match self.vfs_lstat(path) {
90
+ Err(FilesystemError::NotFound) => self.create(path, how),
91
+ _ => self.open(path, how),
92
+ }
93
+ }
94
+
95
+ /// Make the file at `path` and open it, as `->atomic_open` does.
96
+ pub(crate) fn create(
97
+ &self,
98
+ path: &str,
99
+ how: &OpenHow,
100
+ ) -> Result<Box<dyn FileOperations>, FilesystemError> {
101
+ let (nd, child) = self.parent(path)?;
102
+ may_create_dentry(&nd.cred, nd.mnt.sb(), nd.dentry(), &child)?;
103
+ let how = OpenHow {
104
+ create: true,
105
+ ..*how
106
+ };
107
+ let opened = atomic_open(nd.mnt.sb(), nd.dentry(), &child, &how)?;
108
+ match opened.file {
109
+ Some(file) => Ok(file),
110
+ None => opened.spliced.unwrap_or(child).open(&how),
111
+ }
112
+ }
113
+
114
+ /// Truncate or extend the file at `path`, as `->setattr` is asked.
115
+ pub(crate) fn truncate(&self, path: &str, len: u64) -> Result<(), FilesystemError> {
116
+ self.setattr(path, &Iattr::size(len))
117
+ }
118
+
119
+ /// Hand `attr` to what `path` names, as `->setattr` is asked.
120
+ pub(crate) fn setattr(&self, path: &str, attr: &Iattr) -> Result<(), FilesystemError> {
121
+ let child = self.resolve(path)?.path()?;
122
+ child
123
+ .inode()
124
+ .ok_or(FilesystemError::NotFound)?
125
+ .ops()
126
+ .setattr(&child, attr)
127
+ }
128
+
129
+ pub(crate) fn open_dir(&self, path: &str) -> Result<Self, FilesystemError> {
130
+ let dentry = self.resolve(path)?.path()?;
131
+ match dentry.kind()? {
132
+ FileType::Symlink => Err(FilesystemError::FilesystemLoop),
133
+ FileType::Dir => {
134
+ dentry.getattr()?;
135
+ Ok(Self(self.0.with_dentry(dentry)))
136
+ }
137
+ _ => Err(FilesystemError::NotADirectory),
138
+ }
139
+ }
140
+
141
+ /// What the directory reports of itself, as `vfs_getattr`.
142
+ pub(crate) fn vfs_getattr(&self) -> Result<Kstat, FilesystemError> {
143
+ self.0.dentry().getattr()
144
+ }
145
+
146
+ /// What `path` names, a link reported as itself, as `vfs_lstat`; the
147
+ /// directory itself for the empty path.
148
+ pub(crate) fn vfs_lstat(&self, path: &str) -> Result<Kstat, FilesystemError> {
149
+ if path.is_empty() {
150
+ return self.vfs_getattr();
151
+ }
152
+ self.resolve(path)?.getattr()
153
+ }
154
+
155
+ pub(crate) fn lookup(&self, path: &str) -> Result<String, FilesystemError> {
156
+ let nd = filename_parentat(&Arc::default(), &self.0, path)?;
157
+ Ok(lookup_one_qstr_excl(nd.mnt.sb(), nd.dentry(), &nd.last)?.name())
158
+ }
159
+
160
+ #[cfg(unix)]
161
+ pub(crate) fn vfs_readlink(&self, path: &str) -> Result<String, FilesystemError> {
162
+ self.resolve(path)?.path()?.get_link()
163
+ }
164
+
165
+ pub(crate) fn iterate_dir(&self) -> Result<Vec<DirEntry>, FilesystemError> {
166
+ let read = OpenHow {
167
+ read: true,
168
+ ..OpenHow::default()
169
+ };
170
+ File::dentry_open(self.0.clone(), &read, &Arc::default())?.iterate_shared()
171
+ }
172
+
173
+ pub(crate) fn vfs_mkdir(&self, path: &str) -> Result<(), FilesystemError> {
174
+ let (nd, child) = self.parent(path)?;
175
+ vfs_mkdir(&nd.cred, nd.mnt.sb(), nd.dentry(), &child)
176
+ }
177
+
178
+ pub(crate) fn vfs_rmdir(&self, path: &str) -> Result<(), FilesystemError> {
179
+ let (nd, child) = self.parent(path)?;
180
+ vfs_rmdir(&nd.cred, nd.mnt.sb(), nd.dentry(), &child)
181
+ }
182
+
183
+ pub(crate) fn vfs_unlink(&self, path: &str) -> Result<(), FilesystemError> {
184
+ let (nd, child) = self.parent(path)?;
185
+ vfs_unlink(&nd.cred, nd.mnt.sb(), nd.dentry(), &child)
186
+ }
187
+
188
+ /// Move `from` beneath this directory to `to` beneath `to_dir`,
189
+ /// refused where the source stands above the target's directory, as
190
+ /// `start_renaming` refuses it.
191
+ pub(crate) fn rename(
192
+ &self,
193
+ from: &str,
194
+ to_dir: &Self,
195
+ to: &str,
196
+ ) -> Result<(), FilesystemError> {
197
+ let ((source, old), (target, new)) = (self.parent(from)?, to_dir.parent(to)?);
198
+ if target.dentry().is_subdir(&old) {
199
+ return Err(FilesystemError::InvalidInput);
200
+ }
201
+ if source.dentry().is_subdir(&new) {
202
+ return Err(FilesystemError::DirectoryNotEmpty);
203
+ }
204
+ vfs_rename(
205
+ &source.cred,
206
+ source.mnt.sb(),
207
+ &RenameData {
208
+ _held: Vec::new(),
209
+ old_parent: source.dentry().clone(),
210
+ old_dentry: old,
211
+ new_parent: target.dentry().clone(),
212
+ new_dentry: new,
213
+ flag: None,
214
+ },
215
+ )
216
+ }
217
+ }