kobako 0.27.0 → 0.29.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.
- checksums.yaml +4 -4
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +241 -0
- data/Cargo.lock +132 -126
- data/README.md +240 -147
- data/ROADMAP.md +28 -22
- data/SECURITY.md +2 -2
- data/crates/kobako-runtime/CHANGELOG.md +117 -0
- data/crates/kobako-runtime/Cargo.toml +27 -6
- data/crates/kobako-runtime/README.md +16 -10
- data/crates/kobako-runtime/src/dispatch.rs +8 -0
- data/crates/kobako-runtime/src/error.rs +8 -9
- data/crates/kobako-runtime/src/filesystem/directory/spelling.rs +194 -0
- data/crates/kobako-runtime/src/filesystem/directory.rs +1082 -0
- data/crates/kobako-runtime/src/filesystem/memory.rs +1100 -0
- data/crates/kobako-runtime/src/filesystem/overlay/exclusive.rs +88 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack.rs +808 -0
- data/crates/kobako-runtime/src/filesystem/overlay.rs +1955 -0
- data/crates/kobako-runtime/src/filesystem/path.rs +60 -0
- data/crates/kobako-runtime/src/filesystem/temporary.rs +167 -0
- data/crates/kobako-runtime/src/filesystem.rs +392 -0
- data/crates/kobako-runtime/src/grant.rs +20 -0
- data/crates/kobako-runtime/src/lib.rs +12 -2
- data/crates/kobako-runtime/src/mount.rs +178 -0
- data/crates/kobako-runtime/src/profile.rs +4 -4
- data/crates/kobako-runtime/src/runtime.rs +43 -17
- data/crates/kobako-runtime/src/snapshot.rs +33 -15
- data/crates/kobako-runtime/src/yielder.rs +14 -6
- data/crates/kobako-transport/CHANGELOG.md +45 -0
- data/crates/kobako-transport/Cargo.toml +1 -1
- data/crates/kobako-transport/README.md +21 -12
- data/crates/kobako-transport/src/abi.rs +11 -14
- data/crates/kobako-transport/src/envelope/bytes.rs +18 -6
- data/crates/kobako-transport/src/envelope/compile_reply.rs +139 -0
- data/crates/kobako-transport/src/envelope/fault.rs +2 -1
- data/crates/kobako-transport/src/envelope/outcome.rs +17 -16
- data/crates/kobako-transport/src/envelope/preamble.rs +513 -0
- data/crates/kobako-transport/src/envelope/reply.rs +103 -61
- data/crates/kobako-transport/src/envelope/request.rs +305 -0
- data/crates/kobako-transport/src/envelope/run.rs +1 -2
- data/crates/kobako-transport/src/envelope/tag.rs +49 -0
- data/crates/kobako-transport/src/envelope/unsupported.rs +105 -0
- data/crates/kobako-transport/src/envelope.rs +19 -12
- data/crates/kobako-transport/src/lib.rs +0 -2
- data/crates/kobako-wasmtime/CHANGELOG.md +163 -0
- data/crates/kobako-wasmtime/Cargo.toml +23 -16
- data/crates/kobako-wasmtime/README.md +6 -4
- data/crates/kobako-wasmtime/src/abi.rs +67 -36
- data/crates/kobako-wasmtime/src/ambient.rs +64 -5
- data/crates/kobako-wasmtime/src/cache.rs +204 -127
- data/crates/kobako-wasmtime/src/capture.rs +4 -11
- data/crates/kobako-wasmtime/src/config.rs +69 -15
- data/crates/kobako-wasmtime/src/descriptors.rs +167 -0
- data/crates/kobako-wasmtime/src/dispatch.rs +95 -87
- data/crates/kobako-wasmtime/src/driver.rs +174 -125
- data/crates/kobako-wasmtime/src/exports.rs +88 -34
- data/crates/kobako-wasmtime/src/guest_mem.rs +151 -138
- data/crates/kobako-wasmtime/src/imports.rs +46 -0
- data/crates/kobako-wasmtime/src/instance_pre.rs +19 -58
- data/crates/kobako-wasmtime/src/invocation.rs +84 -412
- data/crates/kobako-wasmtime/src/io.rs +89 -0
- data/crates/kobako-wasmtime/src/lib.rs +7 -24
- data/crates/kobako-wasmtime/src/limiter.rs +253 -0
- data/crates/kobako-wasmtime/src/preview1/args.rs +60 -0
- data/crates/kobako-wasmtime/src/preview1/beneath.rs +188 -0
- data/crates/kobako-wasmtime/src/preview1/differential/mount.rs +609 -0
- data/crates/kobako-wasmtime/src/preview1/differential.rs +372 -0
- data/crates/kobako-wasmtime/src/preview1/errno.rs +267 -0
- data/crates/kobako-wasmtime/src/preview1/fd.rs +672 -0
- data/crates/kobako-wasmtime/src/preview1/memory.rs +205 -0
- data/crates/kobako-wasmtime/src/preview1/mount_view.rs +2499 -0
- data/crates/kobako-wasmtime/src/preview1/path.rs +421 -0
- data/crates/kobako-wasmtime/src/preview1/poll.rs +333 -0
- data/crates/kobako-wasmtime/src/preview1/sock.rs +30 -0
- data/crates/kobako-wasmtime/src/preview1.rs +461 -0
- data/crates/kobako-wasmtime/src/trap.rs +66 -59
- data/crates/kobako-wasmtime/src/wasi.rs +317 -0
- data/crates/kobako-wasmtime/tests/compile_posture.rs +43 -0
- data/crates/kobako-wasmtime/tests/guest_file.rs +1054 -0
- data/crates/kobako-wasmtime/tests/preamble_flags.rs +70 -0
- data/crates/kobako-wasmtime/tests/snippet_compile.rs +95 -0
- data/crates/kobako-wasmtime/tests/support/mod.rs +30 -0
- data/crates/kobako-wasmtime/tests/unanswered_call.rs +86 -0
- data/crates/kobako-wasmtime/tests/unsupported_call.rs +51 -0
- data/data/kobako.wasm +0 -0
- data/ext/kobako/Cargo.toml +4 -2
- data/ext/kobako/src/runtime/bridge.rs +177 -113
- data/ext/kobako/src/runtime/errors.rs +26 -32
- data/ext/kobako/src/runtime/escape.rs +53 -0
- data/ext/kobako/src/runtime/filesystem/grants.rs +91 -0
- data/ext/kobako/src/runtime/filesystem/ruby.rs +400 -0
- data/ext/kobako/src/runtime/filesystem.rs +236 -0
- data/ext/kobako/src/runtime/gvl.rs +11 -13
- data/ext/kobako/src/runtime/pinned_value.rs +31 -0
- data/ext/kobako/src/runtime/snapshot.rs +127 -0
- data/ext/kobako/src/runtime.rs +190 -312
- data/lib/kobako/capture.rb +1 -4
- data/lib/kobako/catalog/extensions.rb +21 -59
- data/lib/kobako/catalog/flags.rb +29 -0
- data/lib/kobako/catalog/grants.rb +30 -0
- data/lib/kobako/catalog/handles.rb +7 -54
- data/lib/kobako/catalog/mount.rb +27 -0
- data/lib/kobako/catalog/services.rb +17 -60
- data/lib/kobako/catalog/snippets.rb +41 -69
- data/lib/kobako/catalog.rb +13 -10
- data/lib/kobako/codec/decoder.rb +10 -28
- data/lib/kobako/codec/encoder.rb +3 -22
- data/lib/kobako/codec/error.rb +6 -27
- data/lib/kobako/codec/ext_types.rb +72 -94
- data/lib/kobako/codec/handle_walk.rb +65 -130
- data/lib/kobako/codec/nesting.rb +3 -8
- data/lib/kobako/codec/state.rb +21 -11
- data/lib/kobako/codec/utils.rb +6 -34
- data/lib/kobako/codec/value_budget.rb +86 -0
- data/lib/kobako/codec.rb +10 -22
- data/lib/kobako/constant_name.rb +8 -0
- data/lib/kobako/context.rb +83 -117
- data/lib/kobako/errors.rb +57 -132
- data/lib/kobako/execution.rb +16 -21
- data/lib/kobako/extension.rb +23 -40
- data/lib/kobako/filesystem.rb +38 -0
- data/lib/kobako/handle.rb +7 -12
- data/lib/kobako/outcome.rb +51 -80
- data/lib/kobako/payload/arguments.rb +1 -16
- data/lib/kobako/payload.rb +4 -12
- data/lib/kobako/pool.rb +25 -55
- data/lib/kobako/runtime.rb +7 -21
- data/lib/kobako/sandbox.rb +128 -137
- data/lib/kobako/sandbox_options.rb +71 -90
- data/lib/kobako/snippet/bytecode.rb +11 -0
- data/lib/kobako/snippet/source.rb +1 -16
- data/lib/kobako/snippet.rb +2 -10
- data/lib/kobako/transport/block_failure.rb +35 -0
- data/lib/kobako/transport/call.rb +1 -14
- data/lib/kobako/transport/dispatcher.rb +127 -206
- data/lib/kobako/transport/error.rb +6 -20
- data/lib/kobako/transport/exposure.rb +10 -17
- data/lib/kobako/transport/reflection.rb +108 -63
- data/lib/kobako/transport/run.rb +17 -71
- data/lib/kobako/transport/yielder.rb +20 -94
- data/lib/kobako/transport.rb +4 -15
- data/lib/kobako/unresolved.rb +4 -11
- data/lib/kobako/usage.rb +2 -6
- data/lib/kobako/version.rb +1 -1
- data/lib/kobako.rb +1 -0
- data/sig/kobako/catalog/extensions.rbs +2 -2
- data/sig/kobako/catalog/flags.rbs +13 -0
- data/sig/kobako/catalog/grants.rbs +13 -0
- data/sig/kobako/catalog/mount.rbs +28 -0
- data/sig/kobako/catalog/services.rbs +3 -5
- data/sig/kobako/catalog/snippets.rbs +5 -5
- data/sig/kobako/catalog.rbs +1 -0
- data/sig/kobako/codec/ext_types.rbs +10 -8
- data/sig/kobako/codec/handle_walk.rbs +9 -7
- data/sig/kobako/codec/state.rbs +7 -1
- data/sig/kobako/codec/utils.rbs +2 -2
- data/sig/kobako/codec/value_budget.rbs +23 -0
- data/sig/kobako/codec.rbs +4 -2
- data/sig/kobako/constant_name.rbs +3 -0
- data/sig/kobako/context.rbs +15 -8
- data/sig/kobako/errors.rbs +10 -4
- data/sig/kobako/filesystem.rbs +88 -0
- data/sig/kobako/guest_yielder.rbs +9 -0
- data/sig/kobako/outcome.rbs +6 -8
- data/sig/kobako/pool.rbs +1 -1
- data/sig/kobako/runtime.rbs +21 -45
- data/sig/kobako/sandbox.rbs +11 -1
- data/sig/kobako/sandbox_options.rbs +25 -7
- data/sig/kobako/snapshot.rbs +20 -0
- data/sig/kobako/snippet/bytecode.rbs +12 -0
- data/sig/kobako/transport/block_failure.rbs +13 -0
- data/sig/kobako/transport/dispatcher.rbs +15 -19
- data/sig/kobako/transport/exposure.rbs +1 -1
- data/sig/kobako/transport/reflection.rbs +19 -4
- data/sig/kobako/transport/run.rbs +1 -3
- data/sig/kobako/transport/yielder.rbs +5 -9
- data/sig/kobako/transport.rbs +9 -8
- metadata +75 -10
- data/crates/kobako-transport/src/envelope/invocation_frames.rs +0 -241
- data/crates/kobako-wasmtime/src/frames.rs +0 -273
- data/lib/kobako/snippet/binary.rb +0 -26
- data/rust-toolchain.toml +0 -15
- data/sig/kobako/snippet/binary.rbs +0 -12
|
@@ -0,0 +1,808 @@
|
|
|
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.
|
|
4
|
+
//!
|
|
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.
|
|
9
|
+
|
|
10
|
+
use std::collections::HashSet;
|
|
11
|
+
use std::sync::Arc;
|
|
12
|
+
|
|
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
|
+
};
|
|
18
|
+
|
|
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";
|
|
23
|
+
|
|
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>;
|
|
26
|
+
|
|
27
|
+
#[derive(Clone)]
|
|
28
|
+
struct Layer {
|
|
29
|
+
filesystem: Arc<dyn Filesystem>,
|
|
30
|
+
access: Access,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
impl Layer {
|
|
34
|
+
fn writable(&self) -> bool {
|
|
35
|
+
self.access == Access::ReadWrite
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
fn find(&self, path: &str) -> Result<Option<Metadata>, FilesystemError> {
|
|
39
|
+
self.find_by(path, <dyn Filesystem>::metadata)
|
|
40
|
+
}
|
|
41
|
+
|
|
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
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
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
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
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
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
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);
|
|
80
|
+
}
|
|
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)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/// The whiteout hiding `path`, beside it in its directory.
|
|
100
|
+
fn whiteout(path: &str) -> String {
|
|
101
|
+
join(parent(path), &format!("{WHITEOUT}{}", name(path)))
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
fn copy_whole(source: &mut dyn File, target: &mut dyn File) -> Result<(), FilesystemError> {
|
|
105
|
+
let mut buf = vec![0; 64 * 1024];
|
|
106
|
+
let mut offset = 0;
|
|
107
|
+
loop {
|
|
108
|
+
let read = source.read_at(&mut buf, offset)?;
|
|
109
|
+
if read == 0 {
|
|
110
|
+
return Ok(());
|
|
111
|
+
}
|
|
112
|
+
let mut written = 0;
|
|
113
|
+
while written < read {
|
|
114
|
+
match target.write_at(&buf[written..read], offset + written as u64)? {
|
|
115
|
+
0 => {
|
|
116
|
+
return Err(FilesystemError::Other(
|
|
117
|
+
"a copy up's write wrote nothing".to_string(),
|
|
118
|
+
))
|
|
119
|
+
}
|
|
120
|
+
wrote => written += wrote,
|
|
121
|
+
}
|
|
122
|
+
}
|
|
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
|
+
}
|
|
159
|
+
changed
|
|
160
|
+
}
|
|
161
|
+
|
|
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()
|
|
174
|
+
}
|
|
175
|
+
|
|
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);
|
|
185
|
+
}
|
|
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
|
+
}
|
|
194
|
+
|
|
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)
|
|
502
|
+
}
|
|
503
|
+
|
|
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)
|
|
512
|
+
}
|
|
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),
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
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());
|
|
539
|
+
}
|
|
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
|
+
}
|
|
557
|
+
}
|
|
558
|
+
Ok(path.to_string())
|
|
559
|
+
}
|
|
560
|
+
|
|
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
|
+
}
|
|
577
|
+
|
|
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)
|
|
598
|
+
}
|
|
599
|
+
|
|
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)
|
|
608
|
+
}
|
|
609
|
+
|
|
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)
|
|
646
|
+
}
|
|
647
|
+
|
|
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(())
|
|
672
|
+
}
|
|
673
|
+
|
|
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
|
+
)
|
|
695
|
+
}
|
|
696
|
+
|
|
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
|
+
)
|
|
707
|
+
}
|
|
708
|
+
|
|
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(())
|
|
807
|
+
}
|
|
808
|
+
}
|