Merge branch 'init_rework2' into 'main'

Reduce global state manipulation in init

See merge request redox-os/base!130
This commit is contained in:
Jeremy Soller
2026-02-19 13:54:52 -07:00
10 changed files with 164 additions and 134 deletions
+1
View File
@@ -100,6 +100,7 @@ pub fn main() -> ! {
.filter(|var| !var.starts_with(b"INITFS_"))
.collect::<Vec<_>>()
};
envs.push(b"RUST_BACKTRACE=1");
//envs.push(b"LD_DEBUG=all");
envs.push(b"LD_LIBRARY_PATH=/scheme/initfs/lib");
+12 -3
View File
@@ -4,6 +4,7 @@ extern crate syscall;
use driver_graphics::GraphicsScheme;
use event::{user_data, EventQueue};
use inputd::DisplayHandle;
use std::collections::HashMap;
use std::env;
use std::os::fd::AsRawFd;
@@ -57,9 +58,17 @@ fn daemon(daemon: daemon::Daemon) -> ! {
let mut framebuffers = vec![unsafe { FrameBuffer::new(phys, width, height, stride) }];
//TODO: ideal maximum number of outputs?
let bootloader_env = std::fs::read_to_string("/scheme/sys/env")
.expect("failed to read env")
.lines()
.map(|line| {
let (env, value) = line.split_once('=').unwrap();
(env.to_owned(), value.to_owned())
})
.collect::<HashMap<String, String>>();
for i in 1..1024 {
match env::var(&format!("FRAMEBUFFER{}", i)) {
Ok(var) => match unsafe { FrameBuffer::parse(&var) } {
match bootloader_env.get(&format!("FRAMEBUFFER{}", i)) {
Some(var) => match unsafe { FrameBuffer::parse(&var) } {
Some(fb) => {
println!(
"vesad: framebuffer {}: {}x{} stride {} at 0x{:X}",
@@ -71,7 +80,7 @@ fn daemon(daemon: daemon::Daemon) -> ! {
eprintln!("vesad: framebuffer {}: failed to parse '{}'", i, var);
}
},
Err(_err) => break,
None => break,
};
}
+1 -1
View File
@@ -1,3 +1,3 @@
notify hwd
unset RSDP_ADDR RSDP_SIZE
notify RSDP_ADDR=$ RSDP_SIZE=$ hwd
pcid-spawner --initfs
+1 -5
View File
@@ -1,8 +1,4 @@
# Various daemons that relibc needs to function as well as a bunch of env vars
# that should be set for every program.
export PATH /scheme/initfs/bin
export LD_LIBRARY_PATH /scheme/initfs/lib
export RUST_BACKTRACE 1
# Various daemons that relibc needs to function.
rtcd
scheme null nulld
scheme zero zerod
+1 -3
View File
@@ -1,7 +1,5 @@
scheme input inputd
notify vesad
unset FRAMEBUFFER_ADDR FRAMEBUFFER_VIRT FRAMEBUFFER_WIDTH FRAMEBUFFER_HEIGHT FRAMEBUFFER_STRIDE
#TODO: unset FRAMEBUFFER1 and beyond?
notify FRAMEBUFFER_ADDR=$ FRAMEBUFFER_VIRT=$ FRAMEBUFFER_WIDTH=$ FRAMEBUFFER_HEIGHT=$ FRAMEBUFFER_STRIDE=$ vesad
scheme fbbootlog fbbootlogd
# Activate framebuffer log VT, which disables kernel graphical debug
inputd -A 1
+1 -2
View File
@@ -1,4 +1,3 @@
notify ps2d
notify hwd
unset RSDP_ADDR RSDP_SIZE
notify RSDP_ADDR=$ RSDP_SIZE=$ hwd
pcid-spawner --initfs
+1 -2
View File
@@ -1,2 +1 @@
redoxfs --uuid $REDOXFS_UUID file $REDOXFS_BLOCK
unset REDOXFS_UUID REDOXFS_BLOCK REDOXFS_PASSWORD_ADDR REDOXFS_PASSWORD_SIZE
REDOXFS_PASSWORD_ADDR=$ REDOXFS_PASSWORD_SIZE=$ redoxfs --uuid $REDOXFS_UUID file $REDOXFS_BLOCK
+1 -4
View File
@@ -1,4 +1 @@
cd /
export PATH /usr/bin
export LD_LIBRARY_PATH /usr/lib
run.d /usr/lib/init.d /etc/init.d
switchroot /usr /etc
+62 -64
View File
@@ -1,7 +1,8 @@
use std::collections::BTreeMap;
use std::env;
use std::ffi::OsString;
use std::io::Result;
use std::path::Path;
use std::process;
use libredox::flag::{O_RDONLY, O_WRONLY};
@@ -24,6 +25,7 @@ fn switch_stdio(stdio: &str) -> Result<()> {
struct InitConfig {
log_debug: bool,
skip_cmd: Vec<String>,
envs: BTreeMap<String, OsString>,
}
impl InitConfig {
@@ -38,11 +40,39 @@ impl InitConfig {
Self {
log_debug,
skip_cmd,
envs: BTreeMap::from([("RUST_BACKTRACE".to_owned(), "1".into())]),
}
}
}
fn run(file: &Path, config: &InitConfig) -> Result<()> {
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(),
);
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;
}
};
for entry_path in entries {
if let Err(err) = run(&entry_path, config) {
eprintln!("init: failed to run '{}': {}", entry_path.display(), err);
}
}
}
fn run(file: &Path, config: &mut InitConfig) -> Result<()> {
let (script, errors) = script::Script::from_file(file)?;
for error in errors {
@@ -59,30 +89,13 @@ fn run(file: &Path, config: &InitConfig) -> Result<()> {
Ok(())
}
fn run_command(cmd: Command, config: &InitConfig) {
fn run_command(cmd: Command, config: &mut InitConfig) {
match cmd {
Command::Nothing => {}
Command::Cd(dir) => {
if let Err(err) = env::set_current_dir(&dir) {
eprintln!("init: failed to cd to '{}': {}", dir.display(), err);
}
}
Command::Echo(text) => println!("{text}"),
Command::Export(var, value) => unsafe { env::set_var(var, value) },
Command::RunD(dirs) => {
let entries = match config::config_for_dirs(&dirs) {
Ok(list) => list,
Err(err) => {
eprintln!("init: failed to run.d: '{dirs:?}': {err}");
return;
}
};
for entry_path in entries {
if let Err(err) = run(&entry_path, config) {
eprintln!("init: failed to run '{}': {}", entry_path.display(), err);
}
}
Command::SwitchRoot(prefix, etcdir) => {
switch_root(&prefix, &etcdir, config);
}
Command::Stdio(stdio) => {
if let Err(err) = switch_stdio(&stdio) {
@@ -94,70 +107,62 @@ fn run_command(cmd: Command, config: &InitConfig) {
unsafe { env::remove_var(&env) };
}
}
Command::Nowait(cmd, args) => {
if config.skip_cmd.contains(&cmd) {
eprintln!("init: skipping '{} {}'", cmd, args.join(" "));
Command::Nowait(cmd) => {
if config.skip_cmd.contains(&cmd.cmd) {
eprintln!("init: skipping '{} {}'", cmd.cmd, cmd.args.join(" "));
return;
}
let mut command = process::Command::new(cmd);
for arg in args {
command.arg(arg);
}
let mut command = cmd.into_command(&config.envs);
match command.spawn() {
Ok(_child) => {}
Err(err) => eprintln!("init: failed to execute '{:?}': {}", command, err),
}
}
Command::Notify(cmd, args) => {
if config.skip_cmd.contains(&cmd) {
eprintln!("init: skipping '{} {}'", cmd, args.join(" "));
Command::Notify(cmd) => {
if config.skip_cmd.contains(&cmd.cmd) {
eprintln!("init: skipping '{} {}'", cmd.cmd, cmd.args.join(" "));
return;
}
let mut command = process::Command::new(&cmd);
for arg in args {
command.arg(arg);
}
let command = cmd.into_command(&config.envs);
daemon::Daemon::spawn(command);
}
Command::Scheme(scheme, cmd, args) => {
if config.skip_cmd.contains(&cmd) {
eprintln!("init: skipping '{} {}'", cmd, args.join(" "));
Command::Scheme(scheme, cmd) => {
if config.skip_cmd.contains(&cmd.cmd) {
eprintln!("init: skipping '{} {}'", cmd.cmd, cmd.args.join(" "));
return;
}
let mut command = process::Command::new(&cmd);
for arg in args {
command.arg(arg);
}
let command = cmd.into_command(&config.envs);
daemon::SchemeDaemon::spawn(command, &scheme);
}
Command::Regular(cmd, args) => {
let mut command = process::Command::new(cmd.clone());
for arg in args {
command.arg(arg);
Command::Regular(cmd) => {
if config.skip_cmd.contains(&cmd.cmd) {
eprintln!("init: skipping '{} {}'", cmd.cmd, cmd.args.join(" "));
return;
}
let mut command = cmd.into_command(&config.envs);
let mut child = match command.spawn() {
Ok(child) => child,
Err(err) => {
eprintln!("init: failed to execute '{:?}': {}", command, err);
eprintln!("init: failed to execute {:?}: {}", command, err);
return;
}
};
match child.wait() {
Ok(exit_status) => {
if !exit_status.success() {
eprintln!("{cmd} failed with {exit_status}");
eprintln!("{command:?} failed with {exit_status}");
}
}
Err(err) => {
eprintln!("init: failed to wait for '{:?}': {}", command, err)
eprintln!("init: failed to wait for {:?}: {}", command, err)
}
}
}
@@ -165,19 +170,12 @@ fn run_command(cmd: Command, config: &InitConfig) {
}
fn main() {
let init_config = InitConfig::new();
let entries = match config::config_for_initfs("init") {
Ok(entries) => entries,
Err(err) => {
eprintln!("init: failed to find config files: {}", err);
return;
}
};
for entry_path in entries {
if let Err(err) = run(&entry_path, &init_config) {
eprintln!("init: failed to run '{}': {}", entry_path.display(), err);
}
}
let mut init_config = InitConfig::new();
switch_root(
Path::new("/scheme/initfs"),
Path::new("/scheme/initfs/etc"),
&mut init_config,
);
libredox::call::setrens(0, 0).expect("init: failed to enter null namespace");
+83 -50
View File
@@ -1,5 +1,15 @@
use std::collections::BTreeMap;
use std::ffi::OsString;
use std::path::{Path, PathBuf};
use std::{env, fs, io};
use std::{env, fs, io, iter, process};
fn subst_env<'a>(arg: &str) -> String {
if arg.starts_with('$') {
env::var(&arg[1..]).unwrap_or(String::new())
} else {
arg.to_owned()
}
}
pub struct Script(pub Vec<Command>);
@@ -14,15 +24,9 @@ impl Script {
continue;
}
let args = line.split(' ').map(|arg| {
if arg.starts_with('$') {
env::var(&arg[1..]).unwrap_or(String::new())
} else {
arg.to_string()
}
});
let args = line.split(' ').map(subst_env);
match Command::from_arg_iter(args) {
match Command::parse(args) {
Ok(cmd) => cmds.push(cmd),
Err(err) => errors.push(err),
}
@@ -35,36 +39,29 @@ impl Script {
#[derive(Debug)]
pub enum Command {
// Service
Nowait(String, Vec<String>),
Notify(String, Vec<String>),
Scheme(String, String, Vec<String>),
Regular(String, Vec<String>),
Nowait(Process),
Notify(Process),
Scheme(String, Process),
Regular(Process),
// Modify env
Cd(PathBuf),
Stdio(String),
Export(String, String),
Unset(Vec<String>),
// Misc
Echo(String),
RunD(Vec<PathBuf>),
SwitchRoot(PathBuf, PathBuf),
Nothing,
}
impl Command {
fn from_arg_iter(mut args: impl Iterator<Item = String>) -> Result<Command, String> {
fn parse(mut args: impl Iterator<Item = String>) -> Result<Command, String> {
let Some(cmd) = args.next() else {
return Ok(Command::Nothing);
};
match cmd.as_str() {
"cd" => {
let Some(dir) = args.next() else {
return Err("init: failed to cd: no argument".to_owned());
};
Ok(Command::Cd(PathBuf::from(dir)))
}
"echo" => Ok(Command::Echo(args.collect::<Vec<_>>().join(" "))),
"export" => {
let Some(var) = args.next() else {
@@ -80,15 +77,17 @@ impl Command {
}
Ok(Command::Export(var, value))
}
"run.d" => {
let args = args.map(|arg| PathBuf::from(arg)).collect::<Vec<_>>();
if args.is_empty() {
return Err(
"init: failed to run.d: no argument or all dirs are non-existent"
.to_owned(),
);
}
Ok(Command::RunD(args))
"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 {
@@ -97,31 +96,65 @@ impl Command {
Ok(Command::Stdio(stdio))
}
"unset" => Ok(Command::Unset(args.collect())),
"nowait" => {
let Some(cmd) = args.next() else {
return Err("init: failed to run nowait: no argument".to_owned());
};
Ok(Command::Nowait(cmd, args.collect()))
}
"notify" => {
let Some(cmd) = args.next() else {
return Err("init: failed to run notify: no argument".to_owned());
};
Ok(Command::Notify(cmd, args.collect()))
}
"nowait" => Ok(Command::Nowait(Process::parse(args)?)),
"notify" => Ok(Command::Notify(Process::parse(args)?)),
"scheme" => {
let Some(scheme) = args.next() else {
return Err("init: failed to run scheme: no argument".to_owned());
};
let Some(cmd) = args.next() else {
return Err("init: failed to run scheme: missing command".to_owned());
};
Ok(Command::Scheme(scheme, cmd, args.collect()))
Ok(Command::Scheme(scheme, Process::parse(args)?))
}
_ => Ok(Command::Regular(cmd, args.collect())),
_ => Ok(Command::Regular(Process::parse(
iter::once(cmd).chain(args),
)?)),
}
}
}
#[derive(Debug)]
pub struct Process {
pub cmd: String,
pub args: Vec<String>,
pub envs: Vec<(String, String)>,
}
impl Process {
fn parse(parts: impl Iterator<Item = String>) -> Result<Process, String> {
let mut cmd = None;
let mut args = vec![];
let mut envs = vec![];
for arg in parts {
if cmd.is_none() {
if let Some((env, value)) = arg.split_once('=') {
let value = if value == "$" {
env::var(env).unwrap_or_default()
} else {
subst_env(value)
};
if !value.is_empty() {
envs.push((env.to_owned(), value));
}
} else {
cmd = Some(arg);
}
} else {
args.push(arg);
}
}
if let Some(cmd) = cmd {
Ok(Process { cmd, args, envs })
} else {
Err("no command given".to_owned())
}
}
pub fn into_command(self, base_envs: &BTreeMap<String, OsString>) -> process::Command {
let mut command = process::Command::new(self.cmd);
command.args(self.args);
command.env_clear().envs(base_envs).envs(self.envs);
command
}
}