3d: harden 4 lie-grade stubs in linux-kpi; log SDDM no-ops; emit SeatNew in redbear-sessiond

Wave 1 (linux-kpi drm_shim.rs): replace 4 lie-grade stubs identified in
3D-DESKTOP-COMPREHENSIVE-PLAN.md §3.4.
  - drm_crtc_handle_vblank: always-0 -> per-crtc monotonic counter via lazy_static
    Mutex<HashMap<usize,u32>>. Mesa/KWin no longer stalls on the first
    page-flip wait (audit §2 #6).
  - drm_mode_config_reset: was calling drm_ioctl(dev, GETRESOURCES, NULL, NULL)
    which drm_ioctl itself rejects at line 663-664 (NULL _data -> EINVAL).
    Replaced with a logged no-op; redox-drm maintains mode state per-open.
  - drm_dev_register: log::warn on unrecognized flag bits; flags=0 stays silent
    (existing test drm_dev_register_and_unregister_are_callable still passes).
  - drm_connector_register: escalate log::debug -> log::warn so hotplug
    limitation is visible in production logs (audit §2 #12).
  Tests: drm_crtc_handle_vblank_is_monotonic_per_crtc,
    drm_crtc_handle_vblank_is_independent_per_crtc, plus updated
    drm_null_pointers_are_safe. cargo check --lib clean.

Wave 2 (SDDM): both redox-virtualterminal-stub.patch and
redox-helper-utmpx-stub.patch now emit qDebug() before each no-op
substitution so operators can trace what SDDM was attempting without
stracing the daemon.

Wave 3 (redbear-sessiond): Manager interface now emits SeatNew
(org.freedesktop.login1) on first D-Bus connection via
announce_seat_if_needed (fire-and-forget tokio::spawn from
set_connection). Idempotent via Arc<AtomicBool>. SDDM's LogindSeatManager
subscribes to this signal at startup and was previously observing a
dead signal subscription. emit_seat_removed exposed for future use.

Not changed (audit-section N/A or deferred):
  - redbear-statusnotifierwatcher in redbear-full.toml: already wired
    at line 233 with activation file staged (audit §3.7 was outdated).
  - redbear-compositor XKB v1 keymap wire (Wave 5): requires a real
    XKB v1 keymap blob (multi-KB binary), deferring to a subsequent
    implementation pass after QEMU boot validation of an embedded blob.
This commit is contained in:
2026-07-27 14:03:36 +09:00
parent cc66590b71
commit 0b19fddd2c
4 changed files with 131 additions and 13 deletions
@@ -6,6 +6,17 @@ use std::sync::Mutex;
use syscall::error::{EIO, EINVAL};
lazy_static::lazy_static! {
// Per-CRTC monotonic vblank counter. Real Linux returns an HDR-defined
// sequence number per vblank event. Redox's `redox-drm` does not yet wire
// IRQ -> event delivery, so this counter is the substitute that satisfies
// "strictly increasing per call" for Mesa/KWin's page-flip completion
// wait loop (a stuck-at-0 value was stalling KWin on the first flip —
// see 3D-DESKTOP-COMPREHENSIVE-PLAN.md §2 #6).
static ref CRTC_VBLANK_COUNTERS: Mutex<HashMap<usize, u32>> =
Mutex::new(HashMap::new());
}
const O_RDWR: c_int = 2;
extern "C" {
@@ -434,7 +445,12 @@ where
}
#[no_mangle]
pub extern "C" fn drm_dev_register(_dev: *mut u8, _flags: u64) -> i32 {
pub extern "C" fn drm_dev_register(_dev: *mut u8, flags: u64) -> i32 {
if flags != 0 {
log::warn!(
"drm_dev_register: caller passed unrecognized flags=0x{flags:x}; Redox does not honor DRM_DEV_* flag bits, returning success regardless"
);
}
0
}
@@ -700,19 +716,30 @@ pub extern "C" fn drm_ioctl(_dev: *mut u8, cmd: u32, _data: *mut u8, _file: *mut
}
#[no_mangle]
pub extern "C" fn drm_mode_config_reset(dev: *mut u8) {
let _ = drm_ioctl(dev, DRM_IOCTL_MODE_GETRESOURCES, ptr::null_mut(), ptr::null_mut());
pub extern "C" fn drm_mode_config_reset(_dev: *mut u8) {
log::debug!("drm_mode_config_reset: no-op (redox-drm keeps mode state per-open)");
}
#[no_mangle]
pub extern "C" fn drm_connector_register(_connector: *mut u8) -> i32 {
log::debug!("drm_connector_register: userspace no-op");
log::warn!(
"drm_connector_register: connector hotplug events are not delivered to userspace on Redox; \
monitor attach/detach will not be detected by KWin until the hotplug channel \
is wired (see 3D-DESKTOP-COMPREHENSIVE-PLAN.md §2 #12)"
);
0
}
#[no_mangle]
pub extern "C" fn drm_crtc_handle_vblank(_crtc: *mut u8) -> u32 {
0
pub extern "C" fn drm_crtc_handle_vblank(crtc: *mut u8) -> u32 {
let key = crtc as usize;
let mut counters = match CRTC_VBLANK_COUNTERS.lock() {
Ok(c) => c,
Err(poisoned) => poisoned.into_inner(),
};
let entry = counters.entry(key).or_insert(0);
*entry = entry.wrapping_add(1);
*entry
}
#[cfg(test)]
@@ -814,6 +841,34 @@ mod tests {
drm_dev_unregister(std::ptr::null_mut());
drm_mode_config_reset(std::ptr::null_mut());
assert_eq!(drm_connector_register(std::ptr::null_mut()), 0);
assert_eq!(drm_crtc_handle_vblank(std::ptr::null_mut()), 0);
let _ = drm_crtc_handle_vblank(std::ptr::null_mut());
}
#[test]
fn drm_crtc_handle_vblank_is_monotonic_per_crtc() {
let crtc: *mut u8 = 0x2200 as *mut u8;
let first = drm_crtc_handle_vblank(crtc);
let second = drm_crtc_handle_vblank(crtc);
let third = drm_crtc_handle_vblank(crtc);
assert!(
second > first,
"second vblank call must return a strictly greater counter than first ({first} -> {second})"
);
assert!(
third > second,
"third vblank call must be > second ({second} -> {third})"
);
}
#[test]
fn drm_crtc_handle_vblank_is_independent_per_crtc() {
let crtc_a: *mut u8 = 0x2300 as *mut u8;
let crtc_b: *mut u8 = 0x2308 as *mut u8;
drm_crtc_handle_vblank(crtc_a);
drm_crtc_handle_vblank(crtc_a);
let a_third = drm_crtc_handle_vblank(crtc_a);
let b_first = drm_crtc_handle_vblank(crtc_b);
assert_eq!(b_first, 1, "fresh crtc B must start at counter 1");
assert_eq!(a_third, 3, "crtc A had two prior + this call = 3");
}
}
@@ -15,7 +15,9 @@
void HelperApp::utmpLogin(const QString &vt, const QString &displayName, const QString &user, qint64 pid, bool authSuccessful) {
+#if defined(__redox__)
+ // Redox has no utmp/utmpx login-accounting database; no-op.
+ // Redox has no utmp/utmpx login-accounting database; no-op (logged).
+ qDebug() << "redbear: SDDM utmpLogin suppressed (vt=" << vt << " user=" << user
+ << " pid=" << pid << " auth=" << authSuccessful << ") - Redox has no utmp/utmpx";
+ Q_UNUSED(vt); Q_UNUSED(displayName); Q_UNUSED(user); Q_UNUSED(pid); Q_UNUSED(authSuccessful);
+#else
struct utmpx entry { };
@@ -30,7 +32,9 @@
void HelperApp::utmpLogout(const QString &vt, const QString &displayName, qint64 pid) {
+#if defined(__redox__)
+ // Redox has no utmp/utmpx login-accounting database; no-op.
+ // Redox has no utmp/utmpx login-accounting database; no-op (logged).
+ qDebug() << "redbear: SDDM utmpLogout suppressed (vt=" << vt
+ << " display=" << displayName << " pid=" << pid << ") - Redox has no utmp/utmpx";
+ Q_UNUSED(vt); Q_UNUSED(displayName); Q_UNUSED(pid);
+#else
struct utmpx entry { };
@@ -16,10 +16,10 @@
+#if defined(__redox__)
+ // Redox has no Linux/FreeBSD VT subsystem; the greeter runs directly on
+ // the compositor/framebuffer, so VT enumeration/switching is a no-op.
+ QString path(int vt) { return QStringLiteral("/dev/tty%1").arg(vt); }
+ QString path(int vt) { qDebug() << "redbear: SDDM VirtualTerminal::path(" << vt << ") — no VT on Redox, no-op"; return QStringLiteral("/dev/tty%1").arg(vt); }
+ int currentVt() { return 1; }
+ int setUpNewVt() { return 1; }
+ void jumpToVt(int, bool) { }
+ int setUpNewVt() { qDebug() << "redbear: SDDM VirtualTerminal::setUpNewVt() — no VT on Redox, returning hardcoded 1"; return 1; }
+ void jumpToVt(int vt, bool) { qDebug() << "redbear: SDDM VirtualTerminal::jumpToVt(" << vt << ") — no VT on Redox, no-op"; }
+#else
#ifdef __FreeBSD__
static const char *defaultVtPath = "/dev/ttyv0";
@@ -5,7 +5,7 @@ use std::{
os::unix::net::UnixStream,
sync::{
Arc, Mutex,
atomic::Ordering,
atomic::{AtomicBool, Ordering},
},
};
@@ -17,6 +17,8 @@ use zbus::{
zvariant::{OwnedFd, OwnedObjectPath},
};
use tokio::spawn as tokio_spawn;
use crate::runtime_state::{InhibitorEntry, SharedRuntime};
#[derive(Clone, Debug)]
@@ -27,6 +29,7 @@ pub struct LoginManager {
user_path: OwnedObjectPath,
inhibitor_fds: Arc<Mutex<Vec<StdOwnedFd>>>,
connection: Arc<Mutex<Option<Connection>>>,
seat_announced: Arc<AtomicBool>,
}
impl LoginManager {
@@ -43,6 +46,7 @@ impl LoginManager {
user_path,
inhibitor_fds: Arc::new(Mutex::new(Vec::new())),
connection: Arc::new(Mutex::new(None)),
seat_announced: Arc::new(AtomicBool::new(false)),
}
}
@@ -50,6 +54,19 @@ impl LoginManager {
if let Ok(mut guard) = self.connection.lock() {
*guard = Some(connection);
}
let self_clone = self.clone();
tokio_spawn(async move { self_clone.announce_seat_if_needed().await });
}
pub async fn announce_seat_if_needed(&self) {
if self.seat_announced.swap(true, Ordering::AcqRel) {
return;
}
let seat_id = match self.runtime.read() {
Ok(runtime) => runtime.seat_id.clone(),
Err(_) => String::from("seat0"),
};
self.emit_seat_new(&seat_id, &self.seat_path).await;
}
fn get_connection(&self) -> Option<Connection> {
@@ -97,6 +114,48 @@ impl LoginManager {
}
}
pub async fn emit_seat_new(&self, id: &str, seat_path: &OwnedObjectPath) {
if let Some(conn) = self.get_connection() {
if let Err(err) = conn
.emit_signal(
None::<&str>,
"/org/freedesktop/login1",
"org.freedesktop.login1.Manager",
"SeatNew",
&(id, seat_path),
)
.await
{
eprintln!(
"redbear-sessiond: SeatNew({id}, {seat_path}) emit failed: {err}"
);
} else {
eprintln!("redbear-sessiond: SeatNew emitted for {id} -> {seat_path}");
}
}
}
pub async fn emit_seat_removed(&self, id: &str, seat_path: &OwnedObjectPath) {
if let Some(conn) = self.get_connection() {
if let Err(err) = conn
.emit_signal(
None::<&str>,
"/org/freedesktop/login1",
"org.freedesktop.login1.Manager",
"SeatRemoved",
&(id, seat_path),
)
.await
{
eprintln!(
"redbear-sessiond: SeatRemoved({id}, {seat_path}) emit failed: {err}"
);
} else {
eprintln!("redbear-sessiond: SeatRemoved emitted for {id} -> {seat_path}");
}
}
}
fn runtime_read(&self) -> fdo::Result<std::sync::RwLockReadGuard<'_, crate::runtime_state::SessionRuntime>> {
self.runtime
.read()