From e82f2076b8b832ea9dffa2404dd7a94b565ae588 Mon Sep 17 00:00:00 2001 From: Noratrieb <48135649+Noratrieb@users.noreply.github.com> Date: Sun, 15 Sep 2024 21:49:33 +0200 Subject: [PATCH] stuff --- Cargo.lock | 104 ++++++++++++++++++++++ Cargo.toml | 2 +- ahoy/build-root.sh | 14 ++- ahoy/run.sh | 2 + coreutils/Cargo.toml | 1 + coreutils/src/bin/user.rs | 26 ++++++ quarterdeck/Cargo.toml | 2 + quarterdeck/src/main.rs | 141 +++++++++++++++++++++++++++++- test-services/tcpecho/Cargo.toml | 7 ++ test-services/tcpecho/src/main.rs | 35 ++++++++ 10 files changed, 329 insertions(+), 5 deletions(-) create mode 100644 coreutils/src/bin/user.rs create mode 100644 test-services/tcpecho/Cargo.toml create mode 100644 test-services/tcpecho/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index e793739..ea94f40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -131,8 +131,15 @@ version = "0.1.0" dependencies = [ "clap", "eyre", + "rustix", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" version = "0.3.9" @@ -153,6 +160,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "heck" version = "0.5.0" @@ -165,6 +178,16 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" +[[package]] +name = "indexmap" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -253,6 +276,8 @@ version = "0.1.0" dependencies = [ "eyre", "rustix", + "serde", + "toml", "tracing", "tracing-subscriber", ] @@ -323,6 +348,35 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_spanned" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +dependencies = [ + "serde", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -361,6 +415,13 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tcpecho" +version = "0.1.0" +dependencies = [ + "eyre", +] + [[package]] name = "thread_local" version = "1.1.8" @@ -371,6 +432,40 @@ dependencies = [ "once_cell", ] +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tracing" version = "0.1.40" @@ -544,3 +639,12 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +dependencies = [ + "memchr", +] diff --git a/Cargo.toml b/Cargo.toml index 7da90c7..8902034 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "2" -members = [ "cog", "coreutils","quarterdeck"] +members = [ "cog", "coreutils","quarterdeck", "test-services/tcpecho"] [workspace.dependencies] eyre = "0.6.12" diff --git a/ahoy/build-root.sh b/ahoy/build-root.sh index 2259cb7..62ca21d 100755 --- a/ahoy/build-root.sh +++ b/ahoy/build-root.sh @@ -6,9 +6,8 @@ cd "$(dirname "$0")" target_tuple="$(uname -m)-unknown-linux-musl" -cargo build -p quarterdeck --target "$target_tuple" -cargo build -p cog --target "$target_tuple" -cargo build -p coreutils --target "$target_tuple" +cargo build --target "$target_tuple" \ + -p quarterdeck -p cog -p coreutils -p tcpecho target_dir="../target/$target_tuple/debug" @@ -25,6 +24,15 @@ install_bin quarterdeck install_bin cog install_bin net install_bin ls +install_bin user +install_bin tcpecho mkdir -p rootfs/etc cp /etc/resolv.conf rootfs/etc/resolv.conf + +mkdir -p rootfs/etc/services + +cat > rootfs/etc/services/tcpecho2000.toml < Result<()> { + let args = Cmd::parse(); + + match args.cmd { + Subcommand::Id => { + println!("{}", rustix::process::getuid().as_raw()); + } + } + + Ok(()) +} diff --git a/quarterdeck/Cargo.toml b/quarterdeck/Cargo.toml index 4df0d59..b955496 100644 --- a/quarterdeck/Cargo.toml +++ b/quarterdeck/Cargo.toml @@ -6,5 +6,7 @@ edition = "2021" [dependencies] eyre.workspace = true rustix = { version = "0.38.37", features = ["process", "thread"] } +serde = { version = "1.0.210", features = ["derive"] } +toml = "0.8.19" tracing = "0.1.40" tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } diff --git a/quarterdeck/src/main.rs b/quarterdeck/src/main.rs index b204dd1..58df552 100644 --- a/quarterdeck/src/main.rs +++ b/quarterdeck/src/main.rs @@ -1,4 +1,13 @@ -use eyre::Result; +use std::{ + fs::{DirEntry, File}, + path::Path, + process::Stdio, + time::Duration, +}; + +use eyre::{bail, eyre, Context, Result}; +use rustix::{io::Errno, process::WaitOptions}; +use serde::Deserialize; use tracing::{error, info, warn}; fn main() { @@ -17,6 +26,31 @@ fn run() -> Result<()> { warn!(?err, "Failed to set PR_SET_NO_NEW_PRIVS"); } + let result = std::thread::Builder::new() + .name("reaper".to_owned()) + .spawn(|| reaper()); + if let Err(err) = result { + error!(?err, "Failed to spawn reaper thread"); + } + + let services = read_services(Path::new("/etc/services")); + match services { + Ok((services, errors)) => { + for error in errors { + error!(?error, "Failed to read service"); + } + + for service in services { + std::thread::spawn(move || { + if let Err(err) = run_service(&service) { + error!(name = %service.name, ?err, "Failed to spawn service"); + } + }); + } + } + Err(err) => error!(?err, "Failed to read services"), + } + std::process::Command::new("/bin/cog") .env("PATH", "/bin") .spawn()? @@ -24,3 +58,108 @@ fn run() -> Result<()> { loop {} } + +fn reaper() { + loop { + let result = rustix::process::waitpid(None, WaitOptions::empty()); + if let Err(err) = result { + if err == Errno::CHILD { + // No children.. maybe we're gonna get children in the future? + // TODO: this is probably unnecessary if we do pid 1 properly? + std::thread::sleep(Duration::from_secs(1)); + continue; + } + error!(?err, "Failed to waitpid"); + return; + } + } +} + +#[derive(Deserialize)] +struct Service { + name: String, + exec: Vec, +} + +fn run_service(service: &Service) -> Result<()> { + info!(name = %service.name, "Starting service"); + + std::fs::create_dir_all("/var/logs").wrap_err("creating log base directory")?; + let logs = File::options() + .create(true) + .append(true) + .open(Path::new("/var/logs").join(&service.name)) + .wrap_err("opening log file")?; + + let mut backoff_ms = 1; + loop { + let mut cmd = std::process::Command::new(&service.exec[0]); + cmd.args(&service.exec[1..]); + + cmd.stdin(Stdio::null()); + cmd.stdout(Stdio::from(logs.try_clone().wrap_err("duping log file")?)); + cmd.stderr(Stdio::from(logs.try_clone().wrap_err("duping log file")?)); + + let mut child = cmd + .spawn() + .wrap_err_with(|| format!("spawning command {}", service.exec[0]))?; + + let result = child.wait().wrap_err("waiting for child failed")?; + if result.success() { + info!(name = %service.name, "Service finished, restarting in {backoff_ms}ms"); + } else { + info!(name = %service.name, code = %result.code().unwrap_or(-1), "Service errored, restarting in {backoff_ms}ms"); + } + std::thread::sleep(Duration::from_millis(backoff_ms)); + backoff_ms *= 10; + } +} + +fn read_services(path: &Path) -> Result<(Vec, Vec)> { + let service_files = + std::fs::read_dir(path).wrap_err_with(|| format!("reading {}", path.display()))?; + + let mut services: Vec = vec![]; + let mut errors = vec![]; + + for service_file in service_files { + match read_service_file(service_file) { + Ok(service) => { + if services + .iter() + .any(|old_service| old_service.name == service.name) + { + errors.push(eyre!("service name found twice: {}", service.name)) + } else { + services.push(service) + } + } + Err(err) => errors.push(err), + } + } + + Ok((services, errors)) +} + +fn read_service_file(service_file: std::io::Result) -> Result { + let service_file = service_file.wrap_err("failed to read services")?.path(); + let service_config = std::fs::read(&service_file) + .wrap_err_with(|| format!("reading {}", service_file.display()))?; + let service_config = std::str::from_utf8(&service_config).wrap_err_with(|| { + format!( + "service config for {} is invalid UTF-8", + service_file.display() + ) + })?; + let service = toml::from_str::(service_config) + .wrap_err_with(|| format!("service config for {} is invalid", service_file.display()))?; + + if service.exec.len() < 1 { + bail!( + "service config for {} is invalid, must contain at least one element in exec", + service_file.display() + ); + } + + Ok(service) +} diff --git a/test-services/tcpecho/Cargo.toml b/test-services/tcpecho/Cargo.toml new file mode 100644 index 0000000..603e632 --- /dev/null +++ b/test-services/tcpecho/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "tcpecho" +version = "0.1.0" +edition = "2021" + +[dependencies] +eyre.workspace = true diff --git a/test-services/tcpecho/src/main.rs b/test-services/tcpecho/src/main.rs new file mode 100644 index 0000000..d4972a2 --- /dev/null +++ b/test-services/tcpecho/src/main.rs @@ -0,0 +1,35 @@ +use std::{ + io::{Read, Write}, + net::{TcpListener, TcpStream}, +}; + +use eyre::{Context, Result}; + +fn main() -> Result<()> { + let addr = std::env::var("BIND_ADDR") + .ok() + .or(std::env::args().nth(1)) + .unwrap_or("127.0.0.1:1000".to_owned()); + println!("Listening on address {addr}"); + let stream = TcpListener::bind(&addr).wrap_err_with(|| format!("binding socket on {addr}"))?; + + loop { + let next = stream.accept()?; + std::thread::spawn(move || { + if let Err(err) = echo(next.0) { + eprintln!("{err:?}"); + } + }); + } +} + +fn echo(mut stream: TcpStream) -> Result<()> { + let mut buf = [0; 1024]; + loop { + let read = stream.read(&mut buf)?; + if read == 0 { + return Ok(()); + } + stream.write_all(&buf[..read])?; + } +}