kobako 0.29.0 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (201) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +1 -1
  3. data/CHANGELOG.md +217 -10
  4. data/Cargo.lock +139 -141
  5. data/README.md +96 -574
  6. data/ROADMAP.md +2 -1
  7. data/crates/kobako-runtime/CHANGELOG.md +269 -1
  8. data/crates/kobako-runtime/Cargo.toml +25 -15
  9. data/crates/kobako-runtime/README.md +7 -3
  10. data/crates/kobako-runtime/src/dispatch.rs +1 -1
  11. data/crates/kobako-runtime/src/endpoint/buffer.rs +192 -0
  12. data/crates/kobako-runtime/src/endpoint/capture.rs +115 -0
  13. data/crates/kobako-runtime/src/endpoint/dispatch.rs +253 -0
  14. data/crates/kobako-runtime/src/endpoint/invoke.rs +194 -0
  15. data/crates/kobako-runtime/src/endpoint/memory_cap.rs +238 -0
  16. data/crates/kobako-runtime/src/endpoint.rs +166 -0
  17. data/crates/kobako-runtime/src/filesystem/bind/fid.rs +82 -0
  18. data/crates/kobako-runtime/src/filesystem/{directory/spelling.rs → bind/lookup.rs} +91 -49
  19. data/crates/kobako-runtime/src/filesystem/bind/walk.rs +249 -0
  20. data/crates/kobako-runtime/src/filesystem/bind.rs +2665 -0
  21. data/crates/kobako-runtime/src/filesystem/overlay/file.rs +89 -0
  22. data/crates/kobako-runtime/src/filesystem/overlay/inode.rs +106 -0
  23. data/crates/kobako-runtime/src/filesystem/overlay/path.rs +285 -0
  24. data/crates/kobako-runtime/src/filesystem/overlay/stack/copy_up.rs +135 -0
  25. data/crates/kobako-runtime/src/filesystem/overlay/stack/dir.rs +263 -0
  26. data/crates/kobako-runtime/src/filesystem/overlay/stack/namei.rs +135 -0
  27. data/crates/kobako-runtime/src/filesystem/overlay/stack/readdir.rs +57 -0
  28. data/crates/kobako-runtime/src/filesystem/overlay/stack.rs +227 -734
  29. data/crates/kobako-runtime/src/filesystem/overlay.rs +2649 -1135
  30. data/crates/kobako-runtime/src/filesystem/path.rs +2 -41
  31. data/crates/kobako-runtime/src/filesystem/pipe.rs +221 -0
  32. data/crates/kobako-runtime/src/filesystem/rdir.rs +101 -0
  33. data/crates/kobako-runtime/src/filesystem/tmpfs/tmpfile.rs +32 -0
  34. data/crates/kobako-runtime/src/filesystem/tmpfs/whiteout.rs +85 -0
  35. data/crates/kobako-runtime/src/filesystem/tmpfs/xattr.rs +22 -0
  36. data/crates/kobako-runtime/src/filesystem/tmpfs.rs +1985 -0
  37. data/crates/kobako-runtime/src/filesystem/wrap.rs +224 -0
  38. data/crates/kobako-runtime/src/filesystem.rs +21 -387
  39. data/crates/kobako-runtime/src/hashtable.rs +44 -0
  40. data/crates/kobako-runtime/src/lib.rs +11 -6
  41. data/crates/kobako-runtime/src/locking/seqlock.rs +66 -0
  42. data/crates/kobako-runtime/src/locking.rs +218 -0
  43. data/crates/kobako-runtime/src/mount.rs +31 -104
  44. data/crates/kobako-runtime/src/profile.rs +137 -1
  45. data/crates/kobako-runtime/src/sched.rs +210 -0
  46. data/crates/kobako-runtime/src/security.rs +696 -0
  47. data/crates/kobako-runtime/src/vfs/attr.rs +162 -0
  48. data/crates/kobako-runtime/src/vfs/cred.rs +334 -0
  49. data/crates/kobako-runtime/src/vfs/dentry.rs +1412 -0
  50. data/crates/kobako-runtime/src/vfs/errno.rs +153 -0
  51. data/crates/kobako-runtime/src/vfs/file.rs +1915 -0
  52. data/crates/kobako-runtime/src/vfs/inode.rs +429 -0
  53. data/crates/kobako-runtime/src/vfs/kern_path.rs +217 -0
  54. data/crates/kobako-runtime/src/vfs/mount.rs +59 -0
  55. data/crates/kobako-runtime/src/vfs/namei.rs +5708 -0
  56. data/crates/kobako-runtime/src/vfs/openat2.rs +116 -0
  57. data/crates/kobako-runtime/src/vfs/path.rs +64 -0
  58. data/crates/kobako-runtime/src/vfs/readdir.rs +163 -0
  59. data/crates/kobako-runtime/src/vfs/stat.rs +157 -0
  60. data/crates/kobako-runtime/src/vfs/super_block.rs +122 -0
  61. data/crates/kobako-runtime/src/vfs/utimes.rs +289 -0
  62. data/crates/kobako-runtime/src/vfs.rs +60 -0
  63. data/crates/kobako-runtime/tests/linux_baseline/calls.rs +171 -0
  64. data/crates/kobako-runtime/tests/linux_baseline/concurrent.rs +276 -0
  65. data/crates/kobako-runtime/tests/linux_baseline/kobako.rs +323 -0
  66. data/crates/kobako-runtime/tests/linux_baseline/linux-baseline-expected.txt +16 -0
  67. data/crates/kobako-runtime/tests/linux_baseline/linux.rs +449 -0
  68. data/crates/kobako-runtime/tests/linux_baseline/main.rs +611 -0
  69. data/crates/kobako-runtime/tests/linux_baseline/permissions.rs +236 -0
  70. data/crates/kobako-transport/CHANGELOG.md +8 -2
  71. data/crates/kobako-transport/Cargo.toml +3 -3
  72. data/crates/kobako-transport/README.md +6 -5
  73. data/crates/kobako-transport/src/envelope/bytes.rs +33 -25
  74. data/crates/kobako-transport/src/envelope/call.rs +5 -5
  75. data/crates/kobako-transport/src/envelope/compile_reply.rs +2 -2
  76. data/crates/kobako-transport/src/envelope/error_record.rs +3 -3
  77. data/crates/kobako-transport/src/envelope/fault.rs +2 -2
  78. data/crates/kobako-transport/src/envelope/outcome.rs +3 -3
  79. data/crates/kobako-transport/src/envelope/preamble.rs +12 -12
  80. data/crates/kobako-transport/src/envelope/reply.rs +2 -2
  81. data/crates/kobako-transport/src/envelope/request.rs +2 -2
  82. data/crates/kobako-transport/src/envelope/run.rs +1 -1
  83. data/crates/kobako-transport/src/envelope/unsupported.rs +2 -2
  84. data/crates/kobako-transport/src/envelope.rs +2 -2
  85. data/crates/kobako-wasmtime/CHANGELOG.md +154 -8
  86. data/crates/kobako-wasmtime/Cargo.toml +15 -14
  87. data/crates/kobako-wasmtime/README.md +9 -5
  88. data/crates/kobako-wasmtime/src/abi.rs +12 -20
  89. data/crates/kobako-wasmtime/src/ambient.rs +1 -2
  90. data/crates/kobako-wasmtime/src/cache.rs +5 -5
  91. data/crates/kobako-wasmtime/src/config.rs +22 -7
  92. data/crates/kobako-wasmtime/src/descriptors.rs +133 -100
  93. data/crates/kobako-wasmtime/src/driver.rs +29 -96
  94. data/crates/kobako-wasmtime/src/exports.rs +5 -16
  95. data/crates/kobako-wasmtime/src/guest.rs +38 -0
  96. data/crates/kobako-wasmtime/src/imports.rs +14 -8
  97. data/crates/kobako-wasmtime/src/instance_pre.rs +3 -3
  98. data/crates/kobako-wasmtime/src/invocation.rs +26 -46
  99. data/crates/kobako-wasmtime/src/lib.rs +1 -4
  100. data/crates/kobako-wasmtime/src/limiter.rs +6 -230
  101. data/crates/kobako-wasmtime/src/preview1/differential/mount.rs +69 -46
  102. data/crates/kobako-wasmtime/src/preview1/differential.rs +121 -7
  103. data/crates/kobako-wasmtime/src/preview1/errno.rs +74 -206
  104. data/crates/kobako-wasmtime/src/preview1/fd.rs +1248 -500
  105. data/crates/kobako-wasmtime/src/preview1/memory.rs +117 -150
  106. data/crates/kobako-wasmtime/src/preview1/mount_view/wrap.rs +211 -0
  107. data/crates/kobako-wasmtime/src/preview1/mount_view.rs +1939 -672
  108. data/crates/kobako-wasmtime/src/preview1/path.rs +230 -349
  109. data/crates/kobako-wasmtime/src/preview1/poll.rs +148 -100
  110. data/crates/kobako-wasmtime/src/preview1/sock.rs +6 -23
  111. data/crates/kobako-wasmtime/src/preview1/testsuite-expected.txt +6 -0
  112. data/crates/kobako-wasmtime/src/preview1/testsuite.rs +157 -0
  113. data/crates/kobako-wasmtime/src/preview1.rs +464 -413
  114. data/crates/kobako-wasmtime/src/trap.rs +9 -9
  115. data/crates/kobako-wasmtime/src/wasi.rs +53 -37
  116. data/crates/kobako-wasmtime/tests/{guest_file.rs → integration/guest_file.rs} +232 -201
  117. data/crates/kobako-wasmtime/tests/integration/main.rs +29 -0
  118. data/crates/kobako-wasmtime/tests/{preamble_flags.rs → integration/preamble_flags.rs} +1 -1
  119. data/crates/kobako-wasmtime/tests/{snippet_compile.rs → integration/snippet_compile.rs} +1 -1
  120. data/crates/kobako-wasmtime/tests/{unanswered_call.rs → integration/unanswered_call.rs} +1 -1
  121. data/crates/kobako-wasmtime/tests/{unsupported_call.rs → integration/unsupported_call.rs} +1 -1
  122. data/crates/kobako-wasmtime/witx/typenames.witx +750 -0
  123. data/crates/kobako-wasmtime/witx/wasi_snapshot_preview1.witx +441 -0
  124. data/data/kobako.wasm +0 -0
  125. data/ext/kobako/Cargo.toml +1 -1
  126. data/ext/kobako/src/runtime/bridge.rs +1 -1
  127. data/ext/kobako/src/runtime/errors.rs +1 -1
  128. data/ext/kobako/src/runtime/escape.rs +28 -14
  129. data/ext/kobako/src/runtime/filesystem/grants.rs +21 -5
  130. data/ext/kobako/src/runtime/filesystem/ruby.rs +464 -156
  131. data/ext/kobako/src/runtime/filesystem.rs +109 -137
  132. data/ext/kobako/src/runtime/gvl.rs +43 -8
  133. data/ext/kobako/src/runtime/snapshot.rs +1 -1
  134. data/ext/kobako/src/runtime.rs +6 -1
  135. data/lib/kobako/catalog/extensions.rb +5 -5
  136. data/lib/kobako/catalog/flags.rb +2 -2
  137. data/lib/kobako/catalog/handles.rb +6 -6
  138. data/lib/kobako/catalog/mount.rb +2 -10
  139. data/lib/kobako/catalog/services.rb +2 -2
  140. data/lib/kobako/catalog/snippets.rb +7 -7
  141. data/lib/kobako/catalog.rb +1 -1
  142. data/lib/kobako/codec/decoder.rb +10 -10
  143. data/lib/kobako/codec/ext_types.rb +3 -5
  144. data/lib/kobako/codec/handle_walk.rb +4 -6
  145. data/lib/kobako/codec/nesting.rb +7 -7
  146. data/lib/kobako/codec/utils.rb +1 -1
  147. data/lib/kobako/codec/value_budget.rb +3 -3
  148. data/lib/kobako/context.rb +5 -5
  149. data/lib/kobako/filesystem.rb +44 -13
  150. data/lib/kobako/payload/arguments.rb +2 -2
  151. data/lib/kobako/pool.rb +4 -4
  152. data/lib/kobako/sandbox.rb +28 -28
  153. data/lib/kobako/sandbox_options.rb +2 -3
  154. data/lib/kobako/snippet/bytecode.rb +1 -1
  155. data/lib/kobako/snippet/source.rb +1 -1
  156. data/lib/kobako/transport/dispatcher.rb +5 -5
  157. data/lib/kobako/transport/exposure.rb +2 -2
  158. data/lib/kobako/transport/reflection.rb +7 -7
  159. data/lib/kobako/transport/run.rb +5 -5
  160. data/lib/kobako/transport/yielder.rb +1 -1
  161. data/lib/kobako/version.rb +1 -1
  162. data/lib/kobako.rb +34 -0
  163. data/release-please-config.json +1 -0
  164. data/sig/kobako/catalog/extensions.rbs +3 -3
  165. data/sig/kobako/catalog/handles.rbs +3 -3
  166. data/sig/kobako/catalog/mount.rbs +0 -4
  167. data/sig/kobako/catalog/services.rbs +1 -1
  168. data/sig/kobako/catalog/snippets.rbs +3 -3
  169. data/sig/kobako/catalog.rbs +1 -1
  170. data/sig/kobako/codec/decoder.rbs +1 -1
  171. data/sig/kobako/codec/handle_walk.rbs +1 -1
  172. data/sig/kobako/codec/nesting.rbs +2 -2
  173. data/sig/kobako/codec/utils.rbs +1 -1
  174. data/sig/kobako/codec/value_budget.rbs +1 -1
  175. data/sig/kobako/codec.rbs +1 -2
  176. data/sig/kobako/context.rbs +1 -1
  177. data/sig/kobako/filesystem.rbs +19 -14
  178. data/sig/kobako/guest_yielder.rbs +1 -1
  179. data/sig/kobako/payload/arguments.rbs +1 -1
  180. data/sig/kobako/pool.rbs +2 -2
  181. data/sig/kobako/runtime.rbs +1 -1
  182. data/sig/kobako/sandbox.rbs +3 -5
  183. data/sig/kobako/sandbox_options.rbs +1 -1
  184. data/sig/kobako/transport/dispatcher.rbs +2 -2
  185. data/sig/kobako/transport/exposure.rbs +1 -1
  186. data/sig/kobako/transport/reflection.rbs +2 -2
  187. data/sig/kobako/transport/run.rbs +3 -3
  188. data/sig/kobako/transport.rbs +1 -1
  189. metadata +67 -19
  190. data/crates/kobako-runtime/src/filesystem/directory.rs +0 -1082
  191. data/crates/kobako-runtime/src/filesystem/memory.rs +0 -1100
  192. data/crates/kobako-runtime/src/filesystem/overlay/exclusive.rs +0 -88
  193. data/crates/kobako-runtime/src/filesystem/temporary.rs +0 -167
  194. data/crates/kobako-wasmtime/src/capture.rs +0 -96
  195. data/crates/kobako-wasmtime/src/dispatch.rs +0 -125
  196. data/crates/kobako-wasmtime/src/guest_mem.rs +0 -271
  197. data/crates/kobako-wasmtime/src/io.rs +0 -89
  198. data/crates/kobako-wasmtime/src/preview1/args.rs +0 -60
  199. data/crates/kobako-wasmtime/src/preview1/beneath.rs +0 -188
  200. /data/crates/kobako-wasmtime/tests/{compile_posture.rs → integration/compile_posture.rs} +0 -0
  201. /data/crates/kobako-wasmtime/tests/{support → integration/support}/mod.rs +0 -0
@@ -1,117 +1,142 @@
1
1
  //! The Layers of an Overlay and the rules that make them one view: which
2
- //! Layer shows a path, how a change lands in a writable Layer, and the
3
- //! markers that hide what a read-only Layer still holds.
2
+ //! Layer shows a name, how a change lands in the upper, and the marks that
3
+ //! hide what a lower Layer still holds. The calls on the view are split
4
+ //! as overlayfs splits them: finding a name in `namei`, copying up in
5
+ //! `copy_up`, listing in `readdir`, and changing a directory in `dir`.
4
6
  //!
5
- //! Each change that touches more than one entry writes its marker or its
6
- //! replacement before it takes the old one away, as overlayfs builds a
7
- //! copy before it swaps it in, so a change that fails part way leaves the
8
- //! view as it was or as it was meant to become.
7
+ //! Every Layer is asked about a single name in a directory already held, as
8
+ //! overlayfs looks a name up in each layer's directory. A Layer's directory
9
+ //! merges into the view only beneath a directory it also holds.
10
+ //!
11
+ //! The upper Tmpfs holds the view's changes in `upper` and builds each one
12
+ //! in `work` first, as overlayfs's upperdir and workdir share one mount.
13
+ //! A change that replaces an entry is made whole in `work` and exchanged
14
+ //! into place, so a change that fails part way leaves the view as it was.
15
+
16
+ mod copy_up;
17
+ mod dir;
18
+ mod namei;
19
+ mod readdir;
9
20
 
10
- use std::collections::HashSet;
21
+ use std::sync::atomic::{AtomicU64, Ordering};
11
22
  use std::sync::Arc;
12
23
 
13
- use super::super::path::{ancestors, beneath_itself, is_beneath, join, name, parent};
14
- use super::super::{
15
- Access, DirEntry, File, FileType, Filesystem, FilesystemError, Hold, Metadata, OpenOptions,
16
- Rename,
17
- };
24
+ use super::inode::OverlayDirInode;
25
+ use super::path::OverlayPath;
26
+
27
+ use super::super::{FileOperations, FileType, Filesystem, FilesystemError, Kiocb};
28
+ use crate::vfs::{Cred, Dentry, Identity};
18
29
 
19
- /// The prefix of a whiteout, and of every name reserved for a marker.
20
- const WHITEOUT: &str = ".wh.";
21
- /// The marker hiding every lower Layer's entries of its directory.
22
- const OPAQUE: &str = ".wh..wh..opq";
30
+ /// The directories of the upper Tmpfs holding the view's changes and the
31
+ /// changes being built, as overlayfs's `upperdir` and `workdir`.
32
+ const UPPERDIR: &str = "upper";
33
+ const WORKDIR: &str = "work";
23
34
 
24
- /// How a Layer is asked what a path names: following a link there, or not.
25
- type Lookup = fn(&(dyn Filesystem + 'static), &str) -> Result<Metadata, FilesystemError>;
35
+ /// Where the upper stands among the Layers, topmost first, holding nothing where
36
+ /// an Overlay has none, as `ovl_inode` keeps `__upperdentry` apart from its lower
37
+ /// stack. Every change lands there.
38
+ pub(super) const UPPER: usize = 0;
26
39
 
40
+ /// Each Layer's directory at one directory of the view, topmost first;
41
+ /// `None` where that Layer holds no directory the view merges there.
42
+ pub(super) type Level = Vec<Option<OverlayPath>>;
43
+
44
+ /// One directory of the view: the dentry the runtime holds for it and the
45
+ /// state kept there, as overlayfs reaches a directory by its dentry and
46
+ /// the `ovl_inode` behind it.
27
47
  #[derive(Clone)]
28
- struct Layer {
29
- filesystem: Arc<dyn Filesystem>,
30
- access: Access,
48
+ pub(super) struct ViewDir {
49
+ dentry: Arc<Dentry>,
50
+ inode: Arc<OverlayDirInode>,
31
51
  }
32
52
 
33
- impl Layer {
34
- fn writable(&self) -> bool {
35
- self.access == Access::ReadWrite
53
+ impl ViewDir {
54
+ /// Each Layer's directory this one stands for.
55
+ pub(super) fn level(&self) -> Level {
56
+ self.inode.level()
36
57
  }
58
+ }
37
59
 
38
- fn find(&self, path: &str) -> Result<Option<Metadata>, FilesystemError> {
39
- self.find_by(path, <dyn Filesystem>::metadata)
40
- }
60
+ /// What a Layer shows at a name, as `ovl_lookup_single` tells a whiteout from an
61
+ /// entry.
62
+ enum Presence {
63
+ /// Nothing, and a name too long for that Layer to hold is nothing either, as
64
+ /// `ovl_lookup_single` answers ENOENT and ENAMETOOLONG by moving on.
65
+ Nothing,
66
+ /// A whiteout, which hides the Layers below.
67
+ Whiteout,
68
+ /// An entry, a link reported as itself.
69
+ Entry(LayerEntry),
70
+ }
41
71
 
42
- /// What this Layer holds at `path` as one of the Layers merged beneath
43
- /// a directory shown: a file standing above `path` here ends the merge
44
- /// as a file at `path` does, rather than failing it.
45
- fn find_below(&self, path: &str) -> Result<Option<Metadata>, FilesystemError> {
46
- match self.find(path) {
47
- Err(FilesystemError::NotADirectory) => Ok(Some(Metadata::new(FileType::File, 0))),
48
- found => found,
49
- }
72
+ /// An entry a Layer holds: its name as the Layer stores it, and its dentry there.
73
+ pub(super) struct LayerEntry {
74
+ stored_name: String,
75
+ dentry: Arc<Dentry>,
76
+ }
77
+
78
+ impl LayerEntry {
79
+ /// The name as the Layer stores it.
80
+ pub(super) fn stored_name(&self) -> &str {
81
+ &self.stored_name
50
82
  }
51
83
 
52
- fn find_by(&self, path: &str, lookup: Lookup) -> Result<Option<Metadata>, FilesystemError> {
53
- match lookup(self.filesystem.as_ref(), path) {
54
- Ok(metadata) => Ok(Some(metadata)),
55
- Err(FilesystemError::NotFound) => Ok(None),
56
- Err(err) => Err(err),
57
- }
84
+ /// What kind of entry it is, as its lookup recorded it.
85
+ fn kind(&self) -> Result<FileType, FilesystemError> {
86
+ self.dentry.kind()
58
87
  }
88
+ }
59
89
 
60
- /// Whether this Layer has an entry at `path`, a link pointing at
61
- /// nothing among them. A Layer that cannot answer fails the call, so a
62
- /// marker it could not read never lets a lower entry show.
63
- fn holds(&self, path: &str) -> Result<bool, FilesystemError> {
64
- match self.filesystem.symlink_metadata(path) {
65
- Ok(_) => Ok(true),
66
- Err(FilesystemError::NotFound | FilesystemError::NotADirectory) => Ok(false),
67
- Err(err) => Err(err),
68
- }
90
+ impl Presence {
91
+ fn is_directory(&self) -> bool {
92
+ matches!(self, Self::Entry(found) if found.kind() == Ok(FileType::Dir))
69
93
  }
94
+ }
70
95
 
71
- /// Whether this Layer hides `path` in every Layer below it: a whiteout
72
- /// of it or of a directory above it, an opaque marker in one of those
73
- /// directories or in `path` itself, or a file or link standing at it
74
- /// or above it, as overlayfs merges only directories. A marker lives
75
- /// in a directory, so the walk ends at the first directory above
76
- /// `path` this Layer lacks.
77
- fn hides(&self, path: &str) -> Result<bool, FilesystemError> {
78
- if self.holds(&join(path, OPAQUE))? {
79
- return Ok(true);
96
+ /// What `dir` shows at `name`, read from one lookup, as `ovl_lookup_single` reads
97
+ /// `ovl_is_whiteout` and `d_is_dir` from the dentry it found.
98
+ fn entry(dir: &OverlayPath, name: &str) -> Result<Presence, FilesystemError> {
99
+ match dir.lookup_single(name) {
100
+ Ok(Some((_, dentry))) if dir.is_whiteout_dentry(&dentry) => Ok(Presence::Whiteout),
101
+ Ok(Some((stored_name, dentry))) => Ok(Presence::Entry(LayerEntry {
102
+ stored_name,
103
+ dentry,
104
+ })),
105
+ Ok(None) | Err(FilesystemError::NotFound | FilesystemError::InvalidFilename) => {
106
+ Ok(Presence::Nothing)
80
107
  }
81
- for name in ancestors(path).chain(std::iter::once(path)) {
82
- if self.holds(&whiteout(name))? || self.holds(&join(parent(name), OPAQUE))? {
83
- return Ok(true);
84
- }
85
- match self.filesystem.symlink_metadata(name) {
86
- Ok(metadata) if metadata.file_type != FileType::Directory => return Ok(true),
87
- Ok(_) => {}
88
- Err(FilesystemError::NotFound | FilesystemError::NotADirectory) if name != path => {
89
- return Ok(false)
90
- }
91
- Err(FilesystemError::NotFound | FilesystemError::NotADirectory) => {}
92
- Err(err) => return Err(err),
93
- }
94
- }
95
- Ok(false)
108
+ Err(err) => Err(err),
96
109
  }
97
110
  }
98
111
 
99
- /// The whiteout hiding `path`, beside it in its directory.
100
- fn whiteout(path: &str) -> String {
101
- join(parent(path), &format!("{WHITEOUT}{}", name(path)))
112
+ /// Whether a Layer below the upper shows `name` in `level`, as
113
+ /// `ovl_lower_positive` finds out whether a removal must leave a whiteout. A Layer
114
+ /// that cannot answer is taken to hold something, so nothing below shows through.
115
+ fn ovl_lower_positive(level: &Level, name: &str) -> bool {
116
+ for dir in level.iter().skip(UPPER + 1).flatten() {
117
+ match entry(dir, name) {
118
+ Ok(Presence::Nothing) => {}
119
+ Ok(Presence::Whiteout) => return false,
120
+ Ok(Presence::Entry(_)) | Err(_) => return true,
121
+ }
122
+ }
123
+ false
102
124
  }
103
125
 
104
- fn copy_whole(source: &mut dyn File, target: &mut dyn File) -> Result<(), FilesystemError> {
126
+ fn copy_whole(
127
+ source: &mut dyn FileOperations,
128
+ target: &mut dyn FileOperations,
129
+ ) -> Result<(), FilesystemError> {
105
130
  let mut buf = vec![0; 64 * 1024];
106
- let mut offset = 0;
131
+ let (mut from, mut to) = (Kiocb::at(0), Kiocb::at(0));
107
132
  loop {
108
- let read = source.read_at(&mut buf, offset)?;
133
+ let read = source.read_iter(&mut from, &mut buf)?;
109
134
  if read == 0 {
110
135
  return Ok(());
111
136
  }
112
137
  let mut written = 0;
113
138
  while written < read {
114
- match target.write_at(&buf[written..read], offset + written as u64)? {
139
+ match target.write_iter(&mut to, &buf[written..read])? {
115
140
  0 => {
116
141
  return Err(FilesystemError::Other(
117
142
  "a copy up's write wrote nothing".to_string(),
@@ -120,689 +145,157 @@ fn copy_whole(source: &mut dyn File, target: &mut dyn File) -> Result<(), Filesy
120
145
  wrote => written += wrote,
121
146
  }
122
147
  }
123
- offset += read as u64;
124
- }
125
- }
126
-
127
- /// Take every marker out of a writable Layer's copy of the directory
128
- /// `path`, which the view shows empty, for `act` to change it; any other
129
- /// entry there means it is not. When `act` fails, or a marker will not
130
- /// go, the markers taken out are put back, so the view stays as it was.
131
- fn without_markers(
132
- filesystem: &dyn Filesystem,
133
- path: &str,
134
- act: impl FnOnce() -> Result<(), FilesystemError>,
135
- ) -> Result<(), FilesystemError> {
136
- let markers: Vec<String> = filesystem
137
- .read_dir(path)?
138
- .into_iter()
139
- .map(|entry| entry.name)
140
- .collect();
141
- if markers.iter().any(|name| !name.starts_with(WHITEOUT)) {
142
- return Err(FilesystemError::DirectoryNotEmpty);
143
- }
144
- let mut cleared = Vec::new();
145
- let changed = markers
146
- .iter()
147
- .try_for_each(|name| {
148
- let marker = join(path, name);
149
- filesystem.remove_file(&marker)?;
150
- cleared.push(marker);
151
- Ok(())
152
- })
153
- .and_then(|()| act());
154
- if changed.is_err() {
155
- for marker in cleared {
156
- let _ = put_marker(filesystem, &marker);
157
- }
158
148
  }
159
- changed
160
149
  }
161
150
 
162
- /// An empty file at `path`, as every marker is.
163
- fn put_marker(filesystem: &dyn Filesystem, path: &str) -> Result<(), FilesystemError> {
164
- filesystem
165
- .open(
166
- path,
167
- &OpenOptions {
168
- write: true,
169
- create: true,
170
- ..OpenOptions::default()
171
- },
172
- )?
173
- .close()
151
+ /// The Layer's directory a level holds, which it holds wherever it shows
152
+ /// a name.
153
+ fn layer_dir(dir: &Option<OverlayPath>) -> Result<&OverlayPath, FilesystemError> {
154
+ dir.as_ref().ok_or(FilesystemError::NotFound)
174
155
  }
175
156
 
176
- /// Refuse a path naming an entry reserved for the Overlay's markers, in
177
- /// any case, since a Layer that folds case would hold `.WH.name` as one.
178
- fn refuse_reserved(path: &str) -> Result<(), FilesystemError> {
179
- let marker = |name: &str| {
180
- name.get(..WHITEOUT.len())
181
- .is_some_and(|start| start.eq_ignore_ascii_case(WHITEOUT))
182
- };
183
- if path.split('/').any(marker) {
184
- return Err(FilesystemError::InvalidInput);
157
+ /// The directory `name` in `root`, made there first if it stands nowhere, as
158
+ /// overlayfs's `ovl_workdir_create` makes its upperdir's sibling.
159
+ fn ovl_workdir_create(root: &OverlayPath, name: &str) -> Result<OverlayPath, FilesystemError> {
160
+ match root.do_mkdir(name) {
161
+ Ok(()) | Err(FilesystemError::AlreadyExists) => root.open_dir(name),
162
+ Err(err) => Err(err),
185
163
  }
186
- Ok(())
187
- }
188
-
189
- /// Layers stacked into one view, the first added on top.
190
- #[derive(Clone, Default)]
191
- pub(super) struct Stack {
192
- layers: Vec<Layer>,
193
164
  }
194
165
 
195
- impl Stack {
196
- pub(super) fn push(&mut self, filesystem: Arc<dyn Filesystem>, access: Access) {
197
- self.layers.push(Layer { filesystem, access });
198
- }
199
-
200
- /// The Layers a new Invocation starts from, or `None` when every Layer
201
- /// is shared.
202
- pub(super) fn for_invocation(&self) -> Option<Self> {
203
- self.with_each_layer(|filesystem| filesystem.for_invocation())
204
- }
205
-
206
- /// The Layers a Mount with a Policy reaches, or `None` when no Layer
207
- /// holds a link.
208
- pub(super) fn for_policy(&self) -> Option<Self> {
209
- self.with_each_layer(|filesystem| filesystem.for_policy())
210
- }
211
-
212
- /// The Layers with each Filesystem `next` answers in its place, or
213
- /// `None` when it answers none.
214
- fn with_each_layer(
215
- &self,
216
- next: impl Fn(&Arc<dyn Filesystem>) -> Option<Arc<dyn Filesystem>>,
217
- ) -> Option<Self> {
218
- let next: Vec<_> = self
219
- .layers
220
- .iter()
221
- .map(|layer| next(&layer.filesystem))
222
- .collect();
223
- if next.iter().all(Option::is_none) {
224
- return None;
225
- }
226
- let layers = self
227
- .layers
228
- .iter()
229
- .zip(next)
230
- .map(|(layer, next)| Layer {
231
- filesystem: next.unwrap_or_else(|| layer.filesystem.clone()),
232
- access: layer.access,
233
- })
234
- .collect();
235
- Some(Self { layers })
236
- }
237
-
238
- /// The topmost Layer holding `path` and what it holds there, a link
239
- /// followed within that Layer, or `None` when no Layer shows it or the
240
- /// link shown points at nothing.
241
- fn locate(&self, path: &str) -> Result<Option<(usize, Metadata)>, FilesystemError> {
242
- match self.locate_by(path, <dyn Filesystem>::symlink_metadata)? {
243
- Some((index, metadata)) if metadata.file_type == FileType::Symlink => Ok(self.layers
244
- [index]
245
- .find(path)?
246
- .map(|metadata| (index, metadata))),
247
- found => Ok(found),
248
- }
249
- }
250
-
251
- fn locate_by(
252
- &self,
253
- path: &str,
254
- lookup: Lookup,
255
- ) -> Result<Option<(usize, Metadata)>, FilesystemError> {
256
- for (index, layer) in self.layers.iter().enumerate() {
257
- if let Some(metadata) = layer.find_by(path, lookup)? {
258
- return Ok(Some((index, metadata)));
259
- }
260
- if layer.hides(path)? {
261
- break;
262
- }
263
- }
264
- Ok(None)
265
- }
266
-
267
- /// Every Layer still showing `path`, topmost first, and the topmost
268
- /// Layer below them whose entry there a file or link above shadows.
269
- fn holders(&self, path: &str) -> Result<(Vec<usize>, Option<usize>), FilesystemError> {
270
- let mut holders = Vec::new();
271
- for (index, layer) in self.layers.iter().enumerate() {
272
- let holds = layer.holds(path)?;
273
- if holds {
274
- holders.push(index);
275
- }
276
- if layer.hides(path)? {
277
- let shadowed = if holds {
278
- self.beneath(index, path)?
279
- } else {
280
- None
281
- };
282
- return Ok((holders, shadowed));
283
- }
284
- }
285
- Ok((holders, None))
286
- }
287
-
288
- /// The topmost Layer below `above` holding `path` in sight.
289
- fn beneath(&self, above: usize, path: &str) -> Result<Option<usize>, FilesystemError> {
290
- for (index, layer) in self.layers.iter().enumerate().skip(above + 1) {
291
- if layer.holds(path)? {
292
- return Ok(Some(index));
293
- }
294
- if layer.hides(path)? {
295
- return Ok(None);
296
- }
297
- }
298
- Ok(None)
299
- }
300
-
301
- /// The topmost writable Layer, where every change that is not in place
302
- /// lands.
303
- fn upper(&self) -> Result<usize, FilesystemError> {
304
- self.layers
305
- .iter()
306
- .position(Layer::writable)
307
- .ok_or(FilesystemError::ReadOnlyFilesystem)
308
- }
309
-
310
- /// Refuse as read-only a change landing at `path` in Layer `into` that
311
- /// a read-only Layer above it would cover, holding or hiding `path`, so
312
- /// no change succeeds out of sight.
313
- fn refuse_covered(&self, path: &str, into: usize) -> Result<(), FilesystemError> {
314
- for layer in &self.layers[..into] {
315
- if layer.holds(path)? || layer.hides(path)? {
316
- return Err(FilesystemError::ReadOnlyFilesystem);
317
- }
318
- }
319
- Ok(())
320
- }
321
-
322
- /// Create in Layer `into` each directory above `path` that so far shows
323
- /// only from another Layer. A Layer holds no link it was not handed, so
324
- /// a change reaching through a link another Layer holds is refused as
325
- /// crossing devices rather than landing beside where the link points.
326
- fn copy_up_directories(&self, path: &str, into: usize) -> Result<(), FilesystemError> {
327
- let target = &self.layers[into];
328
- for dir in ancestors(path) {
329
- if target.holds(dir)? {
330
- continue;
331
- }
332
- match self.locate_by(dir, <dyn Filesystem>::symlink_metadata)? {
333
- Some((_, metadata)) => match metadata.file_type {
334
- FileType::Directory => target.filesystem.create_dir(dir)?,
335
- FileType::Symlink => return Err(FilesystemError::CrossesDevices),
336
- _ => return Err(FilesystemError::NotADirectory),
337
- },
338
- None => return Err(FilesystemError::NotFound),
339
- }
340
- }
341
- Ok(())
342
- }
343
-
344
- /// Copy the file at `path` from Layer `from` into Layer `into` whole.
345
- /// Layer `into` held nothing there, so a copy that fails part way is
346
- /// taken away again rather than left to hide the whole file. A link
347
- /// is not copied up, as no Layer is handed one to hold.
348
- fn copy_up_file(&self, path: &str, from: usize, into: usize) -> Result<(), FilesystemError> {
349
- if self.layers[from]
350
- .filesystem
351
- .symlink_metadata(path)?
352
- .file_type
353
- == FileType::Symlink
354
- {
355
- return Err(FilesystemError::CrossesDevices);
356
- }
357
- self.copy_up_directories(path, into)?;
358
- let mut source = self.layers[from].filesystem.open(
359
- path,
360
- &OpenOptions {
361
- read: true,
362
- ..OpenOptions::default()
363
- },
364
- )?;
365
- let target = &self.layers[into].filesystem;
366
- let mut copy = target.open(
367
- path,
368
- &OpenOptions {
369
- write: true,
370
- create: true,
371
- truncate: true,
372
- ..OpenOptions::default()
373
- },
374
- )?;
375
- let copied = copy_whole(source.as_mut(), copy.as_mut()).and(copy.close());
376
- if copied.is_err() {
377
- let _ = target.remove_file(path);
378
- }
379
- copied
380
- }
381
-
382
- /// Clear the whiteout of `path` in Layer `into` once an entry has
383
- /// landed there. A whiteout left beside the entry hides only what lies
384
- /// below, which the entry replaced, so failing to clear it fails
385
- /// nothing.
386
- fn unhide(&self, path: &str, into: usize) {
387
- let _ = self.layers[into].filesystem.remove_file(&whiteout(path));
388
- }
389
-
390
- fn mark(&self, path: &str, into: usize) -> Result<(), FilesystemError> {
391
- self.copy_up_directories(path, into)?;
392
- put_marker(self.layers[into].filesystem.as_ref(), path)
393
- }
394
-
395
- /// The Layer a whiteout over the Layer at `shown` lands in, or `None`
396
- /// when nothing below needs one; refused as read-only when no writable
397
- /// Layer sits above it.
398
- fn whiteout_layer(&self, shown: Option<usize>) -> Result<Option<usize>, FilesystemError> {
399
- match shown {
400
- Some(shown) => match self.upper()? {
401
- upper if upper < shown => Ok(Some(upper)),
402
- _ => Err(FilesystemError::ReadOnlyFilesystem),
403
- },
404
- None => Ok(None),
405
- }
406
- }
407
-
408
- /// Take `path` out of the view: a whiteout over any Layer that keeps it
409
- /// first, then out of every other Layer holding it. A read-only Layer
410
- /// keeps it, and so does a writable one `keeps` answers for, so no
411
- /// Layer changes before each is known to go; `remove` takes it out of
412
- /// one Layer.
413
- fn withdraw(
414
- &self,
415
- path: &str,
416
- keeps: impl Fn(&dyn Filesystem) -> Result<bool, FilesystemError>,
417
- remove: impl Fn(&dyn Filesystem, &str) -> Result<(), FilesystemError>,
418
- ) -> Result<(), FilesystemError> {
419
- let (holders, shadowed) = self.holders(path)?;
420
- let mut kept = Vec::new();
421
- for &index in &holders {
422
- let layer = &self.layers[index];
423
- if !layer.writable() || keeps(layer.filesystem.as_ref())? {
424
- kept.push(index);
425
- }
426
- }
427
- let shown = kept.first().copied().into_iter().chain(shadowed).min();
428
- if let Some(upper) = self.whiteout_layer(shown)? {
429
- self.mark(&whiteout(path), upper)?;
430
- }
431
- for &index in &holders {
432
- if !kept.contains(&index) {
433
- remove(self.layers[index].filesystem.as_ref(), path)?;
434
- }
435
- }
436
- Ok(())
437
- }
438
-
439
- pub(super) fn open(
440
- &self,
441
- path: &str,
442
- options: &OpenOptions,
443
- ) -> Result<Box<dyn File>, FilesystemError> {
444
- refuse_reserved(path)?;
445
- let changes = options.write || options.truncate;
446
- match self.locate(path)? {
447
- Some((
448
- _,
449
- Metadata {
450
- file_type: FileType::Directory,
451
- ..
452
- },
453
- )) => Err(FilesystemError::IsADirectory),
454
- Some(_) if options.create_new => Err(FilesystemError::AlreadyExists),
455
- Some((index, _)) if !changes || self.layers[index].writable() => {
456
- self.layers[index].filesystem.open(path, options)
457
- }
458
- Some((index, _)) => {
459
- let into = self.layers[..index]
460
- .iter()
461
- .rposition(Layer::writable)
462
- .ok_or(FilesystemError::ReadOnlyFilesystem)?;
463
- self.copy_up_file(path, index, into)?;
464
- self.layers[into].filesystem.open(path, options)
465
- }
466
- None if options.create || options.create_new => self.create_file(path, options),
467
- None => Err(FilesystemError::NotFound),
468
- }
469
- }
470
-
471
- /// A link pointing at nothing still names an entry, so an exclusive
472
- /// create finds it there; any other create reaches through it, which
473
- /// only the Layer holding the link may answer. The new file shadows
474
- /// whatever the whiteout hid before the whiteout goes, so nothing
475
- /// shows through in between.
476
- fn create_file(
477
- &self,
478
- path: &str,
479
- options: &OpenOptions,
480
- ) -> Result<Box<dyn File>, FilesystemError> {
481
- match self.locate_by(path, <dyn Filesystem>::symlink_metadata)? {
482
- Some(_) if options.create_new => Err(FilesystemError::AlreadyExists),
483
- Some((index, _)) if self.layers[index].writable() => {
484
- self.layers[index].filesystem.open(path, options)
485
- }
486
- Some(_) => Err(FilesystemError::CrossesDevices),
487
- None => {
488
- let upper = self.upper()?;
489
- self.refuse_covered(path, upper)?;
490
- self.copy_up_directories(path, upper)?;
491
- let file = self.layers[upper].filesystem.open(path, options)?;
492
- self.unhide(path, upper);
493
- Ok(file)
494
- }
495
- }
496
- }
497
-
498
- pub(super) fn metadata(&self, path: &str) -> Result<Metadata, FilesystemError> {
499
- refuse_reserved(path)?;
500
- let (index, metadata) = self.locate(path)?.ok_or(FilesystemError::NotFound)?;
501
- self.merge_identity(path, index, metadata)
166
+ /// Take `name` out of `dir`, a directory only once it is empty, as
167
+ /// overlayfs's `ovl_cleanup` takes away what a change left in its
168
+ /// workdir, so whatever another writer made in it meanwhile stays.
169
+ fn ovl_cleanup(dir: &OverlayPath, name: &str) -> Result<(), FilesystemError> {
170
+ match dir.getattr_at(name)?.file_type {
171
+ FileType::Dir => dir.do_rmdir(name),
172
+ _ => dir.do_unlink(name),
502
173
  }
174
+ }
503
175
 
504
- /// What the Layer showing `path` holds there, a link it holds
505
- /// reported as one even where it points at nothing.
506
- pub(super) fn symlink_metadata(&self, path: &str) -> Result<Metadata, FilesystemError> {
507
- refuse_reserved(path)?;
508
- let (index, metadata) = self
509
- .locate_by(path, <dyn Filesystem>::symlink_metadata)?
510
- .ok_or(FilesystemError::NotFound)?;
511
- self.merge_identity(path, index, metadata)
176
+ /// Take the whiteouts out of the directory `name` in `dir`, and nothing
177
+ /// else, as overlayfs's `ovl_cleanup_whiteouts` does before it removes
178
+ /// one; anything else stands there leaves it holding that.
179
+ fn ovl_cleanup_whiteouts(dir: &OverlayPath, name: &str) -> Result<(), FilesystemError> {
180
+ if dir.getattr_at(name)?.file_type != FileType::Dir {
181
+ return Ok(());
512
182
  }
513
-
514
- /// `path` spelled as the topmost Layer showing it stores it; a name
515
- /// no Layer shows keeps its spelling, beneath its directory's.
516
- pub(super) fn stored_path(&self, path: &str) -> Result<String, FilesystemError> {
517
- if path.is_empty() {
518
- return Ok(String::new());
519
- }
520
- match self.locate_by(path, <dyn Filesystem>::symlink_metadata) {
521
- Ok(Some((index, _))) => self.layers[index].filesystem.stored_path(path),
522
- Ok(None) | Err(FilesystemError::NotFound | FilesystemError::NotADirectory) => {
523
- Ok(join(&self.stored_path(parent(path))?, name(path)))
524
- }
525
- Err(err) => Err(err),
183
+ let inner = dir.open_dir(name)?;
184
+ for entry in inner.entries()? {
185
+ if inner.is_whiteout(&entry.name) {
186
+ inner.do_unlink(&entry.name)?;
526
187
  }
527
188
  }
189
+ Ok(())
190
+ }
528
191
 
529
- /// `path` beneath `base` with its links followed by the Layer showing
530
- /// the first of them, as a link is followed within the Layer holding
531
- /// it; a path the merged view shows no link on answers as given.
532
- pub(super) fn canonicalize_beneath(
533
- &self,
534
- base: &str,
535
- path: &str,
536
- ) -> Result<String, FilesystemError> {
537
- if path.is_empty() {
538
- return Ok(String::new());
192
+ /// Take `name` out of the workdir with all it holds, as overlayfs's
193
+ /// `ovl_workdir_cleanup` empties it of what an earlier mount left.
194
+ fn ovl_workdir_cleanup(dir: &OverlayPath, name: &str) -> Result<(), FilesystemError> {
195
+ let mut pending = vec![(dir.clone(), name.to_string())];
196
+ while let Some((dir, name)) = pending.last().cloned() {
197
+ if dir.getattr_at(&name)?.file_type != FileType::Dir {
198
+ dir.do_unlink(&name)?;
199
+ pending.pop();
200
+ continue;
539
201
  }
540
- let full = join(base, path);
541
- let start = full.len() - path.len();
542
- let ends = full[start..]
543
- .match_indices('/')
544
- .map(|(end, _)| start + end)
545
- .chain([full.len()]);
546
- for end in ends {
547
- match self.locate_by(&full[..end], <dyn Filesystem>::symlink_metadata) {
548
- Ok(Some((index, metadata))) if metadata.file_type == FileType::Symlink => {
549
- return self.layers[index]
550
- .filesystem
551
- .canonicalize_beneath(base, path)
552
- }
553
- Ok(Some(_)) => {}
554
- Ok(None) | Err(FilesystemError::NotFound | FilesystemError::NotADirectory) => break,
555
- Err(err) => return Err(err),
556
- }
202
+ let inner = dir.open_dir(&name)?;
203
+ let entries = inner.entries()?;
204
+ if entries.is_empty() {
205
+ dir.do_rmdir(&name)?;
206
+ pending.pop();
557
207
  }
558
- Ok(path.to_string())
208
+ pending.extend(entries.into_iter().map(|entry| (inner.clone(), entry.name)));
559
209
  }
210
+ Ok(())
211
+ }
560
212
 
561
- /// `metadata`, found at `path` in the Layer at `index`, as the merged
562
- /// view reports it: a directory takes the identity of the lowest Layer
563
- /// still showing it, so copying it up into a writable Layer leaves the
564
- /// identity as it was.
565
- fn merge_identity(
566
- &self,
567
- path: &str,
568
- index: usize,
569
- metadata: Metadata,
570
- ) -> Result<Metadata, FilesystemError> {
571
- if metadata.file_type != FileType::Directory {
572
- return Ok(metadata);
573
- }
574
- let (_, lowest) = self.lowest_directory(path, index, metadata)?;
575
- Ok(metadata.with_identity(lowest.dev, lowest.ino))
576
- }
213
+ /// Layers stacked into one view, topmost first, the upper among them on
214
+ /// top, as overlayfs's `ovl_fs` holds a mount's layers.
215
+ #[derive(Clone)]
216
+ pub(super) struct OverlayFs {
217
+ /// Each Layer's root, the upper's `upper` at `UPPER` where there is one, and
218
+ /// the lower Layers beneath it, topmost first.
219
+ roots: Level,
220
+ /// Where a change is built before it lands, with an upper only.
221
+ work: Option<OverlayPath>,
222
+ /// The next name a change built in `work` takes.
223
+ temp: Arc<AtomicU64>,
224
+ }
577
225
 
578
- /// The lowest Layer at or below `index` still showing the directory
579
- /// `path`, found there as `metadata`, and what it holds there.
580
- fn lowest_directory(
581
- &self,
582
- path: &str,
583
- index: usize,
584
- metadata: Metadata,
585
- ) -> Result<(usize, Metadata), FilesystemError> {
586
- let mut lowest = (index, metadata);
587
- for (at, layer) in self.layers.iter().enumerate().skip(index) {
588
- match layer.find_below(path)? {
589
- Some(found) if found.file_type == FileType::Directory => lowest = (at, found),
590
- Some(_) => break,
591
- None => {}
592
- }
593
- if layer.hides(path)? {
594
- break;
595
- }
596
- }
597
- Ok(lowest)
226
+ impl OverlayFs {
227
+ /// The view of `lower` under `upper`, its `upper` and `work` made where
228
+ /// they stand nowhere and `work` emptied of what a change left, as
229
+ /// overlayfs's mount does with its workdir.
230
+ pub(super) fn new(
231
+ lower: Vec<Arc<Filesystem>>,
232
+ upper: Option<Arc<Filesystem>>,
233
+ ) -> Result<Self, FilesystemError> {
234
+ let mut roots = vec![None];
235
+ let mut work = None;
236
+ let creator_cred = Cred::new(Vec::new());
237
+ if let Some(upper) = upper {
238
+ let root = OverlayPath::root(&upper, &creator_cred);
239
+ roots[UPPER] = Some(ovl_workdir_create(&root, UPPERDIR)?);
240
+ let made = ovl_workdir_create(&root, WORKDIR)?;
241
+ for entry in made.entries()? {
242
+ ovl_workdir_cleanup(&made, &entry.name)?;
243
+ }
244
+ work = Some(made);
245
+ }
246
+ roots.extend(
247
+ lower
248
+ .iter()
249
+ .map(|layer| Some(OverlayPath::root(layer, &creator_cred))),
250
+ );
251
+ Ok(Self {
252
+ roots,
253
+ work,
254
+ temp: Arc::default(),
255
+ })
598
256
  }
599
257
 
600
- /// The Hold of the Layer whose directory gives `path` its identity.
601
- pub(super) fn hold_directory(&self, path: &str) -> Result<Option<Hold>, FilesystemError> {
602
- let (index, metadata) = self.locate(path)?.ok_or(FilesystemError::NotFound)?;
603
- if metadata.file_type != FileType::Directory {
604
- return Err(FilesystemError::NotADirectory);
605
- }
606
- let (lowest, _) = self.lowest_directory(path, index, metadata)?;
607
- self.layers[lowest].filesystem.hold_directory(path)
258
+ /// Whether changes have an upper to land in, as `ovl_upper_mnt` answers.
259
+ pub(super) fn has_upper(&self) -> bool {
260
+ self.work.is_some()
608
261
  }
609
262
 
610
- pub(super) fn read_dir(&self, path: &str) -> Result<Vec<DirEntry>, FilesystemError> {
611
- if self.metadata(path)?.file_type != FileType::Directory {
612
- return Err(FilesystemError::NotADirectory);
613
- }
614
- let mut entries: Vec<DirEntry> = Vec::new();
615
- let mut taken: HashSet<String> = HashSet::new();
616
- for layer in &self.layers {
617
- match layer.find_below(path)? {
618
- Some(Metadata {
619
- file_type: FileType::Directory,
620
- ..
621
- }) => {
622
- let listed = layer.filesystem.read_dir(path)?;
623
- for entry in &listed {
624
- if !entry.name.starts_with(WHITEOUT) && taken.insert(entry.name.clone()) {
625
- entries.push(entry.clone());
626
- }
627
- }
628
- taken.extend(
629
- listed
630
- .iter()
631
- .filter_map(|entry| entry.name.strip_prefix(WHITEOUT))
632
- .map(str::to_string),
633
- );
634
- if listed.iter().any(|entry| entry.name == OPAQUE) {
635
- break;
636
- }
637
- }
638
- Some(_) => break,
639
- None => {}
640
- }
641
- if layer.hides(path)? {
642
- break;
643
- }
644
- }
645
- Ok(entries)
263
+ /// What the topmost Layer's root recorded it stands for.
264
+ pub(super) fn root_identity(&self) -> Option<Identity> {
265
+ let topmost = self.roots.iter().flatten().next()?;
266
+ topmost.dentry().inode()?.identity()
646
267
  }
647
268
 
648
- /// A directory made where a whiteout hid one is marked opaque before
649
- /// the whiteout goes, so the lower Layers' entries never show in it.
650
- pub(super) fn create_dir(&self, path: &str) -> Result<(), FilesystemError> {
651
- refuse_reserved(path)?;
652
- if self
653
- .locate_by(path, <dyn Filesystem>::symlink_metadata)?
654
- .is_some()
655
- {
656
- return Err(FilesystemError::AlreadyExists);
657
- }
658
- let upper = self.upper()?;
659
- self.refuse_covered(path, upper)?;
660
- self.copy_up_directories(path, upper)?;
661
- let layer = &self.layers[upper];
662
- let hid = layer.holds(&whiteout(path))?;
663
- layer.filesystem.create_dir(path)?;
664
- if hid {
665
- if let Err(err) = self.mark(&join(path, OPAQUE), upper) {
666
- let _ = layer.filesystem.remove_dir(path);
667
- return Err(err);
668
- }
669
- self.unhide(path, upper);
670
- }
671
- Ok(())
269
+ /// The state of the root, which stands for every Layer's root, as overlayfs's
270
+ /// `ovl_get_root` keeps it on the root's inode as it mounts.
271
+ pub(super) fn root_dir(&self) -> OverlayDirInode {
272
+ OverlayDirInode::new(self.roots.clone())
672
273
  }
673
274
 
674
- /// A writable Layer's copy of the directory may hold only markers; any
675
- /// other entry there means the view is not what was found empty. A
676
- /// copy still holding what a Layer above hides stays, beneath the
677
- /// whiteout.
678
- pub(super) fn remove_dir(&self, path: &str) -> Result<(), FilesystemError> {
679
- if self.symlink_metadata(path)?.file_type != FileType::Directory {
680
- return Err(FilesystemError::NotADirectory);
681
- }
682
- if !self.read_dir(path)?.is_empty() {
683
- return Err(FilesystemError::DirectoryNotEmpty);
684
- }
685
- self.withdraw(
686
- path,
687
- |filesystem| {
688
- Ok(filesystem
689
- .read_dir(path)?
690
- .iter()
691
- .any(|entry| !entry.name.starts_with(WHITEOUT)))
692
- },
693
- |filesystem, path| without_markers(filesystem, path, || filesystem.remove_dir(path)),
694
- )
275
+ /// The directory of the view `dentry` names, its state found on the inode
276
+ /// its lookup, or the mount for the root, recorded.
277
+ pub(super) fn dir(&self, dentry: &Dentry) -> Result<ViewDir, FilesystemError> {
278
+ let inode = dentry.inode().ok_or(FilesystemError::NotFound)?;
279
+ let inode = OverlayDirInode::of(&inode).ok_or(FilesystemError::NotADirectory)?;
280
+ Ok(ViewDir {
281
+ dentry: dentry.get(),
282
+ inode,
283
+ })
695
284
  }
696
285
 
697
- /// A link is removed as itself, wherever it points.
698
- pub(super) fn remove_file(&self, path: &str) -> Result<(), FilesystemError> {
699
- if self.symlink_metadata(path)?.file_type == FileType::Directory {
700
- return Err(FilesystemError::IsADirectory);
701
- }
702
- self.withdraw(
703
- path,
704
- |_| Ok(false),
705
- |filesystem, path| filesystem.remove_file(path),
706
- )
286
+ /// The directory holding the entry `dentry` names.
287
+ pub(super) fn parent(&self, dentry: &Dentry) -> Result<ViewDir, FilesystemError> {
288
+ self.dir(&dentry.parent())
707
289
  }
708
290
 
709
- /// A directory moves only when the topmost writable Layer alone holds
710
- /// it; moving one out of another would mean copying up everything
711
- /// beneath it, so it is refused as crossing devices, as overlayfs
712
- /// refuses it without `redirect_dir`; so is a link or another kind a
713
- /// lower Layer holds, which no copy up can make. What the view holds
714
- /// at `to` is checked as rename(2) checks it, and a directory moved
715
- /// over one a lower Layer holds is marked opaque before it moves, so
716
- /// it never lands without the marker. The source is whited out before
717
- /// it moves, so it never shows from a read-only Layer once moved. What
718
- /// a lower writable Layer holds is refused as crossing devices, as
719
- /// unionfs-fuse refuses a rename across writable branches, since moving
720
- /// it would change two Layers that could fail apart.
721
- pub(super) fn rename(
722
- &self,
723
- from: &str,
724
- to: &str,
725
- rename: Rename,
726
- ) -> Result<(), FilesystemError> {
727
- refuse_reserved(to)?;
728
- let moving = self.symlink_metadata(from)?;
729
- if from == to {
730
- return Ok(());
731
- }
732
- rename.check(moving.file_type)?;
733
- if is_beneath(to, from) {
734
- return Err(beneath_itself(moving.file_type));
735
- }
736
- let replaced = self
737
- .locate_by(to, <dyn Filesystem>::symlink_metadata)?
738
- .map(|(_, metadata)| metadata.file_type);
739
- // Only a directory replaces a directory, and only an empty one.
740
- match (moving.file_type == FileType::Directory, replaced) {
741
- (false, Some(FileType::Directory)) => return Err(FilesystemError::IsADirectory),
742
- (true, Some(FileType::Directory)) if !self.read_dir(to)?.is_empty() => {
743
- return Err(FilesystemError::DirectoryNotEmpty)
744
- }
745
- (true, Some(found)) if found != FileType::Directory => {
746
- return Err(FilesystemError::NotADirectory)
747
- }
748
- _ => {}
749
- }
750
- let upper = self.upper()?;
751
- self.refuse_covered(to, upper)?;
752
- let mut replaces_below = false;
753
- if moving.file_type == FileType::Directory {
754
- for (index, layer) in self.layers.iter().enumerate() {
755
- replaces_below |= index != upper && layer.holds(to)?;
756
- }
757
- }
758
- let (holders, shadowed) = self.holders(from)?;
759
- if holders
760
- .iter()
761
- .any(|&index| index != upper && self.layers[index].writable())
762
- {
763
- return Err(FilesystemError::CrossesDevices);
764
- }
765
- let read_only = holders
766
- .iter()
767
- .copied()
768
- .find(|&index| !self.layers[index].writable());
769
- let shown_below = self.whiteout_layer(read_only.into_iter().chain(shadowed).min())?;
770
- match moving.file_type {
771
- FileType::Directory if holders != [upper] => {
772
- return Err(FilesystemError::CrossesDevices)
773
- }
774
- FileType::File if holders.first() != Some(&upper) => {
775
- let shown = holders.first().ok_or(FilesystemError::NotFound)?;
776
- self.copy_up_file(from, *shown, upper)?
777
- }
778
- FileType::Symlink | FileType::Unknown if holders.first() != Some(&upper) => {
779
- return Err(FilesystemError::CrossesDevices)
780
- }
781
- _ => {}
782
- }
783
- self.copy_up_directories(to, upper)?;
784
- if let Some(upper) = shown_below {
785
- self.mark(&whiteout(from), upper)?;
786
- }
787
- let filesystem = self.layers[upper].filesystem.as_ref();
788
- let opaque = join(from, OPAQUE);
789
- let marks = replaces_below && !self.layers[upper].holds(&opaque)?;
790
- if marks {
791
- put_marker(filesystem, &opaque)?;
792
- }
793
- let moved = || filesystem.rename(from, to, rename);
794
- let moved = if replaced == Some(FileType::Directory) && self.layers[upper].holds(to)? {
795
- without_markers(filesystem, to, moved)
796
- } else {
797
- moved()
798
- };
799
- if let Err(err) = moved {
800
- if marks {
801
- let _ = filesystem.remove_file(&opaque);
802
- }
803
- return Err(err);
804
- }
805
- self.unhide(to, upper);
806
- Ok(())
291
+ /// The workdir and a name in it no change has taken, as overlayfs's
292
+ /// `ovl_tempname` names one.
293
+ fn tempname(&self) -> Result<(&OverlayPath, String), FilesystemError> {
294
+ let work = self
295
+ .work
296
+ .as_ref()
297
+ .ok_or(FilesystemError::ReadOnlyFilesystem)?;
298
+ let next = self.temp.fetch_add(1, Ordering::Relaxed);
299
+ Ok((work, format!("#{next:x}")))
807
300
  }
808
301
  }