kobako 0.28.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +377 -0
- data/Cargo.lock +181 -154
- data/README.md +99 -459
- data/ROADMAP.md +7 -1
- data/SECURITY.md +1 -1
- data/crates/kobako-runtime/CHANGELOG.md +354 -0
- data/crates/kobako-runtime/Cargo.toml +38 -7
- data/crates/kobako-runtime/README.md +17 -8
- data/crates/kobako-runtime/src/dispatch.rs +9 -1
- data/crates/kobako-runtime/src/endpoint/buffer.rs +192 -0
- data/crates/kobako-runtime/src/endpoint/capture.rs +115 -0
- data/crates/kobako-runtime/src/endpoint/dispatch.rs +253 -0
- data/crates/kobako-runtime/src/endpoint/invoke.rs +194 -0
- data/crates/kobako-runtime/src/endpoint/memory_cap.rs +238 -0
- data/crates/kobako-runtime/src/endpoint.rs +166 -0
- data/crates/kobako-runtime/src/filesystem/bind/fid.rs +82 -0
- data/crates/kobako-runtime/src/filesystem/bind/lookup.rs +236 -0
- data/crates/kobako-runtime/src/filesystem/bind/walk.rs +249 -0
- data/crates/kobako-runtime/src/filesystem/bind.rs +2665 -0
- data/crates/kobako-runtime/src/filesystem/overlay/file.rs +89 -0
- data/crates/kobako-runtime/src/filesystem/overlay/inode.rs +106 -0
- data/crates/kobako-runtime/src/filesystem/overlay/path.rs +285 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/copy_up.rs +135 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/dir.rs +263 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/namei.rs +135 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/readdir.rs +57 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack.rs +301 -0
- data/crates/kobako-runtime/src/filesystem/overlay.rs +3469 -0
- data/crates/kobako-runtime/src/filesystem/path.rs +21 -0
- data/crates/kobako-runtime/src/filesystem/pipe.rs +221 -0
- data/crates/kobako-runtime/src/filesystem/rdir.rs +101 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs/tmpfile.rs +32 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs/whiteout.rs +85 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs/xattr.rs +22 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs.rs +1985 -0
- data/crates/kobako-runtime/src/filesystem/wrap.rs +224 -0
- data/crates/kobako-runtime/src/filesystem.rs +26 -0
- data/crates/kobako-runtime/src/grant.rs +20 -0
- data/crates/kobako-runtime/src/hashtable.rs +44 -0
- data/crates/kobako-runtime/src/lib.rs +16 -1
- data/crates/kobako-runtime/src/locking/seqlock.rs +66 -0
- data/crates/kobako-runtime/src/locking.rs +218 -0
- data/crates/kobako-runtime/src/mount.rs +105 -0
- data/crates/kobako-runtime/src/profile.rs +140 -4
- data/crates/kobako-runtime/src/runtime.rs +12 -0
- data/crates/kobako-runtime/src/sched.rs +210 -0
- data/crates/kobako-runtime/src/security.rs +696 -0
- data/crates/kobako-runtime/src/snapshot.rs +20 -5
- data/crates/kobako-runtime/src/vfs/attr.rs +162 -0
- data/crates/kobako-runtime/src/vfs/cred.rs +334 -0
- data/crates/kobako-runtime/src/vfs/dentry.rs +1412 -0
- data/crates/kobako-runtime/src/vfs/errno.rs +153 -0
- data/crates/kobako-runtime/src/vfs/file.rs +1915 -0
- data/crates/kobako-runtime/src/vfs/inode.rs +429 -0
- data/crates/kobako-runtime/src/vfs/kern_path.rs +217 -0
- data/crates/kobako-runtime/src/vfs/mount.rs +59 -0
- data/crates/kobako-runtime/src/vfs/namei.rs +5708 -0
- data/crates/kobako-runtime/src/vfs/openat2.rs +116 -0
- data/crates/kobako-runtime/src/vfs/path.rs +64 -0
- data/crates/kobako-runtime/src/vfs/readdir.rs +163 -0
- data/crates/kobako-runtime/src/vfs/stat.rs +157 -0
- data/crates/kobako-runtime/src/vfs/super_block.rs +122 -0
- data/crates/kobako-runtime/src/vfs/utimes.rs +289 -0
- data/crates/kobako-runtime/src/vfs.rs +60 -0
- data/crates/kobako-runtime/src/yielder.rs +11 -6
- data/crates/kobako-runtime/tests/linux_baseline/calls.rs +171 -0
- data/crates/kobako-runtime/tests/linux_baseline/concurrent.rs +276 -0
- data/crates/kobako-runtime/tests/linux_baseline/kobako.rs +323 -0
- data/crates/kobako-runtime/tests/linux_baseline/linux-baseline-expected.txt +16 -0
- data/crates/kobako-runtime/tests/linux_baseline/linux.rs +449 -0
- data/crates/kobako-runtime/tests/linux_baseline/main.rs +611 -0
- data/crates/kobako-runtime/tests/linux_baseline/permissions.rs +236 -0
- data/crates/kobako-transport/CHANGELOG.md +23 -0
- data/crates/kobako-transport/Cargo.toml +3 -3
- data/crates/kobako-transport/README.md +6 -5
- data/crates/kobako-transport/src/abi.rs +1 -1
- data/crates/kobako-transport/src/envelope/bytes.rs +33 -25
- data/crates/kobako-transport/src/envelope/call.rs +5 -5
- data/crates/kobako-transport/src/envelope/compile_reply.rs +2 -2
- data/crates/kobako-transport/src/envelope/error_record.rs +3 -3
- data/crates/kobako-transport/src/envelope/fault.rs +4 -3
- data/crates/kobako-transport/src/envelope/outcome.rs +3 -3
- data/crates/kobako-transport/src/envelope/preamble.rs +24 -24
- data/crates/kobako-transport/src/envelope/reply.rs +34 -16
- data/crates/kobako-transport/src/envelope/request.rs +19 -9
- data/crates/kobako-transport/src/envelope/run.rs +1 -1
- data/crates/kobako-transport/src/envelope/unsupported.rs +2 -2
- data/crates/kobako-transport/src/envelope.rs +2 -2
- data/crates/kobako-wasmtime/CHANGELOG.md +265 -0
- data/crates/kobako-wasmtime/Cargo.toml +25 -19
- data/crates/kobako-wasmtime/README.md +13 -7
- data/crates/kobako-wasmtime/src/abi.rs +75 -49
- data/crates/kobako-wasmtime/src/ambient.rs +64 -6
- data/crates/kobako-wasmtime/src/cache.rs +188 -77
- data/crates/kobako-wasmtime/src/config.rs +77 -5
- data/crates/kobako-wasmtime/src/descriptors.rs +200 -0
- data/crates/kobako-wasmtime/src/driver.rs +70 -135
- data/crates/kobako-wasmtime/src/exports.rs +5 -16
- data/crates/kobako-wasmtime/src/guest.rs +38 -0
- data/crates/kobako-wasmtime/src/imports.rs +52 -0
- data/crates/kobako-wasmtime/src/instance_pre.rs +18 -46
- data/crates/kobako-wasmtime/src/invocation.rs +43 -74
- data/crates/kobako-wasmtime/src/lib.rs +5 -4
- data/crates/kobako-wasmtime/src/limiter.rs +6 -230
- data/crates/kobako-wasmtime/src/preview1/differential/mount.rs +632 -0
- data/crates/kobako-wasmtime/src/preview1/differential.rs +486 -0
- data/crates/kobako-wasmtime/src/preview1/errno.rs +135 -0
- data/crates/kobako-wasmtime/src/preview1/fd.rs +1420 -0
- data/crates/kobako-wasmtime/src/preview1/memory.rs +172 -0
- data/crates/kobako-wasmtime/src/preview1/mount_view/wrap.rs +211 -0
- data/crates/kobako-wasmtime/src/preview1/mount_view.rs +3766 -0
- data/crates/kobako-wasmtime/src/preview1/path.rs +302 -0
- data/crates/kobako-wasmtime/src/preview1/poll.rs +381 -0
- data/crates/kobako-wasmtime/src/preview1/sock.rs +13 -0
- data/crates/kobako-wasmtime/src/preview1/testsuite-expected.txt +6 -0
- data/crates/kobako-wasmtime/src/preview1/testsuite.rs +157 -0
- data/crates/kobako-wasmtime/src/preview1.rs +512 -0
- data/crates/kobako-wasmtime/src/trap.rs +45 -25
- data/crates/kobako-wasmtime/src/wasi.rs +333 -0
- data/crates/kobako-wasmtime/tests/integration/compile_posture.rs +43 -0
- data/crates/kobako-wasmtime/tests/integration/guest_file.rs +1085 -0
- data/crates/kobako-wasmtime/tests/integration/main.rs +29 -0
- data/crates/kobako-wasmtime/tests/{preamble_flags.rs → integration/preamble_flags.rs} +4 -3
- data/crates/kobako-wasmtime/tests/{snippet_compile.rs → integration/snippet_compile.rs} +5 -4
- data/crates/kobako-wasmtime/tests/{support → integration/support}/mod.rs +9 -13
- data/crates/kobako-wasmtime/tests/integration/unanswered_call.rs +86 -0
- data/crates/kobako-wasmtime/tests/{unsupported_call.rs → integration/unsupported_call.rs} +2 -1
- data/crates/kobako-wasmtime/witx/typenames.witx +750 -0
- data/crates/kobako-wasmtime/witx/wasi_snapshot_preview1.witx +441 -0
- data/data/kobako.wasm +0 -0
- data/ext/kobako/Cargo.toml +4 -2
- data/ext/kobako/src/runtime/bridge.rs +154 -81
- data/ext/kobako/src/runtime/errors.rs +1 -1
- data/ext/kobako/src/runtime/escape.rs +67 -0
- data/ext/kobako/src/runtime/filesystem/grants.rs +107 -0
- data/ext/kobako/src/runtime/filesystem/ruby.rs +708 -0
- data/ext/kobako/src/runtime/filesystem.rs +208 -0
- data/ext/kobako/src/runtime/gvl.rs +49 -8
- data/ext/kobako/src/runtime/pinned_value.rs +31 -0
- data/ext/kobako/src/runtime/snapshot.rs +127 -0
- data/ext/kobako/src/runtime.rs +125 -199
- data/lib/kobako/catalog/extensions.rb +5 -5
- data/lib/kobako/catalog/flags.rb +2 -14
- data/lib/kobako/catalog/grants.rb +30 -0
- data/lib/kobako/catalog/handles.rb +6 -6
- data/lib/kobako/catalog/mount.rb +19 -0
- data/lib/kobako/catalog/services.rb +13 -2
- data/lib/kobako/catalog/snippets.rb +11 -15
- data/lib/kobako/catalog.rb +11 -0
- data/lib/kobako/codec/decoder.rb +10 -9
- data/lib/kobako/codec/ext_types.rb +4 -6
- data/lib/kobako/codec/handle_walk.rb +4 -6
- data/lib/kobako/codec/nesting.rb +7 -7
- data/lib/kobako/codec/state.rb +19 -3
- data/lib/kobako/codec/utils.rb +1 -1
- data/lib/kobako/codec/value_budget.rb +86 -0
- data/lib/kobako/codec.rb +7 -0
- data/lib/kobako/context.rb +40 -13
- data/lib/kobako/errors.rb +15 -14
- data/lib/kobako/filesystem.rb +69 -0
- data/lib/kobako/payload/arguments.rb +2 -2
- data/lib/kobako/pool.rb +6 -6
- data/lib/kobako/sandbox.rb +55 -27
- data/lib/kobako/sandbox_options.rb +39 -13
- data/lib/kobako/snippet/{binary.rb → bytecode.rb} +2 -2
- data/lib/kobako/snippet/source.rb +1 -1
- data/lib/kobako/snippet.rb +1 -1
- data/lib/kobako/transport/block_failure.rb +35 -0
- data/lib/kobako/transport/dispatcher.rb +33 -29
- data/lib/kobako/transport/exposure.rb +7 -9
- data/lib/kobako/transport/reflection.rb +79 -23
- data/lib/kobako/transport/run.rb +5 -5
- data/lib/kobako/transport/yielder.rb +9 -31
- data/lib/kobako/transport.rb +1 -0
- data/lib/kobako/version.rb +1 -1
- data/lib/kobako.rb +35 -0
- data/release-please-config.json +1 -0
- data/sig/kobako/catalog/extensions.rbs +3 -3
- data/sig/kobako/catalog/flags.rbs +0 -4
- data/sig/kobako/catalog/grants.rbs +13 -0
- data/sig/kobako/catalog/handles.rbs +3 -3
- data/sig/kobako/catalog/mount.rbs +24 -0
- data/sig/kobako/catalog/services.rbs +5 -1
- data/sig/kobako/catalog/snippets.rbs +4 -4
- data/sig/kobako/catalog.rbs +1 -0
- data/sig/kobako/codec/decoder.rbs +1 -1
- data/sig/kobako/codec/handle_walk.rbs +1 -1
- data/sig/kobako/codec/nesting.rbs +2 -2
- data/sig/kobako/codec/state.rbs +7 -1
- data/sig/kobako/codec/utils.rbs +1 -1
- data/sig/kobako/codec/value_budget.rbs +23 -0
- data/sig/kobako/codec.rbs +3 -2
- data/sig/kobako/context.rbs +14 -6
- data/sig/kobako/errors.rbs +4 -4
- data/sig/kobako/filesystem.rbs +93 -0
- data/sig/kobako/guest_yielder.rbs +9 -0
- data/sig/kobako/payload/arguments.rbs +1 -1
- data/sig/kobako/pool.rbs +3 -3
- data/sig/kobako/runtime.rbs +21 -49
- data/sig/kobako/sandbox.rbs +9 -5
- data/sig/kobako/sandbox_options.rbs +25 -3
- data/sig/kobako/snapshot.rbs +20 -0
- data/sig/kobako/snippet/bytecode.rbs +12 -0
- data/sig/kobako/transport/block_failure.rbs +13 -0
- data/sig/kobako/transport/dispatcher.rbs +4 -4
- data/sig/kobako/transport/exposure.rbs +2 -2
- data/sig/kobako/transport/reflection.rbs +14 -1
- data/sig/kobako/transport/run.rbs +3 -3
- data/sig/kobako/transport/yielder.rbs +3 -7
- data/sig/kobako/transport.rbs +3 -3
- metadata +116 -15
- data/crates/kobako-wasmtime/src/capture.rs +0 -96
- data/crates/kobako-wasmtime/src/dispatch.rs +0 -107
- data/crates/kobako-wasmtime/src/guest_mem.rs +0 -265
- data/crates/kobako-wasmtime/src/io.rs +0 -286
- data/sig/kobako/snippet/binary.rbs +0 -12
|
@@ -0,0 +1,1412 @@
|
|
|
1
|
+
//! The names of one Filesystem a walk has reached, each kept by the
|
|
2
|
+
//! directory above it and its name there while something holds it, as
|
|
3
|
+
//! Linux's dentries are: a directory moved through the VFS takes every one
|
|
4
|
+
//! beneath it along, and one removed or replaced through it is dead. A name
|
|
5
|
+
//! found standing stays for the next walk, as Linux's dcache keeps it, up to a
|
|
6
|
+
//! bound; the walk asks `revalidate` before trusting it, so what changed behind
|
|
7
|
+
//! the runtime's back is found again.
|
|
8
|
+
|
|
9
|
+
use std::any::Any;
|
|
10
|
+
use std::num::NonZeroUsize;
|
|
11
|
+
use std::sync::{Arc, Condvar, Mutex, MutexGuard, OnceLock, PoisonError, Weak};
|
|
12
|
+
use std::thread::ThreadId;
|
|
13
|
+
|
|
14
|
+
use lru::LruCache;
|
|
15
|
+
|
|
16
|
+
use super::file::FileOperations;
|
|
17
|
+
use super::inode::{Inode, InodeOperations, Lookup};
|
|
18
|
+
use super::{FileType, FilesystemError, Kstat, OpenHow, SuperBlock};
|
|
19
|
+
use crate::hashtable::Hashtable;
|
|
20
|
+
use crate::locking::seqlock::SeqLock;
|
|
21
|
+
use crate::locking::RwSemaphoreGuard;
|
|
22
|
+
use crate::sched;
|
|
23
|
+
|
|
24
|
+
/// What a Filesystem answers about a name rather than an entry, as Linux's
|
|
25
|
+
/// `dentry_operations`: a name may stand for nothing, so no inode can answer
|
|
26
|
+
/// for it.
|
|
27
|
+
pub trait DentryOperations: Send + Sync {
|
|
28
|
+
/// Whether `dentry`, a name the runtime still holds in `dir`, names what
|
|
29
|
+
/// it recorded, as `d_revalidate` answers; `false` has the runtime drop
|
|
30
|
+
/// it and take the name afresh.
|
|
31
|
+
fn revalidate(
|
|
32
|
+
&self,
|
|
33
|
+
dir: &dyn InodeOperations,
|
|
34
|
+
dentry: &Dentry,
|
|
35
|
+
) -> Result<bool, FilesystemError>;
|
|
36
|
+
|
|
37
|
+
/// Whether `dentry` goes once nothing holds it, rather than stay for the
|
|
38
|
+
/// next walk, as `d_delete` answers. A name whose `revalidate` cannot tell
|
|
39
|
+
/// a change must answer `true`, since it would be trusted when reached again.
|
|
40
|
+
fn delete(&self, _dentry: &Dentry) -> bool {
|
|
41
|
+
false
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/// The most names a Filesystem keeps that nothing holds.
|
|
46
|
+
const MAX_CACHED_DENTRIES: NonZeroUsize = NonZeroUsize::new(1024).expect("the bound is not zero");
|
|
47
|
+
|
|
48
|
+
/// Every move of a dentry, as Linux's `rename_lock`, so a walk up its
|
|
49
|
+
/// parents answers where the dentry stood at one instant, and a walk learns of a
|
|
50
|
+
/// move landing meanwhile.
|
|
51
|
+
pub(super) static RENAME_LOCK: SeqLock = SeqLock::new();
|
|
52
|
+
|
|
53
|
+
/// One name of a Filesystem, and where it stands now. Only the VFS makes
|
|
54
|
+
/// one, so a Filesystem reads the name a call is about from it.
|
|
55
|
+
pub struct Dentry {
|
|
56
|
+
state: Mutex<State>,
|
|
57
|
+
/// This dentry, for whoever is handed it to keep, as Linux's `dget`
|
|
58
|
+
/// takes a reference to any dentry it is handed.
|
|
59
|
+
this: Weak<Dentry>,
|
|
60
|
+
/// The tables of its Filesystem instance a walk finds it in, as `d_sb`
|
|
61
|
+
/// reaches the instance a dentry belongs to.
|
|
62
|
+
hashtables: Arc<Hashtables>,
|
|
63
|
+
/// What the Filesystem keeps on the name, set once, as Linux's
|
|
64
|
+
/// `d_fsdata` holds a filesystem's own state for a dentry.
|
|
65
|
+
fsdata: OnceLock<Arc<dyn Any + Send + Sync>>,
|
|
66
|
+
/// What answers about this name, as `d_alloc` copies `s_d_op` from the
|
|
67
|
+
/// super block.
|
|
68
|
+
ops: Option<Arc<dyn DentryOperations>>,
|
|
69
|
+
/// Wakes the walks waiting while another looks this name up, as Linux's
|
|
70
|
+
/// `d_wait_lookup` waits for `DCACHE_PAR_LOOKUP` to clear.
|
|
71
|
+
wait: Condvar,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/// Where a walk asking for a lookup stands, as `d_alloc_parallel` answers.
|
|
75
|
+
enum InLookup {
|
|
76
|
+
/// A lookup already answered for the name.
|
|
77
|
+
Answered,
|
|
78
|
+
/// This walk asks, and ends the lookup once answered.
|
|
79
|
+
Begun,
|
|
80
|
+
/// This walk's own lookup of the name is under way, and asks again within it.
|
|
81
|
+
Within,
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
struct State {
|
|
85
|
+
/// The directory above, `None` where the dentry is a root, which stands as
|
|
86
|
+
/// its own parent, as Linux's `d_parent == self`, held without a cycle.
|
|
87
|
+
parent: Option<Arc<Dentry>>,
|
|
88
|
+
name: String,
|
|
89
|
+
/// What the name stands for once a lookup found it, `None` while the
|
|
90
|
+
/// dentry is negative.
|
|
91
|
+
inode: Option<Arc<Inode>>,
|
|
92
|
+
/// Where the lookup of the name stands, so a negative dentry says nothing
|
|
93
|
+
/// stands there rather than that nobody asked.
|
|
94
|
+
asked: LookupState,
|
|
95
|
+
/// Whether a walk finds it by its name, as Linux's `d_hash` links it into
|
|
96
|
+
/// `dentry_hashtable`; one still in lookup, or let go of, is not.
|
|
97
|
+
hashed: bool,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/// Where the lookup of a name stands, as `DCACHE_PAR_LOOKUP` marks a dentry a
|
|
101
|
+
/// walk is asking its Filesystem about and `d_add` ends that.
|
|
102
|
+
#[derive(Clone, Copy, PartialEq, Eq)]
|
|
103
|
+
enum LookupState {
|
|
104
|
+
/// No lookup answered for the name: none asked yet, or the one that did
|
|
105
|
+
/// ended without an answer here, as `d_lookup_done` leaves a dentry.
|
|
106
|
+
Unasked,
|
|
107
|
+
/// In lookup for the walk that made the dentry, which has yet to ask, as
|
|
108
|
+
/// `d_alloc_parallel` marks the dentry it makes.
|
|
109
|
+
Allocated(ThreadId),
|
|
110
|
+
/// The walk asking the Filesystem what stands here, while it asks.
|
|
111
|
+
Asking(ThreadId),
|
|
112
|
+
/// A lookup answered for the name, or a call made what stands there.
|
|
113
|
+
Answered,
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
impl Dentry {
|
|
117
|
+
fn new(
|
|
118
|
+
parent: Option<Arc<Dentry>>,
|
|
119
|
+
name: &str,
|
|
120
|
+
ops: Option<Arc<dyn DentryOperations>>,
|
|
121
|
+
hashtables: &Arc<Hashtables>,
|
|
122
|
+
) -> Arc<Self> {
|
|
123
|
+
Arc::new_cyclic(|this| Self {
|
|
124
|
+
state: Mutex::new(State {
|
|
125
|
+
parent,
|
|
126
|
+
name: name.to_string(),
|
|
127
|
+
inode: None,
|
|
128
|
+
asked: LookupState::Unasked,
|
|
129
|
+
hashed: false,
|
|
130
|
+
}),
|
|
131
|
+
this: this.clone(),
|
|
132
|
+
hashtables: hashtables.clone(),
|
|
133
|
+
fsdata: OnceLock::new(),
|
|
134
|
+
ops,
|
|
135
|
+
wait: Condvar::new(),
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/// What answers about this name, the same for every dentry of one
|
|
140
|
+
/// Filesystem.
|
|
141
|
+
pub fn ops(&self) -> Option<&Arc<dyn DentryOperations>> {
|
|
142
|
+
self.ops.as_ref()
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/// The directory above, this dentry itself at a root, as Linux's
|
|
146
|
+
/// `dget_parent` takes it.
|
|
147
|
+
pub(crate) fn parent(&self) -> Arc<Dentry> {
|
|
148
|
+
self.parent_and_name().0
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/// Whether this dentry is a root, standing as its own parent, as Linux's
|
|
152
|
+
/// `IS_ROOT` answers.
|
|
153
|
+
pub(crate) fn is_root(&self) -> bool {
|
|
154
|
+
self.state().parent.is_none()
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/// The directory above and the name there, read together, so a move
|
|
158
|
+
/// landing meanwhile cannot pair one's parent with the other's name, as 9p
|
|
159
|
+
/// holds `rename_sem` to read both.
|
|
160
|
+
pub(crate) fn parent_and_name(&self) -> (Arc<Dentry>, String) {
|
|
161
|
+
let state = self.state();
|
|
162
|
+
let parent = state.parent.clone().unwrap_or_else(|| self.get());
|
|
163
|
+
(parent, state.name.clone())
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/// This dentry, kept for as long as the caller holds it, as Linux's
|
|
167
|
+
/// `dget` does.
|
|
168
|
+
pub(crate) fn get(&self) -> Arc<Dentry> {
|
|
169
|
+
self.this
|
|
170
|
+
.upgrade()
|
|
171
|
+
.expect("a dentry handed out is held by whoever handed it")
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
fn state(&self) -> MutexGuard<'_, State> {
|
|
175
|
+
self.state.lock().unwrap_or_else(PoisonError::into_inner)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/// The name in the directory above, empty for the root.
|
|
179
|
+
pub fn name(&self) -> String {
|
|
180
|
+
self.state().name.clone()
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/// Where this name stands now beneath the Filesystem's root, from a
|
|
184
|
+
/// leading `/` that alone names the root, as Linux's `dentry_path_raw`
|
|
185
|
+
/// answers.
|
|
186
|
+
pub fn path_raw(&self) -> String {
|
|
187
|
+
format!("/{}", self.names().join("/"))
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/// Each name from the Filesystem's root down to this directory, as Linux's
|
|
191
|
+
/// `__dentry_path` walks up until `IS_ROOT`.
|
|
192
|
+
pub(crate) fn names(&self) -> Vec<String> {
|
|
193
|
+
RENAME_LOCK.read(|| {
|
|
194
|
+
let mut names = Vec::new();
|
|
195
|
+
let mut at = self.get();
|
|
196
|
+
while !at.is_root() {
|
|
197
|
+
let (parent, name) = at.parent_and_name();
|
|
198
|
+
names.push(name);
|
|
199
|
+
at = parent;
|
|
200
|
+
}
|
|
201
|
+
names.reverse();
|
|
202
|
+
names
|
|
203
|
+
})
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/// Hold the directory this names shared, as
|
|
207
|
+
/// `inode_lock_shared(d_inode(dentry))`.
|
|
208
|
+
pub(crate) fn inode_lock_shared(&self) -> Result<RwSemaphoreGuard, FilesystemError> {
|
|
209
|
+
self.inode().ok_or(FilesystemError::NotFound)?.lock_shared()
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/// Hold the directory this names exclusive, as
|
|
213
|
+
/// `inode_lock(d_inode(dentry))`.
|
|
214
|
+
pub(crate) fn inode_lock(&self) -> Result<RwSemaphoreGuard, FilesystemError> {
|
|
215
|
+
self.inode().ok_or(FilesystemError::NotFound)?.lock()
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/// Whether the directory this names was removed or replaced, as
|
|
219
|
+
/// `IS_DEADDIR(d_inode(dentry))`.
|
|
220
|
+
pub(crate) fn is_deaddir(&self) -> bool {
|
|
221
|
+
self.inode().is_some_and(|inode| inode.is_deaddir())
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/// Mark the directory this names removed or replaced.
|
|
225
|
+
pub(super) fn mark_dead(&self) {
|
|
226
|
+
if let Some(inode) = self.inode() {
|
|
227
|
+
inode.mark_dead();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/// What the name stands for, `None` while the dentry is negative, as
|
|
232
|
+
/// Linux's `d_inode` reads it.
|
|
233
|
+
pub fn inode(&self) -> Option<Arc<Inode>> {
|
|
234
|
+
self.state().inode.clone()
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/// What answers for the entry this names, as `d_inode(dentry)->i_op`.
|
|
238
|
+
pub(crate) fn inode_ops(&self) -> Result<Arc<dyn InodeOperations>, FilesystemError> {
|
|
239
|
+
match self.inode() {
|
|
240
|
+
Some(inode) => Ok(inode.ops().clone()),
|
|
241
|
+
None => Err(FilesystemError::NotFound),
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/// Record `inode` as what the name stands for, `None` where nothing
|
|
246
|
+
/// does, as a Filesystem's lookup hands Linux's `d_add` what it found.
|
|
247
|
+
/// A dentry already naming something keeps it, as Linux instantiates a
|
|
248
|
+
/// dentry once, so a later lookup never hides a change `d_revalidate`
|
|
249
|
+
/// must see.
|
|
250
|
+
pub fn add(&self, inode: Option<Arc<Inode>>) {
|
|
251
|
+
match inode {
|
|
252
|
+
Some(inode) => inode.add_hashed(self),
|
|
253
|
+
None => self.rehash(),
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/// End the lookup of this name with an answer, and let a walk find it by
|
|
258
|
+
/// its name from now on, waking every walk waiting on it, as Linux's
|
|
259
|
+
/// `__d_add` takes it out of `in_lookup_hashtable` and `__d_rehash`es it.
|
|
260
|
+
pub(super) fn rehash(&self) {
|
|
261
|
+
{
|
|
262
|
+
let mut table = self.hashtables.dentry_hashtable();
|
|
263
|
+
let mut in_lookup = self.hashtables.in_lookup_hashtable();
|
|
264
|
+
let (parent, name) = self.parent_and_name();
|
|
265
|
+
let at = key(&parent, &name);
|
|
266
|
+
let this = self.get();
|
|
267
|
+
if in_lookup
|
|
268
|
+
.get(&at)
|
|
269
|
+
.is_some_and(|held| Arc::ptr_eq(&held, &this))
|
|
270
|
+
{
|
|
271
|
+
in_lookup.remove(&at);
|
|
272
|
+
}
|
|
273
|
+
let mut state = self.state();
|
|
274
|
+
state.asked = LookupState::Answered;
|
|
275
|
+
if !state.hashed {
|
|
276
|
+
table.insert(at, &this);
|
|
277
|
+
state.hashed = true;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
self.wait.notify_all();
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/// Whether a walk finds this dentry by its name, as Linux's `!d_unhashed`
|
|
284
|
+
/// answers.
|
|
285
|
+
pub(super) fn hashed(&self) -> bool {
|
|
286
|
+
self.state().hashed
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/// Record `inode` on this dentry, which no walk finds by a name, as
|
|
290
|
+
/// `d_instantiate` hangs a root or a pseudo dentry on `i_dentry` under
|
|
291
|
+
/// `i_lock`.
|
|
292
|
+
pub(crate) fn instantiate(&self, inode: Arc<Inode>) {
|
|
293
|
+
let mut held = inode.dentry();
|
|
294
|
+
self.__instantiate(inode.clone(), &mut held);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/// Record `inode` here unless the dentry already names something, a
|
|
298
|
+
/// directory's `held` dentry being this one from then on, as
|
|
299
|
+
/// `__d_instantiate` hangs the dentry on `i_dentry`. The caller holds
|
|
300
|
+
/// `held`, the inode's lock on the dentry it keeps.
|
|
301
|
+
pub(super) fn __instantiate(&self, inode: Arc<Inode>, held: &mut Weak<Dentry>) {
|
|
302
|
+
let mut state = self.state();
|
|
303
|
+
if state.inode.is_none() {
|
|
304
|
+
if inode.kind() == FileType::Dir {
|
|
305
|
+
*held = self.this.clone();
|
|
306
|
+
}
|
|
307
|
+
state.inode = Some(inode);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/// What the Filesystem keeps on the name, set once, as Linux's
|
|
312
|
+
/// `d_fsdata` holds a filesystem's own state for a dentry.
|
|
313
|
+
pub fn fsdata(&self) -> &OnceLock<Arc<dyn Any + Send + Sync>> {
|
|
314
|
+
&self.fsdata
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/// Whether this goes once nothing holds it, as `d_delete` answers; a name
|
|
318
|
+
/// no one answers for stays.
|
|
319
|
+
pub(crate) fn goes_when_unused(&self) -> bool {
|
|
320
|
+
self.ops.as_ref().is_some_and(|ops| ops.delete(self))
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/// Whether both names stand for one entry, as hard links do, which Linux's
|
|
324
|
+
/// `vfs_rename` answers as having nothing to move, comparing `source ==
|
|
325
|
+
/// target`. A Filesystem naming no identity cannot tell.
|
|
326
|
+
pub(crate) fn names_the_entry_of(&self, other: &Dentry) -> bool {
|
|
327
|
+
let identity = |dentry: &Dentry| dentry.inode().and_then(|inode| inode.identity());
|
|
328
|
+
identity(self).is_some() && identity(self) == identity(other)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/// Whether a walk is still looking the name up, as Linux's `d_in_lookup`
|
|
332
|
+
/// tells a Filesystem's `atomic_open` the dentry it must look up first.
|
|
333
|
+
pub fn in_lookup(&self) -> bool {
|
|
334
|
+
matches!(
|
|
335
|
+
self.state().asked,
|
|
336
|
+
LookupState::Allocated(_) | LookupState::Asking(_)
|
|
337
|
+
)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/// Ask the directory `dir` of `sb` what stands here where no lookup answered
|
|
341
|
+
/// yet, and record it, as Linux's `lookup_fast` keeps a dentry, a negative
|
|
342
|
+
/// one too, and `__lookup_slow` asks `->lookup` for one just made. The
|
|
343
|
+
/// dentry to use in place of this one is handed back as `d_splice_alias`
|
|
344
|
+
/// hands it, none where this one records the answer.
|
|
345
|
+
pub(crate) fn lookup_inode(
|
|
346
|
+
&self,
|
|
347
|
+
sb: &SuperBlock,
|
|
348
|
+
dir: &Dentry,
|
|
349
|
+
) -> Result<Option<Arc<Dentry>>, FilesystemError> {
|
|
350
|
+
let asked = self.ask_in_lookup(|| {
|
|
351
|
+
let found = dir.inode_ops()?.lookup(self)?;
|
|
352
|
+
self.splice_alias(sb, found)
|
|
353
|
+
})?;
|
|
354
|
+
Ok(asked.flatten())
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/// What `ask` answers for this name where no lookup answered yet, none where
|
|
358
|
+
/// one did. The walk `d_alloc_parallel` made the dentry for asks, as it
|
|
359
|
+
/// holds the dentry in lookup, and `d_lookup_done` ends that.
|
|
360
|
+
pub(crate) fn ask_in_lookup<T>(
|
|
361
|
+
&self,
|
|
362
|
+
ask: impl FnOnce() -> Result<T, FilesystemError>,
|
|
363
|
+
) -> Result<Option<T>, FilesystemError> {
|
|
364
|
+
let ends = match self.begin_lookup()? {
|
|
365
|
+
InLookup::Answered => return Ok(None),
|
|
366
|
+
InLookup::Begun => true,
|
|
367
|
+
InLookup::Within => false,
|
|
368
|
+
};
|
|
369
|
+
let asked = ask();
|
|
370
|
+
if ends {
|
|
371
|
+
self.lookup_done();
|
|
372
|
+
}
|
|
373
|
+
asked.map(Some)
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/// Record what a lookup found at this name of `sb`, as Linux's
|
|
377
|
+
/// `d_splice_alias` does, handing back the dentry to use in place of this
|
|
378
|
+
/// one, none where this one records it. A directory already standing at
|
|
379
|
+
/// another dentry keeps that one, moved here, as a directory has one
|
|
380
|
+
/// dentry: a move that would put it beneath itself answers ELOOP, and one
|
|
381
|
+
/// whose locks another call holds answers ESTALE. An entry stored under
|
|
382
|
+
/// another spelling is recorded on that spelling's dentry, as `d_add_ci`
|
|
383
|
+
/// does.
|
|
384
|
+
pub(crate) fn splice_alias(
|
|
385
|
+
&self,
|
|
386
|
+
sb: &SuperBlock,
|
|
387
|
+
found: Lookup,
|
|
388
|
+
) -> Result<Option<Arc<Dentry>>, FilesystemError> {
|
|
389
|
+
let inode = match found {
|
|
390
|
+
Lookup::Found(inode) => inode,
|
|
391
|
+
Lookup::Absent => {
|
|
392
|
+
self.add(None);
|
|
393
|
+
return Ok(None);
|
|
394
|
+
}
|
|
395
|
+
Lookup::Spelled { name, inode } => return self.add_ci(sb, &name, inode).map(Some),
|
|
396
|
+
};
|
|
397
|
+
match inode.find_alias_or_add(self) {
|
|
398
|
+
None => Ok(None),
|
|
399
|
+
Some(alias) => {
|
|
400
|
+
self.get().unalias(sb, &alias)?;
|
|
401
|
+
Ok(Some(alias))
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/// The dentry of `name`, the spelling the directory above stores the
|
|
407
|
+
/// entry under, with `inode` spliced on it, as Linux's `d_add_ci` finds
|
|
408
|
+
/// that spelling's dentry, or makes one, rather than asking the
|
|
409
|
+
/// Filesystem again. One already held there is taken as it stands, as
|
|
410
|
+
/// `d_hash_and_lookup` asks no `d_revalidate`.
|
|
411
|
+
pub(crate) fn add_ci(
|
|
412
|
+
&self,
|
|
413
|
+
sb: &SuperBlock,
|
|
414
|
+
name: &str,
|
|
415
|
+
inode: Arc<Inode>,
|
|
416
|
+
) -> Result<Arc<Dentry>, FilesystemError> {
|
|
417
|
+
let found = sb.dentries().alloc_parallel(&self.parent(), name)?;
|
|
418
|
+
let spliced = found.ask_in_lookup(|| found.splice_alias(sb, Lookup::Found(inode)))?;
|
|
419
|
+
Ok(spliced.flatten().unwrap_or(found))
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/// `alias`, where a directory of `sb` stands, moved to this dentry's name,
|
|
423
|
+
/// taking its place, as Linux's `__d_unalias` moves it under `rename_lock`.
|
|
424
|
+
/// One that would stand beneath itself answers ELOOP, as `d_ancestor`
|
|
425
|
+
/// refuses it; so does a root, which only a Filesystem handing one inode
|
|
426
|
+
/// to two instances reaches. A move out of another directory takes that
|
|
427
|
+
/// directory's lock and the instance's `rename_mutex` without waiting,
|
|
428
|
+
/// since the caller holds the lock of the one it looks the name up in,
|
|
429
|
+
/// and answers ESTALE where another call holds either.
|
|
430
|
+
fn unalias(
|
|
431
|
+
self: &Arc<Self>,
|
|
432
|
+
sb: &SuperBlock,
|
|
433
|
+
alias: &Arc<Dentry>,
|
|
434
|
+
) -> Result<(), FilesystemError> {
|
|
435
|
+
let (parent, name) = self.parent_and_name();
|
|
436
|
+
let mut table = self.hashtables.dentry_hashtable();
|
|
437
|
+
RENAME_LOCK.write(|| {
|
|
438
|
+
if d_ancestor(alias, self) || alias.is_root() {
|
|
439
|
+
return Err(FilesystemError::FilesystemLoop);
|
|
440
|
+
}
|
|
441
|
+
let (old_parent, old_name) = alias.parent_and_name();
|
|
442
|
+
let _held = match Arc::ptr_eq(&old_parent, &parent) {
|
|
443
|
+
true => None,
|
|
444
|
+
false => {
|
|
445
|
+
let stale = || FilesystemError::StaleNetworkFileHandle;
|
|
446
|
+
let rename = sb.rename_mutex().down_write_trylock().ok_or_else(stale)?;
|
|
447
|
+
let dir = old_parent
|
|
448
|
+
.inode()
|
|
449
|
+
.expect("a directory holding a dentry names its inode")
|
|
450
|
+
.trylock_shared();
|
|
451
|
+
Some((rename, dir.ok_or_else(stale)?))
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
let mut state = alias.state();
|
|
455
|
+
if state.hashed {
|
|
456
|
+
table.remove(&key(&old_parent, &old_name));
|
|
457
|
+
}
|
|
458
|
+
table.insert(key(&parent, &name), alias);
|
|
459
|
+
state.hashed = true;
|
|
460
|
+
state.parent = Some(parent.clone());
|
|
461
|
+
state.name = name.clone();
|
|
462
|
+
Ok(())
|
|
463
|
+
})
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/// Begin the lookup the calling walk holds the dentry in lookup for, or one
|
|
467
|
+
/// nobody asked yet, once no other walk holds it. A Filesystem asking the
|
|
468
|
+
/// runtime about the name its own lookup is answering asks it again within
|
|
469
|
+
/// that lookup, as it would otherwise wait on itself.
|
|
470
|
+
fn begin_lookup(&self) -> Result<InLookup, FilesystemError> {
|
|
471
|
+
self.wait_lookup()?;
|
|
472
|
+
let mut state = self.state();
|
|
473
|
+
Ok(match state.asked {
|
|
474
|
+
LookupState::Asking(_) => InLookup::Within,
|
|
475
|
+
LookupState::Answered => InLookup::Answered,
|
|
476
|
+
LookupState::Allocated(_) | LookupState::Unasked => {
|
|
477
|
+
state.asked = LookupState::Asking(sched::current());
|
|
478
|
+
InLookup::Begun
|
|
479
|
+
}
|
|
480
|
+
})
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/// Wait while another walk holds this name in lookup, until the walk's run
|
|
484
|
+
/// is to stop, as Linux's `d_wait_lookup` does, answering whether the
|
|
485
|
+
/// calling walk holds it in lookup itself.
|
|
486
|
+
fn wait_lookup(&self) -> Result<bool, FilesystemError> {
|
|
487
|
+
let current = sched::current();
|
|
488
|
+
loop {
|
|
489
|
+
let state = self.state();
|
|
490
|
+
match state.asked {
|
|
491
|
+
LookupState::Allocated(walk) | LookupState::Asking(walk) if walk == current => {
|
|
492
|
+
return Ok(true)
|
|
493
|
+
}
|
|
494
|
+
LookupState::Allocated(_) | LookupState::Asking(_)
|
|
495
|
+
if sched::fatal_signal_pending() =>
|
|
496
|
+
{
|
|
497
|
+
return Err(FilesystemError::Interrupted)
|
|
498
|
+
}
|
|
499
|
+
LookupState::Allocated(_) | LookupState::Asking(_) => {
|
|
500
|
+
drop(state);
|
|
501
|
+
sched::schedule(&mut || {
|
|
502
|
+
let state = self.state();
|
|
503
|
+
if matches!(
|
|
504
|
+
state.asked,
|
|
505
|
+
LookupState::Allocated(_) | LookupState::Asking(_)
|
|
506
|
+
) {
|
|
507
|
+
drop(
|
|
508
|
+
self.wait
|
|
509
|
+
.wait_timeout(state, sched::sleep_slice())
|
|
510
|
+
.unwrap_or_else(PoisonError::into_inner),
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
LookupState::Unasked | LookupState::Answered => return Ok(false),
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/// End this walk's lookup where no answer did, taking the dentry out of
|
|
521
|
+
/// `in_lookup_hashtable` and waking every walk waiting on it, as
|
|
522
|
+
/// `d_lookup_done` does; each of them looks the name up again.
|
|
523
|
+
fn lookup_done(&self) {
|
|
524
|
+
{
|
|
525
|
+
let mut in_lookup = self.hashtables.in_lookup_hashtable();
|
|
526
|
+
let (parent, name) = self.parent_and_name();
|
|
527
|
+
let at = key(&parent, &name);
|
|
528
|
+
let mut state = self.state();
|
|
529
|
+
if matches!(
|
|
530
|
+
state.asked,
|
|
531
|
+
LookupState::Allocated(_) | LookupState::Asking(_)
|
|
532
|
+
) {
|
|
533
|
+
if in_lookup
|
|
534
|
+
.get(&at)
|
|
535
|
+
.is_some_and(|held| std::ptr::eq(held.as_ref(), self))
|
|
536
|
+
{
|
|
537
|
+
in_lookup.remove(&at);
|
|
538
|
+
}
|
|
539
|
+
state.asked = LookupState::Unasked;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
self.wait.notify_all();
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/// What kind of entry this records, `NotFound` where it records none, as
|
|
546
|
+
/// Linux's `d_is_*` read a dentry without asking its filesystem.
|
|
547
|
+
pub(crate) fn kind(&self) -> Result<FileType, FilesystemError> {
|
|
548
|
+
self.inode()
|
|
549
|
+
.map(|inode| inode.kind())
|
|
550
|
+
.ok_or(FilesystemError::NotFound)
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/// What the entry this records reports of itself, `NotFound` where it
|
|
554
|
+
/// records none, as `vfs_getattr` asks the inode a dentry holds.
|
|
555
|
+
pub(crate) fn getattr(&self) -> Result<Kstat, FilesystemError> {
|
|
556
|
+
match self.inode() {
|
|
557
|
+
Some(inode) => inode.ops().getattr(self),
|
|
558
|
+
None => Err(FilesystemError::NotFound),
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/// Open the file this records as `how` asks, as `vfs_open` finds
|
|
563
|
+
/// `f_op->open` on the inode a dentry holds.
|
|
564
|
+
pub(crate) fn open(&self, how: &OpenHow) -> Result<Box<dyn FileOperations>, FilesystemError> {
|
|
565
|
+
match self.inode() {
|
|
566
|
+
Some(inode) => inode.ops().open(self, how),
|
|
567
|
+
None => Err(FilesystemError::NotFound),
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/// Where the link this records points, as `vfs_readlink` asks the inode a
|
|
572
|
+
/// dentry holds; anything else refuses as `readlinkat` does.
|
|
573
|
+
pub(crate) fn get_link(&self) -> Result<String, FilesystemError> {
|
|
574
|
+
match self.inode() {
|
|
575
|
+
Some(inode) => inode.ops().get_link(self),
|
|
576
|
+
None => Err(FilesystemError::NotFound),
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/// Whether this dentry is `ancestor` or stands beneath it, as Linux's
|
|
581
|
+
/// `is_subdir` answers.
|
|
582
|
+
pub(crate) fn is_subdir(self: &Arc<Self>, ancestor: &Arc<Dentry>) -> bool {
|
|
583
|
+
Arc::ptr_eq(self, ancestor) || RENAME_LOCK.read(|| d_ancestor(ancestor, self))
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/// Whether `ancestor` stands above `dentry`, as Linux's `d_ancestor` answers.
|
|
588
|
+
/// The caller holds `RENAME_LOCK`, so no move lands during the walk up.
|
|
589
|
+
fn d_ancestor(ancestor: &Arc<Dentry>, dentry: &Arc<Dentry>) -> bool {
|
|
590
|
+
let mut at = dentry.clone();
|
|
591
|
+
while !at.is_root() {
|
|
592
|
+
at = at.parent();
|
|
593
|
+
if Arc::ptr_eq(&at, ancestor) {
|
|
594
|
+
return true;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
false
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/// A dentry holds the one above it, and moves can stack them as deep as a
|
|
601
|
+
/// Filesystem holds directories, so the last reference lets go of each in a
|
|
602
|
+
/// loop, as Linux's `dput` walks up in one.
|
|
603
|
+
impl Drop for Dentry {
|
|
604
|
+
fn drop(&mut self) {
|
|
605
|
+
let parent = |dentry: &mut Dentry| {
|
|
606
|
+
dentry
|
|
607
|
+
.state
|
|
608
|
+
.get_mut()
|
|
609
|
+
.unwrap_or_else(PoisonError::into_inner)
|
|
610
|
+
.parent
|
|
611
|
+
.take()
|
|
612
|
+
};
|
|
613
|
+
let mut above = parent(self);
|
|
614
|
+
while let Some(mut dentry) = above.and_then(Arc::into_inner) {
|
|
615
|
+
above = parent(&mut dentry);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/// The directory above a name, by its address, and the name there.
|
|
621
|
+
type Key = (usize, String);
|
|
622
|
+
|
|
623
|
+
fn key(parent: &Arc<Dentry>, name: &str) -> Key {
|
|
624
|
+
(Arc::as_ptr(parent) as usize, name.to_string())
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/// The dentries of one Filesystem a walk finds by the directory above and the
|
|
628
|
+
/// name there.
|
|
629
|
+
struct Hashtables {
|
|
630
|
+
/// Those a lookup answered, as Linux's `dentry_hashtable`.
|
|
631
|
+
dentry_hashtable: Mutex<Hashtable<Key, Dentry>>,
|
|
632
|
+
/// Those a walk is still looking up, as Linux's `in_lookup_hashtable`, so
|
|
633
|
+
/// any other walk reaching the name waits for that answer.
|
|
634
|
+
in_lookup_hashtable: Mutex<Hashtable<Key, Dentry>>,
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
impl Hashtables {
|
|
638
|
+
fn dentry_hashtable(&self) -> MutexGuard<'_, Hashtable<Key, Dentry>> {
|
|
639
|
+
self.dentry_hashtable
|
|
640
|
+
.lock()
|
|
641
|
+
.unwrap_or_else(PoisonError::into_inner)
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/// Taken after `dentry_hashtable` where a call holds both.
|
|
645
|
+
fn in_lookup_hashtable(&self) -> MutexGuard<'_, Hashtable<Key, Dentry>> {
|
|
646
|
+
self.in_lookup_hashtable
|
|
647
|
+
.lock()
|
|
648
|
+
.unwrap_or_else(PoisonError::into_inner)
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/// Every name of one Filesystem something still holds, by the directory
|
|
653
|
+
/// above it and its name there.
|
|
654
|
+
pub(crate) struct Dentries {
|
|
655
|
+
ops: Option<Arc<dyn DentryOperations>>,
|
|
656
|
+
hashtables: Arc<Hashtables>,
|
|
657
|
+
/// The names kept for the next walk, most recently found last, as Linux's
|
|
658
|
+
/// `d_lru`, each by its address.
|
|
659
|
+
lru: Mutex<LruCache<usize, Arc<Dentry>>>,
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
impl Dentries {
|
|
663
|
+
pub(crate) fn new(ops: Option<Arc<dyn DentryOperations>>) -> Self {
|
|
664
|
+
let hashtables = Arc::new(Hashtables {
|
|
665
|
+
dentry_hashtable: Mutex::default(),
|
|
666
|
+
in_lookup_hashtable: Mutex::default(),
|
|
667
|
+
});
|
|
668
|
+
Self {
|
|
669
|
+
ops,
|
|
670
|
+
hashtables,
|
|
671
|
+
lru: Mutex::new(LruCache::new(MAX_CACHED_DENTRIES)),
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/// What every dentry of this table answers through.
|
|
676
|
+
pub(super) fn ops(&self) -> Option<&Arc<dyn DentryOperations>> {
|
|
677
|
+
self.ops.as_ref()
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
fn lru(&self) -> MutexGuard<'_, LruCache<usize, Arc<Dentry>>> {
|
|
681
|
+
self.lru.lock().unwrap_or_else(PoisonError::into_inner)
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/// Keep `dentry`, which a lookup answered for, standing or not, for the next
|
|
685
|
+
/// walk, unless no walk finds it by its name any more or its Filesystem lets
|
|
686
|
+
/// it go, as `retain_dentry` adds a hashed dentry `d_delete` allows to the
|
|
687
|
+
/// LRU. What this pushes out goes once the table is let go of, as its
|
|
688
|
+
/// release may reach another Filesystem.
|
|
689
|
+
/// The check and the keeping run under the dentry's lock, which `unhash`
|
|
690
|
+
/// takes too, as both hold `d_lock`.
|
|
691
|
+
pub(crate) fn retain(&self, dentry: &Arc<Dentry>) {
|
|
692
|
+
if dentry.goes_when_unused() {
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
let state = dentry.state();
|
|
696
|
+
if !state.hashed {
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
let pushed_out = self
|
|
700
|
+
.lru()
|
|
701
|
+
.push(Arc::as_ptr(dentry) as usize, dentry.clone());
|
|
702
|
+
drop(state);
|
|
703
|
+
drop(pushed_out);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/// Have every dentry made from now on answer through `ops`, as
|
|
707
|
+
/// `set_default_d_op` sets `s_d_op` for each `d_alloc` after it.
|
|
708
|
+
pub(super) fn set_ops(&mut self, ops: Arc<dyn DentryOperations>) {
|
|
709
|
+
self.ops = Some(ops);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/// A root dentry recording `inode`, as `d_make_root` allocates one under no
|
|
713
|
+
/// name and instantiates it.
|
|
714
|
+
pub(super) fn make_root(&self, inode: Arc<Inode>) -> Arc<Dentry> {
|
|
715
|
+
let root = Dentry::new(None, "", self.ops.clone(), &self.hashtables);
|
|
716
|
+
root.instantiate(inode);
|
|
717
|
+
root
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/// A dentry no walk reaches, a root of its own under no name, as
|
|
721
|
+
/// `d_alloc_pseudo` makes one for what is only ever open.
|
|
722
|
+
pub(crate) fn alloc_pseudo(&self) -> Arc<Dentry> {
|
|
723
|
+
Dentry::new(None, "", self.ops.clone(), &self.hashtables)
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/// The dentry a lookup answered for `name` in `parent`, or a new one in
|
|
727
|
+
/// lookup for the calling walk, as Linux's `d_alloc_parallel` finds or
|
|
728
|
+
/// makes one. A walk finding the name in lookup by another waits for that
|
|
729
|
+
/// walk's answer, until its run is to stop, and takes the dentry where the
|
|
730
|
+
/// answer left it at the name, or looks again, as `d_alloc_parallel`
|
|
731
|
+
/// retries on a mismatch.
|
|
732
|
+
pub(crate) fn alloc_parallel(
|
|
733
|
+
&self,
|
|
734
|
+
parent: &Arc<Dentry>,
|
|
735
|
+
name: &str,
|
|
736
|
+
) -> Result<Arc<Dentry>, FilesystemError> {
|
|
737
|
+
let at = key(parent, name);
|
|
738
|
+
loop {
|
|
739
|
+
let waited = {
|
|
740
|
+
let table = self.hashtables.dentry_hashtable();
|
|
741
|
+
if let Some(found) = table.get(&at) {
|
|
742
|
+
return Ok(found);
|
|
743
|
+
}
|
|
744
|
+
let mut in_lookup = self.hashtables.in_lookup_hashtable();
|
|
745
|
+
match in_lookup.get(&at) {
|
|
746
|
+
Some(waited) => waited,
|
|
747
|
+
None => {
|
|
748
|
+
let made = Dentry::new(
|
|
749
|
+
Some(parent.clone()),
|
|
750
|
+
name,
|
|
751
|
+
self.ops.clone(),
|
|
752
|
+
&self.hashtables,
|
|
753
|
+
);
|
|
754
|
+
made.state().asked = LookupState::Allocated(sched::current());
|
|
755
|
+
in_lookup.insert(at, &made);
|
|
756
|
+
return Ok(made);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
};
|
|
760
|
+
if waited.wait_lookup()? {
|
|
761
|
+
return Ok(waited);
|
|
762
|
+
}
|
|
763
|
+
let (above, called) = waited.parent_and_name();
|
|
764
|
+
if waited.hashed() && Arc::ptr_eq(&above, parent) && called == name {
|
|
765
|
+
return Ok(waited);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/// The dentry a lookup answered for `name` in `parent`, as `d_lookup` finds
|
|
771
|
+
/// one.
|
|
772
|
+
pub(crate) fn lookup(&self, parent: &Arc<Dentry>, name: &str) -> Option<Arc<Dentry>> {
|
|
773
|
+
self.hashtables.dentry_hashtable().get(&key(parent, name))
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
/// Whether `dentry` was let go of from its name, as Linux's `d_unlinked`
|
|
777
|
+
/// answers; a root never is.
|
|
778
|
+
pub(crate) fn unlinked(&self, dentry: &Arc<Dentry>) -> bool {
|
|
779
|
+
!dentry.is_root() && !dentry.hashed()
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
/// Let go of `dentry`, which no longer names what stands at its name, and
|
|
783
|
+
/// of the names kept beneath it, as Linux's `d_invalidate` drops it and
|
|
784
|
+
/// shrinks what is below a positive one.
|
|
785
|
+
pub(crate) fn invalidate(&self, dentry: &Arc<Dentry>) {
|
|
786
|
+
if !dentry.hashed() {
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
self.unhash(dentry);
|
|
790
|
+
if dentry.inode().is_some() {
|
|
791
|
+
self.shrink_dcache_parent(dentry);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/// Let go of the names kept for the next walk beneath `parent`, as Linux's
|
|
796
|
+
/// `shrink_dcache_parent` frees the unused dentries below one; one something
|
|
797
|
+
/// still holds stays with its holder. They are found through the LRU, the
|
|
798
|
+
/// only place keeping a dentry nothing else holds, rather than a list of
|
|
799
|
+
/// children.
|
|
800
|
+
pub(crate) fn shrink_dcache_parent(&self, parent: &Arc<Dentry>) {
|
|
801
|
+
let kept: Vec<Arc<Dentry>> = self.lru().iter().map(|(_, held)| held.clone()).collect();
|
|
802
|
+
// Decided before the LRU's lock is taken, as `retain` holds a name's lock
|
|
803
|
+
// while it takes the LRU's.
|
|
804
|
+
let beneath: Vec<usize> = kept
|
|
805
|
+
.iter()
|
|
806
|
+
.filter(|held| !Arc::ptr_eq(held, parent) && held.is_subdir(parent))
|
|
807
|
+
.map(|held| Arc::as_ptr(held) as usize)
|
|
808
|
+
.collect();
|
|
809
|
+
let let_go: Vec<_> = {
|
|
810
|
+
let mut lru = self.lru();
|
|
811
|
+
beneath.iter().filter_map(|at| lru.pop(at)).collect()
|
|
812
|
+
};
|
|
813
|
+
drop(let_go);
|
|
814
|
+
drop(kept);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
/// Let go of `dentry` from its name, so the next lookup of the name makes
|
|
818
|
+
/// another and whoever holds this one keeps it, as Linux's `d_drop` unhashes
|
|
819
|
+
/// one for `d_invalidate` and for a `d_delete` of one still in use.
|
|
820
|
+
pub(crate) fn unhash(&self, dentry: &Arc<Dentry>) {
|
|
821
|
+
drop_hashed(&mut self.hashtables.dentry_hashtable(), dentry);
|
|
822
|
+
let kept = self.lru().pop(&(Arc::as_ptr(dentry) as usize));
|
|
823
|
+
drop(kept);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
/// `dentry` moved to the name `target` stands at, taking every one beneath
|
|
827
|
+
/// it along, and `target` let go of, as Linux's `d_move` follows a rename.
|
|
828
|
+
/// A walk finding `dentry` by a name reaches it throughout, so it is never
|
|
829
|
+
/// taken for unlinked.
|
|
830
|
+
pub(crate) fn move_dentry(&self, dentry: &Arc<Dentry>, target: &Arc<Dentry>) {
|
|
831
|
+
self.unhash(target);
|
|
832
|
+
let mut table = self.hashtables.dentry_hashtable();
|
|
833
|
+
let (parent, name) = target.parent_and_name();
|
|
834
|
+
let (from, from_name) = dentry.parent_and_name();
|
|
835
|
+
RENAME_LOCK.write(|| {
|
|
836
|
+
let mut state = dentry.state();
|
|
837
|
+
if state.hashed {
|
|
838
|
+
table.remove(&key(&from, &from_name));
|
|
839
|
+
}
|
|
840
|
+
table.insert(key(&parent, &name), dentry);
|
|
841
|
+
state.hashed = true;
|
|
842
|
+
state.parent = Some(parent.clone());
|
|
843
|
+
state.name = name.clone();
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
/// `first` and `second` trade the names they stand at, as Linux's
|
|
848
|
+
/// `d_exchange` follows an exchange, so whoever holds either reaches
|
|
849
|
+
/// it where it went.
|
|
850
|
+
pub(crate) fn exchange(&self, first: &Arc<Dentry>, second: &Arc<Dentry>) {
|
|
851
|
+
let mut table = self.hashtables.dentry_hashtable();
|
|
852
|
+
let (first_at, second_at) = (first.parent_and_name(), second.parent_and_name());
|
|
853
|
+
RENAME_LOCK.write(|| {
|
|
854
|
+
let (mut one, mut other) = (first.state(), second.state());
|
|
855
|
+
for (state, (parent, name)) in [(&one, &first_at), (&other, &second_at)] {
|
|
856
|
+
if state.hashed {
|
|
857
|
+
table.remove(&key(parent, name));
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
table.insert(key(&second_at.0, &second_at.1), first);
|
|
861
|
+
table.insert(key(&first_at.0, &first_at.1), second);
|
|
862
|
+
std::mem::swap(&mut one.parent, &mut other.parent);
|
|
863
|
+
std::mem::swap(&mut one.name, &mut other.name);
|
|
864
|
+
(one.hashed, other.hashed) = (true, true);
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
/// Take `dentry` out of `table` where it is hashed, as Linux's `___d_drop`.
|
|
870
|
+
fn drop_hashed(table: &mut Hashtable<Key, Dentry>, dentry: &Arc<Dentry>) {
|
|
871
|
+
let (parent, name) = dentry.parent_and_name();
|
|
872
|
+
let mut state = dentry.state();
|
|
873
|
+
if state.hashed {
|
|
874
|
+
table.remove(&key(&parent, &name));
|
|
875
|
+
state.hashed = false;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
#[cfg(test)]
|
|
880
|
+
mod tests {
|
|
881
|
+
use std::any::Any;
|
|
882
|
+
use std::sync::Arc;
|
|
883
|
+
|
|
884
|
+
use super::{Dentries, Dentry};
|
|
885
|
+
use crate::filesystem::{
|
|
886
|
+
FileOperations, FileType, Filesystem, FilesystemError, InodeOperations, Kstat, Lookup,
|
|
887
|
+
Tmpfs,
|
|
888
|
+
};
|
|
889
|
+
use crate::mount::{Access, Mount};
|
|
890
|
+
use crate::vfs::kern_path::{kern_path, KernPath};
|
|
891
|
+
use crate::vfs::{self, File, Finish, Identity, Inode, OpenHow, Path};
|
|
892
|
+
|
|
893
|
+
/// A root of `dentries` standing for a directory, as `d_make_root` makes one.
|
|
894
|
+
fn make_dir_root(dentries: &Dentries) -> Arc<Dentry> {
|
|
895
|
+
let ops: Arc<dyn InodeOperations> = Arc::new(Tmpfs::new());
|
|
896
|
+
dentries.make_root(Inode::new(&Kstat::new(FileType::Dir, 0), ops))
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
/// The dentry of `name` in `parent`, a lookup having answered that nothing
|
|
900
|
+
/// stands there, so a walk finds it by its name.
|
|
901
|
+
fn negative(dentries: &Dentries, parent: &Arc<Dentry>, name: &str) -> Arc<Dentry> {
|
|
902
|
+
let made = dentries
|
|
903
|
+
.alloc_parallel(parent, name)
|
|
904
|
+
.expect("no other walk holds the name");
|
|
905
|
+
made.add(None);
|
|
906
|
+
made
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
/// A Filesystem answering each call by the path its dentry stands at,
|
|
910
|
+
/// as one written in Ruby does, so a removed directory it opened
|
|
911
|
+
/// reaches whatever stands at that path later and keeps no death of
|
|
912
|
+
/// its own.
|
|
913
|
+
struct ByPath {
|
|
914
|
+
root: KernPath,
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
/// The path `dentry` stands at beneath a `ByPath`'s root, as a path-based
|
|
918
|
+
/// Filesystem builds its own from `dentry_path_raw`.
|
|
919
|
+
fn at(dentry: &Dentry) -> String {
|
|
920
|
+
dentry.path_raw()[1..].to_owned()
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
/// A name it recorded nothing for is looked up again, as a Ruby Filesystem's
|
|
924
|
+
/// `revalidate` answers, so one it made is found where the make left it.
|
|
925
|
+
impl super::DentryOperations for ByPath {
|
|
926
|
+
fn revalidate(
|
|
927
|
+
&self,
|
|
928
|
+
_dir: &dyn InodeOperations,
|
|
929
|
+
dentry: &Dentry,
|
|
930
|
+
) -> Result<bool, FilesystemError> {
|
|
931
|
+
Ok(dentry.inode().is_some())
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
impl crate::vfs::FileSystemType for ByPath {
|
|
936
|
+
fn fill_super(self: Arc<Self>, sb: &mut Filesystem) -> Result<(), FilesystemError> {
|
|
937
|
+
sb.set_default_d_op(self.clone());
|
|
938
|
+
sb.d_make_root(Inode::new(&Kstat::new(FileType::Dir, 0), self));
|
|
939
|
+
Ok(())
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
impl InodeOperations for ByPath {
|
|
944
|
+
fn getattr(&self, dentry: &Dentry) -> Result<Kstat, FilesystemError> {
|
|
945
|
+
self.root.vfs_lstat(&at(dentry))
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
fn lookup(&self, dentry: &Dentry) -> Result<Lookup, FilesystemError> {
|
|
949
|
+
match self.root.vfs_lstat(&at(dentry)) {
|
|
950
|
+
Ok(found) => Ok(Lookup::Found(Inode::new(
|
|
951
|
+
&found,
|
|
952
|
+
Arc::new(Self {
|
|
953
|
+
root: self.root.clone(),
|
|
954
|
+
}),
|
|
955
|
+
))),
|
|
956
|
+
Err(FilesystemError::NotFound) => Ok(Lookup::Absent),
|
|
957
|
+
Err(err) => Err(err),
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
fn open(
|
|
962
|
+
&self,
|
|
963
|
+
dentry: &Dentry,
|
|
964
|
+
how: &OpenHow,
|
|
965
|
+
) -> Result<Box<dyn FileOperations>, FilesystemError> {
|
|
966
|
+
if dentry.kind() == Ok(FileType::Dir) {
|
|
967
|
+
return Ok(Box::new(ByPathDir(self.root.clone())));
|
|
968
|
+
}
|
|
969
|
+
self.root.open(&at(dentry), how)
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
fn atomic_open(&self, dentry: &Dentry, how: &OpenHow) -> Result<Finish, FilesystemError> {
|
|
973
|
+
if dentry.in_lookup() {
|
|
974
|
+
let found = self.lookup(dentry)?;
|
|
975
|
+
if !how.create || !matches!(found, Lookup::Absent) {
|
|
976
|
+
return Ok(Finish::Lookup(found));
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
if !how.create {
|
|
980
|
+
return Ok(Finish::NoOpen { created: false });
|
|
981
|
+
}
|
|
982
|
+
let file = self.root.create(&at(dentry), how)?;
|
|
983
|
+
Ok(Finish::Open {
|
|
984
|
+
file,
|
|
985
|
+
created: true,
|
|
986
|
+
})
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
fn mkdir(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
|
|
990
|
+
self.root.vfs_mkdir(&at(dentry))
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
fn rmdir(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
|
|
994
|
+
self.root.vfs_rmdir(&at(dentry))
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
fn unlink(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
|
|
998
|
+
self.root.vfs_unlink(&at(dentry))
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
fn rename(
|
|
1002
|
+
&self,
|
|
1003
|
+
from: &Dentry,
|
|
1004
|
+
to_dir: &dyn InodeOperations,
|
|
1005
|
+
to: &Dentry,
|
|
1006
|
+
_flag: Option<crate::vfs::RenameFlag>,
|
|
1007
|
+
) -> Result<(), FilesystemError> {
|
|
1008
|
+
(to_dir as &dyn Any)
|
|
1009
|
+
.downcast_ref::<Self>()
|
|
1010
|
+
.ok_or(FilesystemError::CrossesDevices)?;
|
|
1011
|
+
self.root.rename(&at(from), &self.root, &at(to))
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
/// A directory of a `ByPath`, open.
|
|
1016
|
+
struct ByPathDir(KernPath);
|
|
1017
|
+
|
|
1018
|
+
impl FileOperations for ByPathDir {
|
|
1019
|
+
fn iterate_shared(
|
|
1020
|
+
&mut self,
|
|
1021
|
+
dentry: &Dentry,
|
|
1022
|
+
ctx: &mut vfs::DirContext<'_>,
|
|
1023
|
+
) -> Result<(), FilesystemError> {
|
|
1024
|
+
let entries = match at(dentry).as_str() {
|
|
1025
|
+
"" => self.0.iterate_dir(),
|
|
1026
|
+
at => self.0.open_dir(at)?.iterate_dir(),
|
|
1027
|
+
}?;
|
|
1028
|
+
crate::filesystem::Rdir::default().iterate(dentry, ctx, || Ok(entries))
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
fn by_path(tmpfs: &Arc<Tmpfs>) -> Arc<Filesystem> {
|
|
1033
|
+
Filesystem::new(Arc::new(ByPath {
|
|
1034
|
+
root: kern_path(tmpfs.clone()),
|
|
1035
|
+
}))
|
|
1036
|
+
.expect("the instance must stand")
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
fn root_of(filesystem: &Arc<Filesystem>) -> Path {
|
|
1040
|
+
let mount = Mount::new("/data", filesystem.clone(), Access::ReadWrite)
|
|
1041
|
+
.expect("/data is a guest path");
|
|
1042
|
+
Path::root(mount.mnt())
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
fn open_dir(start: &Path, path: &str) -> Path {
|
|
1046
|
+
let how = OpenHow {
|
|
1047
|
+
read: true,
|
|
1048
|
+
directory: true,
|
|
1049
|
+
..OpenHow::default()
|
|
1050
|
+
};
|
|
1051
|
+
match vfs::kern_path::filp_open(&Arc::default(), start, path, &how) {
|
|
1052
|
+
Ok(dir) => dir.path().clone(),
|
|
1053
|
+
Err(_) => panic!("{path} must open as a directory"),
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
// Linux's `path_init` reads `rename_lock` with `__read_seqcount_begin`,
|
|
1058
|
+
// which spins past a write under way, so a walk begun during a move is
|
|
1059
|
+
// never answered EAGAIN for that move.
|
|
1060
|
+
// @behavior RT-265
|
|
1061
|
+
#[test]
|
|
1062
|
+
fn a_walk_begun_during_a_move_begins_once_it_lands() {
|
|
1063
|
+
use crate::locking::seqlock::SeqLock;
|
|
1064
|
+
use crate::sched::testing::MAX_WAIT;
|
|
1065
|
+
use std::sync::mpsc;
|
|
1066
|
+
use std::thread;
|
|
1067
|
+
|
|
1068
|
+
let lock = Arc::new(SeqLock::new());
|
|
1069
|
+
let (entered, entering) = mpsc::channel();
|
|
1070
|
+
let (release, releasing) = mpsc::channel::<()>();
|
|
1071
|
+
let writer = {
|
|
1072
|
+
let lock = lock.clone();
|
|
1073
|
+
thread::spawn(move || {
|
|
1074
|
+
lock.write(|| {
|
|
1075
|
+
entered.send(()).ok();
|
|
1076
|
+
releasing.recv_timeout(MAX_WAIT).ok();
|
|
1077
|
+
})
|
|
1078
|
+
})
|
|
1079
|
+
};
|
|
1080
|
+
entering
|
|
1081
|
+
.recv_timeout(MAX_WAIT)
|
|
1082
|
+
.expect("the write must begin");
|
|
1083
|
+
let reader = {
|
|
1084
|
+
let lock = lock.clone();
|
|
1085
|
+
thread::spawn(move || lock.read_seqbegin())
|
|
1086
|
+
};
|
|
1087
|
+
thread::sleep(std::time::Duration::from_millis(50));
|
|
1088
|
+
let waited = !reader.is_finished();
|
|
1089
|
+
release.send(()).ok();
|
|
1090
|
+
crate::sched::testing::join("the write", writer);
|
|
1091
|
+
let begun = crate::sched::testing::join("the read", reader);
|
|
1092
|
+
assert_eq!(
|
|
1093
|
+
(waited, begun, lock.read_seqretry(begun)),
|
|
1094
|
+
(true, 2, false),
|
|
1095
|
+
"SeqLock::read_seqbegin during a write must wait for it to end and answer where the \
|
|
1096
|
+
writes stand after it, which no later read finds changed"
|
|
1097
|
+
);
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
// `d_revalidate` compares what the dentry recorded first, so a later
|
|
1101
|
+
// lookup replacing it would hide the change it is there to see.
|
|
1102
|
+
#[test]
|
|
1103
|
+
fn a_dentry_keeps_what_a_lookup_first_found_there() {
|
|
1104
|
+
let ops: Arc<dyn InodeOperations> = Arc::new(Tmpfs::new());
|
|
1105
|
+
let (first, second) = (
|
|
1106
|
+
Kstat::new(FileType::Dir, 0).with_identity(1, 2),
|
|
1107
|
+
Kstat::new(FileType::Dir, 0).with_identity(1, 3),
|
|
1108
|
+
);
|
|
1109
|
+
let dentries = Dentries::new(None);
|
|
1110
|
+
let dentry = negative(&dentries, &make_dir_root(&dentries), "dir");
|
|
1111
|
+
dentry.add(Some(Inode::new(&first, ops.clone())));
|
|
1112
|
+
dentry.add(Some(Inode::new(&second, ops)));
|
|
1113
|
+
assert_eq!(
|
|
1114
|
+
dentry.inode().and_then(|inode| inode.identity()),
|
|
1115
|
+
Identity::of(&first),
|
|
1116
|
+
"Dentry::add after a negative lookup and then a positive one must keep the first \
|
|
1117
|
+
inode recorded"
|
|
1118
|
+
);
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
// The Filesystem answers by path, so only the dentry the removal marked
|
|
1122
|
+
// keeps the make from landing in the directory made in its place.
|
|
1123
|
+
// @behavior RT-159
|
|
1124
|
+
#[test]
|
|
1125
|
+
fn a_directory_removed_through_the_vfs_makes_nothing_more_whatever_its_filesystem() {
|
|
1126
|
+
let tmpfs: Arc<Tmpfs> = Arc::new([("dir/keep", "x")].into_iter().collect());
|
|
1127
|
+
let root = root_of(&by_path(&tmpfs));
|
|
1128
|
+
let dir = open_dir(&root, "dir");
|
|
1129
|
+
vfs::filename_unlinkat(&Arc::default(), &root, "dir/keep").expect("the file must go");
|
|
1130
|
+
vfs::filename_rmdir(&Arc::default(), &root, "dir").expect("the directory must go");
|
|
1131
|
+
vfs::filename_mkdirat(&Arc::default(), &root, "dir")
|
|
1132
|
+
.expect("another directory must be made");
|
|
1133
|
+
vfs::filename_mkdirat(&Arc::default(), &root, "dir/other")
|
|
1134
|
+
.expect("an entry must be made in it");
|
|
1135
|
+
assert_eq!(
|
|
1136
|
+
(
|
|
1137
|
+
vfs::filename_mkdirat(&Arc::default(), &dir, "made"),
|
|
1138
|
+
File::dentry_open(
|
|
1139
|
+
dir.clone(),
|
|
1140
|
+
&OpenHow {
|
|
1141
|
+
read: true,
|
|
1142
|
+
..OpenHow::default()
|
|
1143
|
+
},
|
|
1144
|
+
&Arc::default()
|
|
1145
|
+
)
|
|
1146
|
+
.expect("the directory must open")
|
|
1147
|
+
.iterate_dir(&mut vfs::DirContext::new(0, &mut |_| true)),
|
|
1148
|
+
kern_path(tmpfs.clone()).vfs_lstat("dir/made").err()
|
|
1149
|
+
),
|
|
1150
|
+
(
|
|
1151
|
+
Err(FilesystemError::NotFound),
|
|
1152
|
+
Err(FilesystemError::NotFound),
|
|
1153
|
+
Some(FilesystemError::NotFound)
|
|
1154
|
+
),
|
|
1155
|
+
"vfs::filename_mkdirat and File::iterate_dir through a directory vfs::filename_rmdir removed, over \
|
|
1156
|
+
a Filesystem answering by path, must each answer NotFound, as Linux asks \
|
|
1157
|
+
IS_DEADDIR, and make nothing in the directory made in its place"
|
|
1158
|
+
);
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
// @behavior RT-160
|
|
1162
|
+
#[test]
|
|
1163
|
+
fn a_directory_removed_beneath_one_mount_is_removed_beneath_another_of_its_instance() {
|
|
1164
|
+
let tmpfs: Arc<Tmpfs> = Arc::new([("dir/keep", "x")].into_iter().collect());
|
|
1165
|
+
let instance = by_path(&tmpfs);
|
|
1166
|
+
let (first, second) = (root_of(&instance), root_of(&instance));
|
|
1167
|
+
let dir = open_dir(&first, "dir");
|
|
1168
|
+
vfs::filename_unlinkat(&Arc::default(), &second, "dir/keep").expect("the file must go");
|
|
1169
|
+
vfs::filename_rmdir(&Arc::default(), &second, "dir").expect("the directory must go");
|
|
1170
|
+
vfs::filename_mkdirat(&Arc::default(), &second, "dir")
|
|
1171
|
+
.expect("another directory must be made");
|
|
1172
|
+
assert_eq!(
|
|
1173
|
+
vfs::filename_mkdirat(&Arc::default(), &dir, "made"),
|
|
1174
|
+
Err(FilesystemError::NotFound),
|
|
1175
|
+
"vfs::filename_mkdirat through a directory opened beneath one Mount, once vfs::filename_rmdir \
|
|
1176
|
+
removed it beneath another Mount of the same Filesystem, must answer NotFound"
|
|
1177
|
+
);
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
// Built directly, as a path cannot reach this deep. A depth a recursive
|
|
1181
|
+
// release cannot survive on this stack is what tells it from a loop.
|
|
1182
|
+
// @behavior RT-199
|
|
1183
|
+
#[test]
|
|
1184
|
+
fn a_dentry_goes_however_deep_it_stands() {
|
|
1185
|
+
let dentries = Dentries::new(None);
|
|
1186
|
+
let mut leaf = make_dir_root(&dentries);
|
|
1187
|
+
for _ in 0..200_000 {
|
|
1188
|
+
leaf = dentries
|
|
1189
|
+
.alloc_parallel(&leaf, "a")
|
|
1190
|
+
.expect("no other walk holds the name");
|
|
1191
|
+
}
|
|
1192
|
+
let released = std::thread::Builder::new()
|
|
1193
|
+
.stack_size(64 * 1024)
|
|
1194
|
+
.spawn(move || drop(leaf))
|
|
1195
|
+
.expect("the thread must start")
|
|
1196
|
+
.join();
|
|
1197
|
+
assert!(
|
|
1198
|
+
released.is_ok(),
|
|
1199
|
+
"a dentry 200000 directories deep, dropped on a 64 KiB stack, must go without \
|
|
1200
|
+
overflowing it"
|
|
1201
|
+
);
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
// Only a name kept beneath the invalidated directory is let go of: a name
|
|
1205
|
+
// beside it stays for the next walk.
|
|
1206
|
+
#[test]
|
|
1207
|
+
fn a_directory_let_go_of_takes_the_names_kept_beneath_it() {
|
|
1208
|
+
let dentries = Dentries::new(None);
|
|
1209
|
+
let root = make_dir_root(&dentries);
|
|
1210
|
+
let ops: Arc<dyn InodeOperations> = Arc::new(Tmpfs::new());
|
|
1211
|
+
let dir = dentries
|
|
1212
|
+
.alloc_parallel(&root, "dir")
|
|
1213
|
+
.expect("no other walk holds the name");
|
|
1214
|
+
dir.add(Some(Inode::new(&Kstat::new(FileType::Dir, 0), ops)));
|
|
1215
|
+
let kept = |dentry: Arc<Dentry>| {
|
|
1216
|
+
dentries.retain(&dentry);
|
|
1217
|
+
Arc::downgrade(&dentry)
|
|
1218
|
+
};
|
|
1219
|
+
let beneath = kept(negative(
|
|
1220
|
+
&dentries,
|
|
1221
|
+
&negative(&dentries, &dir, "sub"),
|
|
1222
|
+
"leaf",
|
|
1223
|
+
));
|
|
1224
|
+
let beside = kept(negative(&dentries, &root, "other"));
|
|
1225
|
+
dentries.invalidate(&dir);
|
|
1226
|
+
assert_eq!(
|
|
1227
|
+
(beneath.upgrade().is_some(), beside.upgrade().is_some()),
|
|
1228
|
+
(false, true),
|
|
1229
|
+
"Dentries::invalidate of a directory must let go of the names kept beneath it and \
|
|
1230
|
+
keep the others, as d_invalidate calls shrink_dcache_parent"
|
|
1231
|
+
);
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
// A walk let go of the dentry while another still held it, so only
|
|
1235
|
+
// refusing the unhashed one keeps that walk's retain from keeping a dentry
|
|
1236
|
+
// no walk can find, whose Filesystem state may be stale.
|
|
1237
|
+
#[test]
|
|
1238
|
+
fn a_dentry_let_go_of_is_not_kept_for_the_next_walk() {
|
|
1239
|
+
let dentries = Dentries::new(None);
|
|
1240
|
+
let dentry = negative(&dentries, &make_dir_root(&dentries), "gone");
|
|
1241
|
+
dentries.unhash(&dentry);
|
|
1242
|
+
dentries.retain(&dentry);
|
|
1243
|
+
let held = Arc::downgrade(&dentry);
|
|
1244
|
+
drop(dentry);
|
|
1245
|
+
assert!(
|
|
1246
|
+
held.upgrade().is_none(),
|
|
1247
|
+
"Dentries::retain of a dentry Dentries::unhash let go of must not keep it, as \
|
|
1248
|
+
retain_dentry refuses an unhashed one"
|
|
1249
|
+
);
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
#[test]
|
|
1253
|
+
fn the_root_stands_at_a_lone_slash() {
|
|
1254
|
+
let dentries = super::Dentries::new(None);
|
|
1255
|
+
assert_eq!(
|
|
1256
|
+
make_dir_root(&dentries).path_raw(),
|
|
1257
|
+
"/",
|
|
1258
|
+
"Dentry::path_raw of the root must answer a lone /, as dentry_path_raw answers"
|
|
1259
|
+
);
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
#[test]
|
|
1263
|
+
fn an_exchange_swaps_two_dentries() {
|
|
1264
|
+
let dentries = super::Dentries::new(None);
|
|
1265
|
+
let root = make_dir_root(&dentries);
|
|
1266
|
+
let a = negative(&dentries, &root, "a");
|
|
1267
|
+
let b = negative(&dentries, &negative(&dentries, &root, "d"), "b");
|
|
1268
|
+
dentries.exchange(&a, &b);
|
|
1269
|
+
assert_eq!(
|
|
1270
|
+
(
|
|
1271
|
+
a.path_raw(),
|
|
1272
|
+
b.path_raw(),
|
|
1273
|
+
dentries
|
|
1274
|
+
.lookup(&root, "a")
|
|
1275
|
+
.is_some_and(|found| Arc::ptr_eq(&found, &b))
|
|
1276
|
+
),
|
|
1277
|
+
("/d/b".to_string(), "/a".to_string(), true),
|
|
1278
|
+
"Dentries::exchange must hand each dentry the other's name, and find each by its new \
|
|
1279
|
+
name"
|
|
1280
|
+
);
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
/// Witnesses of a race, each run only by `rake crates:stress`: the race
|
|
1284
|
+
/// lands a few times in many tries, and only an optimized build makes
|
|
1285
|
+
/// that many in seconds.
|
|
1286
|
+
mod stress {
|
|
1287
|
+
use std::sync::atomic::{AtomicBool, Ordering};
|
|
1288
|
+
use std::sync::Arc;
|
|
1289
|
+
use std::thread;
|
|
1290
|
+
|
|
1291
|
+
use super::super::{Dentries, Dentry};
|
|
1292
|
+
use super::{make_dir_root, negative};
|
|
1293
|
+
|
|
1294
|
+
/// The dentry a rename onto `name` in `dir` replaces.
|
|
1295
|
+
fn target(dentries: &Dentries, dir: &Arc<Dentry>, name: &str) -> Arc<Dentry> {
|
|
1296
|
+
dentries
|
|
1297
|
+
.alloc_parallel(dir, name)
|
|
1298
|
+
.expect("no other walk holds the name")
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
// `x` is named `z` only while `b` stands beneath `y`, so `z/b/leaf`
|
|
1302
|
+
// is a path `leaf` never stood at: only a read taking `b`'s parent
|
|
1303
|
+
// before the moves and `x`'s name after them answers it.
|
|
1304
|
+
// @behavior RT-172
|
|
1305
|
+
#[test]
|
|
1306
|
+
#[ignore = "a stress witness; run by rake crates:stress"]
|
|
1307
|
+
fn a_path_asked_while_directories_above_it_move_names_one_that_stood() {
|
|
1308
|
+
let dentries = Arc::new(Dentries::new(None));
|
|
1309
|
+
let root = make_dir_root(&dentries);
|
|
1310
|
+
let (x, y) = (
|
|
1311
|
+
negative(&dentries, &root, "x"),
|
|
1312
|
+
negative(&dentries, &root, "y"),
|
|
1313
|
+
);
|
|
1314
|
+
let b = negative(&dentries, &x, "b");
|
|
1315
|
+
let leaf = negative(&dentries, &b, "leaf");
|
|
1316
|
+
let stop = Arc::new(AtomicBool::new(false));
|
|
1317
|
+
let moving = {
|
|
1318
|
+
let (dentries, stop) = (dentries.clone(), stop.clone());
|
|
1319
|
+
thread::spawn(move || {
|
|
1320
|
+
while !stop.load(Ordering::SeqCst) {
|
|
1321
|
+
dentries.move_dentry(&b, &target(&dentries, &y, "b"));
|
|
1322
|
+
dentries.move_dentry(&x, &target(&dentries, &root, "z"));
|
|
1323
|
+
dentries.move_dentry(&x, &target(&dentries, &root, "x"));
|
|
1324
|
+
dentries.move_dentry(&b, &target(&dentries, &x, "b"));
|
|
1325
|
+
}
|
|
1326
|
+
})
|
|
1327
|
+
};
|
|
1328
|
+
let torn = (0..5_000_000)
|
|
1329
|
+
.filter(|_| leaf.path_raw().starts_with("/z/"))
|
|
1330
|
+
.count();
|
|
1331
|
+
stop.store(true, Ordering::SeqCst);
|
|
1332
|
+
moving.join().expect("the moves must not panic");
|
|
1333
|
+
assert_eq!(
|
|
1334
|
+
torn, 0,
|
|
1335
|
+
"Dentry::path_raw while Dentries::move_dentry moves the directories above it must \
|
|
1336
|
+
answer only a path the dentry stood at"
|
|
1337
|
+
);
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
// `retain` holds the name's lock before the LRU's, so only deciding what
|
|
1341
|
+
// stands beneath the directory before taking the LRU's keeps the two
|
|
1342
|
+
// from each holding what the other waits on.
|
|
1343
|
+
// @behavior RT-331
|
|
1344
|
+
#[test]
|
|
1345
|
+
#[ignore = "a stress witness; run by rake crates:stress"]
|
|
1346
|
+
fn a_name_kept_while_the_names_beneath_its_directory_go_waits_on_neither() {
|
|
1347
|
+
let dentries = Arc::new(Dentries::new(None));
|
|
1348
|
+
let dir = negative(&dentries, &make_dir_root(&dentries), "dir");
|
|
1349
|
+
let leaf = negative(&dentries, &dir, "leaf");
|
|
1350
|
+
let stop = Arc::new(AtomicBool::new(false));
|
|
1351
|
+
let keeping = {
|
|
1352
|
+
let (dentries, stop, leaf) = (dentries.clone(), stop.clone(), leaf.clone());
|
|
1353
|
+
thread::spawn(move || {
|
|
1354
|
+
while !stop.load(Ordering::SeqCst) {
|
|
1355
|
+
dentries.retain(&leaf);
|
|
1356
|
+
}
|
|
1357
|
+
})
|
|
1358
|
+
};
|
|
1359
|
+
let shrinking = {
|
|
1360
|
+
let (dentries, stop) = (dentries.clone(), stop.clone());
|
|
1361
|
+
thread::spawn(move || {
|
|
1362
|
+
while !stop.load(Ordering::SeqCst) {
|
|
1363
|
+
dentries.shrink_dcache_parent(&dir);
|
|
1364
|
+
}
|
|
1365
|
+
})
|
|
1366
|
+
};
|
|
1367
|
+
thread::sleep(std::time::Duration::from_secs(2));
|
|
1368
|
+
stop.store(true, Ordering::SeqCst);
|
|
1369
|
+
crate::sched::testing::join(
|
|
1370
|
+
"Dentries::retain racing Dentries::shrink_dcache_parent",
|
|
1371
|
+
keeping,
|
|
1372
|
+
);
|
|
1373
|
+
crate::sched::testing::join(
|
|
1374
|
+
"Dentries::shrink_dcache_parent racing Dentries::retain",
|
|
1375
|
+
shrinking,
|
|
1376
|
+
);
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
// Each move keeps the dentry in the table, so only a read of its parent
|
|
1380
|
+
// before the move and its name after it finds nothing at that key.
|
|
1381
|
+
// @behavior RT-228
|
|
1382
|
+
#[test]
|
|
1383
|
+
#[ignore = "a stress witness; run by rake crates:stress"]
|
|
1384
|
+
fn a_directory_moving_through_the_vfs_is_never_taken_for_unlinked() {
|
|
1385
|
+
let dentries = Arc::new(Dentries::new(None));
|
|
1386
|
+
let root = make_dir_root(&dentries);
|
|
1387
|
+
let (x, y) = (
|
|
1388
|
+
negative(&dentries, &root, "x"),
|
|
1389
|
+
negative(&dentries, &root, "y"),
|
|
1390
|
+
);
|
|
1391
|
+
let moved = negative(&dentries, &x, "a");
|
|
1392
|
+
let stop = Arc::new(AtomicBool::new(false));
|
|
1393
|
+
let moving = {
|
|
1394
|
+
let (dentries, stop, moved) = (dentries.clone(), stop.clone(), moved.clone());
|
|
1395
|
+
thread::spawn(move || {
|
|
1396
|
+
while !stop.load(Ordering::SeqCst) {
|
|
1397
|
+
dentries.move_dentry(&moved, &target(&dentries, &y, "b"));
|
|
1398
|
+
dentries.move_dentry(&moved, &target(&dentries, &x, "a"));
|
|
1399
|
+
}
|
|
1400
|
+
})
|
|
1401
|
+
};
|
|
1402
|
+
let unlinked = (0..5_000_000).filter(|_| dentries.unlinked(&moved)).count();
|
|
1403
|
+
stop.store(true, Ordering::SeqCst);
|
|
1404
|
+
moving.join().expect("the moves must not panic");
|
|
1405
|
+
assert_eq!(
|
|
1406
|
+
unlinked, 0,
|
|
1407
|
+
"Dentries::unlinked while Dentries::move_dentry moves the dentry must answer that \
|
|
1408
|
+
it still stands"
|
|
1409
|
+
);
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
}
|