init: Introduce UnitStore

This will be necessary to store persistent information about units like
if they are currently started and which process is associated with them.
This commit is contained in:
bjorn3
2026-02-25 22:06:25 +01:00
parent 73cc0b0a07
commit 6fcb07e7dd
7 changed files with 179 additions and 77 deletions
+3 -1
View File
@@ -1,5 +1,7 @@
{
"unit": {},
"unit": {
"description": "Set time from realtime clock"
},
"service": {
"cmd": "rtcd",
"type": "oneshot"
-1
View File
@@ -1 +0,0 @@
switchroot /usr /etc
+7
View File
@@ -0,0 +1,7 @@
{
"unit": {},
"switchroot": {
"prefix": "/usr",
"etcdir": "/etc"
}
}
+64 -40
View File
@@ -1,13 +1,14 @@
use std::collections::BTreeMap;
use std::env;
use std::ffi::OsString;
use std::io::Result;
use std::path::Path;
use std::path::{Path, PathBuf};
use std::{env, mem};
use libredox::flag::{O_RDONLY, O_WRONLY};
use serde::Deserialize;
use crate::script::Command;
use crate::unit::Unit;
use crate::unit::{UnitId, UnitStore};
mod script;
mod service;
@@ -48,42 +49,51 @@ impl InitConfig {
}
}
fn switch_root(prefix: &Path, etcdir: &Path, config: &mut InitConfig) {
config
.envs
.insert("PATH".to_owned(), prefix.join("bin").into_os_string());
config.envs.insert(
"LD_LIBRARY_PATH".to_owned(),
prefix.join("lib").into_os_string(),
);
#[derive(Clone, Deserialize)]
struct SwitchRoot {
prefix: PathBuf,
etcdir: PathBuf,
}
let entries = match config::config_for_dirs(&[
prefix.join("lib").join("init.d"),
etcdir.join("init.d"),
]) {
Ok(list) => list,
Err(err) => {
eprintln!("init: failed to switchroot: '{prefix:?}', '{etcdir:?}': {err}");
return;
}
};
impl SwitchRoot {
fn apply(
self,
pending_units: &mut Vec<UnitId>,
unit_store: &mut UnitStore,
config: &mut InitConfig,
) {
config
.envs
.insert("PATH".to_owned(), self.prefix.join("bin").into_os_string());
config.envs.insert(
"LD_LIBRARY_PATH".to_owned(),
self.prefix.join("lib").into_os_string(),
);
for entry_path in entries {
if let Err(err) = run(&entry_path, config) {
eprintln!("init: failed to run '{}': {}", entry_path.display(), err);
unit_store.config_dirs = vec![
self.prefix.join("lib").join("init.d"),
self.etcdir.join("init.d"),
];
let (loaded_units, errors) = unit_store.load_units();
for error in errors {
eprintln!("init: {error}");
}
pending_units.extend(loaded_units);
}
}
fn run(file: &Path, config: &mut InitConfig) -> Result<()> {
let (unit, errors) = Unit::from_file(file)?;
for error in errors {
eprintln!("init: {}: {error}", file.display());
}
fn run(
unit: &UnitId,
pending_units: &mut Vec<UnitId>,
unit_store: &mut UnitStore,
config: &mut InitConfig,
) -> Result<()> {
let unit = unit_store.unit_mut(unit);
match unit.kind {
match &unit.kind {
unit::UnitKind::LegacyScript { script } => {
for cmd in script.0 {
for cmd in script.0.clone() {
if config.log_debug {
eprintln!("init: running: {cmd:?}");
}
@@ -92,10 +102,16 @@ fn run(file: &Path, config: &mut InitConfig) -> Result<()> {
}
unit::UnitKind::Service { service } => {
if config.log_debug {
eprintln!("init: running: {} {}", service.cmd, service.args.join(" "));
eprintln!(
"Starting {}",
unit.info.description.as_ref().unwrap_or(&unit.id.0)
);
}
service.spawn(&config.envs);
}
unit::UnitKind::SwitchRoot { switchroot } => {
switchroot.clone().apply(pending_units, unit_store, config);
}
}
Ok(())
@@ -105,9 +121,6 @@ fn run_command(cmd: Command, config: &mut InitConfig) {
match cmd {
Command::Nothing => {}
Command::Echo(text) => println!("{text}"),
Command::SwitchRoot(prefix, etcdir) => {
switch_root(&prefix, &etcdir, config);
}
Command::Stdio(stdio) => {
if let Err(err) = switch_stdio(&stdio) {
eprintln!("init: failed to switch stdio to '{}': {}", stdio, err);
@@ -130,11 +143,22 @@ fn run_command(cmd: Command, config: &mut InitConfig) {
fn main() {
let mut init_config = InitConfig::new();
switch_root(
Path::new("/scheme/initfs"),
Path::new("/scheme/initfs/etc"),
&mut init_config,
);
let mut unit_store = UnitStore::new();
let mut pending_units = vec![];
SwitchRoot {
prefix: Path::new("/scheme/initfs").to_owned(),
etcdir: Path::new("/scheme/initfs/etc").to_owned(),
}
.apply(&mut pending_units, &mut unit_store, &mut init_config);
while !pending_units.is_empty() {
for unit in mem::take(&mut pending_units) {
if let Err(err) = run(&unit, &mut pending_units, &mut unit_store, &mut init_config) {
eprintln!("init: failed to run {}: {}", unit.0, err);
}
}
}
libredox::call::setrens(0, 0).expect("init: failed to enter null namespace");
+1 -14
View File
@@ -37,7 +37,7 @@ impl Script {
}
}
#[derive(Debug)]
#[derive(Clone, Debug)]
pub enum Command {
// Service
Service(Service),
@@ -47,7 +47,6 @@ pub enum Command {
// Misc
Echo(String),
SwitchRoot(PathBuf, PathBuf),
Nothing,
}
@@ -59,18 +58,6 @@ impl Command {
match cmd.as_str() {
"echo" => Ok(Command::Echo(args.collect::<Vec<_>>().join(" "))),
"switchroot" => {
let Some(prefix) = args.next() else {
return Err("init: failed to switchroot: no argument".to_owned());
};
let Some(etcdir) = args.next() else {
return Err("init: failed to switchroot: missing etcdir".to_owned());
};
Ok(Command::SwitchRoot(
PathBuf::from(prefix),
PathBuf::from(etcdir),
))
}
"stdio" => {
let Some(stdio) = args.next() else {
return Err("init: failed to set stdio: no argument".to_owned());
+7 -7
View File
@@ -4,7 +4,7 @@ use std::process::Command;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
#[derive(Clone, Debug, Deserialize)]
pub struct Service {
pub cmd: String,
#[serde(default)]
@@ -15,7 +15,7 @@ pub struct Service {
pub type_: ServiceType,
}
#[derive(Debug, Default, Deserialize)]
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ServiceType {
#[default]
@@ -26,12 +26,12 @@ pub enum ServiceType {
}
impl Service {
pub fn spawn(self, base_envs: &BTreeMap<String, OsString>) {
let mut command = Command::new(self.cmd);
command.args(self.args);
command.env_clear().envs(base_envs).envs(self.envs);
pub fn spawn(&self, base_envs: &BTreeMap<String, OsString>) {
let mut command = Command::new(&self.cmd);
command.args(&self.args);
command.env_clear().envs(base_envs).envs(&self.envs);
match self.type_ {
match &self.type_ {
ServiceType::Notify => {
daemon::Daemon::spawn(command);
}
+97 -14
View File
@@ -1,13 +1,71 @@
use std::path::Path;
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use std::{fs, io};
use serde::Deserialize;
use crate::SwitchRoot;
use crate::script::Script;
use crate::service::Service;
pub struct UnitStore {
pub config_dirs: Vec<PathBuf>,
units: BTreeMap<UnitId, Unit>,
}
impl UnitStore {
pub fn new() -> Self {
UnitStore {
config_dirs: vec![],
units: BTreeMap::new(),
}
}
pub fn load_units(&mut self) -> (Vec<UnitId>, Vec<String>) {
let mut loaded_units = vec![];
let mut errors = vec![];
let entries = match config::config_for_dirs(&self.config_dirs) {
Ok(entries) => entries,
Err(err) => {
errors.push(format!(
"failed to read configs from {}: {err}",
self.config_dirs
.iter()
.map(|dir| dir.display().to_string())
.collect::<Vec<_>>()
.join(", ")
));
return (loaded_units, errors);
}
};
for entry in entries {
let (unit, new_errors) = match Unit::from_file(&entry) {
Ok(unit) => unit,
Err(err) => {
errors.push(format!("{}: {err}", entry.display()));
continue;
}
};
errors.extend(new_errors);
loaded_units.push(unit.id.clone());
self.units.insert(unit.id.clone(), unit);
}
(loaded_units, errors)
}
pub fn unit_mut(&mut self, unit: &UnitId) -> &mut Unit {
self.units.get_mut(unit).unwrap()
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct UnitId(pub String);
pub struct Unit {
pub name: String,
pub id: UnitId,
pub info: UnitInfo,
pub kind: UnitKind,
@@ -15,17 +73,17 @@ pub struct Unit {
#[derive(Deserialize)]
pub struct UnitInfo {
pub description: Option<String>,
#[serde(default)]
description: String,
pub requires: Vec<String>,
#[serde(default)]
requires: Vec<String>,
#[serde(default)]
requires_weak: Vec<String>,
pub requires_weak: Vec<String>,
}
pub enum UnitKind {
LegacyScript { script: Script },
Service { service: Service },
SwitchRoot { switchroot: SwitchRoot },
}
#[derive(Deserialize)]
@@ -34,14 +92,22 @@ struct SerializedService {
service: Service,
}
#[derive(Deserialize)]
struct SerializedSwitchRoot {
unit: UnitInfo,
switchroot: SwitchRoot,
}
impl Unit {
pub fn from_file(config_path: &Path) -> io::Result<(Self, Vec<String>)> {
let name = config_path
.file_name()
.unwrap()
.to_str()
.unwrap()
.to_owned();
let name = UnitId(
config_path
.file_name()
.unwrap()
.to_str()
.unwrap()
.to_owned(),
);
let config = fs::read_to_string(config_path)?;
@@ -50,7 +116,7 @@ impl Unit {
let (script, warnings) = Script::from_str(&config)?;
(
UnitInfo {
description: "".to_owned(),
description: None,
requires: vec![],
requires_weak: vec![],
},
@@ -68,9 +134,26 @@ impl Unit {
vec![],
)
}
Some("switchroot") => {
let switchroot: SerializedSwitchRoot = serde_json::from_str(&config)?;
(
switchroot.unit,
UnitKind::SwitchRoot {
switchroot: switchroot.switchroot,
},
vec![],
)
}
Some(_) => return Err(io::Error::other("invalid file extension")),
};
Ok((Unit { name, info, kind }, errors))
Ok((
Unit {
id: name,
info,
kind,
},
errors,
))
}
}