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
275
276
//! System capability detection and on-disk cache (1.1.0).
//!
//! `fsys` 1.0 probed hardware lazily on first access and cached the
//! result in a process-wide `OnceLock`. That worked for the hardware
//! probe — the values were cheap to compute and didn't influence
//! backend selection.
//!
//! 1.1.0 introduces a richer capability surface (SPDK eligibility, IOMMU
//! groups, hugepage configuration, kernel feature flags) that costs
//! tens of milliseconds to probe end-to-end. Re-running that probe on
//! every process start is wasteful; the answer rarely changes between
//! runs on the same machine. This module caches the result to disk
//! and invalidates only when something the answer depends on has
//! changed.
//!
//! ## Cache file location
//!
//! - **Linux / macOS:** `$XDG_CACHE_HOME/fsys/capabilities.toml`,
//! falling back to `$HOME/.cache/fsys/capabilities.toml`.
//! - **Windows:** `%LOCALAPPDATA%\fsys\capabilities.toml`.
//! - **Override:** set the `FSYS_CACHE_DIR` environment variable to
//! choose an arbitrary directory (handy for tests and containers).
//!
//! ## Cache invalidation
//!
//! The cache is re-probed when **any** of these are true:
//!
//! 1. The fsys version embedded in the file does not match this
//! crate's `CARGO_PKG_VERSION`.
//! 2. The kernel version (Linux/macOS) or build number (Windows) in
//! the file does not match the live value.
//! 3. The schema version in the file does not match
//! [`Capabilities::SCHEMA_VERSION`].
//! 4. The cache file's modification time is older than 30 days.
//! 5. The `FSYS_REPROBE` environment variable is set to `1`.
//! 6. The cache file is missing, unreadable, or fails to parse.
//!
//! ## Public API
//!
//! - [`capabilities()`] — returns the cached snapshot (or runs the
//! probe + writes the cache on first call). Sub-millisecond on
//! cache hit.
//! - [`probe_capabilities_fresh()`] — forces a re-probe, ignoring the
//! cache. Writes the new result to the cache file.
//! - [`invalidate_capability_cache()`] — deletes the cache file so
//! the next [`capabilities()`] call re-probes.
//!
//! ## What lives here vs. [`crate::hardware`]
//!
//! `hardware` is the foundational probe used by every fsys handle —
//! drive identity, CPU features, IO primitive availability. Those
//! probes are cheap (microseconds) and run in-process; no cache
//! file is justified.
//!
//! `capability` is the *user-space backend eligibility* layer that
//! 1.1.0 builds on top: which optional backends (SPDK, future
//! PMEM / RDMA) the system can host. These probes are expensive
//! (`/sys/bus/pci/...` walk, `/proc/meminfo` parse, IOMMU group
//! enumeration) and the answer is stable across runs, so they cache
//! to disk. Internally, [`Capabilities`] re-exports the relevant
//! subset of [`crate::hardware::HardwareInfo`] for convenience.
pub use ;
use OnceLock;
/// Schema version embedded in the on-disk cache file.
///
/// Bumped whenever the cache file's serialised shape changes in a
/// way that requires re-probing. Bumping this constant forces every
/// existing cache to be invalidated on the next [`capabilities()`]
/// call, regardless of whether the kernel version, fsys version, or
/// age would have triggered re-probing on their own.
pub const CAPABILITY_CACHE_SCHEMA_VERSION: u32 = 1;
/// Cache file age threshold beyond which the entry is considered stale.
///
/// 30 days. Hardware and OS state rarely change inside this window
/// on a server; CI runners may re-probe more often via
/// `FSYS_REPROBE=1`.
pub const CAPABILITY_CACHE_MAX_AGE_DAYS: u64 = 30;
/// Process-wide cache. First call to [`capabilities()`] populates it
/// (reading the disk cache when valid, running the probe + writing
/// the disk cache otherwise). Subsequent calls return the same
/// reference.
static CAPABILITIES: = new;
/// Returns the cached system capability snapshot.
///
/// The first call after process start may take 50-200 ms (full
/// probe). Subsequent calls return a borrowed reference to the same
/// data and complete in well under one millisecond.
///
/// The probe **never panics**. Sub-probes that fail (missing
/// `/proc/meminfo`, denied PCI enumeration, etc.) record their
/// failure as a [`SpdkSkipReason`] on the returned snapshot; the
/// rest of the crate continues to operate using whatever data the
/// probe did manage to collect.
///
/// # Examples
///
/// ```
/// let caps = fsys::capability::capabilities();
/// // Every fsys host has at least conservative defaults.
/// let _hw = &caps.hardware;
/// ```
/// Runs a fresh probe, ignoring any cached data on disk.
///
/// Updates the on-disk cache file with the new result on success.
/// Use this from diagnostic tooling, after a system reconfiguration
/// (e.g. just enabled IOMMU in the kernel command line), or whenever
/// you specifically want the live answer rather than the cached one.
///
/// Note: the process-wide [`OnceLock`] returned by [`capabilities()`]
/// is **not** updated by this call. The next process start is when
/// the cached snapshot is rebuilt.
/// Deletes the on-disk capability cache file.
///
/// The next call to [`capabilities()`] from a new process will
/// re-probe and rewrite the cache. Returns `Ok(())` whether the
/// file existed or not (deleting a non-existent file is treated
/// as success).
///
/// # Errors
///
/// Returns the underlying [`std::io::Error`] if the file existed
/// but could not be deleted (permissions, hardware error, etc.).
/// Runs the full probe — no cache, no shortcuts. Called by
/// [`capabilities()`] on first access and by
/// [`probe_capabilities_fresh()`] explicitly.
/// Returns the current wall-clock time as seconds since the Unix
/// epoch. Uses [`SystemTime::now`] internally; clamps to 0 if the
/// system clock is set before 1970 (degenerate but defensible).