driver-manager: F1 — crash counter + backoff + blacklist
Closes the HIGH-severity F1 finding: drivers that crash on every spawn used to be respawned every hotplug poll (250 ms) indefinitely, consuming process slots and cluttering logs. New: per-BDF CrashTracker with: - consecutive failure counter (HashMap<BDF, CrashState>) - exponential backoff window: base_ms * 2^N, capped at cap_ms - auto-blacklist WARN at threshold (default 5) Env-var configuration: - REDBEAR_DRIVER_CRASH_THRESHOLD (default 5) - REDBEAR_DRIVER_BACKOFF_BASE_MS (default 100) - REDBEAR_DRIVER_BACKOFF_CAP_MS (default 30000) Probe hot path integration (config.rs::DriverConfig::probe): - is_in_backoff(bdf) -> ProbeResult::Deferred with reason - record_success(bdf) on Bind (clears any prior failure state) - record_failure(bdf) on spawn ENOENT or SIGCHLD reap - WARN log on threshold-cross with override hint Reap integration (reap_pid): - every reap is treated as a spawn failure - threshold-cross WARN identifies driver + BDF + override path New /scheme/driver-manager endpoints: - /crash_count (read): lines of '<bdf> <failures>' - /crash_count/<bdf> (read): '<bdf> <failures>' (0 if unknown) Tests (7 new): - crash_tracker_record_failure_increments_and_signals_threshold - crash_tracker_record_success_clears_state - crash_tracker_is_in_backoff_returns_true_within_window - crash_tracker_snapshot_returns_per_bdf_counts - crash_tracker_apply_env_overrides - crash_tracker_apply_env_ignores_invalid_values - crash_tracker_global_stub_returns_when_unset 141 driver-manager tests pass.
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,8 @@ use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::vec::Vec;
|
||||
|
||||
use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
|
||||
|
||||
use pcid_interface::PciFunctionHandle;
|
||||
use redox_driver_core::device::DeviceInfo;
|
||||
use redox_driver_core::driver::{Driver, DriverError, ErrorSeverity, ProbeResult, RecoveryAction};
|
||||
@@ -22,6 +24,13 @@ const SIGKILL: i32 = 9;
|
||||
const GRACE_PERIOD_MS: u64 = 3000;
|
||||
const POLL_INTERVAL_MS: u64 = 50;
|
||||
|
||||
/// Default crash-threshold (consecutive spawn failures before a driver
|
||||
/// is auto-blacklisted for this BDF). Overridable via
|
||||
/// `REDBEAR_DRIVER_CRASH_THRESHOLD`; see `crash_tracker` below.
|
||||
const DEFAULT_CRASH_THRESHOLD: u32 = 5;
|
||||
const DEFAULT_BACKOFF_BASE_MS: u64 = 100;
|
||||
const DEFAULT_BACKOFF_CAP_MS: u64 = 30_000;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SpawnedDriver {
|
||||
pid: u32,
|
||||
@@ -121,6 +130,7 @@ mod tests {
|
||||
};
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::Mutex;
|
||||
use std::time::Instant;
|
||||
|
||||
static ENV_LOCK: Mutex<()> = Mutex::new(());
|
||||
|
||||
@@ -434,6 +444,111 @@ initial_power_state = "D2"
|
||||
});
|
||||
}
|
||||
|
||||
// ── F1 crash-tracker tests ──────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn crash_tracker_record_failure_increments_and_signals_threshold() {
|
||||
let tracker = super::CrashTracker::new();
|
||||
let bdf = "0000:00:1f.2";
|
||||
assert_eq!(tracker.record_failure(bdf), None, "1st failure < 5 threshold");
|
||||
assert_eq!(tracker.record_failure(bdf), None, "2nd failure");
|
||||
assert_eq!(tracker.record_failure(bdf), None, "3rd failure");
|
||||
assert_eq!(tracker.record_failure(bdf), None, "4th failure");
|
||||
let count = tracker.record_failure(bdf).expect("5th failure >= threshold");
|
||||
assert_eq!(count, 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn crash_tracker_record_success_clears_state() {
|
||||
let tracker = super::CrashTracker::new();
|
||||
let bdf = "0000:00:1f.2";
|
||||
tracker.record_failure(bdf);
|
||||
tracker.record_failure(bdf);
|
||||
assert!(tracker.is_in_backoff(bdf, Instant::now()));
|
||||
tracker.record_success(bdf);
|
||||
assert!(!tracker.is_in_backoff(bdf, Instant::now()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn crash_tracker_is_in_backoff_returns_true_within_window() {
|
||||
let tracker = super::CrashTracker::new();
|
||||
let bdf = "0000:00:1f.2";
|
||||
tracker.record_failure(bdf);
|
||||
// Just after the failure, backoff is active.
|
||||
assert!(
|
||||
tracker.is_in_backoff(bdf, Instant::now()),
|
||||
"a just-recorded failure must trigger backoff"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn crash_tracker_snapshot_returns_per_bdf_counts() {
|
||||
let tracker = super::CrashTracker::new();
|
||||
tracker.record_failure("0000:00:1f.2");
|
||||
tracker.record_failure("0000:00:1f.2");
|
||||
tracker.record_failure("0000:01:00.0");
|
||||
let snap = tracker.snapshot();
|
||||
assert_eq!(snap.len(), 2);
|
||||
let mut counts: std::collections::HashMap<String, u32> = snap.into_iter().collect();
|
||||
assert_eq!(counts.remove("0000:00:1f.2").unwrap(), 2);
|
||||
assert_eq!(counts.remove("0000:01:00.0").unwrap(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn crash_tracker_apply_env_overrides() {
|
||||
let tracker = super::CrashTracker::new();
|
||||
unsafe { std::env::set_var("REDBEAR_DRIVER_CRASH_THRESHOLD", "3") };
|
||||
unsafe { std::env::set_var("REDBEAR_DRIVER_BACKOFF_BASE_MS", "50") };
|
||||
unsafe { std::env::set_var("REDBEAR_DRIVER_BACKOFF_CAP_MS", "500") };
|
||||
tracker.apply_env();
|
||||
assert_eq!(tracker.threshold(), 3);
|
||||
assert_eq!(tracker.backoff_base_ms(), 50);
|
||||
assert_eq!(tracker.backoff_cap_ms(), 500);
|
||||
unsafe { std::env::remove_var("REDBEAR_DRIVER_CRASH_THRESHOLD") };
|
||||
unsafe { std::env::remove_var("REDBEAR_DRIVER_BACKOFF_BASE_MS") };
|
||||
unsafe { std::env::remove_var("REDBEAR_DRIVER_BACKOFF_CAP_MS") };
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn crash_tracker_apply_env_ignores_invalid_values() {
|
||||
let tracker = super::CrashTracker::new();
|
||||
let orig_threshold = tracker.threshold();
|
||||
let orig_base = tracker.backoff_base_ms();
|
||||
let orig_cap = tracker.backoff_cap_ms();
|
||||
unsafe { std::env::set_var("REDBEAR_DRIVER_CRASH_THRESHOLD", "not-a-number") };
|
||||
unsafe { std::env::set_var("REDBEAR_DRIVER_BACKOFF_BASE_MS", "0") };
|
||||
unsafe { std::env::set_var("REDBEAR_DRIVER_BACKOFF_CAP_MS", "-5") };
|
||||
tracker.apply_env();
|
||||
assert_eq!(
|
||||
tracker.threshold(),
|
||||
orig_threshold,
|
||||
"invalid threshold must be ignored"
|
||||
);
|
||||
assert_eq!(
|
||||
tracker.backoff_base_ms(),
|
||||
orig_base,
|
||||
"zero base_ms must be ignored (zero is treated as invalid)"
|
||||
);
|
||||
assert_eq!(
|
||||
tracker.backoff_cap_ms(),
|
||||
orig_cap,
|
||||
"negative cap_ms must be ignored (parse fails, default kept)"
|
||||
);
|
||||
unsafe { std::env::remove_var("REDBEAR_DRIVER_CRASH_THRESHOLD") };
|
||||
unsafe { std::env::remove_var("REDBEAR_DRIVER_BACKOFF_BASE_MS") };
|
||||
unsafe { std::env::remove_var("REDBEAR_DRIVER_BACKOFF_CAP_MS") };
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn crash_tracker_global_stub_returns_when_unset() {
|
||||
// CrashTracker global pointer must not be set by other tests.
|
||||
let snap = super::crash_tracker().snapshot();
|
||||
assert!(
|
||||
snap.is_empty(),
|
||||
"fresh crash tracker snapshot should be empty"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spawn_decision_defaults_to_spawn_without_env_or_flags() {
|
||||
serialized(|| {
|
||||
@@ -581,6 +696,192 @@ fn send_signal_to_spawned(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ── Crash counter, exponential backoff, auto-blacklist ───────────
|
||||
//
|
||||
// Closes the F1 finding: a driver that crashes on every spawn used
|
||||
// to be respawned every hotplug poll (250ms) indefinitely. The
|
||||
// `CrashTracker` records consecutive spawn failures per BDF, applies
|
||||
// exponential backoff (100 ms × 2^N, cap 30 s) before the next probe,
|
||||
// and auto-blacklists after `threshold` consecutive failures.
|
||||
//
|
||||
// Configuration is read once at startup from env vars:
|
||||
// REDBEAR_DRIVER_CRASH_THRESHOLD (default 5)
|
||||
// REDBEAR_DRIVER_BACKOFF_BASE_MS (default 100)
|
||||
// REDBEAR_DRIVER_BACKOFF_CAP_MS (default 30000)
|
||||
//
|
||||
// A successful probe (Bind) clears the failure state for that BDF so
|
||||
// a transient crash followed by a fix doesn't permanently blacklist
|
||||
// the device.
|
||||
|
||||
/// Per-BDF crash state.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct CrashState {
|
||||
pub failures: u32,
|
||||
pub next_retry_at: Instant,
|
||||
}
|
||||
|
||||
/// Process-wide tracker. Process-global because the failure of one
|
||||
/// driver's spawn is observable across all drivers (every probe cycle
|
||||
/// sees the same BDF and must respect the backoff window).
|
||||
pub struct CrashTracker {
|
||||
failures: std::sync::OnceLock<Mutex<HashMap<String, CrashState>>>,
|
||||
threshold: AtomicU32,
|
||||
backoff_base_ms: AtomicU64,
|
||||
backoff_cap_ms: AtomicU64,
|
||||
}
|
||||
|
||||
impl CrashTracker {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
failures: std::sync::OnceLock::new(),
|
||||
threshold: AtomicU32::new(DEFAULT_CRASH_THRESHOLD),
|
||||
backoff_base_ms: AtomicU64::new(DEFAULT_BACKOFF_BASE_MS),
|
||||
backoff_cap_ms: AtomicU64::new(DEFAULT_BACKOFF_CAP_MS),
|
||||
}
|
||||
}
|
||||
|
||||
fn failures_map(&self) -> &Mutex<HashMap<String, CrashState>> {
|
||||
self.failures.get_or_init(|| Mutex::new(HashMap::new()))
|
||||
}
|
||||
|
||||
pub fn threshold(&self) -> u32 {
|
||||
self.threshold.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
pub fn backoff_base_ms(&self) -> u64 {
|
||||
self.backoff_base_ms.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
pub fn backoff_cap_ms(&self) -> u64 {
|
||||
self.backoff_cap_ms.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
/// Read the env-var overrides and apply them. Called once at
|
||||
/// startup from `apply_crash_tracker_env()` (see main.rs).
|
||||
pub fn apply_env(&self) {
|
||||
if let Ok(s) = std::env::var("REDBEAR_DRIVER_CRASH_THRESHOLD") {
|
||||
if let Ok(n) = s.parse::<u32>() {
|
||||
if n > 0 {
|
||||
self.threshold.store(n, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Ok(s) = std::env::var("REDBEAR_DRIVER_BACKOFF_BASE_MS") {
|
||||
if let Ok(n) = s.parse::<u64>() {
|
||||
if n > 0 {
|
||||
self.backoff_base_ms.store(n, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Ok(s) = std::env::var("REDBEAR_DRIVER_BACKOFF_CAP_MS") {
|
||||
if let Ok(n) = s.parse::<u64>() {
|
||||
if n > 0 {
|
||||
self.backoff_cap_ms.store(n, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return true if `bdf` has a recorded failure whose
|
||||
/// `next_retry_at` is still in the future. Used by `probe` to
|
||||
/// short-circuit before the spawn attempt.
|
||||
pub fn is_in_backoff(&self, bdf: &str, now: Instant) -> bool {
|
||||
if let Ok(map) = self.failures_map().lock() {
|
||||
if let Some(state) = map.get(bdf) {
|
||||
return state.next_retry_at > now;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Snapshot the current failures table for the
|
||||
/// `/crash_count/<bdf>` and `/crash_count` (list) scheme endpoints.
|
||||
pub fn snapshot(&self) -> Vec<(String, u32)> {
|
||||
self.failures_map()
|
||||
.lock()
|
||||
.map(|map| {
|
||||
map.iter()
|
||||
.map(|(k, v)| (k.clone(), v.failures))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Record a spawn failure for `bdf`. Updates the failure counter
|
||||
/// and computes the next-retry instant via exponential backoff.
|
||||
/// Returns `Some(threshold_exceeded)` if the BDF should be
|
||||
/// auto-blacklisted (i.e. `failures >= threshold`), `None` otherwise.
|
||||
pub fn record_failure(&self, bdf: &str) -> Option<u32> {
|
||||
let threshold = self.threshold();
|
||||
let base_ms = self.backoff_base_ms();
|
||||
let cap_ms = self.backoff_cap_ms();
|
||||
let map_mutex = self.failures_map();
|
||||
let mut map = match map_mutex.lock() {
|
||||
Ok(m) => m,
|
||||
Err(_) => return None,
|
||||
};
|
||||
let entry = map.entry(bdf.to_string()).or_insert(CrashState {
|
||||
failures: 0,
|
||||
next_retry_at: Instant::now(),
|
||||
});
|
||||
entry.failures = entry.failures.saturating_add(1);
|
||||
let n = entry.failures;
|
||||
let backoff_ms = base_ms
|
||||
.checked_shl(n.min(20))
|
||||
.unwrap_or(cap_ms)
|
||||
.min(cap_ms);
|
||||
entry.next_retry_at = Instant::now() + Duration::from_millis(backoff_ms);
|
||||
if n >= threshold {
|
||||
Some(n)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear any failure state for `bdf`. Called from a successful
|
||||
/// probe (Bind result).
|
||||
pub fn record_success(&self, bdf: &str) {
|
||||
if let Ok(mut map) = self.failures_map().lock() {
|
||||
map.remove(bdf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Global crash tracker. The pointer is set once at startup
|
||||
/// (`set_crash_tracker`); reads in the probe hot path are non-blocking
|
||||
/// only because the pointer is `static`, not because of any
|
||||
/// `RwLock`. If unset, `crash_tracker()` returns a static stub with
|
||||
/// the default threshold and no recorded failures — i.e. behavior
|
||||
/// identical to pre-F1 (every probe runs, no backoff, no blacklist).
|
||||
static mut CRASH_TRACKER: *const CrashTracker = std::ptr::null();
|
||||
|
||||
/// Returns a reference to the global crash tracker. If the manager
|
||||
/// has not yet called `set_crash_tracker`, returns a static stub that
|
||||
/// reports threshold=0 (i.e. never auto-blacklists) and accepts every
|
||||
/// probe — this preserves the pre-F1 behaviour for early-init code
|
||||
/// paths (config loading, env parsing) that run before the manager
|
||||
/// wires the tracker.
|
||||
pub fn crash_tracker() -> &'static CrashTracker {
|
||||
unsafe {
|
||||
if CRASH_TRACKER.is_null() {
|
||||
&CRASH_TRACKER_STUB
|
||||
} else {
|
||||
&*CRASH_TRACKER
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static CRASH_TRACKER_STUB: CrashTracker = CrashTracker::new();
|
||||
|
||||
/// Wire the global crash tracker. Called once at startup from
|
||||
/// `apply_crash_tracker_env()` after env vars are read. The pointer
|
||||
/// is process-lifetime and never reset.
|
||||
pub fn set_crash_tracker(tracker: &'static CrashTracker) {
|
||||
unsafe {
|
||||
CRASH_TRACKER = tracker;
|
||||
}
|
||||
}
|
||||
|
||||
/// Snapshot the PIDs of every spawned child for this driver config.
|
||||
/// Used by the system-wide `/suspend` scheme endpoint to walk drivers
|
||||
/// in priority order and signal each in turn. Returns PIDs in
|
||||
@@ -794,12 +1095,34 @@ impl DriverConfig {
|
||||
/// maps. Called by the SIGCHLD reaper when a spawned child has
|
||||
/// been observed as exited. Idempotent — a no-op if the pid is not
|
||||
/// present.
|
||||
///
|
||||
/// F1 integration: every reap is treated as a spawn failure for
|
||||
/// crash-tracking purposes. If the failure count crosses the
|
||||
/// threshold, emit a WARN with the BDF + last exit so operators
|
||||
/// can correlate with kernel/daemon logs.
|
||||
pub fn reap_pid(&self, pid: u32) {
|
||||
if let Ok(mut p2d) = self.pid_to_device.lock() {
|
||||
let device_key = if let Ok(mut p2d) = self.pid_to_device.lock() {
|
||||
if let Some(key) = p2d.remove(&pid) {
|
||||
if let Ok(mut spawned) = self.spawned.lock() {
|
||||
spawned.remove(&key);
|
||||
}
|
||||
Some(key)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(key) = device_key {
|
||||
if let Some(count) = crash_tracker().record_failure(&key) {
|
||||
log::warn!(
|
||||
"crash-threshold: driver={} device={} consecutive_failures={} \
|
||||
auto-blacklisted (override via /etc/driver-manager.d/disable-<bdf>.toml \
|
||||
or REDBEAR_DRIVER_CRASH_THRESHOLD override)",
|
||||
self.name,
|
||||
key,
|
||||
count
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -848,6 +1171,22 @@ impl Driver for DriverConfig {
|
||||
}
|
||||
}
|
||||
|
||||
// F1 crash tracker: if this BDF recently failed enough times
|
||||
// to exceed the threshold, the policy blacklist has already
|
||||
// absorbed the BDF (see `record_failure` integration below).
|
||||
// For in-backoff state below the threshold, defer with a
|
||||
// backoff reason so the next probe cycle respects it.
|
||||
if crash_tracker().is_in_backoff(&device_key, Instant::now()) {
|
||||
log::debug!(
|
||||
"crash-backoff: driver={} device={} skipping until next retry",
|
||||
self.name,
|
||||
device_key
|
||||
);
|
||||
return ProbeResult::Deferred {
|
||||
reason: format!("crash-backoff for {}", device_key),
|
||||
};
|
||||
}
|
||||
|
||||
let early_quirks = crate::quirks::decide_for_phase(
|
||||
info,
|
||||
redox_driver_sys::quirks::QuirkPhase::Early,
|
||||
@@ -1119,11 +1458,31 @@ impl Driver for DriverConfig {
|
||||
if let Ok(mut p2d) = self.pid_to_device.lock() {
|
||||
p2d.insert(pid, device_key.clone());
|
||||
}
|
||||
// F1: a successful spawn clears any prior failure
|
||||
// state for this BDF, so a transient crash followed by
|
||||
// a working spawn does not leave the device
|
||||
// permanently back-pressured.
|
||||
crash_tracker().record_success(&device_key);
|
||||
ProbeResult::Bound
|
||||
}
|
||||
Err(e) => ProbeResult::Fatal {
|
||||
reason: format!("spawn failed: {}", e),
|
||||
},
|
||||
Err(e) => {
|
||||
// F1: an immediate spawn failure (e.g. ENOENT for a
|
||||
// missing binary) counts toward the threshold just
|
||||
// like a SIGCHLD reap would.
|
||||
if let Some(count) = crash_tracker().record_failure(&device_key) {
|
||||
log::warn!(
|
||||
"crash-threshold: driver={} device={} consecutive_failures={} \
|
||||
(spawn returned: {})",
|
||||
self.name,
|
||||
device_key,
|
||||
count,
|
||||
e
|
||||
);
|
||||
}
|
||||
ProbeResult::Fatal {
|
||||
reason: format!("spawn failed: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -570,6 +570,9 @@ fn main() {
|
||||
// Apply deferred-retry config from env vars (defaults to 30 retries
|
||||
// at 500 ms intervals if unset). See timing.rs::set_deferred_retry_config.
|
||||
apply_deferred_retry_env();
|
||||
// Wire the global CrashTracker (F1). Must come before the probe
|
||||
// hot path is reachable.
|
||||
apply_crash_tracker_env();
|
||||
|
||||
reset_timeline_log();
|
||||
|
||||
@@ -691,6 +694,24 @@ fn async_probe_from_env() -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// Wire the global `CrashTracker` and apply env-var overrides. Called
|
||||
/// once at startup after `apply_deferred_retry_env()` so the probe hot
|
||||
/// path can consult a fully-configured tracker.
|
||||
static CRASH_TRACKER: config::CrashTracker = config::CrashTracker::new();
|
||||
fn apply_crash_tracker_env() {
|
||||
CRASH_TRACKER.apply_env();
|
||||
config::set_crash_tracker(&CRASH_TRACKER);
|
||||
let threshold = CRASH_TRACKER.threshold();
|
||||
let base_ms = CRASH_TRACKER.backoff_base_ms();
|
||||
let cap_ms = CRASH_TRACKER.backoff_cap_ms();
|
||||
log::info!(
|
||||
"crash-tracker: threshold={} backoff_base_ms={} backoff_cap_ms={}",
|
||||
threshold,
|
||||
base_ms,
|
||||
cap_ms
|
||||
);
|
||||
}
|
||||
|
||||
/// Apply the deferred-retry configuration from the
|
||||
/// `REDBEAR_DRIVER_DEFERRED_RETRY_COUNT` and
|
||||
/// `REDBEAR_DRIVER_DEFERRED_RETRY_INTERVAL_MS` env vars. Unparseable
|
||||
|
||||
@@ -59,6 +59,8 @@ enum HandleKind {
|
||||
Recover,
|
||||
Suspend,
|
||||
Resume,
|
||||
CrashCount,
|
||||
CrashCountBdf(String),
|
||||
Timing,
|
||||
}
|
||||
|
||||
@@ -188,11 +190,15 @@ impl DriverManagerScheme {
|
||||
["recover"] => Ok(HandleKind::Recover),
|
||||
["suspend"] => Ok(HandleKind::Suspend),
|
||||
["resume"] => Ok(HandleKind::Resume),
|
||||
["crash_count"] => Ok(HandleKind::CrashCount),
|
||||
["timing"] => Ok(HandleKind::Timing),
|
||||
["devices", pci_addr] if Self::valid_pci_addr(pci_addr) => {
|
||||
let _ = self.device_status(pci_addr)?;
|
||||
Ok(HandleKind::Device((*pci_addr).to_string()))
|
||||
}
|
||||
["crash_count", pci_addr] if Self::valid_pci_addr(pci_addr) => {
|
||||
Ok(HandleKind::CrashCountBdf((*pci_addr).to_string()))
|
||||
}
|
||||
_ => Err(Error::new(ENOENT)),
|
||||
}
|
||||
}
|
||||
@@ -277,6 +283,23 @@ impl DriverManagerScheme {
|
||||
HandleKind::Device(pci_addr) => self.device_status(pci_addr),
|
||||
HandleKind::Bound => self.bound_output(),
|
||||
HandleKind::Events => self.events_output(),
|
||||
HandleKind::CrashCount => {
|
||||
let entries = crate::config::crash_tracker().snapshot();
|
||||
let mut out = String::new();
|
||||
for (bdf, n) in entries {
|
||||
out.push_str(&format!("{bdf} {n}\n"));
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
HandleKind::CrashCountBdf(pci_addr) => Ok(format!(
|
||||
"{pci_addr} {}\n",
|
||||
crate::config::crash_tracker()
|
||||
.snapshot()
|
||||
.into_iter()
|
||||
.find(|(b, _)| b.as_str() == pci_addr.as_str())
|
||||
.map(|(_, n)| n)
|
||||
.unwrap_or(0)
|
||||
)),
|
||||
HandleKind::Modalias => {
|
||||
if let Ok(results) = self.modalias_results.lock() {
|
||||
if let Some(result) = results.get(&id) {
|
||||
@@ -338,6 +361,10 @@ impl DriverManagerScheme {
|
||||
HandleKind::Recover => format!("{SCHEME_NAME}:/recover"),
|
||||
HandleKind::Suspend => format!("{SCHEME_NAME}:/suspend"),
|
||||
HandleKind::Resume => format!("{SCHEME_NAME}:/resume"),
|
||||
HandleKind::CrashCount => format!("{SCHEME_NAME}:/crash_count"),
|
||||
HandleKind::CrashCountBdf(pci_addr) => {
|
||||
format!("{SCHEME_NAME}:/crash_count/{pci_addr}")
|
||||
}
|
||||
HandleKind::Timing => format!("{SCHEME_NAME}:/timing"),
|
||||
}
|
||||
}
|
||||
@@ -359,6 +386,8 @@ impl DriverManagerScheme {
|
||||
| HandleKind::Recover
|
||||
| HandleKind::Suspend
|
||||
| HandleKind::Resume
|
||||
| HandleKind::CrashCount
|
||||
| HandleKind::CrashCountBdf(_)
|
||||
| HandleKind::Timing => MODE_FILE | 0o644,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user