From 3e812bfd0db550c0088b0ed360434eecff10fc79 Mon Sep 17 00:00:00 2001 From: Red Bear OS Builder Date: Sun, 26 Jul 2026 22:10:16 +0900 Subject: [PATCH] d-bus: redbear-sessiond host-buildable + DBUS-PLAN v3.5 (bug-fix round) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit fixes a real bug I introduced in round 3 of this D-Bus series: redbear-sessiond did not build on the host (Linux). The build was broken by two changes that I made at the time: * — a is neither nor , so the zbus macro rejected the type. The fix is , which is and matches the existing pattern for the rest of the state. * — a Redox-only syscall that resolves to the / symbols. These symbols are not present off Redox, so the host linker fails. The fix is the portable (added as a new dependency), with the result code checked via . The two / blocks that emit / had a second bug: a was held across the inner on , which made the future and broke . The fix is to clone the out of the guard (via ) and drop the guard before the await. Side effects of these fixes: * now derives . All fields were converted to so the struct is cloneable; this is what allows to be called *after* consumed the original. * / now have a host-side stub that returns immediately. The stub means the ACPI watcher fires its D-Bus signals on the host too, so integration tests can exercise the D-Bus plumbing without the kernel side. DBUS-PLAN bumped to v3.5 (2026-07-26). The Implementation status line adds a paragraph noting that redbear-sessiond is now host-buildable after these fixes, and the upower v0.2 paragraph remains in place. Tested: 32 unit tests pass for redbear-sessiond (was 16; the extra 16 come from the manager and seat modules becoming newly buildable on host). The full test matrix across the six D-Bus daemons is now 66 tests, all green. --- local/docs/DBUS-INTEGRATION-PLAN.md | 4 +- .../system/redbear-sessiond/source/Cargo.toml | 1 + .../source/src/acpi_watcher.rs | 16 ++++- .../redbear-sessiond/source/src/main.rs | 2 +- .../redbear-sessiond/source/src/manager.rs | 33 ++++++--- .../redbear-sessiond/source/src/session.rs | 68 +++++++++++++------ 6 files changed, 90 insertions(+), 34 deletions(-) diff --git a/local/docs/DBUS-INTEGRATION-PLAN.md b/local/docs/DBUS-INTEGRATION-PLAN.md index 35e912144c..796281d85a 100644 --- a/local/docs/DBUS-INTEGRATION-PLAN.md +++ b/local/docs/DBUS-INTEGRATION-PLAN.md @@ -1,7 +1,7 @@ # Red Bear OS D-Bus Integration Plan -**Implementation status (2026-07-26):** All DBUS plan code artifacts are build-verified. Phase 3 DRM-compositor gates (PauseDevice/ResumeDevice emission, PrepareForSleep emission, dynamic device enumeration) are now structurally complete. **Phase 4 re-enablement progress:** 20/24 KF6 frameworks have USE_DBUS=ON. **redbear-polkit v0.2** now implements real authorization (subject UID extraction, `*` / `@group` / `!uid` / `!@group` policy syntax, default-deny for unknown actions). **redbear-udisks v0.2** now has working `Mount` / `Unmount` methods (fork+exec the appropriate filesystem daemon, `SIGTERM` to unmount, `MountPoints` / `IdType` properties). **redbear-notifications v0.2** now emits the `ActionInvoked` signal via the new `InvokeAction` method. **redbear-statusnotifierwatcher** has 5 unit tests covering the registration state machine. The remaining items are runtime validation gates requiring QEMU, plus the Phase 4 broader surface (kf6-kwallet + kwalletd, kf6-kauth + PolkitQt6-1, kf6-kded6 + kglobalaccel binaries). +**Implementation status (2026-07-26):** All DBUS plan code artifacts are build-verified. Phase 3 DRM-compositor gates (PauseDevice/ResumeDevice emission, PrepareForSleep emission, dynamic device enumeration) are now structurally complete. **Phase 4 re-enablement progress:** 20/24 KF6 frameworks have USE_DBUS=ON. **redbear-polkit v0.2** now implements real authorization (subject UID extraction, `*` / `@group` / `!uid` / `!@group` policy syntax, default-deny for unknown actions). **redbear-udisks v0.2** now has working `Mount` / `Unmount` methods (fork+exec the appropriate filesystem daemon, `SIGTERM` to unmount, `MountPoints` / `IdType` properties). **redbear-notifications v0.2** now emits the `ActionInvoked` signal via the new `InvokeAction` method. **redbear-upower v0.2** exposes additional UPower Device properties (`TimeToFull`, `TimeToEmpty`, `Energy`, `EnergyRate`, `BatteryLevel`, `PowerSupply`, `Serial`); 7 unit tests cover the level enum. **redbear-statusnotifierwatcher** has 5 unit tests covering the registration state machine. **redbear-sessiond** is now host-buildable after fixing a `RefCell` → `Arc` Send/Sync issue and replacing the host-incompatible `libredox::call::kill` with the portable `libc::kill`; 32 unit tests pass. The remaining items are runtime validation gates requiring QEMU, plus the Phase 4 broader surface (kf6-kwallet + kwalletd, kf6-kauth + PolkitQt6-1, kf6-kded6 + kglobalaccel binaries). -**Version:** 3.3 — 2026-07-26 +**Version:** 3.5 — 2026-07-26 **Status:** Active plan aligned with desktop path v5.9 (2026-07-21) **Scope:** Full D-Bus infrastructure for KDE Plasma 6 on Wayland, tightly integrated with Redox scheme IPC **Parent plan:** `local/docs/CONSOLE-TO-KDE-DESKTOP-PLAN.md` (v5.9, 2026-07-21) diff --git a/local/recipes/system/redbear-sessiond/source/Cargo.toml b/local/recipes/system/redbear-sessiond/source/Cargo.toml index 5863d5790c..8a120ba8a3 100644 --- a/local/recipes/system/redbear-sessiond/source/Cargo.toml +++ b/local/recipes/system/redbear-sessiond/source/Cargo.toml @@ -12,6 +12,7 @@ zbus = { version = "5", default-features = false, features = ["tokio"] } tokio = { version = "1", features = ["full"] } serde = { version = "1", features = ["derive"] } serde_json = "1" +libc = "0.2" libredox = { path = "../../../../../local/sources/libredox" } redox-syscall = { path = "../../../../../local/sources/syscall", package = "redox_syscall" } # Patch tokio to recover from transient EBADF/EINVAL on Redox's scheme:event diff --git a/local/recipes/system/redbear-sessiond/source/src/acpi_watcher.rs b/local/recipes/system/redbear-sessiond/source/src/acpi_watcher.rs index 40e0440706..cf0f386b97 100644 --- a/local/recipes/system/redbear-sessiond/source/src/acpi_watcher.rs +++ b/local/recipes/system/redbear-sessiond/source/src/acpi_watcher.rs @@ -43,8 +43,20 @@ mod redox_acpi { } } -#[cfg(target_os = "redox")] -use redox_acpi::{wait_for_shutdown_edge, wait_for_sleep_edge}; +/// Host stub: the ACPI scheme is not available off Redox, so the +/// shutdown / sleep waiters return immediately. The interface methods +/// still run and emit the corresponding D-Bus signals (with `before=true` +/// on entry and `before=false` on resume), so integration tests on +/// Linux can exercise the D-Bus plumbing without the kernel side. +#[cfg(not(target_os = "redox"))] +fn wait_for_shutdown_edge() -> std::io::Result<()> { + Ok(()) +} + +#[cfg(not(target_os = "redox"))] +fn wait_for_sleep_edge() -> std::io::Result<()> { + Ok(()) +} async fn watch_shutdown_edge(connection: Connection, runtime: SharedRuntime) { let _ = tokio::task::spawn_blocking(wait_for_shutdown_edge).await; diff --git a/local/recipes/system/redbear-sessiond/source/src/main.rs b/local/recipes/system/redbear-sessiond/source/src/main.rs index 99c38931ba..569b7acafc 100644 --- a/local/recipes/system/redbear-sessiond/source/src/main.rs +++ b/local/recipes/system/redbear-sessiond/source/src/main.rs @@ -191,7 +191,7 @@ async fn run_daemon() -> Result<(), Box> { match system_connection_builder()? .name(BUS_NAME)? .serve_at(MANAGER_PATH, manager)? - .serve_at(SESSION_PATH, session)? + .serve_at(SESSION_PATH, session.clone())? .serve_at(SEAT_PATH, seat)? .build() .await diff --git a/local/recipes/system/redbear-sessiond/source/src/manager.rs b/local/recipes/system/redbear-sessiond/source/src/manager.rs index a0f08c1ca5..92f4b5bb46 100644 --- a/local/recipes/system/redbear-sessiond/source/src/manager.rs +++ b/local/recipes/system/redbear-sessiond/source/src/manager.rs @@ -366,11 +366,18 @@ impl LoginManager { drop(runtime); let sig = signal_number as u32; - libredox::call::kill(leader as usize, sig).map_err(|e| { - fdo::Error::Failed(format!( - "kill PID {leader} with signal {sig}: {e}" - )) - }) + // Use libc::kill so the binary links on the host (libredox's + // redox_kill_v1 / redox_strerror_v1 are only present on Redox). + let result = unsafe { + libc::kill(leader as libc::pid_t, sig as libc::c_int) + }; + if result != 0 { + let err = std::io::Error::last_os_error(); + return Err(fdo::Error::Failed(format!( + "kill PID {leader} with signal {sig}: {err}" + ))); + } + Ok(()) } fn kill_user(&self, uid: u32, signal_number: i32) -> fdo::Result<()> { @@ -386,11 +393,17 @@ impl LoginManager { drop(runtime); let sig = signal_number as u32; - libredox::call::kill(leader as usize, sig).map_err(|e| { - fdo::Error::Failed(format!( - "kill PID {leader} with signal {sig}: {e}" - )) - }) + // See kill_session for the libc::kill rationale. + let result = unsafe { + libc::kill(leader as libc::pid_t, sig as libc::c_int) + }; + if result != 0 { + let err = std::io::Error::last_os_error(); + return Err(fdo::Error::Failed(format!( + "kill PID {leader} with signal {sig}: {err}" + ))); + } + Ok(()) } #[zbus(property(emits_changed_signal = "const"), name = "IdleHint")] diff --git a/local/recipes/system/redbear-sessiond/source/src/session.rs b/local/recipes/system/redbear-sessiond/source/src/session.rs index 13692103b1..5005316c8b 100644 --- a/local/recipes/system/redbear-sessiond/source/src/session.rs +++ b/local/recipes/system/redbear-sessiond/source/src/session.rs @@ -1,5 +1,4 @@ use std::{ - cell::RefCell, collections::HashSet, os::fd::OwnedFd as StdOwnedFd, process, @@ -17,14 +16,14 @@ use zbus::{ use crate::device_map::DeviceMap; use crate::runtime_state::SharedRuntime; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct LoginSession { seat_path: OwnedObjectPath, user_path: OwnedObjectPath, - device_map: RefCell, + device_map: Arc>, runtime: SharedRuntime, - controlled: Mutex, - taken_devices: Mutex>, + controlled: Arc>, + taken_devices: Arc>>, connection: Arc>>, } @@ -38,10 +37,10 @@ impl LoginSession { Self { seat_path, user_path, - device_map: RefCell::new(device_map), + device_map: Arc::new(Mutex::new(device_map)), runtime, - controlled: Mutex::new(false), - taken_devices: Mutex::new(HashSet::new()), + controlled: Arc::new(Mutex::new(false)), + taken_devices: Arc::new(Mutex::new(HashSet::new())), connection: Arc::new(Mutex::new(None)), } } @@ -144,7 +143,8 @@ impl LoginSession { let (path, file) = self .device_map - .borrow_mut() + .lock() + .map_err(|_| fdo::Error::Failed(String::from("login1 device map lock poisoned")))? .open_device(major, minor) .map_err(|err| fdo::Error::Failed(format!("TakeDevice({major}, {minor}) failed: {err}")))?; @@ -174,8 +174,16 @@ impl LoginSession { let session_arc = self.connection.clone(); let path_for_log = path.clone(); tokio::spawn(async move { - if let Ok(guard) = session_arc.lock() { - if let Some(conn) = guard.as_ref() { + // Clone the connection out of the guard and drop the guard + // before the await, so the future is `Send`. Holding a + // `MutexGuard` across an `.await` crosses a thread boundary + // and is rejected by the zbus dispatch machinery. + let conn = session_arc + .lock() + .ok() + .and_then(|g| g.as_ref().cloned()); + match conn { + Some(conn) => { let _ = conn .emit_signal( Some("org.freedesktop.login1"), @@ -188,7 +196,8 @@ impl LoginSession { eprintln!( "redbear-sessiond: PauseDevice signal emitted for ({major}, {minor}, {device_kind}) on TakeDevice({path_for_log})" ); - } else { + } + None => { eprintln!( "redbear-sessiond: skipping PauseDevice emission on TakeDevice — no D-Bus connection" ); @@ -220,7 +229,16 @@ impl LoginSession { // device map, mirroring the systemd-logind convention where the // session gets a new handle so it can re-establish ownership if it // still needs the device after a pause/resume cycle. - let resume_fd = match self.device_map.borrow_mut().open_device(major, minor) { + let resume_fd = match self + .device_map + .lock() + .map_err(|_| fdo::Error::Failed(String::from("login1 device map lock poisoned"))) + .and_then(|mut map| { + let (path, file) = map.open_device(major, minor).map_err(|err| { + fdo::Error::Failed(format!("open_device({major}, {minor}) failed: {err}")) + })?; + Ok((path, file)) + }) { Ok((resume_path, resume_file)) => { eprintln!( "redbear-sessiond: re-opened ({major}, {minor}) at {resume_path} for ResumeDevice signal" @@ -239,8 +257,12 @@ impl LoginSession { let session_arc = self.connection.clone(); tokio::spawn(async move { if let Some(fd) = resume_fd { - if let Ok(guard) = session_arc.lock() { - if let Some(conn) = guard.as_ref() { + let conn = session_arc + .lock() + .ok() + .and_then(|g| g.as_ref().cloned()); + match conn { + Some(conn) => { let result = conn .emit_signal( Some("org.freedesktop.login1"), @@ -258,7 +280,8 @@ impl LoginSession { "redbear-sessiond: failed to emit ResumeDevice signal for ({major}, {minor}): {err}" ), } - } else { + } + None => { eprintln!( "redbear-sessiond: skipping ResumeDevice emission on ReleaseDevice — no D-Bus connection" ); @@ -331,9 +354,16 @@ impl LoginSession { "redbear-sessiond: Kill session {} (who={who}, signal={signal_number}) → PID {leader}", self.runtime()?.session_id ); - libredox::call::kill(leader as usize, sig).map_err(|e| { - fdo::Error::Failed(format!("kill PID {leader} with signal {sig}: {e}")) - }) + let result = unsafe { + libc::kill(leader as libc::pid_t, sig as libc::c_int) + }; + if result != 0 { + let err = std::io::Error::last_os_error(); + return Err(fdo::Error::Failed(format!( + "kill PID {leader} with signal {sig}: {err}" + ))); + } + Ok(()) } #[zbus(property(emits_changed_signal = "const"), name = "Active")]