diff --git a/bootstrap/src/exec.rs b/bootstrap/src/exec.rs index d5100fe5bc..33bb588455 100644 --- a/bootstrap/src/exec.rs +++ b/bootstrap/src/exec.rs @@ -100,6 +100,7 @@ pub fn main() -> ! { .filter(|var| !var.starts_with(b"INITFS_")) .collect::>() }; + envs.push(b"RUST_BACKTRACE=1"); //envs.push(b"LD_DEBUG=all"); envs.push(b"LD_LIBRARY_PATH=/scheme/initfs/lib"); diff --git a/drivers/graphics/vesad/src/main.rs b/drivers/graphics/vesad/src/main.rs index bf605cfe9a..ac87e0b9db 100644 --- a/drivers/graphics/vesad/src/main.rs +++ b/drivers/graphics/vesad/src/main.rs @@ -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::>(); 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, }; } diff --git a/init.d.aarch64-unknown-redox/40_drivers b/init.d.aarch64-unknown-redox/40_drivers index fcfa633d87..e0eb8e2b1c 100644 --- a/init.d.aarch64-unknown-redox/40_drivers +++ b/init.d.aarch64-unknown-redox/40_drivers @@ -1,3 +1,3 @@ notify hwd -unset RSDP_ADDR RSDP_SIZE +notify RSDP_ADDR=$ RSDP_SIZE=$ hwd pcid-spawner --initfs diff --git a/init.d/00_runtime b/init.d/00_runtime index 58973a9c23..d7d4e3d10f 100644 --- a/init.d/00_runtime +++ b/init.d/00_runtime @@ -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 diff --git a/init.d/20_graphics b/init.d/20_graphics index 414b0b4d97..ecfd423b4c 100644 --- a/init.d/20_graphics +++ b/init.d/20_graphics @@ -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 diff --git a/init.d/40_drivers b/init.d/40_drivers index 8f3fa01e71..82daf676c1 100644 --- a/init.d/40_drivers +++ b/init.d/40_drivers @@ -1,4 +1,3 @@ notify ps2d -notify hwd -unset RSDP_ADDR RSDP_SIZE +notify RSDP_ADDR=$ RSDP_SIZE=$ hwd pcid-spawner --initfs diff --git a/init.d/50_rootfs b/init.d/50_rootfs index 9102f1fa6d..ece2c67124 100644 --- a/init.d/50_rootfs +++ b/init.d/50_rootfs @@ -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 diff --git a/init.d/90_exit_initfs b/init.d/90_exit_initfs index 80f22bad22..dea1136bd9 100644 --- a/init.d/90_exit_initfs +++ b/init.d/90_exit_initfs @@ -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 diff --git a/init/src/main.rs b/init/src/main.rs index 9ff4cbacb3..27afab8cad 100644 --- a/init/src/main.rs +++ b/init/src/main.rs @@ -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, + envs: BTreeMap, } 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"); diff --git a/init/src/script.rs b/init/src/script.rs index 50d8ae2e5f..c22ee99b19 100644 --- a/init/src/script.rs +++ b/init/src/script.rs @@ -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); @@ -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), - Notify(String, Vec), - Scheme(String, String, Vec), - Regular(String, Vec), + Nowait(Process), + Notify(Process), + Scheme(String, Process), + Regular(Process), // Modify env - Cd(PathBuf), Stdio(String), Export(String, String), Unset(Vec), // Misc Echo(String), - RunD(Vec), + SwitchRoot(PathBuf, PathBuf), Nothing, } impl Command { - fn from_arg_iter(mut args: impl Iterator) -> Result { + fn parse(mut args: impl Iterator) -> Result { 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::>().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::>(); - 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, + pub envs: Vec<(String, String)>, +} + +impl Process { + fn parse(parts: impl Iterator) -> Result { + 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) -> process::Command { + let mut command = process::Command::new(self.cmd); + command.args(self.args); + command.env_clear().envs(base_envs).envs(self.envs); + command + } +}