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:
@@ -1,5 +1,7 @@
|
||||
{
|
||||
"unit": {},
|
||||
"unit": {
|
||||
"description": "Set time from realtime clock"
|
||||
},
|
||||
"service": {
|
||||
"cmd": "rtcd",
|
||||
"type": "oneshot"
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
switchroot /usr /etc
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"unit": {},
|
||||
"switchroot": {
|
||||
"prefix": "/usr",
|
||||
"etcdir": "/etc"
|
||||
}
|
||||
}
|
||||
+64
-40
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user