kobako 0.28.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +377 -0
- data/Cargo.lock +181 -154
- data/README.md +99 -459
- data/ROADMAP.md +7 -1
- data/SECURITY.md +1 -1
- data/crates/kobako-runtime/CHANGELOG.md +354 -0
- data/crates/kobako-runtime/Cargo.toml +38 -7
- data/crates/kobako-runtime/README.md +17 -8
- data/crates/kobako-runtime/src/dispatch.rs +9 -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/bind/lookup.rs +236 -0
- 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 +301 -0
- data/crates/kobako-runtime/src/filesystem/overlay.rs +3469 -0
- data/crates/kobako-runtime/src/filesystem/path.rs +21 -0
- 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 +26 -0
- data/crates/kobako-runtime/src/grant.rs +20 -0
- data/crates/kobako-runtime/src/hashtable.rs +44 -0
- data/crates/kobako-runtime/src/lib.rs +16 -1
- 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 +105 -0
- data/crates/kobako-runtime/src/profile.rs +140 -4
- data/crates/kobako-runtime/src/runtime.rs +12 -0
- data/crates/kobako-runtime/src/sched.rs +210 -0
- data/crates/kobako-runtime/src/security.rs +696 -0
- data/crates/kobako-runtime/src/snapshot.rs +20 -5
- 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/src/yielder.rs +11 -6
- 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 +23 -0
- data/crates/kobako-transport/Cargo.toml +3 -3
- data/crates/kobako-transport/README.md +6 -5
- data/crates/kobako-transport/src/abi.rs +1 -1
- 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 +4 -3
- data/crates/kobako-transport/src/envelope/outcome.rs +3 -3
- data/crates/kobako-transport/src/envelope/preamble.rs +24 -24
- data/crates/kobako-transport/src/envelope/reply.rs +34 -16
- data/crates/kobako-transport/src/envelope/request.rs +19 -9
- 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 +265 -0
- data/crates/kobako-wasmtime/Cargo.toml +25 -19
- data/crates/kobako-wasmtime/README.md +13 -7
- data/crates/kobako-wasmtime/src/abi.rs +75 -49
- data/crates/kobako-wasmtime/src/ambient.rs +64 -6
- data/crates/kobako-wasmtime/src/cache.rs +188 -77
- data/crates/kobako-wasmtime/src/config.rs +77 -5
- data/crates/kobako-wasmtime/src/descriptors.rs +200 -0
- data/crates/kobako-wasmtime/src/driver.rs +70 -135
- 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 +52 -0
- data/crates/kobako-wasmtime/src/instance_pre.rs +18 -46
- data/crates/kobako-wasmtime/src/invocation.rs +43 -74
- data/crates/kobako-wasmtime/src/lib.rs +5 -4
- data/crates/kobako-wasmtime/src/limiter.rs +6 -230
- data/crates/kobako-wasmtime/src/preview1/differential/mount.rs +632 -0
- data/crates/kobako-wasmtime/src/preview1/differential.rs +486 -0
- data/crates/kobako-wasmtime/src/preview1/errno.rs +135 -0
- data/crates/kobako-wasmtime/src/preview1/fd.rs +1420 -0
- data/crates/kobako-wasmtime/src/preview1/memory.rs +172 -0
- data/crates/kobako-wasmtime/src/preview1/mount_view/wrap.rs +211 -0
- data/crates/kobako-wasmtime/src/preview1/mount_view.rs +3766 -0
- data/crates/kobako-wasmtime/src/preview1/path.rs +302 -0
- data/crates/kobako-wasmtime/src/preview1/poll.rs +381 -0
- data/crates/kobako-wasmtime/src/preview1/sock.rs +13 -0
- 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 +512 -0
- data/crates/kobako-wasmtime/src/trap.rs +45 -25
- data/crates/kobako-wasmtime/src/wasi.rs +333 -0
- data/crates/kobako-wasmtime/tests/integration/compile_posture.rs +43 -0
- data/crates/kobako-wasmtime/tests/integration/guest_file.rs +1085 -0
- data/crates/kobako-wasmtime/tests/integration/main.rs +29 -0
- data/crates/kobako-wasmtime/tests/{preamble_flags.rs → integration/preamble_flags.rs} +4 -3
- data/crates/kobako-wasmtime/tests/{snippet_compile.rs → integration/snippet_compile.rs} +5 -4
- data/crates/kobako-wasmtime/tests/{support → integration/support}/mod.rs +9 -13
- data/crates/kobako-wasmtime/tests/integration/unanswered_call.rs +86 -0
- data/crates/kobako-wasmtime/tests/{unsupported_call.rs → integration/unsupported_call.rs} +2 -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 +4 -2
- data/ext/kobako/src/runtime/bridge.rs +154 -81
- data/ext/kobako/src/runtime/errors.rs +1 -1
- data/ext/kobako/src/runtime/escape.rs +67 -0
- data/ext/kobako/src/runtime/filesystem/grants.rs +107 -0
- data/ext/kobako/src/runtime/filesystem/ruby.rs +708 -0
- data/ext/kobako/src/runtime/filesystem.rs +208 -0
- data/ext/kobako/src/runtime/gvl.rs +49 -8
- 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 +125 -199
- data/lib/kobako/catalog/extensions.rb +5 -5
- data/lib/kobako/catalog/flags.rb +2 -14
- data/lib/kobako/catalog/grants.rb +30 -0
- data/lib/kobako/catalog/handles.rb +6 -6
- data/lib/kobako/catalog/mount.rb +19 -0
- data/lib/kobako/catalog/services.rb +13 -2
- data/lib/kobako/catalog/snippets.rb +11 -15
- data/lib/kobako/catalog.rb +11 -0
- data/lib/kobako/codec/decoder.rb +10 -9
- data/lib/kobako/codec/ext_types.rb +4 -6
- data/lib/kobako/codec/handle_walk.rb +4 -6
- data/lib/kobako/codec/nesting.rb +7 -7
- data/lib/kobako/codec/state.rb +19 -3
- data/lib/kobako/codec/utils.rb +1 -1
- data/lib/kobako/codec/value_budget.rb +86 -0
- data/lib/kobako/codec.rb +7 -0
- data/lib/kobako/context.rb +40 -13
- data/lib/kobako/errors.rb +15 -14
- data/lib/kobako/filesystem.rb +69 -0
- data/lib/kobako/payload/arguments.rb +2 -2
- data/lib/kobako/pool.rb +6 -6
- data/lib/kobako/sandbox.rb +55 -27
- data/lib/kobako/sandbox_options.rb +39 -13
- data/lib/kobako/snippet/{binary.rb → bytecode.rb} +2 -2
- data/lib/kobako/snippet/source.rb +1 -1
- data/lib/kobako/snippet.rb +1 -1
- data/lib/kobako/transport/block_failure.rb +35 -0
- data/lib/kobako/transport/dispatcher.rb +33 -29
- data/lib/kobako/transport/exposure.rb +7 -9
- data/lib/kobako/transport/reflection.rb +79 -23
- data/lib/kobako/transport/run.rb +5 -5
- data/lib/kobako/transport/yielder.rb +9 -31
- data/lib/kobako/transport.rb +1 -0
- data/lib/kobako/version.rb +1 -1
- data/lib/kobako.rb +35 -0
- data/release-please-config.json +1 -0
- data/sig/kobako/catalog/extensions.rbs +3 -3
- data/sig/kobako/catalog/flags.rbs +0 -4
- data/sig/kobako/catalog/grants.rbs +13 -0
- data/sig/kobako/catalog/handles.rbs +3 -3
- data/sig/kobako/catalog/mount.rbs +24 -0
- data/sig/kobako/catalog/services.rbs +5 -1
- data/sig/kobako/catalog/snippets.rbs +4 -4
- data/sig/kobako/catalog.rbs +1 -0
- 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/state.rbs +7 -1
- data/sig/kobako/codec/utils.rbs +1 -1
- data/sig/kobako/codec/value_budget.rbs +23 -0
- data/sig/kobako/codec.rbs +3 -2
- data/sig/kobako/context.rbs +14 -6
- data/sig/kobako/errors.rbs +4 -4
- data/sig/kobako/filesystem.rbs +93 -0
- data/sig/kobako/guest_yielder.rbs +9 -0
- data/sig/kobako/payload/arguments.rbs +1 -1
- data/sig/kobako/pool.rbs +3 -3
- data/sig/kobako/runtime.rbs +21 -49
- data/sig/kobako/sandbox.rbs +9 -5
- data/sig/kobako/sandbox_options.rbs +25 -3
- 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 +4 -4
- data/sig/kobako/transport/exposure.rbs +2 -2
- data/sig/kobako/transport/reflection.rbs +14 -1
- data/sig/kobako/transport/run.rbs +3 -3
- data/sig/kobako/transport/yielder.rbs +3 -7
- data/sig/kobako/transport.rbs +3 -3
- metadata +116 -15
- data/crates/kobako-wasmtime/src/capture.rs +0 -96
- data/crates/kobako-wasmtime/src/dispatch.rs +0 -107
- data/crates/kobako-wasmtime/src/guest_mem.rs +0 -265
- data/crates/kobako-wasmtime/src/io.rs +0 -286
- data/sig/kobako/snippet/binary.rbs +0 -12
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
//! Opening, making, removing, and moving a name in the view, as overlayfs's
|
|
2
|
+
//! `dir.c` lands each change in the upper layer and whites out what a
|
|
3
|
+
//! lower layer still shows. Each change that replaces an entry is built
|
|
4
|
+
//! in the workdir and exchanged into place, so it lands whole or not at
|
|
5
|
+
//! all.
|
|
6
|
+
|
|
7
|
+
use super::super::super::{FileOperations, FileType, FilesystemError, OpenHow};
|
|
8
|
+
use super::super::file::ovl_file;
|
|
9
|
+
use super::super::inode::{OverlayDirInode, OverlayFileInode};
|
|
10
|
+
use super::super::path::OverlayPath;
|
|
11
|
+
use super::{
|
|
12
|
+
entry, layer_dir, ovl_cleanup, ovl_cleanup_whiteouts, ovl_lower_positive, OverlayFs, ViewDir,
|
|
13
|
+
UPPER,
|
|
14
|
+
};
|
|
15
|
+
use crate::vfs::{Dentry, RenameFlag};
|
|
16
|
+
|
|
17
|
+
impl OverlayFs {
|
|
18
|
+
/// Hide `name` in `dir` with a whiteout made in the workdir first, then
|
|
19
|
+
/// moved over what the upper holds there as this call finds it, so a copy up
|
|
20
|
+
/// landing meanwhile is hidden too, as overlayfs's `ovl_cleanup_and_whiteout`
|
|
21
|
+
/// does: only a directory, which no rename replaces while it holds anything,
|
|
22
|
+
/// is exchanged out to be cleaned up.
|
|
23
|
+
fn cleanup_and_whiteout(&self, dir: &OverlayPath, name: &str) -> Result<(), FilesystemError> {
|
|
24
|
+
let (work, temp) = self.tempname()?;
|
|
25
|
+
work.do_whiteout(&temp)?;
|
|
26
|
+
let is_dir = entry(dir, name)?.is_directory();
|
|
27
|
+
let flag = is_dir.then_some(RenameFlag::Exchange);
|
|
28
|
+
if let Err(err) = work.do_rename(&temp, dir, name, flag) {
|
|
29
|
+
let _ = work.do_unlink(&temp);
|
|
30
|
+
return Err(err);
|
|
31
|
+
}
|
|
32
|
+
if is_dir {
|
|
33
|
+
let _ = ovl_cleanup(work, &temp);
|
|
34
|
+
}
|
|
35
|
+
Ok(())
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/// What `make` builds at a name in the workdir, exchanged with what
|
|
39
|
+
/// stands at `name` in `dir`, as overlayfs's `ovl_create_over_whiteout`
|
|
40
|
+
/// and `ovl_clear_empty` exchange theirs. What came out goes with its
|
|
41
|
+
/// whiteouts, and stays in the workdir if another writer put anything
|
|
42
|
+
/// else in it, as overlayfs leaves a cleanup it cannot finish.
|
|
43
|
+
fn exchange_in<T>(
|
|
44
|
+
&self,
|
|
45
|
+
dir: &OverlayPath,
|
|
46
|
+
name: &str,
|
|
47
|
+
make: impl FnOnce(&OverlayPath, &str) -> Result<T, FilesystemError>,
|
|
48
|
+
) -> Result<T, FilesystemError> {
|
|
49
|
+
let (work, temp) = self.tempname()?;
|
|
50
|
+
let made = make(work, &temp)
|
|
51
|
+
.and_then(|made| {
|
|
52
|
+
work.do_rename(&temp, dir, name, Some(RenameFlag::Exchange))?;
|
|
53
|
+
Ok(made)
|
|
54
|
+
})
|
|
55
|
+
.inspect_err(|_| {
|
|
56
|
+
let _ = ovl_cleanup(work, &temp);
|
|
57
|
+
})?;
|
|
58
|
+
let _ = ovl_cleanup_whiteouts(work, &temp).and_then(|()| ovl_cleanup(work, &temp));
|
|
59
|
+
Ok(made)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/// An empty directory marked opaque, exchanged in for what stands at `name` in
|
|
63
|
+
/// `dir`, as overlayfs's `ovl_clear_empty` does, so the view shows it empty
|
|
64
|
+
/// throughout. A dentry naming a directory there no longer matches, so the next
|
|
65
|
+
/// walk finds the one exchanged in, should what follows fail.
|
|
66
|
+
pub(in super::super) fn clear_empty(
|
|
67
|
+
&self,
|
|
68
|
+
dir: &OverlayPath,
|
|
69
|
+
name: &str,
|
|
70
|
+
) -> Result<(), FilesystemError> {
|
|
71
|
+
self.exchange_in(dir, name, |work, temp| {
|
|
72
|
+
work.do_mkdir(temp)?;
|
|
73
|
+
work.open_dir(temp)?.set_opaque()
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/// Whether the upper holds what `dentry` names, read from what its entry
|
|
78
|
+
/// keeps, as `ovl_dentry_upper` answers.
|
|
79
|
+
fn upper_holds(&self, dentry: &Dentry) -> bool {
|
|
80
|
+
let Some(inode) = dentry.inode() else {
|
|
81
|
+
return false;
|
|
82
|
+
};
|
|
83
|
+
match OverlayFileInode::of(&inode) {
|
|
84
|
+
Some(kept) => self.is_in_upper(&kept.real()),
|
|
85
|
+
None => OverlayDirInode::of(&inode).is_some_and(|kept| kept.level()[UPPER].is_some()),
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/// Take `name` out of the view: a whiteout over any Layer that keeps
|
|
90
|
+
/// it, which also takes the upper's own entry away, or else only the
|
|
91
|
+
/// upper's entry, as overlayfs's `ovl_do_remove` chooses. A directory
|
|
92
|
+
/// whose upper holds whiteouts, as `upper_whiteouts` says, is first
|
|
93
|
+
/// replaced by an empty one, as overlayfs's `ovl_clear_empty` does, so it
|
|
94
|
+
/// removes as an empty directory.
|
|
95
|
+
fn do_remove(
|
|
96
|
+
&self,
|
|
97
|
+
dir: &ViewDir,
|
|
98
|
+
dentry: &Dentry,
|
|
99
|
+
upper_whiteouts: bool,
|
|
100
|
+
) -> Result<(), FilesystemError> {
|
|
101
|
+
let name = &dentry.name();
|
|
102
|
+
let level = dir.level();
|
|
103
|
+
let shown_below = ovl_lower_positive(&level, name);
|
|
104
|
+
let upper_holds = self.upper_holds(dentry);
|
|
105
|
+
if upper_whiteouts {
|
|
106
|
+
self.clear_empty(layer_dir(&level[UPPER])?, name)?;
|
|
107
|
+
}
|
|
108
|
+
match (shown_below, upper_holds) {
|
|
109
|
+
(true, _) => self.cleanup_and_whiteout(&self.upper_dir(dir)?, name),
|
|
110
|
+
(false, true) => ovl_cleanup(layer_dir(&level[UPPER])?, name),
|
|
111
|
+
(false, false) => Err(FilesystemError::NotFound),
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/// Open the file `child` names in `dir` on the Layer's dentry its inode
|
|
116
|
+
/// keeps, copied up first for a change, as `ovl_open` does. A lower Layer's
|
|
117
|
+
/// file opened to read reads the upper's once a copy up makes one.
|
|
118
|
+
pub(in super::super) fn open(
|
|
119
|
+
&self,
|
|
120
|
+
dir: &ViewDir,
|
|
121
|
+
child: &Dentry,
|
|
122
|
+
how: &OpenHow,
|
|
123
|
+
) -> Result<Box<dyn FileOperations>, FilesystemError> {
|
|
124
|
+
let inode = child.inode().ok_or(FilesystemError::NotFound)?;
|
|
125
|
+
let kept = OverlayFileInode::of(&inode).ok_or(FilesystemError::IsADirectory)?;
|
|
126
|
+
let real = kept.path_real();
|
|
127
|
+
if self.is_in_upper(real.dentry()) {
|
|
128
|
+
return real.open_realfile(how);
|
|
129
|
+
}
|
|
130
|
+
if !(how.write || how.truncate) {
|
|
131
|
+
let file = real.open_realfile(how)?;
|
|
132
|
+
return Ok(ovl_file(file, real.dentry().clone(), kept));
|
|
133
|
+
}
|
|
134
|
+
self.copy_up_file(dir, child, how.truncate)?;
|
|
135
|
+
kept.path_real().open_realfile(how)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/// Make the file `name`, where the view shows nothing, in the upper.
|
|
139
|
+
pub(in super::super) fn create(
|
|
140
|
+
&self,
|
|
141
|
+
dir: &ViewDir,
|
|
142
|
+
name: &str,
|
|
143
|
+
) -> Result<(), FilesystemError> {
|
|
144
|
+
let upper_dir = self.upper_dir(dir)?;
|
|
145
|
+
match upper_dir.is_whiteout(name) {
|
|
146
|
+
true => self.exchange_in(&upper_dir, name, |work, temp| work.do_create(temp)),
|
|
147
|
+
false => upper_dir.do_create(name),
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/// A directory made where a whiteout hid one is marked opaque, so the
|
|
152
|
+
/// lower Layers' entries never show in it.
|
|
153
|
+
pub(in super::super) fn mkdir(&self, dir: &ViewDir, name: &str) -> Result<(), FilesystemError> {
|
|
154
|
+
let upper_dir = self.upper_dir(dir)?;
|
|
155
|
+
match upper_dir.is_whiteout(name) {
|
|
156
|
+
true => self.clear_empty(&upper_dir, name),
|
|
157
|
+
false => upper_dir.do_mkdir(name),
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
pub(in super::super) fn rmdir(
|
|
162
|
+
&self,
|
|
163
|
+
dir: &ViewDir,
|
|
164
|
+
dentry: &Dentry,
|
|
165
|
+
) -> Result<(), FilesystemError> {
|
|
166
|
+
let upper_whiteouts = self
|
|
167
|
+
.check_empty_dir(dentry)?
|
|
168
|
+
.ok_or(FilesystemError::DirectoryNotEmpty)?;
|
|
169
|
+
self.do_remove(dir, dentry, upper_whiteouts)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/// A link is removed as itself, wherever it points.
|
|
173
|
+
pub(in super::super) fn unlink(
|
|
174
|
+
&self,
|
|
175
|
+
dir: &ViewDir,
|
|
176
|
+
dentry: &Dentry,
|
|
177
|
+
) -> Result<(), FilesystemError> {
|
|
178
|
+
self.do_remove(dir, dentry, false)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/// Whether what `moved` names, of kind `moving`, can move at all, as
|
|
182
|
+
/// `ovl_can_move` reads the dentry's entry: a directory only when the upper
|
|
183
|
+
/// alone holds it, a file always, once copied up, and anything else only from
|
|
184
|
+
/// the upper.
|
|
185
|
+
fn can_move(&self, moved: &Dentry, moving: FileType) -> Result<bool, FilesystemError> {
|
|
186
|
+
if moving == FileType::Dir {
|
|
187
|
+
let level = self.dir(moved)?.level();
|
|
188
|
+
let lower = level.iter().skip(UPPER + 1).flatten().next().is_some();
|
|
189
|
+
return Ok(level[UPPER].is_some() && !lower);
|
|
190
|
+
}
|
|
191
|
+
Ok(moving == FileType::RegFile || self.upper_holds(moved))
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/// A directory moves only when the upper alone holds it; moving one
|
|
195
|
+
/// out of a lower Layer would mean copying up everything beneath it,
|
|
196
|
+
/// so it is refused as crossing devices, as overlayfs refuses it
|
|
197
|
+
/// without `redirect_dir`; so is a link or another kind a lower Layer
|
|
198
|
+
/// holds, which no copy up can make. What the view holds at `to` is
|
|
199
|
+
/// checked as rename(2) checks it. A directory moved into one a lower
|
|
200
|
+
/// Layer also shows is marked opaque before it moves, and the source leaves
|
|
201
|
+
/// a whiteout as it moves when a lower Layer still shows it, as
|
|
202
|
+
/// overlayfs's `ovl_rename` does.
|
|
203
|
+
pub(in super::super) fn rename(
|
|
204
|
+
&self,
|
|
205
|
+
from: &ViewDir,
|
|
206
|
+
moved: &Dentry,
|
|
207
|
+
to: &ViewDir,
|
|
208
|
+
target: &Dentry,
|
|
209
|
+
) -> Result<(), FilesystemError> {
|
|
210
|
+
let (from_name, to_name) = (&moved.name(), &target.name());
|
|
211
|
+
let from_level = from.level();
|
|
212
|
+
let moving = moved.kind()?;
|
|
213
|
+
let to_level = to.level();
|
|
214
|
+
let is_dir = moving == FileType::Dir;
|
|
215
|
+
// What cannot move answers first, as `ovl_rename_start` asks
|
|
216
|
+
// `ovl_can_move` before it looks at what would be replaced.
|
|
217
|
+
if !self.can_move(moved, moving)? {
|
|
218
|
+
return Err(FilesystemError::CrossesDevices);
|
|
219
|
+
}
|
|
220
|
+
// Only an empty directory is replaced.
|
|
221
|
+
let upper_whiteouts = match target.kind() == Ok(FileType::Dir) {
|
|
222
|
+
true => self
|
|
223
|
+
.check_empty_dir(target)?
|
|
224
|
+
.ok_or(FilesystemError::DirectoryNotEmpty)?,
|
|
225
|
+
false => false,
|
|
226
|
+
};
|
|
227
|
+
let shown_below = ovl_lower_positive(&from_level, from_name);
|
|
228
|
+
if moving == FileType::RegFile {
|
|
229
|
+
self.copy_up_file(from, moved, false)?;
|
|
230
|
+
}
|
|
231
|
+
let target_dir = self.upper_dir(to)?;
|
|
232
|
+
// A directory holding only whiteouts is replaced by an empty one first,
|
|
233
|
+
// as `ovl_rename_upper` calls `ovl_clear_empty` for a list not empty.
|
|
234
|
+
if upper_whiteouts {
|
|
235
|
+
self.clear_empty(&target_dir, to_name)?;
|
|
236
|
+
}
|
|
237
|
+
let source_dir = from.level()[UPPER]
|
|
238
|
+
.clone()
|
|
239
|
+
.ok_or(FilesystemError::NotFound)?;
|
|
240
|
+
// Nothing below shows a directory the upper alone holds, so a mark left
|
|
241
|
+
// on one by a move that then fails changes nothing, as `ovl_rename`
|
|
242
|
+
// leaves it.
|
|
243
|
+
let into_merged = to_level
|
|
244
|
+
.iter()
|
|
245
|
+
.enumerate()
|
|
246
|
+
.any(|(index, dir)| index != UPPER && dir.is_some());
|
|
247
|
+
if is_dir && into_merged {
|
|
248
|
+
source_dir.open_dir(from_name)?.set_opaque()?;
|
|
249
|
+
}
|
|
250
|
+
let onto_whiteout = target_dir.is_whiteout(to_name);
|
|
251
|
+
let flag = match (shown_below, onto_whiteout) {
|
|
252
|
+
(true, true) => Some(RenameFlag::Exchange),
|
|
253
|
+
(true, false) => Some(RenameFlag::Whiteout),
|
|
254
|
+
(false, true) if is_dir => Some(RenameFlag::Exchange),
|
|
255
|
+
_ => None,
|
|
256
|
+
};
|
|
257
|
+
source_dir.do_rename(from_name, &target_dir, to_name, flag)?;
|
|
258
|
+
if flag == Some(RenameFlag::Exchange) && !shown_below {
|
|
259
|
+
source_dir.do_unlink(from_name)?;
|
|
260
|
+
}
|
|
261
|
+
Ok(())
|
|
262
|
+
}
|
|
263
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
//! Finding what the view shows at a name, as overlayfs's `namei.c` looks
|
|
2
|
+
//! a name up in each layer and merges the directories it finds.
|
|
3
|
+
|
|
4
|
+
use std::sync::Arc;
|
|
5
|
+
|
|
6
|
+
use super::super::super::{FileType, FilesystemError, InodeOperations, Kstat};
|
|
7
|
+
use super::super::inode::{ovl_inode_init, OverlayDirInode, OverlayFileInode};
|
|
8
|
+
use super::{entry, layer_dir, LayerEntry, Level, OverlayFs, Presence, UPPER};
|
|
9
|
+
use crate::vfs::{Dentry, Inode};
|
|
10
|
+
|
|
11
|
+
impl OverlayFs {
|
|
12
|
+
/// The topmost Layer holding an entry at `name` in the directory `level`, and
|
|
13
|
+
/// that entry, each Layer asked once, as `ovl_lookup_layers` walks them.
|
|
14
|
+
pub(in super::super) fn lookup_layer(
|
|
15
|
+
&self,
|
|
16
|
+
level: &Level,
|
|
17
|
+
name: &str,
|
|
18
|
+
) -> Result<Option<(usize, LayerEntry)>, FilesystemError> {
|
|
19
|
+
for (index, dir) in level.iter().enumerate() {
|
|
20
|
+
let Some(dir) = dir else {
|
|
21
|
+
continue;
|
|
22
|
+
};
|
|
23
|
+
match entry(dir, name)? {
|
|
24
|
+
Presence::Entry(found) => return Ok(Some((index, found))),
|
|
25
|
+
Presence::Whiteout => break,
|
|
26
|
+
Presence::Nothing => {}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
Ok(None)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/// The directory `name` in `level`, from the directory `top` a Layer at `at`
|
|
33
|
+
/// showed: each Layer's own directory there, merged downward until a Layer
|
|
34
|
+
/// holds something else there, hides it, or marks its directory opaque, as
|
|
35
|
+
/// overlayfs merges only directories.
|
|
36
|
+
fn lookup_layers(
|
|
37
|
+
&self,
|
|
38
|
+
level: &Level,
|
|
39
|
+
name: &str,
|
|
40
|
+
(at, top): (usize, LayerEntry),
|
|
41
|
+
) -> Result<Level, FilesystemError> {
|
|
42
|
+
let mut merged = vec![None; level.len()];
|
|
43
|
+
let top = layer_dir(&level[at])?.beneath(top.dentry);
|
|
44
|
+
let opaque = top.is_opaque();
|
|
45
|
+
merged[at] = Some(top);
|
|
46
|
+
if opaque {
|
|
47
|
+
return Ok(merged);
|
|
48
|
+
}
|
|
49
|
+
for (index, dir) in level.iter().enumerate().skip(at + 1) {
|
|
50
|
+
let Some(dir) = dir else {
|
|
51
|
+
continue;
|
|
52
|
+
};
|
|
53
|
+
match entry(dir, name)? {
|
|
54
|
+
Presence::Entry(found) if found.kind() == Ok(FileType::Dir) => {
|
|
55
|
+
let inner = dir.beneath(found.dentry);
|
|
56
|
+
let opaque = inner.is_opaque();
|
|
57
|
+
merged[index] = Some(inner);
|
|
58
|
+
if opaque {
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
Presence::Entry(_) | Presence::Whiteout => break,
|
|
63
|
+
Presence::Nothing => {}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
Ok(merged)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// What the view reports for the directory `level`, as `ovl_getattr`
|
|
70
|
+
/// reports a merged one: what its topmost Layer reports, under the identity
|
|
71
|
+
/// of the topmost lower Layer showing it, as `ovl_path_lower` finds it, so
|
|
72
|
+
/// copying it up leaves the identity as it was, and one name.
|
|
73
|
+
pub(in super::super) fn dir_getattr(&self, level: &Level) -> Result<Kstat, FilesystemError> {
|
|
74
|
+
let mut shown = level.iter().flatten();
|
|
75
|
+
let topmost = shown.next().ok_or(FilesystemError::NotFound)?.getattr()?;
|
|
76
|
+
Ok(match shown.next() {
|
|
77
|
+
Some(lower) => {
|
|
78
|
+
let lower = lower.getattr()?;
|
|
79
|
+
topmost.with_identity(lower.dev, lower.ino).with_nlink(1)
|
|
80
|
+
}
|
|
81
|
+
None => topmost,
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/// The inode of `found`, what the Layer at `index` shows at `name`, answered
|
|
86
|
+
/// for by `ops_of` of its kind, keeping what the lookup found, as `ovl_lookup`
|
|
87
|
+
/// fills `ovl_inode_params` for `ovl_get_inode` under the directory's lock. A
|
|
88
|
+
/// directory one Layer shows stays unmerged, so it keeps what that Layer's
|
|
89
|
+
/// lookup recorded and nothing more is asked of the Layer.
|
|
90
|
+
pub(in super::super) fn lookup(
|
|
91
|
+
&self,
|
|
92
|
+
level: &Level,
|
|
93
|
+
name: &str,
|
|
94
|
+
(index, found): (usize, LayerEntry),
|
|
95
|
+
ops_of: impl Fn(FileType) -> Arc<dyn InodeOperations>,
|
|
96
|
+
) -> Result<Arc<Inode>, FilesystemError> {
|
|
97
|
+
let kind = found.kind()?;
|
|
98
|
+
let ops = ops_of(kind);
|
|
99
|
+
if kind != FileType::Dir {
|
|
100
|
+
let inode = self.file_inode(index, &found.dentry, ops)?;
|
|
101
|
+
let real = layer_dir(&level[index])?.beneath(found.dentry);
|
|
102
|
+
ovl_inode_init(&inode, OverlayFileInode::new(real));
|
|
103
|
+
return Ok(inode);
|
|
104
|
+
}
|
|
105
|
+
let merged = self.lookup_layers(level, name, (index, found))?;
|
|
106
|
+
let inode = match merged.iter().flatten().count() {
|
|
107
|
+
1 => {
|
|
108
|
+
let real = layer_dir(&merged[index])?.dentry();
|
|
109
|
+
let found = real.inode().ok_or(FilesystemError::NotFound)?;
|
|
110
|
+
found.with_ops(ops)
|
|
111
|
+
}
|
|
112
|
+
_ => Inode::new(&self.dir_getattr(&merged)?, ops),
|
|
113
|
+
};
|
|
114
|
+
ovl_inode_init(&inode, OverlayDirInode::new(merged));
|
|
115
|
+
Ok(inode)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/// The inode of the entry `real` in the Layer at `index`, answered for by
|
|
119
|
+
/// `ops`. A hard link in a lower Layer keeps no identity beneath an upper,
|
|
120
|
+
/// as `ovl_hash_bylower` shares no inode for one a copy up would break, so
|
|
121
|
+
/// its names stay two entries of the view. With no upper nothing copies up.
|
|
122
|
+
fn file_inode(
|
|
123
|
+
&self,
|
|
124
|
+
index: usize,
|
|
125
|
+
real: &Dentry,
|
|
126
|
+
ops: Arc<dyn InodeOperations>,
|
|
127
|
+
) -> Result<Arc<Inode>, FilesystemError> {
|
|
128
|
+
let found = real.inode().ok_or(FilesystemError::NotFound)?;
|
|
129
|
+
let breaks = self.has_upper() && index != UPPER && real.getattr()?.nlink > 1;
|
|
130
|
+
Ok(match breaks {
|
|
131
|
+
true => Inode::new(&Kstat::new(found.kind(), 0), ops),
|
|
132
|
+
false => found.with_ops(ops),
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
//! Listing a directory of the view, as overlayfs's `readdir.c` merges each
|
|
2
|
+
//! layer's entries and checks a directory is empty before it goes.
|
|
3
|
+
|
|
4
|
+
use std::collections::HashSet;
|
|
5
|
+
|
|
6
|
+
use super::super::super::{DirEntry, FileType, FilesystemError};
|
|
7
|
+
use super::{Level, OverlayFs, UPPER};
|
|
8
|
+
use crate::vfs::Dentry;
|
|
9
|
+
|
|
10
|
+
/// What a directory of the view lists, and whether its upper holds a
|
|
11
|
+
/// whiteout, which overlayfs's `ovl_dir_read_merged` keeps on its list.
|
|
12
|
+
struct MergedEntries {
|
|
13
|
+
entries: Vec<DirEntry>,
|
|
14
|
+
upper_whiteouts: bool,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/// Each Layer's entries topmost first, a name a Layer above shows or whites
|
|
18
|
+
/// out taken once.
|
|
19
|
+
fn read_merged(level: &Level) -> Result<MergedEntries, FilesystemError> {
|
|
20
|
+
let mut merged = MergedEntries {
|
|
21
|
+
entries: Vec::new(),
|
|
22
|
+
upper_whiteouts: false,
|
|
23
|
+
};
|
|
24
|
+
let mut taken: HashSet<String> = HashSet::new();
|
|
25
|
+
for (index, dir) in level.iter().enumerate() {
|
|
26
|
+
let Some(dir) = dir else { continue };
|
|
27
|
+
for entry in dir.entries()? {
|
|
28
|
+
// A whiteout lists as a device, as overlayfs's `ovl_fill_merge`
|
|
29
|
+
// reads `DT_CHR` before asking.
|
|
30
|
+
let whiteout = entry.file_type == FileType::Chrdev && dir.is_whiteout(&entry.name);
|
|
31
|
+
merged.upper_whiteouts |= whiteout && index == UPPER;
|
|
32
|
+
if taken.insert(entry.name.clone()) && !whiteout {
|
|
33
|
+
merged.entries.push(entry);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
Ok(merged)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
impl OverlayFs {
|
|
41
|
+
/// Each Layer's entries topmost first, a name a Layer above shows or
|
|
42
|
+
/// whites out taken once. A lookup left out what an opaque directory hides.
|
|
43
|
+
pub(in super::super) fn dir_read_merged(
|
|
44
|
+
&self,
|
|
45
|
+
level: &Level,
|
|
46
|
+
) -> Result<Vec<DirEntry>, FilesystemError> {
|
|
47
|
+
Ok(read_merged(level)?.entries)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/// Whether the directory `dentry` names shows no entry, `None` where it
|
|
51
|
+
/// shows one, and otherwise whether its upper holds whiteouts to clear
|
|
52
|
+
/// first, as `ovl_check_empty_dir` hands `ovl_clear_empty` its list.
|
|
53
|
+
pub(super) fn check_empty_dir(&self, dentry: &Dentry) -> Result<Option<bool>, FilesystemError> {
|
|
54
|
+
let merged = read_merged(&self.dir(dentry)?.level())?;
|
|
55
|
+
Ok(merged.entries.is_empty().then_some(merged.upper_whiteouts))
|
|
56
|
+
}
|
|
57
|
+
}
|