gioser

tawasuyu/brahman

Rust 582TOML 222Markdown 73JSON 198635 Definitions

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 · (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

4 files · +69 −17

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>> {