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…
⎇ d6b8f18b43c9bd9bf7268e48b61392a2d62bb229
crates/ente-logind-compat/src/main.rs
Historia
Quién tocó qué
crudo
Pausa: 11 crates del fractal Ente #0 con cerebro completo
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}