From a388ab14b76921e9da0cd25d822d22353a220b69 Mon Sep 17 00:00:00 2001 From: sergio Date: Fri, 22 May 2026 20:45:53 +0000 Subject: [PATCH] =?UTF-8?q?fix(compat):=20auditor=C3=ADa=20de=20stubs=20?= =?UTF-8?q?=E2=80=94=20los=20m=C3=A9todos=20que=20ment=C3=ADan=20dejan=20d?= =?UTF-8?q?e=20mentir?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- Cargo.lock | 2 + .../compat/arje-systemd1-compat/src/main.rs | 25 +++++--- .../compat/arje-timedated-compat/Cargo.toml | 2 + .../compat/arje-timedated-compat/src/main.rs | 57 ++++++++++++++++--- 4 files changed, 69 insertions(+), 17 deletions(-) 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 { - 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 { - info!(%name, "RestartUnit (delega a StopUnit)"); - self.stop_unit(name, mode).await + async fn restart_unit(&self, name: String, _mode: String) -> fdo::Result { + 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 { @@ -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> {