fix(compat): auditoría de stubs — los métodos que mentían dejan de mentir
Repaso de los 11 shims restantes buscando métodos que devolvían éxito
sin hacer el trabajo (como los dos setters de localed). Resultado:
timedated — tres setters arreglados de verdad:
- SetTime: aplica el reloj con clock_settime(CLOCK_REALTIME) en vez de
sólo loggear; si falla (sin CAP_SYS_TIME) devuelve error honesto.
- SetLocalRTC: escribe la tercera línea de /etc/adjtime (UTC|LOCAL),
conservando las dos primeras.
- SetNTP: arje no gestiona un daemon NTP — en vez de fingir éxito,
rechaza honestamente; `CanNTP` pasa a `false` para que GNOME deje el
toggle deshabilitado y ni llegue a llamarlo.
systemd1 — StopUnit/RestartUnit/KillUnit dejaban creer que habían
detenido la unit; ahora devuelven NotSupported honesto (como StartUnit).
Lo demás del repaso ya era honesto: resolved/machined devuelven
NotSupported de frente; polkit/tmpfiles/notify/binfmt/journald no
mienten. timer-compat queda como hueco conocido y autodocumentado (sus
timers disparan pero el spawn es un no-op a la espera del bus).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
a388ab14b76921e9da0cd25d822d22353a220b69 · by sergio · 2026-05-22T20:45:53Z (5 months ago) · parent 4bcdc88c8 · browse the tree at this commit
Semantic delta · 7
crates/compat/arje-systemd1-compat/src/main.rs
crates/compat/arje-timedated-compat/src/main.rs
diff --git a/Cargo.lock b/Cargo.lock
index 39b30d2..2f3b42e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -658,6 +658,8 @@ dependencies = [
"anyhow",
"arje-bus",
"arje-card",
+ "arje-compat-common",
+ "libc",
"tokio",
"tracing",
"tracing-subscriber",
diff --git a/crates/compat/arje-systemd1-compat/src/main.rs b/crates/compat/arje-systemd1-compat/src/main.rs
index aa09b22..21f93bf 100644
--- a/crates/compat/arje-systemd1-compat/src/main.rs
+++ b/crates/compat/arje-systemd1-compat/src/main.rs
@@ -143,15 +143,20 @@ impl SystemdManager {
}
async fn stop_unit(&self, name: String, _mode: String) -> fdo::Result<OwnedObjectPath> {
- warn!(%name, "StopUnit (stub: TODO via bus capability)");
- // TODO: bus → graph → kill PID por label. Por ahora no-op.
- let path = ObjectPath::try_from("/").unwrap();
- Ok(path.into())
+ // No se finge éxito: detener una Card por nombre de unit exige
+ // una capability del bus del fractal que aún no existe. Hasta
+ // entonces, se rechaza con honestidad (igual que StartUnit).
+ warn!(%name, "StopUnit rechazado — el fractal no detiene Cards por nombre de unit");
+ Err(fdo::Error::NotSupported(
+ "StopUnit: el fractal supervisa Cards; no se detienen por nombre de unit".into(),
+ ))
}
- async fn restart_unit(&self, name: String, mode: String) -> fdo::Result<OwnedObjectPath> {
- info!(%name, "RestartUnit (delega a StopUnit)");
- self.stop_unit(name, mode).await
+ async fn restart_unit(&self, name: String, _mode: String) -> fdo::Result<OwnedObjectPath> {
+ warn!(%name, "RestartUnit rechazado — sin StopUnit honesto no hay reinicio");
+ Err(fdo::Error::NotSupported(
+ "RestartUnit: el fractal no reinicia Cards por nombre de unit".into(),
+ ))
}
async fn reload_unit(&self, name: String, _mode: String) -> fdo::Result<OwnedObjectPath> {
@@ -161,8 +166,10 @@ impl SystemdManager {
}
async fn kill_unit(&self, name: String, _who: String, _signal: i32) -> fdo::Result<()> {
- warn!(%name, "KillUnit (stub)");
- Ok(())
+ warn!(%name, "KillUnit rechazado — no implementado");
+ Err(fdo::Error::NotSupported(
+ "KillUnit: enviar señales a una Card por nombre de unit no está implementado".into(),
+ ))
}
async fn subscribe(&self) -> fdo::Result<()> { Ok(()) }
diff --git a/crates/compat/arje-timedated-compat/Cargo.toml b/crates/compat/arje-timedated-compat/Cargo.toml
index 387c37a..5c97bcb 100644
--- a/crates/compat/arje-timedated-compat/Cargo.toml
+++ b/crates/compat/arje-timedated-compat/Cargo.toml
@@ -12,6 +12,8 @@ path = "src/main.rs"
[dependencies]
arje-card = { path = "../../protocol/arje-card" }
arje-bus = { path = "../../runtime/arje-bus" }
+arje-compat-common = { path = "../arje-compat-common" }
+libc = { workspace = true }
anyhow = { workspace = true }
tokio = { workspace = true }
tracing = { workspace = true }
diff --git a/crates/compat/arje-timedated-compat/src/main.rs b/crates/compat/arje-timedated-compat/src/main.rs
index 77d6711..b18e241 100644
--- a/crates/compat/arje-timedated-compat/src/main.rs
+++ b/crates/compat/arje-timedated-compat/src/main.rs
@@ -5,6 +5,7 @@
use arje_bus::{BusClient, BusRequest, BusResponse};
use arje_card::Capability;
+use arje_compat_common::atomic_write;
use std::time::{SystemTime, UNIX_EPOCH};
use tokio::signal::unix::{signal, SignalKind};
use tracing::{info, warn};
@@ -68,10 +69,11 @@ impl TimedateManager {
#[zbus(property)]
async fn local_rtc(&self) -> bool { false }
- /// Si NTP es soportado. Reportamos true (asumimos systemd-timesyncd
- /// o chrony están disponibles en el host).
+ /// Si el sistema puede gestionar NTP. `false`: arje no administra un
+ /// daemon de sincronización propio, así que GNOME deja el toggle
+ /// «hora automática» deshabilitado en vez de fingir que funciona.
#[zbus(property)]
- async fn can_ntp(&self) -> bool { true }
+ async fn can_ntp(&self) -> bool { false }
/// Si NTP está activo. Sin daemon real bajo nuestro control no podemos
/// consultarlo con precisión — false como default seguro.
@@ -99,8 +101,33 @@ impl TimedateManager {
// ----- Setters -----
- async fn set_time(&self, usec_utc: i64, _relative: bool, _interactive: bool) -> fdo::Result<()> {
- info!(usec_utc, "SetTime (stub: requiere CAP_SYS_TIME para aplicar)");
+ async fn set_time(&self, usec_utc: i64, relative: bool, _interactive: bool) -> fdo::Result<()> {
+ // `relative`: el valor es un delta sobre el reloj actual.
+ let target = if relative {
+ let now = SystemTime::now()
+ .duration_since(UNIX_EPOCH)
+ .map(|d| d.as_micros() as i64)
+ .unwrap_or(0);
+ now + usec_utc
+ } else {
+ usec_utc
+ };
+ if target < 0 {
+ return Err(fdo::Error::InvalidArgs("el tiempo resultante es negativo".into()));
+ }
+ let ts = libc::timespec {
+ tv_sec: (target / 1_000_000) as libc::time_t,
+ tv_nsec: ((target % 1_000_000) * 1_000) as _,
+ };
+ // SEGURIDAD: `clock_settime` con un timespec válido vivo en la
+ // pila durante la llamada.
+ let r = unsafe { libc::clock_settime(libc::CLOCK_REALTIME, &ts) };
+ if r != 0 {
+ let e = std::io::Error::last_os_error();
+ warn!(%e, "clock_settime falló (¿falta CAP_SYS_TIME?)");
+ return Err(fdo::Error::Failed(format!("clock_settime: {e}")));
+ }
+ info!(target, "SetTime aplicado al reloj del sistema");
Ok(())
}
@@ -124,13 +151,27 @@ impl TimedateManager {
}
async fn set_local_rtc(&self, local_rtc: bool, _fix_system: bool, _interactive: bool) -> fdo::Result<()> {
- info!(local_rtc, "SetLocalRTC (stub)");
+ let mode = if local_rtc { "LOCAL" } else { "UTC" };
+ // `/etc/adjtime` tiene tres líneas; sólo la tercera (UTC|LOCAL)
+ // nos concierne. Se conservan las dos primeras si ya existían.
+ let existing = std::fs::read_to_string("/etc/adjtime").unwrap_or_default();
+ let mut lines = existing.lines();
+ let l0 = lines.next().unwrap_or("0.0 0 0.0");
+ let l1 = lines.next().unwrap_or("0");
+ atomic_write("/etc/adjtime", format!("{l0}\n{l1}\n{mode}\n").as_bytes())
+ .map_err(|e| fdo::Error::Failed(format!("write /etc/adjtime: {e}")))?;
+ info!(local_rtc, "SetLocalRTC → /etc/adjtime");
Ok(())
}
async fn set_ntp(&self, ntp: bool, _interactive: bool) -> fdo::Result<()> {
- info!(ntp, "SetNTP (stub: no controlamos timesyncd)");
- Ok(())
+ // arje no administra un daemon NTP propio. En vez de fingir
+ // éxito, se rechaza con honestidad — y como `can_ntp` es `false`,
+ // GNOME deja el toggle deshabilitado y normalmente no llega acá.
+ warn!(ntp, "SetNTP rechazado — arje no gestiona un daemon NTP");
+ Err(fdo::Error::NotSupported(
+ "arje no gestiona un daemon NTP (timesyncd/chrony)".into(),
+ ))
}
async fn list_timezones(&self) -> fdo::Result<Vec<String>> {