gioser
⎇ 227715a464b872a0ecd918a069c667e05c95ec27 crates/ente-binfmt-compat/src/main.rs Rust · 109 líneas · 3.7 KB Historia Quién tocó qué crudo

compat-systemd1, NOTIFY_SOCKET, binfmt y timer 227715a46 · Sergio ·

1//! ente-binfmt-compat: registra handlers de binfmt_misc al boot.
2//!
3//! systemd-binfmt lee `/usr/lib/binfmt.d/*.conf` y `/etc/binfmt.d/*.conf` y
4//! escribe cada línea al kernel via `/proc/sys/fs/binfmt_misc/register`.
5//! Esto habilita ejecución transparente de binarios no-ELF (qemu-user,
6//! wine, etc).
7//!
8//! Formato de cada línea:
9//!   :<name>:<type>:<offset>:<magic>:<mask>:<interpreter>:<flags>
10//!
11//! Líneas que empiezan con `#` o vacías se ignoran.
12
13use std::fs;
14use std::io::Write;
15use std::path::Path;
16use tracing::{info, warn};
17use tracing_subscriber::EnvFilter;
18
19const REGISTER_PATH: &str = "/proc/sys/fs/binfmt_misc/register";
20const SEARCH_DIRS: &[&str] = &[
21    "/usr/lib/binfmt.d",
22    "/etc/binfmt.d",
23    "/run/binfmt.d",
24];
25
26fn main() {
27    init_tracing();
28    info!("ente-binfmt-compat: registrando handlers binfmt_misc");
29
30    if !Path::new(REGISTER_PATH).exists() {
31        warn!(path = REGISTER_PATH, "binfmt_misc no montado — skip");
32        std::process::exit(0);
33    }
34
35    let mut registered = 0;
36    let mut errors = 0;
37    let mut skipped = 0;
38
39    for dir in SEARCH_DIRS {
40        if !Path::new(dir).exists() { continue; }
41        let mut entries: Vec<_> = match fs::read_dir(dir) {
42            Ok(rd) => rd.filter_map(|e| e.ok()).collect(),
43            Err(_) => continue,
44        };
45        entries.sort_by_key(|e| e.file_name());
46        for entry in entries {
47            let path = entry.path();
48            if path.extension().map(|e| e != "conf").unwrap_or(true) { continue; }
49            let content = match fs::read_to_string(&path) {
50                Ok(c) => c,
51                Err(e) => { warn!(?e, path = %path.display(), "read"); continue; }
52            };
53            for line in content.lines() {
54                let line = line.trim();
55                if line.is_empty() || line.starts_with('#') { continue; }
56                match register(line) {
57                    Ok(name) => {
58                        info!(file = %path.display(), %name, "binfmt registrado");
59                        registered += 1;
60                    }
61                    Err(e) => {
62                        if e.is_already_exists() {
63                            skipped += 1;
64                        } else {
65                            warn!(?e, file = %path.display(), "registro falló");
66                            errors += 1;
67                        }
68                    }
69                }
70            }
71        }
72    }
73    info!(registered, skipped, errors, "binfmt aplicado");
74    if errors > 0 { std::process::exit(1); }
75}
76
77#[derive(Debug)]
78struct RegError(std::io::Error);
79impl std::fmt::Display for RegError {
80    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) }
81}
82impl RegError {
83    fn is_already_exists(&self) -> bool {
84        // EEXIST = 17 en Linux.
85        self.0.raw_os_error() == Some(17)
86    }
87}
88
89/// Escribe la línea al register file. Devuelve el `name` extraído del
90/// primer campo (entre `:` separators) si tuvo éxito.
91fn register(line: &str) -> Result<String, RegError> {
92    // Sintaxis: :<name>:<type>:<offset>:<magic>:<mask>:<interpreter>:<flags>
93    // Field 0 (después del ':' inicial) es el name.
94    let name = line.split(':').nth(1)
95        .map(|s| s.to_string())
96        .unwrap_or_else(|| "?".into());
97    let mut f = fs::OpenOptions::new()
98        .write(true)
99        .open(REGISTER_PATH)
100        .map_err(RegError)?;
101    f.write_all(line.as_bytes()).map_err(RegError)?;
102    Ok(name)
103}
104
105fn init_tracing() {
106    let filter = EnvFilter::try_from_default_env()
107        .unwrap_or_else(|_| EnvFilter::new("ente_binfmt_compat=info"));
108    tracing_subscriber::fmt().with_env_filter(filter).with_target(true).init();
109}