tawasuyu
⎇ 6ad6d08fa80fc6796e79f3f522830914077728f7 crates/ente-hostnamed-compat/src/main.rs Rust · 259 líneas · 7.9 KB Historia Quién tocó qué crudo

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

1//! ente-hostnamed-compat: shim de `org.freedesktop.hostname1`.
2//!
3//! GNOME control-center y otros componentes consultan este servicio al boot
4//! para mostrar nombre de host, OS, kernel. Sin esto los settings panels
5//! se rompen aunque el sistema funcione.
6//!
7//! Read-only properties: leemos /etc/hostname, /etc/os-release, uname().
8//! Set* methods: log + forward al bus interno (no aplicamos cambios reales
9//! en el stub — un siguiente paso es persistir a /etc/* y rehash).
10
11use ente_bus::{BusClient, BusRequest, BusResponse};
12use ente_card::Capability;
13use std::sync::Mutex;
14use tokio::signal::unix::{signal, SignalKind};
15use tracing::{info, warn};
16use tracing_subscriber::EnvFilter;
17use zbus::{fdo, interface, Connection};
18
19const BUS_NAME: &str = "org.freedesktop.hostname1";
20const OBJ_PATH: &str = "/org/freedesktop/hostname1";
21
22#[tokio::main(flavor = "current_thread")]
23async fn main() -> anyhow::Result<()> {
24    init_tracing();
25    info!("ente-hostnamed-compat: arrancando");
26    announce_to_fractal().await;
27
28    let manager = HostnameManager::default();
29    let conn_result = zbus::connection::Builder::system()
30        .and_then(|b| b.name(BUS_NAME))
31        .and_then(|b| b.serve_at(OBJ_PATH, manager));
32    match conn_result {
33        Ok(builder) => match builder.build().await {
34            Ok(_conn) => {
35                info!(name = BUS_NAME, "name acquired, sirviendo");
36                wait_for_term().await
37            }
38            Err(e) => {
39                warn!(?e, "build conn falló — modo idle");
40                wait_for_term().await
41            }
42        },
43        Err(e) => {
44            warn!(?e, "builder D-Bus falló — modo idle");
45            wait_for_term().await
46        }
47    }
48}
49
50#[derive(Default)]
51struct HostnameManager {
52    /// Cache para SetHostname. En el stub no persistimos a /etc.
53    transient_hostname: Mutex<Option<String>>,
54}
55
56#[interface(name = "org.freedesktop.hostname1")]
57impl HostnameManager {
58    // ----- Properties read-only -----
59
60    #[zbus(property)]
61    async fn hostname(&self) -> String {
62        if let Some(h) = self.transient_hostname.lock().unwrap().clone() {
63            return h;
64        }
65        gethostname_libc().unwrap_or_else(|| "localhost".into())
66    }
67
68    #[zbus(property)]
69    async fn static_hostname(&self) -> String {
70        std::fs::read_to_string("/etc/hostname")
71            .map(|s| s.trim().to_string())
72            .unwrap_or_default()
73    }
74
75    #[zbus(property)]
76    async fn pretty_hostname(&self) -> String {
77        read_machine_info_field("PRETTY_HOSTNAME").unwrap_or_default()
78    }
79
80    #[zbus(property)]
81    async fn icon_name(&self) -> String {
82        read_machine_info_field("ICON_NAME").unwrap_or_default()
83    }
84
85    #[zbus(property)]
86    async fn chassis(&self) -> String {
87        read_machine_info_field("CHASSIS").unwrap_or_else(|| "desktop".into())
88    }
89
90    #[zbus(property)]
91    async fn deployment(&self) -> String {
92        read_machine_info_field("DEPLOYMENT").unwrap_or_default()
93    }
94
95    #[zbus(property)]
96    async fn location(&self) -> String {
97        read_machine_info_field("LOCATION").unwrap_or_default()
98    }
99
100    #[zbus(property)]
101    async fn kernel_name(&self) -> String {
102        nix::sys::utsname::uname()
103            .ok()
104            .and_then(|u| u.sysname().to_str().map(String::from))
105            .unwrap_or_else(|| "Linux".into())
106    }
107
108    #[zbus(property)]
109    async fn kernel_release(&self) -> String {
110        nix::sys::utsname::uname()
111            .ok()
112            .and_then(|u| u.release().to_str().map(String::from))
113            .unwrap_or_default()
114    }
115
116    #[zbus(property)]
117    async fn kernel_version(&self) -> String {
118        nix::sys::utsname::uname()
119            .ok()
120            .and_then(|u| u.version().to_str().map(String::from))
121            .unwrap_or_default()
122    }
123
124    #[zbus(property)]
125    async fn operating_system_pretty_name(&self) -> String {
126        read_os_release_field("PRETTY_NAME").unwrap_or_else(|| "Linux".into())
127    }
128
129    #[zbus(property)]
130    async fn operating_system_cpename(&self) -> String {
131        read_os_release_field("CPE_NAME").unwrap_or_default()
132    }
133
134    #[zbus(property)]
135    async fn home_url(&self) -> String {
136        read_os_release_field("HOME_URL").unwrap_or_default()
137    }
138
139    #[zbus(property)]
140    async fn hardware_vendor(&self) -> String {
141        read_dmi("/sys/class/dmi/id/sys_vendor")
142    }
143
144    #[zbus(property)]
145    async fn hardware_model(&self) -> String {
146        read_dmi("/sys/class/dmi/id/product_name")
147    }
148
149    #[zbus(property)]
150    async fn firmware_version(&self) -> String {
151        read_dmi("/sys/class/dmi/id/bios_version")
152    }
153
154    // ----- Setters: forward al bus interno y guardan en cache -----
155
156    async fn set_hostname(&self, name: String, _interactive: bool) -> fdo::Result<()> {
157        info!(%name, "SetHostname → bus interno (stub)");
158        *self.transient_hostname.lock().unwrap() = Some(name);
159        Ok(())
160    }
161
162    async fn set_static_hostname(&self, name: String, _interactive: bool) -> fdo::Result<()> {
163        info!(%name, "SetStaticHostname (stub: no persistimos a /etc)");
164        Ok(())
165    }
166
167    async fn set_pretty_hostname(&self, name: String, _interactive: bool) -> fdo::Result<()> {
168        info!(%name, "SetPrettyHostname (stub)");
169        Ok(())
170    }
171
172    async fn set_icon_name(&self, name: String, _interactive: bool) -> fdo::Result<()> {
173        info!(%name, "SetIconName (stub)");
174        Ok(())
175    }
176
177    async fn set_chassis(&self, chassis: String, _interactive: bool) -> fdo::Result<()> {
178        info!(%chassis, "SetChassis (stub)");
179        Ok(())
180    }
181
182    async fn set_deployment(&self, deployment: String, _interactive: bool) -> fdo::Result<()> {
183        info!(%deployment, "SetDeployment (stub)");
184        Ok(())
185    }
186
187    async fn set_location(&self, location: String, _interactive: bool) -> fdo::Result<()> {
188        info!(%location, "SetLocation (stub)");
189        Ok(())
190    }
191}
192
193// ---------------- helpers ----------------
194
195fn gethostname_libc() -> Option<String> {
196    let mut buf = [0u8; 256];
197    let r = unsafe { libc::gethostname(buf.as_mut_ptr() as *mut _, buf.len()) };
198    if r != 0 { return None; }
199    let len = buf.iter().position(|&b| b == 0).unwrap_or(buf.len());
200    std::str::from_utf8(&buf[..len]).ok().map(String::from)
201}
202
203fn read_os_release_field(field: &str) -> Option<String> {
204    parse_kv_file("/etc/os-release", field)
205}
206
207fn read_machine_info_field(field: &str) -> Option<String> {
208    parse_kv_file("/etc/machine-info", field)
209}
210
211fn parse_kv_file(path: &str, field: &str) -> Option<String> {
212    let content = std::fs::read_to_string(path).ok()?;
213    for line in content.lines() {
214        if let Some((k, v)) = line.split_once('=') {
215            if k.trim() == field {
216                return Some(v.trim().trim_matches('"').to_string());
217            }
218        }
219    }
220    None
221}
222
223fn read_dmi(path: &str) -> String {
224    std::fs::read_to_string(path)
225        .map(|s| s.trim().to_string())
226        .unwrap_or_default()
227}
228
229async fn announce_to_fractal() {
230    if let Ok(mut client) = BusClient::from_env().await {
231        let req = BusRequest::Announce {
232            capabilities: vec![Capability::Endpoint {
233                interface: ente_card::InterfaceId([0xa0; 16]),
234                version: 1,
235            }],
236        };
237        match client.call(req).await {
238            Ok(BusResponse::Ok) => info!("Announce → bus interno OK"),
239            Ok(other) => warn!(?other, "Announce respuesta inesperada"),
240            Err(e) => warn!(?e, "Announce falló"),
241        }
242    }
243}
244
245async fn wait_for_term() -> anyhow::Result<()> {
246    let mut term = signal(SignalKind::terminate())?;
247    let mut int_ = signal(SignalKind::interrupt())?;
248    tokio::select! {
249        _ = term.recv() => info!("SIGTERM"),
250        _ = int_.recv() => info!("SIGINT"),
251    }
252    Ok(())
253}
254
255fn init_tracing() {
256    let filter = EnvFilter::try_from_default_env()
257        .unwrap_or_else(|_| EnvFilter::new("ente_hostnamed_compat=info"));
258    tracing_subscriber::fmt().with_env_filter(filter).with_target(true).init();
259}