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.
- checksums.yaml +4 -4
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +217 -10
- data/Cargo.lock +139 -141
- data/README.md +96 -574
- data/ROADMAP.md +2 -1
- data/crates/kobako-runtime/CHANGELOG.md +269 -1
- data/crates/kobako-runtime/Cargo.toml +25 -15
- data/crates/kobako-runtime/README.md +7 -3
- data/crates/kobako-runtime/src/dispatch.rs +1 -1
- data/crates/kobako-runtime/src/endpoint/buffer.rs +192 -0
- data/crates/kobako-runtime/src/endpoint/capture.rs +115 -0
- data/crates/kobako-runtime/src/endpoint/dispatch.rs +253 -0
- data/crates/kobako-runtime/src/endpoint/invoke.rs +194 -0
- data/crates/kobako-runtime/src/endpoint/memory_cap.rs +238 -0
- data/crates/kobako-runtime/src/endpoint.rs +166 -0
- data/crates/kobako-runtime/src/filesystem/bind/fid.rs +82 -0
- data/crates/kobako-runtime/src/filesystem/{directory/spelling.rs → bind/lookup.rs} +91 -49
- data/crates/kobako-runtime/src/filesystem/bind/walk.rs +249 -0
- data/crates/kobako-runtime/src/filesystem/bind.rs +2665 -0
- data/crates/kobako-runtime/src/filesystem/overlay/file.rs +89 -0
- data/crates/kobako-runtime/src/filesystem/overlay/inode.rs +106 -0
- data/crates/kobako-runtime/src/filesystem/overlay/path.rs +285 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/copy_up.rs +135 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/dir.rs +263 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/namei.rs +135 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/readdir.rs +57 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack.rs +227 -734
- data/crates/kobako-runtime/src/filesystem/overlay.rs +2649 -1135
- data/crates/kobako-runtime/src/filesystem/path.rs +2 -41
- data/crates/kobako-runtime/src/filesystem/pipe.rs +221 -0
- data/crates/kobako-runtime/src/filesystem/rdir.rs +101 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs/tmpfile.rs +32 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs/whiteout.rs +85 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs/xattr.rs +22 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs.rs +1985 -0
- data/crates/kobako-runtime/src/filesystem/wrap.rs +224 -0
- data/crates/kobako-runtime/src/filesystem.rs +21 -387
- data/crates/kobako-runtime/src/hashtable.rs +44 -0
- data/crates/kobako-runtime/src/lib.rs +11 -6
- data/crates/kobako-runtime/src/locking/seqlock.rs +66 -0
- data/crates/kobako-runtime/src/locking.rs +218 -0
- data/crates/kobako-runtime/src/mount.rs +31 -104
- data/crates/kobako-runtime/src/profile.rs +137 -1
- data/crates/kobako-runtime/src/sched.rs +210 -0
- data/crates/kobako-runtime/src/security.rs +696 -0
- data/crates/kobako-runtime/src/vfs/attr.rs +162 -0
- data/crates/kobako-runtime/src/vfs/cred.rs +334 -0
- data/crates/kobako-runtime/src/vfs/dentry.rs +1412 -0
- data/crates/kobako-runtime/src/vfs/errno.rs +153 -0
- data/crates/kobako-runtime/src/vfs/file.rs +1915 -0
- data/crates/kobako-runtime/src/vfs/inode.rs +429 -0
- data/crates/kobako-runtime/src/vfs/kern_path.rs +217 -0
- data/crates/kobako-runtime/src/vfs/mount.rs +59 -0
- data/crates/kobako-runtime/src/vfs/namei.rs +5708 -0
- data/crates/kobako-runtime/src/vfs/openat2.rs +116 -0
- data/crates/kobako-runtime/src/vfs/path.rs +64 -0
- data/crates/kobako-runtime/src/vfs/readdir.rs +163 -0
- data/crates/kobako-runtime/src/vfs/stat.rs +157 -0
- data/crates/kobako-runtime/src/vfs/super_block.rs +122 -0
- data/crates/kobako-runtime/src/vfs/utimes.rs +289 -0
- data/crates/kobako-runtime/src/vfs.rs +60 -0
- data/crates/kobako-runtime/tests/linux_baseline/calls.rs +171 -0
- data/crates/kobako-runtime/tests/linux_baseline/concurrent.rs +276 -0
- data/crates/kobako-runtime/tests/linux_baseline/kobako.rs +323 -0
- data/crates/kobako-runtime/tests/linux_baseline/linux-baseline-expected.txt +16 -0
- data/crates/kobako-runtime/tests/linux_baseline/linux.rs +449 -0
- data/crates/kobako-runtime/tests/linux_baseline/main.rs +611 -0
- data/crates/kobako-runtime/tests/linux_baseline/permissions.rs +236 -0
- data/crates/kobako-transport/CHANGELOG.md +8 -2
- data/crates/kobako-transport/Cargo.toml +3 -3
- data/crates/kobako-transport/README.md +6 -5
- data/crates/kobako-transport/src/envelope/bytes.rs +33 -25
- data/crates/kobako-transport/src/envelope/call.rs +5 -5
- data/crates/kobako-transport/src/envelope/compile_reply.rs +2 -2
- data/crates/kobako-transport/src/envelope/error_record.rs +3 -3
- data/crates/kobako-transport/src/envelope/fault.rs +2 -2
- data/crates/kobako-transport/src/envelope/outcome.rs +3 -3
- data/crates/kobako-transport/src/envelope/preamble.rs +12 -12
- data/crates/kobako-transport/src/envelope/reply.rs +2 -2
- data/crates/kobako-transport/src/envelope/request.rs +2 -2
- data/crates/kobako-transport/src/envelope/run.rs +1 -1
- data/crates/kobako-transport/src/envelope/unsupported.rs +2 -2
- data/crates/kobako-transport/src/envelope.rs +2 -2
- data/crates/kobako-wasmtime/CHANGELOG.md +154 -8
- data/crates/kobako-wasmtime/Cargo.toml +15 -14
- data/crates/kobako-wasmtime/README.md +9 -5
- data/crates/kobako-wasmtime/src/abi.rs +12 -20
- data/crates/kobako-wasmtime/src/ambient.rs +1 -2
- data/crates/kobako-wasmtime/src/cache.rs +5 -5
- data/crates/kobako-wasmtime/src/config.rs +22 -7
- data/crates/kobako-wasmtime/src/descriptors.rs +133 -100
- data/crates/kobako-wasmtime/src/driver.rs +29 -96
- data/crates/kobako-wasmtime/src/exports.rs +5 -16
- data/crates/kobako-wasmtime/src/guest.rs +38 -0
- data/crates/kobako-wasmtime/src/imports.rs +14 -8
- data/crates/kobako-wasmtime/src/instance_pre.rs +3 -3
- data/crates/kobako-wasmtime/src/invocation.rs +26 -46
- data/crates/kobako-wasmtime/src/lib.rs +1 -4
- data/crates/kobako-wasmtime/src/limiter.rs +6 -230
- data/crates/kobako-wasmtime/src/preview1/differential/mount.rs +69 -46
- data/crates/kobako-wasmtime/src/preview1/differential.rs +121 -7
- data/crates/kobako-wasmtime/src/preview1/errno.rs +74 -206
- data/crates/kobako-wasmtime/src/preview1/fd.rs +1248 -500
- data/crates/kobako-wasmtime/src/preview1/memory.rs +117 -150
- data/crates/kobako-wasmtime/src/preview1/mount_view/wrap.rs +211 -0
- data/crates/kobako-wasmtime/src/preview1/mount_view.rs +1939 -672
- data/crates/kobako-wasmtime/src/preview1/path.rs +230 -349
- data/crates/kobako-wasmtime/src/preview1/poll.rs +148 -100
- data/crates/kobako-wasmtime/src/preview1/sock.rs +6 -23
- data/crates/kobako-wasmtime/src/preview1/testsuite-expected.txt +6 -0
- data/crates/kobako-wasmtime/src/preview1/testsuite.rs +157 -0
- data/crates/kobako-wasmtime/src/preview1.rs +464 -413
- data/crates/kobako-wasmtime/src/trap.rs +9 -9
- data/crates/kobako-wasmtime/src/wasi.rs +53 -37
- data/crates/kobako-wasmtime/tests/{guest_file.rs → integration/guest_file.rs} +232 -201
- data/crates/kobako-wasmtime/tests/integration/main.rs +29 -0
- data/crates/kobako-wasmtime/tests/{preamble_flags.rs → integration/preamble_flags.rs} +1 -1
- data/crates/kobako-wasmtime/tests/{snippet_compile.rs → integration/snippet_compile.rs} +1 -1
- data/crates/kobako-wasmtime/tests/{unanswered_call.rs → integration/unanswered_call.rs} +1 -1
- data/crates/kobako-wasmtime/tests/{unsupported_call.rs → integration/unsupported_call.rs} +1 -1
- data/crates/kobako-wasmtime/witx/typenames.witx +750 -0
- data/crates/kobako-wasmtime/witx/wasi_snapshot_preview1.witx +441 -0
- data/data/kobako.wasm +0 -0
- data/ext/kobako/Cargo.toml +1 -1
- data/ext/kobako/src/runtime/bridge.rs +1 -1
- data/ext/kobako/src/runtime/errors.rs +1 -1
- data/ext/kobako/src/runtime/escape.rs +28 -14
- data/ext/kobako/src/runtime/filesystem/grants.rs +21 -5
- data/ext/kobako/src/runtime/filesystem/ruby.rs +464 -156
- data/ext/kobako/src/runtime/filesystem.rs +109 -137
- data/ext/kobako/src/runtime/gvl.rs +43 -8
- data/ext/kobako/src/runtime/snapshot.rs +1 -1
- data/ext/kobako/src/runtime.rs +6 -1
- data/lib/kobako/catalog/extensions.rb +5 -5
- data/lib/kobako/catalog/flags.rb +2 -2
- data/lib/kobako/catalog/handles.rb +6 -6
- data/lib/kobako/catalog/mount.rb +2 -10
- data/lib/kobako/catalog/services.rb +2 -2
- data/lib/kobako/catalog/snippets.rb +7 -7
- data/lib/kobako/catalog.rb +1 -1
- data/lib/kobako/codec/decoder.rb +10 -10
- data/lib/kobako/codec/ext_types.rb +3 -5
- data/lib/kobako/codec/handle_walk.rb +4 -6
- data/lib/kobako/codec/nesting.rb +7 -7
- data/lib/kobako/codec/utils.rb +1 -1
- data/lib/kobako/codec/value_budget.rb +3 -3
- data/lib/kobako/context.rb +5 -5
- data/lib/kobako/filesystem.rb +44 -13
- data/lib/kobako/payload/arguments.rb +2 -2
- data/lib/kobako/pool.rb +4 -4
- data/lib/kobako/sandbox.rb +28 -28
- data/lib/kobako/sandbox_options.rb +2 -3
- data/lib/kobako/snippet/bytecode.rb +1 -1
- data/lib/kobako/snippet/source.rb +1 -1
- data/lib/kobako/transport/dispatcher.rb +5 -5
- data/lib/kobako/transport/exposure.rb +2 -2
- data/lib/kobako/transport/reflection.rb +7 -7
- data/lib/kobako/transport/run.rb +5 -5
- data/lib/kobako/transport/yielder.rb +1 -1
- data/lib/kobako/version.rb +1 -1
- data/lib/kobako.rb +34 -0
- data/release-please-config.json +1 -0
- data/sig/kobako/catalog/extensions.rbs +3 -3
- data/sig/kobako/catalog/handles.rbs +3 -3
- data/sig/kobako/catalog/mount.rbs +0 -4
- data/sig/kobako/catalog/services.rbs +1 -1
- data/sig/kobako/catalog/snippets.rbs +3 -3
- data/sig/kobako/catalog.rbs +1 -1
- data/sig/kobako/codec/decoder.rbs +1 -1
- data/sig/kobako/codec/handle_walk.rbs +1 -1
- data/sig/kobako/codec/nesting.rbs +2 -2
- data/sig/kobako/codec/utils.rbs +1 -1
- data/sig/kobako/codec/value_budget.rbs +1 -1
- data/sig/kobako/codec.rbs +1 -2
- data/sig/kobako/context.rbs +1 -1
- data/sig/kobako/filesystem.rbs +19 -14
- data/sig/kobako/guest_yielder.rbs +1 -1
- data/sig/kobako/payload/arguments.rbs +1 -1
- data/sig/kobako/pool.rbs +2 -2
- data/sig/kobako/runtime.rbs +1 -1
- data/sig/kobako/sandbox.rbs +3 -5
- data/sig/kobako/sandbox_options.rbs +1 -1
- data/sig/kobako/transport/dispatcher.rbs +2 -2
- data/sig/kobako/transport/exposure.rbs +1 -1
- data/sig/kobako/transport/reflection.rbs +2 -2
- data/sig/kobako/transport/run.rbs +3 -3
- data/sig/kobako/transport.rbs +1 -1
- metadata +67 -19
- data/crates/kobako-runtime/src/filesystem/directory.rs +0 -1082
- data/crates/kobako-runtime/src/filesystem/memory.rs +0 -1100
- data/crates/kobako-runtime/src/filesystem/overlay/exclusive.rs +0 -88
- data/crates/kobako-runtime/src/filesystem/temporary.rs +0 -167
- data/crates/kobako-wasmtime/src/capture.rs +0 -96
- data/crates/kobako-wasmtime/src/dispatch.rs +0 -125
- data/crates/kobako-wasmtime/src/guest_mem.rs +0 -271
- data/crates/kobako-wasmtime/src/io.rs +0 -89
- data/crates/kobako-wasmtime/src/preview1/args.rs +0 -60
- data/crates/kobako-wasmtime/src/preview1/beneath.rs +0 -188
- /data/crates/kobako-wasmtime/tests/{compile_posture.rs → integration/compile_posture.rs} +0 -0
- /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
|
|
3
|
-
//!
|
|
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
|
-
//!
|
|
6
|
-
//!
|
|
7
|
-
//!
|
|
8
|
-
//!
|
|
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::
|
|
21
|
+
use std::sync::atomic::{AtomicU64, Ordering};
|
|
11
22
|
use std::sync::Arc;
|
|
12
23
|
|
|
13
|
-
use super::
|
|
14
|
-
use super::
|
|
15
|
-
|
|
16
|
-
|
|
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
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
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
|
-
///
|
|
25
|
-
|
|
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
|
|
29
|
-
|
|
30
|
-
|
|
48
|
+
pub(super) struct ViewDir {
|
|
49
|
+
dentry: Arc<Dentry>,
|
|
50
|
+
inode: Arc<OverlayDirInode>,
|
|
31
51
|
}
|
|
32
52
|
|
|
33
|
-
impl
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
39
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
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
|
-
///
|
|
100
|
-
|
|
101
|
-
|
|
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(
|
|
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
|
|
131
|
+
let (mut from, mut to) = (Kiocb::at(0), Kiocb::at(0));
|
|
107
132
|
loop {
|
|
108
|
-
let read = source.
|
|
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.
|
|
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
|
-
///
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
///
|
|
177
|
-
///
|
|
178
|
-
fn
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
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
|
-
|
|
515
|
-
|
|
516
|
-
|
|
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
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
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
|
|
541
|
-
let
|
|
542
|
-
|
|
543
|
-
.
|
|
544
|
-
.
|
|
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
|
-
|
|
208
|
+
pending.extend(entries.into_iter().map(|entry| (inner.clone(), entry.name)));
|
|
559
209
|
}
|
|
210
|
+
Ok(())
|
|
211
|
+
}
|
|
560
212
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
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
|
-
|
|
579
|
-
///
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
) -> Result<
|
|
586
|
-
let mut
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
|
|
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
|
-
///
|
|
601
|
-
pub(super) fn
|
|
602
|
-
|
|
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
|
-
|
|
611
|
-
|
|
612
|
-
|
|
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
|
-
///
|
|
649
|
-
///
|
|
650
|
-
pub(super) fn
|
|
651
|
-
|
|
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
|
-
///
|
|
675
|
-
///
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
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
|
-
///
|
|
698
|
-
pub(super) fn
|
|
699
|
-
|
|
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
|
-
///
|
|
710
|
-
///
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
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
|
}
|