1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
//! What:
//! Who a marker's holder is:
//! the identity this process writes into a marker,
//! and the three answers a reader gives about a holder named in one:
//! gone,
//! running,
//! or unknown.
//! Why:
//! A marker names a process by its PID and start tick,
//! which mean something only in the boot and the PID namespace they were read in.
//! The identity therefore ends in the boot ID and the PID namespace,
//! a writer whose `/proc` numbers processes differently from the process itself writes none,
//! and a reader that cannot compare like with like answers unknown,
//! which keeps the lock and proves nothing,
//! instead of reading a number of another namespace as a local process and calling a live service gone.
//! The module is split from `service_index_marker.rs`,
//! which re-exports the public items here under their old names.
//!
//! In TS you'd write (pseudocode):
//! ```ts
//! const identity = ownIdentity(); const state = holderState(holder, readerView); // 'gone' | 'running' | 'unknown'
//! ```
use super::claim_time::current_boot_id;
use super::process_identity::{ProcessIdentityError, current_birth_identity, process_birth_identity};
use super::service_index_marker::{Marked, marker_of};
use std::path::Path;
use std::sync::OnceLock;
/// What separates the parts of the identity in a marker:
/// the birth identity,
/// the boot ID,
/// and the PID namespace.
pub const BOOT_SEPARATOR: char = '#';
/// What a reader makes of the holder a marker names.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum HolderState {
/// No such process,
/// a process born since,
/// or a boot that has ended.
Gone,
/// The process runs now,
/// in the same boot and the same PID namespace.
Running,
/// The reader cannot compare:
/// another PID namespace,
/// a `/proc` that does not agree with the reader's own PID,
/// no boot ID to compare,
/// or a record that cannot be read.
Unknown,
}
/// What a reader can say about its own numbering of processes.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ReaderView<'a> {
/// The boot ID of the host now,
/// empty where it cannot be read.
pub boot: &'a str,
/// The PID namespace of the reader,
/// where it can be read.
pub scope: Option<&'a str>,
/// Whether the reader's `/proc` numbers the reader as the operating system does.
pub procfs_agrees: bool,
}
/// The PID namespace of this process as `/proc/self/ns/pid` names it (the number inside `pid:[...]`),
/// once.
pub fn current_pid_scope() -> Option<&'static str> {
static SCOPE: OnceLock<Option<String>> = OnceLock::new();
return SCOPE
.get_or_init(|| {
let target = std::fs::read_link("/proc/self/ns/pid").ok()?;
let text: String = target.to_string_lossy().into_owned();
let number: &str = text.strip_prefix("pid:[")?.strip_suffix(']')?;
return (!number.is_empty() && number.bytes().all(|byte| { return byte.is_ascii_digit(); })).then(|| { return number.to_string(); });
})
.as_deref();
}
/// Whether this process's `/proc` numbers it as the operating system does,
/// once.
fn current_procfs_agrees() -> bool {
static AGREES: OnceLock<bool> = OnceLock::new();
return *AGREES.get_or_init(|| {
let Ok(stat) = std::fs::read("/proc/self/stat") else { return false };
return check_procfs_view(stat.as_slice(), std::process::id()).is_ok();
});
}
impl ReaderView<'static> {
/// What this process can say now.
pub fn current() -> ReaderView<'static> {
return ReaderView { boot: current_boot_id(), scope: current_pid_scope(), procfs_agrees: current_procfs_agrees() };
}
}
/// What:
/// The PID that a `/proc/self/stat` record names,
/// when it is one.
/// Why:
/// The record is the first thing a process reads of itself in `/proc`;
/// its first field is the PID as the mounted `/proc` numbers it.
/// The name of the process in the record may hold any byte,
/// so the record is read as bytes and only the leading digits are looked at.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// Number(stat.split(' ')[0])
/// ```
pub fn pid_of_self_stat(stat: &[u8]) -> Option<i64> {
let digits: &[u8] = stat.split(|byte| { return *byte == b' '; }).next()?;
return std::str::from_utf8(digits).ok()?.parse::<i64>().ok();
}
/// What:
/// This process's birth identity as a marker names it:
/// the identity,
/// the boot it was read in,
/// and the PID namespace.
/// Why:
/// A PID and a start tick since boot recur after a reboot and mean another process in another PID namespace,
/// so a marker is told by the boot ID and the namespace.
/// A `/proc` that belongs to another PID namespace numbers processes differently from the process itself,
/// and an identity read there names another process,
/// so a process whose `/proc` does not agree with its own PID takes no lock.
/// On Linux a process that cannot name its boot or its namespace takes no lock either:
/// a marker without them cannot be told from a reboot's collision.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// `${await birthIdentity(process.pid)}#${bootId}#${pidNamespace}`
/// ```
///
/// # Errors
///
/// The identity error when `/proc` is another namespace's,
/// the boot or the namespace is unknown,
/// or the identity cannot be read.
pub fn own_identity() -> Result<String, ProcessIdentityError> {
let own: u32 = std::process::id();
#[cfg(target_os = "linux")]
let stat: Vec<u8> = std::fs::read("/proc/self/stat").map_err(|error| { return ProcessIdentityError::Io { pid: i64::from(own), error }; })?;
#[cfg(not(target_os = "linux"))]
let stat: Vec<u8> = format!("{own} ").into_bytes();
return identity_in_view(stat.as_slice(), own, current_boot_id(), current_pid_scope());
}
/// `own_identity` for the process `own`,
/// whose `/proc/self/stat` reads `stat`,
/// on a host whose boot is `boot` and in the PID namespace `scope`.
///
/// # Errors
///
/// As `own_identity`;
/// `ProcMismatch` when `stat` names another PID than `own`,
/// `BootUnavailable` and `ScopeUnavailable` on Linux when the boot or the namespace is not known.
pub fn identity_in_view(stat: &[u8], own: u32, boot: &str, scope: Option<&str>) -> Result<String, ProcessIdentityError> {
check_procfs_view(stat, own)?;
let birth: String = current_birth_identity()?;
#[cfg(target_os = "linux")]
{
if boot.is_empty() {
return Err(ProcessIdentityError::BootUnavailable);
}
if scope.is_none() {
return Err(ProcessIdentityError::ScopeUnavailable);
}
}
let mut identity: String = birth;
for part in [Some(boot).filter(|text| { return !text.is_empty(); }), scope] {
if let Some(text) = part {
identity.push(BOOT_SEPARATOR);
identity.push_str(text);
}
}
return Ok(identity);
}
/// Whether the `/proc/self/stat` record `stat` numbers the process `own` the way the process itself is numbered.
///
/// # Errors
///
/// `ProcMismatch` naming both numbers when `/proc` belongs to another PID namespace,
/// and `MalformedStat` when the record names no PID.
pub fn check_procfs_view(stat: &[u8], own: u32) -> Result<(), ProcessIdentityError> {
let procfs: i64 = pid_of_self_stat(stat).ok_or(ProcessIdentityError::MalformedStat { pid: i64::from(own) })?;
if procfs != i64::from(own) {
return Err(ProcessIdentityError::ProcMismatch { procfs, own });
}
return Ok(());
}
/// The birth identity,
/// the boot ID and the PID namespace a marker names;
/// a marker an earlier release wrote names no boot,
/// and one of the release before the namespace was recorded names no namespace.
fn split_birth(birth: &str) -> (&str, Option<&str>, Option<&str>) {
let mut parts = birth.splitn(3, BOOT_SEPARATOR);
let identity: &str = parts.next().unwrap_or(birth);
return (identity, parts.next(), parts.next());
}
/// What:
/// What a reader with the view `view` makes of `holder`,
/// asking `ask` about the process.
/// Why:
/// A boot that has ended ends every process in it,
/// whatever the reader's numbering.
/// Beyond that the reader compares like with like or says unknown:
/// its `/proc` must agree with its own PID,
/// it must know its boot when the marker names one,
/// and its PID namespace must be the marker's when the marker names one.
/// A holder the marker does not name (an earlier release's marker) is gone,
/// as such markers always were.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// holderStateIn(holder, view, ask) // 'gone' | 'running' | 'unknown'
/// ```
pub fn holder_state_in(
holder: Option<&(i64, String)>,
view: &ReaderView,
ask: &dyn Fn(i64) -> Result<Option<String>, ProcessIdentityError>,
) -> HolderState {
let Some((pid, birth)) = holder else { return HolderState::Gone };
let (identity, boot, scope) = split_birth(birth.as_str());
if let Some(recorded) = boot
&& !view.boot.is_empty()
&& recorded != view.boot
{
return HolderState::Gone;
}
if !view.procfs_agrees || (boot.is_some() && view.boot.is_empty()) {
return HolderState::Unknown;
}
if let Some(recorded) = scope
&& view.scope != Some(recorded)
{
return HolderState::Unknown;
}
return match ask(*pid) {
Ok(None) => HolderState::Gone,
Ok(Some(now)) if now == identity => HolderState::Running,
Ok(Some(_)) => HolderState::Gone,
Err(_) => HolderState::Unknown,
};
}
/// What this process makes of `holder` now.
pub fn holder_state(holder: Option<&(i64, String)>) -> HolderState {
return holder_state_in(holder, &ReaderView::current(), &process_birth_identity);
}
/// Whether the process a marker names is gone;
/// a question that cannot be answered keeps the lock,
/// which is the safe side.
pub fn holder_is_gone(holder: Option<&(i64, String)>) -> bool {
return holder_state(holder) == HolderState::Gone;
}
/// Whether the process a marker names is proven to run now.
pub fn holder_is_running(holder: Option<&(i64, String)>) -> bool {
return holder_state(holder) == HolderState::Running;
}
/// Whether the lock at `lock` carries the service's mark naming a holder that is gone:
/// a dead instance's leftover,
/// which the next landing removes.
/// A mark that names no holder (one an earlier release wrote) is not claimed here,
/// because its age is the only evidence a reader has of it.
pub fn mark_names_a_gone_holder(lock: &Path) -> bool {
return marker_of(lock).is_some_and(|marked: Marked| { return marked.holder.is_some() && holder_is_gone(marked.holder.as_ref()); });
}