From 6a63e7e777fb9182ff5b68aa2167bd5f2e548f0e Mon Sep 17 00:00:00 2001 From: namilsk Date: Mon, 2 Feb 2026 00:19:48 +0300 Subject: Implemented mainloop functions (e.g. restarting services), autofmt all --- init/src/host/locale.rs | 12 +- init/src/host/timezone.rs | 2 +- init/src/main.rs | 12 +- init/src/services/mod.rs | 2 +- init/src/services/unit_parser.rs | 166 ---------------------- init/src/services/units.rs | 293 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 309 insertions(+), 178 deletions(-) delete mode 100644 init/src/services/unit_parser.rs create mode 100644 init/src/services/units.rs (limited to 'init') diff --git a/init/src/host/locale.rs b/init/src/host/locale.rs index d955297..3d9b11d 100644 --- a/init/src/host/locale.rs +++ b/init/src/host/locale.rs @@ -1,6 +1,6 @@ // NOTE: Ts file contains setting localisation by environment variables // Main logic implemented but how i think it can contain with broken logic or checks -// At least if check failed it set C.UTF8 (fallback locale) +// At least if check failed it set C.UTF8 (fallback locale) // And why is std::env::set_var unsafe???? use crate::host::timezone::set_timezone; @@ -10,13 +10,13 @@ use std::io::Write; use std::process::Command; /// Main function which setting system locale. -/// +/// /// Logic && Checks -/// 1. Reading /etc/default/locale to find needed language. +/// 1. Reading /etc/default/locale to find needed language. /// If it broken/has syntax errors skipping this step -/// 2. Checking for locale avalible (also switching `-` and `_`), -/// if not uses fallback locale. -/// +/// 2. Checking for locale avalible (also switching `-` and `_`), +/// if not uses fallback locale. +/// pub fn set_locale(locale: Option) -> Result<(), Box> { let loc = match locale { Some(l) => l, diff --git a/init/src/host/timezone.rs b/init/src/host/timezone.rs index 1062a8b..2da9e85 100644 --- a/init/src/host/timezone.rs +++ b/init/src/host/timezone.rs @@ -1,6 +1,6 @@ use crate::log::*; use std::fs; - + pub fn set_timezone(timezone: Option) -> Result<(), Box> { let tz = match timezone { Some(tz) => tz, diff --git a/init/src/main.rs b/init/src/main.rs index 40e47a4..fa7d086 100644 --- a/init/src/main.rs +++ b/init/src/main.rs @@ -4,8 +4,8 @@ mod log; mod mounts; mod pid_one; mod processes; -mod signals; mod services; +mod signals; use crate::host::locale; use crate::host::set::set_hostname; @@ -15,17 +15,19 @@ use crate::mounts::fstab::FstabEntry; use crate::mounts::rescue; use crate::pid_one::check_pid; use crate::processes::udev::spawn_udev; -use crate::services::unit_parser::Runlevel; +use crate::services::units::{Runlevel, services_mainloop}; use crate::signals::sigchld; // RULE: I will not use .expect() and .unwrap() in this project. This causes panic, // which will affect stability. -use std::sync::Mutex; +use std::sync::Mutex; +use std::thread; /// Variable which show current runlevel; -pub static mut RUNLEVEL_STATE: Mutex = Mutex::new(Runlevel::Undefined); +pub static RUNLEVEL_STATE: Mutex = Mutex::new(Runlevel::Undefined); +// TODO: Add proper RUNLEVEL_STATE switching fn main() -> Result<(), Box> { println!("Initializing your system."); if let Err(e) = check_pid() { @@ -77,5 +79,7 @@ fn main() -> Result<(), Box> { let _ = sigchld::setup_sigchld_handler(); + thread::spawn(move || services_mainloop()); + Ok(()) } diff --git a/init/src/services/mod.rs b/init/src/services/mod.rs index bf86a6a..f16e62e 100644 --- a/init/src/services/mod.rs +++ b/init/src/services/mod.rs @@ -1 +1 @@ -pub mod unit_parser; +pub mod units; diff --git a/init/src/services/unit_parser.rs b/init/src/services/unit_parser.rs deleted file mode 100644 index 1e2bcc7..0000000 --- a/init/src/services/unit_parser.rs +++ /dev/null @@ -1,166 +0,0 @@ -// This module defines unit settings parsing logig -// -// NOTE: ON UNIT PARSING LOGIC -// when parsing a unit in /etc/vigil/units/ we shoul ignore -// ANY broken/bad constructed toml unit configuration file -// without "crashing" logic. -// -// NOTE: ON GENERAL SERVICE LOGGING -// when vigil starts service, it should send out logs to vigil? -// or logs in such init systems are just taking the stdout+stderr -// of service and showing its output? idk 4 now, look into how its supposed to be - -use crate::{log_warning, RUNLEVEL_STATE}; -use serde::Deserialize; -use std::fs::{read_dir, read_to_string}; - -/* - [info] - name = "name" - description = "description" - version = "version" - - [config] - exec = "path to exec" - runlevel = - restart=always|never - -*/ - -#[derive(Deserialize)] -enum Restart { - #[serde(alias = "always")] - Always, - #[serde(alias = "never")] - Never, -} - -#[derive(Deserialize, PartialEq, Eq)] -pub enum Runlevel { - /// The system is shutting down, runlevel int: 0 - Shutdown, - /// One-user system debug-mode, runlevel int: 1 - OneUser, - /// Multi-user CLI (TTY) with no network, runlevel int: 2 - MultiNoNetwork, - /// Multi-user CLI with network, runlevel int: 3 - MultiNetwork, - /// Multi-user mode with GUI, runlevel int: 5 - MultiUserGUINetwork, - /// Runlevel is not critical for running the service, runlevel int: 4 - Undefined, - /// System going too reboot, runlevel int: 6 - Reboot, -} - -#[allow(dead_code)] -#[derive(Deserialize)] -pub struct ServiceInfo { - /// Service name - name: String, - /// Service description - description: String, - /// Service version - version: String, -} - -#[allow(dead_code)] -#[derive(Deserialize)] -pub struct ServiceConfig { - /// Execution command, like ExecStart in sysd - exec: String, - /// Runlevel, like after=*.target - runlevel: Runlevel, - /// Restart service: Always|Never - restart: Restart, -} - -/// Main Unit-file struct -#[allow(dead_code)] -#[derive(Deserialize)] -pub struct Unit { - info: ServiceInfo, - config: ServiceConfig, -} - -#[allow(dead_code)] -impl Restart { - pub fn as_str(&self) -> &'static str { - match self { - Restart::Always => "always", - Restart::Never => "never", - } - } -} - -/// Function which starting services declared in `/etc/vigil/units/` -/// Based on the global runlevel variable, should be runned as second thread -pub fn execute_services() -> Result<(), Box> { - let unit_list = parse_all_units()?; - - loop { - match *RUNLEVEL_STATE.try_lock()? { - Runlevel::Undefined => { - for unit in &unit_list { - if unit.config.runlevel == Runlevel::Undefined { - std::process::Command::new(unit.config.exec.clone()); - } else { - continue; - } - } - } - Runlevel::Reboot => { - todo!(); - } - - Runlevel::Shutdown => { - todo!(); - } - - Runlevel::OneUser => { - todo!(); - } - - Runlevel::MultiNoNetwork => { - todo!(); - } - - Runlevel::MultiNetwork => { - todo!(); - } - - Runlevel::MultiUserGUINetwork => { - todo!(); - } - } - } -} - -#[allow(dead_code)] -fn parse_all_units() -> Result, Box> { - let mut units: Vec = Vec::new(); - - for unit in read_dir("/etc/vigil/units")? { - let unit_path = unit?.path(); - let unit_str: String = match read_to_string(unit_path) { - Ok(content) => content, - Err(e) => { - log_warning(&format!("Error while reading unit: {}", e)); - continue; - } - }; - - let deserialized: Result = toml::from_str(&unit_str); - match deserialized { - Ok(unit) => { - units.push(unit); - } - Err(e) => { - log_warning(&format!("Error while parsing unit: {}", e)); - continue; - } - }; - } - - Ok(units) -} diff --git a/init/src/services/units.rs b/init/src/services/units.rs new file mode 100644 index 0000000..01c4183 --- /dev/null +++ b/init/src/services/units.rs @@ -0,0 +1,293 @@ +// This module defines unit settings parsing logig +// +// NOTE: ON UNIT PARSING LOGIC +// when parsing a unit in /etc/vigil/units/ we shoul ignore +// ANY broken/bad constructed toml unit configuration file +// without "crashing" logic. +// +// NOTE: ON GENERAL SERVICE LOGGING +// when vigil starts service, it should send out logs to vigil? +// or logs in such init systems are just taking the stdout+stderr +// of service and showing its output? idk 4 now, look into how its supposed to be + +use crate::{RUNLEVEL_STATE, log_warning}; +use serde::Deserialize; +use std::{ + fs::{read_dir, read_to_string}, + process::Child, + time::Duration, +}; + +/* + [info] + name = "name" + description = "description" + version = "version" + + [config] + exec = "path to exec" + runlevel = + restart=always|never + +*/ + +#[derive(Deserialize, PartialEq, Clone)] +enum Restart { + #[serde(alias = "always")] + Always, + #[serde(alias = "never")] + Never, +} + +#[derive(Deserialize, PartialEq, Eq)] +pub enum Runlevel { + /// The system is shutting down, runlevel int: 0 + Shutdown, + /// One-user system debug-mode, runlevel int: 1 + OneUser, + /// Multi-user CLI (TTY) with no network, runlevel int: 2 + MultiNoNetwork, + /// Multi-user CLI with network, runlevel int: 3 + MultiNetwork, + /// Multi-user mode with GUI, runlevel int: 5 + MultiGUINetwork, + /// Runlevel is not critical for running the service, runlevel int: 4 + Undefined, + /// System going too reboot, runlevel int: 6 + Reboot, +} + +#[allow(dead_code)] +#[derive(Deserialize)] +pub struct ServiceInfo { + /// Service name + name: String, + /// Service description + description: String, + /// Service version + version: String, +} + +#[allow(dead_code)] +#[derive(Deserialize)] +pub struct ServiceConfig { + /// Execution command, like ExecStart in sysd + exec: String, + /// Runlevel, like after=*.target + runlevel: Runlevel, + /// Restart service: Always|Never + restart: Restart, +} + +/// Main Unit-file struct +#[allow(dead_code)] +#[derive(Deserialize)] +pub struct Unit { + info: ServiceInfo, + config: ServiceConfig, +} + +#[allow(dead_code)] +impl Restart { + pub fn as_str(&self) -> &'static str { + match self { + Restart::Always => "always", + Restart::Never => "never", + } + } +} + +/// Function: +/// 1. starting services declared in `/etc/vigil/units/` +/// 2. checks services for dropping and if ts true to restarting it +/// max restart count: 3 +/// +/// Based on the global runlevel variable, should be runned as second thread +// TODO: More logs && better errors processing && fix clippy warnings +pub fn services_mainloop() -> Result<(), Box> { + let unit_list = parse_all_units()?; + let mut pids: Vec<(Child, String, Restart, u8)> = vec![]; + + loop { + match *RUNLEVEL_STATE.lock().unwrap() { + Runlevel::Undefined => { + for unit in &unit_list { + if unit.config.runlevel == Runlevel::Undefined { + let child = std::process::Command::new(unit.config.exec.clone()) + .spawn() + .map_err(|e| Box::new(e) as Box)?; + pids.push(( + child, + unit.config.exec.clone(), + unit.config.restart.clone(), + 0, + )); + } + } + } + + Runlevel::Reboot => { + for unit in &unit_list { + if unit.config.runlevel == Runlevel::Reboot { + let child = std::process::Command::new(unit.config.exec.clone()) + .spawn() + .map_err(|e| Box::new(e) as Box)?; + pids.push(( + child, + unit.config.exec.clone(), + unit.config.restart.clone(), + 0, + )); + } + } + } + + Runlevel::Shutdown => { + for unit in &unit_list { + if unit.config.runlevel == Runlevel::Shutdown { + let child = std::process::Command::new(unit.config.exec.clone()) + .spawn() + .map_err(|e| Box::new(e) as Box)?; + pids.push(( + child, + unit.config.exec.clone(), + unit.config.restart.clone(), + 0, + )); + } + } + } + + Runlevel::OneUser => { + for unit in &unit_list { + if unit.config.runlevel == Runlevel::OneUser { + let child = std::process::Command::new(unit.config.exec.clone()) + .spawn() + .map_err(|e| Box::new(e) as Box)?; + pids.push(( + child, + unit.config.exec.clone(), + unit.config.restart.clone(), + 0, + )); + } + } + } + + Runlevel::MultiNoNetwork => { + for unit in &unit_list { + if unit.config.runlevel == Runlevel::MultiNoNetwork { + let child = std::process::Command::new(unit.config.exec.clone()) + .spawn() + .map_err(|e| Box::new(e) as Box)?; + pids.push(( + child, + unit.config.exec.clone(), + unit.config.restart.clone(), + 0, + )); + } + } + } + + Runlevel::MultiNetwork => { + for unit in &unit_list { + if unit.config.runlevel == Runlevel::MultiNetwork { + let child = std::process::Command::new(unit.config.exec.clone()) + .spawn() + .map_err(|e| Box::new(e) as Box)?; + pids.push(( + child, + unit.config.exec.clone(), + unit.config.restart.clone(), + 0, + )); + } + } + } + + Runlevel::MultiGUINetwork => { + for unit in &unit_list { + if unit.config.runlevel == Runlevel::MultiGUINetwork { + let child = std::process::Command::new(unit.config.exec.clone()) + .spawn() + .map_err(|e| Box::new(e) as Box)?; + pids.push(( + child, + unit.config.exec.clone(), + unit.config.restart.clone(), + 0, + )); + } + } + } + } + + for i in 0..pids.len() { + match pids[i].0.try_wait() { + Ok(Some(status)) => { + if pids[i].2 == Restart::Always && pids[i].3 < 3 { + let new_child = std::process::Command::new(pids[i].1.clone()) + .spawn() + .map_err(|e| Box::new(e) as Box)?; + log_warning(&format!( + "One of units dropped with Error {};\n Restarting it. Attempt: {}", + status, + pids[i].3 + 1 + )); + + pids.push(( + new_child, + pids[i].1.clone(), + pids[i].2.clone(), + pids[i].3 + 1, + )); + pids.remove(i); + } else { + log_warning(&format!( + "Unit {} has reached maximum restart attempts or restart policy is not Always, not restarting.", + pids[i].1 + )); + continue; + } + } + Ok(None) => continue, + Err(e) => log_warning(&format!("Error occurred while checking services: {}", e)), + } + } + + std::thread::sleep(Duration::from_secs(15)); + } +} + +fn parse_all_units() -> Result, Box> { + let mut units: Vec = Vec::new(); + + for unit in read_dir("/etc/vigil/units") + .map_err(|e| Box::new(e) as Box)? + { + let unit_path = unit + .map_err(|e| Box::new(e) as Box)? + .path(); + let unit_str: String = match read_to_string(unit_path) { + Ok(content) => content, + Err(e) => { + log_warning(&format!("Error while reading unit: {}", e)); + continue; + } + }; + + let deserialized: Result = toml::from_str(&unit_str); + match deserialized { + Ok(unit) => { + units.push(unit); + } + Err(e) => { + log_warning(&format!("Error while parsing unit: {}", e)); + continue; + } + }; + } + + Ok(units) +} -- cgit v1.2.3