tawasuyu

tawasuyu/arje

> Standalone repository, extracted from the tawasuyu monorepo at tag v0.3.3. The domain's code lives under 03ukupacha/arje/; the foundational crates it shares with the rest of the suite come in as git dependencies pinned to that tag. cargo…

Rust 38TOML 12Markdown 1395 Sutichaykuna

⎇ d6b8f18b43c9bd9bf7268e48b61392a2d62bb229 crates/ente-logind-compat/src/main.rs Rust · 249 siq'ikuna · 8.8 KB Ñawpa willay Pi imata llamkarqan ch'uya

Pausa: 11 crates del fractal Ente #0 con cerebro completo d6b8f18b4 · Sergio ·

1//! ente-logind-compat: el Ente que se hace pasar por systemd-logind.
2//!
3//! Vive FUERA de PID 1 — un parser D-Bus en el Init es una bomba con CVEs
4//! históricos. Ejecutado como hijo del Ente #0 con `Restart` supervision.
5//!
6//! Implementa el subset del interface `org.freedesktop.login1.Manager` que
7//! GNOME/KDE consultan en arranque. Cada método se traduce internamente en
8//! una request al bus interno del fractal — capacidades tipadas, no nombres
9//! D-Bus opacos hacia abajo.
10//!
11//! ## Lo que GNOME/KDE realmente llaman al boot
12//!   - ListSessions, ListUsers, GetSession*
13//!   - Inhibit (mantiene un fd vivo mientras la app está activa)
14//!   - CanPowerOff/CanReboot/CanSuspend
15//!   - PowerOff/Reboot/Suspend
16//!   - Properties: IdleHint, Docked, etc.
17//!
18//! El stub responde "no hay sesiones" y "sí puedo apagar" — suficiente para
19//! que GNOME complete arranque sin marcar fallo.
20
21use ente_bus::{BusClient, BusRequest, BusResponse};
22use ente_card::Capability;
23use std::sync::atomic::{AtomicU32, Ordering};
24use std::time::Duration;
25use tokio::signal::unix::{signal, SignalKind};
26use tracing::{info, warn};
27use tracing_subscriber::EnvFilter;
28use zbus::{fdo, interface, zvariant::OwnedObjectPath, Connection};
29
30const BUS_NAME: &str = "org.freedesktop.login1";
31const MANAGER_PATH: &str = "/org/freedesktop/login1";
32
33#[tokio::main(flavor = "current_thread")]
34async fn main() -> anyhow::Result<()> {
35    init_tracing();
36    info!("ente-logind-compat: arrancando");
37
38    let bus_addr = std::env::var("DBUS_SYSTEM_BUS_ADDRESS")
39        .unwrap_or_else(|_| "unix:path=/var/run/dbus/system_bus_socket".into());
40    let bus_path = bus_addr.strip_prefix("unix:path=").unwrap_or(&bus_addr);
41    let bus_present = std::path::Path::new(bus_path).exists();
42    info!(bus_addr, bus_present, "configuración D-Bus");
43
44    // Anunciamos nuestra presencia al bus interno del fractal antes de
45    // intentar registrar el nombre D-Bus. Esto sirve como handshake "estoy
46    // vivo" independiente del estado del system bus.
47    announce_to_fractal().await;
48
49    if !bus_present {
50        warn!("system bus no disponible — modo idle (esperando SIGTERM)");
51        return wait_for_term().await;
52    }
53
54    let conn = match build_connection().await {
55        Ok(c) => c,
56        Err(e) => {
57            warn!(?e, "fallo al registrar org.freedesktop.login1 — modo idle");
58            // No retornamos error: la supervisión Restart entraría en bucle
59            // si systemd-logind real ya posee el nombre. Esperar señal y salir.
60            return wait_for_term().await;
61        }
62    };
63
64    info!("logind compat corriendo — esperando señales");
65    let _ = conn; // mantener viva la conexión hasta SIGTERM
66    wait_for_term().await
67}
68
69async fn build_connection() -> anyhow::Result<Connection> {
70    let manager = LogindManager::default();
71    let conn = zbus::connection::Builder::system()?
72        .name(BUS_NAME)?
73        .serve_at(MANAGER_PATH, manager)?
74        .build()
75        .await?;
76    info!(name = BUS_NAME, path = MANAGER_PATH, "name acquired + manager served");
77    Ok(conn)
78}
79
80async fn announce_to_fractal() {
81    match BusClient::from_env().await {
82        Ok(mut client) => {
83            let req = BusRequest::Announce {
84                capabilities: vec![Capability::LegacyLogind],
85            };
86            match client.call(req).await {
87                Ok(BusResponse::Ok) => info!("Announce → bus interno OK"),
88                Ok(other) => warn!(?other, "Announce respuesta inesperada"),
89                Err(e) => warn!(?e, "Announce falló"),
90            }
91        }
92        Err(e) => warn!(?e, "no se pudo conectar al bus interno"),
93    }
94}
95
96async fn forward_to_fractal(req: BusRequest) -> fdo::Result<()> {
97    let mut client = BusClient::from_env().await
98        .map_err(|e| fdo::Error::Failed(format!("bus client: {e}")))?;
99    match client.call(req).await {
100        Ok(BusResponse::Ok) => Ok(()),
101        Ok(BusResponse::Error(s)) => Err(fdo::Error::Failed(s)),
102        Ok(other) => Err(fdo::Error::Failed(format!("respuesta inesperada: {other:?}"))),
103        Err(e) => Err(fdo::Error::Failed(format!("bus call: {e}"))),
104    }
105}
106
107async fn wait_for_term() -> anyhow::Result<()> {
108    let mut term = signal(SignalKind::terminate())?;
109    let mut int_ = signal(SignalKind::interrupt())?;
110    let mut tick = tokio::time::interval(Duration::from_secs(60));
111    tick.tick().await; // descartar el primer tick inmediato
112    loop {
113        tokio::select! {
114            _ = term.recv() => { info!("SIGTERM — cierre ordenado"); return Ok(()); }
115            _ = int_.recv() => { info!("SIGINT — cierre"); return Ok(()); }
116            _ = tick.tick() => { info!("heartbeat"); }
117        }
118    }
119}
120
121fn init_tracing() {
122    let filter = EnvFilter::try_from_default_env()
123        .unwrap_or_else(|_| EnvFilter::new("ente_logind_compat=info"));
124    tracing_subscriber::fmt().with_env_filter(filter).with_target(true).init();
125}
126
127#[derive(Default)]
128struct LogindManager {
129    /// Contador monótono de inhibits. Real impl mantendría una tabla con
130    /// el fd vivo de cada uno y los enrutaría al bus interno del fractal.
131    inhibit_counter: AtomicU32,
132}
133
134/// Tipos del wire format de `org.freedesktop.login1.Manager`.
135type SessionTuple = (String, u32, String, String, OwnedObjectPath);
136type UserTuple = (u32, String, OwnedObjectPath);
137
138#[interface(name = "org.freedesktop.login1.Manager")]
139impl LogindManager {
140    // ---- Listado / lookup ----
141
142    async fn list_sessions(&self) -> fdo::Result<Vec<SessionTuple>> {
143        Ok(vec![])
144    }
145
146    async fn list_users(&self) -> fdo::Result<Vec<UserTuple>> {
147        Ok(vec![])
148    }
149
150    async fn get_session(&self, _session_id: String) -> fdo::Result<OwnedObjectPath> {
151        Err(fdo::Error::Failed("no sessions in fractal".into()))
152    }
153
154    async fn get_session_by_pid(&self, _pid: u32) -> fdo::Result<OwnedObjectPath> {
155        Err(fdo::Error::Failed("no sessions in fractal".into()))
156    }
157
158    async fn get_user(&self, _uid: u32) -> fdo::Result<OwnedObjectPath> {
159        Err(fdo::Error::Failed("no users in fractal".into()))
160    }
161
162    async fn get_user_by_pid(&self, _pid: u32) -> fdo::Result<OwnedObjectPath> {
163        Err(fdo::Error::Failed("no users in fractal".into()))
164    }
165
166    // ---- Inhibit ----
167    //
168    // Real: devuelve un fd que el cliente mantiene abierto mientras quiere
169    // inhibir. Cuando lo cierra, sabemos que terminó. Aquí: stub que falla
170    // con NotSupported — GNOME registra warning pero continúa el arranque.
171
172    async fn inhibit(
173        &self,
174        what: String,
175        who: String,
176        why: String,
177        mode: String,
178    ) -> fdo::Result<zbus::zvariant::OwnedFd> {
179        let n = self.inhibit_counter.fetch_add(1, Ordering::Relaxed);
180        info!(n, %what, %who, %why, %mode, "Inhibit (stub)");
181        Err(fdo::Error::NotSupported("Inhibit todavía no enruta al bus interno".into()))
182    }
183
184    // ---- Power management ----
185
186    async fn power_off(&self, interactive: bool) -> fdo::Result<()> {
187        info!(interactive, "PowerOff D-Bus → bus interno");
188        forward_to_fractal(BusRequest::PowerOff { interactive }).await
189    }
190
191    async fn reboot(&self, interactive: bool) -> fdo::Result<()> {
192        info!(interactive, "Reboot D-Bus → bus interno");
193        forward_to_fractal(BusRequest::Reboot { interactive }).await
194    }
195
196    async fn suspend(&self, interactive: bool) -> fdo::Result<()> {
197        info!(interactive, "Suspend D-Bus → bus interno");
198        forward_to_fractal(BusRequest::Suspend { interactive }).await
199    }
200
201    async fn hibernate(&self, interactive: bool) -> fdo::Result<()> {
202        info!(interactive, "Hibernate D-Bus → bus interno");
203        forward_to_fractal(BusRequest::Hibernate { interactive }).await
204    }
205
206    async fn can_power_off(&self) -> fdo::Result<String> {
207        Ok("yes".into())
208    }
209
210    async fn can_reboot(&self) -> fdo::Result<String> {
211        Ok("yes".into())
212    }
213
214    async fn can_suspend(&self) -> fdo::Result<String> {
215        // "challenge" = válido, requiere autenticación. GNOME muestra el
216        // botón pero pide PIN/contraseña antes de invocar Suspend.
217        Ok("challenge".into())
218    }
219
220    async fn can_hibernate(&self) -> fdo::Result<String> {
221        Ok("challenge".into())
222    }
223
224    // ---- Properties mínimas ----
225
226    #[zbus(property)]
227    async fn idle_hint(&self) -> bool { false }
228
229    #[zbus(property)]
230    async fn idle_since_hint(&self) -> u64 { 0 }
231
232    #[zbus(property)]
233    async fn idle_since_hint_monotonic(&self) -> u64 { 0 }
234
235    #[zbus(property)]
236    async fn block_inhibited(&self) -> String { String::new() }
237
238    #[zbus(property)]
239    async fn delay_inhibited(&self) -> String { String::new() }
240
241    #[zbus(property)]
242    async fn docked(&self) -> bool { false }
243
244    #[zbus(property)]
245    async fn lid_closed(&self) -> bool { false }
246
247    #[zbus(property)]
248    async fn on_external_power(&self) -> bool { true }
249}