tawasuyu
⎇ 6ad6d08fa80fc6796e79f3f522830914077728f7 crates/ente-timedated-compat/src/main.rs Rust · 168 lines · 5.7 KB History Who touched what raw

4 D-Bus shims sustituyendo systemd: hostnamed, timedated, localed, journald 6ad6d08fa · Sergio ·

1//! ente-timedated-compat: shim de `org.freedesktop.timedate1`.
2//!
3//! GNOME settings panel "Date & Time" llama aquí. Properties read-only se
4//! mapean a syscalls/lecturas del sistema; setters log + forward.
5
6use ente_bus::{BusClient, BusRequest, BusResponse};
7use ente_card::Capability;
8use std::time::{SystemTime, UNIX_EPOCH};
9use tokio::signal::unix::{signal, SignalKind};
10use tracing::{info, warn};
11use tracing_subscriber::EnvFilter;
12use zbus::{fdo, interface};
13
14const BUS_NAME: &str = "org.freedesktop.timedate1";
15const OBJ_PATH: &str = "/org/freedesktop/timedate1";
16
17#[tokio::main(flavor = "current_thread")]
18async fn main() -> anyhow::Result<()> {
19    init_tracing();
20    info!("ente-timedated-compat: arrancando");
21    announce_to_fractal().await;
22
23    let manager = TimedateManager::default();
24    let conn_result = zbus::connection::Builder::system()
25        .and_then(|b| b.name(BUS_NAME))
26        .and_then(|b| b.serve_at(OBJ_PATH, manager));
27    match conn_result {
28        Ok(builder) => match builder.build().await {
29            Ok(_conn) => {
30                info!(name = BUS_NAME, "name acquired, sirviendo");
31                wait_for_term().await
32            }
33            Err(e) => {
34                warn!(?e, "build conn falló — modo idle");
35                wait_for_term().await
36            }
37        },
38        Err(e) => {
39            warn!(?e, "builder D-Bus falló — modo idle");
40            wait_for_term().await
41        }
42    }
43}
44
45#[derive(Default)]
46struct TimedateManager;
47
48#[interface(name = "org.freedesktop.timedate1")]
49impl TimedateManager {
50    // ----- Properties -----
51
52    /// Timezone configurada. Por defecto leemos el target de /etc/localtime
53    /// (un symlink a /usr/share/zoneinfo/<TZ>).
54    #[zbus(property)]
55    async fn timezone(&self) -> String {
56        std::fs::read_link("/etc/localtime")
57            .ok()
58            .and_then(|p| {
59                let s = p.to_string_lossy().into_owned();
60                s.strip_prefix("/usr/share/zoneinfo/").map(String::from)
61                    .or_else(|| s.split("/zoneinfo/").nth(1).map(String::from))
62            })
63            .unwrap_or_else(|| "UTC".into())
64    }
65
66    /// True si el RTC del hardware está en local time. Convención moderna
67    /// es UTC (false). Reportamos false como default.
68    #[zbus(property)]
69    async fn local_rtc(&self) -> bool { false }
70
71    /// Si NTP es soportado. Reportamos true (asumimos systemd-timesyncd
72    /// o chrony están disponibles en el host).
73    #[zbus(property)]
74    async fn can_ntp(&self) -> bool { true }
75
76    /// Si NTP está activo. Sin daemon real bajo nuestro control no podemos
77    /// consultarlo con precisión — false como default seguro.
78    #[zbus(property)]
79    async fn ntp(&self) -> bool { false }
80
81    #[zbus(property)]
82    async fn ntpsynchronized(&self) -> bool { false }
83
84    /// Timestamp actual en microsegundos desde epoch.
85    #[zbus(property)]
86    async fn time_usec(&self) -> u64 {
87        SystemTime::now().duration_since(UNIX_EPOCH)
88            .map(|d| d.as_micros() as u64)
89            .unwrap_or(0)
90    }
91
92    #[zbus(property)]
93    async fn rtctime_usec(&self) -> u64 {
94        // El RTC real requiere ioctl a /dev/rtc — usamos system clock como aprox.
95        SystemTime::now().duration_since(UNIX_EPOCH)
96            .map(|d| d.as_micros() as u64)
97            .unwrap_or(0)
98    }
99
100    // ----- Setters -----
101
102    async fn set_time(&self, usec_utc: i64, _relative: bool, _interactive: bool) -> fdo::Result<()> {
103        info!(usec_utc, "SetTime (stub: requiere CAP_SYS_TIME para aplicar)");
104        Ok(())
105    }
106
107    async fn set_timezone(&self, timezone: String, _interactive: bool) -> fdo::Result<()> {
108        info!(%timezone, "SetTimezone (stub: no actualizamos /etc/localtime)");
109        Ok(())
110    }
111
112    async fn set_local_rtc(&self, local_rtc: bool, _fix_system: bool, _interactive: bool) -> fdo::Result<()> {
113        info!(local_rtc, "SetLocalRTC (stub)");
114        Ok(())
115    }
116
117    async fn set_ntp(&self, ntp: bool, _interactive: bool) -> fdo::Result<()> {
118        info!(ntp, "SetNTP (stub: no controlamos timesyncd)");
119        Ok(())
120    }
121
122    async fn list_timezones(&self) -> fdo::Result<Vec<String>> {
123        // Listar /usr/share/zoneinfo recursivamente. Hacemos un best-effort.
124        let mut out = Vec::new();
125        if let Ok(rd) = std::fs::read_dir("/usr/share/zoneinfo") {
126            for entry in rd.flatten() {
127                if let Ok(name) = entry.file_name().into_string() {
128                    if !name.starts_with(|c: char| c.is_lowercase()) && name != "posix" && name != "right" {
129                        out.push(name);
130                    }
131                }
132            }
133        }
134        Ok(out)
135    }
136}
137
138async fn announce_to_fractal() {
139    if let Ok(mut client) = BusClient::from_env().await {
140        let req = BusRequest::Announce {
141            capabilities: vec![Capability::Endpoint {
142                interface: ente_card::InterfaceId([0xa1; 16]),
143                version: 1,
144            }],
145        };
146        match client.call(req).await {
147            Ok(BusResponse::Ok) => info!("Announce → bus interno OK"),
148            Ok(other) => warn!(?other, "Announce respuesta inesperada"),
149            Err(e) => warn!(?e, "Announce falló"),
150        }
151    }
152}
153
154async fn wait_for_term() -> anyhow::Result<()> {
155    let mut term = signal(SignalKind::terminate())?;
156    let mut int_ = signal(SignalKind::interrupt())?;
157    tokio::select! {
158        _ = term.recv() => info!("SIGTERM"),
159        _ = int_.recv() => info!("SIGINT"),
160    }
161    Ok(())
162}
163
164fn init_tracing() {
165    let filter = EnvFilter::try_from_default_env()
166        .unwrap_or_else(|_| EnvFilter::new("ente_timedated_compat=info"));
167    tracing_subscriber::fmt().with_env_filter(filter).with_target(true).init();
168}