redbear-*: fix logging-refactor regressions + Redox-correct daemon shutdown

The redbear-* logging-unify refactor (println/eprintln -> log::*) left three
mechanical corruptions, caught by --check-sweep:
  1. log::MACRO!();  (empty call + spurious ;, args dangling) -> log::MACRO!(
  2. log::MACRO!(<args>));  (one extra trailing paren) -> log::MACRO!(<args>);
  3. use log::{..};  inserted INSIDE a 'use std::{' block -> moved out
Fixed across redbear-greeter/notifications/session-launch/statusnotifierwatcher/
upower (string-aware paren-balance sweep; balanced nested-paren calls untouched).

Separately, the 'tokio minimal' refactor left redbear-notifications and
redbear-statusnotifierwatcher using tokio::signal::ctrl_c/unix without the
tokio 'signal' feature (compile break). Per redbear-upower's documented finding,
tokio::signal::unix page-faults on Redox (relibc lacks the signal-handler
registration path) and Redox manages daemon lifecycle via the init system, not
POSIX signals. So the correct fix is NOT to re-add the feature but to drop the
signal-based shutdown entirely — the daemon holds its shutdown channel open and
runs until init stops it. Applied to all four signal-using daemons
(notifications, statusnotifierwatcher, udisks, polkit) and removed the now-unused
tokio 'signal' feature from udisks/polkit for consistency with upower.
This commit is contained in:
2026-07-31 10:14:33 +03:00
parent 22dceb4e8a
commit cd429561b2
9 changed files with 92 additions and 129 deletions
@@ -1,5 +1,4 @@
use std::{
use log::{error, info, warn};
env,
fs,
io::{self, BufRead, BufReader, Write},
@@ -9,6 +8,7 @@ use log::{error, info, warn};
thread,
time::{Duration, Instant},
};
use log::{error, info, warn};
use redbear_login_protocol::{AuthRequest, AuthResponse, GreeterRequest, GreeterResponse};
@@ -278,7 +278,7 @@ impl GreeterDaemon {
if attempt == 0 {
return Ok(());
}
log::error!();
log::error!(
"redbear-greeterd: wayland socket appeared after {}ms (primary={}, fallback={})",
attempt * 250,
socket_path.display(),
@@ -287,7 +287,7 @@ impl GreeterDaemon {
return Ok(());
}
if attempt % 20 == 0 && attempt > 0 {
log::error!();
log::error!(
"redbear-greeterd: still waiting for compositor socket {} ({}s elapsed)",
socket_path.display(),
attempt / 4
@@ -304,21 +304,21 @@ impl GreeterDaemon {
fn start_surface(&mut self) -> Result<(), String> {
self.set_state(GreeterState::Starting, "Starting greeter surface");
log::info!("redbear-greeterd: starting compositor ({})...", COMPOSITOR_BIN_PATH));
log::info!("redbear-greeterd: starting compositor ({})...", COMPOSITOR_BIN_PATH);
let compositor_path = if Path::new(COMPOSITOR_BIN_PATH).is_file() {
COMPOSITOR_BIN_PATH
} else {
COMPOSITOR_SHARE_PATH
};
self.compositor = Some(self.spawn_as_greeter(compositor_path)?);
log::info!("redbear-greeterd: waiting for Wayland socket..."));
log::info!("redbear-greeterd: waiting for Wayland socket...");
self.wait_for_wayland_socket()?;
log::info!("redbear-greeterd: compositor ready, launching greeter UI..."));
log::info!("redbear-greeterd: compositor ready, launching greeter UI...");
self.ui = Some(self.spawn_as_greeter("/usr/bin/redbear-greeter-ui")?);
log::info!("redbear-greeterd: greeter UI launched, activating VT {}", self.vt));
log::info!("redbear-greeterd: greeter UI launched, activating VT {}", self.vt);
self.activate_vt(self.vt)?;
self.set_state(GreeterState::GreeterReady, "Ready");
log::info!("redbear-greeterd: greeter ready on VT {}", self.vt));
log::info!("redbear-greeterd: greeter ready on VT {}", self.vt);
Ok(())
}
@@ -515,7 +515,7 @@ impl GreeterDaemon {
match self.listener.accept() {
Ok((stream, _)) => {
if let Err(err) = self.handle_connection(stream) {
log::error!("redbear-greeterd: {err}"));
log::error!("redbear-greeterd: {err}");
}
}
Err(err) if err.kind() == io::ErrorKind::WouldBlock => {
@@ -531,7 +531,7 @@ fn run() -> Result<(), String> {
match parse_args() {
Ok(()) => {}
Err(err) if err.is_empty() => {
log::info!("{}", usage()));
log::info!("{}", usage());
return Ok(());
}
Err(err) => return Err(err),
@@ -544,8 +544,8 @@ fn run() -> Result<(), String> {
fn main() {
let _ = env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).try_init();
if let Err(err) = run() {
log::error!("redbear-greeterd: {err}"));
log::error!("{}", usage()));
log::error!("redbear-greeterd: {err}");
log::error!("{}", usage());
process::exit(1);
}
}
@@ -1,5 +1,5 @@
use std::{
use log::{error, info, warn};
use std::{
collections::{HashMap, HashSet, VecDeque},
env,
error::Error,
@@ -87,7 +87,7 @@ impl Notifications {
let proxy = match DBusProxy::new(&connection).await {
Ok(proxy) => proxy,
Err(err) => {
log::error!("redbear-notifications: failed to create DBus proxy: {err}"));
log::error!("redbear-notifications: failed to create DBus proxy: {err}");
return;
}
};
@@ -111,7 +111,7 @@ impl Notifications {
vanished.insert(sender);
}
Err(err) => {
log::error!();
log::error!(
"redbear-notifications: name_has_owner check failed: {err}"
);
}
@@ -158,7 +158,7 @@ impl Notifications {
Instant::now(),
);
log::error!();
log::error!(
"notification {}: app_name={app_name:?} summary={summary:?} body_len={} owner={owner} replaced={:?}",
result.id,
body.chars().count(),
@@ -175,7 +175,7 @@ impl Notifications {
id: u32,
) {
if self.remove_notification(id, NOTIFICATION_CLOSED_REASON) {
log::error!("notification: closed {id}"));
log::error!("notification: closed {id}");
let _ =
Self::notification_closed(&signal_emitter, id, NOTIFICATION_CLOSED_REASON).await;
}
@@ -212,7 +212,7 @@ impl Notifications {
self.validate_invoke(id, &sender, action_key)
.map_err(fdo::Error::Failed)?;
log::error!("redbear-notifications: invoke action {id:?} -> {action_key:?}"));
log::error!("redbear-notifications: invoke action {id:?} -> {action_key:?}");
let _ = Self::action_invoked(&signal_emitter, id, action_key).await;
let _ = self.complete_invocation(id, action_key);
Ok(())
@@ -530,24 +530,16 @@ fn parse_args() -> Result<Command, String> {
}
fn spawn_signal_handler(shutdown_tx: tokio::sync::watch::Sender<bool>) {
// Redox manages daemon lifecycle through the init system, not POSIX signals.
// tokio::signal::unix page-faults on Redox — relibc does not implement the
// signal-handler registration path the `signal` feature relies on (see the
// note in redbear-upower's Cargo.toml). So there is no signal-driven
// shutdown: keep the watch sender alive (so run_daemon's shutdown_rx stays
// open rather than seeing a closed channel) and never fire it — the daemon
// runs until init stops the process.
tokio::spawn(async move {
#[cfg(unix)]
{
use tokio::signal::unix::{SignalKind, signal};
if let Ok(mut sigterm) = signal(SignalKind::terminate()) {
tokio::select! {
_ = sigterm.recv() => {},
_ = tokio::signal::ctrl_c() => {},
}
} else {
let _ = tokio::signal::ctrl_c().await;
}
}
#[cfg(not(unix))]
{
let _ = tokio::signal::ctrl_c().await;
}
let _ = shutdown_tx.send(true);
let _keep_open = shutdown_tx;
std::future::pending::<()>().await;
});
}
@@ -565,10 +557,10 @@ async fn run_daemon() -> Result<(), Box<dyn Error>> {
notifications.spawn_expiry_sweeper(connection.clone());
notifications.spawn_sender_reaper(connection.clone());
log::info!("redbear-notifications: registered {BUS_NAME} on the session bus"));
log::info!("redbear-notifications: registered {BUS_NAME} on the session bus");
let _ = shutdown_rx.changed().await;
log::info!("redbear-notifications: shutdown signal received, exiting cleanly"));
log::info!("redbear-notifications: shutdown signal received, exiting cleanly");
Ok(())
}
@@ -577,25 +569,25 @@ fn main() {
let _ = env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).try_init();
match parse_args() {
Ok(Command::Help) => {
log::info!("{}", usage()));
log::info!("{}", usage());
}
Ok(Command::Run) => {
let runtime = match RuntimeBuilder::new_multi_thread().enable_all().build() {
Ok(runtime) => runtime,
Err(err) => {
log::error!("redbear-notifications: failed to create tokio runtime: {err}"));
log::error!("redbear-notifications: failed to create tokio runtime: {err}");
process::exit(1);
}
};
if let Err(err) = runtime.block_on(run_daemon()) {
log::error!("redbear-notifications: fatal error: {err}"));
log::error!("redbear-notifications: fatal error: {err}");
process::exit(1);
}
}
Err(err) => {
log::error!("redbear-notifications: {err}"));
log::error!("{}", usage()));
log::error!("redbear-notifications: {err}");
log::error!("{}", usage());
process::exit(1);
}
}
@@ -14,5 +14,5 @@ path = "src/main.rs"
log = { version = "0.4", features = ["std"] }
env_logger = "0.11"
zbus = { version = "5", default-features = false, features = ["tokio"] }
tokio = { version = "1", default-features = false, features = ["rt", "rt-multi-thread", "macros", "net", "time", "sync", "signal"] }
tokio = { version = "1", default-features = false, features = ["rt", "rt-multi-thread", "macros", "net", "time", "sync"] }
serde = { version = "1", features = ["derive"] }
@@ -299,24 +299,15 @@ fn system_connection_builder() -> Result<ConnectionBuilder<'static>, Box<dyn Err
}
fn spawn_signal_handler(shutdown_tx: tokio::sync::watch::Sender<bool>) {
// Redox manages daemon lifecycle through the init system, not POSIX signals.
// tokio::signal::unix page-faults on Redox — relibc does not implement the
// signal-handler registration path the `signal` feature relies on (see the
// note in redbear-upower's Cargo.toml). No signal-driven shutdown: hold the
// watch sender open and never fire it, so the daemon runs until init stops
// the process.
tokio::spawn(async move {
#[cfg(unix)]
{
use tokio::signal::unix::{SignalKind, signal};
if let Ok(mut sigterm) = signal(SignalKind::terminate()) {
tokio::select! {
_ = sigterm.recv() => {},
_ = tokio::signal::ctrl_c() => {},
}
} else {
let _ = tokio::signal::ctrl_c().await;
}
}
#[cfg(not(unix))]
{
let _ = tokio::signal::ctrl_c().await;
}
let _ = shutdown_tx.send(true);
let _keep_open = shutdown_tx;
std::future::pending::<()>().await;
});
}
@@ -1,5 +1,5 @@
use std::{
use log::{error, info, warn};
use std::{
collections::{BTreeMap, HashMap},
env,
ffi::CString,
@@ -402,8 +402,8 @@ fn run() -> Result<(), String> {
fn main() {
let _ = env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).try_init();
if let Err(err) = run() {
log::error!("redbear-session-launch: {err}"));
log::error!("{}", usage()));
log::error!("redbear-session-launch: {err}");
log::error!("{}", usage());
process::exit(1);
}
}
@@ -3,8 +3,8 @@ use std::future::poll_fn;
use std::pin::pin;
use std::sync::{Arc, Mutex};
use zbus::{
use log::{error, info, warn};
use zbus::{
connection::Builder as ConnectionBuilder, export::futures_core::Stream, fdo, interface,
message::Header, object_server::SignalEmitter, proxy, zvariant::ObjectPath,
};
@@ -369,7 +369,7 @@ impl StatusNotifierWatcher {
item: &str,
) -> fdo::Result<()> {
if let Err(msg) = validate_input(item) {
log::error!("statusnotifierwatcher: rejected item registration: {msg}"));
log::error!("statusnotifierwatcher: rejected item registration: {msg}");
return Err(fdo::Error::InvalidArgs(msg));
}
let owner = hdr
@@ -381,7 +381,7 @@ impl StatusNotifierWatcher {
let is_new = self.register_item(&owner, item);
if is_new {
log::info!("statusnotifierwatcher: item registered: {item} (owner: {owner})"));
log::info!("statusnotifierwatcher: item registered: {item} (owner: {owner})");
let _ = Self::status_notifier_item_registered(&signal_emitter, item).await;
}
Ok(())
@@ -406,7 +406,7 @@ impl StatusNotifierWatcher {
let was_present = self.unregister_item(&owner, item);
if was_present {
log::error!("statusnotifierwatcher: item unregistered: {item} (owner: {owner})"));
log::error!("statusnotifierwatcher: item unregistered: {item} (owner: {owner})");
let _ = Self::status_notifier_item_unregistered(&signal_emitter, item).await;
}
Ok(())
@@ -420,7 +420,7 @@ impl StatusNotifierWatcher {
host: &str,
) -> fdo::Result<()> {
if let Err(msg) = validate_input(host) {
log::error!("statusnotifierwatcher: rejected host registration: {msg}"));
log::error!("statusnotifierwatcher: rejected host registration: {msg}");
return Err(fdo::Error::InvalidArgs(msg));
}
let owner = hdr
@@ -432,7 +432,7 @@ impl StatusNotifierWatcher {
let is_new = self.register_host(&owner, host);
if is_new {
log::info!("statusnotifierwatcher: host registered: {host} (owner: {owner})"));
log::info!("statusnotifierwatcher: host registered: {host} (owner: {owner})");
let _ = Self::status_notifier_host_registered(&signal_emitter).await;
}
Ok(())
@@ -457,7 +457,7 @@ impl StatusNotifierWatcher {
let was_present = self.unregister_host(&owner, host);
if was_present {
log::error!("statusnotifierwatcher: host unregistered: {host} (owner: {owner})"));
log::error!("statusnotifierwatcher: host unregistered: {host} (owner: {owner})");
}
Ok(())
}
@@ -535,32 +535,32 @@ async fn run_name_owner_changed_listener(
let proxy = match DBusDaemonProxy::new(&connection).await {
Ok(p) => p,
Err(e) => {
log::error!("statusnotifierwatcher: failed to create DBus proxy: {e}"));
log::error!("statusnotifierwatcher: failed to create DBus proxy: {e}");
return;
}
};
let signals = match proxy.receive_name_owner_changed().await {
Ok(s) => s,
Err(e) => {
log::error!("statusnotifierwatcher: failed to subscribe to NameOwnerChanged: {e}"));
log::error!("statusnotifierwatcher: failed to subscribe to NameOwnerChanged: {e}");
return;
}
};
let object_path: ObjectPath<'_> = match OBJECT_PATH.try_into() {
Ok(path) => path,
Err(e) => {
log::error!("statusnotifierwatcher: invalid object path {OBJECT_PATH}: {e}"));
log::error!("statusnotifierwatcher: invalid object path {OBJECT_PATH}: {e}");
return;
}
};
let signal_emitter = match SignalEmitter::new(&connection, object_path) {
Ok(emitter) => emitter,
Err(e) => {
log::error!("statusnotifierwatcher: failed to create signal emitter: {e}"));
log::error!("statusnotifierwatcher: failed to create signal emitter: {e}");
return;
}
};
log::info!("statusnotifierwatcher: NameOwnerChanged listener active"));
log::info!("statusnotifierwatcher: NameOwnerChanged listener active");
// Consume the signal stream without pulling in futures-lite as a direct
// dependency: zbus re-exports futures_core::Stream, and std provides
// poll_fn + pin! for manual polling.
@@ -578,7 +578,7 @@ async fn run_name_owner_changed_listener(
if !purged.is_empty() {
emit_purge_unregistered_signals(&signal_emitter, &purged).await;
let removed = purged.items.len() + purged.hosts.len();
log::error!();
log::error!(
"statusnotifierwatcher: purged {removed} entries for vanished owner {}",
args.old_owner
);
@@ -610,26 +610,15 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let (shutdown_tx, mut shutdown_rx) = tokio::sync::watch::channel(false);
// Signal handler task for clean shutdown
// Redox manages daemon lifecycle through the init system, not POSIX signals.
// tokio::signal::unix page-faults on Redox — relibc does not implement the
// signal-handler registration path the `signal` feature relies on (see the
// note in redbear-upower's Cargo.toml). No signal-driven shutdown: hold a
// sender open and never fire it, so the daemon runs until init stops it.
let signal_tx = shutdown_tx.clone();
tokio::spawn(async move {
#[cfg(unix)]
{
use tokio::signal::unix::{SignalKind, signal};
if let Ok(mut sigterm) = signal(SignalKind::terminate()) {
tokio::select! {
_ = sigterm.recv() => {},
_ = tokio::signal::ctrl_c() => {},
}
} else {
let _ = tokio::signal::ctrl_c().await;
}
}
#[cfg(not(unix))]
{
let _ = tokio::signal::ctrl_c().await;
}
let _ = signal_tx.send(true);
let _keep_open = signal_tx;
std::future::pending::<()>().await;
});
// Keep original sender alive so receiver doesn't see all-senders-dropped
let _shutdown_guard = shutdown_tx;
@@ -643,14 +632,14 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.build()
.await?;
log::info!("statusnotifierwatcher: {BUS_NAME} registered on session bus"));
log::info!("statusnotifierwatcher: {BUS_NAME} registered on session bus");
// Spawn NameOwnerChanged listener that prunes entries on owner loss.
tokio::spawn(run_name_owner_changed_listener(connection.clone(), watcher));
// Wait for shutdown signal
let _ = shutdown_rx.changed().await;
log::info!("statusnotifierwatcher: shutdown signal received, exiting cleanly"));
log::info!("statusnotifierwatcher: shutdown signal received, exiting cleanly");
drop(connection);
Ok(())
}
@@ -14,6 +14,6 @@ path = "src/main.rs"
log = { version = "0.4", features = ["std"] }
env_logger = "0.11"
zbus = { version = "5", default-features = false, features = ["tokio"] }
tokio = { version = "1", default-features = false, features = ["rt", "rt-multi-thread", "macros", "net", "time", "sync", "signal"] }
tokio = { version = "1", default-features = false, features = ["rt", "rt-multi-thread", "macros", "net", "time", "sync"] }
serde = { version = "1", features = ["derive"] }
libc = "0.2"
@@ -76,24 +76,15 @@ fn system_connection_builder() -> Result<ConnectionBuilder<'static>, Box<dyn Err
}
fn spawn_signal_handler(shutdown_tx: tokio::sync::watch::Sender<bool>) {
// Redox manages daemon lifecycle through the init system, not POSIX signals.
// tokio::signal::unix page-faults on Redox — relibc does not implement the
// signal-handler registration path the `signal` feature relies on (see the
// note in redbear-upower's Cargo.toml). No signal-driven shutdown: hold the
// watch sender open and never fire it, so the daemon runs until init stops
// the process.
tokio::spawn(async move {
#[cfg(unix)]
{
use tokio::signal::unix::{SignalKind, signal};
if let Ok(mut sigterm) = signal(SignalKind::terminate()) {
tokio::select! {
_ = sigterm.recv() => {},
_ = tokio::signal::ctrl_c() => {},
}
} else {
let _ = tokio::signal::ctrl_c().await;
}
}
#[cfg(not(unix))]
{
let _ = tokio::signal::ctrl_c().await;
}
let _ = shutdown_tx.send(true);
let _keep_open = shutdown_tx;
std::future::pending::<()>().await;
});
}
@@ -1,5 +1,5 @@
use std::{
use log::{error, info, warn};
use std::{
env,
error::Error,
fs,
@@ -137,7 +137,7 @@ async fn wait_for_dbus_socket() {
}
tokio::time::sleep(Duration::from_secs(1)).await;
}
log::error!("redbear-upower: timed out waiting for D-Bus socket at {socket_path}"));
log::error!("redbear-upower: timed out waiting for D-Bus socket at {socket_path}");
}
fn parse_object_path(path: &str) -> Result<OwnedObjectPath, Box<dyn Error>> {
@@ -621,7 +621,7 @@ async fn run_daemon() -> Result<(), Box<dyn Error>> {
wait_for_dbus_socket().await;
let runtime = PowerRuntime::discover()?;
if !runtime.available() {
log::error!();
log::error!(
"redbear-upower: /scheme/acpi/power unavailable; serving empty provisional UPower surface"
);
}
@@ -678,7 +678,7 @@ async fn run_daemon() -> Result<(), Box<dyn Error>> {
match builder.build().await {
Ok(connection) => {
log::info!("redbear-upower: registered {BUS_NAME} on the system bus"));
log::info!("redbear-upower: registered {BUS_NAME} on the system bus");
let upower_path = parse_object_path(UPOWER_PATH)?;
let signal_emitter = SignalEmitter::new(&connection, upower_path)?;
@@ -693,22 +693,22 @@ async fn run_daemon() -> Result<(), Box<dyn Error>> {
tokio::select! {
result = shutdown_rx.changed() => {
if result.is_err() {
log::info!("redbear-upower: signal handler exited unexpectedly"));
log::info!("redbear-upower: signal handler exited unexpectedly");
}
log::info!("redbear-upower: shutdown signal received, exiting cleanly"));
log::info!("redbear-upower: shutdown signal received, exiting cleanly");
break;
}
_ = notify_interval.tick() => {
let drained = read_notifications();
if !drained.is_empty() {
log::error!();
log::error!(
"redbear-upower: {} ACPI notification(s): {}",
drained.len(),
drained.join(", ")
);
let current_snapshot = runtime.snapshot();
if current_snapshot != last_snapshot {
log::error!();
log::error!(
"redbear-upower: power state changed after notification, emitting Changed signal"
);
let _ = UPowerDaemon::changed(&signal_emitter).await;
@@ -719,7 +719,7 @@ async fn run_daemon() -> Result<(), Box<dyn Error>> {
_ = poll_interval.tick() => {
let current_snapshot = runtime.snapshot();
if current_snapshot != last_snapshot {
log::error!();
log::error!(
"redbear-upower: power state changed, emitting Changed signal"
);
let _ = UPowerDaemon::changed(&signal_emitter).await;
@@ -734,7 +734,7 @@ async fn run_daemon() -> Result<(), Box<dyn Error>> {
}
Err(err) => {
if attempt < 3 {
log::error!();
log::error!(
"redbear-upower: attempt {attempt}/3 failed ({err}), retrying in 1s..."
);
tokio::time::sleep(Duration::from_secs(1)).await;
@@ -754,25 +754,25 @@ fn main() {
let _ = env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).try_init();
match parse_args() {
Ok(Command::Help) => {
log::info!("{}", usage()));
log::info!("{}", usage());
}
Ok(Command::Run) => {
let runtime = match RuntimeBuilder::new_multi_thread().enable_all().build() {
Ok(runtime) => runtime,
Err(err) => {
log::error!("redbear-upower: failed to create tokio runtime: {err}"));
log::error!("redbear-upower: failed to create tokio runtime: {err}");
process::exit(1);
}
};
if let Err(err) = runtime.block_on(run_daemon()) {
log::error!("redbear-upower: fatal error: {err}"));
log::error!("redbear-upower: fatal error: {err}");
process::exit(1);
}
}
Err(err) => {
log::error!("redbear-upower: {err}"));
log::error!("{}", usage()));
log::error!("redbear-upower: {err}");
log::error!("{}", usage());
process::exit(1);
}
}