⎇ 6ad6d08fa80fc6796e79f3f522830914077728f7
crates/ente-timedated-compat/src/main.rs
History
Who touched what
raw
4 D-Bus shims sustituyendo systemd: hostnamed, timedated, localed, journald
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}