Advance redbear-full Wayland, greeter, and Qt integration
Consolidate the active desktop path around redbear-full while landing the greeter/session stack and the runtime fixes needed to keep Wayland and KWin bring-up moving forward.
This commit is contained in:
@@ -0,0 +1,350 @@
|
||||
use std::{
|
||||
fs,
|
||||
io::{BufRead, BufReader, Write},
|
||||
os::unix::net::UnixStream,
|
||||
path::Path,
|
||||
process,
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
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, Serialize)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
enum Request<'a> {
|
||||
Hello { version: u32 },
|
||||
SubmitLogin { username: &'a str, password: &'a str },
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
enum Response {
|
||||
HelloOk {
|
||||
background: String,
|
||||
icon: String,
|
||||
session_name: String,
|
||||
state: String,
|
||||
message: String,
|
||||
},
|
||||
LoginResult {
|
||||
ok: bool,
|
||||
state: String,
|
||||
message: String,
|
||||
},
|
||||
Error {
|
||||
message: String,
|
||||
},
|
||||
#[serde(other)]
|
||||
Other,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum Mode {
|
||||
Status,
|
||||
Invalid { username: String, password: String },
|
||||
Valid { username: String, password: String },
|
||||
}
|
||||
|
||||
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 = args.next().ok_or_else(|| String::from("missing username after --invalid"))?;
|
||||
let password = args.next().ok_or_else(|| String::from("missing password after --invalid"))?;
|
||||
if args.next().is_some() {
|
||||
return Err(String::from("unexpected extra arguments after --invalid USER PASSWORD"));
|
||||
}
|
||||
Ok(Mode::Invalid { username, password })
|
||||
}
|
||||
Some(flag) if flag == "--valid" => {
|
||||
let username = args.next().ok_or_else(|| String::from("missing username after --valid"))?;
|
||||
let password = args.next().ok_or_else(|| String::from("missing password after --valid"))?;
|
||||
if args.next().is_some() {
|
||||
return Err(String::from("unexpected extra arguments after --valid USER PASSWORD"));
|
||||
}
|
||||
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<Response, 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(Response::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 })? {
|
||||
Response::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(())
|
||||
}
|
||||
Response::Error { message } => Err(format!("greeter hello failed: {message}")),
|
||||
Response::Other => Err(String::from("unexpected greeter hello response")),
|
||||
Response::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, password })? {
|
||||
Response::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(())
|
||||
}
|
||||
}
|
||||
Response::Error { message } => Err(format!("invalid-login request failed: {message}")),
|
||||
Response::Other => Err(String::from("unexpected greeter response for invalid login")),
|
||||
Response::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, password })? {
|
||||
Response::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"));
|
||||
}
|
||||
}
|
||||
Response::Error { message } => {
|
||||
let _ = fs::remove_file(VALIDATION_REQUEST);
|
||||
return Err(format!("valid-login request failed: {message}"));
|
||||
}
|
||||
Response::Other => {
|
||||
let _ = fs::remove_file(VALIDATION_REQUEST);
|
||||
return Err(String::from("unexpected greeter response for valid login"));
|
||||
}
|
||||
Response::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'"))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,9 @@ fn run() -> Result<(), String> {
|
||||
if !stdout.contains("discovery_source=") {
|
||||
return Err("iommu self-test did not report discovery source".to_string());
|
||||
}
|
||||
if !stdout.contains("dmar_present=") {
|
||||
return Err("iommu self-test did not report DMAR presence state".to_string());
|
||||
}
|
||||
if !stdout.contains("units_initialized_now=") {
|
||||
return Err("iommu self-test did not report initialized unit count".to_string());
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
use std::fs::OpenOptions;
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
use std::path::Path;
|
||||
use std::process::{self, Command};
|
||||
|
||||
use syscall::O_NONBLOCK;
|
||||
|
||||
use redbear_hwutils::parse_args;
|
||||
|
||||
const PROGRAM: &str = "redbear-phase-ps2-check";
|
||||
@@ -8,7 +12,14 @@ const USAGE: &str =
|
||||
"Usage: redbear-phase-ps2-check\n\nRun the bounded PS/2 and serio proof check inside the guest.";
|
||||
|
||||
fn require_path(path: &str) -> Result<(), String> {
|
||||
if Path::new(path).exists() {
|
||||
if Path::new(path).exists()
|
||||
|| OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.custom_flags(O_NONBLOCK as i32)
|
||||
.open(path)
|
||||
.is_ok()
|
||||
{
|
||||
println!("present={path}");
|
||||
Ok(())
|
||||
} else {
|
||||
|
||||
+21
-1
@@ -1,4 +1,4 @@
|
||||
use std::path::Path;
|
||||
use std::{env, path::{Path, PathBuf}};
|
||||
use std::process::{self, Command};
|
||||
|
||||
use redbear_hwutils::parse_args;
|
||||
@@ -51,6 +51,25 @@ fn require_wayland_smoke_marker() -> Result<(), String> {
|
||||
Err("qt6-wayland-smoke did not leave a success marker".to_string())
|
||||
}
|
||||
|
||||
fn require_wayland_socket() -> Result<(), String> {
|
||||
let runtime_dir = env::var("XDG_RUNTIME_DIR")
|
||||
.ok()
|
||||
.filter(|value| !value.is_empty())
|
||||
.unwrap_or_else(|| "/tmp/run/user/0".to_string());
|
||||
let display = env::var("WAYLAND_DISPLAY")
|
||||
.ok()
|
||||
.filter(|value| !value.is_empty())
|
||||
.unwrap_or_else(|| "wayland-0".to_string());
|
||||
let socket = PathBuf::from(runtime_dir).join(display);
|
||||
|
||||
if socket.exists() {
|
||||
println!("{}", socket.display());
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("missing Wayland socket {}", socket.display()))
|
||||
}
|
||||
}
|
||||
|
||||
fn run() -> Result<(), String> {
|
||||
parse_args(PROGRAM, USAGE, std::env::args()).map_err(|err| {
|
||||
if err.is_empty() {
|
||||
@@ -66,6 +85,7 @@ fn run() -> Result<(), String> {
|
||||
require_path("/usr/bin/qt6-plugin-check")?;
|
||||
require_path("/usr/bin/qt6-wayland-smoke")?;
|
||||
require_path("/home/root/.wayland-session.started")?;
|
||||
require_wayland_socket()?;
|
||||
require_wayland_smoke_marker()?;
|
||||
|
||||
let status = Command::new("redbear-info")
|
||||
|
||||
Reference in New Issue
Block a user