feat: recipe durability guard — prevents build system from deleting local recipes
Add guard-recipes.sh with four modes: - --verify: check all local/recipes have correct symlinks into recipes/ - --fix: repair broken symlinks (run before builds) - --save-all: snapshot all recipe.toml into local/recipes/ - --restore: recreate all symlinks from local/recipes/ (run after sync-upstream) Wired into apply-patches.sh (post-patch) and sync-upstream.sh (post-sync). This prevents the build system from deleting recipe files during cargo cook, make distclean, or upstream source refresh.
This commit is contained in:
@@ -0,0 +1,354 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fs::{self, File, OpenOptions};
|
||||
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
use std::string::String;
|
||||
use std::sync::Mutex;
|
||||
use std::vec::Vec;
|
||||
|
||||
use pcid_interface::PciFunctionHandle;
|
||||
use redox_driver_core::device::DeviceInfo;
|
||||
use redox_driver_core::driver::{Driver, DriverError, ProbeResult};
|
||||
use redox_driver_core::r#match::DriverMatch;
|
||||
use redox_driver_core::params::{DriverParams, ParamValue};
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SpawnedDriver {
|
||||
pid: u32,
|
||||
bind_handle: File,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DriverConfig {
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub priority: i32,
|
||||
pub command: Vec<String>,
|
||||
pub matches: Vec<DriverMatch>,
|
||||
pub depends_on: Vec<String>,
|
||||
spawned: Mutex<HashMap<String, SpawnedDriver>>,
|
||||
}
|
||||
|
||||
impl Clone for DriverConfig {
|
||||
fn clone(&self) -> Self {
|
||||
DriverConfig {
|
||||
name: self.name.clone(),
|
||||
description: self.description.clone(),
|
||||
priority: self.priority,
|
||||
command: self.command.clone(),
|
||||
matches: self.matches.clone(),
|
||||
depends_on: self.depends_on.clone(),
|
||||
spawned: Mutex::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct RawDriverMatch {
|
||||
vendor: Option<u16>,
|
||||
device: Option<u16>,
|
||||
class: Option<u8>,
|
||||
subclass: Option<u8>,
|
||||
prog_if: Option<u8>,
|
||||
subsystem_vendor: Option<u16>,
|
||||
subsystem_device: Option<u16>,
|
||||
}
|
||||
|
||||
impl From<RawDriverMatch> for DriverMatch {
|
||||
fn from(r: RawDriverMatch) -> Self {
|
||||
DriverMatch {
|
||||
vendor: r.vendor,
|
||||
device: r.device,
|
||||
class: r.class,
|
||||
subclass: r.subclass,
|
||||
prog_if: r.prog_if,
|
||||
subsystem_vendor: r.subsystem_vendor,
|
||||
subsystem_device: r.subsystem_device,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DriverConfig {
|
||||
pub fn load_all(dir: &str) -> Result<Vec<DriverConfig>, String> {
|
||||
let entries = fs::read_dir(dir).map_err(|e| format!("read_dir failed: {}", e))?;
|
||||
|
||||
let mut configs = Vec::new();
|
||||
|
||||
for entry in entries {
|
||||
let entry = entry.map_err(|e| format!("entry error: {}", e))?;
|
||||
let path = entry.path();
|
||||
|
||||
if !path.is_file() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let data = fs::read_to_string(&path)
|
||||
.map_err(|e| format!("read {} failed: {}", path.display(), e))?;
|
||||
|
||||
let parsed: RawDriverToml = toml::from_str(&data)
|
||||
.map_err(|e| format!("parse {} failed: {}", path.display(), e))?;
|
||||
|
||||
for driver in parsed.driver {
|
||||
let matches: Vec<DriverMatch> =
|
||||
driver.r#match.into_iter().map(DriverMatch::from).collect();
|
||||
|
||||
configs.push(DriverConfig {
|
||||
name: driver.name,
|
||||
description: driver.description,
|
||||
priority: driver.priority,
|
||||
command: driver.command,
|
||||
matches,
|
||||
depends_on: driver.depends_on,
|
||||
spawned: Mutex::new(HashMap::new()),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
configs.sort_by(|a, b| b.priority.cmp(&a.priority));
|
||||
Ok(configs)
|
||||
}
|
||||
}
|
||||
|
||||
fn pci_device_path(info: &DeviceInfo) -> String {
|
||||
if info.raw_path.starts_with("/scheme/pci/") {
|
||||
info.raw_path.clone()
|
||||
} else {
|
||||
format!("/scheme/pci/{}", info.id.path)
|
||||
}
|
||||
}
|
||||
|
||||
fn claim_pci_device(info: &DeviceInfo) -> Result<(String, File), ProbeResult> {
|
||||
let device_path = pci_device_path(info);
|
||||
let bind_path = format!("{}/bind", device_path);
|
||||
|
||||
match OpenOptions::new().read(true).write(true).open(&bind_path) {
|
||||
Ok(bind_handle) => Ok((device_path, bind_handle)),
|
||||
Err(err) => match err.raw_os_error() {
|
||||
Some(code) if code == syscall::EALREADY as i32 || code == 114 => {
|
||||
log::debug!("device {} already claimed via {}", info.id.path, bind_path);
|
||||
Err(ProbeResult::NotSupported)
|
||||
}
|
||||
_ => Err(ProbeResult::Deferred {
|
||||
reason: format!("bind {} failed: {}", bind_path, err),
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn open_pcid_channel(device_path: &str) -> Result<OwnedFd, ProbeResult> {
|
||||
let mut handle = match PciFunctionHandle::connect_by_path(Path::new(device_path)) {
|
||||
Ok(handle) => handle,
|
||||
Err(err) => {
|
||||
return Err(ProbeResult::Deferred {
|
||||
reason: format!("open channel for {} failed: {}", device_path, err),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
handle.enable_device();
|
||||
|
||||
let channel_fd = handle.into_inner_fd();
|
||||
let channel_fd = unsafe { OwnedFd::from_raw_fd(channel_fd) };
|
||||
Ok(channel_fd)
|
||||
}
|
||||
|
||||
fn check_scheme_available(name: &str) -> bool {
|
||||
if std::path::Path::new(&format!("/scheme/{}", name)).exists() {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
impl Driver for DriverConfig {
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
&self.description
|
||||
}
|
||||
|
||||
fn priority(&self) -> i32 {
|
||||
self.priority
|
||||
}
|
||||
|
||||
fn match_table(&self) -> &[DriverMatch] {
|
||||
&self.matches
|
||||
}
|
||||
|
||||
fn probe(&self, info: &DeviceInfo) -> ProbeResult {
|
||||
let device_key = info.id.path.clone();
|
||||
|
||||
{
|
||||
let spawned = self.spawned.lock().unwrap();
|
||||
if spawned.contains_key(&device_key) {
|
||||
log::debug!("driver {} already bound to {}", self.name, device_key);
|
||||
return ProbeResult::Bound;
|
||||
}
|
||||
}
|
||||
|
||||
if self.command.is_empty() {
|
||||
return ProbeResult::Fatal {
|
||||
reason: String::from("empty command"),
|
||||
};
|
||||
}
|
||||
|
||||
let actual_path = if self.command[0].starts_with('/') {
|
||||
self.command[0].clone()
|
||||
} else {
|
||||
format!("/usr/lib/drivers/{}", self.command[0])
|
||||
};
|
||||
|
||||
if !std::path::Path::new(&actual_path).exists() {
|
||||
return ProbeResult::Deferred {
|
||||
reason: format!("driver binary not found: {}", actual_path),
|
||||
};
|
||||
}
|
||||
|
||||
let deps: Vec<String> = if !self.depends_on.is_empty() {
|
||||
self.depends_on.clone()
|
||||
} else {
|
||||
guess_dependencies(&self.name)
|
||||
};
|
||||
for dep in &deps {
|
||||
if !check_scheme_available(dep) {
|
||||
return ProbeResult::Deferred {
|
||||
reason: format!("dependency scheme not ready: {}", dep),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
log::info!("probing {} with driver {}", device_key, self.name);
|
||||
|
||||
let (device_path, bind_handle) = match claim_pci_device(info) {
|
||||
Ok(claimed) => claimed,
|
||||
Err(result) => return result,
|
||||
};
|
||||
|
||||
let channel_fd = match open_pcid_channel(&device_path) {
|
||||
Ok(channel_fd) => channel_fd,
|
||||
Err(result) => return result,
|
||||
};
|
||||
|
||||
let mut cmd = Command::new(&actual_path);
|
||||
for arg in &self.command[1..] {
|
||||
cmd.arg(arg);
|
||||
}
|
||||
|
||||
cmd.env("PCID_CLIENT_CHANNEL", channel_fd.as_raw_fd().to_string());
|
||||
cmd.env("PCID_DEVICE_PATH", &device_path);
|
||||
|
||||
match cmd.spawn() {
|
||||
Ok(child) => {
|
||||
let pid = child.id();
|
||||
log::info!(
|
||||
"driver {} spawned (pid {}) for device {}",
|
||||
self.name,
|
||||
pid,
|
||||
device_key
|
||||
);
|
||||
let mut spawned = self.spawned.lock().unwrap();
|
||||
spawned.insert(device_key, SpawnedDriver { pid, bind_handle });
|
||||
ProbeResult::Bound
|
||||
}
|
||||
Err(e) => ProbeResult::Fatal {
|
||||
reason: format!("spawn failed: {}", e),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn remove(&self, info: &DeviceInfo) -> Result<(), DriverError> {
|
||||
let device_key = info.id.path.clone();
|
||||
let binding = {
|
||||
let mut spawned = self.spawned.lock().unwrap();
|
||||
spawned.remove(&device_key)
|
||||
};
|
||||
|
||||
match binding {
|
||||
Some(binding) => {
|
||||
let bind_fd = binding.bind_handle.as_raw_fd();
|
||||
log::info!(
|
||||
"unbound: device {} from driver {} (pid {}, bind fd {})",
|
||||
device_key,
|
||||
self.name,
|
||||
binding.pid,
|
||||
bind_fd
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
_ => {
|
||||
log::warn!("driver {} not bound to device {}", self.name, device_key);
|
||||
Err(DriverError::Other("not bound"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn params(&self) -> DriverParams {
|
||||
let mut p = DriverParams::new();
|
||||
p.define(
|
||||
"enabled",
|
||||
"Whether this driver is active",
|
||||
ParamValue::Bool(true),
|
||||
true,
|
||||
);
|
||||
p.define(
|
||||
"priority",
|
||||
"Probe priority (higher = earlier)",
|
||||
ParamValue::Int(self.priority as i64),
|
||||
false,
|
||||
);
|
||||
p
|
||||
}
|
||||
}
|
||||
|
||||
/// Driver-specified dependencies. Parsed from [driver.depends] TOML field.
|
||||
/// Example: depends_on = ["pci", "acpi"]
|
||||
/// When specified, takes precedence over guess_dependencies().
|
||||
fn guess_dependencies(driver_name: &str) -> Vec<String> {
|
||||
match driver_name {
|
||||
"xhcid" | "usbhubd" | "usbctl" | "usbhidd" | "usbscsid" => {
|
||||
vec![String::from("pci")]
|
||||
}
|
||||
"nvmed" | "ahcid" | "ided" | "virtio-blkd" => {
|
||||
vec![String::from("pci")]
|
||||
}
|
||||
"e1000d" | "rtl8168d" | "rtl8139d" | "ixgbed" | "virtio-netd" => {
|
||||
vec![String::from("pci")]
|
||||
}
|
||||
"vesad" | "virtio-gpud" | "redox-drm" => {
|
||||
vec![String::from("pci")]
|
||||
}
|
||||
"ihdad" | "ac97d" | "sb16d" => {
|
||||
vec![String::from("pci")]
|
||||
}
|
||||
"ps2d" => vec![String::from("serio")],
|
||||
"i2c-hidd" => vec![String::from("i2c")],
|
||||
"dw-acpi-i2cd" | "amd-mp2-i2cd" | "intel-lpss-i2cd" => {
|
||||
vec![String::from("acpi"), String::from("i2c")]
|
||||
}
|
||||
_ => vec![String::from("pci")],
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct RawDriverToml {
|
||||
driver: Vec<RawDriverEntry>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct RawDriverEntry {
|
||||
name: String,
|
||||
#[serde(default)]
|
||||
description: String,
|
||||
#[serde(default)]
|
||||
priority: i32,
|
||||
#[serde(default)]
|
||||
command: Vec<String>,
|
||||
#[serde(rename = "match")]
|
||||
r#match: Vec<RawDriverMatch>,
|
||||
#[serde(default)]
|
||||
depends_on: Vec<String>,
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
use std::process::Command;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn spawn_driver(command: &[String]) -> Result<std::process::Child, std::io::Error> {
|
||||
if command.is_empty() {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
"empty command",
|
||||
));
|
||||
}
|
||||
|
||||
let mut cmd = Command::new(&command[0]);
|
||||
for arg in &command[1..] {
|
||||
cmd.arg(arg);
|
||||
}
|
||||
|
||||
cmd.spawn()
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
use std::collections::BTreeSet;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use redox_driver_core::device::DeviceId;
|
||||
use redox_driver_core::driver::ProbeResult;
|
||||
use redox_driver_core::manager::DeviceManager;
|
||||
use redox_driver_core::manager::ProbeEvent;
|
||||
|
||||
use crate::scheme::{DriverManagerScheme, notify_bind, notify_unbind};
|
||||
|
||||
pub fn run_hotplug_loop(
|
||||
manager: Arc<Mutex<DeviceManager>>,
|
||||
scheme: Arc<DriverManagerScheme>,
|
||||
poll_interval_ms: u64,
|
||||
) {
|
||||
log::info!(
|
||||
"hotplug: starting event loop ({} ms poll)",
|
||||
poll_interval_ms
|
||||
);
|
||||
|
||||
loop {
|
||||
thread::sleep(Duration::from_millis(poll_interval_ms));
|
||||
|
||||
let events = match manager.lock() {
|
||||
Ok(mut mgr) => mgr.enumerate(),
|
||||
Err(err) => {
|
||||
log::error!("hotplug: failed to enumerate devices: manager lock poisoned: {err}");
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
let mut seen_pci_devices = BTreeSet::new();
|
||||
let mut pci_enumerated = false;
|
||||
|
||||
for event in &events {
|
||||
match event {
|
||||
ProbeEvent::BusEnumerated { bus, .. } => {
|
||||
if bus == "pci" {
|
||||
pci_enumerated = true;
|
||||
}
|
||||
}
|
||||
ProbeEvent::BusEnumerationFailed { bus, error } => {
|
||||
log::error!("hotplug: bus {} enumeration failed: {:?}", bus, error);
|
||||
}
|
||||
ProbeEvent::AlreadyBound {
|
||||
device,
|
||||
driver_name,
|
||||
} => {
|
||||
track_pci_device(device, &mut seen_pci_devices);
|
||||
notify_bound_device(scheme.as_ref(), device, driver_name);
|
||||
log::debug!("hotplug: already bound {} -> {}", device.path, driver_name);
|
||||
}
|
||||
ProbeEvent::ProbeCompleted {
|
||||
device,
|
||||
driver_name,
|
||||
result,
|
||||
} => {
|
||||
track_pci_device(device, &mut seen_pci_devices);
|
||||
match result {
|
||||
ProbeResult::Bound => {
|
||||
log::info!("hotplug: bound {} -> {}", device.path, driver_name);
|
||||
notify_bound_device(scheme.as_ref(), device, driver_name);
|
||||
}
|
||||
ProbeResult::Deferred { reason } => {
|
||||
log::info!(
|
||||
"hotplug: deferred {} -> {} ({})",
|
||||
device.path,
|
||||
driver_name,
|
||||
reason
|
||||
);
|
||||
}
|
||||
ProbeResult::Fatal { reason } => {
|
||||
log::error!(
|
||||
"hotplug: fatal {} -> {} ({})",
|
||||
device.path,
|
||||
driver_name,
|
||||
reason
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
ProbeEvent::NoDriverFound { device } => {
|
||||
track_pci_device(device, &mut seen_pci_devices);
|
||||
log::debug!("hotplug: no driver for new device {}", device.path);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if pci_enumerated {
|
||||
for pci_addr in scheme.bound_device_addresses() {
|
||||
if !seen_pci_devices.contains(&pci_addr) {
|
||||
log::info!("hotplug: removed {}", pci_addr);
|
||||
notify_unbind(scheme.as_ref(), &pci_addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let retry_events = match manager.lock() {
|
||||
Ok(mut mgr) => mgr.retry_deferred(),
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
"hotplug: failed to retry deferred probes: manager lock poisoned: {err}"
|
||||
);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
let mut resolved = 0usize;
|
||||
for event in &retry_events {
|
||||
if let ProbeEvent::ProbeCompleted {
|
||||
device,
|
||||
driver_name,
|
||||
result,
|
||||
} = event
|
||||
{
|
||||
if *result == ProbeResult::Bound {
|
||||
resolved += 1;
|
||||
notify_bound_device(scheme.as_ref(), device, driver_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if resolved > 0 {
|
||||
log::info!("hotplug: resolved {} deferred probes", resolved);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn track_pci_device(device: &DeviceId, seen_pci_devices: &mut BTreeSet<String>) {
|
||||
if device.bus == "pci" {
|
||||
seen_pci_devices.insert(device.path.clone());
|
||||
}
|
||||
}
|
||||
|
||||
fn notify_bound_device(scheme: &DriverManagerScheme, device: &DeviceId, driver_name: &str) {
|
||||
if device.bus == "pci" {
|
||||
notify_bind(scheme, &device.path, driver_name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
mod config;
|
||||
mod exec;
|
||||
mod hotplug;
|
||||
mod scheme;
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::{env, fs, process};
|
||||
|
||||
use redox_driver_core::device::DeviceId;
|
||||
use redox_driver_core::driver::ProbeResult;
|
||||
use redox_driver_core::manager::{DeviceManager, ManagerConfig, ProbeEvent};
|
||||
use redox_driver_pci::PciBus;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
|
||||
use config::DriverConfig;
|
||||
use scheme::{DriverManagerScheme, notify_bind};
|
||||
|
||||
struct StderrLogger;
|
||||
|
||||
const BOOT_TIMELINE_PATH: &str = "/tmp/redbear-boot-timeline.json";
|
||||
|
||||
impl log::Log for StderrLogger {
|
||||
fn enabled(&self, metadata: &log::Metadata) -> bool {
|
||||
metadata.level() <= log::Level::Info
|
||||
}
|
||||
fn log(&self, record: &log::Record) {
|
||||
if self.enabled(record.metadata()) {
|
||||
eprintln!("[{}] driver-manager: {}", record.level(), record.args());
|
||||
}
|
||||
}
|
||||
fn flush(&self) {}
|
||||
}
|
||||
|
||||
fn run_enumeration(
|
||||
manager: &Arc<Mutex<DeviceManager>>,
|
||||
scheme: &DriverManagerScheme,
|
||||
) -> (usize, usize) {
|
||||
let enum_start = Instant::now();
|
||||
let events = match manager.lock() {
|
||||
Ok(mut mgr) => mgr.enumerate(),
|
||||
Err(err) => {
|
||||
log::error!("failed to enumerate devices: manager lock poisoned: {err}");
|
||||
return (0, 0);
|
||||
}
|
||||
};
|
||||
let enum_duration = enum_start.elapsed();
|
||||
|
||||
let mut bound = 0usize;
|
||||
let mut deferred = 0usize;
|
||||
|
||||
for event in &events {
|
||||
log_timeline(event);
|
||||
match event {
|
||||
ProbeEvent::ProbeCompleted {
|
||||
device,
|
||||
driver_name,
|
||||
result,
|
||||
} => {
|
||||
match result {
|
||||
ProbeResult::Bound => {
|
||||
log::info!("bound: {} -> {}", device.path, driver_name);
|
||||
notify_bound_device(scheme, device, driver_name);
|
||||
bound += 1;
|
||||
}
|
||||
ProbeResult::Deferred { reason } => {
|
||||
log::info!("deferred: {} -> {} ({})", device.path, driver_name, reason);
|
||||
deferred += 1;
|
||||
}
|
||||
ProbeResult::Fatal { reason } => {
|
||||
log::error!("fatal: {} -> {} ({})", device.path, driver_name, reason);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
ProbeEvent::BusEnumerated { bus, device_count } => {
|
||||
log::info!("bus {} enumerated {} device(s)", bus, device_count);
|
||||
}
|
||||
ProbeEvent::BusEnumerationFailed { bus, error } => {
|
||||
log::error!("bus {} enumeration failed: {:?}", bus, error);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
log::info!(
|
||||
"enumeration complete: {} bound, {} deferred ({}ms total)",
|
||||
bound, deferred, enum_duration.as_millis()
|
||||
);
|
||||
|
||||
(bound, deferred)
|
||||
}
|
||||
|
||||
fn notify_bound_device(scheme: &DriverManagerScheme, device: &DeviceId, driver_name: &str) {
|
||||
if device.bus == "pci" {
|
||||
notify_bind(scheme, &device.path, driver_name);
|
||||
}
|
||||
}
|
||||
|
||||
fn reset_timeline_log() {
|
||||
if let Err(err) = fs::write(BOOT_TIMELINE_PATH, "") {
|
||||
log::warn!("failed to reset boot timeline log at {BOOT_TIMELINE_PATH}: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
fn log_timeline(event: &ProbeEvent) {
|
||||
let timestamp = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_millis();
|
||||
|
||||
let entry = match event {
|
||||
ProbeEvent::BusEnumerated { bus, device_count } => format!(
|
||||
r#"{{"ts":{},"event":"bus_enumerated","bus":"{}","count":{}}}"#,
|
||||
timestamp, bus, device_count
|
||||
),
|
||||
ProbeEvent::ProbeCompleted {
|
||||
device,
|
||||
driver_name,
|
||||
result,
|
||||
} => {
|
||||
let status = match result {
|
||||
ProbeResult::Bound => "bound",
|
||||
ProbeResult::Deferred { .. } => "deferred",
|
||||
ProbeResult::Fatal { .. } => "failed",
|
||||
ProbeResult::NotSupported => "skipped",
|
||||
};
|
||||
format!(
|
||||
r#"{{"ts":{},"event":"probe","device":"{}","driver":"{}","status":"{}"}}"#,
|
||||
timestamp, device.path, driver_name, status
|
||||
)
|
||||
}
|
||||
_ => return,
|
||||
};
|
||||
|
||||
match OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(BOOT_TIMELINE_PATH)
|
||||
{
|
||||
Ok(mut file) => {
|
||||
if let Err(err) = writeln!(file, "{entry}") {
|
||||
log::warn!("failed to append boot timeline entry to {BOOT_TIMELINE_PATH}: {err}");
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!("failed to open boot timeline log at {BOOT_TIMELINE_PATH}: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
log::set_logger(&StderrLogger).ok();
|
||||
log::set_max_level(log::LevelFilter::Info);
|
||||
|
||||
let args: Vec<String> = env::args().collect();
|
||||
let initfs = args.iter().any(|a| a == "--initfs");
|
||||
let hotplug_mode = args.iter().any(|a| a == "--hotplug");
|
||||
|
||||
let config_dir = if initfs {
|
||||
"/scheme/initfs/lib/drivers.d"
|
||||
} else {
|
||||
"/lib/drivers.d"
|
||||
};
|
||||
|
||||
let driver_configs = match DriverConfig::load_all(config_dir) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
log::error!("failed to load driver configs: {}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
if driver_configs.is_empty() {
|
||||
log::warn!("no driver configs found in {}", config_dir);
|
||||
process::exit(0);
|
||||
}
|
||||
|
||||
log::info!("loaded {} driver config(s)", driver_configs.len());
|
||||
|
||||
let manager_config = ManagerConfig {
|
||||
max_concurrent_probes: 4,
|
||||
deferred_retry_ms: 500,
|
||||
async_probe: true,
|
||||
};
|
||||
|
||||
let manager = Arc::new(Mutex::new(DeviceManager::new(manager_config.clone())));
|
||||
let scheme = Arc::new(DriverManagerScheme::new());
|
||||
|
||||
match manager.lock() {
|
||||
Ok(mut mgr) => {
|
||||
mgr.register_bus(Box::new(PciBus::new()));
|
||||
|
||||
for dc in &driver_configs {
|
||||
mgr.register_driver(Box::new(dc.clone()));
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("failed to configure driver manager: manager lock poisoned: {err}");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
let mgr_clone = Arc::clone(&manager);
|
||||
let scheme_clone = Arc::clone(&scheme);
|
||||
|
||||
reset_timeline_log();
|
||||
|
||||
if manager_config.async_probe {
|
||||
let handle = thread::spawn(move || {
|
||||
let (bound, deferred) = run_enumeration(&mgr_clone, scheme_clone.as_ref());
|
||||
log::info!("async enum: {} bound, {} deferred", bound, deferred);
|
||||
});
|
||||
if handle.join().is_err() {
|
||||
log::error!("initial enumeration thread panicked");
|
||||
process::exit(1);
|
||||
}
|
||||
} else {
|
||||
let (bound, deferred) = run_enumeration(&manager, scheme.as_ref());
|
||||
log::info!("enum complete: {} bound, {} deferred", bound, deferred);
|
||||
}
|
||||
|
||||
if let Err(err) = scheme::start_scheme_server(Arc::clone(&scheme)) {
|
||||
log::error!("{err}");
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
if hotplug_mode {
|
||||
log::info!("entering hotplug event loop");
|
||||
hotplug::run_hotplug_loop(manager.clone(), scheme.clone(), 2000);
|
||||
return;
|
||||
}
|
||||
|
||||
let max_retries = 30u32;
|
||||
for retry in 1..=max_retries {
|
||||
thread::sleep(Duration::from_millis(500));
|
||||
|
||||
let retry_events = match manager.lock() {
|
||||
Ok(mut mgr) => mgr.retry_deferred(),
|
||||
Err(err) => {
|
||||
log::error!("failed to retry deferred probes: manager lock poisoned: {err}");
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let mut remaining = 0;
|
||||
let mut newly_bound = 0;
|
||||
|
||||
for event in &retry_events {
|
||||
log_timeline(event);
|
||||
if let ProbeEvent::ProbeCompleted {
|
||||
device,
|
||||
driver_name,
|
||||
result,
|
||||
} = event
|
||||
{
|
||||
match result {
|
||||
ProbeResult::Bound => {
|
||||
newly_bound += 1;
|
||||
notify_bound_device(scheme.as_ref(), device, driver_name);
|
||||
}
|
||||
ProbeResult::Deferred { .. } => remaining += 1,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if remaining == 0 {
|
||||
log::info!("all deferred resolved after {} retries", retry);
|
||||
return;
|
||||
}
|
||||
|
||||
if newly_bound > 0 {
|
||||
log::info!(
|
||||
"retry #{}: {} new, {} remaining",
|
||||
retry,
|
||||
newly_bound,
|
||||
remaining
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
log::warn!("deferred probe retry limit reached");
|
||||
process::exit(0);
|
||||
}
|
||||
@@ -0,0 +1,448 @@
|
||||
#[cfg(target_os = "redox")]
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::fs;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
use redox_scheme::scheme::SchemeSync;
|
||||
#[cfg(target_os = "redox")]
|
||||
use redox_scheme::{CallerCtx, OpenResult};
|
||||
#[cfg(target_os = "redox")]
|
||||
use redox_scheme::{
|
||||
SignalBehavior, Socket,
|
||||
scheme::{SchemeState, register_sync_scheme},
|
||||
};
|
||||
#[cfg(target_os = "redox")]
|
||||
use syscall::Stat;
|
||||
#[cfg(target_os = "redox")]
|
||||
use syscall::error::{EACCES, EBADF, EINVAL, EIO, ENOENT, Error, Result};
|
||||
#[cfg(target_os = "redox")]
|
||||
use syscall::flag::{EventFlags, MODE_DIR, MODE_FILE, O_ACCMODE, O_RDONLY};
|
||||
#[cfg(target_os = "redox")]
|
||||
use syscall::schemev2::NewFdFlags;
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
const SCHEME_NAME: &str = "driver-manager";
|
||||
#[cfg(target_os = "redox")]
|
||||
const ROOT_ID: usize = 1;
|
||||
const MAX_EVENT_LINES: usize = 256;
|
||||
const PARAM_ROOT: &str = "/tmp/redbear-driver-params";
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
enum HandleKind {
|
||||
Root,
|
||||
Devices,
|
||||
Device(String),
|
||||
Bound,
|
||||
Events,
|
||||
}
|
||||
|
||||
pub struct DriverManagerScheme {
|
||||
pub bound_devices: Mutex<HashMap<String, String>>,
|
||||
events: Mutex<VecDeque<String>>,
|
||||
#[cfg(target_os = "redox")]
|
||||
handles: Mutex<BTreeMap<usize, HandleKind>>,
|
||||
#[cfg(target_os = "redox")]
|
||||
next_id: AtomicUsize,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
struct SchemeServer {
|
||||
scheme: Arc<DriverManagerScheme>,
|
||||
}
|
||||
|
||||
impl DriverManagerScheme {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
bound_devices: Mutex::new(HashMap::new()),
|
||||
events: Mutex::new(VecDeque::new()),
|
||||
#[cfg(target_os = "redox")]
|
||||
handles: Mutex::new(BTreeMap::new()),
|
||||
#[cfg(target_os = "redox")]
|
||||
next_id: AtomicUsize::new(ROOT_ID + 1),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bound_device_addresses(&self) -> Vec<String> {
|
||||
match self.sorted_bound_addresses() {
|
||||
Ok(addresses) => addresses,
|
||||
Err(err) => {
|
||||
log::error!("driver-manager: failed to snapshot bound devices: {err}");
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
fn alloc_handle(&self, kind: HandleKind) -> Result<usize> {
|
||||
let id = self.next_id.fetch_add(1, Ordering::Relaxed);
|
||||
let mut handles = self.handles.lock().map_err(|_| Error::new(EIO))?;
|
||||
handles.insert(id, kind);
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
fn handle(&self, id: usize) -> Result<HandleKind> {
|
||||
if id == ROOT_ID {
|
||||
return Ok(HandleKind::Root);
|
||||
}
|
||||
|
||||
let handles = self.handles.lock().map_err(|_| Error::new(EIO))?;
|
||||
handles.get(&id).cloned().ok_or(Error::new(EBADF))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
fn open_from_root(&self, path: &str) -> Result<HandleKind> {
|
||||
let trimmed = path.trim_matches('/');
|
||||
if trimmed.is_empty() {
|
||||
return Ok(HandleKind::Root);
|
||||
}
|
||||
|
||||
let segments = trimmed
|
||||
.split('/')
|
||||
.filter(|segment| !segment.is_empty())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
match segments.as_slice() {
|
||||
["devices"] => Ok(HandleKind::Devices),
|
||||
["bound"] => Ok(HandleKind::Bound),
|
||||
["events"] => Ok(HandleKind::Events),
|
||||
["devices", pci_addr] if Self::valid_pci_addr(pci_addr) => {
|
||||
let _ = self.device_status(pci_addr)?;
|
||||
Ok(HandleKind::Device((*pci_addr).to_string()))
|
||||
}
|
||||
_ => Err(Error::new(ENOENT)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
fn open_from_devices(&self, path: &str) -> Result<HandleKind> {
|
||||
let trimmed = path.trim_matches('/');
|
||||
if trimmed.is_empty() {
|
||||
return Ok(HandleKind::Devices);
|
||||
}
|
||||
|
||||
if trimmed.contains('/') || !Self::valid_pci_addr(trimmed) {
|
||||
return Err(Error::new(ENOENT));
|
||||
}
|
||||
|
||||
let _ = self.device_status(trimmed)?;
|
||||
Ok(HandleKind::Device(trimmed.to_string()))
|
||||
}
|
||||
|
||||
fn sorted_bound_addresses(&self) -> std::result::Result<Vec<String>, String> {
|
||||
let bound_devices = self
|
||||
.bound_devices
|
||||
.lock()
|
||||
.map_err(|err| format!("bound_devices lock poisoned: {err}"))?;
|
||||
let mut addresses = bound_devices.keys().cloned().collect::<Vec<_>>();
|
||||
addresses.sort_unstable();
|
||||
Ok(addresses)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
fn device_status(&self, pci_addr: &str) -> Result<String> {
|
||||
let bound_devices = self.bound_devices.lock().map_err(|_| Error::new(EIO))?;
|
||||
let driver_name = bound_devices
|
||||
.get(pci_addr)
|
||||
.cloned()
|
||||
.ok_or(Error::new(ENOENT))?;
|
||||
|
||||
Ok(format!(
|
||||
"pci_addr={pci_addr}\ndriver={driver_name}\nenabled=true\n"
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
fn events_output(&self) -> Result<String> {
|
||||
let events = self.events.lock().map_err(|_| Error::new(EIO))?;
|
||||
Ok(events.iter().cloned().collect::<String>())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
fn bound_output(&self) -> Result<String> {
|
||||
let bound_devices = self.bound_devices.lock().map_err(|_| Error::new(EIO))?;
|
||||
let mut entries = bound_devices
|
||||
.iter()
|
||||
.map(|(pci_addr, driver_name)| format!("{pci_addr} -> {driver_name}"))
|
||||
.collect::<Vec<_>>();
|
||||
entries.sort_unstable();
|
||||
|
||||
if entries.is_empty() {
|
||||
Ok(String::new())
|
||||
} else {
|
||||
Ok(format!("{}\n", entries.join("\n")))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
fn read_handle_string(&self, kind: &HandleKind) -> Result<String> {
|
||||
match kind {
|
||||
HandleKind::Root => Ok("devices\nevents\n".to_string()),
|
||||
HandleKind::Devices => {
|
||||
let addresses = self.sorted_bound_addresses().map_err(|err| {
|
||||
log::error!("driver-manager: failed to read bound device list: {err}");
|
||||
Error::new(EIO)
|
||||
})?;
|
||||
if addresses.is_empty() {
|
||||
Ok(String::new())
|
||||
} else {
|
||||
Ok(format!("{}\n", addresses.join("\n")))
|
||||
}
|
||||
}
|
||||
HandleKind::Device(pci_addr) => self.device_status(pci_addr),
|
||||
HandleKind::Bound => self.bound_output(),
|
||||
HandleKind::Events => self.events_output(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
fn handle_path(&self, kind: &HandleKind) -> String {
|
||||
match kind {
|
||||
HandleKind::Root => format!("{SCHEME_NAME}:/"),
|
||||
HandleKind::Devices => format!("{SCHEME_NAME}:/devices"),
|
||||
HandleKind::Device(pci_addr) => format!("{SCHEME_NAME}:/devices/{pci_addr}"),
|
||||
HandleKind::Bound => format!("{SCHEME_NAME}:/bound"),
|
||||
HandleKind::Events => format!("{SCHEME_NAME}:/events"),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
fn handle_mode(&self, kind: &HandleKind) -> u16 {
|
||||
match kind {
|
||||
HandleKind::Root | HandleKind::Devices => MODE_DIR | 0o755,
|
||||
HandleKind::Device(_) | HandleKind::Bound | HandleKind::Events => MODE_FILE | 0o644,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
fn valid_pci_addr(value: &str) -> bool {
|
||||
!value.is_empty()
|
||||
&& value
|
||||
.chars()
|
||||
.all(|ch| ch.is_ascii_hexdigit() || matches!(ch, ':' | '.'))
|
||||
}
|
||||
|
||||
fn push_event_line(&self, line: String) {
|
||||
match self.events.lock() {
|
||||
Ok(mut events) => {
|
||||
if events.len() >= MAX_EVENT_LINES {
|
||||
events.pop_front();
|
||||
}
|
||||
events.push_back(line);
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("driver-manager: failed to record hotplug event: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
impl SchemeServer {
|
||||
fn new(scheme: Arc<DriverManagerScheme>) -> Self {
|
||||
Self { scheme }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
impl SchemeSync for SchemeServer {
|
||||
fn scheme_root(&mut self) -> Result<usize> {
|
||||
Ok(ROOT_ID)
|
||||
}
|
||||
|
||||
fn openat(
|
||||
&mut self,
|
||||
dirfd: usize,
|
||||
path: &str,
|
||||
flags: usize,
|
||||
_fcntl_flags: u32,
|
||||
_ctx: &CallerCtx,
|
||||
) -> Result<OpenResult> {
|
||||
if flags & O_ACCMODE != O_RDONLY {
|
||||
return Err(Error::new(EACCES));
|
||||
}
|
||||
|
||||
let kind = match self.scheme.handle(dirfd)? {
|
||||
HandleKind::Root => self.scheme.open_from_root(path)?,
|
||||
HandleKind::Devices => self.scheme.open_from_devices(path)?,
|
||||
_ => return Err(Error::new(EACCES)),
|
||||
};
|
||||
|
||||
Ok(OpenResult::ThisScheme {
|
||||
number: self.scheme.alloc_handle(kind)?,
|
||||
flags: NewFdFlags::empty(),
|
||||
})
|
||||
}
|
||||
|
||||
fn read(
|
||||
&mut self,
|
||||
id: usize,
|
||||
buf: &mut [u8],
|
||||
offset: u64,
|
||||
_flags: u32,
|
||||
_ctx: &CallerCtx,
|
||||
) -> Result<usize> {
|
||||
let kind = self.scheme.handle(id)?;
|
||||
let data = self.scheme.read_handle_string(&kind)?;
|
||||
let bytes = data.as_bytes();
|
||||
let offset = usize::try_from(offset).map_err(|_| Error::new(EINVAL))?;
|
||||
|
||||
if offset >= bytes.len() {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let count = (bytes.len() - offset).min(buf.len());
|
||||
buf[..count].copy_from_slice(&bytes[offset..offset + count]);
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
fn fstat(&mut self, id: usize, stat: &mut Stat, _ctx: &CallerCtx) -> Result<()> {
|
||||
let kind = self.scheme.handle(id)?;
|
||||
stat.st_mode = self.scheme.handle_mode(&kind);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fpath(&mut self, id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> Result<usize> {
|
||||
let kind = self.scheme.handle(id)?;
|
||||
let path = self.scheme.handle_path(&kind);
|
||||
let bytes = path.as_bytes();
|
||||
let count = bytes.len().min(buf.len());
|
||||
buf[..count].copy_from_slice(&bytes[..count]);
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
fn fsync(&mut self, id: usize, _ctx: &CallerCtx) -> Result<()> {
|
||||
let _ = self.scheme.handle(id)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fcntl(&mut self, id: usize, _cmd: usize, _arg: usize, _ctx: &CallerCtx) -> Result<usize> {
|
||||
let _ = self.scheme.handle(id)?;
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn fevent(&mut self, id: usize, _flags: EventFlags, _ctx: &CallerCtx) -> Result<EventFlags> {
|
||||
let _ = self.scheme.handle(id)?;
|
||||
Ok(EventFlags::empty())
|
||||
}
|
||||
|
||||
fn on_close(&mut self, id: usize) {
|
||||
if id == ROOT_ID {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Ok(mut handles) = self.scheme.handles.lock() {
|
||||
handles.remove(&id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_driver_param(pci_addr: &str, param: &str, value: &str) -> std::io::Result<()> {
|
||||
let dir = format!("{PARAM_ROOT}/{pci_addr}");
|
||||
fs::create_dir_all(&dir)?;
|
||||
fs::write(format!("{dir}/{param}"), value)
|
||||
}
|
||||
|
||||
pub fn notify_bind(scheme: &DriverManagerScheme, pci_addr: &str, driver_name: &str) {
|
||||
match scheme.bound_devices.lock() {
|
||||
Ok(mut bound_devices) => {
|
||||
bound_devices.insert(pci_addr.to_string(), driver_name.to_string());
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
"driver-manager: failed to update bound device state for {pci_addr}: {err}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
scheme.push_event_line(format!(
|
||||
"action=bind pci_addr={pci_addr} driver={driver_name}\n"
|
||||
));
|
||||
|
||||
if let Err(err) = write_driver_param(pci_addr, "driver", driver_name) {
|
||||
log::warn!("driver-manager: failed to write driver param for {pci_addr}: {err}");
|
||||
}
|
||||
if let Err(err) = write_driver_param(pci_addr, "enabled", "true") {
|
||||
log::warn!("driver-manager: failed to write enabled param for {pci_addr}: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn notify_unbind(scheme: &DriverManagerScheme, pci_addr: &str) {
|
||||
let previous_driver = match scheme.bound_devices.lock() {
|
||||
Ok(mut bound_devices) => bound_devices.remove(pci_addr),
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
"driver-manager: failed to remove bound device state for {pci_addr}: {err}"
|
||||
);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let event_line = if let Some(driver_name) = previous_driver.as_deref() {
|
||||
format!("action=unbind pci_addr={pci_addr} driver={driver_name}\n")
|
||||
} else {
|
||||
format!("action=unbind pci_addr={pci_addr}\n")
|
||||
};
|
||||
scheme.push_event_line(event_line);
|
||||
|
||||
if let Err(err) = write_driver_param(pci_addr, "driver", "") {
|
||||
log::warn!("driver-manager: failed to clear driver param for {pci_addr}: {err}");
|
||||
}
|
||||
if let Err(err) = write_driver_param(pci_addr, "enabled", "false") {
|
||||
log::warn!("driver-manager: failed to write disabled param for {pci_addr}: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
pub fn start_scheme_server(scheme: Arc<DriverManagerScheme>) -> std::result::Result<(), String> {
|
||||
let socket = Socket::create()
|
||||
.map_err(|err| format!("driver-manager: failed to create scheme socket: {err}"))?;
|
||||
let mut server = SchemeServer::new(scheme);
|
||||
|
||||
register_sync_scheme(&socket, SCHEME_NAME, &mut server)
|
||||
.map_err(|err| format!("driver-manager: failed to register scheme:{SCHEME_NAME}: {err}"))?;
|
||||
|
||||
log::info!("driver-manager: registered scheme:{SCHEME_NAME}");
|
||||
|
||||
std::thread::Builder::new()
|
||||
.name("driver-manager-scheme".to_string())
|
||||
.spawn(move || {
|
||||
let mut state = SchemeState::new();
|
||||
|
||||
loop {
|
||||
let request = match socket.next_request(SignalBehavior::Restart) {
|
||||
Ok(Some(request)) => request,
|
||||
Ok(None) => {
|
||||
log::info!("driver-manager: scheme socket closed, shutting down");
|
||||
break;
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("driver-manager: failed to read scheme request: {err}");
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
if let redox_scheme::RequestKind::Call(request) = request.kind() {
|
||||
let response = request.handle_sync(&mut server, &mut state);
|
||||
if let Err(err) = socket.write_response(response, SignalBehavior::Restart) {
|
||||
log::error!("driver-manager: failed to write scheme response: {err}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.map_err(|err| format!("driver-manager: failed to spawn scheme server thread: {err}"))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
pub fn start_scheme_server(_scheme: Arc<DriverManagerScheme>) -> std::result::Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user