gioser

tawasuyu/brahman

Rust 534TOML 203Markdown 58JSON 197690 Definiciones

⎇ 762bf95dfd770cc8bf51f21605bc2cc6cc88ea76 crates/init/arje-absorb/src/dinit.rs Rust · 105 líneas · 3.4 KB Historia Quién tocó qué crudo

feat(arje): arje-absorb — absorbe otros inits a una Semilla brahman 762bf95df · sergio ·

1//! Absorbe **dinit**: parsea los archivos de `/etc/dinit.d`.
2
3use std::fs;
4use std::path::Path;
5
6use crate::model::{split_command, ForeignService, ServiceKind};
7
8/// Descubre los servicios dinit de `<root>/etc/dinit.d`.
9///
10/// Cada archivo es un servicio con líneas `clave = valor`. Absorbemos
11/// los de `type` = `process`/`bgprocess` (daemon) y `scripted`
12/// (one-shot) que declaren un `command`. `internal`/`triggered` no
13/// encarnan un proceso y se omiten — entre ellos el servicio `boot`.
14pub fn absorb(root: &Path) -> anyhow::Result<Vec<ForeignService>> {
15    let dir = root.join("etc/dinit.d");
16    anyhow::ensure!(
17        dir.is_dir(),
18        "no encontré /etc/dinit.d en {}",
19        root.display()
20    );
21    let mut names: Vec<String> = fs::read_dir(&dir)?
22        .flatten()
23        .filter(|e| e.file_type().map(|t| t.is_file()).unwrap_or(false))
24        .map(|e| e.file_name().to_string_lossy().into_owned())
25        .filter(|n| !n.starts_with('.'))
26        .collect();
27    names.sort();
28
29    let mut out = Vec::new();
30    for name in names {
31        let text = fs::read_to_string(dir.join(&name))?;
32        if let Some(svc) = parse_service(&name, &text) {
33            out.push(svc);
34        }
35    }
36    Ok(out)
37}
38
39/// Parsea un archivo de servicio dinit. `None` si no encarna un proceso.
40fn parse_service(name: &str, text: &str) -> Option<ForeignService> {
41    let mut ty = String::new();
42    let mut command = String::new();
43    for raw in text.lines() {
44        let line = raw.trim();
45        if line.is_empty() || line.starts_with('#') {
46            continue;
47        }
48        let Some((key, val)) = line.split_once('=') else {
49            continue;
50        };
51        match key.trim() {
52            "type" => ty = val.trim().to_string(),
53            "command" => command = val.trim().to_string(),
54            _ => {}
55        }
56    }
57    let kind = match ty.as_str() {
58        "process" | "bgprocess" => ServiceKind::Daemon,
59        "scripted" => ServiceKind::OneShot,
60        _ => return None, // internal, triggered, o sin `type`
61    };
62    let (exec, argv) = split_command(&command)?;
63    Some(ForeignService {
64        name: format!("dinit-{name}"),
65        exec,
66        argv,
67        env: Vec::new(),
68        kind,
69    })
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[test]
77    fn absorbs_process_and_scripted_skips_internal() {
78        let tmp = tempfile::tempdir().unwrap();
79        let d = tmp.path().join("etc/dinit.d");
80        fs::create_dir_all(&d).unwrap();
81        fs::write(d.join("boot"), "type = internal\nwaits-for = sshd\n").unwrap();
82        fs::write(
83            d.join("sshd"),
84            "# servicio ssh\ntype = process\ncommand = /usr/sbin/sshd -D\nrestart = true\n",
85        )
86        .unwrap();
87        fs::write(d.join("fsck"), "type = scripted\ncommand = /sbin/fsck -A\n").unwrap();
88
89        let svcs = absorb(tmp.path()).unwrap();
90        // sshd + fsck — boot (internal) se omite.
91        assert_eq!(svcs.len(), 2);
92        let sshd = svcs.iter().find(|s| s.name == "dinit-sshd").unwrap();
93        assert_eq!(sshd.kind, ServiceKind::Daemon);
94        assert_eq!(sshd.exec, "/usr/sbin/sshd");
95        assert_eq!(sshd.argv, ["-D"]);
96        let fsck = svcs.iter().find(|s| s.name == "dinit-fsck").unwrap();
97        assert_eq!(fsck.kind, ServiceKind::OneShot);
98    }
99
100    #[test]
101    fn errors_without_dinit_d() {
102        let tmp = tempfile::tempdir().unwrap();
103        assert!(absorb(tmp.path()).is_err());
104    }
105}