gioser

tawasuyu/brahman

Rust 528TOML 203Markdown 58JSON 197560 Definitions

⎇ d1b700eb2bda9fa5f6f913d36f6ce1fb1e6d290b crates/init/arje-kernel/src/surface.rs Rust · 118 lines · 4.8 KB History Who touched what raw

fix(init): el reboot-loop de Fedora — remount rw + /run tmpfs + shell de rescate d1b700eb2 · sergio ·

1//! Bootstrap del entorno kernel para PID 1: remonta `/` rw, monta
2//! procfs/sysfs/devtmpfs/cgroup2 y las superficies escribibles volátiles
3//! (`/run`, `/tmp`, `/dev/pts`, `/dev/shm`), y registra al proceso como
4//! subreaper para adoptar huérfanos.
5//!
6//! Idempotente: si un punto de montaje ya existe (lo montó el initramfs),
7//! el segundo mount falla con EBUSY y simplemente se ignora.
8//!
9//! **Por qué importa `/run`:** el cmdline de arranque suele traer `ro`
10//! (systemd remonta rw temprano; nosotros también debemos). Sin remontar
11//! `/` y sin `/run` como tmpfs, crear el socket del bus interno falla con
12//! EROFS — y PID 1 moriría, provocando un kernel panic. Esta función es
13//! infalible a propósito: devuelve `Ok` siempre y sólo loggea los fallos.
14
15use nix::mount::{mount, MsFlags};
16use tracing::{debug, warn};
17
18/// Prepara el entorno del kernel para PID 1. Nunca falla de forma dura:
19/// cada paso es best-effort y los problemas se loggean, porque un `Err`
20/// que llegue hasta `main` terminaría PID 1.
21pub fn bootstrap_kernel_surface() -> anyhow::Result<()> {
22    // 1) Remontar `/` lectura-escritura. El cmdline casi siempre trae
23    //    `ro`; sin esto el resto del sistema queda de sólo lectura.
24    if let Err(e) = mount::<str, str, str, str>(
25        None, "/", None, MsFlags::MS_REMOUNT, None,
26    ) {
27        warn!(?e, "remount / rw falló — el sistema puede quedar de sólo lectura");
28    }
29
30    // 2) Pseudo-filesystems del kernel. NOSUID/NOEXEC/NODEV donde aplica.
31    let pseudo: [(&str, &str, &str, MsFlags); 4] = [
32        ("proc", "/proc", "proc",
33            MsFlags::MS_NOSUID.union(MsFlags::MS_NOEXEC).union(MsFlags::MS_NODEV)),
34        ("sysfs", "/sys", "sysfs",
35            MsFlags::MS_NOSUID.union(MsFlags::MS_NOEXEC).union(MsFlags::MS_NODEV)),
36        ("devtmpfs", "/dev", "devtmpfs", MsFlags::MS_NOSUID),
37        ("cgroup2", "/sys/fs/cgroup", "cgroup2",
38            MsFlags::MS_NOSUID.union(MsFlags::MS_NOEXEC).union(MsFlags::MS_NODEV)),
39    ];
40    for (src, dst, fstype, flags) in pseudo {
41        let _ = mount::<str, str, str, str>(Some(src), dst, Some(fstype), flags, None);
42    }
43
44    // 3) Superficies escribibles volátiles. `/run` como tmpfs es lo que
45    //    permite crear el socket del bus interno aun con `/` de sólo
46    //    lectura. `mkdir` best-effort antes de cada montaje.
47    let volatile: [(&str, &str, &str, MsFlags, &str); 4] = [
48        ("tmpfs", "/run", "tmpfs",
49            MsFlags::MS_NOSUID.union(MsFlags::MS_NODEV), "mode=0755"),
50        ("tmpfs", "/tmp", "tmpfs",
51            MsFlags::MS_NOSUID.union(MsFlags::MS_NODEV), "mode=1777"),
52        ("devpts", "/dev/pts", "devpts",
53            MsFlags::MS_NOSUID.union(MsFlags::MS_NOEXEC), "mode=0620,gid=5"),
54        ("tmpfs", "/dev/shm", "tmpfs",
55            MsFlags::MS_NOSUID.union(MsFlags::MS_NODEV), "mode=1777"),
56    ];
57    for (src, dst, fstype, flags, data) in volatile {
58        let _ = std::fs::create_dir_all(dst);
59        let _ = mount::<str, str, str, str>(Some(src), dst, Some(fstype), flags, Some(data));
60    }
61    let _ = std::fs::create_dir_all("/run/lock");
62
63    debug!("kernel surface bootstrap completo");
64    Ok(())
65}
66
67/// PR_SET_CHILD_SUBREAPER: que adoptemos huérfanos del fractal.
68///
69/// En PID 1 esto es redundante (el kernel ya lo hace), pero se deja explícito
70/// para que ente-zero corriendo como sub-init en un container mantenga la
71/// misma semántica.
72pub fn become_child_subreaper() -> anyhow::Result<()> {
73    let r = unsafe { libc::prctl(libc::PR_SET_CHILD_SUBREAPER, 1u64, 0u64, 0u64, 0u64) };
74    if r != 0 {
75        anyhow::bail!(
76            "prctl PR_SET_CHILD_SUBREAPER falló: {}",
77            std::io::Error::last_os_error()
78        );
79    }
80    Ok(())
81}
82
83/// Cosechar zombis hasta vaciar la cola de niños muertos. Devuelve los
84/// PIDs cosechados con su estado, como tuplas.
85pub fn reap_all() -> Vec<ReapedChild> {
86    use nix::errno::Errno;
87    use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus};
88    let mut out = Vec::new();
89    loop {
90        match waitpid(None, Some(WaitPidFlag::WNOHANG)) {
91            Ok(WaitStatus::Exited(pid, code)) => {
92                out.push(ReapedChild { pid: pid.as_raw(), status: ReapStatus::Exited(code) });
93            }
94            Ok(WaitStatus::Signaled(pid, sig, _core)) => {
95                out.push(ReapedChild { pid: pid.as_raw(), status: ReapStatus::Signaled(sig as i32) });
96            }
97            Ok(WaitStatus::StillAlive) => return out,
98            Err(Errno::ECHILD) => return out,
99            Err(_) => return out,
100            Ok(_) => continue, // Stopped/Continued — irrelevantes
101        }
102    }
103    // unreachable, satisface al borrow checker
104    #[allow(unreachable_code)]
105    out
106}
107
108#[derive(Debug, Clone)]
109pub struct ReapedChild {
110    pub pid: i32,
111    pub status: ReapStatus,
112}
113
114#[derive(Debug, Clone)]
115pub enum ReapStatus {
116    Exited(i32),
117    Signaled(i32),
118}