diff --git a/init/.gitignore b/init/.gitignore new file mode 100644 index 0000000000..b83d22266a --- /dev/null +++ b/init/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/init/Cargo.lock b/init/Cargo.lock new file mode 100644 index 0000000000..9ec341303e --- /dev/null +++ b/init/Cargo.lock @@ -0,0 +1,42 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "init" +version = "0.1.0" +dependencies = [ + "libredox", +] + +[[package]] +name = "libc" +version = "0.2.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags", + "libc", + "redox_syscall", +] + +[[package]] +name = "redox_syscall" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +dependencies = [ + "bitflags", +] diff --git a/init/Cargo.toml b/init/Cargo.toml new file mode 100644 index 0000000000..1c2eee4630 --- /dev/null +++ b/init/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "init" +version = "0.1.0" +edition = "2021" +license = "MIT" + +[dependencies] +libredox = "0.1" diff --git a/init/LICENSE b/init/LICENSE new file mode 100644 index 0000000000..5deeece4f4 --- /dev/null +++ b/init/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Redox OS + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/init/README.md b/init/README.md new file mode 100644 index 0000000000..bfa86d6df4 --- /dev/null +++ b/init/README.md @@ -0,0 +1,30 @@ +# RedoxOS init +This repository contains the init system for RedoxOS. + +Init is currently being rewritten to support a couple of behaviors that are helpful for running a configurable and robust system. These include: +- Dependency relationships between services +- Parallel service startup and shutdown +- Configuration files for each service, including: + - Dependencies + - Methods + - TODO: Users/Groups for services + - TODO: Scheme namespaces for services +- Service management + - Enable and disable services + - Restart failed services + +## How To Contribute + +To learn how to contribute to this system component you need to read the following document: + +- [CONTRIBUTING.md](https://gitlab.redox-os.org/redox-os/redox/-/blob/master/CONTRIBUTING.md) + +## Development + +To learn how to do development with this system component inside the Redox build system you need to read the [Build System](https://doc.redox-os.org/book/build-system-reference.html) and [Coding and Building](https://doc.redox-os.org/book/coding-and-building.html) pages. + +### How To Build + +To build this system component you need to download the Redox build system, you can learn how to do it on the [Building Redox](https://doc.redox-os.org/book/podman-build.html) page. + +This is necessary because they only work with cross-compilation to a Redox virtual machine, but you can do some testing from Linux. diff --git a/init/src/main.rs b/init/src/main.rs new file mode 100644 index 0000000000..8b8eef158f --- /dev/null +++ b/init/src/main.rs @@ -0,0 +1,230 @@ +use std::collections::BTreeMap; +use std::env; +use std::ffi::CString; +use std::fs::{read_dir, File}; +use std::io::{BufRead, BufReader, Result, Write}; +use std::path::Path; +use std::process::Command; + +use libredox::error::Error as OsError; + +use libredox::flag::{O_RDONLY, O_WRONLY}; + +fn set_default_scheme(scheme: &str) -> std::result::Result<(), OsError> { + use std::ffi::{c_char, c_int}; + + extern "C" { + fn set_default_scheme(scheme: *const c_char) -> c_int; + } + + let cstr = + CString::new(scheme.as_bytes()).expect(&format!("init: invalid default scheme {}", scheme)); + + let res = unsafe { set_default_scheme(cstr.as_ptr()) }; + + match res { + 0 => Ok(()), + error_code => Err(OsError::new(error_code)), + } +} + +fn switch_stdio(stdio: &str) -> Result<()> { + let stdin = libredox::Fd::open(stdio, O_RDONLY, 0)?; + let stdout = libredox::Fd::open(stdio, O_WRONLY, 0)?; + let stderr = libredox::Fd::open(stdio, O_WRONLY, 0)?; + + stdin.dup2(0, &[])?; + stdout.dup2(1, &[])?; + stderr.dup2(2, &[])?; + + Ok(()) +} + +pub fn run(file: &Path) -> Result<()> { + let file = File::open(file)?; + let reader = BufReader::new(file); + for line_res in reader.lines() { + let line_raw = line_res?; + let line = line_raw.trim(); + if !line.is_empty() && !line.starts_with('#') { + let mut args = line.split(' ').map(|arg| { + if arg.starts_with('$') { + env::var(&arg[1..]).unwrap_or(String::new()) + } else { + arg.to_string() + } + }); + + if let Some(cmd) = args.next() { + match cmd.as_str() { + "cd" => { + if let Some(dir) = args.next() { + if let Err(err) = env::set_current_dir(&dir) { + println!("init: failed to cd to '{}': {}", dir, err); + } + } else { + println!("init: failed to cd: no argument"); + } + } + "set-default-scheme" => { + if let Some(scheme) = args.next() { + if let Err(err) = set_default_scheme(&scheme) { + println!( + "init: failed to set default scheme to '{}': {}", + scheme, err + ); + } + } else { + println!("init: failed to set default scheme: no argument"); + } + } + "echo" => { + if let Some(arg) = args.next() { + print!("{}", arg); + } + for arg in args { + print!(" {}", arg); + } + print!("\n"); + } + "export" => { + if let Some(var) = args.next() { + let mut value = String::new(); + if let Some(arg) = args.next() { + value.push_str(&arg); + } + for arg in args { + value.push(' '); + value.push_str(&arg); + } + env::set_var(var, value); + } else { + println!("init: failed to export: no argument"); + } + } + "run" => { + if let Some(new_file) = args.next() { + if let Err(err) = run(&Path::new(&new_file)) { + println!("init: failed to run '{}': {}", new_file, err); + } + } else { + println!("init: failed to run: no argument"); + } + } + "run.d" => { + // This must be a BTreeMap to iterate in sorted order. + let mut entries = BTreeMap::new(); + let mut missing_arg = true; + + for new_dir in args { + if !Path::new(&new_dir).exists() { + // Skip non-existent dirs + continue; + } + missing_arg = false; + + match read_dir(&new_dir) { + Ok(list) => { + for entry_res in list { + match entry_res { + Ok(entry) => { + // This intentionally overwrites older entries with + // the same filename to allow overriding entries in + // one search dir with those in a later search dir. + entries.insert(entry.file_name(), entry.path()); + } + Err(err) => { + println!( + "init: failed to run.d: '{}': {}", + new_dir, err + ); + } + } + } + } + Err(err) => { + println!("init: failed to run.d: '{}': {}", new_dir, err); + } + } + } + + if missing_arg { + println!( + "init: failed to run.d: no argument or all dirs are non-existent" + ); + } else { + // This takes advantage of BTreeMap iterating in sorted order. + for (_, entry_path) in entries { + if let Err(err) = run(&entry_path) { + println!( + "init: failed to run '{}': {}", + entry_path.display(), + err + ); + } + } + } + } + "stdio" => { + if let Some(stdio) = args.next() { + if let Err(err) = switch_stdio(&stdio) { + println!("init: failed to switch stdio to '{}': {}", stdio, err); + } + } else { + println!("init: failed to set stdio: no argument"); + } + } + "unset" => { + for arg in args { + env::remove_var(&arg); + } + } + _ => { + let mut command = Command::new(cmd.clone()); + for arg in args { + command.arg(arg); + } + + match command.spawn() { + Ok(child) => match child.wait_with_output() { + Ok(output) => { + std::io::stdout() + .write_all(output.stdout.as_slice()) + .unwrap(); + std::io::stderr() + .write_all(output.stderr.as_slice()) + .unwrap(); + println!("{cmd} done."); + } + Err(err) => { + println!("init: failed to wait for '{}': {}", line, err) + } + }, + Err(err) => println!("init: failed to execute '{}': {}", line, err), + } + } + } + } + } + } + + Ok(()) +} + +pub fn main() { + if let Err(err) = set_default_scheme("initfs") { + println!("init: failed to set default scheme: {}", err); + } + + let config = "/etc/init.rc"; + if let Err(err) = run(&Path::new(config)) { + println!("init: failed to run {}: {}", config, err); + } + + libredox::call::setrens(0, 0).expect("init: failed to enter null namespace"); + + loop { + let mut status = 0; + libredox::call::waitpid(0, &mut status, 0).unwrap(); + } +}