This commit is contained in:
nora 2024-09-15 21:49:33 +02:00
parent f0ffa77500
commit e82f2076b8
10 changed files with 329 additions and 5 deletions

104
Cargo.lock generated
View file

@ -131,8 +131,15 @@ version = "0.1.0"
dependencies = [ dependencies = [
"clap", "clap",
"eyre", "eyre",
"rustix",
] ]
[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]] [[package]]
name = "errno" name = "errno"
version = "0.3.9" version = "0.3.9"
@ -153,6 +160,12 @@ dependencies = [
"once_cell", "once_cell",
] ]
[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]] [[package]]
name = "heck" name = "heck"
version = "0.5.0" version = "0.5.0"
@ -165,6 +178,16 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" 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]] [[package]]
name = "is_terminal_polyfill" name = "is_terminal_polyfill"
version = "1.70.1" version = "1.70.1"
@ -253,6 +276,8 @@ version = "0.1.0"
dependencies = [ dependencies = [
"eyre", "eyre",
"rustix", "rustix",
"serde",
"toml",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
] ]
@ -323,6 +348,35 @@ dependencies = [
"windows-sys", "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]] [[package]]
name = "sharded-slab" name = "sharded-slab"
version = "0.1.7" version = "0.1.7"
@ -361,6 +415,13 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "tcpecho"
version = "0.1.0"
dependencies = [
"eyre",
]
[[package]] [[package]]
name = "thread_local" name = "thread_local"
version = "1.1.8" version = "1.1.8"
@ -371,6 +432,40 @@ dependencies = [
"once_cell", "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]] [[package]]
name = "tracing" name = "tracing"
version = "0.1.40" version = "0.1.40"
@ -544,3 +639,12 @@ name = "windows_x86_64_msvc"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f"
dependencies = [
"memchr",
]

View file

@ -1,6 +1,6 @@
[workspace] [workspace]
resolver = "2" resolver = "2"
members = [ "cog", "coreutils","quarterdeck"] members = [ "cog", "coreutils","quarterdeck", "test-services/tcpecho"]
[workspace.dependencies] [workspace.dependencies]
eyre = "0.6.12" eyre = "0.6.12"

View file

@ -6,9 +6,8 @@ cd "$(dirname "$0")"
target_tuple="$(uname -m)-unknown-linux-musl" target_tuple="$(uname -m)-unknown-linux-musl"
cargo build -p quarterdeck --target "$target_tuple" cargo build --target "$target_tuple" \
cargo build -p cog --target "$target_tuple" -p quarterdeck -p cog -p coreutils -p tcpecho
cargo build -p coreutils --target "$target_tuple"
target_dir="../target/$target_tuple/debug" target_dir="../target/$target_tuple/debug"
@ -25,6 +24,15 @@ install_bin quarterdeck
install_bin cog install_bin cog
install_bin net install_bin net
install_bin ls install_bin ls
install_bin user
install_bin tcpecho
mkdir -p rootfs/etc mkdir -p rootfs/etc
cp /etc/resolv.conf rootfs/etc/resolv.conf cp /etc/resolv.conf rootfs/etc/resolv.conf
mkdir -p rootfs/etc/services
cat > rootfs/etc/services/tcpecho2000.toml <<EOF
name = "tcpecho2000"
exec = [ "/bin/tcpecho", "0.0.0.0:2000" ]
EOF

View file

@ -10,4 +10,6 @@ exec bwrap \
--unshare-uts \ --unshare-uts \
--unshare-cgroup \ --unshare-cgroup \
--as-pid-1 \ --as-pid-1 \
--uid 1 \
--gid 1 \
/bin/quarterdeck /bin/quarterdeck

View file

@ -6,3 +6,4 @@ edition = "2021"
[dependencies] [dependencies]
clap = { version = "4.5.17", features = ["derive"] } clap = { version = "4.5.17", features = ["derive"] }
eyre.workspace = true eyre.workspace = true
rustix = { version = "0.38.37", features = ["process"] }

26
coreutils/src/bin/user.rs Normal file
View file

@ -0,0 +1,26 @@
use clap::Parser;
use eyre::Result;
#[derive(Parser)]
struct Cmd {
#[command(subcommand)]
cmd: Subcommand,
}
#[derive(clap::Subcommand)]
enum Subcommand {
/// Get the current user's uid
Id,
}
fn main() -> Result<()> {
let args = Cmd::parse();
match args.cmd {
Subcommand::Id => {
println!("{}", rustix::process::getuid().as_raw());
}
}
Ok(())
}

View file

@ -6,5 +6,7 @@ edition = "2021"
[dependencies] [dependencies]
eyre.workspace = true eyre.workspace = true
rustix = { version = "0.38.37", features = ["process", "thread"] } rustix = { version = "0.38.37", features = ["process", "thread"] }
serde = { version = "1.0.210", features = ["derive"] }
toml = "0.8.19"
tracing = "0.1.40" tracing = "0.1.40"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }

View file

@ -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}; use tracing::{error, info, warn};
fn main() { fn main() {
@ -17,6 +26,31 @@ fn run() -> Result<()> {
warn!(?err, "Failed to set PR_SET_NO_NEW_PRIVS"); 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") std::process::Command::new("/bin/cog")
.env("PATH", "/bin") .env("PATH", "/bin")
.spawn()? .spawn()?
@ -24,3 +58,108 @@ fn run() -> Result<()> {
loop {} 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<String>,
}
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<Service>, Vec<eyre::Report>)> {
let service_files =
std::fs::read_dir(path).wrap_err_with(|| format!("reading {}", path.display()))?;
let mut services: Vec<Service> = 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<DirEntry>) -> Result<Service> {
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>(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)
}

View file

@ -0,0 +1,7 @@
[package]
name = "tcpecho"
version = "0.1.0"
edition = "2021"
[dependencies]
eyre.workspace = true

View file

@ -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])?;
}
}