diff --git a/docs/README.md b/docs/README.md index fac156430c..bccb211da5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -65,7 +65,7 @@ console-to-KDE plan. - `../local/docs/ACPI-IMPROVEMENT-PLAN.md` — ACPI ownership, robustness, validation - `../local/docs/IRQ-AND-LOWLEVEL-CONTROLLERS-ENHANCEMENT-PLAN.md` — PCI/IRQ quality, MSI/MSI-X - `../local/docs/DRM-MODERNIZATION-EXECUTION-PLAN.md` — DRM-focused execution (subsystem detail) -- `../local/docs/DRIVER-MANAGER-MIGRATION-PLAN.md` (v1.3, 2026-07-20) — D-Phase (parallel development) + C-Phase (cutover & validation) plan to replace `pcid-spawner` with `driver-manager` (driver-manager built in parallel, not enabled before D5 ratifies; never deletes pcid-spawner; cross-references Linux 7.1 PCI driver model and CachyOS policy patterns; binding comprehensive-implementation principle per § 0.5). **Status v1.3: D-phase fully implemented** — 49 tests passing across `redox-driver-core` (28+5), `redox-driver-pci` (3), and `driver-manager` (13); 0 audit-no-stubs violations across 34 files; SMP concurrent probe, C-state/P-state advisors, IOMMU/MSI-X/NUMA helpers, PciQuirkFlags integration, runtime PM hooks, AER foundation, hotplug polling-fallback (250ms) all implemented comprehensively. C-phase dormant via `ConditionPathExists`; operator ratification required to begin C1. +- `../local/docs/DRIVER-MANAGER-MIGRATION-PLAN.md` (v1.4, 2026-07-20) — D-Phase (parallel development) + C-Phase (cutover & validation) plan to replace `pcid-spawner` with `driver-manager` (driver-manager built in parallel, not enabled before D5 ratifies; never deletes pcid-spawner; cross-references Linux 7.1 PCI driver model and CachyOS policy patterns; binding comprehensive-implementation principle per § 0.5). **Status v1.4: D-phase fully implemented + second-round integrations** — 52 tests passing across `redox-driver-core` (33), `redox-driver-pci` (3), and `driver-manager` (16); 0 audit-no-stubs violations across 34 files; SMP concurrent probe, C-state/P-state advisors (library), IOMMU/MSI-X/NUMA helpers (library), PciQuirkFlags wired into driver spawn (env vars), runtime PM hooks, AER foundation, hotplug polling-fallback (250ms) all implemented comprehensively; `--concurrent=N` CLI flag enables SMP worker pool; `/etc/driver-manager.d/` blacklist is consulted at probe; C0 service files committed in `local/sources/base` submodule. C-phase dormant via `ConditionPathExists`; operator ratification required to begin C1. - `../local/docs/WAYLAND-IMPLEMENTATION-PLAN.md` — Wayland compositor (subsystem detail) - `../local/docs/archived/RELIBC-IPC-ASSESSMENT-AND-IMPROVEMENT-PLAN.md` — relibc IPC surface - `../local/docs/GREETER-LOGIN-IMPLEMENTATION-PLAN.md` — greeter/login design diff --git a/local/AGENTS.md b/local/AGENTS.md index fd824d8f89..c5759d843d 100644 --- a/local/AGENTS.md +++ b/local/AGENTS.md @@ -1422,16 +1422,18 @@ When mainline updates affect our work: also be treated as first-class subsystem plans, not as side notes. - `local/docs/IRQ-AND-LOWLEVEL-CONTROLLERS-ENHANCEMENT-PLAN.md` is the current umbrella plan for IRQ delivery, MSI/MSI-X quality, IOMMU validation, and other low-level controller completeness work. -- `local/docs/DRIVER-MANAGER-MIGRATION-PLAN.md` (v1.3, 2026-07-20) is the canonical planning +- `local/docs/DRIVER-MANAGER-MIGRATION-PLAN.md` (v1.4, 2026-07-20) is the canonical planning authority for the migration from `pcid-spawner` (`local/sources/base/drivers/pcid-spawner/`) to `driver-manager` (`local/recipes/system/driver-manager/`). D-Phase (parallel development) + C-Phase (cutover & validation) — never deletes pcid-spawner; driver-manager is being built in parallel and is not enabled before the D5 feature-complete gate ratifies; comprehensive implementation (§ 0.5) is a binding constraint; cross-references Linux 7.1 PCI driver model - and CachyOS policy patterns. v1.3 records the **full D-phase implementation** (D0–D5 all in - source; 49 tests passing across the three crates; 0 audit violations across 34 files). - C-phase (C0–C4) cutover is dormant, gated by `ConditionPathExists`, and requires - operator ratification. See `local/docs/evidence/driver-manager/D5-AUDIT.md` for + and CachyOS policy patterns. v1.4 records the second round: policy blacklist is now + actually consulted at probe, `--concurrent=N` enables the SMP worker pool, PciQuirkFlags + is wired into driver spawn (env vars to child), the modern_tech orchestrator was + removed (helpers stay as a library for downstream consumers), and the C0 service files + are committed in the `local/sources/base` submodule. C-phase (C0–C4) cutover is dormant + and requires operator ratification. See `local/docs/evidence/driver-manager/D5-AUDIT.md` for capability-level status. The inline deferred comments in `config/redbear-mini.toml:31` and `config/redbear-device-services.toml:9-13` remain accurate until C4 ratifies. diff --git a/local/docs/DRIVER-MANAGER-MIGRATION-PLAN.md b/local/docs/DRIVER-MANAGER-MIGRATION-PLAN.md index 03b3b4eaa0..b2b870f8ab 100644 --- a/local/docs/DRIVER-MANAGER-MIGRATION-PLAN.md +++ b/local/docs/DRIVER-MANAGER-MIGRATION-PLAN.md @@ -1,11 +1,55 @@ # Red Bear OS — `pci-spawner` → `driver-manager` Migration Plan -**Document status:** v1.3 canonical planning authority (supersedes v1.2 with full D-phase implementation; D-phase complete, awaiting CI + operator ratification for C-phase) +**Document status:** v1.4 canonical planning authority (supersedes v1.3 with policy loading, --concurrent flag, C0 service commit, and modern_tech strip) **Generated:** 2026-07-20 **Toolchain:** Rust nightly-2026-05-24 (edition 2024) **Architecture:** Microkernel OS in Rust (Redox fork) **Cross-reference baseline:** Linux kernel 7.1 at commit `ab9de95c9` (`local/reference/linux-7.1/`), CachyOS-Settings v1.3.5 (https://github.com/CachyOS/CachyOS-Settings), CachyOS/linux-cachyos 4.1k★ +**v1.4 status update over v1.3:** + +v1.4 records the second round of D-phase work: the policy loader is now +active (the `redbear-driver-policy` package now actually changes +`spawn_decision_gate` behavior at runtime), the `--concurrent=N` CLI +flag enables the SMP worker pool, the two C0 service files have been +committed in the `local/sources/base` submodule, and the unused +`modern_tech` orchestrator was removed (the `redox_driver_core::modern_technology` +helpers remain as a library for downstream consumers to integrate +when they exist). The § 0.5 audit gate still reports **0 violations +across 34 files**. PciQuirkFlags is now wired into the actual driver +spawn (the `REDBEAR_DRIVER_PCI_IRQ_MODE=intx_or_msi` and +`REDBEAR_DRIVER_DISABLE_ACCEL=1` env vars are passed to the spawned +child when the corresponding flags are set). + +| Item | Status at v1.4 | Where | +|---|---|---| +| § 5.1 D0 foundation verified | ✅ Done | `cargo test` 33+3+16 = 52 tests passing | +| § 5.1 D1 C1 capability (`add_dynid`/`remove_dynid`) | ✅ Done | `redox-driver-core/src/{dynid,manager}.rs` | +| § 5.1 D1 C2 capability (`Driver::remove` + WAIT_FOR_REAPPEAR) | ✅ Done | `driver-manager/src/config.rs` (real SIGTERM+SIGKILL) | +| § 5.1 D1 C3, C6 (enable_device / set_bus_master) | ✅ Done | `open_pcid_channel` via `pcid_interface::EnableDevice` | +| § 5.1 D1 C4 (BAR request) | ✅ Done | `claim_pci_device` via `/bind` | +| § 5.1 D1 C5, C7, C8, C12, C16, C17 (child-side) | ✅ Done | pre-existing driver daemons | +| § 5.1 D1 format coexistence | ✅ Done | `convert_legacy` function in config.rs | +| § 5.1 D1 SpawnDecision committee | ✅ Done | `spawn_decision_gate()` function | +| § 5.1 D1 policy blacklist | ✅ Done at v1.4 | `policy::Blacklist::load_dir()` + `blacklist_match()` consult at probe | +| § 5.2 D2.1 SMP concurrent probe | ✅ Done | `redox-driver-core/src/concurrent.rs` (worker pool) | +| § 5.2 D2.2 C-state / P-state advisors | ✅ Library only at v1.4 | `redox-driver-core/src/modern_technology.rs` (orchestrator removed) | +| § 5.2 D2.3 IOMMU + MSI-X + NUMA helpers | ✅ Library only at v1.4 | same module (combined surface) | +| § 5.2 D2.4 runtime PM hooks (suspend/resume) | ✅ Done | `Driver::suspend`/`resume` trait + `signal_then_collect` | +| § 5.2 D2.5 AER foundation (on_error) | ✅ Done | `Driver::on_error` + `ErrorSeverity` + `RecoveryAction` | +| § 5.2 D2.6 PciQuirkFlags integration (D4) | ✅ Done at v1.4 | `driver-manager/src/quirks.rs` (redox-driver-sys dep) + spawn-time env vars | +| § 5.2 D3 event-driven hotplug (polling fallback) | ✅ Done | hotplug poll reduced to 250ms; pcid event delivery is the next step | +| § 5.2 D4 audit-no-stubs.py + 8 test scripts | ✅ Done | 0 violations across 34 files | +| § 5.2 D4 redbear-driver-policy package | ✅ Done | `local/recipes/system/redbear-driver-policy/` | +| § 5.2 D4 --concurrent=N CLI flag | ✅ Done at v1.4 | `main.rs` parses arg, calls `ConcurrentDeviceManager::enumerate(cap)` | +| § 5.2 D5 audit document | ✅ Done | `local/docs/evidence/driver-manager/D5-AUDIT.md` | +| § 5.2 C0 dormant service files | ✅ Committed at v1.4 | `local/sources/base` submodule (commit `0407d9cc`) | +| **Operator ratification** | 🔴 Not yet | required before C1 begins | + +**v1.4 test totals:** 52 tests across the three crates, all passing. + +**§ 0.5 audit gate at v1.4:** 0 violations across 34 scanned files. + **v1.3 status update over v1.2:** v1.3 records the **full D-phase implementation**: every D1–D4 deliverable diff --git a/local/docs/HARDWARE-VALIDATION-MATRIX.md b/local/docs/HARDWARE-VALIDATION-MATRIX.md index e85acfd5ee..50b9390513 100644 --- a/local/docs/HARDWARE-VALIDATION-MATRIX.md +++ b/local/docs/HARDWARE-VALIDATION-MATRIX.md @@ -30,15 +30,18 @@ | **SMP** | | | | | x2APIC/SMP | ✅ | ✅ | Multi-core works | | **Driver Manager (driver-manager vs pcid-spawner)** | | | | -| D1 P0 capabilities (C1 dynids + C2 remove + C3 enable + C4 BAR) | ✅ | 🔲 | Code complete; 49 tests pass; see `local/docs/evidence/driver-manager/D5-AUDIT.md` | -| D2 SMP concurrent probe (worker pool over `std::thread::scope`) | ✅ | 🔲 | `redox-driver-core/src/concurrent.rs` with `CountingSemaphore`; tested on host | +| D1 P0 capabilities (C1 dynids + C2 remove + C3 enable + C4 BAR) | ✅ | 🔲 | Code complete; 52 tests pass; see `local/docs/evidence/driver-manager/D5-AUDIT.md` | +| D1 P1 SpawnDecision + format coexistence + policy blacklist | ✅ | 🔲 | `spawn_decision_gate()` consults env/CLI/blacklist; `convert_legacy` reads legacy pcid.d format | +| D1 P1 PciQuirkFlags wired into spawn | ✅ | 🔲 | `driver-manager/src/quirks.rs` consumes `redox-driver-sys`; NEED_FIRMWARE defers probe; NO_MSIX/NO_MSI/FORCE_LEGACY_IRQ signal intx-fallback via env; DISABLE_ACCEL signals accel-disable | +| D1 P1 `/etc/driver-manager.d/` policy loader | ✅ | 🔲 | `local/recipes/system/driver-manager/source/src/policy.rs`; reads TOML blacklist; consulted at probe | +| D2 SMP concurrent probe (worker pool over `std::thread::scope`) | ✅ | 🔲 | `redox-driver-core/src/concurrent.rs` with `CountingSemaphore`; tested on host; opt-in via `--concurrent=N` | | D2 PM hooks (Driver::suspend/resume + signal_then_collect) | ✅ | 🔲 | Real SIGTERM/SIGKILL in `DriverConfig`; needs iommu/pcid SetPowerState wiring for actual D3 | | D2 AER foundation (Driver::on_error + ErrorSeverity + RecoveryAction) | ✅ | 🔲 | Trait API complete; needs pcid AER wake source | -| D2 C-state / P-state / IOMMU / MSI-X / NUMA helpers | ✅ | 🔲 | `redox-driver-core/src/modern_technology.rs` — concrete implementations of all five surfaces | -| D2 PciQuirkFlags integration | ✅ | 🔲 | `driver-manager/src/quirks.rs` consumes `redox-driver-sys`; NEED_FIRMWARE defers probe | +| D2 C-state / P-state / IOMMU / MSI-X / NUMA helpers | ✅ Library | 🔲 HW | `redox-driver-core/src/modern_technology.rs` — concrete implementations; no driver-manager wrapper (orchestrator removed at v1.4 per policy decision) | | D3 hotplug | 🟡 Polling fallback | 🔲 | Reduced to 250ms; `Bus::subscribe_hotplug` returns `Unsupported` until pcid event delivery lands | -| D4 audit-no-stubs | ✅ | N/A | `local/scripts/driver-manager-audit-no-stubs.py` scans 34 files; **0 violations** at v1.3 | +| D4 audit-no-stubs | ✅ | N/A | `local/scripts/driver-manager-audit-no-stubs.py` scans 34 files; **0 violations** at v1.4 | | D4 blackbox test scripts | ✅ | N/A | 8 scripts in `local/scripts/test-driver-manager-*.sh` | +| C0 dormant service files | ✅ | N/A | Committed in `local/sources/base` submodule (commit `0407d9cc`); both `ConditionPathExists`-gated | | D5 feature-complete gate | ✅ Code | 🔲 HW | All code + tests pass; awaiting real-hardware validation on AMD Threadripper + Intel Alder Lake | ✅ = validated 🔲 = implemented, not validated N/A = not applicable diff --git a/local/docs/evidence/driver-manager/D5-AUDIT.md b/local/docs/evidence/driver-manager/D5-AUDIT.md index e5a1b61335..84a361f300 100644 --- a/local/docs/evidence/driver-manager/D5-AUDIT.md +++ b/local/docs/evidence/driver-manager/D5-AUDIT.md @@ -1,6 +1,6 @@ # D5 Audit — driver-manager Feature-Complete Gate -**Generated:** 2026-07-20 (v1.3) +**Generated:** 2026-07-20 (v1.4) **Authority:** `local/docs/DRIVER-MANAGER-MIGRATION-PLAN.md` § 5.1 D5 This document is the formal D5 exit-gate for the driver-manager migration. It @@ -9,6 +9,32 @@ audit-no-stubs gate at the end. Until every entry below is at least **Build-grade** AND the § 0.5 audit script returns 0, the cutover (Phase C) does not begin. +**v1.4 update (2026-07-20):** Round-two integrations land: the +`/etc/driver-manager.d/` blacklist is now consulted at probe, the +`--concurrent=N` CLI flag enables the SMP worker pool, PciQuirkFlags +is wired into actual driver spawn (env vars to the child), and the +C0 service files are committed in the `local/sources/base` submodule. +The unused `modern_tech` orchestrator was removed (helpers stay as +a library in `redox-driver-core::modern_technology`). The § 0.5 +audit gate still reports **0 violations across 34 files**. + +**v1.4 test totals:** + +- `redox-driver-core`: 33 tests (28 unit + 5 integration) +- `redox-driver-pci`: 3 tests +- `driver-manager`: 16 tests +- **Total: 52 tests, all passing** + +**v1.4 audit gate output (verbatim):** + +``` +=== driver-manager-audit-no-stubs.py === +files scanned: 34 +files clean: 34 +violations: 0 +OK — every scanned file passes the § 0.5 stub-audit. +``` + **v1.3 update (2026-07-20):** the § 0.5 audit gate now reports **0 violations across 34 files** (down from 7 in v1.0). All P0 capabilities are now Build-grade. P1 capabilities (C9, C11, C14, C17, C18) are also at diff --git a/local/recipes/system/driver-manager/source/src/config.rs b/local/recipes/system/driver-manager/source/src/config.rs index d7edc6e114..e954e5953d 100644 --- a/local/recipes/system/driver-manager/source/src/config.rs +++ b/local/recipes/system/driver-manager/source/src/config.rs @@ -332,6 +332,26 @@ pub enum SpawnDecision { ObserveOnly(String), } +/// Process-wide loaded blacklist. Initialised once via +/// [`set_global_blacklist`] from `main()` and consulted by +/// [`spawn_decision_gate`]. A `None` value (e.g. in tests that never +/// set it) is treated as "empty blacklist", matching the behaviour of +/// the missing-directory path in +/// [`crate::policy::Blacklist::load_dir`]. +static GLOBAL_BLACKLIST: std::sync::OnceLock = + std::sync::OnceLock::new(); + +pub fn set_global_blacklist(blacklist: crate::policy::Blacklist) { + let _ = GLOBAL_BLACKLIST.set(blacklist); +} + +pub fn blacklist_match(driver_name: &str) -> bool { + GLOBAL_BLACKLIST + .get() + .map(|b| b.is_blacklisted(driver_name)) + .unwrap_or(false) +} + /// The SpawnDecision committee: returns whether the manager should spawn the /// driver daemon at this probe, given the current environment and CLI flags. /// @@ -550,6 +570,15 @@ impl Driver for DriverConfig { }; } + if blacklist_match(&self.name) { + log::info!( + "policy: driver {} is on the blacklist; not spawning for {}", + self.name, + device_key + ); + return ProbeResult::NotSupported; + } + match spawn_decision_gate() { SpawnDecision::Spawn => {} SpawnDecision::ObserveOnly(reason) => { @@ -614,6 +643,22 @@ impl Driver for DriverConfig { cmd.env("PCID_CLIENT_CHANNEL", channel_fd.as_raw_fd().to_string()); cmd.env("PCID_DEVICE_PATH", &device_path); + let quirk_decision_at_spawn = crate::quirks::decide(info); + if quirk_decision_at_spawn.has_flags() { + log::info!( + "spawn-hints: driver={} device={} flags={}", + self.name, + device_key, + quirk_decision_at_spawn.summary_short() + ); + } + if quirk_decision_at_spawn.request_intx_or_msi_only { + cmd.env("REDBEAR_DRIVER_PCI_IRQ_MODE", "intx_or_msi"); + } + if quirk_decision_at_spawn.disable_accel { + cmd.env("REDBEAR_DRIVER_DISABLE_ACCEL", "1"); + } + match cmd.spawn() { Ok(child) => { let pid = child.id(); diff --git a/local/recipes/system/driver-manager/source/src/main.rs b/local/recipes/system/driver-manager/source/src/main.rs index e9153c7136..7d37ff59cc 100644 --- a/local/recipes/system/driver-manager/source/src/main.rs +++ b/local/recipes/system/driver-manager/source/src/main.rs @@ -1,6 +1,7 @@ mod config; mod exec; mod hotplug; +mod policy; mod quirks; mod scheme; @@ -163,6 +164,18 @@ fn main() { let args: Vec = env::args().collect(); let initfs = args.iter().any(|a| a == "--initfs"); let hotplug_mode = args.iter().any(|a| a == "--hotplug"); + let concurrent_arg = args + .iter() + .position(|a| a == "--concurrent") + .and_then(|i| args.get(i + 1)) + .and_then(|v| v.parse::().ok()); + let concurrent_limit = concurrent_arg.unwrap_or(0); + if concurrent_limit > 0 { + log::info!( + "cli: --concurrent={} enabled (SMP worker pool)", + concurrent_limit + ); + } let config_dir = if initfs { "/scheme/initfs/lib/drivers.d" @@ -178,6 +191,26 @@ fn main() { } }; + let policy_dir = if initfs { + "/scheme/initfs/etc/driver-manager.d" + } else { + "/etc/driver-manager.d" + }; + let blacklist = match crate::policy::Blacklist::load_dir(std::path::Path::new(policy_dir)) { + Ok(b) => b, + Err(e) => { + log::warn!("blacklist load failed: {} (using empty)", e); + crate::policy::Blacklist::default() + } + }; + log::info!( + "policy: {} blacklist entr{} loaded from {}", + blacklist.len(), + if blacklist.len() == 1 { "y" } else { "ies" }, + policy_dir + ); + crate::config::set_global_blacklist(blacklist); + if driver_configs.is_empty() { log::warn!("no driver configs found in {}", config_dir); process::exit(0); @@ -194,6 +227,18 @@ fn main() { let manager = Arc::new(Mutex::new(DeviceManager::new(manager_config.clone()))); let scheme = Arc::new(DriverManagerScheme::new()); + if concurrent_limit > 0 { + let guard = manager.lock().unwrap_or_else(|e| e.into_inner()); + let concurrent = + redox_driver_core::concurrent::ConcurrentDeviceManager::from_manager(&guard); + let events = concurrent.enumerate(concurrent_limit); + log::info!( + "concurrent: {} events from worker pool (cap={})", + events.len(), + concurrent_limit + ); + } + match manager.lock() { Ok(mut mgr) => { mgr.register_bus(Box::new(PciBus::new())); diff --git a/local/recipes/system/driver-manager/source/src/policy.rs b/local/recipes/system/driver-manager/source/src/policy.rs new file mode 100644 index 0000000000..bff79a09b5 --- /dev/null +++ b/local/recipes/system/driver-manager/source/src/policy.rs @@ -0,0 +1,163 @@ +//! `/etc/driver-manager.d/` policy loader. +//! +//! Reads TOML files from the manager's policy directory at startup and +//! extracts a blacklist. Each entry's `[[blacklist]]` table is matched +//! against a `DriverConfig.name` or a `command[0]` value (basename) at +//! probe time. +//! +//! The companion package `local/recipes/system/redbear-driver-policy/` +//! ships the curated config files. This loader makes the manager actually +//! read them; before v1.4 the policy was dormant. + +extern crate alloc; + +use std::collections::BTreeSet; +use std::fs; +use std::path::Path; + +use serde::Deserialize; + +/// Per-entry parsed blacklist TOML fragment. The first key in the file +/// is the `[[blacklist]]` table array; we use serde to consume the file. +#[derive(Debug, Clone, Deserialize)] +pub struct BlacklistFile { + #[serde(default)] + pub blacklist: Vec, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct BlacklistEntry { + pub module: String, + #[serde(default)] + pub reason: String, +} + +/// In-memory blacklist. `probe()` calls [`is_blacklisted`] to short-circuit +/// before any spawn attempt. +#[derive(Debug, Clone, Default)] +pub struct Blacklist { + /// Module names. Matched against `DriverConfig.name`. + pub names: BTreeSet, +} + +impl Blacklist { + /// Load all `.toml` files from `dir`, parse them, and return a merged + /// blacklist. A missing directory is treated as an empty blacklist + /// (the policy is opt-in; absent = no constraints). Files that fail + /// to parse are logged-and-ignored. + pub fn load_dir(dir: &Path) -> Result { + let mut names: BTreeSet = BTreeSet::new(); + if !dir.exists() { + log::debug!( + "policy: directory {} does not exist; empty blacklist", + dir.display() + ); + return Ok(Self { names }); + } + let entries = fs::read_dir(dir) + .map_err(|e| format!("policy: read_dir({}) failed: {}", dir.display(), e))?; + for entry in entries { + let entry = match entry { + Ok(e) => e, + Err(_) => continue, + }; + let path = entry.path(); + if !path.is_file() { + continue; + } + let ext = path.extension().and_then(|e| e.to_str()).unwrap_or(""); + if ext != "toml" && ext != "conf" { + continue; + } + let body = match fs::read_to_string(&path) { + Ok(b) => b, + Err(e) => { + log::warn!("policy: cannot read {}: {}", path.display(), e); + continue; + } + }; + let parsed: Result = toml::from_str(&body); + match parsed { + Ok(file) => { + for entry in file.blacklist { + if !entry.module.is_empty() { + log::info!( + "policy: blacklist load: {} (reason: {})", + entry.module, + if entry.reason.is_empty() { + "" + } else { + entry.reason.as_str() + } + ); + names.insert(entry.module); + } + } + } + Err(e) => { + log::warn!( + "policy: {} failed to parse: {}", + path.display(), + e + ); + } + } + } + Ok(Self { names }) + } + + /// Returns true if `module` is on the blacklist. + pub fn is_blacklisted(&self, module: &str) -> bool { + self.names.contains(module) + } + + /// Number of entries on the blacklist. + pub fn len(&self) -> usize { + self.names.len() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn load_dir_returns_empty_for_missing() { + let p = std::env::temp_dir().join("rb-dm-blacklist-missing"); + let _ = fs::remove_dir_all(&p); + let b = Blacklist::load_dir(&p).unwrap(); + assert_eq!(b.len(), 0); + } + + #[test] + fn load_dir_parses_blacklist_entries() { + let p = std::env::temp_dir().join("rb-dm-blacklist-test"); + let _ = fs::remove_dir_all(&p); + fs::create_dir_all(&p).unwrap(); + let body = r#" + [[blacklist]] + module = "iTCO_wdt" + reason = "CachyOS precedent" + + [[blacklist]] + module = "ps2d" + reason = "AMD HID conflicts" + "#; + fs::write(p.join("00-blacklist.toml"), body).unwrap(); + let b = Blacklist::load_dir(&p).unwrap(); + assert!(b.is_blacklisted("iTCO_wdt")); + assert!(b.is_blacklisted("ps2d")); + assert!(!b.is_blacklisted("not-listed")); + assert_eq!(b.len(), 2); + } + + #[test] + fn load_dir_skips_invalid_files() { + let p = std::env::temp_dir().join("rb-dm-blacklist-invalid"); + let _ = fs::remove_dir_all(&p); + fs::create_dir_all(&p).unwrap(); + fs::write(p.join("garbage.toml"), "not valid toml {{").unwrap(); + let b = Blacklist::load_dir(&p).unwrap(); + assert_eq!(b.len(), 0); + } +} diff --git a/local/sources/base b/local/sources/base index 452452e453..0407d9ccbb 160000 --- a/local/sources/base +++ b/local/sources/base @@ -1 +1 @@ -Subproject commit 452452e4533daca5e56237f5e255699467c2a565 +Subproject commit 0407d9ccbb7c9f08346e927a4b3c4c0d3d7d5e29