tawasuyu

tawasuyu/brahman

Rust 212TOML 64JSON 13HTML 12666 Definiciones

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

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

1//! Absorbe **sysvinit**: parsea `/etc/inittab`.
2
3use std::fs;
4use std::path::Path;
5
6use anyhow::Context;
7
8use crate::model::{split_command, ForeignService, ServiceKind};
9
10/// Lee `<root>/etc/inittab` y devuelve sus servicios.
11///
12/// Formato de cada línea: `id:runlevels:action:process`. Tomamos las
13/// que tienen un `process` real: `respawn` → daemon supervisado;
14/// `wait`/`once`/`boot`/`bootwait`/`sysinit` → one-shot. El resto de
15/// acciones (`initdefault`, `ctrlaltdel`, `power*`, `off`, …) no lanzan
16/// un servicio y se ignoran.
17pub fn absorb(root: &Path) -> anyhow::Result<Vec<ForeignService>> {
18    let path = root.join("etc/inittab");
19    let text =
20        fs::read_to_string(&path).with_context(|| format!("leyendo {}", path.display()))?;
21    let mut out = Vec::new();
22    for raw in text.lines() {
23        let line = raw.trim();
24        if line.is_empty() || line.starts_with('#') {
25            continue;
26        }
27        let mut f = line.splitn(4, ':');
28        let (id, action, process) = match (f.next(), f.next(), f.next(), f.next()) {
29            (Some(id), Some(_rl), Some(action), Some(process)) => (id, action, process),
30            _ => continue, // línea malformada
31        };
32        let kind = match action.trim() {
33            "respawn" => ServiceKind::Daemon,
34            "wait" | "once" | "boot" | "bootwait" | "sysinit" => ServiceKind::OneShot,
35            _ => continue,
36        };
37        // El proceso puede empezar con `+` (sysvinit: no escribir utmp).
38        let process = process.trim().trim_start_matches('+').trim();
39        let Some((exec, argv)) = split_command(process) else {
40            continue;
41        };
42        out.push(ForeignService {
43            name: format!("sysv-{}", id.trim()),
44            exec,
45            argv,
46            env: Vec::new(),
47            kind,
48        });
49    }
50    Ok(out)
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56
57    fn with_inittab(content: &str) -> tempfile::TempDir {
58        let tmp = tempfile::tempdir().unwrap();
59        fs::create_dir_all(tmp.path().join("etc")).unwrap();
60        fs::write(tmp.path().join("etc/inittab"), content).unwrap();
61        tmp
62    }
63
64    #[test]
65    fn parses_respawn_and_oneshot() {
66        let tmp = with_inittab(
67            "# consolas del sistema\n\
68             id:3:initdefault:\n\
69             si::sysinit:/etc/rc.d/rc.sysinit\n\
70             1:2345:respawn:/sbin/agetty 38400 tty1 linux\n\
71             rc::wait:/etc/rc.d/rc 3\n",
72        );
73        let svcs = absorb(tmp.path()).unwrap();
74        // sysinit + respawn + wait — initdefault no cuenta.
75        assert_eq!(svcs.len(), 3);
76        let agetty = svcs.iter().find(|s| s.name == "sysv-1").unwrap();
77        assert_eq!(agetty.kind, ServiceKind::Daemon);
78        assert_eq!(agetty.exec, "/sbin/agetty");
79        assert_eq!(agetty.argv, ["38400", "tty1", "linux"]);
80        let si = svcs.iter().find(|s| s.name == "sysv-si").unwrap();
81        assert_eq!(si.kind, ServiceKind::OneShot);
82    }
83
84    #[test]
85    fn skips_comments_and_blank_lines() {
86        let tmp = with_inittab("\n   \n# sólo comentarios\n");
87        assert!(absorb(tmp.path()).unwrap().is_empty());
88    }
89
90    #[test]
91    fn strips_leading_plus() {
92        let tmp = with_inittab("x:2:respawn:+/sbin/getty tty2\n");
93        let svcs = absorb(tmp.path()).unwrap();
94        assert_eq!(svcs[0].exec, "/sbin/getty");
95    }
96}