kobako 0.29.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +217 -10
- data/Cargo.lock +139 -141
- data/README.md +96 -574
- data/ROADMAP.md +2 -1
- data/crates/kobako-runtime/CHANGELOG.md +269 -1
- data/crates/kobako-runtime/Cargo.toml +25 -15
- data/crates/kobako-runtime/README.md +7 -3
- data/crates/kobako-runtime/src/dispatch.rs +1 -1
- data/crates/kobako-runtime/src/endpoint/buffer.rs +192 -0
- data/crates/kobako-runtime/src/endpoint/capture.rs +115 -0
- data/crates/kobako-runtime/src/endpoint/dispatch.rs +253 -0
- data/crates/kobako-runtime/src/endpoint/invoke.rs +194 -0
- data/crates/kobako-runtime/src/endpoint/memory_cap.rs +238 -0
- data/crates/kobako-runtime/src/endpoint.rs +166 -0
- data/crates/kobako-runtime/src/filesystem/bind/fid.rs +82 -0
- data/crates/kobako-runtime/src/filesystem/{directory/spelling.rs → bind/lookup.rs} +91 -49
- data/crates/kobako-runtime/src/filesystem/bind/walk.rs +249 -0
- data/crates/kobako-runtime/src/filesystem/bind.rs +2665 -0
- data/crates/kobako-runtime/src/filesystem/overlay/file.rs +89 -0
- data/crates/kobako-runtime/src/filesystem/overlay/inode.rs +106 -0
- data/crates/kobako-runtime/src/filesystem/overlay/path.rs +285 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/copy_up.rs +135 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/dir.rs +263 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/namei.rs +135 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/readdir.rs +57 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack.rs +227 -734
- data/crates/kobako-runtime/src/filesystem/overlay.rs +2649 -1135
- data/crates/kobako-runtime/src/filesystem/path.rs +2 -41
- data/crates/kobako-runtime/src/filesystem/pipe.rs +221 -0
- data/crates/kobako-runtime/src/filesystem/rdir.rs +101 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs/tmpfile.rs +32 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs/whiteout.rs +85 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs/xattr.rs +22 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs.rs +1985 -0
- data/crates/kobako-runtime/src/filesystem/wrap.rs +224 -0
- data/crates/kobako-runtime/src/filesystem.rs +21 -387
- data/crates/kobako-runtime/src/hashtable.rs +44 -0
- data/crates/kobako-runtime/src/lib.rs +11 -6
- data/crates/kobako-runtime/src/locking/seqlock.rs +66 -0
- data/crates/kobako-runtime/src/locking.rs +218 -0
- data/crates/kobako-runtime/src/mount.rs +31 -104
- data/crates/kobako-runtime/src/profile.rs +137 -1
- data/crates/kobako-runtime/src/sched.rs +210 -0
- data/crates/kobako-runtime/src/security.rs +696 -0
- data/crates/kobako-runtime/src/vfs/attr.rs +162 -0
- data/crates/kobako-runtime/src/vfs/cred.rs +334 -0
- data/crates/kobako-runtime/src/vfs/dentry.rs +1412 -0
- data/crates/kobako-runtime/src/vfs/errno.rs +153 -0
- data/crates/kobako-runtime/src/vfs/file.rs +1915 -0
- data/crates/kobako-runtime/src/vfs/inode.rs +429 -0
- data/crates/kobako-runtime/src/vfs/kern_path.rs +217 -0
- data/crates/kobako-runtime/src/vfs/mount.rs +59 -0
- data/crates/kobako-runtime/src/vfs/namei.rs +5708 -0
- data/crates/kobako-runtime/src/vfs/openat2.rs +116 -0
- data/crates/kobako-runtime/src/vfs/path.rs +64 -0
- data/crates/kobako-runtime/src/vfs/readdir.rs +163 -0
- data/crates/kobako-runtime/src/vfs/stat.rs +157 -0
- data/crates/kobako-runtime/src/vfs/super_block.rs +122 -0
- data/crates/kobako-runtime/src/vfs/utimes.rs +289 -0
- data/crates/kobako-runtime/src/vfs.rs +60 -0
- data/crates/kobako-runtime/tests/linux_baseline/calls.rs +171 -0
- data/crates/kobako-runtime/tests/linux_baseline/concurrent.rs +276 -0
- data/crates/kobako-runtime/tests/linux_baseline/kobako.rs +323 -0
- data/crates/kobako-runtime/tests/linux_baseline/linux-baseline-expected.txt +16 -0
- data/crates/kobako-runtime/tests/linux_baseline/linux.rs +449 -0
- data/crates/kobako-runtime/tests/linux_baseline/main.rs +611 -0
- data/crates/kobako-runtime/tests/linux_baseline/permissions.rs +236 -0
- data/crates/kobako-transport/CHANGELOG.md +8 -2
- data/crates/kobako-transport/Cargo.toml +3 -3
- data/crates/kobako-transport/README.md +6 -5
- data/crates/kobako-transport/src/envelope/bytes.rs +33 -25
- data/crates/kobako-transport/src/envelope/call.rs +5 -5
- data/crates/kobako-transport/src/envelope/compile_reply.rs +2 -2
- data/crates/kobako-transport/src/envelope/error_record.rs +3 -3
- data/crates/kobako-transport/src/envelope/fault.rs +2 -2
- data/crates/kobako-transport/src/envelope/outcome.rs +3 -3
- data/crates/kobako-transport/src/envelope/preamble.rs +12 -12
- data/crates/kobako-transport/src/envelope/reply.rs +2 -2
- data/crates/kobako-transport/src/envelope/request.rs +2 -2
- data/crates/kobako-transport/src/envelope/run.rs +1 -1
- data/crates/kobako-transport/src/envelope/unsupported.rs +2 -2
- data/crates/kobako-transport/src/envelope.rs +2 -2
- data/crates/kobako-wasmtime/CHANGELOG.md +154 -8
- data/crates/kobako-wasmtime/Cargo.toml +15 -14
- data/crates/kobako-wasmtime/README.md +9 -5
- data/crates/kobako-wasmtime/src/abi.rs +12 -20
- data/crates/kobako-wasmtime/src/ambient.rs +1 -2
- data/crates/kobako-wasmtime/src/cache.rs +5 -5
- data/crates/kobako-wasmtime/src/config.rs +22 -7
- data/crates/kobako-wasmtime/src/descriptors.rs +133 -100
- data/crates/kobako-wasmtime/src/driver.rs +29 -96
- data/crates/kobako-wasmtime/src/exports.rs +5 -16
- data/crates/kobako-wasmtime/src/guest.rs +38 -0
- data/crates/kobako-wasmtime/src/imports.rs +14 -8
- data/crates/kobako-wasmtime/src/instance_pre.rs +3 -3
- data/crates/kobako-wasmtime/src/invocation.rs +26 -46
- data/crates/kobako-wasmtime/src/lib.rs +1 -4
- data/crates/kobako-wasmtime/src/limiter.rs +6 -230
- data/crates/kobako-wasmtime/src/preview1/differential/mount.rs +69 -46
- data/crates/kobako-wasmtime/src/preview1/differential.rs +121 -7
- data/crates/kobako-wasmtime/src/preview1/errno.rs +74 -206
- data/crates/kobako-wasmtime/src/preview1/fd.rs +1248 -500
- data/crates/kobako-wasmtime/src/preview1/memory.rs +117 -150
- data/crates/kobako-wasmtime/src/preview1/mount_view/wrap.rs +211 -0
- data/crates/kobako-wasmtime/src/preview1/mount_view.rs +1939 -672
- data/crates/kobako-wasmtime/src/preview1/path.rs +230 -349
- data/crates/kobako-wasmtime/src/preview1/poll.rs +148 -100
- data/crates/kobako-wasmtime/src/preview1/sock.rs +6 -23
- data/crates/kobako-wasmtime/src/preview1/testsuite-expected.txt +6 -0
- data/crates/kobako-wasmtime/src/preview1/testsuite.rs +157 -0
- data/crates/kobako-wasmtime/src/preview1.rs +464 -413
- data/crates/kobako-wasmtime/src/trap.rs +9 -9
- data/crates/kobako-wasmtime/src/wasi.rs +53 -37
- data/crates/kobako-wasmtime/tests/{guest_file.rs → integration/guest_file.rs} +232 -201
- data/crates/kobako-wasmtime/tests/integration/main.rs +29 -0
- data/crates/kobako-wasmtime/tests/{preamble_flags.rs → integration/preamble_flags.rs} +1 -1
- data/crates/kobako-wasmtime/tests/{snippet_compile.rs → integration/snippet_compile.rs} +1 -1
- data/crates/kobako-wasmtime/tests/{unanswered_call.rs → integration/unanswered_call.rs} +1 -1
- data/crates/kobako-wasmtime/tests/{unsupported_call.rs → integration/unsupported_call.rs} +1 -1
- data/crates/kobako-wasmtime/witx/typenames.witx +750 -0
- data/crates/kobako-wasmtime/witx/wasi_snapshot_preview1.witx +441 -0
- data/data/kobako.wasm +0 -0
- data/ext/kobako/Cargo.toml +1 -1
- data/ext/kobako/src/runtime/bridge.rs +1 -1
- data/ext/kobako/src/runtime/errors.rs +1 -1
- data/ext/kobako/src/runtime/escape.rs +28 -14
- data/ext/kobako/src/runtime/filesystem/grants.rs +21 -5
- data/ext/kobako/src/runtime/filesystem/ruby.rs +464 -156
- data/ext/kobako/src/runtime/filesystem.rs +109 -137
- data/ext/kobako/src/runtime/gvl.rs +43 -8
- data/ext/kobako/src/runtime/snapshot.rs +1 -1
- data/ext/kobako/src/runtime.rs +6 -1
- data/lib/kobako/catalog/extensions.rb +5 -5
- data/lib/kobako/catalog/flags.rb +2 -2
- data/lib/kobako/catalog/handles.rb +6 -6
- data/lib/kobako/catalog/mount.rb +2 -10
- data/lib/kobako/catalog/services.rb +2 -2
- data/lib/kobako/catalog/snippets.rb +7 -7
- data/lib/kobako/catalog.rb +1 -1
- data/lib/kobako/codec/decoder.rb +10 -10
- data/lib/kobako/codec/ext_types.rb +3 -5
- data/lib/kobako/codec/handle_walk.rb +4 -6
- data/lib/kobako/codec/nesting.rb +7 -7
- data/lib/kobako/codec/utils.rb +1 -1
- data/lib/kobako/codec/value_budget.rb +3 -3
- data/lib/kobako/context.rb +5 -5
- data/lib/kobako/filesystem.rb +44 -13
- data/lib/kobako/payload/arguments.rb +2 -2
- data/lib/kobako/pool.rb +4 -4
- data/lib/kobako/sandbox.rb +28 -28
- data/lib/kobako/sandbox_options.rb +2 -3
- data/lib/kobako/snippet/bytecode.rb +1 -1
- data/lib/kobako/snippet/source.rb +1 -1
- data/lib/kobako/transport/dispatcher.rb +5 -5
- data/lib/kobako/transport/exposure.rb +2 -2
- data/lib/kobako/transport/reflection.rb +7 -7
- data/lib/kobako/transport/run.rb +5 -5
- data/lib/kobako/transport/yielder.rb +1 -1
- data/lib/kobako/version.rb +1 -1
- data/lib/kobako.rb +34 -0
- data/release-please-config.json +1 -0
- data/sig/kobako/catalog/extensions.rbs +3 -3
- data/sig/kobako/catalog/handles.rbs +3 -3
- data/sig/kobako/catalog/mount.rbs +0 -4
- data/sig/kobako/catalog/services.rbs +1 -1
- data/sig/kobako/catalog/snippets.rbs +3 -3
- data/sig/kobako/catalog.rbs +1 -1
- data/sig/kobako/codec/decoder.rbs +1 -1
- data/sig/kobako/codec/handle_walk.rbs +1 -1
- data/sig/kobako/codec/nesting.rbs +2 -2
- data/sig/kobako/codec/utils.rbs +1 -1
- data/sig/kobako/codec/value_budget.rbs +1 -1
- data/sig/kobako/codec.rbs +1 -2
- data/sig/kobako/context.rbs +1 -1
- data/sig/kobako/filesystem.rbs +19 -14
- data/sig/kobako/guest_yielder.rbs +1 -1
- data/sig/kobako/payload/arguments.rbs +1 -1
- data/sig/kobako/pool.rbs +2 -2
- data/sig/kobako/runtime.rbs +1 -1
- data/sig/kobako/sandbox.rbs +3 -5
- data/sig/kobako/sandbox_options.rbs +1 -1
- data/sig/kobako/transport/dispatcher.rbs +2 -2
- data/sig/kobako/transport/exposure.rbs +1 -1
- data/sig/kobako/transport/reflection.rbs +2 -2
- data/sig/kobako/transport/run.rbs +3 -3
- data/sig/kobako/transport.rbs +1 -1
- metadata +67 -19
- data/crates/kobako-runtime/src/filesystem/directory.rs +0 -1082
- data/crates/kobako-runtime/src/filesystem/memory.rs +0 -1100
- data/crates/kobako-runtime/src/filesystem/overlay/exclusive.rs +0 -88
- data/crates/kobako-runtime/src/filesystem/temporary.rs +0 -167
- data/crates/kobako-wasmtime/src/capture.rs +0 -96
- data/crates/kobako-wasmtime/src/dispatch.rs +0 -125
- data/crates/kobako-wasmtime/src/guest_mem.rs +0 -271
- data/crates/kobako-wasmtime/src/io.rs +0 -89
- data/crates/kobako-wasmtime/src/preview1/args.rs +0 -60
- data/crates/kobako-wasmtime/src/preview1/beneath.rs +0 -188
- /data/crates/kobako-wasmtime/tests/{compile_posture.rs → integration/compile_posture.rs} +0 -0
- /data/crates/kobako-wasmtime/tests/{support → integration/support}/mod.rs +0 -0
|
@@ -1,146 +1,425 @@
|
|
|
1
|
-
//! `Overlay`: Layers
|
|
2
|
-
//!
|
|
1
|
+
//! `Overlay`: read-only lower Layers under an upper Tmpfs, stacked into
|
|
2
|
+
//! one Filesystem, as Linux overlayfs stacks a mount's layers.
|
|
3
3
|
//!
|
|
4
|
-
//! The
|
|
5
|
-
//!
|
|
6
|
-
//!
|
|
7
|
-
//!
|
|
8
|
-
//!
|
|
9
|
-
//!
|
|
10
|
-
//! across several Layers is never seen half made.
|
|
11
|
-
|
|
12
|
-
mod
|
|
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;
|
|
13
15
|
mod stack;
|
|
14
16
|
|
|
17
|
+
use std::any::Any;
|
|
15
18
|
use std::sync::Arc;
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
use super::rdir::Rdir;
|
|
19
21
|
use super::{
|
|
20
|
-
|
|
22
|
+
DentryOperations, FileOperations, FileSystemType, FileType, Filesystem, FilesystemError, Iattr,
|
|
23
|
+
InodeOperations, Kstat, Lookup, OpenHow, RenameFlag, Tmpfs,
|
|
21
24
|
};
|
|
22
|
-
use
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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.
|
|
29
44
|
#[derive(Clone)]
|
|
30
45
|
pub struct Overlay {
|
|
31
|
-
|
|
32
|
-
|
|
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
|
+
}
|
|
33
106
|
}
|
|
34
107
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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),
|
|
38
122
|
}
|
|
39
123
|
}
|
|
40
124
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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());
|
|
47
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
|
+
}))
|
|
48
156
|
}
|
|
49
157
|
|
|
50
|
-
///
|
|
51
|
-
///
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
+
})
|
|
58
173
|
}
|
|
174
|
+
}
|
|
59
175
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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)
|
|
66
240
|
}
|
|
67
241
|
}
|
|
68
242
|
|
|
69
|
-
impl
|
|
70
|
-
fn
|
|
71
|
-
|
|
72
|
-
self.stack.open(path, options)
|
|
243
|
+
impl InodeOperations for OverlayLink {
|
|
244
|
+
fn getattr(&self, dentry: &Dentry) -> Result<Kstat, FilesystemError> {
|
|
245
|
+
self.0.real_getattr(dentry)
|
|
73
246
|
}
|
|
74
247
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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()
|
|
78
252
|
}
|
|
253
|
+
}
|
|
79
254
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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(())
|
|
83
275
|
}
|
|
276
|
+
}
|
|
84
277
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
+
}))
|
|
88
338
|
}
|
|
89
339
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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(())
|
|
93
349
|
}
|
|
94
350
|
|
|
95
|
-
fn
|
|
96
|
-
|
|
97
|
-
self.stack.hold_directory(path)
|
|
351
|
+
fn mkdir(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
|
|
352
|
+
self.fs.mkdir(&self.fs.parent(dentry)?, &dentry.name())
|
|
98
353
|
}
|
|
99
354
|
|
|
100
|
-
fn
|
|
101
|
-
|
|
102
|
-
self.stack.read_dir(path)
|
|
355
|
+
fn rmdir(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
|
|
356
|
+
self.fs.rmdir(&self.fs.parent(dentry)?, dentry)
|
|
103
357
|
}
|
|
104
358
|
|
|
105
|
-
fn
|
|
106
|
-
|
|
107
|
-
self.stack.create_dir(path)
|
|
359
|
+
fn unlink(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
|
|
360
|
+
self.fs.unlink(&self.fs.parent(dentry)?, dentry)
|
|
108
361
|
}
|
|
109
362
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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)
|
|
113
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
|
+
}
|
|
114
389
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
self
|
|
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)
|
|
118
399
|
}
|
|
119
400
|
|
|
120
|
-
fn
|
|
121
|
-
|
|
122
|
-
|
|
401
|
+
fn read_iter(
|
|
402
|
+
&mut self,
|
|
403
|
+
_iocb: &mut super::Kiocb,
|
|
404
|
+
_buf: &mut [u8],
|
|
405
|
+
) -> Result<usize, FilesystemError> {
|
|
406
|
+
generic_read_dir()
|
|
123
407
|
}
|
|
124
408
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
lock: self.lock.clone(),
|
|
133
|
-
}))
|
|
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()))
|
|
134
416
|
}
|
|
135
417
|
|
|
136
|
-
///
|
|
137
|
-
///
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
stack,
|
|
142
|
-
lock: self.lock.clone(),
|
|
143
|
-
}))
|
|
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(())
|
|
144
423
|
}
|
|
145
424
|
}
|
|
146
425
|
|
|
@@ -151,96 +430,173 @@ mod tests {
|
|
|
151
430
|
use std::thread;
|
|
152
431
|
use std::time::{Duration, Instant};
|
|
153
432
|
|
|
154
|
-
use super::super::
|
|
155
|
-
use super::super::
|
|
156
|
-
use super::super::{FileType,
|
|
433
|
+
use super::super::tmpfs::Tmpfs;
|
|
434
|
+
use super::super::wrap::{Hook, Wrap};
|
|
435
|
+
use super::super::{FileType, Finish, Kiocb};
|
|
157
436
|
use super::*;
|
|
437
|
+
use crate::vfs::kern_path::{kern_path, KernPath};
|
|
438
|
+
use crate::vfs::Mask;
|
|
158
439
|
|
|
159
|
-
///
|
|
160
|
-
|
|
161
|
-
()
|
|
162
|
-
|
|
163
|
-
self.metadata(path)
|
|
164
|
-
}
|
|
440
|
+
/// `filesystem` reached through the VFS.
|
|
441
|
+
fn at(filesystem: impl FileSystemType) -> KernPath {
|
|
442
|
+
kern_path(Arc::new(filesystem))
|
|
443
|
+
}
|
|
165
444
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
) -> Result<String, FilesystemError> {
|
|
171
|
-
Ok(path.to_string())
|
|
172
|
-
}
|
|
445
|
+
/// The Filesystem `filesystem` holds, reached through the VFS.
|
|
446
|
+
fn on<F: FileSystemType>(filesystem: &Arc<F>) -> KernPath {
|
|
447
|
+
kern_path(filesystem.clone())
|
|
448
|
+
}
|
|
173
449
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
+
}
|
|
178
462
|
}
|
|
179
463
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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)
|
|
186
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
|
+
);
|
|
187
491
|
}
|
|
188
492
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
+
);
|
|
196
515
|
}
|
|
197
516
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
+
);
|
|
205
539
|
}
|
|
206
540
|
|
|
207
|
-
fn
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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> {
|
|
217
553
|
let mut buf = vec![0; 64];
|
|
218
|
-
let read = file
|
|
554
|
+
let read = file
|
|
555
|
+
.read_iter(&mut Kiocb::at(0), &mut buf)
|
|
556
|
+
.expect("the file must read");
|
|
219
557
|
buf.truncate(read);
|
|
220
558
|
buf
|
|
221
559
|
}
|
|
222
560
|
|
|
223
|
-
fn
|
|
224
|
-
fs.
|
|
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(
|
|
225
567
|
path,
|
|
226
|
-
&
|
|
568
|
+
&OpenHow {
|
|
227
569
|
write: true,
|
|
228
|
-
create: true,
|
|
229
570
|
truncate: true,
|
|
230
|
-
..
|
|
571
|
+
..OpenHow::default()
|
|
231
572
|
},
|
|
232
573
|
)?
|
|
233
|
-
.
|
|
574
|
+
.write_iter(&mut Kiocb::at(0), contents)
|
|
234
575
|
.map(drop)
|
|
235
576
|
}
|
|
236
577
|
|
|
237
|
-
|
|
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]) {
|
|
238
591
|
write(fs, path, contents).expect("the write must land");
|
|
239
592
|
}
|
|
240
593
|
|
|
241
|
-
fn names(fs: &
|
|
242
|
-
let
|
|
243
|
-
.
|
|
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
|
|
244
600
|
.expect("the directory must list")
|
|
245
601
|
.into_iter()
|
|
246
602
|
.map(|entry| entry.name)
|
|
@@ -249,18 +605,90 @@ mod tests {
|
|
|
249
605
|
names
|
|
250
606
|
}
|
|
251
607
|
|
|
252
|
-
fn
|
|
608
|
+
fn tmpfs_with(path: &str, contents: &str) -> Arc<Tmpfs> {
|
|
253
609
|
Arc::new([(path, contents)].into_iter().collect())
|
|
254
610
|
}
|
|
255
611
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
(
|
|
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)
|
|
264
692
|
}
|
|
265
693
|
|
|
266
694
|
/// A host directory holding `target`, `dir/inner`, and the links `link`
|
|
@@ -288,74 +716,67 @@ mod tests {
|
|
|
288
716
|
Self(path)
|
|
289
717
|
}
|
|
290
718
|
|
|
291
|
-
///
|
|
292
|
-
fn overlay(&self) ->
|
|
293
|
-
|
|
294
|
-
super::super::Directory::new(&self.0).expect("the host directory must open");
|
|
295
|
-
Overlay::new()
|
|
296
|
-
.layer(Arc::new(Memory::new()), Access::ReadWrite)
|
|
297
|
-
.layer(Arc::new(lower), Access::Read)
|
|
719
|
+
/// An empty upper over the host directory as the lower Layer.
|
|
720
|
+
fn overlay(&self) -> KernPath {
|
|
721
|
+
self.under(Arc::new(Tmpfs::new()))
|
|
298
722
|
}
|
|
299
|
-
}
|
|
300
723
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
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")
|
|
305
731
|
}
|
|
306
732
|
}
|
|
307
733
|
|
|
308
|
-
// The
|
|
309
|
-
//
|
|
310
|
-
//
|
|
311
|
-
//
|
|
312
|
-
// @behavior FS-098
|
|
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
|
|
313
738
|
#[cfg(unix)]
|
|
314
739
|
#[test]
|
|
315
|
-
fn
|
|
316
|
-
let
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
std::os::unix::fs::symlink("../target", host.0.join("dir/up"))
|
|
322
|
-
.expect("the link must be made");
|
|
323
|
-
let overlay = host.overlay();
|
|
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");
|
|
324
746
|
assert_eq!(
|
|
325
747
|
(
|
|
326
|
-
overlay
|
|
327
|
-
overlay
|
|
748
|
+
kind(&overlay, "dirlink/inner"),
|
|
749
|
+
names(&overlay, "dirlink").len()
|
|
328
750
|
),
|
|
329
|
-
(
|
|
330
|
-
"a
|
|
331
|
-
|
|
332
|
-
of `dir` must not be permitted"
|
|
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"
|
|
333
754
|
);
|
|
334
755
|
}
|
|
335
756
|
|
|
336
|
-
|
|
337
|
-
|
|
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.
|
|
338
766
|
// @behavior FS-100
|
|
339
767
|
#[cfg(unix)]
|
|
340
768
|
#[test]
|
|
341
|
-
fn
|
|
342
|
-
let host = LinkTree::new("
|
|
769
|
+
fn an_overlay_opens_no_link_as_a_directory_where_asked_not_to_follow() {
|
|
770
|
+
let host = LinkTree::new("nofollow");
|
|
343
771
|
let overlay = host.overlay();
|
|
344
|
-
let reading = OpenOptions {
|
|
345
|
-
read: true,
|
|
346
|
-
..OpenOptions::default()
|
|
347
|
-
};
|
|
348
|
-
let for_policy = overlay
|
|
349
|
-
.for_policy()
|
|
350
|
-
.expect("an Overlay over a Directory has a Filesystem for a Policy");
|
|
351
772
|
assert_eq!(
|
|
352
773
|
(
|
|
353
|
-
overlay.
|
|
354
|
-
|
|
774
|
+
overlay.open_dir("dirlink").map(drop),
|
|
775
|
+
overlay.vfs_readlink("dirlink")
|
|
355
776
|
),
|
|
356
|
-
(
|
|
357
|
-
"a
|
|
358
|
-
|
|
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"
|
|
359
780
|
);
|
|
360
781
|
}
|
|
361
782
|
|
|
@@ -365,16 +786,12 @@ mod tests {
|
|
|
365
786
|
fn an_overlay_reports_a_link_its_layer_holds_as_a_link() {
|
|
366
787
|
let host = LinkTree::new("lstat");
|
|
367
788
|
let overlay = host.overlay();
|
|
368
|
-
let file_type = |path|
|
|
369
|
-
overlay
|
|
370
|
-
.symlink_metadata(path)
|
|
371
|
-
.map(|metadata| metadata.file_type)
|
|
372
|
-
};
|
|
789
|
+
let file_type = |path| kind(&overlay, path);
|
|
373
790
|
assert_eq!(
|
|
374
791
|
(file_type("link"), file_type("dangling")),
|
|
375
792
|
(Ok(FileType::Symlink), Ok(FileType::Symlink)),
|
|
376
|
-
"a link a
|
|
377
|
-
|
|
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"
|
|
378
795
|
);
|
|
379
796
|
}
|
|
380
797
|
|
|
@@ -387,11 +804,11 @@ mod tests {
|
|
|
387
804
|
let host = LinkTree::new("unlink");
|
|
388
805
|
let overlay = host.overlay();
|
|
389
806
|
let removed = (
|
|
390
|
-
overlay.
|
|
391
|
-
overlay.
|
|
392
|
-
overlay.
|
|
807
|
+
overlay.vfs_rmdir("dirlink"),
|
|
808
|
+
overlay.vfs_unlink("dirlink"),
|
|
809
|
+
overlay.vfs_unlink("dangling"),
|
|
393
810
|
);
|
|
394
|
-
let gone = |path| overlay.
|
|
811
|
+
let gone = |path| overlay.vfs_lstat(path).err();
|
|
395
812
|
assert_eq!(
|
|
396
813
|
(
|
|
397
814
|
removed,
|
|
@@ -405,8 +822,8 @@ mod tests {
|
|
|
405
822
|
Some(FilesystemError::NotFound),
|
|
406
823
|
true
|
|
407
824
|
),
|
|
408
|
-
"a link a read-only
|
|
409
|
-
|
|
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, \
|
|
410
827
|
leaving the Layer as it was"
|
|
411
828
|
);
|
|
412
829
|
}
|
|
@@ -428,13 +845,13 @@ mod tests {
|
|
|
428
845
|
assert_eq!(
|
|
429
846
|
(
|
|
430
847
|
write(&overlay, "pipe", b"upper"),
|
|
431
|
-
overlay.rename("pipe", "moved"
|
|
848
|
+
overlay.rename("pipe", &overlay, "moved")
|
|
432
849
|
),
|
|
433
850
|
(
|
|
434
851
|
Err(FilesystemError::PermissionDenied),
|
|
435
852
|
Err(FilesystemError::CrossesDevices)
|
|
436
853
|
),
|
|
437
|
-
"a FIFO a read-only
|
|
854
|
+
"a FIFO a read-only Bind Layer holds, written or renamed through an Overlay, must \
|
|
438
855
|
be refused as permission denied and as crossing devices"
|
|
439
856
|
);
|
|
440
857
|
}
|
|
@@ -454,181 +871,287 @@ mod tests {
|
|
|
454
871
|
assert!(made.success(), "the FIFO must be made");
|
|
455
872
|
let overlay = host.overlay();
|
|
456
873
|
overlay
|
|
457
|
-
.
|
|
458
|
-
.expect("a directory must be made in the
|
|
459
|
-
let file_type = |path| overlay
|
|
874
|
+
.vfs_mkdir("made")
|
|
875
|
+
.expect("a directory must be made in the upper");
|
|
876
|
+
let file_type = |path| kind(&overlay, path);
|
|
460
877
|
assert_eq!(
|
|
461
878
|
(
|
|
462
|
-
overlay.rename("pipe", "dir"
|
|
463
|
-
overlay.rename("made", "pipe"
|
|
879
|
+
overlay.rename("pipe", &overlay, "dir"),
|
|
880
|
+
overlay.rename("made", &overlay, "pipe"),
|
|
464
881
|
file_type("pipe"),
|
|
465
882
|
file_type("made"),
|
|
466
883
|
),
|
|
467
884
|
(
|
|
468
885
|
Err(FilesystemError::IsADirectory),
|
|
469
886
|
Err(FilesystemError::NotADirectory),
|
|
470
|
-
Ok(FileType::
|
|
471
|
-
Ok(FileType::
|
|
887
|
+
Ok(FileType::Fifo),
|
|
888
|
+
Ok(FileType::Dir)
|
|
472
889
|
),
|
|
473
890
|
"a FIFO renamed onto a directory through an Overlay must be refused as a directory, \
|
|
474
891
|
and a directory renamed onto a FIFO as no directory, leaving both in place"
|
|
475
892
|
);
|
|
476
893
|
}
|
|
477
894
|
|
|
478
|
-
|
|
479
|
-
|
|
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.
|
|
480
1017
|
// @behavior FS-051
|
|
481
1018
|
#[cfg(unix)]
|
|
482
1019
|
#[test]
|
|
483
|
-
fn
|
|
1020
|
+
fn an_overlay_refuses_to_move_a_link_its_read_only_layer_holds() {
|
|
484
1021
|
let host = LinkTree::new("through");
|
|
485
1022
|
let overlay = host.overlay();
|
|
486
|
-
let creates = OpenOptions {
|
|
487
|
-
write: true,
|
|
488
|
-
create: true,
|
|
489
|
-
..OpenOptions::default()
|
|
490
|
-
};
|
|
491
|
-
let refused = [
|
|
492
|
-
write(&overlay, "link", b"upper"),
|
|
493
|
-
overlay.open("dirlink/new", &creates).map(drop),
|
|
494
|
-
overlay.open("dangling", &creates).map(drop),
|
|
495
|
-
overlay.rename("link", "moved", Rename::Any),
|
|
496
|
-
];
|
|
497
1023
|
assert_eq!(
|
|
498
|
-
(refused, read(&overlay, "target")),
|
|
499
1024
|
(
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
Err(FilesystemError::CrossesDevices),
|
|
503
|
-
Err(FilesystemError::CrossesDevices),
|
|
504
|
-
Err(FilesystemError::CrossesDevices)
|
|
505
|
-
],
|
|
506
|
-
b"lower".to_vec()
|
|
1025
|
+
overlay.rename("link", &overlay, "moved"),
|
|
1026
|
+
read(&overlay, "target")
|
|
507
1027
|
),
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
as it was"
|
|
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"
|
|
511
1031
|
);
|
|
512
1032
|
}
|
|
513
1033
|
|
|
514
|
-
// The lower
|
|
1034
|
+
// The lower Tmpfs holds a readable `dangling`, so only the upper
|
|
515
1035
|
// link standing at that path can keep the read from reaching it.
|
|
516
1036
|
// @behavior FS-053
|
|
517
1037
|
#[cfg(unix)]
|
|
518
1038
|
#[test]
|
|
519
1039
|
fn a_link_a_layer_holds_hides_what_the_layers_below_hold_there() {
|
|
520
1040
|
let host = LinkTree::new("shadow");
|
|
521
|
-
let
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
1041
|
+
let overlay = at(lowers(
|
|
1042
|
+
Arc::new(host.bind()),
|
|
1043
|
+
tmpfs_with("dangling", "lower"),
|
|
1044
|
+
));
|
|
525
1045
|
assert_eq!(
|
|
526
1046
|
overlay
|
|
527
1047
|
.open(
|
|
528
1048
|
"dangling",
|
|
529
|
-
&
|
|
1049
|
+
&OpenHow {
|
|
530
1050
|
read: true,
|
|
531
|
-
..
|
|
1051
|
+
..OpenHow::default()
|
|
532
1052
|
}
|
|
533
1053
|
)
|
|
534
1054
|
.map(drop),
|
|
535
|
-
Err(FilesystemError::
|
|
536
|
-
"a lower file beneath a dangling link a higher
|
|
537
|
-
opened through Overlay::open, must
|
|
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"
|
|
538
1058
|
);
|
|
539
1059
|
}
|
|
540
1060
|
|
|
541
|
-
// The lower
|
|
1061
|
+
// The lower Tmpfs holds `dirlink/x` beside where the upper link points,
|
|
542
1062
|
// so only the link hiding everything beneath it keeps `x` out.
|
|
543
1063
|
// @behavior FS-057
|
|
544
1064
|
#[cfg(unix)]
|
|
545
1065
|
#[test]
|
|
546
1066
|
fn a_link_a_layer_holds_hides_what_the_layers_below_hold_beneath_it() {
|
|
547
1067
|
let host = LinkTree::new("shadow-beneath");
|
|
548
|
-
let
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
let found = overlay
|
|
553
|
-
.metadata("dirlink/x")
|
|
554
|
-
.map(|metadata| metadata.file_type);
|
|
1068
|
+
let overlay = at(lowers(
|
|
1069
|
+
Arc::new(host.bind()),
|
|
1070
|
+
tmpfs_with("dirlink/x", "lower"),
|
|
1071
|
+
));
|
|
555
1072
|
assert_eq!(
|
|
556
|
-
(
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
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"
|
|
560
1082
|
);
|
|
561
1083
|
}
|
|
562
1084
|
|
|
563
|
-
// The lower directory
|
|
564
|
-
//
|
|
1085
|
+
// The lower directory stays in its Layer, so only the whiteout keeps
|
|
1086
|
+
// it from showing once the upper file goes.
|
|
565
1087
|
// @behavior FS-058
|
|
566
1088
|
#[test]
|
|
567
1089
|
fn removing_a_file_over_a_lower_directory_leaves_it_hidden() {
|
|
568
|
-
let lower =
|
|
569
|
-
let overlay =
|
|
570
|
-
|
|
571
|
-
.layer(lower.clone(), Access::ReadWrite);
|
|
572
|
-
let removed = overlay.remove_file("f");
|
|
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");
|
|
573
1093
|
assert_eq!(
|
|
574
1094
|
(
|
|
575
1095
|
removed,
|
|
576
|
-
overlay.
|
|
577
|
-
lower.
|
|
1096
|
+
overlay.vfs_lstat("f").err(),
|
|
1097
|
+
on(&lower).vfs_lstat("f/g").is_ok()
|
|
578
1098
|
),
|
|
579
1099
|
(Ok(()), Some(FilesystemError::NotFound), true),
|
|
580
1100
|
"a file a higher Layer holds over a lower Layer's directory, removed through \
|
|
581
|
-
|
|
1101
|
+
vfs_unlink, must leave the path gone from the view and the lower \
|
|
582
1102
|
directory as it was"
|
|
583
1103
|
);
|
|
584
1104
|
}
|
|
585
1105
|
|
|
586
|
-
// The
|
|
1106
|
+
// The upper's `dir` holds only the whiteout hiding `file`, so
|
|
587
1107
|
// only judging the view, not that copy, lets the move go.
|
|
588
1108
|
// @behavior FS-059
|
|
589
1109
|
#[test]
|
|
590
1110
|
fn a_directory_moves_onto_one_holding_only_markers() {
|
|
591
|
-
let (overlay, _) = overlay_on(Arc::new(
|
|
1111
|
+
let (overlay, _) = overlay_on(Arc::new(Tmpfs::new()));
|
|
592
1112
|
overlay
|
|
593
|
-
.
|
|
1113
|
+
.vfs_unlink("dir/file")
|
|
594
1114
|
.expect("the file must be removed");
|
|
595
1115
|
overlay
|
|
596
|
-
.
|
|
1116
|
+
.vfs_mkdir("new")
|
|
597
1117
|
.expect("the directory must be made");
|
|
598
1118
|
overwrite(&overlay, "new/made", b"made");
|
|
599
|
-
let moved = overlay.rename("new", "dir"
|
|
1119
|
+
let moved = overlay.rename("new", &overlay, "dir");
|
|
600
1120
|
assert_eq!(
|
|
601
1121
|
(moved, names(&overlay, "dir")),
|
|
602
1122
|
(Ok(()), vec!["made".to_string()]),
|
|
603
1123
|
"a directory renamed through Overlay::rename onto one the view shows empty, whose \
|
|
604
|
-
|
|
1124
|
+
upper copy holds only a whiteout, must take its place and show only its own entries"
|
|
605
1125
|
);
|
|
606
1126
|
}
|
|
607
1127
|
|
|
608
|
-
// The directory made again is marked opaque in the
|
|
609
|
-
//
|
|
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.
|
|
610
1130
|
// @behavior FS-060
|
|
611
1131
|
#[test]
|
|
612
|
-
fn
|
|
613
|
-
let (overlay, _) = overlay_on(Arc::new(
|
|
1132
|
+
fn a_directory_made_again_moves_as_the_uppers_own() {
|
|
1133
|
+
let (overlay, _) = overlay_on(Arc::new(Tmpfs::new()));
|
|
614
1134
|
overlay
|
|
615
|
-
.
|
|
1135
|
+
.vfs_unlink("dir/file")
|
|
616
1136
|
.expect("the file must be removed");
|
|
617
1137
|
overlay
|
|
618
|
-
.
|
|
1138
|
+
.vfs_rmdir("dir")
|
|
619
1139
|
.expect("the directory must be removed");
|
|
620
1140
|
overlay
|
|
621
|
-
.
|
|
1141
|
+
.vfs_mkdir("dir")
|
|
622
1142
|
.expect("the directory must be made again");
|
|
623
|
-
let moved = overlay.rename("dir", "moved"
|
|
1143
|
+
let moved = overlay.rename("dir", &overlay, "moved");
|
|
624
1144
|
assert_eq!(
|
|
625
1145
|
(
|
|
626
1146
|
moved,
|
|
627
|
-
overlay.
|
|
628
|
-
overlay
|
|
1147
|
+
overlay.vfs_lstat("dir").err(),
|
|
1148
|
+
overlay
|
|
1149
|
+
.open_dir("moved")
|
|
1150
|
+
.and_then(|dir| dir.iterate_dir())
|
|
1151
|
+
.map(|entries| entries.len())
|
|
629
1152
|
),
|
|
630
1153
|
(Ok(()), Some(FilesystemError::NotFound), Ok(0)),
|
|
631
|
-
"a directory made again through an Overlay where a
|
|
1154
|
+
"a directory made again through an Overlay where a lower Layer's one was removed, \
|
|
632
1155
|
renamed through Overlay::rename, must move and leave nothing of the lower one behind"
|
|
633
1156
|
);
|
|
634
1157
|
}
|
|
@@ -636,9 +1159,9 @@ mod tests {
|
|
|
636
1159
|
// @behavior FS-061
|
|
637
1160
|
#[test]
|
|
638
1161
|
fn an_overlay_refuses_a_file_moved_beneath_itself_as_no_directory() {
|
|
639
|
-
let (overlay, _) = overlay_on(Arc::new(
|
|
1162
|
+
let (overlay, _) = overlay_on(Arc::new(Tmpfs::new()));
|
|
640
1163
|
assert_eq!(
|
|
641
|
-
overlay.rename("dir/file", "dir/file/x"
|
|
1164
|
+
overlay.rename("dir/file", &overlay, "dir/file/x"),
|
|
642
1165
|
Err(FilesystemError::NotADirectory),
|
|
643
1166
|
"a file renamed through Overlay::rename to a path beneath itself must be refused as \
|
|
644
1167
|
no directory, as rename(2) refuses it"
|
|
@@ -653,21 +1176,20 @@ mod tests {
|
|
|
653
1176
|
fn an_overlay_finds_a_dangling_link_where_a_new_entry_would_go() {
|
|
654
1177
|
let host = LinkTree::new("dangling");
|
|
655
1178
|
let overlay = host.overlay();
|
|
656
|
-
let exclusive =
|
|
1179
|
+
let exclusive = OpenHow {
|
|
657
1180
|
write: true,
|
|
658
|
-
|
|
659
|
-
..OpenOptions::default()
|
|
1181
|
+
..OpenHow::default()
|
|
660
1182
|
};
|
|
661
1183
|
assert_eq!(
|
|
662
1184
|
(
|
|
663
|
-
overlay.
|
|
664
|
-
overlay.
|
|
1185
|
+
overlay.create("dangling", &exclusive).map(drop),
|
|
1186
|
+
overlay.vfs_mkdir("dangling")
|
|
665
1187
|
),
|
|
666
1188
|
(
|
|
667
1189
|
Err(FilesystemError::AlreadyExists),
|
|
668
1190
|
Err(FilesystemError::AlreadyExists)
|
|
669
1191
|
),
|
|
670
|
-
"an exclusive create and a create_dir at a dangling link a
|
|
1192
|
+
"an exclusive create and a create_dir at a dangling link a lower Bind Layer \
|
|
671
1193
|
holds, through an Overlay, must find it already there"
|
|
672
1194
|
);
|
|
673
1195
|
}
|
|
@@ -675,7 +1197,7 @@ mod tests {
|
|
|
675
1197
|
// @behavior FS-008
|
|
676
1198
|
#[test]
|
|
677
1199
|
fn an_overlay_reads_through_to_a_lower_layer() {
|
|
678
|
-
let (overlay, _) = overlay_on(Arc::new(
|
|
1200
|
+
let (overlay, _) = overlay_on(Arc::new(Tmpfs::new()));
|
|
679
1201
|
assert_eq!(
|
|
680
1202
|
read(&overlay, "dir/file"),
|
|
681
1203
|
b"lower",
|
|
@@ -685,67 +1207,53 @@ mod tests {
|
|
|
685
1207
|
|
|
686
1208
|
// @behavior FS-009
|
|
687
1209
|
#[test]
|
|
688
|
-
fn
|
|
689
|
-
let upper = Arc::new(
|
|
1210
|
+
fn a_write_to_a_lower_layers_file_lands_in_the_upper() {
|
|
1211
|
+
let upper = Arc::new(Tmpfs::new());
|
|
690
1212
|
let (overlay, _) = overlay_on(upper.clone());
|
|
691
1213
|
overwrite(&overlay, "dir/file", b"upper");
|
|
692
1214
|
assert_eq!(
|
|
693
|
-
read(upper
|
|
1215
|
+
read(&on(&upper), "upper/dir/file"),
|
|
694
1216
|
b"upper",
|
|
695
|
-
"a
|
|
1217
|
+
"a lower Layer's file written through Overlay::open must land in the upper"
|
|
696
1218
|
);
|
|
697
1219
|
}
|
|
698
1220
|
|
|
699
1221
|
// @behavior FS-010
|
|
700
1222
|
#[test]
|
|
701
|
-
fn
|
|
702
|
-
let (overlay, lower) = overlay_on(Arc::new(
|
|
1223
|
+
fn an_overlay_leaves_a_lower_layer_as_it_was() {
|
|
1224
|
+
let (overlay, lower) = overlay_on(Arc::new(Tmpfs::new()));
|
|
703
1225
|
overwrite(&overlay, "dir/file", b"upper");
|
|
704
1226
|
overlay
|
|
705
|
-
.
|
|
1227
|
+
.vfs_unlink("dir/file")
|
|
706
1228
|
.expect("the file must be removed");
|
|
707
1229
|
assert_eq!(
|
|
708
|
-
read(lower
|
|
1230
|
+
read(&on(&lower), "dir/file"),
|
|
709
1231
|
b"lower",
|
|
710
|
-
"a
|
|
1232
|
+
"a lower Layer's file written and removed through an Overlay must stay as it was"
|
|
711
1233
|
);
|
|
712
1234
|
}
|
|
713
1235
|
|
|
714
1236
|
// @behavior FS-011
|
|
715
1237
|
#[test]
|
|
716
1238
|
fn a_file_removed_through_an_overlay_is_gone_from_its_view() {
|
|
717
|
-
let (overlay, _) = overlay_on(Arc::new(
|
|
1239
|
+
let (overlay, _) = overlay_on(Arc::new(Tmpfs::new()));
|
|
718
1240
|
overlay
|
|
719
|
-
.
|
|
1241
|
+
.vfs_unlink("dir/file")
|
|
720
1242
|
.expect("the file must be removed");
|
|
721
1243
|
assert_eq!(
|
|
722
|
-
overlay.
|
|
1244
|
+
overlay.vfs_lstat("dir/file"),
|
|
723
1245
|
Err(FilesystemError::NotFound),
|
|
724
|
-
"a lower file removed through
|
|
725
|
-
);
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
// @behavior FS-012
|
|
729
|
-
#[test]
|
|
730
|
-
fn an_overlay_over_a_temporary_starts_every_invocation_from_its_lower_layers() {
|
|
731
|
-
let (overlay, _) = overlay_on(Arc::new(Temporary::new()));
|
|
732
|
-
overwrite(&overlay, "dir/file", b"upper");
|
|
733
|
-
let next = overlay
|
|
734
|
-
.for_invocation()
|
|
735
|
-
.expect("an Overlay over a Temporary must start each Invocation afresh");
|
|
736
|
-
assert_eq!(
|
|
737
|
-
read(next.as_ref(), "dir/file"),
|
|
738
|
-
b"lower",
|
|
739
|
-
"Overlay::for_invocation over a Temporary must hand the next Invocation the lower content"
|
|
1246
|
+
"a lower file removed through vfs_unlink must no longer be found there"
|
|
740
1247
|
);
|
|
741
1248
|
}
|
|
742
1249
|
|
|
743
1250
|
// @behavior FS-013
|
|
744
1251
|
#[test]
|
|
745
1252
|
fn an_upper_layer_hides_the_same_path_in_a_lower_one() {
|
|
746
|
-
let overlay =
|
|
747
|
-
|
|
748
|
-
|
|
1253
|
+
let overlay = at(lowers(
|
|
1254
|
+
tmpfs_with("note", "upper"),
|
|
1255
|
+
tmpfs_with("note", "lower"),
|
|
1256
|
+
));
|
|
749
1257
|
assert_eq!(
|
|
750
1258
|
read(&overlay, "note"),
|
|
751
1259
|
b"upper",
|
|
@@ -755,267 +1263,586 @@ mod tests {
|
|
|
755
1263
|
|
|
756
1264
|
// @behavior FS-014
|
|
757
1265
|
#[test]
|
|
758
|
-
fn
|
|
759
|
-
let upper =
|
|
760
|
-
let
|
|
761
|
-
let overlay = Overlay::new()
|
|
762
|
-
.layer(upper.clone(), Access::ReadWrite)
|
|
763
|
-
.layer(lower.clone(), Access::ReadWrite);
|
|
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()));
|
|
764
1269
|
overwrite(&overlay, "note", b"changed");
|
|
765
1270
|
assert_eq!(
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
"
|
|
1271
|
+
read(&on(&upper), "upper/note"),
|
|
1272
|
+
b"changed",
|
|
1273
|
+
"the upper's file written through Overlay::open must change in place"
|
|
769
1274
|
);
|
|
770
1275
|
}
|
|
771
1276
|
|
|
772
|
-
//
|
|
773
|
-
// move from landing in two Layers.
|
|
774
|
-
// @behavior FS-088
|
|
1277
|
+
// @behavior FS-015
|
|
775
1278
|
#[test]
|
|
776
|
-
fn
|
|
777
|
-
let upper = Arc::new(
|
|
778
|
-
let lower =
|
|
779
|
-
let overlay =
|
|
780
|
-
|
|
781
|
-
.layer(lower.clone(), Access::ReadWrite);
|
|
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");
|
|
782
1284
|
assert_eq!(
|
|
783
|
-
(
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
upper.read_dir("").map(|entries| entries.len())
|
|
787
|
-
),
|
|
788
|
-
(Err(FilesystemError::CrossesDevices), b"lower".to_vec(), Ok(0)),
|
|
789
|
-
"a file a lower writable Layer holds, renamed through Overlay::rename, must be refused \
|
|
790
|
-
as crossing devices with nothing moved or copied"
|
|
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"
|
|
791
1288
|
);
|
|
792
1289
|
}
|
|
793
1290
|
|
|
794
|
-
//
|
|
795
|
-
// call that answers by what landed rather than by the clean-up after
|
|
796
|
-
// it succeeds.
|
|
797
|
-
// @behavior FS-089
|
|
1291
|
+
// @behavior FS-016
|
|
798
1292
|
#[test]
|
|
799
|
-
fn
|
|
800
|
-
let
|
|
1293
|
+
fn a_removal_leaves_a_whiteout_in_the_upper() {
|
|
1294
|
+
let upper = Arc::new(Tmpfs::new());
|
|
1295
|
+
let (overlay, _) = overlay_on(upper.clone());
|
|
801
1296
|
overlay
|
|
802
|
-
.
|
|
1297
|
+
.vfs_unlink("dir/file")
|
|
803
1298
|
.expect("the file must be removed");
|
|
804
1299
|
assert_eq!(
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
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"
|
|
809
1306
|
);
|
|
810
1307
|
}
|
|
811
1308
|
|
|
812
|
-
//
|
|
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
|
|
813
1313
|
#[test]
|
|
814
|
-
fn
|
|
815
|
-
|
|
816
|
-
let
|
|
817
|
-
let overlay =
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
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();
|
|
822
1341
|
assert_eq!(
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
"a
|
|
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"
|
|
826
1346
|
);
|
|
827
1347
|
}
|
|
828
1348
|
|
|
829
|
-
//
|
|
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
|
|
830
1353
|
#[test]
|
|
831
|
-
fn
|
|
832
|
-
|
|
833
|
-
let (
|
|
834
|
-
overlay
|
|
835
|
-
|
|
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)
|
|
836
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
|
+
);
|
|
837
1382
|
assert_eq!(
|
|
838
|
-
|
|
839
|
-
Ok(
|
|
840
|
-
"
|
|
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"
|
|
841
1387
|
);
|
|
842
1388
|
}
|
|
843
1389
|
|
|
844
1390
|
// @behavior FS-017
|
|
845
1391
|
#[test]
|
|
846
|
-
fn
|
|
847
|
-
let upper = Arc::new(
|
|
1392
|
+
fn a_whiteout_lasts_as_long_as_the_upper() {
|
|
1393
|
+
let upper = Arc::new(Tmpfs::new());
|
|
848
1394
|
let (overlay, lower) = overlay_on(upper.clone());
|
|
849
1395
|
overlay
|
|
850
|
-
.
|
|
1396
|
+
.vfs_unlink("dir/file")
|
|
851
1397
|
.expect("the file must be removed");
|
|
852
|
-
let rebuilt =
|
|
853
|
-
.layer(upper, Access::ReadWrite)
|
|
854
|
-
.layer(lower, Access::Read);
|
|
1398
|
+
let rebuilt = at(over(lower, upper));
|
|
855
1399
|
assert_eq!(
|
|
856
|
-
rebuilt.
|
|
1400
|
+
rebuilt.vfs_lstat("dir/file"),
|
|
857
1401
|
Err(FilesystemError::NotFound),
|
|
858
|
-
"a lower file removed through an Overlay over a
|
|
1402
|
+
"a lower file removed through an Overlay over a Tmpfs must stay gone from a new Overlay over the same Layers"
|
|
859
1403
|
);
|
|
860
1404
|
}
|
|
861
1405
|
|
|
862
1406
|
// @behavior FS-018
|
|
863
1407
|
#[test]
|
|
864
1408
|
fn a_directory_made_again_where_one_was_removed_shows_nothing_from_below() {
|
|
865
|
-
let (overlay, _) = overlay_on(Arc::new(
|
|
1409
|
+
let (overlay, _) = overlay_on(Arc::new(Tmpfs::new()));
|
|
866
1410
|
overlay
|
|
867
|
-
.
|
|
1411
|
+
.vfs_unlink("dir/file")
|
|
868
1412
|
.expect("the file must be removed");
|
|
869
1413
|
overlay
|
|
870
|
-
.
|
|
1414
|
+
.vfs_rmdir("dir")
|
|
871
1415
|
.expect("the directory must be removed");
|
|
872
1416
|
overlay
|
|
873
|
-
.
|
|
1417
|
+
.vfs_mkdir("dir")
|
|
874
1418
|
.expect("the directory must be made");
|
|
875
1419
|
assert_eq!(
|
|
876
1420
|
names(&overlay, "dir"),
|
|
877
1421
|
Vec::<String>::new(),
|
|
878
|
-
"a directory made again through
|
|
1422
|
+
"a directory made again through vfs_mkdir must list nothing from the lower Layer"
|
|
879
1423
|
);
|
|
880
1424
|
}
|
|
881
1425
|
|
|
882
1426
|
// @behavior FS-019
|
|
883
1427
|
#[test]
|
|
884
1428
|
fn an_overlay_lists_none_of_its_markers() {
|
|
885
|
-
let (overlay, _) = overlay_on(Arc::new(
|
|
1429
|
+
let (overlay, _) = overlay_on(Arc::new(Tmpfs::new()));
|
|
886
1430
|
overwrite(&overlay, "dir/new", b"made");
|
|
887
1431
|
overlay
|
|
888
|
-
.
|
|
1432
|
+
.vfs_unlink("dir/file")
|
|
889
1433
|
.expect("the file must be removed");
|
|
890
1434
|
assert_eq!(
|
|
891
1435
|
names(&overlay, "dir"),
|
|
892
1436
|
["new"],
|
|
893
|
-
"a directory holding a whiteout listed through Overlay::read_dir must
|
|
1437
|
+
"a directory holding a whiteout listed through Overlay::read_dir must not list the \
|
|
1438
|
+
whited-out name"
|
|
894
1439
|
);
|
|
895
1440
|
}
|
|
896
1441
|
|
|
897
|
-
//
|
|
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
|
|
898
1472
|
#[test]
|
|
899
|
-
fn
|
|
900
|
-
let
|
|
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())));
|
|
901
1478
|
assert_eq!(
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
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"
|
|
905
1486
|
);
|
|
906
1487
|
}
|
|
907
1488
|
|
|
908
|
-
// A
|
|
909
|
-
//
|
|
1489
|
+
// A whiteout is a device rather than a name, so no name is the
|
|
1490
|
+
// Overlay's to reserve.
|
|
1491
|
+
// @behavior FS-020
|
|
910
1492
|
#[test]
|
|
911
|
-
fn
|
|
912
|
-
let (overlay, _) = overlay_on(Arc::new(
|
|
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");
|
|
913
1496
|
assert_eq!(
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
"a file named .
|
|
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"
|
|
917
1501
|
);
|
|
918
1502
|
}
|
|
919
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
|
+
|
|
920
1518
|
// @behavior FS-021
|
|
921
1519
|
#[test]
|
|
922
|
-
fn
|
|
923
|
-
let
|
|
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);
|
|
924
1527
|
assert_eq!(
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
"a file
|
|
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"
|
|
928
1532
|
);
|
|
929
1533
|
}
|
|
930
1534
|
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
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
|
+
}
|
|
936
1550
|
}
|
|
937
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
|
|
938
1555
|
#[test]
|
|
939
|
-
fn
|
|
940
|
-
let
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
.expect("the file must move");
|
|
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()));
|
|
945
1561
|
assert_eq!(
|
|
946
1562
|
(
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
(
|
|
952
|
-
b"lower".to_vec(),
|
|
953
|
-
Err(FilesystemError::NotFound),
|
|
954
|
-
b"lower".to_vec()
|
|
1563
|
+
overlay.vfs_mkdir("long"),
|
|
1564
|
+
on(&upper)
|
|
1565
|
+
.vfs_lstat("upper/long")
|
|
1566
|
+
.map(|found| found.file_type)
|
|
955
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"
|
|
956
1571
|
);
|
|
957
1572
|
}
|
|
958
1573
|
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
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
|
+
);
|
|
967
1591
|
}
|
|
968
1592
|
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
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
|
+
}
|
|
974
1615
|
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
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
|
+
}
|
|
982
1646
|
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
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
|
+
}
|
|
986
1667
|
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
*self.finished_inside.lock().unwrap() = Some(finished.load(Ordering::SeqCst) == 1);
|
|
1000
|
-
*self.other.lock().unwrap() = Some(handle);
|
|
1001
|
-
}
|
|
1002
|
-
self.memory.read_dir(path)
|
|
1003
|
-
}
|
|
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
|
+
}
|
|
1004
1680
|
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
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
|
+
}
|
|
1008
1694
|
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
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
|
+
}
|
|
1012
1806
|
|
|
1013
|
-
|
|
1014
|
-
|
|
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);
|
|
1015
1830
|
}
|
|
1831
|
+
}
|
|
1016
1832
|
|
|
1017
|
-
|
|
1018
|
-
|
|
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)
|
|
1019
1846
|
}
|
|
1020
1847
|
}
|
|
1021
1848
|
|
|
@@ -1025,113 +1852,112 @@ mod tests {
|
|
|
1025
1852
|
// @behavior FS-037
|
|
1026
1853
|
#[test]
|
|
1027
1854
|
fn a_change_through_an_overlay_waits_for_the_one_in_progress() {
|
|
1028
|
-
let
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
other: Mutex::new(None),
|
|
1033
|
-
});
|
|
1034
|
-
upper
|
|
1035
|
-
.memory
|
|
1036
|
-
.remove_file("dir/.keep")
|
|
1855
|
+
let tmpfs = tmpfs_with("dir/.keep", "");
|
|
1856
|
+
let interleave = Arc::new(Interleave::default());
|
|
1857
|
+
on(&tmpfs)
|
|
1858
|
+
.vfs_unlink("dir/.keep")
|
|
1037
1859
|
.expect("the seed must go");
|
|
1038
|
-
let
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
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
|
|
1042
1874
|
.other
|
|
1043
1875
|
.lock()
|
|
1044
1876
|
.unwrap()
|
|
1045
1877
|
.take()
|
|
1046
|
-
.expect("the other call must have started")
|
|
1047
|
-
|
|
1048
|
-
.expect("the other call must not panic");
|
|
1878
|
+
.expect("the other call must have started");
|
|
1879
|
+
crate::sched::testing::join("the other call", other);
|
|
1049
1880
|
assert_eq!(
|
|
1050
|
-
(
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
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"
|
|
1054
1889
|
);
|
|
1055
1890
|
}
|
|
1056
1891
|
|
|
1057
1892
|
// @behavior FS-038
|
|
1058
1893
|
#[test]
|
|
1059
|
-
fn
|
|
1894
|
+
fn a_change_through_an_overlay_from_inside_one_answers_a_deadlock() {
|
|
1895
|
+
type Answers = (bool, Result<(), FilesystemError>);
|
|
1896
|
+
|
|
1060
1897
|
struct Reenters {
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
answer: Mutex<Option<Result<Metadata, FilesystemError>>>,
|
|
1898
|
+
overlay: Mutex<Option<crate::vfs::Path>>,
|
|
1899
|
+
answer: Mutex<Option<Answers>>,
|
|
1064
1900
|
}
|
|
1065
1901
|
|
|
1066
|
-
impl
|
|
1067
|
-
|
|
1068
|
-
never_repeats_identities!();
|
|
1069
|
-
is_shared!();
|
|
1070
|
-
matches_names_exactly!();
|
|
1071
|
-
|
|
1072
|
-
fn open(
|
|
1902
|
+
impl Hook for Reenters {
|
|
1903
|
+
fn getattr(
|
|
1073
1904
|
&self,
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
) -> Result<
|
|
1077
|
-
self.
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
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
|
+
));
|
|
1083
1915
|
}
|
|
1084
|
-
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
fn read_dir(&self, path: &str) -> Result<Vec<DirEntry>, FilesystemError> {
|
|
1088
|
-
self.memory.read_dir(path)
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
fn create_dir(&self, path: &str) -> Result<(), FilesystemError> {
|
|
1092
|
-
self.memory.create_dir(path)
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1095
|
-
fn remove_dir(&self, path: &str) -> Result<(), FilesystemError> {
|
|
1096
|
-
self.memory.remove_dir(path)
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1099
|
-
fn remove_file(&self, path: &str) -> Result<(), FilesystemError> {
|
|
1100
|
-
self.memory.remove_file(path)
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
fn rename(&self, from: &str, to: &str, rename: Rename) -> Result<(), FilesystemError> {
|
|
1104
|
-
self.memory.rename(from, to, rename)
|
|
1916
|
+
inner.getattr(dentry)
|
|
1105
1917
|
}
|
|
1106
1918
|
}
|
|
1107
1919
|
|
|
1108
1920
|
let layer = Arc::new(Reenters {
|
|
1109
|
-
memory: [("note", "x")].into_iter().collect(),
|
|
1110
1921
|
overlay: Mutex::new(None),
|
|
1111
1922
|
answer: Mutex::new(None),
|
|
1112
1923
|
});
|
|
1113
|
-
let
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
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)
|
|
1117
1938
|
.expect("the outer call must answer");
|
|
1939
|
+
let answers = layer.answer.lock().unwrap().take();
|
|
1118
1940
|
assert_eq!(
|
|
1119
|
-
|
|
1120
|
-
Some(Err(FilesystemError::Deadlock)),
|
|
1121
|
-
"a
|
|
1122
|
-
must answer Deadlock rather than wait forever"
|
|
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"
|
|
1123
1945
|
);
|
|
1124
1946
|
}
|
|
1125
1947
|
|
|
1126
|
-
///
|
|
1127
|
-
///
|
|
1128
|
-
|
|
1129
|
-
|
|
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));
|
|
1130
1956
|
let upper = Arc::new(upper);
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1957
|
+
(
|
|
1958
|
+
at(over(tmpfs_with("dir/file", "lower"), upper.clone())),
|
|
1959
|
+
upper,
|
|
1960
|
+
)
|
|
1135
1961
|
}
|
|
1136
1962
|
|
|
1137
1963
|
// The upper copy is all the room there is, so the whiteout cannot be
|
|
@@ -1139,32 +1965,37 @@ mod tests {
|
|
|
1139
1965
|
// @behavior FS-040
|
|
1140
1966
|
#[test]
|
|
1141
1967
|
fn a_removal_that_cannot_leave_its_whiteout_removes_nothing() {
|
|
1142
|
-
let (overlay, _) = full_over_lower(&[("dir/file", "upper")],
|
|
1968
|
+
let (overlay, _) = full_over_lower(&[("dir/file", "upper")], 2);
|
|
1143
1969
|
assert_eq!(
|
|
1144
|
-
(overlay.
|
|
1970
|
+
(overlay.vfs_unlink("dir/file"), read(&overlay, "dir/file")),
|
|
1145
1971
|
(Err(FilesystemError::StorageFull), b"upper".to_vec()),
|
|
1146
|
-
"a file removed through
|
|
1972
|
+
"a file removed through vfs_unlink whose whiteout finds no room must stay as \
|
|
1147
1973
|
it was"
|
|
1148
1974
|
);
|
|
1149
1975
|
}
|
|
1150
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.
|
|
1151
1979
|
// @behavior FS-041
|
|
1152
1980
|
#[test]
|
|
1153
|
-
fn
|
|
1154
|
-
let
|
|
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");
|
|
1155
1991
|
assert_eq!(
|
|
1156
|
-
(
|
|
1157
|
-
overlay.create_dir("dir"),
|
|
1158
|
-
overlay.metadata("dir"),
|
|
1159
|
-
upper.metadata("dir")
|
|
1160
|
-
),
|
|
1992
|
+
(overlay.vfs_mkdir("gone"), overlay.vfs_lstat("gone")),
|
|
1161
1993
|
(
|
|
1162
1994
|
Err(FilesystemError::StorageFull),
|
|
1163
|
-
Err(FilesystemError::NotFound),
|
|
1164
1995
|
Err(FilesystemError::NotFound)
|
|
1165
1996
|
),
|
|
1166
|
-
"a directory made through
|
|
1167
|
-
|
|
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"
|
|
1168
1999
|
);
|
|
1169
2000
|
}
|
|
1170
2001
|
|
|
@@ -1173,11 +2004,11 @@ mod tests {
|
|
|
1173
2004
|
// @behavior FS-042
|
|
1174
2005
|
#[test]
|
|
1175
2006
|
fn a_rename_that_cannot_leave_its_whiteout_moves_nothing() {
|
|
1176
|
-
let (overlay, _) = full_over_lower(&[],
|
|
2007
|
+
let (overlay, _) = full_over_lower(&[], 2);
|
|
1177
2008
|
assert_eq!(
|
|
1178
2009
|
(
|
|
1179
|
-
overlay.rename("dir/file", "dir/moved"
|
|
1180
|
-
overlay.
|
|
2010
|
+
overlay.rename("dir/file", &overlay, "dir/moved"),
|
|
2011
|
+
overlay.vfs_lstat("dir/moved"),
|
|
1181
2012
|
read(&overlay, "dir/file")
|
|
1182
2013
|
),
|
|
1183
2014
|
(
|
|
@@ -1190,149 +2021,39 @@ mod tests {
|
|
|
1190
2021
|
);
|
|
1191
2022
|
}
|
|
1192
2023
|
|
|
1193
|
-
// The lower writable copy still holds the file the upper whiteout
|
|
1194
|
-
// hides, so it cannot be removed; removing the upper copy first would
|
|
1195
|
-
// show the file again.
|
|
1196
|
-
// @behavior FS-074
|
|
1197
|
-
#[test]
|
|
1198
|
-
fn a_directory_a_lower_writable_layer_still_fills_is_removed_behind_a_whiteout() {
|
|
1199
|
-
let lower = memory_with("dir/file", "lower");
|
|
1200
|
-
let overlay = Overlay::new()
|
|
1201
|
-
.layer(memory_with("dir/.wh.file", ""), Access::ReadWrite)
|
|
1202
|
-
.layer(lower.clone(), Access::ReadWrite);
|
|
1203
|
-
assert_eq!(
|
|
1204
|
-
(
|
|
1205
|
-
overlay.remove_dir("dir"),
|
|
1206
|
-
overlay.metadata("dir").err(),
|
|
1207
|
-
read(lower.as_ref(), "dir/file")
|
|
1208
|
-
),
|
|
1209
|
-
(Ok(()), Some(FilesystemError::NotFound), b"lower".to_vec()),
|
|
1210
|
-
"a directory the view shows empty, removed through Overlay::remove_dir while a lower \
|
|
1211
|
-
writable Layer still holds a whited-out file in it, must leave the view and stay \
|
|
1212
|
-
below unseen"
|
|
1213
|
-
);
|
|
1214
|
-
}
|
|
1215
|
-
|
|
1216
|
-
// The marker is the one entry past the room the upper Layer has, so
|
|
1217
|
-
// marking after the move would leave the directory moved.
|
|
1218
|
-
// @behavior FS-075
|
|
1219
|
-
#[test]
|
|
1220
|
-
fn a_rename_that_cannot_mark_its_directory_opaque_moves_nothing() {
|
|
1221
|
-
let upper = Arc::new(Memory::new().with_inodes(Some(2)));
|
|
1222
|
-
upper
|
|
1223
|
-
.create_dir("moving")
|
|
1224
|
-
.expect("the directory must be made");
|
|
1225
|
-
let lower = Arc::new(Memory::new());
|
|
1226
|
-
lower.create_dir("dir").expect("the directory must be made");
|
|
1227
|
-
let overlay = Overlay::new()
|
|
1228
|
-
.layer(upper.clone(), Access::ReadWrite)
|
|
1229
|
-
.layer(lower, Access::Read);
|
|
1230
|
-
assert_eq!(
|
|
1231
|
-
(
|
|
1232
|
-
overlay.rename("moving", "dir", Rename::Directory),
|
|
1233
|
-
upper.metadata("moving").map(|metadata| metadata.file_type),
|
|
1234
|
-
upper.metadata("dir").err()
|
|
1235
|
-
),
|
|
1236
|
-
(
|
|
1237
|
-
Err(FilesystemError::StorageFull),
|
|
1238
|
-
Ok(FileType::Directory),
|
|
1239
|
-
Some(FilesystemError::NotFound)
|
|
1240
|
-
),
|
|
1241
|
-
"a directory renamed through Overlay::rename over one a lower Layer holds, with no \
|
|
1242
|
-
room for its opaque marker, must stay where it was"
|
|
1243
|
-
);
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
// The writable Layer sits between two read-only ones, so a rename
|
|
1247
|
-
// landing in it beneath the top Layer's `to` would vanish from view.
|
|
1248
|
-
// @behavior FS-076
|
|
1249
|
-
#[test]
|
|
1250
|
-
fn a_rename_a_read_only_layer_above_would_cover_is_refused() {
|
|
1251
|
-
let overlay = Overlay::new()
|
|
1252
|
-
.layer(memory_with("to", "top"), Access::Read)
|
|
1253
|
-
.layer(Arc::new(Memory::new()), Access::ReadWrite)
|
|
1254
|
-
.layer(memory_with("from", "moved"), Access::Read);
|
|
1255
|
-
assert_eq!(
|
|
1256
|
-
(
|
|
1257
|
-
overlay.rename("from", "to", Rename::Any),
|
|
1258
|
-
read(&overlay, "from"),
|
|
1259
|
-
read(&overlay, "to")
|
|
1260
|
-
),
|
|
1261
|
-
(
|
|
1262
|
-
Err(FilesystemError::ReadOnlyFilesystem),
|
|
1263
|
-
b"moved".to_vec(),
|
|
1264
|
-
b"top".to_vec()
|
|
1265
|
-
),
|
|
1266
|
-
"a file renamed through Overlay::rename onto a name a read-only Layer above the \
|
|
1267
|
-
writable one holds must be refused as read-only and move nothing"
|
|
1268
|
-
);
|
|
1269
|
-
}
|
|
1270
|
-
|
|
1271
|
-
// @behavior FS-077
|
|
1272
|
-
#[test]
|
|
1273
|
-
fn a_name_a_read_only_layer_above_hides_is_not_made() {
|
|
1274
|
-
let upper = Arc::new(Memory::new());
|
|
1275
|
-
let overlay = Overlay::new()
|
|
1276
|
-
.layer(
|
|
1277
|
-
Arc::new(
|
|
1278
|
-
[(".wh.file", ""), (".wh.dir", "")]
|
|
1279
|
-
.into_iter()
|
|
1280
|
-
.collect::<Memory>(),
|
|
1281
|
-
),
|
|
1282
|
-
Access::Read,
|
|
1283
|
-
)
|
|
1284
|
-
.layer(upper.clone(), Access::ReadWrite);
|
|
1285
|
-
assert_eq!(
|
|
1286
|
-
(
|
|
1287
|
-
write(&overlay, "file", b"new"),
|
|
1288
|
-
overlay.create_dir("dir"),
|
|
1289
|
-
upper.metadata("file").err(),
|
|
1290
|
-
upper.metadata("dir").err()
|
|
1291
|
-
),
|
|
1292
|
-
(
|
|
1293
|
-
Err(FilesystemError::ReadOnlyFilesystem),
|
|
1294
|
-
Err(FilesystemError::ReadOnlyFilesystem),
|
|
1295
|
-
Some(FilesystemError::NotFound),
|
|
1296
|
-
Some(FilesystemError::NotFound)
|
|
1297
|
-
),
|
|
1298
|
-
"a file and a directory made through an Overlay where a read-only Layer above the \
|
|
1299
|
-
writable one holds their whiteouts must be refused as read-only and not made"
|
|
1300
|
-
);
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
2024
|
// @behavior FS-043
|
|
1304
2025
|
#[test]
|
|
1305
2026
|
fn a_directory_a_lower_layer_holds_does_not_move_through_an_overlay() {
|
|
1306
|
-
let (overlay, _) = overlay_on(Arc::new(
|
|
2027
|
+
let (overlay, _) = overlay_on(Arc::new(Tmpfs::new()));
|
|
1307
2028
|
assert_eq!(
|
|
1308
2029
|
(
|
|
1309
|
-
overlay.rename("dir", "moved"
|
|
1310
|
-
overlay.
|
|
2030
|
+
overlay.rename("dir", &overlay, "moved"),
|
|
2031
|
+
overlay.vfs_lstat("moved")
|
|
1311
2032
|
),
|
|
1312
2033
|
(
|
|
1313
2034
|
Err(FilesystemError::CrossesDevices),
|
|
1314
2035
|
Err(FilesystemError::NotFound)
|
|
1315
2036
|
),
|
|
1316
|
-
"a directory a
|
|
2037
|
+
"a directory a lower Layer holds renamed through Overlay::rename must be refused \
|
|
1317
2038
|
as crossing devices, as overlayfs refuses it"
|
|
1318
2039
|
);
|
|
1319
2040
|
}
|
|
1320
2041
|
|
|
1321
|
-
// The directory shows only from the
|
|
2042
|
+
// The directory shows only from the lower Layer, so only a check of
|
|
1322
2043
|
// the whole view finds it in the way.
|
|
1323
2044
|
// @behavior FS-044
|
|
1324
2045
|
#[test]
|
|
1325
2046
|
fn a_rename_onto_what_an_overlay_shows_is_checked_against_the_view() {
|
|
1326
|
-
let upper = Arc::new(
|
|
2047
|
+
let upper = Arc::new(Tmpfs::new());
|
|
1327
2048
|
let (overlay, _) = overlay_on(upper.clone());
|
|
1328
2049
|
overwrite(&overlay, "note", b"x");
|
|
1329
2050
|
overlay
|
|
1330
|
-
.
|
|
2051
|
+
.vfs_mkdir("empty")
|
|
1331
2052
|
.expect("the directory must be made");
|
|
1332
2053
|
assert_eq!(
|
|
1333
2054
|
(
|
|
1334
|
-
overlay.rename("note", "dir"
|
|
1335
|
-
overlay.rename("empty", "dir"
|
|
2055
|
+
overlay.rename("note", &overlay, "dir"),
|
|
2056
|
+
overlay.rename("empty", &overlay, "dir"),
|
|
1336
2057
|
read(&overlay, "dir/file")
|
|
1337
2058
|
),
|
|
1338
2059
|
(
|
|
@@ -1340,7 +2061,7 @@ mod tests {
|
|
|
1340
2061
|
Err(FilesystemError::DirectoryNotEmpty),
|
|
1341
2062
|
b"lower".to_vec()
|
|
1342
2063
|
),
|
|
1343
|
-
"a rename through Overlay::rename onto a directory only a
|
|
2064
|
+
"a rename through Overlay::rename onto a directory only a lower Layer shows must \
|
|
1344
2065
|
be refused as rename(2) refuses it, leaving that directory as it was"
|
|
1345
2066
|
);
|
|
1346
2067
|
}
|
|
@@ -1350,39 +2071,245 @@ mod tests {
|
|
|
1350
2071
|
// @behavior FS-045
|
|
1351
2072
|
#[test]
|
|
1352
2073
|
fn a_directory_moved_over_a_lower_one_is_marked_opaque() {
|
|
1353
|
-
let upper = Arc::new(
|
|
1354
|
-
let lower: Arc<
|
|
1355
|
-
lower
|
|
1356
|
-
|
|
1357
|
-
.
|
|
1358
|
-
|
|
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()));
|
|
1359
2080
|
overlay
|
|
1360
|
-
.
|
|
2081
|
+
.vfs_mkdir("made")
|
|
1361
2082
|
.expect("the directory must be made");
|
|
1362
2083
|
overlay
|
|
1363
|
-
.rename("made", "dir"
|
|
2084
|
+
.rename("made", &overlay, "dir")
|
|
1364
2085
|
.expect("the directory must move over the empty one");
|
|
2086
|
+
overwrite(&on(&lower), "dir/later", b"lower");
|
|
1365
2087
|
assert_eq!(
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
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"
|
|
1372
2299
|
);
|
|
1373
2300
|
}
|
|
1374
2301
|
|
|
1375
|
-
// The
|
|
2302
|
+
// The upper has room for the copy's entry but for less than
|
|
1376
2303
|
// the lower file's bytes, so the copy fails part way.
|
|
1377
2304
|
// @behavior FS-046
|
|
1378
2305
|
#[test]
|
|
1379
2306
|
fn a_copy_up_that_fails_part_way_leaves_nothing_behind() {
|
|
1380
|
-
let upper = Arc::new(
|
|
2307
|
+
let upper = Arc::new(Tmpfs::new().with_size(Some(2)).with_nr_inodes(Some(8)));
|
|
1381
2308
|
let (overlay, _) = overlay_on(upper.clone());
|
|
1382
2309
|
assert_eq!(
|
|
1383
2310
|
(
|
|
1384
|
-
|
|
1385
|
-
upper.
|
|
2311
|
+
write_over(&overlay, "dir/file", b"x"),
|
|
2312
|
+
on(&upper).vfs_lstat("upper/dir/file"),
|
|
1386
2313
|
read(&overlay, "dir/file")
|
|
1387
2314
|
),
|
|
1388
2315
|
(
|
|
@@ -1395,374 +2322,894 @@ mod tests {
|
|
|
1395
2322
|
);
|
|
1396
2323
|
}
|
|
1397
2324
|
|
|
1398
|
-
/// A
|
|
1399
|
-
/// `
|
|
1400
|
-
struct
|
|
1401
|
-
|
|
1402
|
-
|
|
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
|
+
}
|
|
1403
2340
|
}
|
|
1404
2341
|
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
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,
|
|
1408
2347
|
}
|
|
1409
2348
|
|
|
1410
|
-
impl
|
|
1411
|
-
fn
|
|
1412
|
-
self
|
|
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)
|
|
1413
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
|
+
}
|
|
1414
2369
|
|
|
1415
|
-
|
|
1416
|
-
|
|
2370
|
+
impl FoldsCase {
|
|
2371
|
+
fn new(tmpfs: Tmpfs) -> Self {
|
|
2372
|
+
Self { root: at(tmpfs) }
|
|
1417
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
|
+
}
|
|
1418
2380
|
|
|
1419
|
-
|
|
1420
|
-
|
|
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))
|
|
1421
2395
|
}
|
|
2396
|
+
}
|
|
1422
2397
|
|
|
1423
|
-
|
|
1424
|
-
|
|
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(())
|
|
1425
2402
|
}
|
|
2403
|
+
}
|
|
1426
2404
|
|
|
1427
|
-
|
|
1428
|
-
|
|
2405
|
+
impl InodeOperations for FoldsCase {
|
|
2406
|
+
fn getattr(&self, dentry: &Dentry) -> Result<Kstat, FilesystemError> {
|
|
2407
|
+
self.root.vfs_lstat(&folded_path_of(dentry))
|
|
1429
2408
|
}
|
|
1430
2409
|
|
|
1431
|
-
fn
|
|
1432
|
-
|
|
1433
|
-
|
|
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),
|
|
1434
2421
|
}
|
|
1435
|
-
self.file.close()
|
|
1436
2422
|
}
|
|
1437
|
-
}
|
|
1438
|
-
|
|
1439
|
-
impl Filesystem for Trickles {
|
|
1440
|
-
holds_no_links!();
|
|
1441
|
-
never_repeats_identities!();
|
|
1442
|
-
is_shared!();
|
|
1443
|
-
matches_names_exactly!();
|
|
1444
2423
|
|
|
1445
2424
|
fn open(
|
|
1446
2425
|
&self,
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
) -> Result<Box<dyn
|
|
1450
|
-
Ok(
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
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)
|
|
1454
2433
|
}
|
|
1455
2434
|
|
|
1456
|
-
fn
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
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
|
+
})
|
|
1466
2450
|
}
|
|
1467
2451
|
|
|
1468
|
-
fn
|
|
1469
|
-
self.
|
|
2452
|
+
fn mkdir(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
|
|
2453
|
+
self.root.vfs_mkdir(&folded_path_of(dentry))
|
|
1470
2454
|
}
|
|
1471
2455
|
|
|
1472
|
-
fn
|
|
1473
|
-
self.
|
|
2456
|
+
fn rmdir(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
|
|
2457
|
+
self.root.vfs_rmdir(&folded_path_of(dentry))
|
|
1474
2458
|
}
|
|
1475
2459
|
|
|
1476
|
-
fn
|
|
1477
|
-
self.
|
|
2460
|
+
fn unlink(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
|
|
2461
|
+
self.root.vfs_unlink(&folded_path_of(dentry))
|
|
1478
2462
|
}
|
|
1479
|
-
}
|
|
1480
|
-
|
|
1481
|
-
/// A Memory refusing to move or remove a directory.
|
|
1482
|
-
struct HoldsDirectories(Memory);
|
|
1483
|
-
|
|
1484
|
-
impl Filesystem for HoldsDirectories {
|
|
1485
|
-
holds_no_links!();
|
|
1486
|
-
never_repeats_identities!();
|
|
1487
|
-
is_shared!();
|
|
1488
|
-
matches_names_exactly!();
|
|
1489
2463
|
|
|
1490
|
-
fn
|
|
2464
|
+
fn rename(
|
|
1491
2465
|
&self,
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
self.0.metadata(path)
|
|
1500
|
-
}
|
|
1501
|
-
|
|
1502
|
-
fn read_dir(&self, path: &str) -> Result<Vec<DirEntry>, FilesystemError> {
|
|
1503
|
-
self.0.read_dir(path)
|
|
1504
|
-
}
|
|
1505
|
-
|
|
1506
|
-
fn create_dir(&self, path: &str) -> Result<(), FilesystemError> {
|
|
1507
|
-
self.0.create_dir(path)
|
|
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))
|
|
1508
2473
|
}
|
|
2474
|
+
}
|
|
1509
2475
|
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
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
|
+
}
|
|
1513
2480
|
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
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
|
+
}
|
|
1517
2513
|
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
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
|
+
);
|
|
1521
2532
|
}
|
|
1522
2533
|
|
|
1523
|
-
|
|
1524
|
-
|
|
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
|
+
}
|
|
1525
2560
|
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
matches_names_exactly!();
|
|
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>>>);
|
|
1531
2565
|
|
|
2566
|
+
impl Hook for RunsAfterOpen {
|
|
1532
2567
|
fn open(
|
|
1533
2568
|
&self,
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
}
|
|
1543
|
-
|
|
1544
|
-
fn read_dir(&self, path: &str) -> Result<Vec<DirEntry>, FilesystemError> {
|
|
1545
|
-
self.0.read_dir(path)
|
|
1546
|
-
}
|
|
1547
|
-
|
|
1548
|
-
fn create_dir(&self, path: &str) -> Result<(), FilesystemError> {
|
|
1549
|
-
self.0.create_dir(path)
|
|
1550
|
-
}
|
|
1551
|
-
|
|
1552
|
-
fn remove_dir(&self, path: &str) -> Result<(), FilesystemError> {
|
|
1553
|
-
self.0.remove_dir(path)
|
|
1554
|
-
}
|
|
1555
|
-
|
|
1556
|
-
fn remove_file(&self, path: &str) -> Result<(), FilesystemError> {
|
|
1557
|
-
match path.rsplit('/').next() {
|
|
1558
|
-
Some(name) if name.starts_with(".wh.") => Err(FilesystemError::PermissionDenied),
|
|
1559
|
-
_ => self.0.remove_file(path),
|
|
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));
|
|
1560
2577
|
}
|
|
2578
|
+
opened
|
|
1561
2579
|
}
|
|
2580
|
+
}
|
|
1562
2581
|
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
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
|
+
);
|
|
1566
2601
|
}
|
|
1567
2602
|
|
|
1568
|
-
|
|
1569
|
-
|
|
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
|
+
}
|
|
1570
2653
|
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
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
|
+
}
|
|
1576
2702
|
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
options: &OpenOptions,
|
|
1581
|
-
) -> Result<Box<dyn File>, FilesystemError> {
|
|
1582
|
-
self.0.open(path, options)
|
|
1583
|
-
}
|
|
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);
|
|
1584
2706
|
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
));
|
|
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));
|
|
1594
2715
|
}
|
|
1595
|
-
|
|
1596
|
-
}
|
|
1597
|
-
|
|
1598
|
-
fn read_dir(&self, path: &str) -> Result<Vec<DirEntry>, FilesystemError> {
|
|
1599
|
-
self.0.read_dir(path)
|
|
1600
|
-
}
|
|
1601
|
-
|
|
1602
|
-
fn create_dir(&self, path: &str) -> Result<(), FilesystemError> {
|
|
1603
|
-
self.0.create_dir(path)
|
|
1604
|
-
}
|
|
1605
|
-
|
|
1606
|
-
fn remove_dir(&self, path: &str) -> Result<(), FilesystemError> {
|
|
1607
|
-
self.0.remove_dir(path)
|
|
1608
|
-
}
|
|
1609
|
-
|
|
1610
|
-
fn remove_file(&self, path: &str) -> Result<(), FilesystemError> {
|
|
1611
|
-
self.0.remove_file(path)
|
|
1612
|
-
}
|
|
1613
|
-
|
|
1614
|
-
fn rename(&self, from: &str, to: &str, rename: Rename) -> Result<(), FilesystemError> {
|
|
1615
|
-
self.0.rename(from, to, rename)
|
|
2716
|
+
inner.getattr(dentry)
|
|
1616
2717
|
}
|
|
1617
2718
|
}
|
|
1618
2719
|
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
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
|
+
);
|
|
1624
2770
|
}
|
|
1625
2771
|
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
)
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
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
|
+
);
|
|
1643
2836
|
}
|
|
1644
|
-
self.memory.metadata(path)
|
|
1645
2837
|
}
|
|
1646
2838
|
|
|
1647
|
-
|
|
1648
|
-
|
|
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
|
+
}
|
|
1649
2856
|
}
|
|
2857
|
+
}
|
|
1650
2858
|
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
}
|
|
2859
|
+
/// A lower Layer whose one entry, `pipe`, is a FIFO, counting each open.
|
|
2860
|
+
struct HoldsFifo(Arc<AtomicUsize>);
|
|
1654
2861
|
|
|
1655
|
-
|
|
1656
|
-
|
|
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(())
|
|
1657
2866
|
}
|
|
2867
|
+
}
|
|
1658
2868
|
|
|
1659
|
-
|
|
1660
|
-
|
|
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
|
+
})
|
|
1661
2875
|
}
|
|
1662
2876
|
|
|
1663
|
-
fn
|
|
1664
|
-
|
|
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
|
+
})
|
|
1665
2885
|
}
|
|
1666
|
-
}
|
|
1667
|
-
|
|
1668
|
-
/// A Memory storing names in lower case and reaching each by any
|
|
1669
|
-
/// case, as a host that folds case does.
|
|
1670
|
-
struct FoldsCase(Memory);
|
|
1671
|
-
|
|
1672
|
-
impl Filesystem for FoldsCase {
|
|
1673
|
-
holds_no_links!();
|
|
1674
|
-
never_repeats_identities!();
|
|
1675
|
-
is_shared!();
|
|
1676
2886
|
|
|
1677
2887
|
fn open(
|
|
1678
2888
|
&self,
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
) -> Result<Box<dyn
|
|
1682
|
-
self.0.
|
|
2889
|
+
_dentry: &Dentry,
|
|
2890
|
+
_how: &OpenHow,
|
|
2891
|
+
) -> Result<Box<dyn FileOperations>, FilesystemError> {
|
|
2892
|
+
self.0.fetch_add(1, Ordering::SeqCst);
|
|
2893
|
+
Err(FilesystemError::Interrupted)
|
|
1683
2894
|
}
|
|
2895
|
+
}
|
|
1684
2896
|
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
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
|
+
}
|
|
1688
2931
|
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
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
|
+
}
|
|
1692
2960
|
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
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
|
+
}
|
|
1696
2977
|
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
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
|
+
}
|
|
1700
3013
|
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
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
|
+
}
|
|
1704
3065
|
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
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
|
+
}
|
|
1708
3084
|
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
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
|
+
);
|
|
1713
3105
|
}
|
|
1714
3106
|
|
|
1715
3107
|
// @behavior FS-083
|
|
1716
3108
|
#[test]
|
|
1717
3109
|
fn an_overlay_spells_a_path_as_the_layer_showing_it_stores_it() {
|
|
1718
|
-
let overlay =
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
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");
|
|
1724
3119
|
assert_eq!(
|
|
1725
3120
|
(
|
|
1726
|
-
overlay.
|
|
1727
|
-
|
|
3121
|
+
overlay.lookup("PRIVATE"),
|
|
3122
|
+
private.lookup("SECRET"),
|
|
3123
|
+
private.lookup("New")
|
|
1728
3124
|
),
|
|
1729
3125
|
(
|
|
1730
|
-
Ok("private
|
|
1731
|
-
Ok("
|
|
3126
|
+
Ok("private".to_string()),
|
|
3127
|
+
Ok("secret".to_string()),
|
|
3128
|
+
Ok("New".to_string())
|
|
1732
3129
|
),
|
|
1733
|
-
"a
|
|
1734
|
-
must come back as that Layer stores
|
|
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"
|
|
1735
3132
|
);
|
|
1736
3133
|
}
|
|
1737
3134
|
|
|
1738
|
-
// A
|
|
1739
|
-
//
|
|
1740
|
-
//
|
|
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
|
|
1741
3139
|
#[test]
|
|
1742
|
-
fn
|
|
1743
|
-
let
|
|
1744
|
-
let overlay =
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
"
|
|
1750
|
-
|
|
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"
|
|
1751
3202
|
);
|
|
1752
3203
|
}
|
|
1753
3204
|
|
|
1754
3205
|
// @behavior FS-080
|
|
1755
3206
|
#[test]
|
|
1756
3207
|
fn a_file_gone_during_a_rename_answers_not_found() {
|
|
1757
|
-
let
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
};
|
|
1761
|
-
let overlay = Overlay::new()
|
|
1762
|
-
.layer(Arc::new(Memory::new()), Access::ReadWrite)
|
|
1763
|
-
.layer(Arc::new(lower), Access::Read);
|
|
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())));
|
|
1764
3211
|
assert_eq!(
|
|
1765
|
-
overlay.rename("file", "moved"
|
|
3212
|
+
overlay.rename("file", &overlay, "moved"),
|
|
1766
3213
|
Err(FilesystemError::NotFound),
|
|
1767
3214
|
"a file renamed through Overlay::rename that its Layer loses during the call must \
|
|
1768
3215
|
answer not found"
|
|
@@ -1774,148 +3221,134 @@ mod tests {
|
|
|
1774
3221
|
// @behavior FS-078
|
|
1775
3222
|
#[test]
|
|
1776
3223
|
fn a_directory_over_a_lower_file_on_its_way_reads_as_a_directory() {
|
|
1777
|
-
let overlay =
|
|
1778
|
-
|
|
1779
|
-
|
|
3224
|
+
let overlay = at(over(
|
|
3225
|
+
tmpfs_with("dir", "lower"),
|
|
3226
|
+
tmpfs_with("upper/dir/sub/file", "upper"),
|
|
3227
|
+
));
|
|
1780
3228
|
assert_eq!(
|
|
1781
3229
|
(
|
|
1782
|
-
overlay
|
|
3230
|
+
overlay
|
|
3231
|
+
.vfs_lstat("dir/sub")
|
|
3232
|
+
.map(|metadata| metadata.file_type),
|
|
1783
3233
|
read(&overlay, "dir/sub/file")
|
|
1784
3234
|
),
|
|
1785
|
-
(Ok(FileType::
|
|
1786
|
-
"a directory looked up through
|
|
3235
|
+
(Ok(FileType::Dir), b"upper".to_vec()),
|
|
3236
|
+
"a directory looked up through vfs_lstat above a lower Layer holding a file on \
|
|
1787
3237
|
its path must read as a directory, as its files open"
|
|
1788
3238
|
);
|
|
1789
3239
|
}
|
|
1790
3240
|
|
|
1791
|
-
//
|
|
1792
|
-
// cleared the whiteout from `dir`, so only putting it back keeps the
|
|
1793
|
-
// read-only Layer's `dir/file` hidden.
|
|
1794
|
-
// @behavior FS-063
|
|
3241
|
+
// @behavior FS-101
|
|
1795
3242
|
#[test]
|
|
1796
|
-
fn
|
|
1797
|
-
let (overlay, _) = overlay_on(Arc::new(
|
|
1798
|
-
overlay
|
|
1799
|
-
|
|
1800
|
-
.expect("the file must be removed");
|
|
1801
|
-
overlay
|
|
1802
|
-
.create_dir("new")
|
|
1803
|
-
.expect("the directory must be made");
|
|
1804
|
-
let refused = (
|
|
1805
|
-
overlay.rename("new", "dir", Rename::Directory),
|
|
1806
|
-
overlay.remove_dir("dir"),
|
|
1807
|
-
);
|
|
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");
|
|
1808
3247
|
assert_eq!(
|
|
1809
|
-
(
|
|
1810
|
-
(
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
Err(FilesystemError::PermissionDenied)
|
|
1814
|
-
),
|
|
1815
|
-
Some(FilesystemError::NotFound)
|
|
1816
|
-
),
|
|
1817
|
-
"a rename onto and a removal of a directory whose writable copy holds only a whiteout, \
|
|
1818
|
-
refused by the Layer through an Overlay, must leave the whited-out file hidden"
|
|
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"
|
|
1819
3252
|
);
|
|
1820
3253
|
}
|
|
1821
3254
|
|
|
1822
|
-
|
|
1823
|
-
Arc::new(Trickles {
|
|
1824
|
-
memory: Memory::new(),
|
|
1825
|
-
refuses_close,
|
|
1826
|
-
})
|
|
1827
|
-
}
|
|
1828
|
-
|
|
1829
|
-
// @behavior FS-054
|
|
3255
|
+
// @behavior FS-102
|
|
1830
3256
|
#[test]
|
|
1831
|
-
fn
|
|
1832
|
-
let
|
|
1833
|
-
let
|
|
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");
|
|
1834
3260
|
overlay
|
|
1835
|
-
.
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
write: true,
|
|
1839
|
-
..OpenOptions::default()
|
|
1840
|
-
},
|
|
1841
|
-
)
|
|
1842
|
-
.expect("the open must copy the lower file up");
|
|
3261
|
+
.rename("dir/file", &overlay, "dir/moved")
|
|
3262
|
+
.expect("the rename must land");
|
|
3263
|
+
overwrite(&overlay, "dir/moved", b"moved");
|
|
1843
3264
|
assert_eq!(
|
|
1844
|
-
|
|
1845
|
-
b"
|
|
1846
|
-
"a
|
|
1847
|
-
|
|
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"
|
|
1848
3269
|
);
|
|
1849
3270
|
}
|
|
1850
3271
|
|
|
1851
|
-
// @behavior FS-
|
|
3272
|
+
// @behavior FS-103
|
|
1852
3273
|
#[test]
|
|
1853
|
-
fn
|
|
1854
|
-
let
|
|
1855
|
-
let
|
|
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");
|
|
1856
3285
|
assert_eq!(
|
|
1857
|
-
(
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
),
|
|
1862
|
-
(
|
|
1863
|
-
Err(FilesystemError::PermissionDenied),
|
|
1864
|
-
Err(FilesystemError::NotFound),
|
|
1865
|
-
b"lower".to_vec()
|
|
1866
|
-
),
|
|
1867
|
-
"a write through Overlay::open whose copy up the writable Layer refuses to close must \
|
|
1868
|
-
leave no copy hiding the lower file"
|
|
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"
|
|
1869
3290
|
);
|
|
1870
3291
|
}
|
|
1871
3292
|
|
|
1872
|
-
///
|
|
1873
|
-
|
|
3293
|
+
/// A Tmpfs whose first open sleeps a while, flagging that it began.
|
|
3294
|
+
#[derive(Default)]
|
|
3295
|
+
struct SleepsOnOpen {
|
|
3296
|
+
opening: AtomicBool,
|
|
3297
|
+
}
|
|
1874
3298
|
|
|
1875
|
-
impl
|
|
1876
|
-
fn
|
|
1877
|
-
self
|
|
1878
|
-
|
|
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)
|
|
1879
3310
|
}
|
|
1880
3311
|
}
|
|
1881
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.
|
|
1882
3315
|
// @behavior FS-039
|
|
1883
3316
|
#[test]
|
|
1884
|
-
fn
|
|
1885
|
-
let counts =
|
|
1886
|
-
let
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
let
|
|
1890
|
-
let
|
|
1891
|
-
let
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
}
|
|
1895
|
-
|
|
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"));
|
|
1896
3330
|
assert_eq!(
|
|
1897
3331
|
(
|
|
1898
|
-
|
|
1899
|
-
|
|
3332
|
+
crate::sched::testing::join("the first copy up", copying),
|
|
3333
|
+
waited,
|
|
3334
|
+
counts.load(Ordering::SeqCst) > 0
|
|
1900
3335
|
),
|
|
1901
|
-
(Ok(
|
|
1902
|
-
"a
|
|
1903
|
-
Blocking given
|
|
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"
|
|
1904
3339
|
);
|
|
1905
3340
|
}
|
|
1906
3341
|
|
|
1907
|
-
// Copying up makes the directory in the
|
|
1908
|
-
//
|
|
3342
|
+
// Copying up makes the directory in the upper, whose own identity
|
|
3343
|
+
// differs, so only reporting the lower Layer's keeps it.
|
|
1909
3344
|
// @behavior FS-072
|
|
1910
3345
|
#[test]
|
|
1911
3346
|
fn an_overlay_keeps_a_directorys_identity_when_a_write_copies_it_up() {
|
|
1912
|
-
let lower: Arc<dyn
|
|
1913
|
-
Arc::new([("dir/a.txt", "a")].into_iter().collect::<
|
|
1914
|
-
let overlay =
|
|
1915
|
-
|
|
1916
|
-
.
|
|
1917
|
-
let identity = |overlay: &Overlay| {
|
|
1918
|
-
let metadata = overlay.metadata("dir").expect("the directory must stand");
|
|
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");
|
|
1919
3352
|
(metadata.dev, metadata.ino)
|
|
1920
3353
|
};
|
|
1921
3354
|
let before = identity(&overlay);
|
|
@@ -1923,33 +3356,114 @@ mod tests {
|
|
|
1923
3356
|
assert_eq!(
|
|
1924
3357
|
(identity(&overlay) == before, before.1 != 0),
|
|
1925
3358
|
(true, true),
|
|
1926
|
-
"a directory of a
|
|
1927
|
-
keep the identity
|
|
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"
|
|
1928
3387
|
);
|
|
1929
3388
|
}
|
|
1930
3389
|
|
|
1931
3390
|
// @behavior FS-073
|
|
1932
3391
|
#[test]
|
|
1933
3392
|
fn an_overlay_names_a_directory_alike_whether_a_link_at_its_end_is_followed() {
|
|
1934
|
-
let
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
)
|
|
1940
|
-
};
|
|
1941
|
-
let overlay = Overlay::new()
|
|
1942
|
-
.layer(layer("upper"), Access::ReadWrite)
|
|
1943
|
-
.layer(layer("lower"), Access::Read);
|
|
1944
|
-
let identity = |metadata: Result<Metadata, FilesystemError>| {
|
|
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>| {
|
|
1945
3398
|
let metadata = metadata.expect("the directory must stand");
|
|
1946
3399
|
(metadata.dev, metadata.ino)
|
|
1947
3400
|
};
|
|
1948
3401
|
assert_eq!(
|
|
1949
|
-
identity(overlay.
|
|
1950
|
-
identity(overlay.
|
|
1951
|
-
"a directory two Layers hold, looked up through
|
|
1952
|
-
report the identity
|
|
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"
|
|
1953
3406
|
);
|
|
1954
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
|
+
}
|
|
1955
3469
|
}
|