⎇ 762bf95dfd770cc8bf51f21605bc2cc6cc88ea76
crates/init/arje-absorb/src/dinit.rs
Ñawpa willay
Pi imata llamkarqan
ch'uya
feat(arje): arje-absorb — absorbe otros inits a una Semilla brahman
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}