Merge branch 'init_rework8' into 'main'
init: Introduce a basic job scheduler See merge request redox-os/base!215
This commit is contained in:
+19
-26
@@ -2,7 +2,8 @@
|
||||
#![feature(never_type)]
|
||||
|
||||
use std::io::{self, PipeWriter, Read, Write};
|
||||
use std::os::fd::{AsRawFd, FromRawFd, RawFd};
|
||||
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
|
||||
use std::os::unix::process::CommandExt;
|
||||
use std::process::Command;
|
||||
|
||||
use libredox::Fd;
|
||||
@@ -20,6 +21,20 @@ unsafe fn get_fd(var: &str) -> RawFd {
|
||||
fd
|
||||
}
|
||||
|
||||
unsafe fn pass_fd(cmd: &mut Command, env: &str, fd: OwnedFd) {
|
||||
cmd.env(env, format!("{}", fd.as_raw_fd()));
|
||||
unsafe {
|
||||
cmd.pre_exec(move || {
|
||||
// Pass notify pipe to child
|
||||
if libc::fcntl(fd.as_raw_fd(), libc::F_SETFD, 0) == -1 {
|
||||
Err(io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// A long running background process that handles requests.
|
||||
#[must_use = "Daemon::ready must be called"]
|
||||
pub struct Daemon {
|
||||
@@ -43,29 +58,16 @@ impl Daemon {
|
||||
pub fn spawn(mut cmd: Command) {
|
||||
let (mut read_pipe, write_pipe) = io::pipe().unwrap();
|
||||
|
||||
// Pass notify pipe to child
|
||||
if unsafe { libc::fcntl(write_pipe.as_raw_fd(), libc::F_SETFD, 0) } == -1 {
|
||||
eprintln!(
|
||||
"daemon: failed to unset CLOEXEC flag for notify pipe: {}",
|
||||
io::Error::last_os_error()
|
||||
);
|
||||
return;
|
||||
}
|
||||
cmd.env("INIT_NOTIFY", format!("{}", write_pipe.as_raw_fd()));
|
||||
unsafe { pass_fd(&mut cmd, "INIT_NOTIFY", write_pipe.into()) };
|
||||
|
||||
if let Err(err) = cmd.spawn() {
|
||||
eprintln!("daemon: failed to execute {cmd:?}: {err}");
|
||||
return;
|
||||
}
|
||||
drop(write_pipe);
|
||||
|
||||
let mut data = [0];
|
||||
match read_pipe.read_exact(&mut data) {
|
||||
Ok(()) => {
|
||||
if data[0] != 0 {
|
||||
eprintln!("daemon: {cmd:?} failed with {}", data[0]);
|
||||
}
|
||||
}
|
||||
Ok(()) => {}
|
||||
Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => {
|
||||
eprintln!("daemon: {cmd:?} exited without notifying readiness");
|
||||
}
|
||||
@@ -127,21 +129,12 @@ impl SchemeDaemon {
|
||||
pub fn spawn(mut cmd: Command, scheme_name: &str) {
|
||||
let (read_pipe, write_pipe) = io::pipe().unwrap();
|
||||
|
||||
// Pass notify pipe to child
|
||||
if unsafe { libc::fcntl(write_pipe.as_raw_fd(), libc::F_SETFD, 0) } == -1 {
|
||||
eprintln!(
|
||||
"daemon: failed to unset CLOEXEC flag for notify pipe: {}",
|
||||
io::Error::last_os_error()
|
||||
);
|
||||
return;
|
||||
}
|
||||
cmd.env("INIT_NOTIFY", format!("{}", write_pipe.as_raw_fd()));
|
||||
unsafe { pass_fd(&mut cmd, "INIT_NOTIFY", write_pipe.into()) };
|
||||
|
||||
if let Err(err) = cmd.spawn() {
|
||||
eprintln!("daemon: failed to execute {cmd:?}: {err}");
|
||||
return;
|
||||
}
|
||||
drop(write_pipe);
|
||||
|
||||
let mut new_fd = usize::MAX;
|
||||
let fd_bytes = unsafe {
|
||||
|
||||
+66
-127
@@ -1,15 +1,16 @@
|
||||
use std::collections::{BTreeMap, VecDeque};
|
||||
use std::collections::BTreeMap;
|
||||
use std::env;
|
||||
use std::ffi::OsString;
|
||||
use std::io::Result;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::Path;
|
||||
|
||||
use libredox::flag::{O_RDONLY, O_WRONLY};
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::scheduler::Scheduler;
|
||||
use crate::script::Command;
|
||||
use crate::unit::{UnitId, UnitStore};
|
||||
use crate::unit::{Unit, UnitId, UnitStore};
|
||||
|
||||
mod scheduler;
|
||||
mod script;
|
||||
mod service;
|
||||
mod unit;
|
||||
@@ -49,83 +50,25 @@ impl InitConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct SwitchRoot {
|
||||
prefix: PathBuf,
|
||||
etcdir: PathBuf,
|
||||
target: Option<UnitId>,
|
||||
fn switch_root(unit_store: &mut UnitStore, config: &mut InitConfig, prefix: &Path, etcdir: &Path) {
|
||||
eprintln!(
|
||||
"init: switchroot to {} {}",
|
||||
prefix.display(),
|
||||
etcdir.display()
|
||||
);
|
||||
|
||||
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(),
|
||||
);
|
||||
|
||||
unit_store.config_dirs = vec![prefix.join("lib").join("init.d"), etcdir.join("init.d")];
|
||||
}
|
||||
|
||||
impl SwitchRoot {
|
||||
fn apply(
|
||||
self,
|
||||
pending_units: &mut VecDeque<UnitId>,
|
||||
unit_store: &mut UnitStore,
|
||||
config: &mut InitConfig,
|
||||
) {
|
||||
eprintln!(
|
||||
"init: switchroot to {} {}",
|
||||
self.prefix.display(),
|
||||
self.etcdir.display()
|
||||
);
|
||||
|
||||
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(),
|
||||
);
|
||||
|
||||
unit_store.config_dirs = vec![
|
||||
self.prefix.join("lib").join("init.d"),
|
||||
self.etcdir.join("init.d"),
|
||||
];
|
||||
|
||||
let mut errors = vec![];
|
||||
|
||||
let loaded_units =
|
||||
unit_store.load_units(UnitId("00_runtime.target".to_owned()), &mut errors);
|
||||
pending_units.extend(loaded_units);
|
||||
|
||||
if let Some(target) = self.target {
|
||||
let loaded_units = unit_store.load_units(target, &mut errors);
|
||||
pending_units.extend(loaded_units);
|
||||
} else {
|
||||
let entries = match config::config_for_dirs(&unit_store.config_dirs) {
|
||||
Ok(entries) => entries,
|
||||
Err(err) => {
|
||||
eprintln!(
|
||||
"init: failed to read configs from {}: {err}",
|
||||
unit_store
|
||||
.config_dirs
|
||||
.iter()
|
||||
.map(|dir| dir.display().to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
for entry in entries {
|
||||
let loaded_units = unit_store.load_units(
|
||||
UnitId(entry.file_name().unwrap().to_str().unwrap().to_owned()),
|
||||
&mut errors,
|
||||
);
|
||||
pending_units.extend(loaded_units);
|
||||
}
|
||||
}
|
||||
|
||||
for error in errors {
|
||||
eprintln!("init: {error}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run(unit: &UnitId, unit_store: &mut UnitStore, config: &mut InitConfig) -> Result<()> {
|
||||
let unit = unit_store.unit_mut(unit);
|
||||
|
||||
fn run(unit: &mut Unit, config: &mut InitConfig) -> Result<()> {
|
||||
match &unit.kind {
|
||||
unit::UnitKind::LegacyScript { script } => {
|
||||
for cmd in script.0.clone() {
|
||||
@@ -185,17 +128,21 @@ fn run_command(cmd: Command, config: &mut InitConfig) {
|
||||
fn main() {
|
||||
let mut init_config = InitConfig::new();
|
||||
let mut unit_store = UnitStore::new();
|
||||
let mut pending_units = VecDeque::new();
|
||||
let mut scheduler = Scheduler::new();
|
||||
|
||||
switch_root(
|
||||
&mut unit_store,
|
||||
&mut init_config,
|
||||
Path::new("/scheme/initfs"),
|
||||
Path::new("/scheme/initfs/etc"),
|
||||
);
|
||||
|
||||
let runtime_target = UnitId("00_runtime.target".to_owned());
|
||||
let initfs_target = UnitId("90_initfs.target".to_owned());
|
||||
scheduler.schedule_start_and_report_errors(&mut unit_store, runtime_target.clone());
|
||||
unit_store.set_runtime_target(runtime_target);
|
||||
|
||||
SwitchRoot {
|
||||
prefix: Path::new("/scheme/initfs").to_owned(),
|
||||
etcdir: Path::new("/scheme/initfs/etc").to_owned(),
|
||||
target: Some(initfs_target.clone()),
|
||||
}
|
||||
.apply(&mut pending_units, &mut unit_store, &mut init_config);
|
||||
scheduler
|
||||
.schedule_start_and_report_errors(&mut unit_store, UnitId("90_initfs.target".to_owned()));
|
||||
|
||||
let mut command = std::process::Command::new("logd");
|
||||
command.env_clear().envs(&init_config.envs);
|
||||
@@ -204,50 +151,42 @@ fn main() {
|
||||
eprintln!("init: failed to switch stdio to '/scheme/log': {err}");
|
||||
}
|
||||
|
||||
'a: while let Some(unit) = pending_units.pop_front() {
|
||||
if let Some(condition_architecture) = &unit_store.unit(&unit).info.condition_architecture {
|
||||
if !condition_architecture
|
||||
.iter()
|
||||
.any(|arch| arch == std::env::consts::ARCH)
|
||||
{
|
||||
continue 'a;
|
||||
}
|
||||
}
|
||||
if let Some(condition_board) = &unit_store.unit(&unit).info.condition_board {
|
||||
if !condition_board
|
||||
.iter()
|
||||
.any(|board| Some(&**board) == option_env!("BOARD"))
|
||||
{
|
||||
continue 'a;
|
||||
}
|
||||
}
|
||||
if unit_store.unit(&unit).info.default_dependencies {
|
||||
if pending_units.contains(&runtime_target) {
|
||||
pending_units.push_back(unit);
|
||||
continue 'a;
|
||||
}
|
||||
}
|
||||
for dep in &unit_store.unit(&unit).info.requires_weak {
|
||||
if pending_units.contains(dep) {
|
||||
pending_units.push_back(unit);
|
||||
continue 'a;
|
||||
}
|
||||
}
|
||||
scheduler.step(&mut unit_store, &mut init_config);
|
||||
|
||||
if let Err(err) = run(&unit, &mut unit_store, &mut init_config) {
|
||||
eprintln!("init: failed to run {}: {}", unit.0, err);
|
||||
}
|
||||
switch_root(
|
||||
&mut unit_store,
|
||||
&mut init_config,
|
||||
Path::new("/usr"),
|
||||
Path::new("/etc"),
|
||||
);
|
||||
{
|
||||
// FIXME introduce multi-user.target unit and replace the config dir iteration
|
||||
// scheduler.schedule_start_and_report_errors(&mut unit_store, UnitId("multi-user.target".to_owned()));
|
||||
|
||||
if unit == initfs_target {
|
||||
SwitchRoot {
|
||||
prefix: PathBuf::from("/usr"),
|
||||
etcdir: PathBuf::from("/etc"),
|
||||
// FIXME make target non-optional once there is a multi-user.target unit
|
||||
target: None, //UnitId("multi-user.target".to_owned()),
|
||||
let entries = match config::config_for_dirs(&unit_store.config_dirs) {
|
||||
Ok(entries) => entries,
|
||||
Err(err) => {
|
||||
eprintln!(
|
||||
"init: failed to read configs from {}: {err}",
|
||||
unit_store
|
||||
.config_dirs
|
||||
.iter()
|
||||
.map(|dir| dir.display().to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
);
|
||||
return;
|
||||
}
|
||||
.apply(&mut pending_units, &mut unit_store, &mut init_config);
|
||||
};
|
||||
for entry in entries {
|
||||
scheduler.schedule_start_and_report_errors(
|
||||
&mut unit_store,
|
||||
UnitId(entry.file_name().unwrap().to_str().unwrap().to_owned()),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
scheduler.step(&mut unit_store, &mut init_config);
|
||||
|
||||
libredox::call::setrens(0, 0).expect("init: failed to enter null namespace");
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use crate::InitConfig;
|
||||
use crate::unit::{UnitId, UnitStore};
|
||||
|
||||
pub struct Scheduler {
|
||||
pending: VecDeque<Job>,
|
||||
}
|
||||
|
||||
struct Job {
|
||||
unit: UnitId,
|
||||
kind: JobKind,
|
||||
}
|
||||
|
||||
enum JobKind {
|
||||
Start,
|
||||
}
|
||||
|
||||
impl Scheduler {
|
||||
pub fn new() -> Scheduler {
|
||||
Scheduler {
|
||||
pending: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn schedule_start_and_report_errors(
|
||||
&mut self,
|
||||
unit_store: &mut UnitStore,
|
||||
unit_id: UnitId,
|
||||
) {
|
||||
let mut errors = vec![];
|
||||
self.schedule_start(unit_store, unit_id, &mut errors);
|
||||
for error in errors {
|
||||
eprintln!("init: {error}");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn schedule_start(
|
||||
&mut self,
|
||||
unit_store: &mut UnitStore,
|
||||
unit_id: UnitId,
|
||||
errors: &mut Vec<String>,
|
||||
) {
|
||||
let loaded_units = unit_store.load_units(unit_id.clone(), errors);
|
||||
for unit_id in loaded_units {
|
||||
if !unit_store.unit(&unit_id).conditions_met() {
|
||||
continue;
|
||||
}
|
||||
|
||||
self.pending.push_back(Job {
|
||||
unit: unit_id,
|
||||
kind: JobKind::Start,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn step(&mut self, unit_store: &mut UnitStore, init_config: &mut InitConfig) {
|
||||
'a: loop {
|
||||
let Some(job) = self.pending.pop_front() else {
|
||||
return;
|
||||
};
|
||||
|
||||
match job.kind {
|
||||
JobKind::Start => {
|
||||
let unit = unit_store.unit_mut(&job.unit);
|
||||
|
||||
for dep in &unit.info.requires_weak {
|
||||
for pending_job in &self.pending {
|
||||
if &pending_job.unit == dep {
|
||||
self.pending.push_back(job);
|
||||
continue 'a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(err) = crate::run(unit, init_config) {
|
||||
eprintln!("init: failed to run {}: {}", job.unit.0, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+43
-1
@@ -10,6 +10,7 @@ use crate::service::Service;
|
||||
pub struct UnitStore {
|
||||
pub config_dirs: Vec<PathBuf>,
|
||||
units: BTreeMap<UnitId, Unit>,
|
||||
runtime_target: Option<UnitId>,
|
||||
}
|
||||
|
||||
impl UnitStore {
|
||||
@@ -17,9 +18,16 @@ impl UnitStore {
|
||||
UnitStore {
|
||||
config_dirs: vec![],
|
||||
units: BTreeMap::new(),
|
||||
runtime_target: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_runtime_target(&mut self, unit_id: UnitId) {
|
||||
assert!(self.runtime_target.is_none());
|
||||
assert!(self.units.contains_key(&unit_id));
|
||||
self.runtime_target = Some(unit_id);
|
||||
}
|
||||
|
||||
fn load_single_unit(&mut self, unit_id: UnitId, errors: &mut Vec<String>) -> Option<UnitId> {
|
||||
let (filename, instance) = if let Some((base_service, rest)) = unit_id.0.split_once('@') {
|
||||
let Some((instance, ext)) = rest.rsplit_once('.') else {
|
||||
@@ -42,13 +50,25 @@ impl UnitStore {
|
||||
return None;
|
||||
};
|
||||
|
||||
let unit = match Unit::from_file(unit_id.clone(), &path, instance, errors) {
|
||||
let mut unit = match Unit::from_file(unit_id.clone(), &path, instance, errors) {
|
||||
Ok(unit) => unit,
|
||||
Err(err) => {
|
||||
errors.push(format!("{}: {err}", path.display()));
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
if unit.info.default_dependencies {
|
||||
if let Some(runtime_target) = self.runtime_target.clone() {
|
||||
unit.info.requires_weak.push(runtime_target);
|
||||
} else {
|
||||
errors.push(format!(
|
||||
"{}: dependency of the runtime target must have default dependencies disabled",
|
||||
path.display(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
self.units.insert(unit_id.clone(), unit);
|
||||
|
||||
Some(unit_id)
|
||||
@@ -213,4 +233,26 @@ impl Unit {
|
||||
|
||||
Ok(Unit { id, info, kind })
|
||||
}
|
||||
|
||||
pub fn conditions_met(&self) -> bool {
|
||||
if let Some(condition_architecture) = &self.info.condition_architecture {
|
||||
if !condition_architecture
|
||||
.iter()
|
||||
.any(|arch| arch == std::env::consts::ARCH)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(condition_board) = &self.info.condition_board {
|
||||
if !condition_board
|
||||
.iter()
|
||||
.any(|board| Some(&**board) == option_env!("BOARD"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user