pipette/
init.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! PID 1 init mode for pipette.
5//!
6//! When pipette runs as PID 1 (e.g., via `rdinit=/pipette` in the kernel
7//! cmdline), it performs minimal init duties (mount filesystems), then
8//! forks. The child runs the normal pipette agent. PID 1 stays in a
9//! simple `wait()` loop, reaping all children (including orphans adopted
10//! by PID 1). When the pipette child exits, PID 1 calls `reboot(2)`.
11//!
12//! This fork-based design avoids conflicts between PID 1's reaping
13//! duties and the pipette execute handler's `Child::wait()` calls —
14//! PID 1 never runs pipette logic, so there's no race between
15//! `waitpid(-1)` and `waitpid(specific_pid)`.
16
17// UNSAFETY: Required for libc calls (fork, mount, reboot, waitpid).
18#![expect(unsafe_code)]
19
20use std::ffi::CString;
21use std::io;
22use std::sync::atomic::AtomicBool;
23use std::sync::atomic::Ordering;
24
25static FORKED_FROM_PID1: AtomicBool = AtomicBool::new(false);
26
27/// Returns `true` if this process is running as PID 1.
28pub fn is_pid1() -> bool {
29    std::process::id() == 1
30}
31
32/// Returns `true` if this process was forked from PID 1 and should
33/// use `reboot(2)` directly for shutdown.
34pub fn was_forked_from_pid1() -> bool {
35    FORKED_FROM_PID1.load(Ordering::Relaxed)
36}
37
38/// Perform minimal init duties and fork.
39///
40/// Mounts `/dev`, `/proc`, `/sys`, then forks. Returns `Ok(())` in the
41/// child (which should continue to run the pipette agent). Never returns
42/// in the parent (PID 1 stays in a reap loop until the child exits,
43/// then calls `reboot(2)` to power off).
44pub fn init_as_pid1() -> anyhow::Result<()> {
45    eprintln!("Pipette running as PID 1, performing init duties");
46
47    // Mount essential filesystems
48    mount_or_warn("devtmpfs", "/dev", "devtmpfs");
49    mount_or_warn("proc", "/proc", "proc");
50    mount_or_warn("sysfs", "/sys", "sysfs");
51
52    // Fork: child runs pipette, parent stays as PID 1 reaper.
53    // SAFETY: fork() is safe to call here. We have not spawned any
54    // threads yet, so the forked child has a consistent address space.
55    let pid = unsafe { libc::fork() };
56    match pid {
57        -1 => {
58            anyhow::bail!("fork() failed: {}", io::Error::last_os_error());
59        }
60        0 => {
61            // Child: set flag so shutdown handler uses reboot(2),
62            // then return to caller to run the normal pipette agent.
63            FORKED_FROM_PID1.store(true, Ordering::Relaxed);
64            Ok(())
65        }
66        child_pid => {
67            // Parent (PID 1): reap loop — never returns.
68            eprintln!("Pipette PID 1: forked child {child_pid}, entering reap loop");
69            pid1_reap_loop(child_pid);
70        }
71    }
72}
73
74/// PID 1 reap loop: wait for children forever.
75///
76/// Under normal operation, pipette does not exit — the host tears
77/// down the VM directly. If the pipette child does exit unexpectedly,
78/// we panic, which will cause the kernel to panic and the VMM to
79/// observe a triple fault.
80fn pid1_reap_loop(pipette_pid: i32) -> ! {
81    loop {
82        let mut status: i32 = 0;
83        // SAFETY: wait() is safe to call with a valid pointer to status.
84        let pid = unsafe { libc::wait(&mut status) };
85
86        // wait() can only fail with ECHILD (no children) or EINTR
87        // (interrupted by signal). ECHILD is impossible because PID 1
88        // always has the pipette child, and adopted orphans keep the
89        // child count nonzero until the pipette child exits. EINTR
90        // just means we should retry.
91        if pid == -1 {
92            let err = io::Error::last_os_error();
93            assert!(
94                err.raw_os_error() == Some(libc::EINTR),
95                "wait() failed unexpectedly: {err}"
96            );
97            continue;
98        }
99
100        if pid == pipette_pid {
101            let exit_code = if libc::WIFEXITED(status) {
102                libc::WEXITSTATUS(status)
103            } else {
104                -1
105            };
106            panic!("pipette child {pipette_pid} exited unexpectedly (status {exit_code})");
107        }
108        // Any other child — just reaped an orphan, continue looping.
109    }
110}
111
112fn mount_or_warn(source: &str, target: &str, fstype: &str) {
113    if let Err(e) = mount(source, target, fstype) {
114        eprintln!("warning: failed to mount {fstype} on {target}: {e}");
115    }
116}
117
118fn mount(source: &str, target: &str, fstype: &str) -> io::Result<()> {
119    // Create mount point if it doesn't exist
120    let _ = std::fs::create_dir_all(target);
121
122    let source = CString::new(source).unwrap();
123    let target = CString::new(target).unwrap();
124    let fstype = CString::new(fstype).unwrap();
125
126    // SAFETY: calling libc::mount with valid C string pointers and
127    // no mount data. The arguments are constructed from Rust &str
128    // values via CString::new above.
129    let ret = unsafe {
130        libc::mount(
131            source.as_ptr(),
132            target.as_ptr(),
133            fstype.as_ptr(),
134            0,
135            std::ptr::null(),
136        )
137    };
138
139    if ret == 0 {
140        Ok(())
141    } else {
142        Err(io::Error::last_os_error())
143    }
144}