d-bus: redbear-sessiond host-buildable + DBUS-PLAN v3.5 (bug-fix round)
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.
This commit is contained in:
@@ -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<Mutex>` 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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -191,7 +191,7 @@ async fn run_daemon() -> Result<(), Box<dyn Error>> {
|
||||
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
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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<DeviceMap>,
|
||||
device_map: Arc<Mutex<DeviceMap>>,
|
||||
runtime: SharedRuntime,
|
||||
controlled: Mutex<bool>,
|
||||
taken_devices: Mutex<HashSet<(u32, u32)>>,
|
||||
controlled: Arc<Mutex<bool>>,
|
||||
taken_devices: Arc<Mutex<HashSet<(u32, u32)>>>,
|
||||
connection: Arc<Mutex<Option<Connection>>>,
|
||||
}
|
||||
|
||||
@@ -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")]
|
||||
|
||||
Reference in New Issue
Block a user