Files
RedBear-OS/local/recipes/system/redbear-hwutils/source/src/bin/redbear-greeter-check.rs
T
vasilito 8acc73d774 milestone: desktop path Phases 1-5
Phase 1 (Runtime Substrate): 4 check binaries, --probe, POSIX tests
Phase 2 (Wayland Compositor): bounded scaffold, zero warnings
Phase 3 (KWin Session): preflight checker (KWin stub, gated on Qt6Quick)
Phase 4 (KDE Plasma): 18 KF6 enabled, preflight checker
Phase 5 (Hardware GPU): DRM/firmware/Mesa preflight checker

Build: zero warnings, all scripts syntax-clean. Oracle-verified.
2026-04-29 09:54:06 +01:00

357 lines
12 KiB
Rust

use std::{
fs,
io::{BufRead, BufReader, Write},
os::unix::net::UnixStream,
path::Path,
process, thread,
time::{Duration, Instant},
};
use redbear_login_protocol::{GreeterRequest as Request, GreeterResponse};
const PROGRAM: &str = "redbear-greeter-check";
const USAGE: &str = "Usage: redbear-greeter-check [--invalid USER PASSWORD | --valid USER PASSWORD]\n\nQuery the installed Red Bear greeter surface inside the guest.";
const GREETER_SOCKET: &str = "/run/redbear-greeterd.sock";
const GREETERD_BIN: &str = "/usr/bin/redbear-greeterd";
const GREETER_UI_BIN: &str = "/usr/bin/redbear-greeter-ui";
const AUTHD_BIN: &str = "/usr/bin/redbear-authd";
const SESSION_LAUNCH_BIN: &str = "/usr/bin/redbear-session-launch";
const GREETER_BACKGROUND: &str = "/usr/share/redbear/greeter/background.png";
const GREETER_ICON: &str = "/usr/share/redbear/greeter/icon.png";
const AUTHD_SERVICE: &str = "/usr/lib/init.d/19_redbear-authd.service";
const DISPLAY_SHIM_SERVICE: &str = "/usr/lib/init.d/20_display.service";
const GREETER_SERVICE: &str = "/usr/lib/init.d/20_greeter.service";
const ACTIVATE_CONSOLE_SERVICE: &str = "/usr/lib/init.d/29_activate_console.service";
const CONSOLE_SERVICE: &str = "/usr/lib/init.d/30_console.service";
const DEBUG_CONSOLE_SERVICE: &str = "/usr/lib/init.d/31_debug_console.service";
const VALIDATION_REQUEST: &str = "/run/redbear-kde-session.validation-request";
const VALIDATION_SUCCESS: &str = "/run/redbear-kde-session.validation-success";
#[derive(Debug, PartialEq, Eq)]
enum Mode {
Status,
Invalid { username: String, password: String },
Valid { username: String, password: String },
}
fn parse_credentials(
args: &mut impl Iterator<Item = String>,
flag: &str,
) -> Result<(String, String), String> {
let username = args
.next()
.ok_or_else(|| format!("missing username after {flag}"))?;
let password = args
.next()
.ok_or_else(|| format!("missing password after {flag}"))?;
if args.next().is_some() {
return Err(format!(
"unexpected extra arguments after {flag} USER PASSWORD"
));
}
Ok((username, password))
}
fn parse_mode_from_args<I>(args: I) -> Result<Mode, String>
where
I: IntoIterator<Item = String>,
{
let mut args = args.into_iter();
match args.next() {
None => Ok(Mode::Status),
Some(flag) if flag == "--help" || flag == "-h" => Err(String::new()),
Some(flag) if flag == "--invalid" => {
let (username, password) = parse_credentials(&mut args, &flag)?;
Ok(Mode::Invalid { username, password })
}
Some(flag) if flag == "--valid" => {
let (username, password) = parse_credentials(&mut args, &flag)?;
Ok(Mode::Valid { username, password })
}
Some(other) => Err(format!("unsupported argument '{other}'")),
}
}
fn parse_mode() -> Result<Mode, String> {
parse_mode_from_args(std::env::args().skip(1))
}
fn send_request(request: &Request) -> Result<GreeterResponse, String> {
let mut stream = UnixStream::connect(GREETER_SOCKET)
.map_err(|err| format!("failed to connect to {GREETER_SOCKET}: {err}"))?;
let payload = serde_json::to_string(request)
.map_err(|err| format!("failed to serialize greeter request: {err}"))?;
stream
.write_all(payload.as_bytes())
.and_then(|_| stream.write_all(b"\n"))
.map_err(|err| format!("failed to write greeter request: {err}"))?;
let mut reader = BufReader::new(stream);
let mut line = String::new();
reader
.read_line(&mut line)
.map_err(|err| format!("failed to read greeter response: {err}"))?;
serde_json::from_str(line.trim())
.map_err(|err| format!("failed to parse greeter response: {err}"))
}
fn require_path(path: &str) -> Result<(), String> {
if Path::new(path).exists() {
println!("{path}");
Ok(())
} else {
Err(format!("missing {path}"))
}
}
fn wait_for_validation_marker(path: &str, timeout: Duration) -> Result<(), String> {
let start = Instant::now();
while start.elapsed() <= timeout {
if Path::new(path).exists() {
return Ok(());
}
thread::sleep(Duration::from_millis(250));
}
Err(format!("timed out waiting for {path}"))
}
fn wait_for_greeter_ready(timeout: Duration) -> Result<(), String> {
let start = Instant::now();
while start.elapsed() <= timeout {
match send_request(&Request::Hello { version: 1 }) {
Ok(GreeterResponse::HelloOk { state, message, .. }) if state == "greeter_ready" => {
println!("GREETER_VALID_READY_MESSAGE={message}");
return Ok(());
}
Ok(_) => {}
Err(_) => {}
}
thread::sleep(Duration::from_millis(250));
}
Err(String::from(
"timed out waiting for greeter to return to greeter_ready",
))
}
fn run_status() -> Result<(), String> {
println!("=== Red Bear Greeter Runtime Check ===");
require_path(GREETERD_BIN)?;
require_path(GREETER_UI_BIN)?;
require_path(AUTHD_BIN)?;
require_path(SESSION_LAUNCH_BIN)?;
require_path(GREETER_BACKGROUND)?;
require_path(GREETER_ICON)?;
require_path(AUTHD_SERVICE)?;
require_path(DISPLAY_SHIM_SERVICE)?;
require_path(GREETER_SERVICE)?;
require_path(ACTIVATE_CONSOLE_SERVICE)?;
require_path(CONSOLE_SERVICE)?;
require_path(DEBUG_CONSOLE_SERVICE)?;
require_path(GREETER_SOCKET)?;
match send_request(&Request::Hello { version: 1 })? {
GreeterResponse::HelloOk {
background,
icon,
session_name,
state,
message,
} => {
println!("GREETER_BACKGROUND={background}");
println!("GREETER_ICON={icon}");
println!("GREETER_SESSION={session_name}");
println!("GREETER_STATE={state}");
println!("GREETER_MESSAGE={message}");
println!("GREETER_HELLO=ok");
Ok(())
}
GreeterResponse::Error { message } => Err(format!("greeter hello failed: {message}")),
GreeterResponse::ActionResult { .. } => Err(String::from(
"unexpected power response when greeting greeter",
)),
GreeterResponse::LoginResult { .. } => Err(String::from(
"unexpected login result when greeting greeter",
)),
}
}
fn run_invalid(username: &str, password: &str) -> Result<(), String> {
match send_request(&Request::SubmitLogin {
username: username.to_string(),
password: password.to_string(),
})? {
GreeterResponse::LoginResult { ok, state, message } => {
println!("GREETER_INVALID_STATE={state}");
println!("GREETER_INVALID_MESSAGE={message}");
if ok {
Err(String::from("invalid login unexpectedly succeeded"))
} else {
println!("GREETER_INVALID=ok");
Ok(())
}
}
GreeterResponse::Error { message } => {
Err(format!("invalid-login request failed: {message}"))
}
GreeterResponse::ActionResult { .. } => {
Err(String::from("unexpected power response for invalid login"))
}
GreeterResponse::HelloOk { .. } => {
Err(String::from("unexpected hello response for invalid login"))
}
}
}
fn run_valid(username: &str, password: &str) -> Result<(), String> {
let _ = fs::remove_file(VALIDATION_REQUEST);
let _ = fs::remove_file(VALIDATION_SUCCESS);
fs::write(VALIDATION_REQUEST, b"bounded-session\n")
.map_err(|err| format!("failed to create validation request: {err}"))?;
match send_request(&Request::SubmitLogin {
username: username.to_string(),
password: password.to_string(),
})? {
GreeterResponse::LoginResult { ok, state, message } => {
println!("GREETER_VALID_STATE={state}");
println!("GREETER_VALID_MESSAGE={message}");
if !ok {
let _ = fs::remove_file(VALIDATION_REQUEST);
return Err(String::from("valid login unexpectedly failed"));
}
}
GreeterResponse::Error { message } => {
let _ = fs::remove_file(VALIDATION_REQUEST);
return Err(format!("valid-login request failed: {message}"));
}
GreeterResponse::ActionResult { .. } => {
let _ = fs::remove_file(VALIDATION_REQUEST);
return Err(String::from("unexpected power response for valid login"));
}
GreeterResponse::HelloOk { .. } => {
let _ = fs::remove_file(VALIDATION_REQUEST);
return Err(String::from("unexpected hello response for valid login"));
}
}
wait_for_validation_marker(VALIDATION_SUCCESS, Duration::from_secs(30))?;
println!("GREETER_VALID_SESSION=started");
wait_for_greeter_ready(Duration::from_secs(30))?;
let _ = fs::remove_file(VALIDATION_REQUEST);
let _ = fs::remove_file(VALIDATION_SUCCESS);
println!("GREETER_VALID=ok");
Ok(())
}
fn main() {
let mode = match parse_mode() {
Ok(mode) => mode,
Err(err) if err.is_empty() => {
println!("{USAGE}");
process::exit(0);
}
Err(err) => {
eprintln!("{PROGRAM}: {err}");
eprintln!("{USAGE}");
process::exit(1);
}
};
let result = match mode {
Mode::Status => run_status(),
Mode::Invalid { username, password } => run_invalid(&username, &password),
Mode::Valid { username, password } => run_valid(&username, &password),
};
if let Err(err) = result {
eprintln!("{PROGRAM}: {err}");
process::exit(1);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_mode_defaults_to_status() {
assert_eq!(
parse_mode_from_args(Vec::<String>::new()).expect("status mode should parse"),
Mode::Status
);
}
#[test]
fn parse_mode_accepts_invalid_login_arguments() {
assert_eq!(
parse_mode_from_args(vec![
String::from("--invalid"),
String::from("alice"),
String::from("wrong"),
])
.expect("invalid-login mode should parse"),
Mode::Invalid {
username: String::from("alice"),
password: String::from("wrong"),
}
);
}
#[test]
fn parse_mode_accepts_valid_login_arguments() {
assert_eq!(
parse_mode_from_args(vec![
String::from("--valid"),
String::from("alice"),
String::from("password"),
])
.expect("valid-login mode should parse"),
Mode::Valid {
username: String::from("alice"),
password: String::from("password"),
}
);
}
#[test]
fn parse_mode_rejects_extra_valid_arguments() {
assert_eq!(
parse_mode_from_args(vec![
String::from("--valid"),
String::from("alice"),
String::from("password"),
String::from("extra"),
]),
Err(String::from(
"unexpected extra arguments after --valid USER PASSWORD"
))
);
}
#[test]
fn parse_mode_rejects_extra_invalid_arguments() {
assert_eq!(
parse_mode_from_args(vec![
String::from("--invalid"),
String::from("alice"),
String::from("wrong"),
String::from("extra"),
]),
Err(String::from(
"unexpected extra arguments after --invalid USER PASSWORD"
))
);
}
#[test]
fn parse_mode_rejects_unknown_flags() {
assert_eq!(
parse_mode_from_args(vec![String::from("--bogus")]),
Err(String::from("unsupported argument '--bogus'"))
);
}
}