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,3469 @@
|
|
|
1
|
+
//! `Overlay`: read-only lower Layers under an upper Tmpfs, stacked into
|
|
2
|
+
//! one Filesystem, as Linux overlayfs stacks a mount's layers.
|
|
3
|
+
//!
|
|
4
|
+
//! The upper must be a Tmpfs, as gVisor takes only an upper that holds
|
|
5
|
+
//! its marks. A removal hides what a lower Layer still shows with a
|
|
6
|
+
//! whiteout, and a directory made again where one was removed is marked
|
|
7
|
+
//! opaque, each as Linux marks it, in the upper, so they last as long as
|
|
8
|
+
//! it does. The VFS holds each directory a call changes, as Linux's
|
|
9
|
+
//! `i_rwsem` does above overlayfs, and a copy up holds the lock of what
|
|
10
|
+
//! it copies, so a change across several Layers is never seen half made.
|
|
11
|
+
|
|
12
|
+
mod file;
|
|
13
|
+
mod inode;
|
|
14
|
+
mod path;
|
|
15
|
+
mod stack;
|
|
16
|
+
|
|
17
|
+
use std::any::Any;
|
|
18
|
+
use std::sync::Arc;
|
|
19
|
+
|
|
20
|
+
use super::rdir::Rdir;
|
|
21
|
+
use super::{
|
|
22
|
+
DentryOperations, FileOperations, FileSystemType, FileType, Filesystem, FilesystemError, Iattr,
|
|
23
|
+
InodeOperations, Kstat, Lookup, OpenHow, RenameFlag, Tmpfs,
|
|
24
|
+
};
|
|
25
|
+
use crate::vfs::{
|
|
26
|
+
generic_file_llseek, generic_read_dir, notify_change, Dentry, DirContext, Inode, Whence,
|
|
27
|
+
NAME_MAX,
|
|
28
|
+
};
|
|
29
|
+
use inode::{OverlayDirInode, OverlayFileInode};
|
|
30
|
+
use path::OverlayPath;
|
|
31
|
+
use stack::OverlayFs;
|
|
32
|
+
|
|
33
|
+
/// Read-only lower Layers under an upper Tmpfs, stacked into one
|
|
34
|
+
/// Filesystem. Two Overlays sharing an upper are not serialized against
|
|
35
|
+
/// each other, as overlayfs leaves two mounts sharing an upper directory
|
|
36
|
+
/// undefined.
|
|
37
|
+
///
|
|
38
|
+
/// Each directory of the view keeps each Layer's directory it stands for
|
|
39
|
+
/// on its dentry, so it is reached wherever those directories move, as a
|
|
40
|
+
/// descriptor reaches its directory, and a directory opened beneath it is
|
|
41
|
+
/// this Overlay asked about that dentry. overlayfs leaves a change made to
|
|
42
|
+
/// a Layer outside it undefined; here such a change is the host's, and a
|
|
43
|
+
/// directory reached before it keeps the Layers' directories it holds.
|
|
44
|
+
#[derive(Clone)]
|
|
45
|
+
pub struct Overlay {
|
|
46
|
+
fs: OverlayFs,
|
|
47
|
+
/// How many Overlays stack here, itself among them, as Linux counts
|
|
48
|
+
/// a super block's `s_stack_depth`.
|
|
49
|
+
stack_depth: usize,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// The deepest Overlays may stack, as Linux's `FILESYSTEM_MAX_STACK_DEPTH`
|
|
53
|
+
/// bounds it, so a call through them cannot run out of stack.
|
|
54
|
+
const MAX_STACK_DEPTH: usize = 2;
|
|
55
|
+
|
|
56
|
+
/// The most lower Layers an Overlay takes, as overlayfs's `OVL_MAX_STACK`.
|
|
57
|
+
const MAX_LOWER_LAYERS: usize = 500;
|
|
58
|
+
|
|
59
|
+
impl Overlay {
|
|
60
|
+
/// An Overlay of `lower`, topmost first, under `upper`. Each Layer is
|
|
61
|
+
/// asked through that Filesystem's dentries, as overlayfs asks each
|
|
62
|
+
/// layer by its own lookup. As overlayfs's mount does, it refuses no
|
|
63
|
+
/// lower Layer, a read-only view of fewer than two, too many lower
|
|
64
|
+
/// Layers, a stack too deep, and one instance as two Layers, as
|
|
65
|
+
/// overlayfs answers overlapping layers; as gVisor's does, it refuses an
|
|
66
|
+
/// upper that is no Tmpfs.
|
|
67
|
+
pub fn new(
|
|
68
|
+
lower: Vec<Arc<Filesystem>>,
|
|
69
|
+
upper: Option<Arc<Filesystem>>,
|
|
70
|
+
) -> Result<Self, FilesystemError> {
|
|
71
|
+
let layers: Vec<_> = lower.iter().chain(&upper).collect();
|
|
72
|
+
let overlapping = layers
|
|
73
|
+
.iter()
|
|
74
|
+
.enumerate()
|
|
75
|
+
.any(|(at, layer)| layers[..at].iter().any(|seen| Arc::ptr_eq(seen, layer)));
|
|
76
|
+
if overlapping {
|
|
77
|
+
return Err(FilesystemError::InvalidInput);
|
|
78
|
+
}
|
|
79
|
+
let too_few = if upper.is_some() { 1 } else { 2 };
|
|
80
|
+
let upper_tmpfs = upper.as_ref().is_none_or(|upper| {
|
|
81
|
+
upper
|
|
82
|
+
.root()
|
|
83
|
+
.and_then(|root| root.inode())
|
|
84
|
+
.is_some_and(|root| (root.ops().as_ref() as &dyn Any).is::<Tmpfs>())
|
|
85
|
+
});
|
|
86
|
+
let stack_depth = 1 + lower
|
|
87
|
+
.iter()
|
|
88
|
+
.filter_map(|layer| layer.root()?.inode())
|
|
89
|
+
.filter_map(|root| {
|
|
90
|
+
let overlay = (root.ops().as_ref() as &dyn Any).downcast_ref::<Self>()?;
|
|
91
|
+
Some(overlay.stack_depth)
|
|
92
|
+
})
|
|
93
|
+
.max()
|
|
94
|
+
.unwrap_or(0);
|
|
95
|
+
if !(too_few..=MAX_LOWER_LAYERS).contains(&lower.len())
|
|
96
|
+
|| !upper_tmpfs
|
|
97
|
+
|| stack_depth > MAX_STACK_DEPTH
|
|
98
|
+
{
|
|
99
|
+
return Err(FilesystemError::InvalidInput);
|
|
100
|
+
}
|
|
101
|
+
Ok(Self {
|
|
102
|
+
fs: OverlayFs::new(lower, upper)?,
|
|
103
|
+
stack_depth,
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/// The Layer's dentry the entry `dentry` answers through, and the inode the view
|
|
109
|
+
/// recorded, as `ovl_getattr` asks the real path.
|
|
110
|
+
fn real_of(dentry: &Dentry) -> Option<(Arc<Dentry>, Arc<Inode>)> {
|
|
111
|
+
let inode = dentry.inode()?;
|
|
112
|
+
let real = OverlayFileInode::of(&inode)?.real();
|
|
113
|
+
Some((real, inode))
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/// Whether `real`, a Layer's dentry, still names what it recorded, as
|
|
117
|
+
/// `ovl_revalidate_real` asks it.
|
|
118
|
+
fn ovl_revalidate_real(real: &Dentry) -> Result<bool, FilesystemError> {
|
|
119
|
+
match real.ops() {
|
|
120
|
+
Some(ops) => ops.revalidate(real.parent().inode_ops()?.as_ref(), real),
|
|
121
|
+
None => Ok(true),
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/// A name stays valid while each Layer's dentry its entry keeps does, as
|
|
126
|
+
/// `ovl_dentry_revalidate` asks each real dentry, and while a directory still
|
|
127
|
+
/// stands for what each Layer holds there, as `ovl_clear_empty` drops a dentry
|
|
128
|
+
/// whose upper no longer matches.
|
|
129
|
+
impl DentryOperations for Overlay {
|
|
130
|
+
fn revalidate(
|
|
131
|
+
&self,
|
|
132
|
+
_dir: &dyn InodeOperations,
|
|
133
|
+
dentry: &Dentry,
|
|
134
|
+
) -> Result<bool, FilesystemError> {
|
|
135
|
+
let Some(inode) = dentry.inode() else {
|
|
136
|
+
return Ok(matches!(self.lookup(dentry)?, Lookup::Absent));
|
|
137
|
+
};
|
|
138
|
+
if let Some(kept) = OverlayFileInode::of(&inode) {
|
|
139
|
+
return ovl_revalidate_real(&kept.real());
|
|
140
|
+
}
|
|
141
|
+
let Some(kept) = OverlayDirInode::of(&inode) else {
|
|
142
|
+
return Ok(true);
|
|
143
|
+
};
|
|
144
|
+
let shown = kept.level();
|
|
145
|
+
for layer in shown.iter().flatten() {
|
|
146
|
+
if !ovl_revalidate_real(layer.dentry())? {
|
|
147
|
+
return Ok(false);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
let name = dentry.name();
|
|
151
|
+
let above = self.fs.parent(dentry)?.level();
|
|
152
|
+
Ok(above.iter().zip(&shown).all(|pair| match pair {
|
|
153
|
+
(Some(layer), Some(shown)) => layer.matches_real(&name, shown.dentry()),
|
|
154
|
+
_ => true,
|
|
155
|
+
}))
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/// A name goes once nothing holds it where any Layer behind it would let
|
|
159
|
+
/// its own go, since its `revalidate` answers only as those Layers do.
|
|
160
|
+
fn delete(&self, dentry: &Dentry) -> bool {
|
|
161
|
+
let Some(inode) = dentry.inode() else {
|
|
162
|
+
return false;
|
|
163
|
+
};
|
|
164
|
+
if let Some(kept) = OverlayFileInode::of(&inode) {
|
|
165
|
+
return kept.real().goes_when_unused();
|
|
166
|
+
}
|
|
167
|
+
OverlayDirInode::of(&inode).is_some_and(|kept| {
|
|
168
|
+
kept.level()
|
|
169
|
+
.iter()
|
|
170
|
+
.flatten()
|
|
171
|
+
.any(|layer| layer.dentry().goes_when_unused())
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/// A file of the view, or an entry a Layer holds that is none of the others, as
|
|
177
|
+
/// `ovl_file_inode_operations` answers for one.
|
|
178
|
+
struct OverlayRegular(Overlay);
|
|
179
|
+
|
|
180
|
+
/// A link of the view, as `ovl_symlink_inode_operations` answers for one.
|
|
181
|
+
struct OverlayLink(Overlay);
|
|
182
|
+
|
|
183
|
+
impl Overlay {
|
|
184
|
+
/// The operations an entry of `kind` answers through.
|
|
185
|
+
fn ops_of(&self, kind: FileType) -> Arc<dyn InodeOperations> {
|
|
186
|
+
match kind {
|
|
187
|
+
FileType::Dir => Arc::new(self.clone()),
|
|
188
|
+
FileType::Symlink => Arc::new(OverlayLink(self.clone())),
|
|
189
|
+
_ => Arc::new(OverlayRegular(self.clone())),
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/// What the Layer the entry's name is found in reports, under the identity
|
|
194
|
+
/// the view recorded.
|
|
195
|
+
fn real_getattr(&self, dentry: &Dentry) -> Result<Kstat, FilesystemError> {
|
|
196
|
+
let (real, inode) = real_of(dentry).ok_or(FilesystemError::NotFound)?;
|
|
197
|
+
let found = real.getattr()?;
|
|
198
|
+
Ok(match inode.identity() {
|
|
199
|
+
Some(identity) => identity.onto(found),
|
|
200
|
+
None => found,
|
|
201
|
+
})
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
impl InodeOperations for OverlayRegular {
|
|
206
|
+
fn getattr(&self, dentry: &Dentry) -> Result<Kstat, FilesystemError> {
|
|
207
|
+
self.0.real_getattr(dentry)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/// Copied up first, as `ovl_setattr` does, so a lower Layer stays as it
|
|
211
|
+
/// was whatever its Filesystem would keep.
|
|
212
|
+
fn setattr(&self, dentry: &Dentry, attr: &Iattr) -> Result<(), FilesystemError> {
|
|
213
|
+
let overlay = &self.0;
|
|
214
|
+
overlay
|
|
215
|
+
.fs
|
|
216
|
+
.copy_up_file(&overlay.fs.parent(dentry)?, dentry, false)?;
|
|
217
|
+
let (real, _) = real_of(dentry).ok_or(FilesystemError::NotFound)?;
|
|
218
|
+
// The open's truncate never reached the upper's open, so the upper must
|
|
219
|
+
// empty the file itself, and the view's open file is none of the upper's,
|
|
220
|
+
// as `ovl_setattr` clears `ATTR_OPEN` and `ATTR_FILE`.
|
|
221
|
+
let held = real.inode_lock()?;
|
|
222
|
+
notify_change(
|
|
223
|
+
&real,
|
|
224
|
+
&held,
|
|
225
|
+
&Iattr {
|
|
226
|
+
open: false,
|
|
227
|
+
file: None,
|
|
228
|
+
..*attr
|
|
229
|
+
},
|
|
230
|
+
)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
fn open(
|
|
234
|
+
&self,
|
|
235
|
+
dentry: &Dentry,
|
|
236
|
+
how: &OpenHow,
|
|
237
|
+
) -> Result<Box<dyn FileOperations>, FilesystemError> {
|
|
238
|
+
let fs = &self.0.fs;
|
|
239
|
+
fs.open(&fs.parent(dentry)?, dentry, how)
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
impl InodeOperations for OverlayLink {
|
|
244
|
+
fn getattr(&self, dentry: &Dentry) -> Result<Kstat, FilesystemError> {
|
|
245
|
+
self.0.real_getattr(dentry)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/// Where the link points, as `ovl_get_link` asks the real dentry.
|
|
249
|
+
fn get_link(&self, dentry: &Dentry) -> Result<String, FilesystemError> {
|
|
250
|
+
let (real, _) = real_of(dentry).ok_or(FilesystemError::NotFound)?;
|
|
251
|
+
real.get_link()
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
impl FileSystemType for Overlay {
|
|
256
|
+
/// With no upper nothing can change, as `ovl_fill_super` sets `SB_RDONLY`.
|
|
257
|
+
/// Its root takes the identity its topmost Layer's root recorded, as
|
|
258
|
+
/// `ovl_get_root` reads a real root's `i_ino`, so making the view asks no
|
|
259
|
+
/// Layer anything.
|
|
260
|
+
fn fill_super(self: Arc<Self>, sb: &mut Filesystem) -> Result<(), FilesystemError> {
|
|
261
|
+
sb.set_default_d_op(self.clone());
|
|
262
|
+
if !self.fs.has_upper() {
|
|
263
|
+
sb.set_rdonly();
|
|
264
|
+
}
|
|
265
|
+
let root = Kstat::new(FileType::Dir, 0);
|
|
266
|
+
let root = match self.fs.root_identity() {
|
|
267
|
+
Some(identity) => identity.onto(root),
|
|
268
|
+
None => root,
|
|
269
|
+
};
|
|
270
|
+
let root_dir = self.fs.root_dir();
|
|
271
|
+
let root = Inode::new(&root, self);
|
|
272
|
+
inode::ovl_inode_init(&root, root_dir);
|
|
273
|
+
sb.d_make_root(root);
|
|
274
|
+
Ok(())
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/// A directory of the view, finding what a call is about through the dentry it
|
|
279
|
+
/// is handed, as `ovl_dir_inode_operations` answers for one.
|
|
280
|
+
impl InodeOperations for Overlay {
|
|
281
|
+
/// It reports its merged Layers.
|
|
282
|
+
fn getattr(&self, dentry: &Dentry) -> Result<Kstat, FilesystemError> {
|
|
283
|
+
self.fs.dir_getattr(&self.fs.dir(dentry)?.level())
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/// Copied up first, as `ovl_setattr` does for any entry, so a lower Layer
|
|
287
|
+
/// stays as it was.
|
|
288
|
+
fn setattr(&self, dentry: &Dentry, attr: &Iattr) -> Result<(), FilesystemError> {
|
|
289
|
+
let upper = self.fs.upper_dir(&self.fs.dir(dentry)?)?;
|
|
290
|
+
let held = upper.dentry().inode_lock()?;
|
|
291
|
+
notify_change(upper.dentry(), &held, attr)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/// The entry as a Layer spells it, as `ovl_lookup` hands
|
|
295
|
+
/// `d_splice_alias` the inode it made, a name no Layer could hold refused
|
|
296
|
+
/// before any is asked, as it compares `namelen`.
|
|
297
|
+
fn lookup(&self, dentry: &Dentry) -> Result<Lookup, FilesystemError> {
|
|
298
|
+
let level = self.fs.parent(dentry)?.level();
|
|
299
|
+
let name = dentry.name();
|
|
300
|
+
if name.len() > NAME_MAX {
|
|
301
|
+
return Err(FilesystemError::InvalidFilename);
|
|
302
|
+
}
|
|
303
|
+
let Some(found) = self.fs.lookup_layer(&level, &name)? else {
|
|
304
|
+
return Ok(Lookup::Absent);
|
|
305
|
+
};
|
|
306
|
+
let stored_name = found.1.stored_name().to_string();
|
|
307
|
+
let inode = self
|
|
308
|
+
.fs
|
|
309
|
+
.lookup(&level, &stored_name, found, |kind| self.ops_of(kind))?;
|
|
310
|
+
match stored_name == name {
|
|
311
|
+
true => Ok(Lookup::Found(inode)),
|
|
312
|
+
false => Ok(Lookup::Spelled {
|
|
313
|
+
name: stored_name,
|
|
314
|
+
inode,
|
|
315
|
+
}),
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
fn open(
|
|
320
|
+
&self,
|
|
321
|
+
dentry: &Dentry,
|
|
322
|
+
_how: &OpenHow,
|
|
323
|
+
) -> Result<Box<dyn FileOperations>, FilesystemError> {
|
|
324
|
+
let _real = self
|
|
325
|
+
.fs
|
|
326
|
+
.dir(dentry)?
|
|
327
|
+
.level()
|
|
328
|
+
.iter()
|
|
329
|
+
.flatten()
|
|
330
|
+
.map(OverlayPath::open_real)
|
|
331
|
+
.collect::<Result<_, _>>()?;
|
|
332
|
+
let fs = self.fs.clone();
|
|
333
|
+
Ok(Box::new(OverlayDir {
|
|
334
|
+
fs,
|
|
335
|
+
_real,
|
|
336
|
+
cache: Rdir::default(),
|
|
337
|
+
}))
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/// What was made is found through the view's own lookup, as `ovl_instantiate`
|
|
341
|
+
/// records the inode for the real dentry.
|
|
342
|
+
fn create(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
|
|
343
|
+
self.fs.create(&self.fs.parent(dentry)?, &dentry.name())?;
|
|
344
|
+
match self.lookup(dentry)? {
|
|
345
|
+
Lookup::Found(inode) => dentry.add(Some(inode)),
|
|
346
|
+
_ => return Err(FilesystemError::NotFound),
|
|
347
|
+
}
|
|
348
|
+
Ok(())
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
fn mkdir(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
|
|
352
|
+
self.fs.mkdir(&self.fs.parent(dentry)?, &dentry.name())
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
fn rmdir(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
|
|
356
|
+
self.fs.rmdir(&self.fs.parent(dentry)?, dentry)
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
fn unlink(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
|
|
360
|
+
self.fs.unlink(&self.fs.parent(dentry)?, dentry)
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/// Both directories lie in one Overlay, however many directories of
|
|
364
|
+
/// it are open. Only its upper takes a flag, which it asks itself.
|
|
365
|
+
fn rename(
|
|
366
|
+
&self,
|
|
367
|
+
from: &Dentry,
|
|
368
|
+
_to_dir: &dyn InodeOperations,
|
|
369
|
+
to: &Dentry,
|
|
370
|
+
flag: Option<RenameFlag>,
|
|
371
|
+
) -> Result<(), FilesystemError> {
|
|
372
|
+
if flag.is_some() {
|
|
373
|
+
return Err(FilesystemError::InvalidInput);
|
|
374
|
+
}
|
|
375
|
+
self.fs
|
|
376
|
+
.rename(&self.fs.parent(from)?, from, &self.fs.parent(to)?, to)
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/// A directory of the view, open: it merges its Layers each time it is
|
|
381
|
+
/// listed, finding them through the dentry it is handed, and holds each Layer's
|
|
382
|
+
/// directory open while it is, as `struct ovl_dir_file` holds its `realfile`.
|
|
383
|
+
struct OverlayDir {
|
|
384
|
+
fs: OverlayFs,
|
|
385
|
+
_real: Vec<Box<dyn FileOperations>>,
|
|
386
|
+
/// What a listing merged, held until it ends, as `ovl_dir_cache` holds it.
|
|
387
|
+
cache: Rdir,
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
impl FileOperations for OverlayDir {
|
|
391
|
+
fn llseek(
|
|
392
|
+
&mut self,
|
|
393
|
+
dentry: &Dentry,
|
|
394
|
+
pos: u64,
|
|
395
|
+
offset: i64,
|
|
396
|
+
whence: Whence,
|
|
397
|
+
) -> Result<u64, FilesystemError> {
|
|
398
|
+
generic_file_llseek(dentry, pos, offset, whence)
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
fn read_iter(
|
|
402
|
+
&mut self,
|
|
403
|
+
_iocb: &mut super::Kiocb,
|
|
404
|
+
_buf: &mut [u8],
|
|
405
|
+
) -> Result<usize, FilesystemError> {
|
|
406
|
+
generic_read_dir()
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
fn iterate_shared(
|
|
410
|
+
&mut self,
|
|
411
|
+
dentry: &Dentry,
|
|
412
|
+
ctx: &mut DirContext<'_>,
|
|
413
|
+
) -> Result<(), FilesystemError> {
|
|
414
|
+
let (fs, cache) = (&self.fs, &mut self.cache);
|
|
415
|
+
cache.iterate(dentry, ctx, || fs.dir_read_merged(&fs.dir(dentry)?.level()))
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/// Its Layers answer for what they hold, and its upper is a Tmpfs, so
|
|
419
|
+
/// nothing is left to make durable, as `ovl_dir_fsync` finds with no upper
|
|
420
|
+
/// of its own to sync.
|
|
421
|
+
fn fsync(&mut self, _datasync: bool) -> Result<(), FilesystemError> {
|
|
422
|
+
Ok(())
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
#[cfg(test)]
|
|
427
|
+
mod tests {
|
|
428
|
+
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
|
429
|
+
use std::sync::Mutex;
|
|
430
|
+
use std::thread;
|
|
431
|
+
use std::time::{Duration, Instant};
|
|
432
|
+
|
|
433
|
+
use super::super::tmpfs::Tmpfs;
|
|
434
|
+
use super::super::wrap::{Hook, Wrap};
|
|
435
|
+
use super::super::{FileType, Finish, Kiocb};
|
|
436
|
+
use super::*;
|
|
437
|
+
use crate::vfs::kern_path::{kern_path, KernPath};
|
|
438
|
+
use crate::vfs::Mask;
|
|
439
|
+
|
|
440
|
+
/// `filesystem` reached through the VFS.
|
|
441
|
+
fn at(filesystem: impl FileSystemType) -> KernPath {
|
|
442
|
+
kern_path(Arc::new(filesystem))
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/// The Filesystem `filesystem` holds, reached through the VFS.
|
|
446
|
+
fn on<F: FileSystemType>(filesystem: &Arc<F>) -> KernPath {
|
|
447
|
+
kern_path(filesystem.clone())
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
#[derive(Default)]
|
|
451
|
+
struct CountsLookups(AtomicUsize);
|
|
452
|
+
|
|
453
|
+
impl Hook for CountsLookups {
|
|
454
|
+
fn lookup(
|
|
455
|
+
&self,
|
|
456
|
+
inner: &dyn InodeOperations,
|
|
457
|
+
dentry: &Dentry,
|
|
458
|
+
) -> Result<Lookup, FilesystemError> {
|
|
459
|
+
self.0.fetch_add(1, Ordering::SeqCst);
|
|
460
|
+
inner.lookup(dentry)
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// A Filesystem written in Ruby pays for each ask and keeps no dentry, so a
|
|
465
|
+
// name found absent, which no Filesystem keeps, shows how often a Layer is
|
|
466
|
+
// asked by itself.
|
|
467
|
+
// @behavior FS-184
|
|
468
|
+
#[test]
|
|
469
|
+
fn a_lookup_asks_each_layer_once_for_a_name() {
|
|
470
|
+
let lower: Tmpfs = [("file", "x")].into_iter().collect();
|
|
471
|
+
let counts = Arc::new(CountsLookups::default());
|
|
472
|
+
let view = at(Overlay::new(
|
|
473
|
+
vec![
|
|
474
|
+
Filesystem::new(Arc::new(Wrap::new(Arc::new(lower), counts.clone())))
|
|
475
|
+
.expect("the instance must stand"),
|
|
476
|
+
],
|
|
477
|
+
Some(Filesystem::new(Arc::new(Tmpfs::new())).expect("the instance must stand")),
|
|
478
|
+
)
|
|
479
|
+
.expect("the Overlay must stack"));
|
|
480
|
+
let asks = |path: &str| {
|
|
481
|
+
counts.0.store(0, Ordering::SeqCst);
|
|
482
|
+
let _ = view.vfs_lstat(path);
|
|
483
|
+
counts.0.load(Ordering::SeqCst)
|
|
484
|
+
};
|
|
485
|
+
assert_eq!(
|
|
486
|
+
(asks("missing"), asks("file")),
|
|
487
|
+
(1, 1),
|
|
488
|
+
"a stat through an Overlay of a name its lower Layer lacks, and of one it holds, \
|
|
489
|
+
must ask that Layer once"
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Only the upper could refuse the name once the directory was copied up, so
|
|
494
|
+
// the directory standing nowhere in the upper is what shows no change began.
|
|
495
|
+
// @behavior FS-183
|
|
496
|
+
#[test]
|
|
497
|
+
fn a_name_past_name_max_answers_too_long_and_copies_nothing_up() {
|
|
498
|
+
let upper = Arc::new(Tmpfs::new());
|
|
499
|
+
let (view, _lower) = overlay_on(upper.clone());
|
|
500
|
+
let long = format!("dir/{}", "n".repeat(NAME_MAX + 1));
|
|
501
|
+
assert_eq!(
|
|
502
|
+
(
|
|
503
|
+
view.vfs_lstat(&long).err(),
|
|
504
|
+
view.vfs_mkdir(&long).err(),
|
|
505
|
+
on(&upper).vfs_lstat("upper/dir").err(),
|
|
506
|
+
),
|
|
507
|
+
(
|
|
508
|
+
Some(FilesystemError::InvalidFilename),
|
|
509
|
+
Some(FilesystemError::InvalidFilename),
|
|
510
|
+
Some(FilesystemError::NotFound),
|
|
511
|
+
),
|
|
512
|
+
"a stat and a make of a name past NAME_MAX in a lower directory, through an Overlay, \
|
|
513
|
+
must answer ENAMETOOLONG and leave that directory uncopied"
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// @behavior FS-140
|
|
518
|
+
#[test]
|
|
519
|
+
fn a_file_with_nothing_to_flush_syncs() {
|
|
520
|
+
let lower: Tmpfs = [("file", "x")].into_iter().collect();
|
|
521
|
+
let upper = Tmpfs::new();
|
|
522
|
+
let overlay = at(Overlay::new(
|
|
523
|
+
vec![Filesystem::new(Arc::new(lower)).expect("the instance must stand")],
|
|
524
|
+
Some(Filesystem::new(Arc::new(upper)).expect("the instance must stand")),
|
|
525
|
+
)
|
|
526
|
+
.expect("the Overlay must stack"));
|
|
527
|
+
assert_eq!(
|
|
528
|
+
open_to_read(&overlay, "file").fsync(false),
|
|
529
|
+
Ok(()),
|
|
530
|
+
"FileOperations::fsync on an Overlay's file open to read from a Tmpfs Layer must \
|
|
531
|
+
succeed"
|
|
532
|
+
);
|
|
533
|
+
let tmpfs = at([("file", "x")].into_iter().collect::<Tmpfs>());
|
|
534
|
+
assert_eq!(
|
|
535
|
+
open_to_read(&tmpfs, "file").fsync(false),
|
|
536
|
+
Ok(()),
|
|
537
|
+
"FileOperations::fsync on a Tmpfs file must succeed"
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
fn open_to_read(fs: &KernPath, path: &str) -> Box<dyn FileOperations> {
|
|
542
|
+
fs.open(
|
|
543
|
+
path,
|
|
544
|
+
&OpenHow {
|
|
545
|
+
read: true,
|
|
546
|
+
..OpenHow::default()
|
|
547
|
+
},
|
|
548
|
+
)
|
|
549
|
+
.expect("the file must open to read")
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
fn read_from(file: &mut dyn FileOperations) -> Vec<u8> {
|
|
553
|
+
let mut buf = vec![0; 64];
|
|
554
|
+
let read = file
|
|
555
|
+
.read_iter(&mut Kiocb::at(0), &mut buf)
|
|
556
|
+
.expect("the file must read");
|
|
557
|
+
buf.truncate(read);
|
|
558
|
+
buf
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
fn read(fs: &KernPath, path: &str) -> Vec<u8> {
|
|
562
|
+
read_from(open_to_read(fs, path).as_mut())
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
fn write(fs: &KernPath, path: &str, contents: &[u8]) -> Result<(), FilesystemError> {
|
|
566
|
+
fs.open_or_create(
|
|
567
|
+
path,
|
|
568
|
+
&OpenHow {
|
|
569
|
+
write: true,
|
|
570
|
+
truncate: true,
|
|
571
|
+
..OpenHow::default()
|
|
572
|
+
},
|
|
573
|
+
)?
|
|
574
|
+
.write_iter(&mut Kiocb::at(0), contents)
|
|
575
|
+
.map(drop)
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/// Write at the start of what stands at `path`, so a copy up brings its
|
|
579
|
+
/// bytes along.
|
|
580
|
+
fn write_over(fs: &KernPath, path: &str, contents: &[u8]) -> Result<(), FilesystemError> {
|
|
581
|
+
let how = OpenHow {
|
|
582
|
+
write: true,
|
|
583
|
+
..OpenHow::default()
|
|
584
|
+
};
|
|
585
|
+
fs.open(path, &how)?
|
|
586
|
+
.write_iter(&mut Kiocb::at(0), contents)
|
|
587
|
+
.map(drop)
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
fn overwrite(fs: &KernPath, path: &str, contents: &[u8]) {
|
|
591
|
+
write(fs, path, contents).expect("the write must land");
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
fn names(fs: &KernPath, path: &str) -> Vec<String> {
|
|
595
|
+
let listed = match path {
|
|
596
|
+
"" => fs.iterate_dir(),
|
|
597
|
+
path => fs.open_dir(path).and_then(|dir| dir.iterate_dir()),
|
|
598
|
+
};
|
|
599
|
+
let mut names: Vec<String> = listed
|
|
600
|
+
.expect("the directory must list")
|
|
601
|
+
.into_iter()
|
|
602
|
+
.map(|entry| entry.name)
|
|
603
|
+
.collect();
|
|
604
|
+
names.sort();
|
|
605
|
+
names
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
fn tmpfs_with(path: &str, contents: &str) -> Arc<Tmpfs> {
|
|
609
|
+
Arc::new([(path, contents)].into_iter().collect())
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// A helper beneath the VFS skips the super block's read-only answer, so only
|
|
613
|
+
// the view's own upper standing empty keeps the change off its lower Layers.
|
|
614
|
+
// @behavior FS-197
|
|
615
|
+
#[test]
|
|
616
|
+
fn a_change_reaching_an_overlay_with_no_upper_leaves_its_lower_layers_as_they_are() {
|
|
617
|
+
let top = tmpfs_with("dir/file", "top");
|
|
618
|
+
let overlay = at(lowers(top.clone(), Arc::new(Tmpfs::new())));
|
|
619
|
+
assert_eq!(
|
|
620
|
+
(
|
|
621
|
+
overlay.vfs_mkdir("made").err(),
|
|
622
|
+
on(&top).vfs_lstat("made").err()
|
|
623
|
+
),
|
|
624
|
+
(
|
|
625
|
+
Some(FilesystemError::ReadOnlyFilesystem),
|
|
626
|
+
Some(FilesystemError::NotFound)
|
|
627
|
+
),
|
|
628
|
+
"OverlayFs::mkdir with no upper, reached beneath the VFS's read-only answer, must \
|
|
629
|
+
answer EROFS and make nothing in its topmost lower Layer"
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/// A security module recording the name of each directory it is asked to
|
|
634
|
+
/// let change.
|
|
635
|
+
struct RecordsChanges(Arc<Mutex<Vec<String>>>);
|
|
636
|
+
|
|
637
|
+
impl crate::vfs::SecurityOperations for RecordsChanges {
|
|
638
|
+
fn inode_permission(
|
|
639
|
+
&self,
|
|
640
|
+
dentry: &Dentry,
|
|
641
|
+
mask: crate::vfs::Mask,
|
|
642
|
+
) -> Result<(), FilesystemError> {
|
|
643
|
+
if mask.contains(crate::vfs::Mask::WRITE) {
|
|
644
|
+
self.0.lock().expect("never poisoned").push(dentry.name());
|
|
645
|
+
}
|
|
646
|
+
Ok(())
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// A Layer's directories carry names of their own, so a module asked about
|
|
651
|
+
// one would record more than the view's root.
|
|
652
|
+
// @behavior FS-198
|
|
653
|
+
#[test]
|
|
654
|
+
fn a_call_through_an_overlay_asks_a_module_of_the_view_alone() {
|
|
655
|
+
let overlay = over(tmpfs_with("f", "x"), Arc::new(Tmpfs::new()));
|
|
656
|
+
let mount = crate::mount::Mount::new(
|
|
657
|
+
"/data",
|
|
658
|
+
Filesystem::new(Arc::new(overlay)).expect("the instance must stand"),
|
|
659
|
+
crate::mount::Access::ReadWrite,
|
|
660
|
+
)
|
|
661
|
+
.expect("/data is a guest path");
|
|
662
|
+
let asked = Arc::new(Mutex::new(Vec::new()));
|
|
663
|
+
let cred = crate::vfs::Cred::new(vec![Arc::new(RecordsChanges(asked.clone()))]);
|
|
664
|
+
let root = crate::vfs::Path::root(mount.mnt());
|
|
665
|
+
let made = crate::vfs::filename_mkdirat(&cred, &root, "d");
|
|
666
|
+
assert_eq!(
|
|
667
|
+
(made, asked.lock().expect("never poisoned").clone()),
|
|
668
|
+
(Ok(()), vec![String::new()]),
|
|
669
|
+
"vfs::filename_mkdirat through an Overlay must ask a security module of the view's \
|
|
670
|
+
directory alone, as overlayfs makes its Layers' calls with its creator_cred"
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/// `upper` over the one lower Layer `lower`.
|
|
675
|
+
fn over(lower: Arc<dyn FileSystemType>, upper: Arc<Tmpfs>) -> Overlay {
|
|
676
|
+
let instance = |fs| Filesystem::new(fs).expect("the instance must stand");
|
|
677
|
+
Overlay::new(vec![instance(lower)], Some(instance(upper)))
|
|
678
|
+
.expect("an upper over a lower Layer must make a view")
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/// The lower Layers `top` over `bottom`, with no upper.
|
|
682
|
+
fn lowers(top: Arc<dyn FileSystemType>, bottom: Arc<dyn FileSystemType>) -> Overlay {
|
|
683
|
+
let instance = |fs| Filesystem::new(fs).expect("the instance must stand");
|
|
684
|
+
Overlay::new(vec![instance(top), instance(bottom)], None)
|
|
685
|
+
.expect("two lower Layers must make a view")
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/// `upper` over a lower Tmpfs holding `dir/file` = "lower".
|
|
689
|
+
fn overlay_on(upper: Arc<Tmpfs>) -> (KernPath, Arc<Tmpfs>) {
|
|
690
|
+
let lower = tmpfs_with("dir/file", "lower");
|
|
691
|
+
(at(over(lower.clone(), upper)), lower)
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/// A host directory holding `target`, `dir/inner`, and the links `link`
|
|
695
|
+
/// to `target`, `dirlink` to `dir`, and `dangling` to nothing, removed
|
|
696
|
+
/// when it drops.
|
|
697
|
+
#[cfg(unix)]
|
|
698
|
+
struct LinkTree(std::path::PathBuf);
|
|
699
|
+
|
|
700
|
+
#[cfg(unix)]
|
|
701
|
+
impl LinkTree {
|
|
702
|
+
fn new(name: &str) -> Self {
|
|
703
|
+
let path =
|
|
704
|
+
std::env::temp_dir().join(format!("kobako-overlay-{name}-{}", std::process::id()));
|
|
705
|
+
let _ = std::fs::remove_dir_all(&path);
|
|
706
|
+
std::fs::create_dir_all(path.join("dir")).expect("the host directory must be created");
|
|
707
|
+
std::fs::write(path.join("target"), "lower").expect("the host file must be written");
|
|
708
|
+
std::fs::write(path.join("dir/inner"), "lower").expect("the host file must be written");
|
|
709
|
+
for (target, link) in [
|
|
710
|
+
("target", "link"),
|
|
711
|
+
("dir", "dirlink"),
|
|
712
|
+
("missing", "dangling"),
|
|
713
|
+
] {
|
|
714
|
+
std::os::unix::fs::symlink(target, path.join(link)).expect("the link must be made");
|
|
715
|
+
}
|
|
716
|
+
Self(path)
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/// An empty upper over the host directory as the lower Layer.
|
|
720
|
+
fn overlay(&self) -> KernPath {
|
|
721
|
+
self.under(Arc::new(Tmpfs::new()))
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/// The host directory as the lower Layer beneath `upper`.
|
|
725
|
+
fn under(&self, upper: Arc<Tmpfs>) -> KernPath {
|
|
726
|
+
at(over(Arc::new(self.bind()), upper))
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
fn bind(&self) -> super::super::Bind {
|
|
730
|
+
super::super::Bind::new(&self.0).expect("the host directory must open")
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// The upper Layer's `dirlink` is a directory and the lower one's a link,
|
|
735
|
+
// so only merging directory over directory keeps the lower link's
|
|
736
|
+
// target out of the view.
|
|
737
|
+
// @behavior FS-113
|
|
738
|
+
#[cfg(unix)]
|
|
739
|
+
#[test]
|
|
740
|
+
fn a_lower_layers_link_beneath_an_upper_directory_is_not_merged() {
|
|
741
|
+
let tree = LinkTree::new("not-merged");
|
|
742
|
+
let overlay = tree.under(tmpfs_with("upper/dirlink/.keep", ""));
|
|
743
|
+
overlay
|
|
744
|
+
.vfs_unlink("dirlink/.keep")
|
|
745
|
+
.expect("the seed must go");
|
|
746
|
+
assert_eq!(
|
|
747
|
+
(
|
|
748
|
+
kind(&overlay, "dirlink/inner"),
|
|
749
|
+
names(&overlay, "dirlink").len()
|
|
750
|
+
),
|
|
751
|
+
(Err(FilesystemError::NotFound), 0),
|
|
752
|
+
"a name beneath a directory an upper Layer holds, where a lower Layer holds a link, \
|
|
753
|
+
through vfs_lstat and its listing must show nothing the link leads to"
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
#[cfg(unix)]
|
|
758
|
+
impl Drop for LinkTree {
|
|
759
|
+
fn drop(&mut self) {
|
|
760
|
+
let _ = std::fs::remove_dir_all(&self.0);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// The link stands in the Bind Layer beneath a Tmpfs one, so only
|
|
765
|
+
// the Overlay asking that Layer without following refuses it.
|
|
766
|
+
// @behavior FS-100
|
|
767
|
+
#[cfg(unix)]
|
|
768
|
+
#[test]
|
|
769
|
+
fn an_overlay_opens_no_link_as_a_directory_where_asked_not_to_follow() {
|
|
770
|
+
let host = LinkTree::new("nofollow");
|
|
771
|
+
let overlay = host.overlay();
|
|
772
|
+
assert_eq!(
|
|
773
|
+
(
|
|
774
|
+
overlay.open_dir("dirlink").map(drop),
|
|
775
|
+
overlay.vfs_readlink("dirlink")
|
|
776
|
+
),
|
|
777
|
+
(Err(FilesystemError::FilesystemLoop), Ok("dir".to_string())),
|
|
778
|
+
"a link a Bind Layer holds, opened through Overlay::open_dir, must be a \
|
|
779
|
+
link loop, and through vfs_readlink must answer where it points"
|
|
780
|
+
);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// @behavior FS-049
|
|
784
|
+
#[cfg(unix)]
|
|
785
|
+
#[test]
|
|
786
|
+
fn an_overlay_reports_a_link_its_layer_holds_as_a_link() {
|
|
787
|
+
let host = LinkTree::new("lstat");
|
|
788
|
+
let overlay = host.overlay();
|
|
789
|
+
let file_type = |path| kind(&overlay, path);
|
|
790
|
+
assert_eq!(
|
|
791
|
+
(file_type("link"), file_type("dangling")),
|
|
792
|
+
(Ok(FileType::Symlink), Ok(FileType::Symlink)),
|
|
793
|
+
"a link a Bind Layer holds, pointing at a file or at nothing, looked up through \
|
|
794
|
+
vfs_lstat must be reported as a link"
|
|
795
|
+
);
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// A link to a directory follows to one, and a dangling link follows to
|
|
799
|
+
// nothing, so only judging each link itself lets both go.
|
|
800
|
+
// @behavior FS-050
|
|
801
|
+
#[cfg(unix)]
|
|
802
|
+
#[test]
|
|
803
|
+
fn an_overlay_removes_a_link_its_read_only_layer_holds_as_itself() {
|
|
804
|
+
let host = LinkTree::new("unlink");
|
|
805
|
+
let overlay = host.overlay();
|
|
806
|
+
let removed = (
|
|
807
|
+
overlay.vfs_rmdir("dirlink"),
|
|
808
|
+
overlay.vfs_unlink("dirlink"),
|
|
809
|
+
overlay.vfs_unlink("dangling"),
|
|
810
|
+
);
|
|
811
|
+
let gone = |path| overlay.vfs_lstat(path).err();
|
|
812
|
+
assert_eq!(
|
|
813
|
+
(
|
|
814
|
+
removed,
|
|
815
|
+
gone("dirlink"),
|
|
816
|
+
gone("dangling"),
|
|
817
|
+
host.0.join("dirlink").is_symlink()
|
|
818
|
+
),
|
|
819
|
+
(
|
|
820
|
+
(Err(FilesystemError::NotADirectory), Ok(()), Ok(())),
|
|
821
|
+
Some(FilesystemError::NotFound),
|
|
822
|
+
Some(FilesystemError::NotFound),
|
|
823
|
+
true
|
|
824
|
+
),
|
|
825
|
+
"a link a read-only Bind Layer holds, removed through vfs_rmdir and \
|
|
826
|
+
vfs_unlink, must be refused as no directory and then removed as itself, \
|
|
827
|
+
leaving the Layer as it was"
|
|
828
|
+
);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// A write would copy the FIFO up, which opens it, and a rename would
|
|
832
|
+
// move what no Layer above can hold, so only refusals keep either
|
|
833
|
+
// from waiting on a peer or landing nothing.
|
|
834
|
+
// @behavior FS-092
|
|
835
|
+
#[cfg(unix)]
|
|
836
|
+
#[test]
|
|
837
|
+
fn an_overlay_neither_copies_up_nor_moves_a_fifo_below_it() {
|
|
838
|
+
let host = LinkTree::new("fifo");
|
|
839
|
+
let made = std::process::Command::new("mkfifo")
|
|
840
|
+
.arg(host.0.join("pipe"))
|
|
841
|
+
.status()
|
|
842
|
+
.expect("mkfifo must run");
|
|
843
|
+
assert!(made.success(), "the FIFO must be made");
|
|
844
|
+
let overlay = host.overlay();
|
|
845
|
+
assert_eq!(
|
|
846
|
+
(
|
|
847
|
+
write(&overlay, "pipe", b"upper"),
|
|
848
|
+
overlay.rename("pipe", &overlay, "moved")
|
|
849
|
+
),
|
|
850
|
+
(
|
|
851
|
+
Err(FilesystemError::PermissionDenied),
|
|
852
|
+
Err(FilesystemError::CrossesDevices)
|
|
853
|
+
),
|
|
854
|
+
"a FIFO a read-only Bind Layer holds, written or renamed through an Overlay, must \
|
|
855
|
+
be refused as permission denied and as crossing devices"
|
|
856
|
+
);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
// A FIFO is no directory, so each rename meets a kind it cannot
|
|
860
|
+
// replace; were it taken for neither, the directory made above would
|
|
861
|
+
// hide the FIFO, and the FIFO's rename would answer for its Layer.
|
|
862
|
+
// @behavior FS-095
|
|
863
|
+
#[cfg(unix)]
|
|
864
|
+
#[test]
|
|
865
|
+
fn an_overlay_replaces_neither_a_fifo_nor_a_directory_with_the_other() {
|
|
866
|
+
let host = LinkTree::new("fifo-kinds");
|
|
867
|
+
let made = std::process::Command::new("mkfifo")
|
|
868
|
+
.arg(host.0.join("pipe"))
|
|
869
|
+
.status()
|
|
870
|
+
.expect("mkfifo must run");
|
|
871
|
+
assert!(made.success(), "the FIFO must be made");
|
|
872
|
+
let overlay = host.overlay();
|
|
873
|
+
overlay
|
|
874
|
+
.vfs_mkdir("made")
|
|
875
|
+
.expect("a directory must be made in the upper");
|
|
876
|
+
let file_type = |path| kind(&overlay, path);
|
|
877
|
+
assert_eq!(
|
|
878
|
+
(
|
|
879
|
+
overlay.rename("pipe", &overlay, "dir"),
|
|
880
|
+
overlay.rename("made", &overlay, "pipe"),
|
|
881
|
+
file_type("pipe"),
|
|
882
|
+
file_type("made"),
|
|
883
|
+
),
|
|
884
|
+
(
|
|
885
|
+
Err(FilesystemError::IsADirectory),
|
|
886
|
+
Err(FilesystemError::NotADirectory),
|
|
887
|
+
Ok(FileType::Fifo),
|
|
888
|
+
Ok(FileType::Dir)
|
|
889
|
+
),
|
|
890
|
+
"a FIFO renamed onto a directory through an Overlay must be refused as a directory, \
|
|
891
|
+
and a directory renamed onto a FIFO as no directory, leaving both in place"
|
|
892
|
+
);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
/// A lower Layer whose one file, `file`, would keep any time it is asked
|
|
896
|
+
/// to, recording that it was.
|
|
897
|
+
struct KeepsTimes(Arc<AtomicUsize>);
|
|
898
|
+
|
|
899
|
+
impl FileSystemType for KeepsTimes {
|
|
900
|
+
fn fill_super(self: Arc<Self>, sb: &mut Filesystem) -> Result<(), FilesystemError> {
|
|
901
|
+
sb.d_make_root(Inode::new(&Kstat::new(FileType::Dir, 0), self));
|
|
902
|
+
Ok(())
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
impl InodeOperations for KeepsTimes {
|
|
907
|
+
fn getattr(&self, dentry: &Dentry) -> Result<Kstat, FilesystemError> {
|
|
908
|
+
Ok(match dentry.name().as_str() {
|
|
909
|
+
"file" => Kstat::new(FileType::RegFile, 0).with_identity(1, 2),
|
|
910
|
+
_ => Kstat::new(FileType::Dir, 0).with_identity(1, 1),
|
|
911
|
+
})
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
fn lookup(&self, dentry: &Dentry) -> Result<Lookup, FilesystemError> {
|
|
915
|
+
Ok(match dentry.name().as_str() {
|
|
916
|
+
"file" => Lookup::Found(Inode::new(
|
|
917
|
+
&Kstat::new(FileType::RegFile, 0).with_identity(1, 2),
|
|
918
|
+
Arc::new(Self(self.0.clone())),
|
|
919
|
+
)),
|
|
920
|
+
_ => Lookup::Absent,
|
|
921
|
+
})
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
fn setattr(&self, _dentry: &Dentry, _attr: &Iattr) -> Result<(), FilesystemError> {
|
|
925
|
+
self.0.fetch_add(1, Ordering::SeqCst);
|
|
926
|
+
Ok(())
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
fn open(
|
|
930
|
+
&self,
|
|
931
|
+
_dentry: &Dentry,
|
|
932
|
+
_how: &OpenHow,
|
|
933
|
+
) -> Result<Box<dyn FileOperations>, FilesystemError> {
|
|
934
|
+
struct Empty;
|
|
935
|
+
impl FileOperations for Empty {
|
|
936
|
+
fn read_iter(
|
|
937
|
+
&mut self,
|
|
938
|
+
_iocb: &mut Kiocb,
|
|
939
|
+
_buf: &mut [u8],
|
|
940
|
+
) -> Result<usize, FilesystemError> {
|
|
941
|
+
Ok(0)
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
Ok(Box::new(Empty))
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// A lower Layer that would keep the time shows the call stopped at the upper.
|
|
949
|
+
// @behavior FS-147
|
|
950
|
+
#[test]
|
|
951
|
+
fn a_time_set_through_an_overlay_leaves_its_lower_layer_as_it_was() {
|
|
952
|
+
let asked = Arc::new(AtomicUsize::new(0));
|
|
953
|
+
let upper = Arc::new(Tmpfs::new());
|
|
954
|
+
let overlay = Overlay::new(
|
|
955
|
+
vec![Filesystem::new(Arc::new(KeepsTimes(asked.clone())))
|
|
956
|
+
.expect("the instance must stand")],
|
|
957
|
+
Some(Filesystem::new(upper.clone()).expect("the instance must stand")),
|
|
958
|
+
)
|
|
959
|
+
.unwrap_or_else(|_| panic!("the Overlay must make"));
|
|
960
|
+
let mount = crate::mount::Mount::new(
|
|
961
|
+
"/data",
|
|
962
|
+
Filesystem::new(Arc::new(overlay)).expect("the instance must stand"),
|
|
963
|
+
crate::mount::Access::ReadWrite,
|
|
964
|
+
)
|
|
965
|
+
.expect("/data is a guest path");
|
|
966
|
+
let answer = crate::vfs::do_utimes_path(
|
|
967
|
+
&Arc::default(),
|
|
968
|
+
&crate::vfs::Path::root(mount.mnt()),
|
|
969
|
+
"file",
|
|
970
|
+
false,
|
|
971
|
+
&Iattr::times(None, Some(crate::filesystem::SetTime::Now)),
|
|
972
|
+
);
|
|
973
|
+
assert_eq!(
|
|
974
|
+
(
|
|
975
|
+
answer,
|
|
976
|
+
asked.load(Ordering::SeqCst),
|
|
977
|
+
kern_path(upper).vfs_lstat("upper/file").is_ok()
|
|
978
|
+
),
|
|
979
|
+
(Ok(()), 0, true),
|
|
980
|
+
"vfs::do_utimes_paththrough an Overlay on a file of its lower Layer must copy the file \
|
|
981
|
+
up, set the time there, and never ask the lower Layer"
|
|
982
|
+
);
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
// A recursive climb needs more than this stack at this depth, in a build
|
|
986
|
+
// of any optimization, so only a loop passes.
|
|
987
|
+
// @behavior FS-146
|
|
988
|
+
#[test]
|
|
989
|
+
fn a_change_copies_up_however_deep_it_lands() {
|
|
990
|
+
let deep = vec!["a"; 1500].join("/");
|
|
991
|
+
let lower: Tmpfs = [(format!("{deep}/file"), "x")].into_iter().collect();
|
|
992
|
+
let overlay = Overlay::new(
|
|
993
|
+
vec![Filesystem::new(Arc::new(lower)).expect("the instance must stand")],
|
|
994
|
+
Some(Filesystem::new(Arc::new(Tmpfs::new())).expect("the instance must stand")),
|
|
995
|
+
)
|
|
996
|
+
.unwrap_or_else(|_| panic!("the Overlay must make"));
|
|
997
|
+
let made = std::thread::Builder::new()
|
|
998
|
+
.stack_size(256 * 1024)
|
|
999
|
+
.spawn(move || {
|
|
1000
|
+
let view = kern_path(Arc::new(overlay));
|
|
1001
|
+
view.vfs_mkdir(&format!("{deep}/made"))?;
|
|
1002
|
+
view.vfs_lstat(&format!("{deep}/made"))
|
|
1003
|
+
.map(|found| found.file_type)
|
|
1004
|
+
})
|
|
1005
|
+
.expect("the thread must start")
|
|
1006
|
+
.join()
|
|
1007
|
+
.expect("the thread must not panic");
|
|
1008
|
+
assert_eq!(
|
|
1009
|
+
made,
|
|
1010
|
+
Ok(FileType::Dir),
|
|
1011
|
+
"a directory made 1500 directories deep in a lower Layer, through an Overlay on a \
|
|
1012
|
+
256 KiB stack, must copy every directory above it up and stand"
|
|
1013
|
+
);
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
// The upper is a Tmpfs, which holds no link, so a link cannot land there.
|
|
1017
|
+
// @behavior FS-051
|
|
1018
|
+
#[cfg(unix)]
|
|
1019
|
+
#[test]
|
|
1020
|
+
fn an_overlay_refuses_to_move_a_link_its_read_only_layer_holds() {
|
|
1021
|
+
let host = LinkTree::new("through");
|
|
1022
|
+
let overlay = host.overlay();
|
|
1023
|
+
assert_eq!(
|
|
1024
|
+
(
|
|
1025
|
+
overlay.rename("link", &overlay, "moved"),
|
|
1026
|
+
read(&overlay, "target")
|
|
1027
|
+
),
|
|
1028
|
+
(Err(FilesystemError::CrossesDevices), b"lower".to_vec()),
|
|
1029
|
+
"a rename of a link a read-only Bind Layer holds, through an Overlay, must be refused \
|
|
1030
|
+
as crossing devices and leave the target as it was"
|
|
1031
|
+
);
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
// The lower Tmpfs holds a readable `dangling`, so only the upper
|
|
1035
|
+
// link standing at that path can keep the read from reaching it.
|
|
1036
|
+
// @behavior FS-053
|
|
1037
|
+
#[cfg(unix)]
|
|
1038
|
+
#[test]
|
|
1039
|
+
fn a_link_a_layer_holds_hides_what_the_layers_below_hold_there() {
|
|
1040
|
+
let host = LinkTree::new("shadow");
|
|
1041
|
+
let overlay = at(lowers(
|
|
1042
|
+
Arc::new(host.bind()),
|
|
1043
|
+
tmpfs_with("dangling", "lower"),
|
|
1044
|
+
));
|
|
1045
|
+
assert_eq!(
|
|
1046
|
+
overlay
|
|
1047
|
+
.open(
|
|
1048
|
+
"dangling",
|
|
1049
|
+
&OpenHow {
|
|
1050
|
+
read: true,
|
|
1051
|
+
..OpenHow::default()
|
|
1052
|
+
}
|
|
1053
|
+
)
|
|
1054
|
+
.map(drop),
|
|
1055
|
+
Err(FilesystemError::FilesystemLoop),
|
|
1056
|
+
"a lower file beneath a dangling link a higher Bind Layer holds at its path, \
|
|
1057
|
+
opened through Overlay::open, must be refused as the link, the file unreached"
|
|
1058
|
+
);
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
// The lower Tmpfs holds `dirlink/x` beside where the upper link points,
|
|
1062
|
+
// so only the link hiding everything beneath it keeps `x` out.
|
|
1063
|
+
// @behavior FS-057
|
|
1064
|
+
#[cfg(unix)]
|
|
1065
|
+
#[test]
|
|
1066
|
+
fn a_link_a_layer_holds_hides_what_the_layers_below_hold_beneath_it() {
|
|
1067
|
+
let host = LinkTree::new("shadow-beneath");
|
|
1068
|
+
let overlay = at(lowers(
|
|
1069
|
+
Arc::new(host.bind()),
|
|
1070
|
+
tmpfs_with("dirlink/x", "lower"),
|
|
1071
|
+
));
|
|
1072
|
+
assert_eq!(
|
|
1073
|
+
(
|
|
1074
|
+
overlay
|
|
1075
|
+
.vfs_lstat("dirlink")
|
|
1076
|
+
.map(|metadata| metadata.file_type),
|
|
1077
|
+
overlay.open_dir("dirlink").map(drop)
|
|
1078
|
+
),
|
|
1079
|
+
(Ok(FileType::Symlink), Err(FilesystemError::FilesystemLoop)),
|
|
1080
|
+
"a directory a higher Bind Layer holds as a link, looked up and opened through an \
|
|
1081
|
+
Overlay, must stand as the link, so none of a lower Layer's entries beneath it show"
|
|
1082
|
+
);
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
// The lower directory stays in its Layer, so only the whiteout keeps
|
|
1086
|
+
// it from showing once the upper file goes.
|
|
1087
|
+
// @behavior FS-058
|
|
1088
|
+
#[test]
|
|
1089
|
+
fn removing_a_file_over_a_lower_directory_leaves_it_hidden() {
|
|
1090
|
+
let lower = tmpfs_with("f/g", "lower");
|
|
1091
|
+
let overlay = at(over(lower.clone(), tmpfs_with("upper/f", "upper")));
|
|
1092
|
+
let removed = overlay.vfs_unlink("f");
|
|
1093
|
+
assert_eq!(
|
|
1094
|
+
(
|
|
1095
|
+
removed,
|
|
1096
|
+
overlay.vfs_lstat("f").err(),
|
|
1097
|
+
on(&lower).vfs_lstat("f/g").is_ok()
|
|
1098
|
+
),
|
|
1099
|
+
(Ok(()), Some(FilesystemError::NotFound), true),
|
|
1100
|
+
"a file a higher Layer holds over a lower Layer's directory, removed through \
|
|
1101
|
+
vfs_unlink, must leave the path gone from the view and the lower \
|
|
1102
|
+
directory as it was"
|
|
1103
|
+
);
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
// The upper's `dir` holds only the whiteout hiding `file`, so
|
|
1107
|
+
// only judging the view, not that copy, lets the move go.
|
|
1108
|
+
// @behavior FS-059
|
|
1109
|
+
#[test]
|
|
1110
|
+
fn a_directory_moves_onto_one_holding_only_markers() {
|
|
1111
|
+
let (overlay, _) = overlay_on(Arc::new(Tmpfs::new()));
|
|
1112
|
+
overlay
|
|
1113
|
+
.vfs_unlink("dir/file")
|
|
1114
|
+
.expect("the file must be removed");
|
|
1115
|
+
overlay
|
|
1116
|
+
.vfs_mkdir("new")
|
|
1117
|
+
.expect("the directory must be made");
|
|
1118
|
+
overwrite(&overlay, "new/made", b"made");
|
|
1119
|
+
let moved = overlay.rename("new", &overlay, "dir");
|
|
1120
|
+
assert_eq!(
|
|
1121
|
+
(moved, names(&overlay, "dir")),
|
|
1122
|
+
(Ok(()), vec!["made".to_string()]),
|
|
1123
|
+
"a directory renamed through Overlay::rename onto one the view shows empty, whose \
|
|
1124
|
+
upper copy holds only a whiteout, must take its place and show only its own entries"
|
|
1125
|
+
);
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
// The directory made again is marked opaque in the upper, so only that
|
|
1129
|
+
// mark tells it apart from one the lower Layer still holds.
|
|
1130
|
+
// @behavior FS-060
|
|
1131
|
+
#[test]
|
|
1132
|
+
fn a_directory_made_again_moves_as_the_uppers_own() {
|
|
1133
|
+
let (overlay, _) = overlay_on(Arc::new(Tmpfs::new()));
|
|
1134
|
+
overlay
|
|
1135
|
+
.vfs_unlink("dir/file")
|
|
1136
|
+
.expect("the file must be removed");
|
|
1137
|
+
overlay
|
|
1138
|
+
.vfs_rmdir("dir")
|
|
1139
|
+
.expect("the directory must be removed");
|
|
1140
|
+
overlay
|
|
1141
|
+
.vfs_mkdir("dir")
|
|
1142
|
+
.expect("the directory must be made again");
|
|
1143
|
+
let moved = overlay.rename("dir", &overlay, "moved");
|
|
1144
|
+
assert_eq!(
|
|
1145
|
+
(
|
|
1146
|
+
moved,
|
|
1147
|
+
overlay.vfs_lstat("dir").err(),
|
|
1148
|
+
overlay
|
|
1149
|
+
.open_dir("moved")
|
|
1150
|
+
.and_then(|dir| dir.iterate_dir())
|
|
1151
|
+
.map(|entries| entries.len())
|
|
1152
|
+
),
|
|
1153
|
+
(Ok(()), Some(FilesystemError::NotFound), Ok(0)),
|
|
1154
|
+
"a directory made again through an Overlay where a lower Layer's one was removed, \
|
|
1155
|
+
renamed through Overlay::rename, must move and leave nothing of the lower one behind"
|
|
1156
|
+
);
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
// @behavior FS-061
|
|
1160
|
+
#[test]
|
|
1161
|
+
fn an_overlay_refuses_a_file_moved_beneath_itself_as_no_directory() {
|
|
1162
|
+
let (overlay, _) = overlay_on(Arc::new(Tmpfs::new()));
|
|
1163
|
+
assert_eq!(
|
|
1164
|
+
overlay.rename("dir/file", &overlay, "dir/file/x"),
|
|
1165
|
+
Err(FilesystemError::NotADirectory),
|
|
1166
|
+
"a file renamed through Overlay::rename to a path beneath itself must be refused as \
|
|
1167
|
+
no directory, as rename(2) refuses it"
|
|
1168
|
+
);
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
// The link points at nothing, so only the link itself can be what an
|
|
1172
|
+
// exclusive create or a new directory finds there.
|
|
1173
|
+
// @behavior FS-052
|
|
1174
|
+
#[cfg(unix)]
|
|
1175
|
+
#[test]
|
|
1176
|
+
fn an_overlay_finds_a_dangling_link_where_a_new_entry_would_go() {
|
|
1177
|
+
let host = LinkTree::new("dangling");
|
|
1178
|
+
let overlay = host.overlay();
|
|
1179
|
+
let exclusive = OpenHow {
|
|
1180
|
+
write: true,
|
|
1181
|
+
..OpenHow::default()
|
|
1182
|
+
};
|
|
1183
|
+
assert_eq!(
|
|
1184
|
+
(
|
|
1185
|
+
overlay.create("dangling", &exclusive).map(drop),
|
|
1186
|
+
overlay.vfs_mkdir("dangling")
|
|
1187
|
+
),
|
|
1188
|
+
(
|
|
1189
|
+
Err(FilesystemError::AlreadyExists),
|
|
1190
|
+
Err(FilesystemError::AlreadyExists)
|
|
1191
|
+
),
|
|
1192
|
+
"an exclusive create and a create_dir at a dangling link a lower Bind Layer \
|
|
1193
|
+
holds, through an Overlay, must find it already there"
|
|
1194
|
+
);
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
// @behavior FS-008
|
|
1198
|
+
#[test]
|
|
1199
|
+
fn an_overlay_reads_through_to_a_lower_layer() {
|
|
1200
|
+
let (overlay, _) = overlay_on(Arc::new(Tmpfs::new()));
|
|
1201
|
+
assert_eq!(
|
|
1202
|
+
read(&overlay, "dir/file"),
|
|
1203
|
+
b"lower",
|
|
1204
|
+
"a lower file read through Overlay::open must read back the lower content"
|
|
1205
|
+
);
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
// @behavior FS-009
|
|
1209
|
+
#[test]
|
|
1210
|
+
fn a_write_to_a_lower_layers_file_lands_in_the_upper() {
|
|
1211
|
+
let upper = Arc::new(Tmpfs::new());
|
|
1212
|
+
let (overlay, _) = overlay_on(upper.clone());
|
|
1213
|
+
overwrite(&overlay, "dir/file", b"upper");
|
|
1214
|
+
assert_eq!(
|
|
1215
|
+
read(&on(&upper), "upper/dir/file"),
|
|
1216
|
+
b"upper",
|
|
1217
|
+
"a lower Layer's file written through Overlay::open must land in the upper"
|
|
1218
|
+
);
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
// @behavior FS-010
|
|
1222
|
+
#[test]
|
|
1223
|
+
fn an_overlay_leaves_a_lower_layer_as_it_was() {
|
|
1224
|
+
let (overlay, lower) = overlay_on(Arc::new(Tmpfs::new()));
|
|
1225
|
+
overwrite(&overlay, "dir/file", b"upper");
|
|
1226
|
+
overlay
|
|
1227
|
+
.vfs_unlink("dir/file")
|
|
1228
|
+
.expect("the file must be removed");
|
|
1229
|
+
assert_eq!(
|
|
1230
|
+
read(&on(&lower), "dir/file"),
|
|
1231
|
+
b"lower",
|
|
1232
|
+
"a lower Layer's file written and removed through an Overlay must stay as it was"
|
|
1233
|
+
);
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
// @behavior FS-011
|
|
1237
|
+
#[test]
|
|
1238
|
+
fn a_file_removed_through_an_overlay_is_gone_from_its_view() {
|
|
1239
|
+
let (overlay, _) = overlay_on(Arc::new(Tmpfs::new()));
|
|
1240
|
+
overlay
|
|
1241
|
+
.vfs_unlink("dir/file")
|
|
1242
|
+
.expect("the file must be removed");
|
|
1243
|
+
assert_eq!(
|
|
1244
|
+
overlay.vfs_lstat("dir/file"),
|
|
1245
|
+
Err(FilesystemError::NotFound),
|
|
1246
|
+
"a lower file removed through vfs_unlink must no longer be found there"
|
|
1247
|
+
);
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
// @behavior FS-013
|
|
1251
|
+
#[test]
|
|
1252
|
+
fn an_upper_layer_hides_the_same_path_in_a_lower_one() {
|
|
1253
|
+
let overlay = at(lowers(
|
|
1254
|
+
tmpfs_with("note", "upper"),
|
|
1255
|
+
tmpfs_with("note", "lower"),
|
|
1256
|
+
));
|
|
1257
|
+
assert_eq!(
|
|
1258
|
+
read(&overlay, "note"),
|
|
1259
|
+
b"upper",
|
|
1260
|
+
"a path both Layers hold read through Overlay::open must read the upper Layer's content"
|
|
1261
|
+
);
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
// @behavior FS-014
|
|
1265
|
+
#[test]
|
|
1266
|
+
fn a_write_to_the_uppers_file_changes_it_in_place() {
|
|
1267
|
+
let upper = tmpfs_with("upper/note", "upper");
|
|
1268
|
+
let overlay = at(over(tmpfs_with("note", "lower"), upper.clone()));
|
|
1269
|
+
overwrite(&overlay, "note", b"changed");
|
|
1270
|
+
assert_eq!(
|
|
1271
|
+
read(&on(&upper), "upper/note"),
|
|
1272
|
+
b"changed",
|
|
1273
|
+
"the upper's file written through Overlay::open must change in place"
|
|
1274
|
+
);
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
// @behavior FS-015
|
|
1278
|
+
#[test]
|
|
1279
|
+
fn a_new_file_lands_in_the_upper() {
|
|
1280
|
+
let upper = Arc::new(Tmpfs::new());
|
|
1281
|
+
let lower = Arc::new(Tmpfs::new());
|
|
1282
|
+
let overlay = at(over(lower.clone(), upper.clone()));
|
|
1283
|
+
overwrite(&overlay, "new", b"made");
|
|
1284
|
+
assert_eq!(
|
|
1285
|
+
(read(&on(&upper), "upper/new"), on(&lower).vfs_lstat("new")),
|
|
1286
|
+
(b"made".to_vec(), Err(FilesystemError::NotFound)),
|
|
1287
|
+
"a file created through Overlay::open must land in the upper alone"
|
|
1288
|
+
);
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
// @behavior FS-016
|
|
1292
|
+
#[test]
|
|
1293
|
+
fn a_removal_leaves_a_whiteout_in_the_upper() {
|
|
1294
|
+
let upper = Arc::new(Tmpfs::new());
|
|
1295
|
+
let (overlay, _) = overlay_on(upper.clone());
|
|
1296
|
+
overlay
|
|
1297
|
+
.vfs_unlink("dir/file")
|
|
1298
|
+
.expect("the file must be removed");
|
|
1299
|
+
assert_eq!(
|
|
1300
|
+
on(&upper)
|
|
1301
|
+
.vfs_lstat("upper/dir/file")
|
|
1302
|
+
.map(|metadata| metadata.file_type),
|
|
1303
|
+
Ok(FileType::Chrdev),
|
|
1304
|
+
"a lower Layer's file removed through vfs_unlink must leave a whiteout, a \
|
|
1305
|
+
device of no kind the guest opens, at its name in the upper"
|
|
1306
|
+
);
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
// The Layer changes behind the view between the lookup and the listing, so
|
|
1310
|
+
// only Layers bound as the name is found keep the directory it found, as
|
|
1311
|
+
// they cannot be taken for another one by a move in that window.
|
|
1312
|
+
// @behavior FS-165
|
|
1313
|
+
#[test]
|
|
1314
|
+
fn a_directory_found_stands_for_the_layers_it_was_found_in() {
|
|
1315
|
+
use crate::vfs::lookup_one_unlocked;
|
|
1316
|
+
let lower = Arc::new([("dir/old", "x")].into_iter().collect::<Tmpfs>());
|
|
1317
|
+
let overlay = Arc::new(over(lower.clone(), Arc::new(Tmpfs::new())));
|
|
1318
|
+
let filesystem = Filesystem::new(overlay.clone()).expect("the instance must stand");
|
|
1319
|
+
let root = filesystem.s_root();
|
|
1320
|
+
let dir = lookup_one_unlocked(&Default::default(), &filesystem, &root, "dir")
|
|
1321
|
+
.expect("the directory must be found");
|
|
1322
|
+
let layer = on(&lower);
|
|
1323
|
+
layer
|
|
1324
|
+
.rename("dir", &layer, "moved")
|
|
1325
|
+
.expect("the Layer's directory must move");
|
|
1326
|
+
layer.vfs_mkdir("dir").expect("another must be made");
|
|
1327
|
+
overwrite(&layer, "dir/new", b"y");
|
|
1328
|
+
let names: Vec<String> = overlay
|
|
1329
|
+
.fs
|
|
1330
|
+
.dir_read_merged(
|
|
1331
|
+
&overlay
|
|
1332
|
+
.fs
|
|
1333
|
+
.dir(&dir)
|
|
1334
|
+
.expect("the directory must stand")
|
|
1335
|
+
.level(),
|
|
1336
|
+
)
|
|
1337
|
+
.expect("the directory must list")
|
|
1338
|
+
.into_iter()
|
|
1339
|
+
.map(|entry| entry.name)
|
|
1340
|
+
.collect();
|
|
1341
|
+
assert_eq!(
|
|
1342
|
+
names,
|
|
1343
|
+
["old"],
|
|
1344
|
+
"OverlayFs::dir_read_merged of a directory OverlayFs found before its Layer changed must list \
|
|
1345
|
+
the Layer's directory found with it"
|
|
1346
|
+
);
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
// What fails after the exchange only a second writer's use of the inodes
|
|
1350
|
+
// can force, so the exchange is called alone: the next walk must find the
|
|
1351
|
+
// empty directory rather than the dentry whose upper no longer matches.
|
|
1352
|
+
// @behavior FS-164
|
|
1353
|
+
#[test]
|
|
1354
|
+
fn a_directory_emptied_for_its_removal_stays_empty_should_the_removal_fail() {
|
|
1355
|
+
use crate::vfs::{lookup_one_unlocked, vfs_unlink};
|
|
1356
|
+
let lower = Arc::new([("dir/file", "x")].into_iter().collect::<Tmpfs>());
|
|
1357
|
+
let overlay = Arc::new(over(lower, Arc::new(Tmpfs::new())));
|
|
1358
|
+
let filesystem = Filesystem::new(overlay.clone()).expect("the instance must stand");
|
|
1359
|
+
let root = filesystem.s_root();
|
|
1360
|
+
let dir = lookup_one_unlocked(&Default::default(), &filesystem, &root, "dir")
|
|
1361
|
+
.expect("the directory must be found");
|
|
1362
|
+
let file = lookup_one_unlocked(&Default::default(), &filesystem, &dir, "file")
|
|
1363
|
+
.expect("the file must be found");
|
|
1364
|
+
vfs_unlink(&Default::default(), &filesystem, &dir, &file)
|
|
1365
|
+
.expect("the file must be removed");
|
|
1366
|
+
let upper_root = overlay.fs.dir(&root).expect("the root must stand").level()[0]
|
|
1367
|
+
.clone()
|
|
1368
|
+
.expect("the upper shows the root");
|
|
1369
|
+
overlay
|
|
1370
|
+
.fs
|
|
1371
|
+
.clear_empty(&upper_root, "dir")
|
|
1372
|
+
.expect("the directory must be exchanged");
|
|
1373
|
+
let walked = lookup_one_unlocked(&Default::default(), &filesystem, &root, "dir")
|
|
1374
|
+
.expect("the directory must be found again");
|
|
1375
|
+
let names = overlay.fs.dir_read_merged(
|
|
1376
|
+
&overlay
|
|
1377
|
+
.fs
|
|
1378
|
+
.dir(&walked)
|
|
1379
|
+
.expect("the directory must stand")
|
|
1380
|
+
.level(),
|
|
1381
|
+
);
|
|
1382
|
+
assert_eq!(
|
|
1383
|
+
names.map(|entries| entries.len()),
|
|
1384
|
+
Ok(0),
|
|
1385
|
+
"OverlayFs::dir_read_merged of a directory found by a walk after OverlayFs::clear_empty \
|
|
1386
|
+
replaced it must show nothing of the lower Layer below it"
|
|
1387
|
+
);
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
// @behavior FS-017
|
|
1391
|
+
#[test]
|
|
1392
|
+
fn a_whiteout_lasts_as_long_as_the_upper() {
|
|
1393
|
+
let upper = Arc::new(Tmpfs::new());
|
|
1394
|
+
let (overlay, lower) = overlay_on(upper.clone());
|
|
1395
|
+
overlay
|
|
1396
|
+
.vfs_unlink("dir/file")
|
|
1397
|
+
.expect("the file must be removed");
|
|
1398
|
+
let rebuilt = at(over(lower, upper));
|
|
1399
|
+
assert_eq!(
|
|
1400
|
+
rebuilt.vfs_lstat("dir/file"),
|
|
1401
|
+
Err(FilesystemError::NotFound),
|
|
1402
|
+
"a lower file removed through an Overlay over a Tmpfs must stay gone from a new Overlay over the same Layers"
|
|
1403
|
+
);
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
// @behavior FS-018
|
|
1407
|
+
#[test]
|
|
1408
|
+
fn a_directory_made_again_where_one_was_removed_shows_nothing_from_below() {
|
|
1409
|
+
let (overlay, _) = overlay_on(Arc::new(Tmpfs::new()));
|
|
1410
|
+
overlay
|
|
1411
|
+
.vfs_unlink("dir/file")
|
|
1412
|
+
.expect("the file must be removed");
|
|
1413
|
+
overlay
|
|
1414
|
+
.vfs_rmdir("dir")
|
|
1415
|
+
.expect("the directory must be removed");
|
|
1416
|
+
overlay
|
|
1417
|
+
.vfs_mkdir("dir")
|
|
1418
|
+
.expect("the directory must be made");
|
|
1419
|
+
assert_eq!(
|
|
1420
|
+
names(&overlay, "dir"),
|
|
1421
|
+
Vec::<String>::new(),
|
|
1422
|
+
"a directory made again through vfs_mkdir must list nothing from the lower Layer"
|
|
1423
|
+
);
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
// @behavior FS-019
|
|
1427
|
+
#[test]
|
|
1428
|
+
fn an_overlay_lists_none_of_its_markers() {
|
|
1429
|
+
let (overlay, _) = overlay_on(Arc::new(Tmpfs::new()));
|
|
1430
|
+
overwrite(&overlay, "dir/new", b"made");
|
|
1431
|
+
overlay
|
|
1432
|
+
.vfs_unlink("dir/file")
|
|
1433
|
+
.expect("the file must be removed");
|
|
1434
|
+
assert_eq!(
|
|
1435
|
+
names(&overlay, "dir"),
|
|
1436
|
+
["new"],
|
|
1437
|
+
"a directory holding a whiteout listed through Overlay::read_dir must not list the \
|
|
1438
|
+
whited-out name"
|
|
1439
|
+
);
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
// Linux's `ovl_rename` answers ESTALE once the upper moved beneath it; the
|
|
1443
|
+
// view is undefined here, so the promise is only that the call answers,
|
|
1444
|
+
// and the bound turns a hang into a failure.
|
|
1445
|
+
// @behavior FS-204
|
|
1446
|
+
#[test]
|
|
1447
|
+
fn an_upper_changed_beside_its_overlay_still_answers() {
|
|
1448
|
+
use crate::vfs;
|
|
1449
|
+
let (overlay, upper) = beside_its_upper(
|
|
1450
|
+
[("upper/a/x", "x"), ("upper/a/k", "k")]
|
|
1451
|
+
.into_iter()
|
|
1452
|
+
.collect(),
|
|
1453
|
+
);
|
|
1454
|
+
let cred = Arc::default();
|
|
1455
|
+
vfs::vfs_fstatat(&cred, &overlay, "a/x", false).expect("a/x must stand");
|
|
1456
|
+
vfs::filename_renameat2(&cred, &upper, "upper/a", &upper, "upper/b")
|
|
1457
|
+
.expect("the upper's directory must move");
|
|
1458
|
+
let answered = crate::sched::testing::within_max_wait(|| {
|
|
1459
|
+
vfs::filename_renameat2(&cred, &overlay, "a/x", &overlay, "a/y").err()
|
|
1460
|
+
});
|
|
1461
|
+
assert_ne!(
|
|
1462
|
+
answered,
|
|
1463
|
+
Some(FilesystemError::Interrupted),
|
|
1464
|
+
"vfs::filename_renameat2 through an Overlay whose upper another Mount changed must \
|
|
1465
|
+
answer within its bound"
|
|
1466
|
+
);
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
// The lower Layer is a Tmpfs seen through a wrapper, so it reports the
|
|
1470
|
+
// device a whiteout is, yet is no Tmpfs whose marks the Overlay reads.
|
|
1471
|
+
// @behavior FS-203
|
|
1472
|
+
#[test]
|
|
1473
|
+
fn an_overlay_reads_a_whiteout_only_from_a_tmpfs() {
|
|
1474
|
+
let tmpfs = Tmpfs::new();
|
|
1475
|
+
tmpfs.whiteout("device").expect("the device must be made");
|
|
1476
|
+
let lower = Wrap::new(Arc::new(tmpfs), Arc::new(CountsLookups::default()));
|
|
1477
|
+
let overlay = at(over(Arc::new(lower), Arc::new(Tmpfs::new())));
|
|
1478
|
+
assert_eq!(
|
|
1479
|
+
(
|
|
1480
|
+
names(&overlay, ""),
|
|
1481
|
+
overlay.vfs_lstat("device").map(|found| found.file_type)
|
|
1482
|
+
),
|
|
1483
|
+
(vec!["device".to_string()], Ok(FileType::Chrdev)),
|
|
1484
|
+
"a character device in a lower Layer that is no Tmpfs, listed and stated through an \
|
|
1485
|
+
Overlay, must be listed and found rather than read as a whiteout"
|
|
1486
|
+
);
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
// A whiteout is a device rather than a name, so no name is the
|
|
1490
|
+
// Overlay's to reserve.
|
|
1491
|
+
// @behavior FS-020
|
|
1492
|
+
#[test]
|
|
1493
|
+
fn a_name_starting_with_wh_is_an_ordinary_name() {
|
|
1494
|
+
let (overlay, _) = overlay_on(Arc::new(Tmpfs::new()));
|
|
1495
|
+
overwrite(&overlay, "dir/.wh.file", b"made");
|
|
1496
|
+
assert_eq!(
|
|
1497
|
+
(read(&overlay, "dir/.wh.file"), read(&overlay, "dir/file")),
|
|
1498
|
+
(b"made".to_vec(), b"lower".to_vec()),
|
|
1499
|
+
"a file named .wh.file created through Overlay::open must be made, and must hide \
|
|
1500
|
+
nothing"
|
|
1501
|
+
);
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
/// An Overlay of two lower Layers and no upper on a Mount allowing writes,
|
|
1505
|
+
/// and the root of that Mount.
|
|
1506
|
+
fn mounted_with_no_upper() -> (crate::mount::Mount, crate::vfs::Path) {
|
|
1507
|
+
let overlay = lowers(tmpfs_with("dir/file", "lower"), Arc::new(Tmpfs::new()));
|
|
1508
|
+
let mount = crate::mount::Mount::new(
|
|
1509
|
+
"/data",
|
|
1510
|
+
Filesystem::new(Arc::new(overlay)).expect("the instance must stand"),
|
|
1511
|
+
crate::mount::Access::ReadWrite,
|
|
1512
|
+
)
|
|
1513
|
+
.expect("/data is a guest path");
|
|
1514
|
+
let root = crate::vfs::Path::root(mount.mnt());
|
|
1515
|
+
(mount, root)
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
// @behavior FS-021
|
|
1519
|
+
#[test]
|
|
1520
|
+
fn an_overlay_with_no_upper_refuses_every_change_as_read_only() {
|
|
1521
|
+
let (mount, root) = mounted_with_no_upper();
|
|
1522
|
+
let how = crate::vfs::OpenHow {
|
|
1523
|
+
write: true,
|
|
1524
|
+
..Default::default()
|
|
1525
|
+
};
|
|
1526
|
+
let written = crate::vfs::kern_path::filp_open(&Arc::default(), &root, "dir/file", &how);
|
|
1527
|
+
assert_eq!(
|
|
1528
|
+
(written.err(), mount.mnt().is_readonly()),
|
|
1529
|
+
(Some(FilesystemError::ReadOnlyFilesystem), true),
|
|
1530
|
+
"a file opened to write through an Overlay with no upper on a read-write Mount must \
|
|
1531
|
+
be refused as read-only"
|
|
1532
|
+
);
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
/// A Layer holding a name past what it can hold, as a Layer on a host with
|
|
1536
|
+
/// a shorter limit answers one.
|
|
1537
|
+
struct RefusesLongName;
|
|
1538
|
+
|
|
1539
|
+
impl crate::filesystem::wrap::Hook for RefusesLongName {
|
|
1540
|
+
fn lookup(
|
|
1541
|
+
&self,
|
|
1542
|
+
inner: &dyn InodeOperations,
|
|
1543
|
+
dentry: &Dentry,
|
|
1544
|
+
) -> Result<Lookup, FilesystemError> {
|
|
1545
|
+
match dentry.name().as_str() {
|
|
1546
|
+
"long" => Err(FilesystemError::InvalidFilename),
|
|
1547
|
+
_ => inner.lookup(dentry),
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
// `ovl_lookup_single` moves on from ENAMETOOLONG, so a name one Layer cannot
|
|
1553
|
+
// hold is only absent there, and the upper takes it.
|
|
1554
|
+
// @behavior FS-174
|
|
1555
|
+
#[test]
|
|
1556
|
+
fn a_name_too_long_for_a_lower_layer_lands_in_the_upper() {
|
|
1557
|
+
let lower =
|
|
1558
|
+
crate::filesystem::wrap::Wrap::new(Arc::new(Tmpfs::new()), Arc::new(RefusesLongName));
|
|
1559
|
+
let upper = Arc::new(Tmpfs::new());
|
|
1560
|
+
let overlay = at(over(Arc::new(lower), upper.clone()));
|
|
1561
|
+
assert_eq!(
|
|
1562
|
+
(
|
|
1563
|
+
overlay.vfs_mkdir("long"),
|
|
1564
|
+
on(&upper)
|
|
1565
|
+
.vfs_lstat("upper/long")
|
|
1566
|
+
.map(|found| found.file_type)
|
|
1567
|
+
),
|
|
1568
|
+
(Ok(()), Ok(FileType::Dir)),
|
|
1569
|
+
"a directory made through an Overlay at a name a lower Layer answers ENAMETOOLONG for \
|
|
1570
|
+
must land in the upper"
|
|
1571
|
+
);
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
// Replacing the target needs an inode only where it holds whiteouts to clear
|
|
1575
|
+
// away, so with none left only a rename leaving an empty one alone lands.
|
|
1576
|
+
// @behavior FS-175
|
|
1577
|
+
#[test]
|
|
1578
|
+
fn a_rename_onto_an_empty_upper_directory_builds_no_replacement() {
|
|
1579
|
+
let upper = Arc::new(Tmpfs::new().with_nr_inodes(Some(5)));
|
|
1580
|
+
let lower: Tmpfs = [("base", "x")].into_iter().collect();
|
|
1581
|
+
let overlay = at(over(Arc::new(lower), upper));
|
|
1582
|
+
overlay.vfs_mkdir("from").expect("the source must be made");
|
|
1583
|
+
overlay.vfs_mkdir("to").expect("the target must be made");
|
|
1584
|
+
assert_eq!(
|
|
1585
|
+
overlay.rename("from", &overlay, "to"),
|
|
1586
|
+
Ok(()),
|
|
1587
|
+
"a rename through an Overlay of an upper directory onto an empty upper directory, with \
|
|
1588
|
+
no inode left in the upper, must land, as ovl_rename_upper clears only what holds \
|
|
1589
|
+
whiteouts"
|
|
1590
|
+
);
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
// overlayfs's `ovl_path_lower` is the topmost lower layer, and its stat of a
|
|
1594
|
+
// merged directory is the real one's, so a Layer's entries change the size.
|
|
1595
|
+
// @behavior FS-176
|
|
1596
|
+
#[test]
|
|
1597
|
+
fn a_merged_directory_reports_its_topmost_layer_under_the_lowers_identity() {
|
|
1598
|
+
let lower: Arc<Tmpfs> = Arc::new([("dir/a", "x"), ("dir/b", "x")].into_iter().collect());
|
|
1599
|
+
let upper = Arc::new(Tmpfs::new());
|
|
1600
|
+
let overlay = at(over(lower.clone(), upper));
|
|
1601
|
+
overlay
|
|
1602
|
+
.vfs_mkdir("dir/made")
|
|
1603
|
+
.expect("the directory must be made");
|
|
1604
|
+
let found = overlay.vfs_lstat("dir").expect("the directory must stand");
|
|
1605
|
+
let below = on(&lower)
|
|
1606
|
+
.vfs_lstat("dir")
|
|
1607
|
+
.expect("the directory must stand");
|
|
1608
|
+
assert_eq!(
|
|
1609
|
+
(found.ino, found.dev, found.nlink, found.size),
|
|
1610
|
+
(below.ino, below.dev, 1, 3 * 20),
|
|
1611
|
+
"a directory two Layers show, stated through an Overlay, must report the lower \
|
|
1612
|
+
Layer's identity, one name, and the size of the upper's directory holding one entry"
|
|
1613
|
+
);
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
// Bind reports the same identity for both names, so without the view keeping
|
|
1617
|
+
// them apart the VFS would answer the move as having nothing to do.
|
|
1618
|
+
// @behavior FS-170
|
|
1619
|
+
#[cfg(unix)]
|
|
1620
|
+
#[test]
|
|
1621
|
+
fn a_lower_layers_hard_link_moves_onto_the_other_as_two_entries() {
|
|
1622
|
+
let host = LinkTree::new("hard-link");
|
|
1623
|
+
std::fs::write(host.0.join("a"), "one").expect("the host file must be written");
|
|
1624
|
+
std::fs::hard_link(host.0.join("a"), host.0.join("b")).expect("the host link must be made");
|
|
1625
|
+
let overlay = at(over(Arc::new(host.bind()), Arc::new(Tmpfs::new())));
|
|
1626
|
+
write(&overlay, "a", b"two").expect("the file must copy up and be written");
|
|
1627
|
+
overlay
|
|
1628
|
+
.rename("a", &overlay, "b")
|
|
1629
|
+
.expect("the move must land");
|
|
1630
|
+
assert_eq!(
|
|
1631
|
+
(
|
|
1632
|
+
overlay.vfs_lstat("a").map(drop),
|
|
1633
|
+
read(&overlay, "b"),
|
|
1634
|
+
std::fs::read(host.0.join("b")).expect("the host file must be read"),
|
|
1635
|
+
),
|
|
1636
|
+
(
|
|
1637
|
+
Err(FilesystemError::NotFound),
|
|
1638
|
+
b"two".to_vec(),
|
|
1639
|
+
b"one".to_vec()
|
|
1640
|
+
),
|
|
1641
|
+
"a hard link in a lower Layer written and moved onto its other name through an \
|
|
1642
|
+
Overlay must leave only the new name, holding what was written, and the lower Layer \
|
|
1643
|
+
as it was"
|
|
1644
|
+
);
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
// overlayfs's super block is read-only with no upper, so `mnt_want_write`
|
|
1648
|
+
// answers before anything looks at what stands at the name.
|
|
1649
|
+
// @behavior FS-169
|
|
1650
|
+
#[test]
|
|
1651
|
+
fn an_overlay_with_no_upper_refuses_a_change_before_what_stands_answers() {
|
|
1652
|
+
use crate::vfs::{filename_renameat2, filename_rmdir, filename_unlinkat};
|
|
1653
|
+
let (_mount, root) = mounted_with_no_upper();
|
|
1654
|
+
let cred = Arc::default();
|
|
1655
|
+
let read_only = || Err(FilesystemError::ReadOnlyFilesystem);
|
|
1656
|
+
assert_eq!(
|
|
1657
|
+
(
|
|
1658
|
+
filename_renameat2(&cred, &root, "dir/file", &root, "dir/file"),
|
|
1659
|
+
filename_unlinkat(&cred, &root, "missing"),
|
|
1660
|
+
filename_rmdir(&cred, &root, "dir"),
|
|
1661
|
+
),
|
|
1662
|
+
(read_only(), read_only(), read_only()),
|
|
1663
|
+
"a move onto its own name, a removal of nothing, and a removal of a directory holding \
|
|
1664
|
+
an entry through an Overlay with no upper must each answer EROFS"
|
|
1665
|
+
);
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
/// Why `Overlay::new` refuses `lower` under `upper`, or `None`.
|
|
1669
|
+
fn refusal(
|
|
1670
|
+
lower: Vec<Arc<dyn FileSystemType>>,
|
|
1671
|
+
upper: Option<Arc<dyn FileSystemType>>,
|
|
1672
|
+
) -> Option<FilesystemError> {
|
|
1673
|
+
let instance = |fs| Filesystem::new(fs).expect("the instance must stand");
|
|
1674
|
+
Overlay::new(
|
|
1675
|
+
lower.into_iter().map(instance).collect(),
|
|
1676
|
+
upper.map(instance),
|
|
1677
|
+
)
|
|
1678
|
+
.err()
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
// gVisor takes only an upper that holds its markers, and only a
|
|
1682
|
+
// Tmpfs does here.
|
|
1683
|
+
// @behavior FS-123
|
|
1684
|
+
#[test]
|
|
1685
|
+
fn an_overlay_refuses_an_upper_that_is_no_tmpfs() {
|
|
1686
|
+
let host = std::env::temp_dir();
|
|
1687
|
+
let bind = super::super::Bind::new(&host).expect("the host directory must open");
|
|
1688
|
+
assert_eq!(
|
|
1689
|
+
refusal(vec![Arc::new(Tmpfs::new())], Some(Arc::new(bind))),
|
|
1690
|
+
Some(FilesystemError::InvalidInput),
|
|
1691
|
+
"Overlay::new with a Bind as its upper must be refused as invalid input"
|
|
1692
|
+
);
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
// Nothing else tells these apart: every other rule holds of each list, and
|
|
1696
|
+
// a view over them would show the upper's own workdir.
|
|
1697
|
+
// @behavior FS-148
|
|
1698
|
+
#[test]
|
|
1699
|
+
fn an_overlay_refuses_one_instance_as_two_layers() {
|
|
1700
|
+
let instance = || Filesystem::new(Arc::new(Tmpfs::new())).expect("the instance must stand");
|
|
1701
|
+
let (shared, doubled) = (instance(), instance());
|
|
1702
|
+
assert_eq!(
|
|
1703
|
+
(
|
|
1704
|
+
Overlay::new(vec![shared.clone()], Some(shared)).err(),
|
|
1705
|
+
Overlay::new(vec![doubled.clone(), doubled, instance()], None).err(),
|
|
1706
|
+
),
|
|
1707
|
+
(
|
|
1708
|
+
Some(FilesystemError::InvalidInput),
|
|
1709
|
+
Some(FilesystemError::InvalidInput),
|
|
1710
|
+
),
|
|
1711
|
+
"Overlay::new with one instance as its upper and a lower Layer, or as two lower \
|
|
1712
|
+
Layers, must be refused as invalid input"
|
|
1713
|
+
);
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
// @behavior FS-124
|
|
1717
|
+
#[test]
|
|
1718
|
+
fn an_overlay_without_an_upper_needs_two_lower_layers() {
|
|
1719
|
+
assert_eq!(
|
|
1720
|
+
refusal(vec![Arc::new(Tmpfs::new())], None),
|
|
1721
|
+
Some(FilesystemError::InvalidInput),
|
|
1722
|
+
"Overlay::new with one lower Layer and no upper must be refused as invalid input"
|
|
1723
|
+
);
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
// Two Overlays deep is what Linux allows, so only a third is refused.
|
|
1727
|
+
// @behavior FS-126
|
|
1728
|
+
#[test]
|
|
1729
|
+
fn an_overlay_stacks_at_most_two_deep() {
|
|
1730
|
+
let first: Arc<dyn FileSystemType> =
|
|
1731
|
+
Arc::new(lowers(Arc::new(Tmpfs::new()), Arc::new(Tmpfs::new())));
|
|
1732
|
+
let second: Arc<dyn FileSystemType> = Arc::new(over(first.clone(), Arc::new(Tmpfs::new())));
|
|
1733
|
+
assert_eq!(
|
|
1734
|
+
(
|
|
1735
|
+
refusal(vec![first], Some(Arc::new(Tmpfs::new()))),
|
|
1736
|
+
refusal(vec![second], Some(Arc::new(Tmpfs::new())))
|
|
1737
|
+
),
|
|
1738
|
+
(None, Some(FilesystemError::InvalidInput)),
|
|
1739
|
+
"Overlay::new over an Overlay must make a view, and over an Overlay over an Overlay \
|
|
1740
|
+
must be refused as invalid input"
|
|
1741
|
+
);
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
// @behavior FS-127
|
|
1745
|
+
#[test]
|
|
1746
|
+
fn an_overlay_takes_at_most_five_hundred_lower_layers() {
|
|
1747
|
+
let layers = |count: usize| -> Vec<Arc<dyn FileSystemType>> {
|
|
1748
|
+
(0..count)
|
|
1749
|
+
.map(|_| Arc::new(Tmpfs::new()) as Arc<dyn FileSystemType>)
|
|
1750
|
+
.collect()
|
|
1751
|
+
};
|
|
1752
|
+
assert_eq!(
|
|
1753
|
+
(refusal(layers(500), None), refusal(layers(501), None)),
|
|
1754
|
+
(None, Some(FilesystemError::InvalidInput)),
|
|
1755
|
+
"Overlay::new must take 500 lower Layers and refuse 501 as invalid input"
|
|
1756
|
+
);
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
// @behavior FS-125
|
|
1760
|
+
#[test]
|
|
1761
|
+
fn an_overlay_needs_a_lower_layer() {
|
|
1762
|
+
assert_eq!(
|
|
1763
|
+
refusal(Vec::new(), Some(Arc::new(Tmpfs::new()))),
|
|
1764
|
+
Some(FilesystemError::InvalidInput),
|
|
1765
|
+
"Overlay::new with an upper and no lower Layer must be refused as invalid input"
|
|
1766
|
+
);
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
#[test]
|
|
1770
|
+
fn an_overlay_lists_every_layer_without_what_it_hid() {
|
|
1771
|
+
let (overlay, _) = overlay_on(Arc::new(Tmpfs::new()));
|
|
1772
|
+
overwrite(&overlay, "dir/new", b"made");
|
|
1773
|
+
assert_eq!(names(&overlay, "dir"), ["file", "new"]);
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
#[test]
|
|
1777
|
+
fn a_lower_file_renamed_through_an_overlay_moves_into_the_upper() {
|
|
1778
|
+
let upper = Arc::new(Tmpfs::new());
|
|
1779
|
+
let (overlay, lower) = overlay_on(upper.clone());
|
|
1780
|
+
overlay
|
|
1781
|
+
.rename("dir/file", &overlay, "dir/moved")
|
|
1782
|
+
.expect("the file must move");
|
|
1783
|
+
assert_eq!(
|
|
1784
|
+
(
|
|
1785
|
+
read(&overlay, "dir/moved"),
|
|
1786
|
+
overlay.vfs_lstat("dir/file"),
|
|
1787
|
+
read(&on(&lower), "dir/file"),
|
|
1788
|
+
),
|
|
1789
|
+
(
|
|
1790
|
+
b"lower".to_vec(),
|
|
1791
|
+
Err(FilesystemError::NotFound),
|
|
1792
|
+
b"lower".to_vec()
|
|
1793
|
+
),
|
|
1794
|
+
);
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
/// Another call through the VFS at `overlay`, started once on a thread
|
|
1798
|
+
/// of its own and given a while to finish, recording whether it did
|
|
1799
|
+
/// before the call starting it went on.
|
|
1800
|
+
#[derive(Default)]
|
|
1801
|
+
struct Interleave {
|
|
1802
|
+
overlay: Mutex<Option<crate::vfs::Path>>,
|
|
1803
|
+
finished_inside: Mutex<Option<bool>>,
|
|
1804
|
+
other: Mutex<Option<thread::JoinHandle<()>>>,
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
impl Interleave {
|
|
1808
|
+
fn start(&self) {
|
|
1809
|
+
let Some(overlay) = self.overlay.lock().unwrap().take() else {
|
|
1810
|
+
return;
|
|
1811
|
+
};
|
|
1812
|
+
let finished = Arc::new(AtomicUsize::new(0));
|
|
1813
|
+
let marker = finished.clone();
|
|
1814
|
+
let handle = thread::spawn(move || {
|
|
1815
|
+
let how = crate::vfs::OpenHow {
|
|
1816
|
+
write: true,
|
|
1817
|
+
create: true,
|
|
1818
|
+
..crate::vfs::OpenHow::default()
|
|
1819
|
+
};
|
|
1820
|
+
let _ =
|
|
1821
|
+
crate::vfs::kern_path::filp_open(&Arc::default(), &overlay, "dir/new", &how);
|
|
1822
|
+
marker.store(1, Ordering::SeqCst);
|
|
1823
|
+
});
|
|
1824
|
+
let deadline = Instant::now() + Duration::from_millis(200);
|
|
1825
|
+
while finished.load(Ordering::SeqCst) == 0 && Instant::now() < deadline {
|
|
1826
|
+
thread::sleep(Duration::from_millis(5));
|
|
1827
|
+
}
|
|
1828
|
+
*self.finished_inside.lock().unwrap() = Some(finished.load(Ordering::SeqCst) == 1);
|
|
1829
|
+
*self.other.lock().unwrap() = Some(handle);
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
/// A Tmpfs whose directory beneath its root starts `Interleave` the first
|
|
1834
|
+
/// time one is listed, as a removal's emptiness check lists it.
|
|
1835
|
+
impl Hook for Interleave {
|
|
1836
|
+
fn open(
|
|
1837
|
+
&self,
|
|
1838
|
+
inner: &dyn InodeOperations,
|
|
1839
|
+
dentry: &Dentry,
|
|
1840
|
+
how: &OpenHow,
|
|
1841
|
+
) -> Result<Box<dyn FileOperations>, FilesystemError> {
|
|
1842
|
+
if dentry.kind() == Ok(FileType::Dir) && !dentry.is_root() {
|
|
1843
|
+
self.start();
|
|
1844
|
+
}
|
|
1845
|
+
inner.open(dentry, how)
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
// The other call starts inside this one, between its emptiness check
|
|
1850
|
+
// and its removal, so only the Overlay's lock keeps the file it
|
|
1851
|
+
// creates from being removed with the directory.
|
|
1852
|
+
// @behavior FS-037
|
|
1853
|
+
#[test]
|
|
1854
|
+
fn a_change_through_an_overlay_waits_for_the_one_in_progress() {
|
|
1855
|
+
let tmpfs = tmpfs_with("dir/.keep", "");
|
|
1856
|
+
let interleave = Arc::new(Interleave::default());
|
|
1857
|
+
on(&tmpfs)
|
|
1858
|
+
.vfs_unlink("dir/.keep")
|
|
1859
|
+
.expect("the seed must go");
|
|
1860
|
+
let mount = crate::mount::Mount::new(
|
|
1861
|
+
"/data",
|
|
1862
|
+
Filesystem::new(Arc::new(over(
|
|
1863
|
+
Arc::new(Wrap::new(tmpfs, interleave.clone())),
|
|
1864
|
+
Arc::new(Tmpfs::new()),
|
|
1865
|
+
)))
|
|
1866
|
+
.expect("the instance must stand"),
|
|
1867
|
+
crate::mount::Access::ReadWrite,
|
|
1868
|
+
)
|
|
1869
|
+
.expect("/data is a guest path");
|
|
1870
|
+
let root = crate::vfs::Path::root(mount.mnt());
|
|
1871
|
+
*interleave.overlay.lock().unwrap() = Some(root.clone());
|
|
1872
|
+
let removed = crate::vfs::filename_rmdir(&Arc::default(), &root, "dir").is_ok();
|
|
1873
|
+
let other = interleave
|
|
1874
|
+
.other
|
|
1875
|
+
.lock()
|
|
1876
|
+
.unwrap()
|
|
1877
|
+
.take()
|
|
1878
|
+
.expect("the other call must have started");
|
|
1879
|
+
crate::sched::testing::join("the other call", other);
|
|
1880
|
+
assert_eq!(
|
|
1881
|
+
(
|
|
1882
|
+
removed,
|
|
1883
|
+
*interleave.finished_inside.lock().unwrap(),
|
|
1884
|
+
crate::vfs::vfs_fstatat(&Arc::default(), &root, "dir/new", false).is_ok()
|
|
1885
|
+
),
|
|
1886
|
+
(true, Some(false), false),
|
|
1887
|
+
"a create through vfs::do_file_open into an Overlay directory vfs::filename_rmdir is removing must \
|
|
1888
|
+
wait for it, failing rather than landing in the directory being removed"
|
|
1889
|
+
);
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
// @behavior FS-038
|
|
1893
|
+
#[test]
|
|
1894
|
+
fn a_change_through_an_overlay_from_inside_one_answers_a_deadlock() {
|
|
1895
|
+
type Answers = (bool, Result<(), FilesystemError>);
|
|
1896
|
+
|
|
1897
|
+
struct Reenters {
|
|
1898
|
+
overlay: Mutex<Option<crate::vfs::Path>>,
|
|
1899
|
+
answer: Mutex<Option<Answers>>,
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
impl Hook for Reenters {
|
|
1903
|
+
fn getattr(
|
|
1904
|
+
&self,
|
|
1905
|
+
inner: &dyn InodeOperations,
|
|
1906
|
+
dentry: &Dentry,
|
|
1907
|
+
) -> Result<Kstat, FilesystemError> {
|
|
1908
|
+
let reentering = self.overlay.lock().unwrap().take();
|
|
1909
|
+
if let Some(overlay) = reentering {
|
|
1910
|
+
*self.answer.lock().unwrap() = Some((
|
|
1911
|
+
crate::vfs::vfs_fstatat(&Arc::default(), &overlay, &dentry.name(), false)
|
|
1912
|
+
.is_ok(),
|
|
1913
|
+
crate::vfs::filename_mkdirat(&Arc::default(), &overlay, "made"),
|
|
1914
|
+
));
|
|
1915
|
+
}
|
|
1916
|
+
inner.getattr(dentry)
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
let layer = Arc::new(Reenters {
|
|
1921
|
+
overlay: Mutex::new(None),
|
|
1922
|
+
answer: Mutex::new(None),
|
|
1923
|
+
});
|
|
1924
|
+
let tmpfs: Tmpfs = [("note", "x")].into_iter().collect();
|
|
1925
|
+
let mount = crate::mount::Mount::new(
|
|
1926
|
+
"/data",
|
|
1927
|
+
Filesystem::new(Arc::new(over(
|
|
1928
|
+
Arc::new(Wrap::new(Arc::new(tmpfs), layer.clone())),
|
|
1929
|
+
Arc::new(Tmpfs::new()),
|
|
1930
|
+
)))
|
|
1931
|
+
.expect("the instance must stand"),
|
|
1932
|
+
crate::mount::Access::ReadWrite,
|
|
1933
|
+
)
|
|
1934
|
+
.expect("/data is a guest path");
|
|
1935
|
+
let root = crate::vfs::Path::root(mount.mnt());
|
|
1936
|
+
*layer.overlay.lock().unwrap() = Some(root.clone());
|
|
1937
|
+
crate::vfs::vfs_fstatat(&Arc::default(), &root, "note", false)
|
|
1938
|
+
.expect("the outer call must answer");
|
|
1939
|
+
let answers = layer.answer.lock().unwrap().take();
|
|
1940
|
+
assert_eq!(
|
|
1941
|
+
answers,
|
|
1942
|
+
Some((true, Err(FilesystemError::Deadlock))),
|
|
1943
|
+
"a read through an Overlay made from inside one of its own reads on the same thread \
|
|
1944
|
+
must answer, and a change must answer Deadlock rather than wait forever"
|
|
1945
|
+
);
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
/// An upper whose `upper` is seeded with `files`, with room for
|
|
1949
|
+
/// `inodes` more than its root, `upper`, and `work` take, over a lower
|
|
1950
|
+
/// Tmpfs holding `dir/file` = "lower".
|
|
1951
|
+
fn full_over_lower(files: &[(&str, &str)], inodes: usize) -> (KernPath, Arc<Tmpfs>) {
|
|
1952
|
+
let seeded = files
|
|
1953
|
+
.iter()
|
|
1954
|
+
.map(|(path, contents)| (format!("upper/{path}"), *contents));
|
|
1955
|
+
let upper = Tmpfs::from_iter(seeded).with_nr_inodes(Some(inodes + 3));
|
|
1956
|
+
let upper = Arc::new(upper);
|
|
1957
|
+
(
|
|
1958
|
+
at(over(tmpfs_with("dir/file", "lower"), upper.clone())),
|
|
1959
|
+
upper,
|
|
1960
|
+
)
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
// The upper copy is all the room there is, so the whiteout cannot be
|
|
1964
|
+
// made; taking the copy away first would show the lower file again.
|
|
1965
|
+
// @behavior FS-040
|
|
1966
|
+
#[test]
|
|
1967
|
+
fn a_removal_that_cannot_leave_its_whiteout_removes_nothing() {
|
|
1968
|
+
let (overlay, _) = full_over_lower(&[("dir/file", "upper")], 2);
|
|
1969
|
+
assert_eq!(
|
|
1970
|
+
(overlay.vfs_unlink("dir/file"), read(&overlay, "dir/file")),
|
|
1971
|
+
(Err(FilesystemError::StorageFull), b"upper".to_vec()),
|
|
1972
|
+
"a file removed through vfs_unlink whose whiteout finds no room must stay as \
|
|
1973
|
+
it was"
|
|
1974
|
+
);
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
// The directory is built in the workdir before it replaces the
|
|
1978
|
+
// whiteout, so a workdir with no room leaves the whiteout standing.
|
|
1979
|
+
// @behavior FS-041
|
|
1980
|
+
#[test]
|
|
1981
|
+
fn a_directory_made_over_a_whiteout_without_room_is_not_made() {
|
|
1982
|
+
let lower = Arc::new(Tmpfs::new());
|
|
1983
|
+
on(&lower)
|
|
1984
|
+
.vfs_mkdir("gone")
|
|
1985
|
+
.expect("the directory must be made");
|
|
1986
|
+
let upper = Arc::new(Tmpfs::new().with_nr_inodes(Some(4)));
|
|
1987
|
+
let overlay = at(over(lower, upper));
|
|
1988
|
+
overlay
|
|
1989
|
+
.vfs_rmdir("gone")
|
|
1990
|
+
.expect("the directory must be removed");
|
|
1991
|
+
assert_eq!(
|
|
1992
|
+
(overlay.vfs_mkdir("gone"), overlay.vfs_lstat("gone")),
|
|
1993
|
+
(
|
|
1994
|
+
Err(FilesystemError::StorageFull),
|
|
1995
|
+
Err(FilesystemError::NotFound)
|
|
1996
|
+
),
|
|
1997
|
+
"a directory made through vfs_mkdir over a whiteout, with no room left, \
|
|
1998
|
+
must be refused as out of space and leave the name hidden"
|
|
1999
|
+
);
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
// The copy up takes the last room, so the whiteout over the source
|
|
2003
|
+
// cannot be made; moving first would leave the file in two places.
|
|
2004
|
+
// @behavior FS-042
|
|
2005
|
+
#[test]
|
|
2006
|
+
fn a_rename_that_cannot_leave_its_whiteout_moves_nothing() {
|
|
2007
|
+
let (overlay, _) = full_over_lower(&[], 2);
|
|
2008
|
+
assert_eq!(
|
|
2009
|
+
(
|
|
2010
|
+
overlay.rename("dir/file", &overlay, "dir/moved"),
|
|
2011
|
+
overlay.vfs_lstat("dir/moved"),
|
|
2012
|
+
read(&overlay, "dir/file")
|
|
2013
|
+
),
|
|
2014
|
+
(
|
|
2015
|
+
Err(FilesystemError::StorageFull),
|
|
2016
|
+
Err(FilesystemError::NotFound),
|
|
2017
|
+
b"lower".to_vec()
|
|
2018
|
+
),
|
|
2019
|
+
"a file renamed through Overlay::rename whose whiteout finds no room must stay where \
|
|
2020
|
+
it was"
|
|
2021
|
+
);
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
// @behavior FS-043
|
|
2025
|
+
#[test]
|
|
2026
|
+
fn a_directory_a_lower_layer_holds_does_not_move_through_an_overlay() {
|
|
2027
|
+
let (overlay, _) = overlay_on(Arc::new(Tmpfs::new()));
|
|
2028
|
+
assert_eq!(
|
|
2029
|
+
(
|
|
2030
|
+
overlay.rename("dir", &overlay, "moved"),
|
|
2031
|
+
overlay.vfs_lstat("moved")
|
|
2032
|
+
),
|
|
2033
|
+
(
|
|
2034
|
+
Err(FilesystemError::CrossesDevices),
|
|
2035
|
+
Err(FilesystemError::NotFound)
|
|
2036
|
+
),
|
|
2037
|
+
"a directory a lower Layer holds renamed through Overlay::rename must be refused \
|
|
2038
|
+
as crossing devices, as overlayfs refuses it"
|
|
2039
|
+
);
|
|
2040
|
+
}
|
|
2041
|
+
|
|
2042
|
+
// The directory shows only from the lower Layer, so only a check of
|
|
2043
|
+
// the whole view finds it in the way.
|
|
2044
|
+
// @behavior FS-044
|
|
2045
|
+
#[test]
|
|
2046
|
+
fn a_rename_onto_what_an_overlay_shows_is_checked_against_the_view() {
|
|
2047
|
+
let upper = Arc::new(Tmpfs::new());
|
|
2048
|
+
let (overlay, _) = overlay_on(upper.clone());
|
|
2049
|
+
overwrite(&overlay, "note", b"x");
|
|
2050
|
+
overlay
|
|
2051
|
+
.vfs_mkdir("empty")
|
|
2052
|
+
.expect("the directory must be made");
|
|
2053
|
+
assert_eq!(
|
|
2054
|
+
(
|
|
2055
|
+
overlay.rename("note", &overlay, "dir"),
|
|
2056
|
+
overlay.rename("empty", &overlay, "dir"),
|
|
2057
|
+
read(&overlay, "dir/file")
|
|
2058
|
+
),
|
|
2059
|
+
(
|
|
2060
|
+
Err(FilesystemError::IsADirectory),
|
|
2061
|
+
Err(FilesystemError::DirectoryNotEmpty),
|
|
2062
|
+
b"lower".to_vec()
|
|
2063
|
+
),
|
|
2064
|
+
"a rename through Overlay::rename onto a directory only a lower Layer shows must \
|
|
2065
|
+
be refused as rename(2) refuses it, leaving that directory as it was"
|
|
2066
|
+
);
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
// A directory moved over an empty one a lower Layer holds must not take
|
|
2070
|
+
// up what that Layer later shows there.
|
|
2071
|
+
// @behavior FS-045
|
|
2072
|
+
#[test]
|
|
2073
|
+
fn a_directory_moved_over_a_lower_one_is_marked_opaque() {
|
|
2074
|
+
let upper = Arc::new(Tmpfs::new());
|
|
2075
|
+
let lower: Arc<Tmpfs> = Arc::new([("dir/.keep", "")].into_iter().collect());
|
|
2076
|
+
on(&lower)
|
|
2077
|
+
.vfs_unlink("dir/.keep")
|
|
2078
|
+
.expect("the seed must go");
|
|
2079
|
+
let overlay = at(over(lower.clone(), upper.clone()));
|
|
2080
|
+
overlay
|
|
2081
|
+
.vfs_mkdir("made")
|
|
2082
|
+
.expect("the directory must be made");
|
|
2083
|
+
overlay
|
|
2084
|
+
.rename("made", &overlay, "dir")
|
|
2085
|
+
.expect("the directory must move over the empty one");
|
|
2086
|
+
overwrite(&on(&lower), "dir/later", b"lower");
|
|
2087
|
+
assert_eq!(
|
|
2088
|
+
names(&overlay, "dir"),
|
|
2089
|
+
Vec::<String>::new(),
|
|
2090
|
+
"a directory moved through Overlay::rename over one a lower Layer holds must be \
|
|
2091
|
+
marked opaque, so a file the lower Layer gains there later does not show"
|
|
2092
|
+
);
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
// A change is built in `work` before it lands, so `work` holds nothing
|
|
2096
|
+
// once each change is done, and only `upper` holds what the view shows.
|
|
2097
|
+
// @behavior FS-128
|
|
2098
|
+
#[test]
|
|
2099
|
+
fn an_overlay_keeps_its_changes_in_its_tmpfss_upper() {
|
|
2100
|
+
let upper = Arc::new(Tmpfs::new());
|
|
2101
|
+
let (overlay, _) = overlay_on(upper.clone());
|
|
2102
|
+
overwrite(&overlay, "dir/file", b"changed");
|
|
2103
|
+
overlay
|
|
2104
|
+
.vfs_unlink("dir/file")
|
|
2105
|
+
.expect("the file must be removed");
|
|
2106
|
+
assert_eq!(
|
|
2107
|
+
(names(&on(&upper), ""), names(&on(&upper), "work")),
|
|
2108
|
+
(
|
|
2109
|
+
vec!["upper".to_string(), "work".to_string()],
|
|
2110
|
+
Vec::<String>::new()
|
|
2111
|
+
),
|
|
2112
|
+
"an Overlay's upper Tmpfs, after a copy up and a removal through it, must hold only \
|
|
2113
|
+
upper and an empty work"
|
|
2114
|
+
);
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2117
|
+
// The upper's `dir` holds the whiteout the file's removal left, so only
|
|
2118
|
+
// clearing it before the removal lets what is exchanged out go.
|
|
2119
|
+
// @behavior FS-134
|
|
2120
|
+
#[test]
|
|
2121
|
+
fn a_directory_removed_through_an_overlay_leaves_nothing_in_its_workdir() {
|
|
2122
|
+
let upper = Arc::new(Tmpfs::new());
|
|
2123
|
+
let (overlay, _) = overlay_on(upper.clone());
|
|
2124
|
+
overlay
|
|
2125
|
+
.vfs_unlink("dir/file")
|
|
2126
|
+
.expect("the file must be removed");
|
|
2127
|
+
overlay
|
|
2128
|
+
.vfs_rmdir("dir")
|
|
2129
|
+
.expect("the emptied directory must be removed");
|
|
2130
|
+
assert_eq!(
|
|
2131
|
+
(names(&on(&upper), "work"), kind(&overlay, "dir")),
|
|
2132
|
+
(Vec::<String>::new(), Err(FilesystemError::NotFound)),
|
|
2133
|
+
"vfs_rmdir of a lower directory whose file vfs_unlink removed \
|
|
2134
|
+
must leave the upper Tmpfs's work empty and the directory gone"
|
|
2135
|
+
);
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
// The open writes nothing, so only the copy taking the lower file's
|
|
2139
|
+
// times, rather than the time it was made, keeps them.
|
|
2140
|
+
// @behavior FS-129
|
|
2141
|
+
#[test]
|
|
2142
|
+
fn a_copy_up_keeps_the_lower_files_times() {
|
|
2143
|
+
let lower = tmpfs_with("dir/file", "lower");
|
|
2144
|
+
let before = on(&lower)
|
|
2145
|
+
.vfs_lstat("dir/file")
|
|
2146
|
+
.expect("the lower file must stand");
|
|
2147
|
+
std::thread::sleep(Duration::from_millis(10));
|
|
2148
|
+
let overlay = at(over(lower, Arc::new(Tmpfs::new())));
|
|
2149
|
+
overlay
|
|
2150
|
+
.open(
|
|
2151
|
+
"dir/file",
|
|
2152
|
+
&OpenHow {
|
|
2153
|
+
write: true,
|
|
2154
|
+
..OpenHow::default()
|
|
2155
|
+
},
|
|
2156
|
+
)
|
|
2157
|
+
.expect("the open must copy the file up");
|
|
2158
|
+
assert_eq!(
|
|
2159
|
+
overlay.vfs_lstat("dir/file").map(|stat| stat.mtime),
|
|
2160
|
+
Ok(before.mtime),
|
|
2161
|
+
"a lower file opened to write through Overlay::open, writing nothing, must keep the \
|
|
2162
|
+
lower file's modified time once copied up"
|
|
2163
|
+
);
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
// The second copy up lands in a directory the first made, so only
|
|
2167
|
+
// restoring its times leaves what the view reports unchanged.
|
|
2168
|
+
// @behavior FS-130
|
|
2169
|
+
#[test]
|
|
2170
|
+
fn a_copy_up_leaves_its_directorys_times_as_they_were() {
|
|
2171
|
+
let lower: Arc<Tmpfs> = Arc::new([("dir/a", "a"), ("dir/b", "b")].into_iter().collect());
|
|
2172
|
+
let overlay = at(over(lower, Arc::new(Tmpfs::new())));
|
|
2173
|
+
overwrite(&overlay, "dir/a", b"changed");
|
|
2174
|
+
let before = overlay
|
|
2175
|
+
.vfs_lstat("dir")
|
|
2176
|
+
.expect("the directory must stand")
|
|
2177
|
+
.mtime;
|
|
2178
|
+
std::thread::sleep(Duration::from_millis(10));
|
|
2179
|
+
overlay
|
|
2180
|
+
.open(
|
|
2181
|
+
"dir/b",
|
|
2182
|
+
&OpenHow {
|
|
2183
|
+
write: true,
|
|
2184
|
+
..OpenHow::default()
|
|
2185
|
+
},
|
|
2186
|
+
)
|
|
2187
|
+
.expect("the open must copy the file up");
|
|
2188
|
+
assert_eq!(
|
|
2189
|
+
overlay.vfs_lstat("dir").map(|stat| stat.mtime),
|
|
2190
|
+
Ok(before),
|
|
2191
|
+
"a lower file copied up through Overlay::open into a directory the upper holds must \
|
|
2192
|
+
leave that directory's modified time as it was"
|
|
2193
|
+
);
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2196
|
+
/// An Overlay over an empty lower Layer under the Tmpfs `upper`, and
|
|
2197
|
+
/// that same Tmpfs mounted on its own, each reached through the VFS.
|
|
2198
|
+
fn beside_its_upper(upper: Tmpfs) -> (crate::vfs::Path, crate::vfs::Path) {
|
|
2199
|
+
let upper = Filesystem::new(Arc::new(upper)).expect("the instance must stand");
|
|
2200
|
+
let overlay = Overlay::new(
|
|
2201
|
+
vec![Filesystem::new(Arc::new(Tmpfs::new())).expect("the instance must stand")],
|
|
2202
|
+
Some(upper.clone()),
|
|
2203
|
+
)
|
|
2204
|
+
.expect("an upper over a lower Layer must make a view");
|
|
2205
|
+
let root = |filesystem| {
|
|
2206
|
+
let mount =
|
|
2207
|
+
crate::mount::Mount::new("/data", filesystem, crate::mount::Access::ReadWrite)
|
|
2208
|
+
.expect("/data is a guest path");
|
|
2209
|
+
crate::vfs::Path::root(mount.mnt())
|
|
2210
|
+
};
|
|
2211
|
+
(
|
|
2212
|
+
root(Filesystem::new(Arc::new(overlay)).expect("the instance must stand")),
|
|
2213
|
+
root(upper),
|
|
2214
|
+
)
|
|
2215
|
+
}
|
|
2216
|
+
|
|
2217
|
+
// The move holds the directory it leaves and the one above it, and the
|
|
2218
|
+
// removal holds the same two, so only taking the one above first in
|
|
2219
|
+
// both keeps them from waiting on each other; unordered, they wait
|
|
2220
|
+
// within a few moves.
|
|
2221
|
+
// @behavior FS-131
|
|
2222
|
+
#[test]
|
|
2223
|
+
fn an_overlay_and_a_mount_of_its_upper_never_wait_on_each_other() {
|
|
2224
|
+
let (overlay, upper) = beside_its_upper(
|
|
2225
|
+
[("upper/a/b/c", "c"), ("upper/a/b/keep", "")]
|
|
2226
|
+
.into_iter()
|
|
2227
|
+
.collect(),
|
|
2228
|
+
);
|
|
2229
|
+
let moved = Arc::new(AtomicBool::new(false));
|
|
2230
|
+
let removing = {
|
|
2231
|
+
let moved = moved.clone();
|
|
2232
|
+
thread::spawn(move || {
|
|
2233
|
+
while !moved.load(Ordering::SeqCst) {
|
|
2234
|
+
let _ = crate::vfs::filename_rmdir(&Arc::default(), &upper, "upper/a/b");
|
|
2235
|
+
}
|
|
2236
|
+
})
|
|
2237
|
+
};
|
|
2238
|
+
let (done, finished) = std::sync::mpsc::channel();
|
|
2239
|
+
thread::spawn(move || {
|
|
2240
|
+
for _ in 0..500 {
|
|
2241
|
+
crate::vfs::filename_renameat2(&Arc::default(), &overlay, "a/b/c", &overlay, "a/c")
|
|
2242
|
+
.expect("the file must move up");
|
|
2243
|
+
crate::vfs::filename_renameat2(&Arc::default(), &overlay, "a/c", &overlay, "a/b/c")
|
|
2244
|
+
.expect("the file must move back");
|
|
2245
|
+
}
|
|
2246
|
+
let _ = done.send(());
|
|
2247
|
+
});
|
|
2248
|
+
let returned = finished
|
|
2249
|
+
.recv_timeout(crate::sched::testing::MAX_WAIT)
|
|
2250
|
+
.is_ok();
|
|
2251
|
+
moved.store(true, Ordering::SeqCst);
|
|
2252
|
+
if returned {
|
|
2253
|
+
crate::sched::testing::join("the removal", removing);
|
|
2254
|
+
}
|
|
2255
|
+
assert!(
|
|
2256
|
+
returned,
|
|
2257
|
+
"vfs::filename_renameat2 of a file up one directory through an Overlay, while vfs::filename_rmdir \
|
|
2258
|
+
removes that directory through a Mount of its upper Tmpfs, must return"
|
|
2259
|
+
);
|
|
2260
|
+
}
|
|
2261
|
+
|
|
2262
|
+
// The make holds only the directory it makes in, so only the removal
|
|
2263
|
+
// holding that directory too, and removing it only once empty, keeps
|
|
2264
|
+
// what the make made from going with it. The two land in the same
|
|
2265
|
+
// instant only a few times in this many tries.
|
|
2266
|
+
// @behavior FS-132
|
|
2267
|
+
#[test]
|
|
2268
|
+
fn a_removal_through_an_overlay_waits_for_a_change_in_that_directory() {
|
|
2269
|
+
let (overlay, upper) = beside_its_upper([("upper/a/.keep", "")].into_iter().collect());
|
|
2270
|
+
let stop = Arc::new(AtomicBool::new(false));
|
|
2271
|
+
let churning = {
|
|
2272
|
+
let stop = stop.clone();
|
|
2273
|
+
thread::spawn(move || {
|
|
2274
|
+
while !stop.load(Ordering::SeqCst) {
|
|
2275
|
+
let _ = crate::vfs::filename_mkdirat(&Arc::default(), &overlay, "a/b");
|
|
2276
|
+
let _ = crate::vfs::filename_rmdir(&Arc::default(), &overlay, "a/b");
|
|
2277
|
+
}
|
|
2278
|
+
})
|
|
2279
|
+
};
|
|
2280
|
+
let mut lost = 0;
|
|
2281
|
+
for _ in 0..100_000 {
|
|
2282
|
+
if crate::vfs::filename_mkdirat(&Arc::default(), &upper, "upper/a/b/made").is_ok() {
|
|
2283
|
+
match crate::vfs::vfs_fstatat(&Arc::default(), &upper, "upper/a/b/made", false) {
|
|
2284
|
+
Ok(_) => {
|
|
2285
|
+
let _ =
|
|
2286
|
+
crate::vfs::filename_rmdir(&Arc::default(), &upper, "upper/a/b/made");
|
|
2287
|
+
}
|
|
2288
|
+
Err(_) => lost += 1,
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
stop.store(true, Ordering::SeqCst);
|
|
2293
|
+
churning.join().expect("the Overlay's calls must not panic");
|
|
2294
|
+
assert_eq!(
|
|
2295
|
+
lost, 0,
|
|
2296
|
+
"vfs::filename_mkdirat in a directory through a Mount of an Overlay's upper Tmpfs, while \
|
|
2297
|
+
vfs::filename_rmdir removes that directory through the Overlay, must leave every \
|
|
2298
|
+
directory it made standing"
|
|
2299
|
+
);
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
// The upper has room for the copy's entry but for less than
|
|
2303
|
+
// the lower file's bytes, so the copy fails part way.
|
|
2304
|
+
// @behavior FS-046
|
|
2305
|
+
#[test]
|
|
2306
|
+
fn a_copy_up_that_fails_part_way_leaves_nothing_behind() {
|
|
2307
|
+
let upper = Arc::new(Tmpfs::new().with_size(Some(2)).with_nr_inodes(Some(8)));
|
|
2308
|
+
let (overlay, _) = overlay_on(upper.clone());
|
|
2309
|
+
assert_eq!(
|
|
2310
|
+
(
|
|
2311
|
+
write_over(&overlay, "dir/file", b"x"),
|
|
2312
|
+
on(&upper).vfs_lstat("upper/dir/file"),
|
|
2313
|
+
read(&overlay, "dir/file")
|
|
2314
|
+
),
|
|
2315
|
+
(
|
|
2316
|
+
Err(FilesystemError::StorageFull),
|
|
2317
|
+
Err(FilesystemError::NotFound),
|
|
2318
|
+
b"lower".to_vec()
|
|
2319
|
+
),
|
|
2320
|
+
"a write through Overlay::open whose copy up runs out of room must leave no partial \
|
|
2321
|
+
copy hiding the lower file"
|
|
2322
|
+
);
|
|
2323
|
+
}
|
|
2324
|
+
|
|
2325
|
+
/// A Tmpfs answering a stat of `dir` at any depth with an I/O failure,
|
|
2326
|
+
/// though it opens `dir` as any other directory.
|
|
2327
|
+
struct StatsNoDir;
|
|
2328
|
+
|
|
2329
|
+
impl Hook for StatsNoDir {
|
|
2330
|
+
fn getattr(
|
|
2331
|
+
&self,
|
|
2332
|
+
inner: &dyn InodeOperations,
|
|
2333
|
+
dentry: &Dentry,
|
|
2334
|
+
) -> Result<Kstat, FilesystemError> {
|
|
2335
|
+
if dentry.name() == "dir" {
|
|
2336
|
+
return Err(FilesystemError::Other("dir has no stat".to_string()));
|
|
2337
|
+
}
|
|
2338
|
+
inner.getattr(dentry)
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
/// A Tmpfs whose `file` is gone after its first lookup, as a host
|
|
2343
|
+
/// directory another process changes mid-call.
|
|
2344
|
+
#[derive(Default)]
|
|
2345
|
+
struct Vanishes {
|
|
2346
|
+
seen: AtomicBool,
|
|
2347
|
+
}
|
|
2348
|
+
|
|
2349
|
+
impl Hook for Vanishes {
|
|
2350
|
+
fn getattr(
|
|
2351
|
+
&self,
|
|
2352
|
+
inner: &dyn InodeOperations,
|
|
2353
|
+
dentry: &Dentry,
|
|
2354
|
+
) -> Result<Kstat, FilesystemError> {
|
|
2355
|
+
if dentry.name() == "file" && self.seen.swap(true, Ordering::SeqCst) {
|
|
2356
|
+
return Err(FilesystemError::NotFound);
|
|
2357
|
+
}
|
|
2358
|
+
inner.getattr(dentry)
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2362
|
+
/// A Tmpfs storing names in lower case and reaching each by any
|
|
2363
|
+
/// case, as a host that folds case does, answering each call by the
|
|
2364
|
+
/// folded path from its root.
|
|
2365
|
+
#[derive(Clone)]
|
|
2366
|
+
struct FoldsCase {
|
|
2367
|
+
root: KernPath,
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
impl FoldsCase {
|
|
2371
|
+
fn new(tmpfs: Tmpfs) -> Self {
|
|
2372
|
+
Self { root: at(tmpfs) }
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
/// The path `dentry` stands at beneath the root, folded.
|
|
2377
|
+
fn folded_path_of(dentry: &Dentry) -> String {
|
|
2378
|
+
dentry.path_raw()[1..].to_lowercase()
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
/// A directory of a `FoldsCase`, open.
|
|
2382
|
+
struct FoldedDir(KernPath);
|
|
2383
|
+
|
|
2384
|
+
impl FileOperations for FoldedDir {
|
|
2385
|
+
fn iterate_shared(
|
|
2386
|
+
&mut self,
|
|
2387
|
+
dentry: &Dentry,
|
|
2388
|
+
ctx: &mut DirContext<'_>,
|
|
2389
|
+
) -> Result<(), FilesystemError> {
|
|
2390
|
+
let entries = match folded_path_of(dentry).as_str() {
|
|
2391
|
+
"" => self.0.iterate_dir(),
|
|
2392
|
+
at => self.0.open_dir(at)?.iterate_dir(),
|
|
2393
|
+
}?;
|
|
2394
|
+
crate::filesystem::Rdir::default().iterate(dentry, ctx, || Ok(entries))
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
|
|
2398
|
+
impl FileSystemType for FoldsCase {
|
|
2399
|
+
fn fill_super(self: Arc<Self>, sb: &mut Filesystem) -> Result<(), FilesystemError> {
|
|
2400
|
+
sb.d_make_root(Inode::new(&Kstat::new(FileType::Dir, 0), self));
|
|
2401
|
+
Ok(())
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
|
|
2405
|
+
impl InodeOperations for FoldsCase {
|
|
2406
|
+
fn getattr(&self, dentry: &Dentry) -> Result<Kstat, FilesystemError> {
|
|
2407
|
+
self.root.vfs_lstat(&folded_path_of(dentry))
|
|
2408
|
+
}
|
|
2409
|
+
|
|
2410
|
+
fn lookup(&self, dentry: &Dentry) -> Result<Lookup, FilesystemError> {
|
|
2411
|
+
let name = dentry.name();
|
|
2412
|
+
let lower = name.to_lowercase();
|
|
2413
|
+
match self.root.vfs_lstat(&folded_path_of(dentry)) {
|
|
2414
|
+
Ok(found) if lower != name => Ok(Lookup::Spelled {
|
|
2415
|
+
name: lower,
|
|
2416
|
+
inode: Inode::new(&found, Arc::new(self.clone())),
|
|
2417
|
+
}),
|
|
2418
|
+
Ok(found) => Ok(Lookup::Found(Inode::new(&found, Arc::new(self.clone())))),
|
|
2419
|
+
Err(FilesystemError::NotFound) => Ok(Lookup::Absent),
|
|
2420
|
+
Err(err) => Err(err),
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2424
|
+
fn open(
|
|
2425
|
+
&self,
|
|
2426
|
+
dentry: &Dentry,
|
|
2427
|
+
how: &OpenHow,
|
|
2428
|
+
) -> Result<Box<dyn FileOperations>, FilesystemError> {
|
|
2429
|
+
if dentry.kind() == Ok(FileType::Dir) {
|
|
2430
|
+
return Ok(Box::new(FoldedDir(self.root.clone())));
|
|
2431
|
+
}
|
|
2432
|
+
self.root.open(&folded_path_of(dentry), how)
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
fn atomic_open(&self, dentry: &Dentry, how: &OpenHow) -> Result<Finish, FilesystemError> {
|
|
2436
|
+
if dentry.in_lookup() {
|
|
2437
|
+
let found = self.lookup(dentry)?;
|
|
2438
|
+
if !how.create || !matches!(found, Lookup::Absent) {
|
|
2439
|
+
return Ok(Finish::Lookup(found));
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
if !how.create {
|
|
2443
|
+
return Ok(Finish::NoOpen { created: false });
|
|
2444
|
+
}
|
|
2445
|
+
let file = self.root.create(&folded_path_of(dentry), how)?;
|
|
2446
|
+
Ok(Finish::Open {
|
|
2447
|
+
file,
|
|
2448
|
+
created: true,
|
|
2449
|
+
})
|
|
2450
|
+
}
|
|
2451
|
+
|
|
2452
|
+
fn mkdir(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
|
|
2453
|
+
self.root.vfs_mkdir(&folded_path_of(dentry))
|
|
2454
|
+
}
|
|
2455
|
+
|
|
2456
|
+
fn rmdir(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
|
|
2457
|
+
self.root.vfs_rmdir(&folded_path_of(dentry))
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2460
|
+
fn unlink(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
|
|
2461
|
+
self.root.vfs_unlink(&folded_path_of(dentry))
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2464
|
+
fn rename(
|
|
2465
|
+
&self,
|
|
2466
|
+
from: &Dentry,
|
|
2467
|
+
_to_dir: &dyn InodeOperations,
|
|
2468
|
+
to: &Dentry,
|
|
2469
|
+
_flag: Option<RenameFlag>,
|
|
2470
|
+
) -> Result<(), FilesystemError> {
|
|
2471
|
+
self.root
|
|
2472
|
+
.rename(&folded_path_of(from), &self.root, &folded_path_of(to))
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
|
|
2476
|
+
/// What `path` names through `fs`, or why it names nothing.
|
|
2477
|
+
fn kind(fs: &KernPath, path: &str) -> Result<FileType, FilesystemError> {
|
|
2478
|
+
fs.vfs_lstat(path).map(|metadata| metadata.file_type)
|
|
2479
|
+
}
|
|
2480
|
+
|
|
2481
|
+
// The inner directory is opened too, so only directories held as the
|
|
2482
|
+
// Layers' directories themselves, not reached again by their path,
|
|
2483
|
+
// take it along.
|
|
2484
|
+
// @behavior FS-110
|
|
2485
|
+
#[test]
|
|
2486
|
+
fn a_directory_opened_from_an_overlay_follows_it_when_it_moves() {
|
|
2487
|
+
let upper = Arc::new(Tmpfs::new());
|
|
2488
|
+
let overlay = at(over(Arc::new(Tmpfs::new()), upper.clone()));
|
|
2489
|
+
overlay.vfs_mkdir("dir").expect("dir must be made");
|
|
2490
|
+
overlay.vfs_mkdir("dir/inner").expect("inner must be made");
|
|
2491
|
+
let dir = overlay.open_dir("dir").expect("dir must open");
|
|
2492
|
+
let inner = overlay.open_dir("dir/inner").expect("inner must open");
|
|
2493
|
+
overlay
|
|
2494
|
+
.rename("dir", &overlay, "moved")
|
|
2495
|
+
.expect("the directory must move");
|
|
2496
|
+
overwrite(&dir, "a", b"x");
|
|
2497
|
+
overwrite(&inner, "b", b"x");
|
|
2498
|
+
assert_eq!(
|
|
2499
|
+
(
|
|
2500
|
+
kind(&overlay, "moved/a"),
|
|
2501
|
+
kind(&overlay, "moved/inner/b"),
|
|
2502
|
+
kind(&overlay, "dir")
|
|
2503
|
+
),
|
|
2504
|
+
(
|
|
2505
|
+
Ok(FileType::RegFile),
|
|
2506
|
+
Ok(FileType::RegFile),
|
|
2507
|
+
Err(FilesystemError::NotFound)
|
|
2508
|
+
),
|
|
2509
|
+
"files made through directories Overlay::open_dir opened before Overlay::rename moved \
|
|
2510
|
+
the outer one must stand beneath its new path"
|
|
2511
|
+
);
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2514
|
+
// @behavior FS-111
|
|
2515
|
+
#[test]
|
|
2516
|
+
fn an_overlay_directory_removed_while_opened_makes_nothing_more() {
|
|
2517
|
+
let overlay = at(over(Arc::new(Tmpfs::new()), Arc::new(Tmpfs::new())));
|
|
2518
|
+
overlay.vfs_mkdir("dir").expect("dir must be made");
|
|
2519
|
+
let dir = overlay.open_dir("dir").expect("dir must open");
|
|
2520
|
+
overlay.vfs_rmdir("dir").expect("dir must remove");
|
|
2521
|
+
overlay.vfs_mkdir("dir").expect("another dir must be made");
|
|
2522
|
+
assert_eq!(
|
|
2523
|
+
(
|
|
2524
|
+
write(&dir, "made", b"x"),
|
|
2525
|
+
dir.iterate_dir().map(|entries| entries.len()),
|
|
2526
|
+
names(&overlay, "dir").len()
|
|
2527
|
+
),
|
|
2528
|
+
(Err(FilesystemError::NotFound), Ok(0), 0),
|
|
2529
|
+
"a directory Overlay::open_dir opened, once vfs_rmdir removed it and another \
|
|
2530
|
+
was made in its place, must make nothing, list nothing, and leave the new one empty"
|
|
2531
|
+
);
|
|
2532
|
+
}
|
|
2533
|
+
|
|
2534
|
+
// A change made to a Layer outside its Overlay is the host's, so the
|
|
2535
|
+
// opened directory keeps the Layer's directory it holds, as a
|
|
2536
|
+
// descriptor keeps its directory, rather than reach the one made in
|
|
2537
|
+
// its place.
|
|
2538
|
+
// @behavior FS-112
|
|
2539
|
+
#[test]
|
|
2540
|
+
fn an_overlay_directory_keeps_its_layers_directory_through_a_change_outside_it() {
|
|
2541
|
+
let upper = tmpfs_with("upper/dir/file", "x");
|
|
2542
|
+
let overlay = at(over(Arc::new(Tmpfs::new()), upper.clone()));
|
|
2543
|
+
let dir = overlay.open_dir("dir").expect("dir must open");
|
|
2544
|
+
let outside = on(&upper);
|
|
2545
|
+
outside
|
|
2546
|
+
.rename("upper/dir", &outside, "upper/moved")
|
|
2547
|
+
.expect("the Layer must move it");
|
|
2548
|
+
outside
|
|
2549
|
+
.vfs_mkdir("upper/dir")
|
|
2550
|
+
.expect("the Layer must make another");
|
|
2551
|
+
overwrite(&outside, "upper/dir/file", b"other");
|
|
2552
|
+
assert_eq!(
|
|
2553
|
+
read(&dir, "file"),
|
|
2554
|
+
b"x".to_vec(),
|
|
2555
|
+
"a read through a directory Overlay::open_dir opened, once its Layer moved it away \
|
|
2556
|
+
and made another in its place outside the Overlay, must read the directory it \
|
|
2557
|
+
opened"
|
|
2558
|
+
);
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2561
|
+
/// A lower Layer running what it holds right after its first open, while a
|
|
2562
|
+
/// file of it is being opened through the view.
|
|
2563
|
+
#[derive(Default)]
|
|
2564
|
+
struct RunsAfterOpen(Mutex<Option<Box<dyn FnOnce() + Send>>>);
|
|
2565
|
+
|
|
2566
|
+
impl Hook for RunsAfterOpen {
|
|
2567
|
+
fn open(
|
|
2568
|
+
&self,
|
|
2569
|
+
inner: &dyn InodeOperations,
|
|
2570
|
+
dentry: &Dentry,
|
|
2571
|
+
how: &OpenHow,
|
|
2572
|
+
) -> Result<Box<dyn FileOperations>, FilesystemError> {
|
|
2573
|
+
let opened = inner.open(dentry, how);
|
|
2574
|
+
let run = self.0.lock().expect("never poisoned").take();
|
|
2575
|
+
if let Some(run) = run {
|
|
2576
|
+
crate::sched::testing::join("the writer", thread::spawn(run));
|
|
2577
|
+
}
|
|
2578
|
+
opened
|
|
2579
|
+
}
|
|
2580
|
+
}
|
|
2581
|
+
|
|
2582
|
+
// The copy up lands between the lower open and whatever the file does to
|
|
2583
|
+
// learn of one, so only a file asking where the data is at each read sees
|
|
2584
|
+
// it. A count taken after the open would say no reader was there to tell.
|
|
2585
|
+
// @behavior FS-157
|
|
2586
|
+
#[test]
|
|
2587
|
+
fn a_file_opened_as_a_copy_up_lands_reads_the_copy() {
|
|
2588
|
+
let hook = Arc::new(RunsAfterOpen::default());
|
|
2589
|
+
let lower = Wrap::new(tmpfs_with("note", "lower"), hook.clone());
|
|
2590
|
+
let overlay = at(over(Arc::new(lower), Arc::new(Tmpfs::new())));
|
|
2591
|
+
*hook.0.lock().expect("never poisoned") = Some({
|
|
2592
|
+
let writer = overlay.clone();
|
|
2593
|
+
Box::new(move || overwrite(&writer, "note", b"upper"))
|
|
2594
|
+
});
|
|
2595
|
+
assert_eq!(
|
|
2596
|
+
read(&overlay, "note"),
|
|
2597
|
+
b"upper".to_vec(),
|
|
2598
|
+
"a read through a file of a lower Layer opened while a write copied it up must read \
|
|
2599
|
+
the copy"
|
|
2600
|
+
);
|
|
2601
|
+
}
|
|
2602
|
+
|
|
2603
|
+
// A descriptor held across the removal still names the lower file, while
|
|
2604
|
+
// what stands at its name now is another entry, so only a copy up refusing
|
|
2605
|
+
// a name taken keeps the lower Layer from being asked.
|
|
2606
|
+
// @behavior FS-158
|
|
2607
|
+
#[test]
|
|
2608
|
+
fn a_time_set_on_a_file_whose_name_was_taken_leaves_its_lower_layer() {
|
|
2609
|
+
use crate::mount::{Access, Mount};
|
|
2610
|
+
use crate::vfs::{self, OpenHow, Path};
|
|
2611
|
+
let asked = Arc::new(AtomicUsize::new(0));
|
|
2612
|
+
let overlay = Overlay::new(
|
|
2613
|
+
vec![Filesystem::new(Arc::new(KeepsTimes(asked.clone())))
|
|
2614
|
+
.expect("the instance must stand")],
|
|
2615
|
+
Some(Filesystem::new(Arc::new(Tmpfs::new())).expect("the instance must stand")),
|
|
2616
|
+
)
|
|
2617
|
+
.expect("the Overlay must stack");
|
|
2618
|
+
let mount = Mount::new(
|
|
2619
|
+
"/data",
|
|
2620
|
+
Filesystem::new(Arc::new(overlay)).expect("the instance must stand"),
|
|
2621
|
+
Access::ReadWrite,
|
|
2622
|
+
)
|
|
2623
|
+
.expect("/data is a guest path");
|
|
2624
|
+
let root = Path::root(mount.mnt());
|
|
2625
|
+
let cred = Arc::default();
|
|
2626
|
+
let (read, create) = (
|
|
2627
|
+
OpenHow {
|
|
2628
|
+
read: true,
|
|
2629
|
+
..OpenHow::default()
|
|
2630
|
+
},
|
|
2631
|
+
OpenHow {
|
|
2632
|
+
write: true,
|
|
2633
|
+
create: true,
|
|
2634
|
+
..OpenHow::default()
|
|
2635
|
+
},
|
|
2636
|
+
);
|
|
2637
|
+
let held = vfs::kern_path::filp_open(&cred, &root, "file", &read)
|
|
2638
|
+
.unwrap_or_else(|_| panic!("the file must open"));
|
|
2639
|
+
vfs::filename_unlinkat(&cred, &root, "file").expect("the file must be removed");
|
|
2640
|
+
drop(vfs::kern_path::filp_open(&cred, &root, "file", &create));
|
|
2641
|
+
let answer = vfs::do_utimes_fd(
|
|
2642
|
+
&cred,
|
|
2643
|
+
held.path(),
|
|
2644
|
+
&Iattr::times(None, Some(crate::vfs::SetTime::Now)),
|
|
2645
|
+
);
|
|
2646
|
+
assert_eq!(
|
|
2647
|
+
(answer, asked.load(Ordering::SeqCst)),
|
|
2648
|
+
(Err(FilesystemError::AlreadyExists), 0),
|
|
2649
|
+
"vfs::do_utimes_fd on a held lower file whose name another entry took must answer \
|
|
2650
|
+
EEXIST and leave the lower Layer unasked"
|
|
2651
|
+
);
|
|
2652
|
+
}
|
|
2653
|
+
|
|
2654
|
+
// The held file's directory was removed and made again, and the upper
|
|
2655
|
+
// directory it knows of is the removed one, so nothing lands in the new one.
|
|
2656
|
+
// @behavior FS-179
|
|
2657
|
+
#[test]
|
|
2658
|
+
fn a_held_file_of_a_directory_made_again_stays_out_of_it() {
|
|
2659
|
+
use crate::mount::{Access, Mount};
|
|
2660
|
+
use crate::vfs::{self, OpenHow, Path};
|
|
2661
|
+
let lower: Tmpfs = [("d/f", "removed")].into_iter().collect();
|
|
2662
|
+
let overlay = Overlay::new(
|
|
2663
|
+
vec![Filesystem::new(Arc::new(lower)).expect("the instance must stand")],
|
|
2664
|
+
Some(Filesystem::new(Arc::new(Tmpfs::new())).expect("the instance must stand")),
|
|
2665
|
+
)
|
|
2666
|
+
.expect("the Overlay must stack");
|
|
2667
|
+
let mount = Mount::new(
|
|
2668
|
+
"/data",
|
|
2669
|
+
Filesystem::new(Arc::new(overlay)).expect("the instance must stand"),
|
|
2670
|
+
Access::ReadWrite,
|
|
2671
|
+
)
|
|
2672
|
+
.expect("/data is a guest path");
|
|
2673
|
+
let root = Path::root(mount.mnt());
|
|
2674
|
+
let cred = Arc::default();
|
|
2675
|
+
let read = OpenHow {
|
|
2676
|
+
read: true,
|
|
2677
|
+
..OpenHow::default()
|
|
2678
|
+
};
|
|
2679
|
+
let held = vfs::kern_path::filp_open(&cred, &root, "d/f", &read)
|
|
2680
|
+
.unwrap_or_else(|_| panic!("the file must open"));
|
|
2681
|
+
vfs::filename_unlinkat(&cred, &root, "d/f").expect("the file must be removed");
|
|
2682
|
+
vfs::filename_rmdir(&cred, &root, "d").expect("the directory must be removed");
|
|
2683
|
+
vfs::filename_mkdirat(&cred, &root, "d").expect("the directory must be made again");
|
|
2684
|
+
let answer = vfs::do_utimes_fd(
|
|
2685
|
+
&cred,
|
|
2686
|
+
held.path(),
|
|
2687
|
+
&Iattr::times(None, Some(crate::vfs::SetTime::Now)),
|
|
2688
|
+
);
|
|
2689
|
+
assert_eq!(
|
|
2690
|
+
(
|
|
2691
|
+
answer,
|
|
2692
|
+
vfs::vfs_fstatat(&cred, &root, "d/f", false).map(|_| ())
|
|
2693
|
+
),
|
|
2694
|
+
(
|
|
2695
|
+
Err(FilesystemError::NotFound),
|
|
2696
|
+
Err(FilesystemError::NotFound)
|
|
2697
|
+
),
|
|
2698
|
+
"vfs::do_utimes_fd on a held lower file whose directory was removed and made again \
|
|
2699
|
+
must answer ENOENT and leave the file out of the view"
|
|
2700
|
+
);
|
|
2701
|
+
}
|
|
2702
|
+
|
|
2703
|
+
/// Slows the first ask about the directory `e` once armed, so a second copy
|
|
2704
|
+
/// up of it finds the first under way.
|
|
2705
|
+
struct SlowsDirectory(AtomicBool);
|
|
2706
|
+
|
|
2707
|
+
impl Hook for SlowsDirectory {
|
|
2708
|
+
fn getattr(
|
|
2709
|
+
&self,
|
|
2710
|
+
inner: &dyn InodeOperations,
|
|
2711
|
+
dentry: &Dentry,
|
|
2712
|
+
) -> Result<Kstat, FilesystemError> {
|
|
2713
|
+
if dentry.name() == "e" && self.0.swap(false, Ordering::SeqCst) {
|
|
2714
|
+
thread::sleep(Duration::from_millis(100));
|
|
2715
|
+
}
|
|
2716
|
+
inner.getattr(dentry)
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
|
|
2720
|
+
// The second copy up decided the directory was not up yet, and waited for the
|
|
2721
|
+
// first. Without asking again under the lock, it would find the first's copy
|
|
2722
|
+
// standing at the name and refuse.
|
|
2723
|
+
// @behavior FS-181
|
|
2724
|
+
#[test]
|
|
2725
|
+
fn a_directory_two_files_copy_up_at_once_takes_both() {
|
|
2726
|
+
use crate::mount::{Access, Mount};
|
|
2727
|
+
use crate::vfs::{self, OpenHow, Path};
|
|
2728
|
+
let lower: Tmpfs = [("d/e/f", "1"), ("d/e/g", "2")].into_iter().collect();
|
|
2729
|
+
let hook = Arc::new(SlowsDirectory(AtomicBool::new(false)));
|
|
2730
|
+
let overlay = Overlay::new(
|
|
2731
|
+
vec![
|
|
2732
|
+
Filesystem::new(Arc::new(Wrap::new(Arc::new(lower), hook.clone())))
|
|
2733
|
+
.expect("the instance must stand"),
|
|
2734
|
+
],
|
|
2735
|
+
Some(Filesystem::new(Arc::new(Tmpfs::new())).expect("the instance must stand")),
|
|
2736
|
+
)
|
|
2737
|
+
.expect("the Overlay must stack");
|
|
2738
|
+
let mount = Mount::new(
|
|
2739
|
+
"/data",
|
|
2740
|
+
Filesystem::new(Arc::new(overlay)).expect("the instance must stand"),
|
|
2741
|
+
Access::ReadWrite,
|
|
2742
|
+
)
|
|
2743
|
+
.expect("/data is a guest path");
|
|
2744
|
+
let root = Path::root(mount.mnt());
|
|
2745
|
+
let cred = Arc::default();
|
|
2746
|
+
let write = OpenHow {
|
|
2747
|
+
write: true,
|
|
2748
|
+
..OpenHow::default()
|
|
2749
|
+
};
|
|
2750
|
+
for name in ["d/e/f", "d/e/g"] {
|
|
2751
|
+
vfs::vfs_fstatat(&cred, &root, name, false).expect("the file stands");
|
|
2752
|
+
}
|
|
2753
|
+
hook.0.store(true, Ordering::SeqCst);
|
|
2754
|
+
let opened = thread::scope(|scope| {
|
|
2755
|
+
let (first, second) = (
|
|
2756
|
+
scope.spawn(|| vfs::kern_path::filp_open(&cred, &root, "d/e/f", &write).map(drop)),
|
|
2757
|
+
scope.spawn(|| vfs::kern_path::filp_open(&cred, &root, "d/e/g", &write).map(drop)),
|
|
2758
|
+
);
|
|
2759
|
+
(
|
|
2760
|
+
crate::sched::testing::join_scoped("the first open", first),
|
|
2761
|
+
crate::sched::testing::join_scoped("the second open", second),
|
|
2762
|
+
)
|
|
2763
|
+
});
|
|
2764
|
+
assert_eq!(
|
|
2765
|
+
opened,
|
|
2766
|
+
(Ok(()), Ok(())),
|
|
2767
|
+
"vfs::do_file_open to write two files of one lower directory at once must copy the \
|
|
2768
|
+
directory up once and open both"
|
|
2769
|
+
);
|
|
2770
|
+
}
|
|
2771
|
+
|
|
2772
|
+
mod stress {
|
|
2773
|
+
use super::*;
|
|
2774
|
+
use crate::mount::{Access, Mount};
|
|
2775
|
+
use crate::vfs::{self, OpenHow, Path};
|
|
2776
|
+
|
|
2777
|
+
/// What a removal and an open to write answer racing over one lower file,
|
|
2778
|
+
/// with what the descriptor the open made reports of itself.
|
|
2779
|
+
fn race_removal_with_a_write() -> (
|
|
2780
|
+
Result<(), FilesystemError>,
|
|
2781
|
+
Option<Result<FileType, FilesystemError>>,
|
|
2782
|
+
) {
|
|
2783
|
+
let lower: Tmpfs = [("d/f", "lower")].into_iter().collect();
|
|
2784
|
+
let overlay = Overlay::new(
|
|
2785
|
+
vec![Filesystem::new(Arc::new(lower)).expect("the instance must stand")],
|
|
2786
|
+
Some(Filesystem::new(Arc::new(Tmpfs::new())).expect("the instance must stand")),
|
|
2787
|
+
)
|
|
2788
|
+
.expect("the Overlay must stack");
|
|
2789
|
+
let mount = Mount::new(
|
|
2790
|
+
"/data",
|
|
2791
|
+
Filesystem::new(Arc::new(overlay)).expect("the instance must stand"),
|
|
2792
|
+
Access::ReadWrite,
|
|
2793
|
+
)
|
|
2794
|
+
.expect("/data is a guest path");
|
|
2795
|
+
let root = Path::root(mount.mnt());
|
|
2796
|
+
let cred = Arc::default();
|
|
2797
|
+
let write = OpenHow {
|
|
2798
|
+
write: true,
|
|
2799
|
+
..OpenHow::default()
|
|
2800
|
+
};
|
|
2801
|
+
let ready = std::sync::atomic::AtomicBool::new(false);
|
|
2802
|
+
std::thread::scope(|scope| {
|
|
2803
|
+
let opening = scope.spawn(|| {
|
|
2804
|
+
crate::sched::testing::wait_until("the removal starting", || {
|
|
2805
|
+
ready.load(Ordering::SeqCst)
|
|
2806
|
+
});
|
|
2807
|
+
vfs::kern_path::filp_open(&cred, &root, "d/f", &write)
|
|
2808
|
+
.ok()
|
|
2809
|
+
.map(|file| file.getattr().map(|metadata| metadata.file_type))
|
|
2810
|
+
});
|
|
2811
|
+
ready.store(true, Ordering::SeqCst);
|
|
2812
|
+
let removed = crate::sched::testing::within_max_wait(|| {
|
|
2813
|
+
vfs::filename_unlinkat(&cred, &root, "d/f")
|
|
2814
|
+
});
|
|
2815
|
+
(
|
|
2816
|
+
removed,
|
|
2817
|
+
crate::sched::testing::join_scoped("the open", opening),
|
|
2818
|
+
)
|
|
2819
|
+
})
|
|
2820
|
+
}
|
|
2821
|
+
|
|
2822
|
+
// A removal deciding how to hide the name before the copy up landed asked
|
|
2823
|
+
// the rename to find nothing there.
|
|
2824
|
+
// @behavior FS-182
|
|
2825
|
+
#[test]
|
|
2826
|
+
#[ignore = "a stress witness; run by rake crates:stress"]
|
|
2827
|
+
fn a_file_removed_as_it_is_copied_up_goes() {
|
|
2828
|
+
for _ in 0..50_000 {
|
|
2829
|
+
let (removed, _) = race_removal_with_a_write();
|
|
2830
|
+
assert_eq!(
|
|
2831
|
+
removed,
|
|
2832
|
+
Ok(()),
|
|
2833
|
+
"vfs::filename_unlinkat of a file another Thread opens to write through an \
|
|
2834
|
+
Overlay must remove it"
|
|
2835
|
+
);
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
|
|
2839
|
+
// A copy up reading its name back after its lock went found the whiteout
|
|
2840
|
+
// the removal left there.
|
|
2841
|
+
// @behavior FS-180
|
|
2842
|
+
#[test]
|
|
2843
|
+
#[ignore = "a stress witness; run by rake crates:stress"]
|
|
2844
|
+
fn a_file_copied_up_as_its_name_is_removed_stays_a_file() {
|
|
2845
|
+
for _ in 0..50_000 {
|
|
2846
|
+
let (_, opened) = race_removal_with_a_write();
|
|
2847
|
+
if let Some(kind) = opened {
|
|
2848
|
+
assert_eq!(
|
|
2849
|
+
kind,
|
|
2850
|
+
Ok(FileType::RegFile),
|
|
2851
|
+
"File::getattr of a file opened to write through an Overlay as it is \
|
|
2852
|
+
removed must report a file"
|
|
2853
|
+
);
|
|
2854
|
+
}
|
|
2855
|
+
}
|
|
2856
|
+
}
|
|
2857
|
+
}
|
|
2858
|
+
|
|
2859
|
+
/// A lower Layer whose one entry, `pipe`, is a FIFO, counting each open.
|
|
2860
|
+
struct HoldsFifo(Arc<AtomicUsize>);
|
|
2861
|
+
|
|
2862
|
+
impl FileSystemType for HoldsFifo {
|
|
2863
|
+
fn fill_super(self: Arc<Self>, sb: &mut Filesystem) -> Result<(), FilesystemError> {
|
|
2864
|
+
sb.d_make_root(Inode::new(&Kstat::new(FileType::Dir, 0), self));
|
|
2865
|
+
Ok(())
|
|
2866
|
+
}
|
|
2867
|
+
}
|
|
2868
|
+
|
|
2869
|
+
impl InodeOperations for HoldsFifo {
|
|
2870
|
+
fn getattr(&self, dentry: &Dentry) -> Result<Kstat, FilesystemError> {
|
|
2871
|
+
Ok(match dentry.name().as_str() {
|
|
2872
|
+
"pipe" => Kstat::new(FileType::Fifo, 0).with_identity(1, 2),
|
|
2873
|
+
_ => Kstat::new(FileType::Dir, 0).with_identity(1, 1),
|
|
2874
|
+
})
|
|
2875
|
+
}
|
|
2876
|
+
|
|
2877
|
+
fn lookup(&self, dentry: &Dentry) -> Result<Lookup, FilesystemError> {
|
|
2878
|
+
Ok(match dentry.name().as_str() {
|
|
2879
|
+
"pipe" => Lookup::Found(Inode::new(
|
|
2880
|
+
&Kstat::new(FileType::Fifo, 0).with_identity(1, 2),
|
|
2881
|
+
Arc::new(Self(self.0.clone())),
|
|
2882
|
+
)),
|
|
2883
|
+
_ => Lookup::Absent,
|
|
2884
|
+
})
|
|
2885
|
+
}
|
|
2886
|
+
|
|
2887
|
+
fn open(
|
|
2888
|
+
&self,
|
|
2889
|
+
_dentry: &Dentry,
|
|
2890
|
+
_how: &OpenHow,
|
|
2891
|
+
) -> Result<Box<dyn FileOperations>, FilesystemError> {
|
|
2892
|
+
self.0.fetch_add(1, Ordering::SeqCst);
|
|
2893
|
+
Err(FilesystemError::Interrupted)
|
|
2894
|
+
}
|
|
2895
|
+
}
|
|
2896
|
+
|
|
2897
|
+
// A special file answers a time without any open having been asked, as
|
|
2898
|
+
// an open of one could wait for a peer past the invocation's timeout.
|
|
2899
|
+
// @behavior FS-162
|
|
2900
|
+
#[test]
|
|
2901
|
+
fn a_time_set_on_a_special_file_below_an_overlay_opens_nothing() {
|
|
2902
|
+
use crate::mount::{Access, Mount};
|
|
2903
|
+
use crate::vfs::{self, Path};
|
|
2904
|
+
let opened = Arc::new(AtomicUsize::new(0));
|
|
2905
|
+
let overlay = Overlay::new(
|
|
2906
|
+
vec![Filesystem::new(Arc::new(HoldsFifo(opened.clone())))
|
|
2907
|
+
.expect("the instance must stand")],
|
|
2908
|
+
Some(Filesystem::new(Arc::new(Tmpfs::new())).expect("the instance must stand")),
|
|
2909
|
+
)
|
|
2910
|
+
.expect("the Overlay must stack");
|
|
2911
|
+
let mount = Mount::new(
|
|
2912
|
+
"/data",
|
|
2913
|
+
Filesystem::new(Arc::new(overlay)).expect("the instance must stand"),
|
|
2914
|
+
Access::ReadWrite,
|
|
2915
|
+
)
|
|
2916
|
+
.expect("/data is a guest path");
|
|
2917
|
+
let answer = vfs::do_utimes_path(
|
|
2918
|
+
&Arc::default(),
|
|
2919
|
+
&Path::root(mount.mnt()),
|
|
2920
|
+
"pipe",
|
|
2921
|
+
false,
|
|
2922
|
+
&Iattr::times(None, Some(crate::vfs::SetTime::Now)),
|
|
2923
|
+
);
|
|
2924
|
+
assert_eq!(
|
|
2925
|
+
(answer, opened.load(Ordering::SeqCst)),
|
|
2926
|
+
(Err(FilesystemError::PermissionDenied), 0),
|
|
2927
|
+
"vfs::do_utimes_pathon a FIFO a lower Layer holds through an Overlay must answer EACCES \
|
|
2928
|
+
and open nothing"
|
|
2929
|
+
);
|
|
2930
|
+
}
|
|
2931
|
+
|
|
2932
|
+
// An earlier Overlay's upper, stacked as a lower Layer of another, holds a
|
|
2933
|
+
// whiteout in a directory only that Layer shows, so only a walk reading
|
|
2934
|
+
// such a directory for marks finds nothing there.
|
|
2935
|
+
// @behavior FS-159
|
|
2936
|
+
#[test]
|
|
2937
|
+
fn a_whiteout_in_a_directory_one_layer_shows_names_nothing() {
|
|
2938
|
+
let lower: Tmpfs = [("file", "x")].into_iter().collect();
|
|
2939
|
+
let first_upper = Filesystem::new(Arc::new(Tmpfs::new())).expect("the instance must stand");
|
|
2940
|
+
let first = kern_path(Arc::new(
|
|
2941
|
+
Overlay::new(
|
|
2942
|
+
vec![Filesystem::new(Arc::new(lower)).expect("the instance must stand")],
|
|
2943
|
+
Some(first_upper.clone()),
|
|
2944
|
+
)
|
|
2945
|
+
.expect("the Overlay must stack"),
|
|
2946
|
+
));
|
|
2947
|
+
first.vfs_unlink("file").expect("the file must be removed");
|
|
2948
|
+
let second = at(Overlay::new(
|
|
2949
|
+
vec![first_upper],
|
|
2950
|
+
Some(Filesystem::new(Arc::new(Tmpfs::new())).expect("the instance must stand")),
|
|
2951
|
+
)
|
|
2952
|
+
.expect("the Overlay must stack"));
|
|
2953
|
+
assert_eq!(
|
|
2954
|
+
second.vfs_lstat("upper/file").err(),
|
|
2955
|
+
Some(FilesystemError::NotFound),
|
|
2956
|
+
"a stat through an Overlay of a whiteout in a directory one Layer shows must answer \
|
|
2957
|
+
ENOENT, as the listing shows nothing there"
|
|
2958
|
+
);
|
|
2959
|
+
}
|
|
2960
|
+
|
|
2961
|
+
// A lower directory moved onto a full one answers why it cannot move, so
|
|
2962
|
+
// only asking that first keeps what stands at the target from answering.
|
|
2963
|
+
// @behavior FS-160
|
|
2964
|
+
#[test]
|
|
2965
|
+
fn a_lower_directory_answers_that_it_cannot_move_before_its_target_is_read() {
|
|
2966
|
+
let lower: Tmpfs = [("dir/file", "x"), ("full/file", "x")]
|
|
2967
|
+
.into_iter()
|
|
2968
|
+
.collect();
|
|
2969
|
+
let overlay = at(over(Arc::new(lower), Arc::new(Tmpfs::new())));
|
|
2970
|
+
assert_eq!(
|
|
2971
|
+
overlay.rename("dir", &overlay, "full"),
|
|
2972
|
+
Err(FilesystemError::CrossesDevices),
|
|
2973
|
+
"a rename through an Overlay of a lower Layer's directory onto one holding entries \
|
|
2974
|
+
must answer EXDEV, as ovl_rename_start asks whether it can move first"
|
|
2975
|
+
);
|
|
2976
|
+
}
|
|
2977
|
+
|
|
2978
|
+
// The lower file is far bigger than the upper holds, so only a copy up
|
|
2979
|
+
// leaving its bytes behind can land.
|
|
2980
|
+
// @behavior FS-155
|
|
2981
|
+
#[test]
|
|
2982
|
+
fn a_truncating_open_copies_up_none_of_the_bytes() {
|
|
2983
|
+
use crate::mount::{Access, Mount};
|
|
2984
|
+
use crate::vfs::{self, OpenHow, Path};
|
|
2985
|
+
let lower: Tmpfs = [("file", vec![b'x'; 1 << 20])].into_iter().collect();
|
|
2986
|
+
let upper = Tmpfs::new().with_size(Some(4096)).with_nr_inodes(None);
|
|
2987
|
+
let overlay = Overlay::new(
|
|
2988
|
+
vec![Filesystem::new(Arc::new(lower)).expect("the instance must stand")],
|
|
2989
|
+
Some(Filesystem::new(Arc::new(upper)).expect("the instance must stand")),
|
|
2990
|
+
)
|
|
2991
|
+
.expect("the Overlay must stack");
|
|
2992
|
+
let mount = Mount::new(
|
|
2993
|
+
"/data",
|
|
2994
|
+
Filesystem::new(Arc::new(overlay)).expect("the instance must stand"),
|
|
2995
|
+
Access::ReadWrite,
|
|
2996
|
+
)
|
|
2997
|
+
.expect("/data is a guest path");
|
|
2998
|
+
let root = Path::root(mount.mnt());
|
|
2999
|
+
let cred = Arc::default();
|
|
3000
|
+
let how = OpenHow {
|
|
3001
|
+
read: true,
|
|
3002
|
+
write: true,
|
|
3003
|
+
truncate: true,
|
|
3004
|
+
..OpenHow::default()
|
|
3005
|
+
};
|
|
3006
|
+
let opened = vfs::kern_path::filp_open(&cred, &root, "file", &how);
|
|
3007
|
+
assert!(
|
|
3008
|
+
opened.is_ok_and(|mut file| file.read(&mut [0; 8]) == Ok(0)),
|
|
3009
|
+
"vfs::do_file_open with O_TRUNC of a lower file past what the upper holds must land \
|
|
3010
|
+
on an empty file"
|
|
3011
|
+
);
|
|
3012
|
+
}
|
|
3013
|
+
|
|
3014
|
+
// Only the directory the view holds open keeps its Layer's host directory
|
|
3015
|
+
// through walks past a Bind's bound, so only that reaches it once the host
|
|
3016
|
+
// moved it.
|
|
3017
|
+
// @behavior FS-152
|
|
3018
|
+
#[cfg(unix)]
|
|
3019
|
+
#[test]
|
|
3020
|
+
fn an_overlay_directory_held_open_keeps_its_bind_layers_directory_past_the_bound() {
|
|
3021
|
+
use crate::mount::{Access, Mount};
|
|
3022
|
+
use crate::vfs::{self, OpenHow, Path};
|
|
3023
|
+
let host = std::env::temp_dir().join(format!("kobako-overlay-held-{}", std::process::id()));
|
|
3024
|
+
let _ = std::fs::remove_dir_all(&host);
|
|
3025
|
+
let deep = host.join(vec!["d"; 100].join("/"));
|
|
3026
|
+
std::fs::create_dir_all(&deep).expect("the host tree must be made");
|
|
3027
|
+
std::fs::create_dir_all(host.join("held")).expect("the host tree must be made");
|
|
3028
|
+
std::fs::write(host.join("held/f"), "x").expect("the host file must be written");
|
|
3029
|
+
let lower = Filesystem::new(Arc::new(
|
|
3030
|
+
crate::filesystem::Bind::new(&host).expect("the host directory must open"),
|
|
3031
|
+
))
|
|
3032
|
+
.expect("the instance must stand");
|
|
3033
|
+
let overlay = Overlay::new(
|
|
3034
|
+
vec![lower],
|
|
3035
|
+
Some(Filesystem::new(Arc::new(Tmpfs::new())).expect("the instance must stand")),
|
|
3036
|
+
)
|
|
3037
|
+
.expect("the Overlay must stack");
|
|
3038
|
+
let mount = Mount::new(
|
|
3039
|
+
"/data",
|
|
3040
|
+
Filesystem::new(Arc::new(overlay)).expect("the instance must stand"),
|
|
3041
|
+
Access::Read,
|
|
3042
|
+
)
|
|
3043
|
+
.expect("/data is a guest path");
|
|
3044
|
+
let root = Path::root(mount.mnt());
|
|
3045
|
+
let cred = Arc::default();
|
|
3046
|
+
let directory = OpenHow {
|
|
3047
|
+
read: true,
|
|
3048
|
+
directory: true,
|
|
3049
|
+
..OpenHow::default()
|
|
3050
|
+
};
|
|
3051
|
+
let Ok(held) = vfs::kern_path::filp_open(&cred, &root, "held", &directory) else {
|
|
3052
|
+
panic!("the directory must open");
|
|
3053
|
+
};
|
|
3054
|
+
vfs::vfs_fstatat(&cred, &root, &vec!["d"; 100].join("/"), false)
|
|
3055
|
+
.unwrap_or_else(|_| panic!("the deep directory must stand"));
|
|
3056
|
+
std::fs::rename(host.join("held"), host.join("moved")).expect("the host must move it");
|
|
3057
|
+
let reached = vfs::vfs_fstatat(&cred, held.path(), "f", false);
|
|
3058
|
+
let _ = std::fs::remove_dir_all(&host);
|
|
3059
|
+
assert!(
|
|
3060
|
+
reached.is_ok(),
|
|
3061
|
+
"vfs::vfs_fstatat through an Overlay directory held open over a Bind Layer, once a \
|
|
3062
|
+
walk passed the bound and the host moved it, must still reach the directory it opened"
|
|
3063
|
+
);
|
|
3064
|
+
}
|
|
3065
|
+
|
|
3066
|
+
// Only the lower Layer holds `dir` when both handles open it, so the
|
|
3067
|
+
// second sees the file only if the copy up through the first reaches
|
|
3068
|
+
// it too.
|
|
3069
|
+
// @behavior FS-119
|
|
3070
|
+
#[test]
|
|
3071
|
+
fn a_copy_up_through_one_overlay_directory_is_seen_through_another() {
|
|
3072
|
+
let lower: Arc<dyn FileSystemType> = tmpfs_with("dir/a.txt", "a");
|
|
3073
|
+
let overlay = at(over(lower, Arc::new(Tmpfs::new())));
|
|
3074
|
+
let first = overlay.open_dir("dir").expect("dir must open");
|
|
3075
|
+
let second = overlay.open_dir("dir").expect("dir must open again");
|
|
3076
|
+
write(&first, "b.txt", b"b").expect("the write must copy dir up");
|
|
3077
|
+
assert_eq!(
|
|
3078
|
+
read(&second, "b.txt"),
|
|
3079
|
+
b"b".to_vec(),
|
|
3080
|
+
"a read through one directory Overlay::open_dir opened, of a file written through \
|
|
3081
|
+
another opened before the write copied it up, must read the file"
|
|
3082
|
+
);
|
|
3083
|
+
}
|
|
3084
|
+
|
|
3085
|
+
// The lower Layer holds no `dir`, so reaching it again by the path it
|
|
3086
|
+
// was opened at would miss where it moved.
|
|
3087
|
+
// @behavior FS-120
|
|
3088
|
+
#[test]
|
|
3089
|
+
fn a_write_through_a_moved_overlay_directory_lands_where_it_went() {
|
|
3090
|
+
let upper = tmpfs_with("upper/dir/a.txt", "a");
|
|
3091
|
+
let overlay = at(over(Arc::new(Tmpfs::new()), upper.clone()));
|
|
3092
|
+
let dir = overlay.open_dir("dir").expect("dir must open");
|
|
3093
|
+
overlay
|
|
3094
|
+
.rename("dir", &overlay, "moved")
|
|
3095
|
+
.expect("the directory must move");
|
|
3096
|
+
assert_eq!(
|
|
3097
|
+
(
|
|
3098
|
+
write(&dir, "b.txt", b"b"),
|
|
3099
|
+
read(&on(&upper), "upper/moved/b.txt")
|
|
3100
|
+
),
|
|
3101
|
+
(Ok(()), b"b".to_vec()),
|
|
3102
|
+
"a write through a directory Overlay::open_dir opened, once Overlay::rename moved it \
|
|
3103
|
+
over a lower Layer, must land where it moved"
|
|
3104
|
+
);
|
|
3105
|
+
}
|
|
3106
|
+
|
|
3107
|
+
// @behavior FS-083
|
|
3108
|
+
#[test]
|
|
3109
|
+
fn an_overlay_spells_a_path_as_the_layer_showing_it_stores_it() {
|
|
3110
|
+
let overlay = at(over(
|
|
3111
|
+
Arc::new(FoldsCase::new(
|
|
3112
|
+
[("private/secret", "x")].into_iter().collect(),
|
|
3113
|
+
)),
|
|
3114
|
+
Arc::new(Tmpfs::new()),
|
|
3115
|
+
));
|
|
3116
|
+
let private = overlay
|
|
3117
|
+
.open_dir("PRIVATE")
|
|
3118
|
+
.expect("the directory must open in any case");
|
|
3119
|
+
assert_eq!(
|
|
3120
|
+
(
|
|
3121
|
+
overlay.lookup("PRIVATE"),
|
|
3122
|
+
private.lookup("SECRET"),
|
|
3123
|
+
private.lookup("New")
|
|
3124
|
+
),
|
|
3125
|
+
(
|
|
3126
|
+
Ok("private".to_string()),
|
|
3127
|
+
Ok("secret".to_string()),
|
|
3128
|
+
Ok("New".to_string())
|
|
3129
|
+
),
|
|
3130
|
+
"a name asked of Overlay::lookup in another case, shown by a Layer folding case, \
|
|
3131
|
+
must come back as that Layer stores it, a new name kept as given"
|
|
3132
|
+
);
|
|
3133
|
+
}
|
|
3134
|
+
|
|
3135
|
+
// A directory only one Layer shows has nothing to merge or hide, so the
|
|
3136
|
+
// walk keeps what that Layer's lookup recorded, as overlayfs leaves it
|
|
3137
|
+
// unmerged and reads `d_is_dir` rather than ask for a stat.
|
|
3138
|
+
// @behavior FS-122
|
|
3139
|
+
#[test]
|
|
3140
|
+
fn a_directory_one_layer_shows_is_walked_without_its_stat() {
|
|
3141
|
+
let only: Arc<dyn FileSystemType> = tmpfs_with("top/dir/file", "only");
|
|
3142
|
+
let overlay = at(over(
|
|
3143
|
+
Arc::new(Wrap::new(only, Arc::new(StatsNoDir))),
|
|
3144
|
+
Arc::new(Tmpfs::new()),
|
|
3145
|
+
));
|
|
3146
|
+
assert_eq!(
|
|
3147
|
+
read(&overlay, "top/dir/file"),
|
|
3148
|
+
b"only",
|
|
3149
|
+
"a file read through Overlay::open beneath a directory only the lower Layer shows, \
|
|
3150
|
+
that Layer answering no stat of the directory, must read its content"
|
|
3151
|
+
);
|
|
3152
|
+
}
|
|
3153
|
+
|
|
3154
|
+
/// Refuses every open, as a Layer whose directory cannot open does.
|
|
3155
|
+
struct RefusesOpen;
|
|
3156
|
+
|
|
3157
|
+
impl Hook for RefusesOpen {
|
|
3158
|
+
fn open(
|
|
3159
|
+
&self,
|
|
3160
|
+
_inner: &dyn InodeOperations,
|
|
3161
|
+
_dentry: &Dentry,
|
|
3162
|
+
_how: &OpenHow,
|
|
3163
|
+
) -> Result<Box<dyn FileOperations>, FilesystemError> {
|
|
3164
|
+
Err(FilesystemError::PermissionDenied)
|
|
3165
|
+
}
|
|
3166
|
+
}
|
|
3167
|
+
|
|
3168
|
+
// A host removing a Bind's directory cannot witness this, as the fid the
|
|
3169
|
+
// lookup took still opens it, so a Layer refuses instead.
|
|
3170
|
+
// @behavior FS-166
|
|
3171
|
+
#[test]
|
|
3172
|
+
fn a_directory_a_layer_cannot_open_answers_why() {
|
|
3173
|
+
use crate::mount::{Access, Mount};
|
|
3174
|
+
use crate::vfs::{self, OpenHow};
|
|
3175
|
+
let lower: Arc<dyn FileSystemType> = tmpfs_with("top/file", "lower");
|
|
3176
|
+
let overlay = over(
|
|
3177
|
+
Arc::new(Wrap::new(lower, Arc::new(RefusesOpen))),
|
|
3178
|
+
Arc::new(Tmpfs::new()),
|
|
3179
|
+
);
|
|
3180
|
+
let mount = Mount::new(
|
|
3181
|
+
"/data",
|
|
3182
|
+
Filesystem::new(Arc::new(overlay)).expect("the instance must stand"),
|
|
3183
|
+
Access::Read,
|
|
3184
|
+
)
|
|
3185
|
+
.expect("/data is a guest path");
|
|
3186
|
+
let directory = OpenHow {
|
|
3187
|
+
read: true,
|
|
3188
|
+
directory: true,
|
|
3189
|
+
..OpenHow::default()
|
|
3190
|
+
};
|
|
3191
|
+
let answer = vfs::kern_path::filp_open(
|
|
3192
|
+
&Arc::default(),
|
|
3193
|
+
&vfs::Path::root(mount.mnt()),
|
|
3194
|
+
"top",
|
|
3195
|
+
&directory,
|
|
3196
|
+
);
|
|
3197
|
+
assert_eq!(
|
|
3198
|
+
answer.err(),
|
|
3199
|
+
Some(FilesystemError::PermissionDenied),
|
|
3200
|
+
"vfs::do_file_open of an Overlay's directory whose Layer refuses its open must answer \
|
|
3201
|
+
what the Layer answered"
|
|
3202
|
+
);
|
|
3203
|
+
}
|
|
3204
|
+
|
|
3205
|
+
// @behavior FS-080
|
|
3206
|
+
#[test]
|
|
3207
|
+
fn a_file_gone_during_a_rename_answers_not_found() {
|
|
3208
|
+
let tmpfs: Tmpfs = [("file", "lower")].into_iter().collect();
|
|
3209
|
+
let lower = Wrap::new(Arc::new(tmpfs), Arc::new(Vanishes::default()));
|
|
3210
|
+
let overlay = at(over(Arc::new(lower), Arc::new(Tmpfs::new())));
|
|
3211
|
+
assert_eq!(
|
|
3212
|
+
overlay.rename("file", &overlay, "moved"),
|
|
3213
|
+
Err(FilesystemError::NotFound),
|
|
3214
|
+
"a file renamed through Overlay::rename that its Layer loses during the call must \
|
|
3215
|
+
answer not found"
|
|
3216
|
+
);
|
|
3217
|
+
}
|
|
3218
|
+
|
|
3219
|
+
// The upper Layer's `dir/sub` stands where the lower Layer holds a
|
|
3220
|
+
// file `dir`, so the merge below it meets a file on the way.
|
|
3221
|
+
// @behavior FS-078
|
|
3222
|
+
#[test]
|
|
3223
|
+
fn a_directory_over_a_lower_file_on_its_way_reads_as_a_directory() {
|
|
3224
|
+
let overlay = at(over(
|
|
3225
|
+
tmpfs_with("dir", "lower"),
|
|
3226
|
+
tmpfs_with("upper/dir/sub/file", "upper"),
|
|
3227
|
+
));
|
|
3228
|
+
assert_eq!(
|
|
3229
|
+
(
|
|
3230
|
+
overlay
|
|
3231
|
+
.vfs_lstat("dir/sub")
|
|
3232
|
+
.map(|metadata| metadata.file_type),
|
|
3233
|
+
read(&overlay, "dir/sub/file")
|
|
3234
|
+
),
|
|
3235
|
+
(Ok(FileType::Dir), b"upper".to_vec()),
|
|
3236
|
+
"a directory looked up through vfs_lstat above a lower Layer holding a file on \
|
|
3237
|
+
its path must read as a directory, as its files open"
|
|
3238
|
+
);
|
|
3239
|
+
}
|
|
3240
|
+
|
|
3241
|
+
// @behavior FS-101
|
|
3242
|
+
#[test]
|
|
3243
|
+
fn a_file_opened_to_read_before_a_copy_up_reads_the_copy() {
|
|
3244
|
+
let (overlay, _) = overlay_on(Arc::new(Tmpfs::default()));
|
|
3245
|
+
let mut reader = open_to_read(&overlay, "dir/file");
|
|
3246
|
+
overwrite(&overlay, "dir/file", b"upper");
|
|
3247
|
+
assert_eq!(
|
|
3248
|
+
read_from(reader.as_mut()),
|
|
3249
|
+
b"upper".to_vec(),
|
|
3250
|
+
"a file Overlay::open handed out to read before another open copied it up must \
|
|
3251
|
+
read what the copy holds"
|
|
3252
|
+
);
|
|
3253
|
+
}
|
|
3254
|
+
|
|
3255
|
+
// @behavior FS-102
|
|
3256
|
+
#[test]
|
|
3257
|
+
fn a_file_opened_to_read_before_a_rename_reads_the_moved_copy() {
|
|
3258
|
+
let (overlay, _) = overlay_on(Arc::new(Tmpfs::default()));
|
|
3259
|
+
let mut reader = open_to_read(&overlay, "dir/file");
|
|
3260
|
+
overlay
|
|
3261
|
+
.rename("dir/file", &overlay, "dir/moved")
|
|
3262
|
+
.expect("the rename must land");
|
|
3263
|
+
overwrite(&overlay, "dir/moved", b"moved");
|
|
3264
|
+
assert_eq!(
|
|
3265
|
+
read_from(reader.as_mut()),
|
|
3266
|
+
b"moved".to_vec(),
|
|
3267
|
+
"a file Overlay::open handed out to read before Overlay::rename moved it must read \
|
|
3268
|
+
what the moved copy holds"
|
|
3269
|
+
);
|
|
3270
|
+
}
|
|
3271
|
+
|
|
3272
|
+
// @behavior FS-103
|
|
3273
|
+
#[test]
|
|
3274
|
+
fn a_file_open_to_read_keeps_what_it_held_once_removed() {
|
|
3275
|
+
let (overlay, _) = overlay_on(Arc::new(Tmpfs::default()));
|
|
3276
|
+
let mut reader = open_to_read(&overlay, "dir/file");
|
|
3277
|
+
overwrite(&overlay, "dir/file", b"upper");
|
|
3278
|
+
overlay
|
|
3279
|
+
.vfs_unlink("dir/file")
|
|
3280
|
+
.expect("the removal must land");
|
|
3281
|
+
overwrite(&overlay, "dir/file", b"fresh");
|
|
3282
|
+
overlay
|
|
3283
|
+
.rename("dir/file", &overlay, "dir/moved")
|
|
3284
|
+
.expect("the rename must land");
|
|
3285
|
+
assert_eq!(
|
|
3286
|
+
read_from(reader.as_mut()),
|
|
3287
|
+
b"upper".to_vec(),
|
|
3288
|
+
"a file Overlay::open handed out to read must keep reading what it held once \
|
|
3289
|
+
vfs_unlink took it away, whatever later stands at its path"
|
|
3290
|
+
);
|
|
3291
|
+
}
|
|
3292
|
+
|
|
3293
|
+
/// A Tmpfs whose first open sleeps a while, flagging that it began.
|
|
3294
|
+
#[derive(Default)]
|
|
3295
|
+
struct SleepsOnOpen {
|
|
3296
|
+
opening: AtomicBool,
|
|
3297
|
+
}
|
|
3298
|
+
|
|
3299
|
+
impl Hook for SleepsOnOpen {
|
|
3300
|
+
fn open(
|
|
3301
|
+
&self,
|
|
3302
|
+
inner: &dyn InodeOperations,
|
|
3303
|
+
dentry: &Dentry,
|
|
3304
|
+
how: &OpenHow,
|
|
3305
|
+
) -> Result<Box<dyn FileOperations>, FilesystemError> {
|
|
3306
|
+
if !self.opening.swap(true, Ordering::SeqCst) {
|
|
3307
|
+
thread::sleep(Duration::from_millis(100));
|
|
3308
|
+
}
|
|
3309
|
+
inner.open(dentry, how)
|
|
3310
|
+
}
|
|
3311
|
+
}
|
|
3312
|
+
|
|
3313
|
+
// The first copy up holds the file's lock while its lower open sleeps,
|
|
3314
|
+
// so the second, of the same file, finds the lock held and waits.
|
|
3315
|
+
// @behavior FS-039
|
|
3316
|
+
#[test]
|
|
3317
|
+
fn a_contended_copy_up_waits_through_the_registered_blocking() {
|
|
3318
|
+
let counts = crate::sched::testing::count_waits();
|
|
3319
|
+
let sleeps = Arc::new(SleepsOnOpen::default());
|
|
3320
|
+
let lower = Wrap::new(tmpfs_with("note", "lower"), sleeps.clone());
|
|
3321
|
+
let overlay = over(Arc::new(lower), Arc::new(Tmpfs::new()));
|
|
3322
|
+
let first = at(overlay);
|
|
3323
|
+
let second = first.clone();
|
|
3324
|
+
let copying = thread::spawn(move || write_over(&first, "note", b"first"));
|
|
3325
|
+
crate::sched::testing::wait_until("the first copy up opening its lower file", || {
|
|
3326
|
+
sleeps.opening.load(Ordering::SeqCst)
|
|
3327
|
+
});
|
|
3328
|
+
let waited =
|
|
3329
|
+
crate::sched::testing::within_max_wait(|| write_over(&second, "note", b"later"));
|
|
3330
|
+
assert_eq!(
|
|
3331
|
+
(
|
|
3332
|
+
crate::sched::testing::join("the first copy up", copying),
|
|
3333
|
+
waited,
|
|
3334
|
+
counts.load(Ordering::SeqCst) > 0
|
|
3335
|
+
),
|
|
3336
|
+
(Ok(()), Ok(()), true),
|
|
3337
|
+
"a write through an Overlay copying up a file another thread is copying up must wait \
|
|
3338
|
+
through the Blocking given to sched::register_blocking, then land"
|
|
3339
|
+
);
|
|
3340
|
+
}
|
|
3341
|
+
|
|
3342
|
+
// Copying up makes the directory in the upper, whose own identity
|
|
3343
|
+
// differs, so only reporting the lower Layer's keeps it.
|
|
3344
|
+
// @behavior FS-072
|
|
3345
|
+
#[test]
|
|
3346
|
+
fn an_overlay_keeps_a_directorys_identity_when_a_write_copies_it_up() {
|
|
3347
|
+
let lower: Arc<dyn FileSystemType> =
|
|
3348
|
+
Arc::new([("dir/a.txt", "a")].into_iter().collect::<Tmpfs>());
|
|
3349
|
+
let overlay = at(over(lower, Arc::new(Tmpfs::new())));
|
|
3350
|
+
let identity = |overlay: &KernPath| {
|
|
3351
|
+
let metadata = overlay.vfs_lstat("dir").expect("the directory must stand");
|
|
3352
|
+
(metadata.dev, metadata.ino)
|
|
3353
|
+
};
|
|
3354
|
+
let before = identity(&overlay);
|
|
3355
|
+
overwrite(&overlay, "dir/b.txt", b"b");
|
|
3356
|
+
assert_eq!(
|
|
3357
|
+
(identity(&overlay) == before, before.1 != 0),
|
|
3358
|
+
(true, true),
|
|
3359
|
+
"a directory of a lower Layer copied up by a write through an Overlay's open must \
|
|
3360
|
+
keep the identity vfs_lstat reported before"
|
|
3361
|
+
);
|
|
3362
|
+
}
|
|
3363
|
+
|
|
3364
|
+
// The upper Layer's directory changes after the lower one is made, so
|
|
3365
|
+
// the two report different times and only the upper's matches.
|
|
3366
|
+
// @behavior FS-117
|
|
3367
|
+
#[test]
|
|
3368
|
+
fn an_overlay_reports_a_merged_directory_as_overlayfs_does() {
|
|
3369
|
+
let lower: Arc<Tmpfs> = Arc::new([("dir/a.txt", "a")].into_iter().collect());
|
|
3370
|
+
let upper: Arc<Tmpfs> = Arc::new(Tmpfs::new());
|
|
3371
|
+
let overlay = at(over(lower.clone(), upper.clone()));
|
|
3372
|
+
std::thread::sleep(std::time::Duration::from_millis(10));
|
|
3373
|
+
write(&overlay, "dir/b.txt", b"b").expect("the upper must take a write");
|
|
3374
|
+
let merged = overlay.vfs_lstat("dir").expect("the directory must stand");
|
|
3375
|
+
let modified = |layer: &Arc<Tmpfs>, path: &str| {
|
|
3376
|
+
on(layer).vfs_lstat(path).expect("a Layer holds it").mtime
|
|
3377
|
+
};
|
|
3378
|
+
assert_eq!(
|
|
3379
|
+
(
|
|
3380
|
+
merged.nlink,
|
|
3381
|
+
merged.mtime == modified(&upper, "upper/dir"),
|
|
3382
|
+
merged.mtime == modified(&lower, "dir"),
|
|
3383
|
+
),
|
|
3384
|
+
(1, true, false),
|
|
3385
|
+
"Overlay::getattr of a directory two Layers hold, written last in the upper, must \
|
|
3386
|
+
count one name and report the upper Layer's times"
|
|
3387
|
+
);
|
|
3388
|
+
}
|
|
3389
|
+
|
|
3390
|
+
// @behavior FS-073
|
|
3391
|
+
#[test]
|
|
3392
|
+
fn an_overlay_names_a_directory_alike_whether_a_link_at_its_end_is_followed() {
|
|
3393
|
+
let overlay = at(over(
|
|
3394
|
+
tmpfs_with("dir/lower", "x"),
|
|
3395
|
+
tmpfs_with("upper/dir/upper", "x"),
|
|
3396
|
+
));
|
|
3397
|
+
let identity = |metadata: Result<Kstat, FilesystemError>| {
|
|
3398
|
+
let metadata = metadata.expect("the directory must stand");
|
|
3399
|
+
(metadata.dev, metadata.ino)
|
|
3400
|
+
};
|
|
3401
|
+
assert_eq!(
|
|
3402
|
+
identity(overlay.vfs_lstat("dir")),
|
|
3403
|
+
identity(overlay.vfs_lstat("dir")),
|
|
3404
|
+
"a directory two Layers hold, looked up through vfs_lstat, must \
|
|
3405
|
+
report the identity vfs_lstat does"
|
|
3406
|
+
);
|
|
3407
|
+
}
|
|
3408
|
+
|
|
3409
|
+
/// A Layer whose `permission` refuses `mask` of what stands at `name`.
|
|
3410
|
+
struct RefusesOf {
|
|
3411
|
+
name: &'static str,
|
|
3412
|
+
mask: Mask,
|
|
3413
|
+
}
|
|
3414
|
+
|
|
3415
|
+
impl Hook for RefusesOf {
|
|
3416
|
+
fn permission(
|
|
3417
|
+
&self,
|
|
3418
|
+
inner: &dyn InodeOperations,
|
|
3419
|
+
dentry: &Dentry,
|
|
3420
|
+
mask: Mask,
|
|
3421
|
+
) -> Result<(), FilesystemError> {
|
|
3422
|
+
if dentry.name() == self.name && mask.contains(self.mask) {
|
|
3423
|
+
return Err(FilesystemError::PermissionDenied);
|
|
3424
|
+
}
|
|
3425
|
+
inner.permission(dentry, mask)
|
|
3426
|
+
}
|
|
3427
|
+
}
|
|
3428
|
+
|
|
3429
|
+
type LayerCall = fn(&KernPath) -> Result<(), FilesystemError>;
|
|
3430
|
+
|
|
3431
|
+
// Only a Layer of the Host App's own answers `permission`, so each call is
|
|
3432
|
+
// made over a lower Layer refusing just the one ask that call makes of it.
|
|
3433
|
+
// @behavior FS-206
|
|
3434
|
+
#[test]
|
|
3435
|
+
fn an_overlay_asks_a_layers_permission_where_overlayfs_does() {
|
|
3436
|
+
let calls: [(&str, &str, Mask, LayerCall); 4] = [
|
|
3437
|
+
("finding a name", "dir", Mask::EXEC, |view| {
|
|
3438
|
+
view.vfs_lstat("dir/file").map(drop)
|
|
3439
|
+
}),
|
|
3440
|
+
("opening a file", "file", Mask::OPEN, |view| {
|
|
3441
|
+
let read = OpenHow {
|
|
3442
|
+
read: true,
|
|
3443
|
+
..OpenHow::default()
|
|
3444
|
+
};
|
|
3445
|
+
view.open("dir/file", &read).map(drop)
|
|
3446
|
+
}),
|
|
3447
|
+
("listing a directory", "dir", Mask::OPEN, |view| {
|
|
3448
|
+
view.open_dir("dir")?.iterate_dir().map(drop)
|
|
3449
|
+
}),
|
|
3450
|
+
("copying a file up", "file", Mask::OPEN, |view| {
|
|
3451
|
+
write_over(view, "dir/file", b"y")
|
|
3452
|
+
}),
|
|
3453
|
+
];
|
|
3454
|
+
for (call, name, mask, run) in calls {
|
|
3455
|
+
let lower = Wrap::new(
|
|
3456
|
+
tmpfs_with("dir/file", "x"),
|
|
3457
|
+
Arc::new(RefusesOf { name, mask }),
|
|
3458
|
+
);
|
|
3459
|
+
let view = at(over(Arc::new(lower), Arc::new(Tmpfs::new())));
|
|
3460
|
+
assert_eq!(
|
|
3461
|
+
run(&view),
|
|
3462
|
+
Err(FilesystemError::PermissionDenied),
|
|
3463
|
+
"{call} through an Overlay over a Layer whose permission refuses {mask:?} of \
|
|
3464
|
+
`{name}` must answer that refusal, as overlayfs asks inode_permission of the \
|
|
3465
|
+
real inode"
|
|
3466
|
+
);
|
|
3467
|
+
}
|
|
3468
|
+
}
|
|
3469
|
+
}
|