From d9faecc4ba13b94e6afc2b0714fe6ee020fee83a Mon Sep 17 00:00:00 2001 From: Vasilito Date: Wed, 15 Apr 2026 12:57:45 +0100 Subject: [PATCH] Extend Red Bear runtime tooling Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- .../system/redbear-hwutils/source/Cargo.toml | 29 +++++ .../source/src/bin/evtest-rbos.rs | 79 +++++++++++++ .../source/src/bin/input-inject-rbos.rs | 106 ++++++++++++++++++ .../src/bin/redbear-phase-iommu-check.rs | 67 +++++++++++ .../src/bin/redbear-phase3-input-check.rs | 38 +++++++ .../src/bin/redbear-phase4-wayland-check.rs | 47 ++++++++ .../src/bin/redbear-phase5-network-check.rs | 51 +++++++++ .../src/bin/redbear-phase6-kde-check.rs | 48 ++++++++ 8 files changed, 465 insertions(+) create mode 100644 local/recipes/system/redbear-hwutils/source/src/bin/evtest-rbos.rs create mode 100644 local/recipes/system/redbear-hwutils/source/src/bin/input-inject-rbos.rs create mode 100644 local/recipes/system/redbear-hwutils/source/src/bin/redbear-phase-iommu-check.rs create mode 100644 local/recipes/system/redbear-hwutils/source/src/bin/redbear-phase3-input-check.rs create mode 100644 local/recipes/system/redbear-hwutils/source/src/bin/redbear-phase4-wayland-check.rs create mode 100644 local/recipes/system/redbear-hwutils/source/src/bin/redbear-phase5-network-check.rs create mode 100644 local/recipes/system/redbear-hwutils/source/src/bin/redbear-phase6-kde-check.rs diff --git a/local/recipes/system/redbear-hwutils/source/Cargo.toml b/local/recipes/system/redbear-hwutils/source/Cargo.toml index 129b467c..48b10dc6 100644 --- a/local/recipes/system/redbear-hwutils/source/Cargo.toml +++ b/local/recipes/system/redbear-hwutils/source/Cargo.toml @@ -11,6 +11,35 @@ path = "src/bin/lspci.rs" name = "lsusb" path = "src/bin/lsusb.rs" +[[bin]] +name = "redbear-evtest" +path = "src/bin/evtest-rbos.rs" + +[[bin]] +name = "redbear-input-inject" +path = "src/bin/input-inject-rbos.rs" + +[[bin]] +name = "redbear-phase3-input-check" +path = "src/bin/redbear-phase3-input-check.rs" + +[[bin]] +name = "redbear-phase4-wayland-check" +path = "src/bin/redbear-phase4-wayland-check.rs" + +[[bin]] +name = "redbear-phase5-network-check" +path = "src/bin/redbear-phase5-network-check.rs" + +[[bin]] +name = "redbear-phase6-kde-check" +path = "src/bin/redbear-phase6-kde-check.rs" + +[[bin]] +name = "redbear-phase-iommu-check" +path = "src/bin/redbear-phase-iommu-check.rs" + [dependencies] serde = { version = "1", features = ["derive"] } serde_json = "1" +orbclient = "0.3" diff --git a/local/recipes/system/redbear-hwutils/source/src/bin/evtest-rbos.rs b/local/recipes/system/redbear-hwutils/source/src/bin/evtest-rbos.rs new file mode 100644 index 00000000..3fb8a014 --- /dev/null +++ b/local/recipes/system/redbear-hwutils/source/src/bin/evtest-rbos.rs @@ -0,0 +1,79 @@ +use std::fs::File; +use std::io::Read; +use std::process; +use std::time::{Duration, Instant}; + +use redbear_hwutils::parse_args; + +const PROGRAM: &str = "redbear-evtest"; +const USAGE: &str = "Usage: redbear-evtest\n\nRead the first keyboard event from the udev-backed evdev consumer path and print it."; +const EVENT_SIZE: usize = 24; +const EV_KEY: u16 = 0x01; + +#[derive(Clone, Copy, Debug)] +struct InputEvent { + event_type: u16, + code: u16, + value: i32, +} + +impl InputEvent { + fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() != EVENT_SIZE { + return Err(format!("expected {EVENT_SIZE} bytes, got {}", bytes.len())); + } + + Ok(Self { + event_type: u16::from_le_bytes([bytes[16], bytes[17]]), + code: u16::from_le_bytes([bytes[18], bytes[19]]), + value: i32::from_le_bytes([bytes[20], bytes[21], bytes[22], bytes[23]]), + }) + } +} + +fn run() -> Result<(), String> { + parse_args(PROGRAM, USAGE, std::env::args()).map_err(|err| { + if err.is_empty() { + process::exit(0); + } + err + })?; + + let candidate_paths = ["/scheme/udev/dev/input/event0", "/scheme/evdev/event0"]; + let mut last_error = String::new(); + + for event_path in candidate_paths { + let mut file = match File::open(event_path) { + Ok(file) => file, + Err(err) => { + last_error = format!("failed to open {event_path}: {err}"); + continue; + } + }; + + let deadline = Instant::now() + Duration::from_secs(10); + let mut raw = [0_u8; EVENT_SIZE]; + + while Instant::now() < deadline { + file.read_exact(&mut raw) + .map_err(|err| format!("failed to read evdev event from {event_path}: {err}"))?; + let event = InputEvent::from_bytes(&raw)?; + if event.event_type == EV_KEY { + println!("SOURCE={event_path}"); + println!("EV_KEY code={} value={}", event.code, event.value); + return Ok(()); + } + } + + last_error = format!("timed out waiting for a key event on {event_path}"); + } + + Err(last_error) +} + +fn main() { + if let Err(err) = run() { + eprintln!("{PROGRAM}: {err}"); + process::exit(1); + } +} diff --git a/local/recipes/system/redbear-hwutils/source/src/bin/input-inject-rbos.rs b/local/recipes/system/redbear-hwutils/source/src/bin/input-inject-rbos.rs new file mode 100644 index 00000000..abed814e --- /dev/null +++ b/local/recipes/system/redbear-hwutils/source/src/bin/input-inject-rbos.rs @@ -0,0 +1,106 @@ +use std::fs::{File, OpenOptions}; +use std::io::{Read, Write}; +use std::process; +use std::time::{Duration, Instant}; + +use orbclient::{KeyEvent, K_A}; +use redbear_hwutils::parse_args; + +const PROGRAM: &str = "redbear-input-inject"; +const USAGE: &str = + "Usage: redbear-input-inject\n\nInject a synthetic 'A' key press/release through /scheme/input/producer and verify the first evdev consumer event."; +const EVENT_SIZE: usize = 24; +const EV_KEY: u16 = 0x01; + +#[derive(Clone, Copy, Debug)] +struct InputEvent { + event_type: u16, + code: u16, + value: i32, +} + +impl InputEvent { + fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() != EVENT_SIZE { + return Err(format!("expected {EVENT_SIZE} bytes, got {}", bytes.len())); + } + + Ok(Self { + event_type: u16::from_le_bytes([bytes[16], bytes[17]]), + code: u16::from_le_bytes([bytes[18], bytes[19]]), + value: i32::from_le_bytes([bytes[20], bytes[21], bytes[22], bytes[23]]), + }) + } +} + +fn open_consumer() -> Result<(File, &'static str), String> { + for path in ["/scheme/udev/dev/input/event0", "/scheme/evdev/event0"] { + if let Ok(file) = File::open(path) { + return Ok((file, path)); + } + } + Err("failed to open an evdev consumer path".to_string()) +} + +fn run() -> Result<(), String> { + parse_args(PROGRAM, USAGE, std::env::args()).map_err(|err| { + if err.is_empty() { + process::exit(0); + } + err + })?; + + let (mut consumer, consumer_path) = open_consumer()?; + + let mut producer = OpenOptions::new() + .write(true) + .open("/scheme/input/producer") + .map_err(|err| format!("failed to open /scheme/input/producer: {err}"))?; + + producer + .write_all( + &KeyEvent { + character: 'a', + scancode: K_A, + pressed: true, + } + .to_event(), + ) + .map_err(|err| format!("failed to inject key press: {err}"))?; + producer + .write_all( + &KeyEvent { + character: 'a', + scancode: K_A, + pressed: false, + } + .to_event(), + ) + .map_err(|err| format!("failed to inject key release: {err}"))?; + + let deadline = Instant::now() + Duration::from_secs(5); + let mut raw = [0_u8; EVENT_SIZE]; + while Instant::now() < deadline { + consumer + .read_exact(&mut raw) + .map_err(|err| format!("failed to read evdev event from {consumer_path}: {err}"))?; + let event = InputEvent::from_bytes(&raw)?; + if event.event_type == EV_KEY { + println!("Injected synthetic key event: A"); + println!("SOURCE={consumer_path}"); + println!("EV_KEY code={} value={}", event.code, event.value); + return Ok(()); + } + } + + Err(format!( + "timed out waiting for an evdev consumer event on {consumer_path}" + )) +} + +fn main() { + if let Err(err) = run() { + eprintln!("{PROGRAM}: {err}"); + process::exit(1); + } +} diff --git a/local/recipes/system/redbear-hwutils/source/src/bin/redbear-phase-iommu-check.rs b/local/recipes/system/redbear-hwutils/source/src/bin/redbear-phase-iommu-check.rs new file mode 100644 index 00000000..0d9c9d18 --- /dev/null +++ b/local/recipes/system/redbear-hwutils/source/src/bin/redbear-phase-iommu-check.rs @@ -0,0 +1,67 @@ +use std::path::Path; +use std::process::{self, Command}; + +use redbear_hwutils::parse_args; + +const PROGRAM: &str = "redbear-phase-iommu-check"; +const USAGE: &str = "Usage: redbear-phase-iommu-check\n\nShow the installed IOMMU validation surface inside the guest."; + +fn require_path(path: &str) -> Result<(), String> { + if Path::new(path).exists() { + println!("{path}"); + Ok(()) + } else { + Err(format!("missing {path}")) + } +} + +fn run() -> Result<(), String> { + parse_args(PROGRAM, USAGE, std::env::args()).map_err(|err| { + if err.is_empty() { + process::exit(0); + } + err + })?; + + println!("=== Red Bear OS IOMMU Runtime Check ==="); + require_path("/usr/bin/iommu")?; + + let output = Command::new("/usr/bin/iommu") + .env("IOMMU_LOG", "info") + .arg("--self-test-init") + .output() + .map_err(|err| format!("failed to run /usr/bin/iommu --self-test-init: {err}"))?; + + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + print!("{}", stdout); + print!("{}", stderr); + + if !output.status.success() { + return Err(format!( + "iommu self-test exited with status {:?}", + output.status.code() + )); + } + if !stdout.contains("units_detected=") { + return Err("iommu self-test did not report detected unit count".to_string()); + } + if !stdout.contains("units_initialized_now=") { + return Err("iommu self-test did not report initialized unit count".to_string()); + } + if !stdout.contains("units_initialized_after=") { + return Err("iommu self-test did not report initialized-after count".to_string()); + } + if !stdout.contains("events_drained=") { + return Err("iommu self-test did not report drained events".to_string()); + } + + Ok(()) +} + +fn main() { + if let Err(err) = run() { + eprintln!("{PROGRAM}: {err}"); + process::exit(1); + } +} diff --git a/local/recipes/system/redbear-hwutils/source/src/bin/redbear-phase3-input-check.rs b/local/recipes/system/redbear-hwutils/source/src/bin/redbear-phase3-input-check.rs new file mode 100644 index 00000000..a12a153b --- /dev/null +++ b/local/recipes/system/redbear-hwutils/source/src/bin/redbear-phase3-input-check.rs @@ -0,0 +1,38 @@ +use std::process::{self, Command}; + +use redbear_hwutils::parse_args; + +const PROGRAM: &str = "redbear-phase3-input-check"; +const USAGE: &str = + "Usage: redbear-phase3-input-check\n\nRun the Phase 3 input-path check inside the guest."; + +fn run_cmd(name: &str) -> Result<(), String> { + let status = Command::new(name) + .status() + .map_err(|err| format!("failed to run {name}: {err}"))?; + if status.success() { + Ok(()) + } else { + Err(format!("{name} exited with status {status}")) + } +} + +fn run() -> Result<(), String> { + parse_args(PROGRAM, USAGE, std::env::args()).map_err(|err| { + if err.is_empty() { + process::exit(0); + } + err + })?; + + run_cmd("redbear-input-inject")?; + run_cmd("redbear-evtest")?; + Ok(()) +} + +fn main() { + if let Err(err) = run() { + eprintln!("{PROGRAM}: {err}"); + process::exit(1); + } +} diff --git a/local/recipes/system/redbear-hwutils/source/src/bin/redbear-phase4-wayland-check.rs b/local/recipes/system/redbear-hwutils/source/src/bin/redbear-phase4-wayland-check.rs new file mode 100644 index 00000000..c58d72c9 --- /dev/null +++ b/local/recipes/system/redbear-hwutils/source/src/bin/redbear-phase4-wayland-check.rs @@ -0,0 +1,47 @@ +use std::path::Path; +use std::process::{self, Command}; + +use redbear_hwutils::parse_args; + +const PROGRAM: &str = "redbear-phase4-wayland-check"; +const USAGE: &str = "Usage: redbear-phase4-wayland-check\n\nShow the installed Phase 4 Wayland launch surface inside the guest."; + +fn require_path(path: &str) -> Result<(), String> { + if Path::new(path).exists() { + println!("{path}"); + Ok(()) + } else { + Err(format!("missing {path}")) + } +} + +fn run() -> Result<(), String> { + parse_args(PROGRAM, USAGE, std::env::args()).map_err(|err| { + if err.is_empty() { + process::exit(0); + } + err + })?; + + println!("=== Red Bear OS Phase 4 Wayland Runtime Check ==="); + require_path("/usr/bin/orbital-wayland")?; + require_path("/usr/bin/wayland-session")?; + require_path("/usr/bin/smallvil")?; + + let status = Command::new("redbear-info") + .arg("--json") + .status() + .map_err(|err| format!("failed to run redbear-info --json: {err}"))?; + if status.success() { + Ok(()) + } else { + Err(format!("redbear-info exited with status {status}")) + } +} + +fn main() { + if let Err(err) = run() { + eprintln!("{PROGRAM}: {err}"); + process::exit(1); + } +} diff --git a/local/recipes/system/redbear-hwutils/source/src/bin/redbear-phase5-network-check.rs b/local/recipes/system/redbear-hwutils/source/src/bin/redbear-phase5-network-check.rs new file mode 100644 index 00000000..2c799604 --- /dev/null +++ b/local/recipes/system/redbear-hwutils/source/src/bin/redbear-phase5-network-check.rs @@ -0,0 +1,51 @@ +use std::path::Path; +use std::process::{self, Command}; + +use redbear_hwutils::parse_args; + +const PROGRAM: &str = "redbear-phase5-network-check"; +const USAGE: &str = "Usage: redbear-phase5-network-check\n\nShow the installed Phase 5 networking/session plumbing surface inside the guest."; + +fn require_path(path: &str) -> Result<(), String> { + if Path::new(path).exists() { + println!("{path}"); + Ok(()) + } else { + Err(format!("missing {path}")) + } +} + +fn run() -> Result<(), String> { + parse_args(PROGRAM, USAGE, std::env::args()).map_err(|err| { + if err.is_empty() { + process::exit(0); + } + err + })?; + + println!("=== Red Bear OS Phase 5 Networking Check ==="); + require_path("/usr/bin/dbus-daemon")?; + + let info_status = Command::new("redbear-info") + .arg("--json") + .status() + .map_err(|err| format!("failed to run redbear-info --json: {err}"))?; + if !info_status.success() { + return Err(format!("redbear-info exited with status {info_status}")); + } + + let _ = Command::new("netctl").arg("status").status(); + if Path::new("/run/dbus/system_bus_socket").exists() { + println!("DBUS_SYSTEM_BUS=present"); + } else { + println!("DBUS_SYSTEM_BUS=missing"); + } + Ok(()) +} + +fn main() { + if let Err(err) = run() { + eprintln!("{PROGRAM}: {err}"); + process::exit(1); + } +} diff --git a/local/recipes/system/redbear-hwutils/source/src/bin/redbear-phase6-kde-check.rs b/local/recipes/system/redbear-hwutils/source/src/bin/redbear-phase6-kde-check.rs new file mode 100644 index 00000000..bbc629ac --- /dev/null +++ b/local/recipes/system/redbear-hwutils/source/src/bin/redbear-phase6-kde-check.rs @@ -0,0 +1,48 @@ +use std::path::Path; +use std::process::{self, Command}; + +use redbear_hwutils::parse_args; + +const PROGRAM: &str = "redbear-phase6-kde-check"; +const USAGE: &str = "Usage: redbear-phase6-kde-check\n\nShow the installed Phase 6 KDE session surface inside the guest."; + +fn require_path(path: &str) -> Result<(), String> { + if Path::new(path).exists() { + println!("{path}"); + Ok(()) + } else { + Err(format!("missing {path}")) + } +} + +fn run() -> Result<(), String> { + parse_args(PROGRAM, USAGE, std::env::args()).map_err(|err| { + if err.is_empty() { + process::exit(0); + } + err + })?; + + println!("=== Red Bear OS Phase 6 KDE Runtime Check ==="); + require_path("/usr/bin/orbital-kde")?; + require_path("/usr/bin/kwin_wayland")?; + require_path("/usr/bin/dbus-daemon")?; + require_path("/usr/bin/seatd")?; + + let status = Command::new("redbear-info") + .arg("--json") + .status() + .map_err(|err| format!("failed to run redbear-info --json: {err}"))?; + if status.success() { + Ok(()) + } else { + Err(format!("redbear-info exited with status {status}")) + } +} + +fn main() { + if let Err(err) = run() { + eprintln!("{PROGRAM}: {err}"); + process::exit(1); + } +}