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
|
@@ -0,0 +1,611 @@
|
|
|
1
|
+
//! Compares the VFS and the built-in Filesystems with a real Linux: one seeded
|
|
2
|
+
//! sequence of calls goes through kobako and through a real tmpfs, overlayfs, or
|
|
3
|
+
//! directory, and each answer and the tree left behind are compared. This is
|
|
4
|
+
//! evidence for a change: the VFS's baseline is Linux VFS's shape, and a
|
|
5
|
+
//! Filesystem's is the specification.
|
|
6
|
+
//!
|
|
7
|
+
//! `linux-baseline-expected.txt` names the differences known so far, which a line
|
|
8
|
+
//! there does not settle. A new one fails, and so does one no sequence reaches any
|
|
9
|
+
//! more. `concurrent` holds calls
|
|
10
|
+
//! racing on one instance, and `permissions` what a Policy and another user are
|
|
11
|
+
//! refused. `rake crates:linux_baseline` runs this as root in a container, since
|
|
12
|
+
//! overlayfs needs a mount.
|
|
13
|
+
|
|
14
|
+
#![cfg(target_os = "linux")]
|
|
15
|
+
|
|
16
|
+
mod calls;
|
|
17
|
+
mod concurrent;
|
|
18
|
+
mod kobako;
|
|
19
|
+
mod linux;
|
|
20
|
+
mod permissions;
|
|
21
|
+
|
|
22
|
+
use std::collections::{BTreeMap, BTreeSet};
|
|
23
|
+
use std::fs;
|
|
24
|
+
use std::os::unix::fs::symlink;
|
|
25
|
+
use std::path::Path;
|
|
26
|
+
use std::sync::{Arc, Mutex, PoisonError};
|
|
27
|
+
use std::thread::sleep;
|
|
28
|
+
use std::time::Duration;
|
|
29
|
+
|
|
30
|
+
use kobako_runtime::filesystem::{Bind, Filesystem, Overlay, Tmpfs};
|
|
31
|
+
use kobako_runtime::mount::Access;
|
|
32
|
+
|
|
33
|
+
use calls::{Call, Rng};
|
|
34
|
+
use kobako::Kobako;
|
|
35
|
+
use linux::{mount, on_tmpfs, snapshot_into, umount, Linux};
|
|
36
|
+
|
|
37
|
+
/// Every entry beneath a root, and what it shows.
|
|
38
|
+
pub type Snapshot = BTreeMap<String, String>;
|
|
39
|
+
|
|
40
|
+
/// When each entry beneath a root was last modified and changed.
|
|
41
|
+
pub type Times<T> = BTreeMap<String, (T, T)>;
|
|
42
|
+
|
|
43
|
+
/// Where each setup keeps its directories, a tmpfs in every container.
|
|
44
|
+
const BASE: &str = "/dev/shm/kobako-linux-baseline";
|
|
45
|
+
|
|
46
|
+
const EXPECTED: &str = include_str!("linux-baseline-expected.txt");
|
|
47
|
+
|
|
48
|
+
/// What an Overlay leaves out, turned off whatever this kernel defaults to, so
|
|
49
|
+
/// its answers are the ones kobako aligns with.
|
|
50
|
+
const OVERLAY_FEATURES_OFF: &str = "redirect_dir=off,metacopy=off,index=off,xino=off";
|
|
51
|
+
|
|
52
|
+
/// One setup runs at a time, as two sharing its directories would collide.
|
|
53
|
+
static SERIAL: Mutex<()> = Mutex::new(());
|
|
54
|
+
|
|
55
|
+
/// Longer than Linux's coarse clock ticks, so a change moves a time on both.
|
|
56
|
+
const CLOCK_TICK: Duration = Duration::from_millis(12);
|
|
57
|
+
|
|
58
|
+
/// What a stat shows, the same on both sides. A directory merged from Layers
|
|
59
|
+
/// reports no size both sides could agree on, and a link's size is where it
|
|
60
|
+
/// points, which a Filesystem may report its own way.
|
|
61
|
+
pub fn describe(kind: &str, len: u64, nlink: u64, merged: bool) -> String {
|
|
62
|
+
match (kind, merged) {
|
|
63
|
+
("dir", true) => format!("dir nlink={nlink}"),
|
|
64
|
+
("lnk", _) => "lnk".to_string(),
|
|
65
|
+
_ => format!("{kind} len={len} nlink={nlink}"),
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
#[derive(Clone, Copy, PartialEq, Eq)]
|
|
70
|
+
enum Setup {
|
|
71
|
+
Tmpfs,
|
|
72
|
+
Overlay,
|
|
73
|
+
OverlayNoUpper,
|
|
74
|
+
Bind,
|
|
75
|
+
ReadOnlyBind,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
impl Setup {
|
|
79
|
+
fn name(self) -> &'static str {
|
|
80
|
+
match self {
|
|
81
|
+
Self::Tmpfs => "tmpfs",
|
|
82
|
+
Self::Overlay => "overlay",
|
|
83
|
+
Self::OverlayNoUpper => "overlay-no-upper",
|
|
84
|
+
Self::Bind => "bind",
|
|
85
|
+
Self::ReadOnlyBind => "read-only-bind",
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
fn dir(self, name: &str) -> String {
|
|
90
|
+
format!("{BASE}/{}/{name}", self.name())
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
fn merged(self) -> bool {
|
|
94
|
+
matches!(self, Self::Overlay | Self::OverlayNoUpper)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
fn changes(self) -> bool {
|
|
98
|
+
matches!(self, Self::Tmpfs | Self::Overlay | Self::Bind)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/// What a sequence walks. A Tmpfs holds no links.
|
|
102
|
+
fn paths(self) -> Vec<String> {
|
|
103
|
+
let mut paths: Vec<String> = [
|
|
104
|
+
"", "a", "b", "c", "d", "e", "m", "d/f", "d/g", "d/e", "d/e/h", "m/s", "m/x", "a/",
|
|
105
|
+
"b/", "d/", "d/.", "d/..", "d/../a", ".", "a/b", "b/c", "d/e/..",
|
|
106
|
+
]
|
|
107
|
+
.map(str::to_string)
|
|
108
|
+
.to_vec();
|
|
109
|
+
paths.push("x/".repeat(2048));
|
|
110
|
+
paths.push("N".repeat(256));
|
|
111
|
+
paths.push(format!("d/{}", "N".repeat(256)));
|
|
112
|
+
paths.push("M".repeat(255));
|
|
113
|
+
paths.extend(
|
|
114
|
+
[
|
|
115
|
+
"d/f/", "d/f/.", "d/f/..", "a/..", "./a", "d//f", "d/./f", "./", "d/e/../f", "d/e/",
|
|
116
|
+
]
|
|
117
|
+
.map(str::to_string),
|
|
118
|
+
);
|
|
119
|
+
if self != Self::Tmpfs {
|
|
120
|
+
paths.extend(["h1", "h2", "l", "l/", "n", "n/f", "n/", "g", "g/"].map(str::to_string));
|
|
121
|
+
paths.extend(["l/x", "n/..", "n/../a", "n/e", "g/x", "n/e/.."].map(str::to_string));
|
|
122
|
+
}
|
|
123
|
+
paths
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/// Lay out what a run starts from. What only one side keeps beneath the
|
|
127
|
+
/// other's view is laid out once, as nothing a run does reaches a lower Layer.
|
|
128
|
+
fn prepare(self) {
|
|
129
|
+
let _ = fs::remove_dir_all(format!("{BASE}/{}", self.name()));
|
|
130
|
+
fs::create_dir_all(format!("{BASE}/{}", self.name())).expect("the setup's directory");
|
|
131
|
+
if self.merged() {
|
|
132
|
+
seed_directory(&self.dir("kobako-lower"));
|
|
133
|
+
seed_directory(&self.dir("linux-lower"));
|
|
134
|
+
}
|
|
135
|
+
if self == Self::OverlayNoUpper {
|
|
136
|
+
for side in ["kobako-second", "linux-second"] {
|
|
137
|
+
let dir = self.dir(side);
|
|
138
|
+
fs::create_dir_all(format!("{dir}/m/y")).expect("the Layer's directory");
|
|
139
|
+
fs::create_dir_all(format!("{dir}/z")).expect("the Layer's directory");
|
|
140
|
+
fs::write(format!("{dir}/m/x"), "hidden").expect("the Layer's file");
|
|
141
|
+
fs::write(format!("{dir}/b"), "low").expect("the Layer's file");
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
fn start(self, settle: bool) -> (Kobako, Linux) {
|
|
147
|
+
match self {
|
|
148
|
+
Self::Tmpfs => {
|
|
149
|
+
let filesystem =
|
|
150
|
+
Filesystem::new(Arc::new(Tmpfs::new())).expect("the instance must stand");
|
|
151
|
+
let mut kobako = Kobako::new(filesystem, Access::ReadWrite, false);
|
|
152
|
+
let root = self.dir("linux");
|
|
153
|
+
let _ = fs::remove_dir_all(&root);
|
|
154
|
+
fs::create_dir_all(&root).expect("the tmpfs directory");
|
|
155
|
+
let mut linux = Linux::new(&root, false);
|
|
156
|
+
for call in tmpfs_seed() {
|
|
157
|
+
if settle {
|
|
158
|
+
sleep(CLOCK_TICK);
|
|
159
|
+
}
|
|
160
|
+
kobako.run(&call);
|
|
161
|
+
linux.run(&call);
|
|
162
|
+
}
|
|
163
|
+
(kobako, linux)
|
|
164
|
+
}
|
|
165
|
+
Self::Bind | Self::ReadOnlyBind => {
|
|
166
|
+
let (kobako_dir, linux_dir) = (self.dir("kobako"), self.dir("linux"));
|
|
167
|
+
let linux_root = self.dir("linux-mount");
|
|
168
|
+
if self == Self::ReadOnlyBind {
|
|
169
|
+
umount(&linux_root);
|
|
170
|
+
}
|
|
171
|
+
seed_directory(&kobako_dir);
|
|
172
|
+
seed_directory(&linux_dir);
|
|
173
|
+
let filesystem =
|
|
174
|
+
Filesystem::new(Arc::new(bind(&kobako_dir))).expect("the instance must stand");
|
|
175
|
+
let access = match self {
|
|
176
|
+
Self::ReadOnlyBind => Access::Read,
|
|
177
|
+
_ => Access::ReadWrite,
|
|
178
|
+
};
|
|
179
|
+
let kobako = Kobako::new(filesystem, access, false);
|
|
180
|
+
if self == Self::Bind {
|
|
181
|
+
return (kobako, Linux::new(&linux_dir, false));
|
|
182
|
+
}
|
|
183
|
+
fs::create_dir_all(&linux_root).expect("the mount point");
|
|
184
|
+
mount(&linux_dir, &linux_root, None, libc::MS_BIND, None);
|
|
185
|
+
mount(
|
|
186
|
+
&linux_dir,
|
|
187
|
+
&linux_root,
|
|
188
|
+
None,
|
|
189
|
+
libc::MS_BIND | libc::MS_REMOUNT | libc::MS_RDONLY,
|
|
190
|
+
None,
|
|
191
|
+
);
|
|
192
|
+
(kobako, Linux::new(&linux_root, false))
|
|
193
|
+
}
|
|
194
|
+
Self::Overlay | Self::OverlayNoUpper => self.start_overlay(),
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
fn start_overlay(self) -> (Kobako, Linux) {
|
|
199
|
+
let root = self.dir("linux-merged");
|
|
200
|
+
umount(&root);
|
|
201
|
+
fs::create_dir_all(&root).expect("the mount point");
|
|
202
|
+
let lower = Filesystem::new(Arc::new(bind(&self.dir("kobako-lower"))))
|
|
203
|
+
.expect("the instance must stand");
|
|
204
|
+
let (overlay, data) = if self == Self::Overlay {
|
|
205
|
+
let (upper, work) = (self.dir("linux-upper"), self.dir("linux-work"));
|
|
206
|
+
for dir in [&upper, &work] {
|
|
207
|
+
let _ = fs::remove_dir_all(dir);
|
|
208
|
+
fs::create_dir_all(dir).expect("the overlayfs directory");
|
|
209
|
+
}
|
|
210
|
+
let upper_tmpfs =
|
|
211
|
+
Filesystem::new(Arc::new(Tmpfs::new())).expect("the instance must stand");
|
|
212
|
+
(
|
|
213
|
+
Overlay::new(vec![lower], Some(upper_tmpfs)),
|
|
214
|
+
format!(
|
|
215
|
+
"lowerdir={},upperdir={upper},workdir={work},{OVERLAY_FEATURES_OFF}",
|
|
216
|
+
self.dir("linux-lower")
|
|
217
|
+
),
|
|
218
|
+
)
|
|
219
|
+
} else {
|
|
220
|
+
let second = Filesystem::new(Arc::new(bind(&self.dir("kobako-second"))))
|
|
221
|
+
.expect("the instance must stand");
|
|
222
|
+
(
|
|
223
|
+
Overlay::new(vec![lower, second], None),
|
|
224
|
+
format!(
|
|
225
|
+
"lowerdir={}:{}",
|
|
226
|
+
self.dir("linux-lower"),
|
|
227
|
+
self.dir("linux-second")
|
|
228
|
+
),
|
|
229
|
+
)
|
|
230
|
+
};
|
|
231
|
+
let overlay = overlay.unwrap_or_else(|error| panic!("the Overlay must stack: {error:?}"));
|
|
232
|
+
mount("overlay", &root, Some("overlay"), 0, Some(&data));
|
|
233
|
+
let filesystem = Filesystem::new(Arc::new(overlay)).expect("the instance must stand");
|
|
234
|
+
(
|
|
235
|
+
Kobako::new(filesystem, Access::ReadWrite, true),
|
|
236
|
+
Linux::new(&root, true),
|
|
237
|
+
)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/// Let go of what a finished run left mounted, and hold a lower Layer to
|
|
241
|
+
/// what it started as, as neither side's run may reach one.
|
|
242
|
+
fn finish(self) {
|
|
243
|
+
match self {
|
|
244
|
+
Self::Overlay | Self::OverlayNoUpper => {
|
|
245
|
+
umount(&self.dir("linux-merged"));
|
|
246
|
+
let snapshot = |side: &str| {
|
|
247
|
+
let mut out = Snapshot::new();
|
|
248
|
+
snapshot_into(Path::new(&self.dir(side)), "", false, &mut out);
|
|
249
|
+
out
|
|
250
|
+
};
|
|
251
|
+
assert_eq!(
|
|
252
|
+
snapshot("kobako-lower"),
|
|
253
|
+
snapshot("linux-lower"),
|
|
254
|
+
"a sequence through an Overlay must leave its lower Layer as overlayfs leaves \
|
|
255
|
+
its own"
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
Self::ReadOnlyBind => umount(&self.dir("linux-mount")),
|
|
259
|
+
_ => {}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
fn bind(dir: &str) -> Bind {
|
|
265
|
+
Bind::new(dir).unwrap_or_else(|error| panic!("{dir} must bind: {error}"))
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/// A directory holding what a walk meets: files, hard links, directories, and
|
|
269
|
+
/// links to a file, to a directory, and to nothing.
|
|
270
|
+
fn seed_directory(dir: &str) {
|
|
271
|
+
let _ = fs::remove_dir_all(dir);
|
|
272
|
+
for sub in ["d/e", "m/s"] {
|
|
273
|
+
fs::create_dir_all(format!("{dir}/{sub}")).expect("the seeded directory");
|
|
274
|
+
}
|
|
275
|
+
for (file, content) in [
|
|
276
|
+
("a", "hello"),
|
|
277
|
+
("d/f", "inner"),
|
|
278
|
+
("m/x", "x"),
|
|
279
|
+
("h1", "hard"),
|
|
280
|
+
] {
|
|
281
|
+
fs::write(format!("{dir}/{file}"), content).expect("the seeded file");
|
|
282
|
+
}
|
|
283
|
+
fs::hard_link(format!("{dir}/h1"), format!("{dir}/h2")).expect("the seeded hard link");
|
|
284
|
+
for (target, name) in [("a", "l"), ("d", "n"), ("nowhere", "g")] {
|
|
285
|
+
symlink(target, format!("{dir}/{name}")).expect("the seeded link");
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/// The same tree on a Tmpfs, which takes no seeded directory of the host's.
|
|
290
|
+
fn tmpfs_seed() -> Vec<Call> {
|
|
291
|
+
let file = |path: &str, slot| {
|
|
292
|
+
Call::Open(calls::Open {
|
|
293
|
+
path: path.to_string(),
|
|
294
|
+
read: true,
|
|
295
|
+
write: true,
|
|
296
|
+
create: true,
|
|
297
|
+
slot,
|
|
298
|
+
..calls::Open::default()
|
|
299
|
+
})
|
|
300
|
+
};
|
|
301
|
+
let mkdir = |path: &str| Call::Mkdir(None, path.to_string());
|
|
302
|
+
vec![
|
|
303
|
+
file("a", 0),
|
|
304
|
+
Call::Write(0, 4),
|
|
305
|
+
Call::Close(0),
|
|
306
|
+
mkdir("d"),
|
|
307
|
+
mkdir("d/e"),
|
|
308
|
+
file("d/f", 0),
|
|
309
|
+
Call::Write(0, 2),
|
|
310
|
+
Call::Close(0),
|
|
311
|
+
mkdir("m"),
|
|
312
|
+
mkdir("m/s"),
|
|
313
|
+
]
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/// A way one setup differs, by the shortest sequence that reaches it.
|
|
317
|
+
struct Difference {
|
|
318
|
+
calls: Vec<Call>,
|
|
319
|
+
detail: String,
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
fn class(call: &Call, kobako: &str, linux: &str) -> String {
|
|
323
|
+
let brief = |answer: &str| {
|
|
324
|
+
let answer = answer.split(':').next().unwrap_or(answer);
|
|
325
|
+
answer.split(" len=").next().unwrap_or(answer).to_string()
|
|
326
|
+
};
|
|
327
|
+
// What a move starts from decides what a difference waives: a link of a lower
|
|
328
|
+
// Layer is kept apart from any other entry refusing to move.
|
|
329
|
+
let subject = match call {
|
|
330
|
+
Call::Rename(_, from, _) => format!(" from={}", from.split('/').next().unwrap_or(from)),
|
|
331
|
+
_ => String::new(),
|
|
332
|
+
};
|
|
333
|
+
format!(
|
|
334
|
+
"{}{subject} kobako={} linux={}",
|
|
335
|
+
call.kind(),
|
|
336
|
+
brief(kobako),
|
|
337
|
+
brief(linux)
|
|
338
|
+
)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
fn note(found: &mut BTreeMap<String, Difference>, class: String, calls: &[Call], detail: String) {
|
|
342
|
+
let difference = found.entry(class).or_insert_with(|| Difference {
|
|
343
|
+
calls: calls.to_vec(),
|
|
344
|
+
detail: detail.clone(),
|
|
345
|
+
});
|
|
346
|
+
if calls.len() < difference.calls.len() {
|
|
347
|
+
difference.calls = calls.to_vec();
|
|
348
|
+
difference.detail = detail;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/// Fail unless what `found` holds is what the expected file names for `name`.
|
|
353
|
+
fn assert_named(name: &str, found: &BTreeMap<String, Difference>) {
|
|
354
|
+
let expected: BTreeSet<String> = EXPECTED
|
|
355
|
+
.lines()
|
|
356
|
+
.filter_map(|line| line.split('#').next())
|
|
357
|
+
.filter_map(|line| line.trim().strip_prefix(name))
|
|
358
|
+
.filter_map(|line| line.strip_prefix(' '))
|
|
359
|
+
.map(|line| line.trim().to_string())
|
|
360
|
+
.collect();
|
|
361
|
+
let new: Vec<String> = found
|
|
362
|
+
.iter()
|
|
363
|
+
.filter(|(class, _)| !expected.contains(*class))
|
|
364
|
+
.map(|(class, difference)| {
|
|
365
|
+
let calls: Vec<String> = difference
|
|
366
|
+
.calls
|
|
367
|
+
.iter()
|
|
368
|
+
.map(|call| format!(" {call:?}"))
|
|
369
|
+
.collect();
|
|
370
|
+
format!(
|
|
371
|
+
"{name} {class}\n {}\n{}",
|
|
372
|
+
difference.detail,
|
|
373
|
+
calls.join("\n")
|
|
374
|
+
)
|
|
375
|
+
})
|
|
376
|
+
.collect();
|
|
377
|
+
let gone: Vec<&String> = expected
|
|
378
|
+
.iter()
|
|
379
|
+
.filter(|class| !found.contains_key(*class))
|
|
380
|
+
.collect();
|
|
381
|
+
assert!(
|
|
382
|
+
new.is_empty() && gone.is_empty(),
|
|
383
|
+
"{name} must answer as Linux does, apart from what linux-baseline-expected.txt names.\nNew \
|
|
384
|
+
differences:\n{}\nNamed but not reached:\n{gone:?}",
|
|
385
|
+
new.join("\n")
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/// Fail unless what `found` holds is what the expected file names for `setup`,
|
|
390
|
+
/// and unless every kind of call this setup can answer ever succeeded.
|
|
391
|
+
fn assert_holds(setup: Setup, found: &BTreeMap<String, Difference>, succeeded: &BTreeSet<String>) {
|
|
392
|
+
assert_named(setup.name(), found);
|
|
393
|
+
let mut owed = vec![
|
|
394
|
+
"Open", "Close", "Read", "Pread", "Seek", "Fstat", "Fsync", "Readdir", "Stat",
|
|
395
|
+
];
|
|
396
|
+
if setup.changes() {
|
|
397
|
+
owed.extend([
|
|
398
|
+
"Write", "Pwrite", "Truncate", "Mkdir", "Rmdir", "Unlink", "Rename",
|
|
399
|
+
]);
|
|
400
|
+
}
|
|
401
|
+
let missing: Vec<_> = owed
|
|
402
|
+
.into_iter()
|
|
403
|
+
.filter(|kind| !succeeded.contains(*kind))
|
|
404
|
+
.collect();
|
|
405
|
+
assert!(
|
|
406
|
+
missing.is_empty(),
|
|
407
|
+
"{}: no sequence ever made {missing:?} succeed, so agreeing on them shows nothing",
|
|
408
|
+
setup.name()
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/// Hold `setup` to Linux over `runs` sequences of at most `longest` calls.
|
|
413
|
+
fn holds(setup: Setup, runs: u64, longest: u64) {
|
|
414
|
+
let _serial = SERIAL.lock().unwrap_or_else(PoisonError::into_inner);
|
|
415
|
+
assert!(
|
|
416
|
+
on_tmpfs("/dev/shm"),
|
|
417
|
+
"/dev/shm must be a tmpfs, whose answers a Tmpfs is held to; run this through `rake \
|
|
418
|
+
crates:linux_baseline`"
|
|
419
|
+
);
|
|
420
|
+
setup.prepare();
|
|
421
|
+
let paths = setup.paths();
|
|
422
|
+
let mut found = BTreeMap::new();
|
|
423
|
+
let mut succeeded = BTreeSet::new();
|
|
424
|
+
for seed in 1..=runs {
|
|
425
|
+
let mut rng = Rng::new(seed);
|
|
426
|
+
let length = 1 + rng.random_below(longest);
|
|
427
|
+
let sequence: Vec<Call> = (0..length).map(|_| rng.call(&paths)).collect();
|
|
428
|
+
let (mut kobako, mut linux) = setup.start(false);
|
|
429
|
+
let mut agreed = true;
|
|
430
|
+
for (at, call) in sequence.iter().enumerate() {
|
|
431
|
+
let (theirs, ours) = (linux.run(call), kobako.run(call));
|
|
432
|
+
if ours != theirs {
|
|
433
|
+
let detail = format!("kobako={ours} linux={theirs}");
|
|
434
|
+
note(
|
|
435
|
+
&mut found,
|
|
436
|
+
class(call, &ours, &theirs),
|
|
437
|
+
&sequence[..=at],
|
|
438
|
+
detail,
|
|
439
|
+
);
|
|
440
|
+
agreed = false;
|
|
441
|
+
break;
|
|
442
|
+
}
|
|
443
|
+
if ours.starts_with("ok") || ours.contains("len=") || ours == "dir" {
|
|
444
|
+
succeeded.insert(call.kind().to_string());
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
if agreed {
|
|
448
|
+
let (ours, theirs) = (kobako.snapshot(), linux.snapshot());
|
|
449
|
+
if let Some(path) = ours
|
|
450
|
+
.keys()
|
|
451
|
+
.chain(theirs.keys())
|
|
452
|
+
.find(|path| ours.get(*path) != theirs.get(*path))
|
|
453
|
+
{
|
|
454
|
+
let detail = format!(
|
|
455
|
+
"{path}: kobako={:?} linux={:?}",
|
|
456
|
+
ours.get(path),
|
|
457
|
+
theirs.get(path)
|
|
458
|
+
);
|
|
459
|
+
note(&mut found, "Tree".to_string(), &sequence, detail);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
drop((kobako, linux));
|
|
463
|
+
setup.finish();
|
|
464
|
+
}
|
|
465
|
+
assert_holds(setup, &found, &succeeded);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/// Which of each entry's two times `after` shows moved since `before`.
|
|
469
|
+
fn times_moved<T: PartialEq>(before: &Times<T>, after: &Times<T>) -> BTreeSet<String> {
|
|
470
|
+
let mut moved = BTreeSet::new();
|
|
471
|
+
for (path, (modified, changed)) in after {
|
|
472
|
+
if let Some((was_modified, was_changed)) = before.get(path) {
|
|
473
|
+
if modified != was_modified {
|
|
474
|
+
moved.insert(format!("{path} modified"));
|
|
475
|
+
}
|
|
476
|
+
if changed != was_changed {
|
|
477
|
+
moved.insert(format!("{path} changed"));
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
moved
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/// Hold `setup` to which times each call moves, over `runs` sequences, waiting
|
|
485
|
+
/// out the clock around each call. Where the two disagree on an answer, the
|
|
486
|
+
/// run that holds answers reports it.
|
|
487
|
+
fn moves_the_same_times(setup: Setup, runs: u64, longest: u64) {
|
|
488
|
+
let _serial = SERIAL.lock().unwrap_or_else(PoisonError::into_inner);
|
|
489
|
+
setup.prepare();
|
|
490
|
+
let paths = setup.paths();
|
|
491
|
+
let mut calls_moving_a_time = 0;
|
|
492
|
+
for seed in 1..=runs {
|
|
493
|
+
let mut rng = Rng::new(seed);
|
|
494
|
+
let length = 1 + rng.random_below(longest);
|
|
495
|
+
let sequence: Vec<Call> = (0..length).map(|_| rng.call(&paths)).collect();
|
|
496
|
+
let (mut kobako, mut linux) = setup.start(true);
|
|
497
|
+
for (at, call) in sequence.iter().enumerate() {
|
|
498
|
+
let (kobako_before, linux_before) = (kobako.times(), linux.times());
|
|
499
|
+
sleep(CLOCK_TICK);
|
|
500
|
+
if kobako.run(call) != linux.run(call) {
|
|
501
|
+
break;
|
|
502
|
+
}
|
|
503
|
+
let ours = times_moved(&kobako_before, &kobako.times());
|
|
504
|
+
let theirs = times_moved(&linux_before, &linux.times());
|
|
505
|
+
assert_eq!(
|
|
506
|
+
ours,
|
|
507
|
+
theirs,
|
|
508
|
+
"{} through vfs must move the times Linux moves, after {:#?}",
|
|
509
|
+
setup.name(),
|
|
510
|
+
&sequence[..=at]
|
|
511
|
+
);
|
|
512
|
+
calls_moving_a_time += usize::from(!ours.is_empty());
|
|
513
|
+
}
|
|
514
|
+
drop((kobako, linux));
|
|
515
|
+
setup.finish();
|
|
516
|
+
}
|
|
517
|
+
assert!(
|
|
518
|
+
calls_moving_a_time >= 40,
|
|
519
|
+
"{}: only {calls_moving_a_time} calls moved a time, too few for agreeing to show anything",
|
|
520
|
+
setup.name()
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/// Hold `setup` to Linux over `rounds` of racing sequences of at most `longest`
|
|
525
|
+
/// calls each.
|
|
526
|
+
fn holds_racing(setup: Setup, rounds: u64, longest: u64) {
|
|
527
|
+
let _serial = SERIAL.lock().unwrap_or_else(PoisonError::into_inner);
|
|
528
|
+
setup.prepare();
|
|
529
|
+
let mut found = concurrent::Found::default();
|
|
530
|
+
for seed in 1..=rounds {
|
|
531
|
+
let (kobako, linux) = setup.start(false);
|
|
532
|
+
let kobako: Vec<Kobako> = (0..concurrent::THREADS).map(|_| kobako.another()).collect();
|
|
533
|
+
let linux: Vec<Linux> = (0..concurrent::THREADS).map(|_| linux.another()).collect();
|
|
534
|
+
drop(concurrent::round(seed, longest, kobako, linux, &mut found));
|
|
535
|
+
setup.finish();
|
|
536
|
+
}
|
|
537
|
+
assert!(
|
|
538
|
+
found.broken_by_linux.is_empty(),
|
|
539
|
+
"{}: Linux itself broke a rule racing calls are held to, so the rule is wrong: {:#?}",
|
|
540
|
+
setup.name(),
|
|
541
|
+
found.broken_by_linux
|
|
542
|
+
);
|
|
543
|
+
let answers: Vec<_> = found.kobako.difference(&found.linux).collect();
|
|
544
|
+
assert!(
|
|
545
|
+
answers.is_empty() && found.broken_by_kobako.is_empty(),
|
|
546
|
+
"{} racing on one instance must answer only what Linux answers and leave what any \
|
|
547
|
+
interleaving leaves.\nAnswers Linux never gave: {answers:#?}\nBroken at rest: {:#?}",
|
|
548
|
+
setup.name(),
|
|
549
|
+
found.broken_by_kobako
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
#[test]
|
|
554
|
+
#[ignore = "needs root and a mount namespace; run by rake crates:linux_baseline"]
|
|
555
|
+
fn a_tmpfs_answers_as_a_tmpfs_of_linux() {
|
|
556
|
+
holds(Setup::Tmpfs, 40_000, 30);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
#[test]
|
|
560
|
+
#[ignore = "needs root and a mount namespace; run by rake crates:linux_baseline"]
|
|
561
|
+
fn an_overlay_answers_as_overlayfs() {
|
|
562
|
+
holds(Setup::Overlay, 8_000, 30);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
#[test]
|
|
566
|
+
#[ignore = "needs root and a mount namespace; run by rake crates:linux_baseline"]
|
|
567
|
+
fn an_overlay_without_an_upper_answers_as_overlayfs() {
|
|
568
|
+
holds(Setup::OverlayNoUpper, 8_000, 20);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
#[test]
|
|
572
|
+
#[ignore = "needs root and a mount namespace; run by rake crates:linux_baseline"]
|
|
573
|
+
fn a_bind_answers_as_the_directory_it_binds() {
|
|
574
|
+
holds(Setup::Bind, 15_000, 30);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
#[test]
|
|
578
|
+
#[ignore = "needs root and a mount namespace; run by rake crates:linux_baseline"]
|
|
579
|
+
fn a_read_only_bind_answers_as_a_read_only_mount() {
|
|
580
|
+
holds(Setup::ReadOnlyBind, 6_000, 15);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
#[test]
|
|
584
|
+
#[ignore = "needs root and a mount namespace; run by rake crates:linux_baseline"]
|
|
585
|
+
fn a_tmpfs_moves_the_times_a_tmpfs_of_linux_moves() {
|
|
586
|
+
moves_the_same_times(Setup::Tmpfs, 600, 6);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
#[test]
|
|
590
|
+
#[ignore = "needs root and a mount namespace; run by rake crates:linux_baseline"]
|
|
591
|
+
fn an_overlay_moves_the_times_overlayfs_moves() {
|
|
592
|
+
moves_the_same_times(Setup::Overlay, 600, 6);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
#[test]
|
|
596
|
+
#[ignore = "needs root and a mount namespace; run by rake crates:linux_baseline"]
|
|
597
|
+
fn calls_racing_on_a_tmpfs_answer_as_linux() {
|
|
598
|
+
holds_racing(Setup::Tmpfs, 30_000, 40);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
#[test]
|
|
602
|
+
#[ignore = "needs root and a mount namespace; run by rake crates:linux_baseline"]
|
|
603
|
+
fn calls_racing_on_an_overlay_answer_as_overlayfs() {
|
|
604
|
+
holds_racing(Setup::Overlay, 30_000, 40);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
#[test]
|
|
608
|
+
#[ignore = "needs root and a mount namespace; run by rake crates:linux_baseline"]
|
|
609
|
+
fn calls_racing_on_a_bind_answer_as_linux() {
|
|
610
|
+
holds_racing(Setup::Bind, 30_000, 40);
|
|
611
|
+
}
|