kanit

Toy init system
Log | Files | Refs | README | LICENSE

lib.rs (5340B)


      1 #[cfg(feature = "timings")]
      2 use std::fs::File;
      3 #[cfg(not(feature = "testing"))]
      4 use std::io;
      5 #[cfg(feature = "timings")]
      6 use std::io::Write;
      7 #[cfg(not(feature = "testing"))]
      8 use std::os::unix::process::CommandExt;
      9 #[cfg(not(feature = "testing"))]
     10 use std::process::Command;
     11 use std::process::ExitCode;
     12 use std::thread::sleep;
     13 use std::time::Duration;
     14 use std::{env, panic, process};
     15 
     16 #[cfg(not(feature = "testing"))]
     17 use log::LevelFilter;
     18 #[cfg(feature = "timings")]
     19 use log::warn;
     20 use log::{error, info};
     21 use nix::sys::reboot::{RebootMode, reboot, set_cad_enabled};
     22 use nix::sys::signal::{SigSet, Signal, kill};
     23 #[cfg(not(feature = "testing"))]
     24 use nix::sys::utsname::uname;
     25 use nix::unistd::{Pid, sync};
     26 
     27 #[cfg(feature = "baked-rc")]
     28 use baked_rc::*;
     29 #[cfg(not(feature = "testing"))]
     30 use ev_loop::ev_loop;
     31 use kanit_common::constants;
     32 use kanit_common::error::{Context, Result};
     33 #[cfg(not(feature = "testing"))]
     34 use kanit_diagnostics::Colors;
     35 #[cfg(feature = "testing")]
     36 use kanit_diagnostics::tap as kanit_tap;
     37 use kanit_diagnostics::timing as kanit_timing;
     38 #[cfg(feature = "timings")]
     39 use kanit_timing::Scope;
     40 #[cfg(not(feature = "baked-rc"))]
     41 use rc::*;
     42 
     43 #[cfg(feature = "baked-rc")]
     44 mod baked_rc;
     45 #[cfg(not(feature = "testing"))]
     46 mod bsod;
     47 #[cfg(not(feature = "testing"))]
     48 mod ev_loop;
     49 #[cfg(not(feature = "baked-rc"))]
     50 mod rc;
     51 
     52 #[cfg(feature = "timings")]
     53 fn write_scope(file: &mut File, scope: &Scope) -> Result<()> {
     54     let scope_fmt = format!(
     55         "{} {} {}\n",
     56         scope.name,
     57         scope.duration.unwrap_or(Duration::from_secs(0)).as_micros(),
     58         scope.level
     59     );
     60 
     61     file.write(scope_fmt.as_bytes())
     62         .context("failed to write scope")?;
     63     Ok(())
     64 }
     65 
     66 #[cfg(feature = "timings")]
     67 fn write_timing() -> Result<()> {
     68     let mut file = File::create(constants::KAN_TIMINGS).context("failed to open times file")?;
     69 
     70     for scope in kanit_timing::get_scopes().iter() {
     71         write_scope(&mut file, scope)?;
     72     }
     73 
     74     Ok(())
     75 }
     76 
     77 fn initialize() -> Result<()> {
     78     let id = kanit_timing::push_scope("initialize");
     79 
     80     #[cfg(not(feature = "testing"))]
     81     let platform = uname()
     82         .map(|u| {
     83             format!(
     84                 "{} {} ({})",
     85                 u.sysname().to_string_lossy(),
     86                 u.release().to_string_lossy(),
     87                 u.machine().to_string_lossy()
     88             )
     89         })
     90         .unwrap_or_else(|_| "unknown".to_string());
     91 
     92     #[cfg(not(feature = "testing"))]
     93     info!(
     94         "== Kanit {}{}{} on {}{}{} ==\n",
     95         Colors::BrightGreen,
     96         constants::KAN_VERSION,
     97         Colors::reset(),
     98         Colors::BrightMagenta,
     99         platform,
    100         Colors::reset()
    101     );
    102 
    103     let mut set = SigSet::all();
    104     set.remove(Signal::SIGCHLD);
    105 
    106     set.thread_block().context("failed to block signals")?;
    107 
    108     set_cad_enabled(false).context("failed to ignore CAD")?;
    109 
    110     // SAFETY: single threaded
    111     unsafe {
    112         env::set_var("PATH", constants::KAN_PATH);
    113     }
    114 
    115     initialize_rc()?;
    116 
    117     kanit_timing::pop_scope(id);
    118 
    119     #[cfg(feature = "timings")]
    120     if let Err(e) = write_timing() {
    121         warn!("failed to write timings: {e}");
    122     };
    123 
    124     Ok(())
    125 }
    126 
    127 pub fn teardown(cmd: Option<RebootMode>) -> Result<()> {
    128     teardown_rc()?;
    129 
    130     info!("terminating remaining processes");
    131 
    132     kill(Pid::from_raw(-1), Signal::SIGTERM).context("failed to terminate all processes")?;
    133 
    134     sleep(Duration::from_secs(3));
    135 
    136     info!("killing remaining processes");
    137 
    138     kill(Pid::from_raw(-1), Signal::SIGKILL).context("failed to kill all processes")?;
    139 
    140     sync();
    141 
    142     // reboot will always return an error
    143     if let Some(cmd) = cmd {
    144         let _ = reboot(cmd);
    145     }
    146 
    147     Ok(())
    148 }
    149 
    150 #[cfg(feature = "testing")]
    151 fn failure_handle() {
    152     kanit_tap::bail::<&str>(None);
    153     let _ = reboot(RebootMode::RB_POWER_OFF);
    154 }
    155 
    156 #[cfg(not(feature = "testing"))]
    157 fn failure_handle() {
    158     error!("dropping into emergency shell");
    159 
    160     let _ = Command::new("sh").exec();
    161 
    162     error!("failed to drop into emergency shell; good luck o7");
    163 
    164     loop {
    165         // kernel panic if this loop doesn't exist
    166         sleep(Duration::from_secs(1));
    167     }
    168 }
    169 
    170 pub fn handle_cli() -> ExitCode {
    171     if process::id() != 1 {
    172         eprintln!("init must be ran as PID 1");
    173         return ExitCode::FAILURE;
    174     }
    175 
    176     #[cfg(feature = "testing")]
    177     panic::set_hook(Box::new(|info| {
    178         kanit_tap::bail(info.payload().downcast_ref::<&str>());
    179         let _ = reboot(RebootMode::RB_POWER_OFF);
    180     }));
    181 
    182     #[cfg(not(feature = "testing"))]
    183     panic::set_hook(Box::new(|info| {
    184         error!("panic detected; tearing down");
    185         let _ = teardown(None); // attempt a teardown
    186         bsod::bsod(info);
    187     }));
    188 
    189     kanit_diagnostics::tap::header();
    190 
    191     #[cfg(not(feature = "testing"))]
    192     if let Err(e) = kanit_diagnostics::InitializationLogger::init(LevelFilter::Trace) {
    193         let _ = write!(&mut io::stdout().lock(), "{e}");
    194     }
    195 
    196     kanit_timing::register();
    197 
    198     if let Err(e) = initialize() {
    199         error!("failed to initialize: {e}");
    200         failure_handle();
    201     }
    202 
    203     #[cfg(not(feature = "testing"))] // no way to test yet
    204     if let Err(e) = ev_loop() {
    205         error!("event loop failed: {e}");
    206         failure_handle();
    207     }
    208 
    209     #[cfg(feature = "testing")]
    210     teardown(Some(RebootMode::RB_POWER_OFF)).expect("failed to unwrap");
    211 
    212     ExitCode::SUCCESS
    213 }