Extend Red Bear runtime tooling
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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<Self, String> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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<Self, String> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user