From 716707f1bc93610ab8aa1e827258bc988d66e522 Mon Sep 17 00:00:00 2001 From: javalsai Date: Wed, 2 Jul 2025 04:08:41 +0200 Subject: [PATCH] chore: great error handling --- Cargo.lock | 28 +++++++++++++++++ Cargo.toml | 2 ++ src/constants.rs | 1 + src/ipc/mod.rs | 53 +++++++++++++++---------------- src/main.rs | 82 +++++++++++++++++++++++++++++------------------- 5 files changed, 106 insertions(+), 60 deletions(-) create mode 100644 src/constants.rs diff --git a/Cargo.lock b/Cargo.lock index d8a52ef..e01f82b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -52,6 +52,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + [[package]] name = "autocfg" version = "1.5.0" @@ -327,6 +333,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "toml" version = "0.8.23" @@ -373,12 +399,14 @@ name = "trpha" version = "0.1.0" dependencies = [ "anstyle", + "anyhow", "clap", "ctrlc", "httparse", "parking_lot", "serde", "shlex", + "thiserror", "toml", ] diff --git a/Cargo.toml b/Cargo.toml index 36071db..c03d6e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,12 +5,14 @@ edition = "2024" [dependencies] anstyle = "1" +anyhow = "1" clap = { version = "4", features = ["derive", "string"] } ctrlc = "3" httparse = "1" parking_lot = { version = "0", features = ["arc_lock", "serde"] } serde = { version = "1", features = ["derive"] } shlex = "1" +thiserror = "2" toml = "0" [features] diff --git a/src/constants.rs b/src/constants.rs new file mode 100644 index 0000000..ef9d11a --- /dev/null +++ b/src/constants.rs @@ -0,0 +1 @@ +pub const DEFAULT_CONFIG: &[u8] = include_bytes!("../config.default.toml"); diff --git a/src/ipc/mod.rs b/src/ipc/mod.rs index b72b566..18a2c39 100644 --- a/src/ipc/mod.rs +++ b/src/ipc/mod.rs @@ -8,15 +8,17 @@ use std::{ thread::{self, JoinHandle}, }; +use anyhow::Context; use parking_lot::RwLockWriteGuard; use crate::config; -pub fn start_client(config: config::Schema) { - let mut stream = UnixStream::connect(config.ipc.expect("no ipc socket specified")) - .expect("failed to connect to unix socket"); +// TODO: make more anyhow +pub fn start_client(config: config::Schema) -> anyhow::Result<()> { + let mut stream = UnixStream::connect(config.ipc.context("no ipc socket specified")?) + .context("failed to connect to unix socket")?; - let stream2 = stream.try_clone().expect("failed to clone stream"); + let stream2 = stream.try_clone().context("failed to clone stream")?; let mut reader = BufReader::new(stream2); let stdin = io::stdin(); @@ -32,7 +34,7 @@ pub fn start_client(config: config::Schema) { for line in (&mut reader).lines() { let Ok(line) = line else { - return; + return Ok(()); }; if line == "EOF" { break; @@ -43,9 +45,11 @@ pub fn start_client(config: config::Schema) { print!("> "); _ = stdout().flush(); } + + Ok(()) } -pub fn handle_daemon_client(mut stream: UnixStream, config: Arc) { +pub fn handle_daemon_client(mut stream: UnixStream, config: &Arc) { let Ok(stream2) = stream.try_clone() else { return; }; @@ -68,13 +72,11 @@ pub fn handle_daemon_client(mut stream: UnixStream, config: Arc) _ = writeln!( stream, "weak {}, strong {}", - Arc::weak_count(&config), - Arc::strong_count(&config) + Arc::weak_count(config), + Arc::strong_count(config) ); } - "confdump" => { - _ = writeln!(stream, "{:#?}", config.as_ref()); - } + "confdump" => _ = writeln!(stream, "{:#?}", config.as_ref()), "hosts" => { if let [arg0, args @ ..] = args { match arg0.as_str() { @@ -107,13 +109,13 @@ pub fn handle_daemon_client(mut stream: UnixStream, config: Arc) "err: parsing value as socket addres {err:?}" ); } - }; + } } else { _ = writeln!( stream, "err: value wasn't 3 comma separated values" ); - }; + } } else { _ = writeln!( stream, @@ -136,43 +138,40 @@ pub fn handle_daemon_client(mut stream: UnixStream, config: Arc) } } else { _ = writeln!(stream, "err: not enough arguments"); - }; + } } _ => { _ = writeln!(stream, "command {arg0:?} doesn't exist, type \"help\""); } - }; - }; + } + } _ = stream.write_all(b"EOF\n"); // BEL char } } -pub fn handle_daemon(config: Arc) -> Option> { - if let Some(ipc_path) = config.ipc.as_ref() { +pub fn handle_daemon(config: Arc) -> Option>> { + config.ipc.clone().map(|ipc_path| { println!("starting ipc daemon at {ipc_path:#?}"); - let listener = UnixListener::bind(ipc_path.clone()).expect("failed to bind to ipc socket"); - let ipc_path_clone = ipc_path.clone(); + let listener = UnixListener::bind(&ipc_path).context("failed to bind to ipc socket")?; ctrlc::try_set_handler(move || { - _ = fs::remove_file(&ipc_path_clone); + _ = fs::remove_file(&ipc_path); process::exit(2); }) - .expect("failed to set exit handler"); + .context("failed to set exit handler")?; println!("ipc daemon started"); - Some(thread::spawn(move || { + Ok(thread::spawn(move || { for stream in listener.incoming() { match stream { Ok(stream) => { - handle_daemon_client(stream, config.clone()); + handle_daemon_client(stream, &config); } Err(err) => eprintln!("ipc daemon error: {err:#?}"), } } })) - } else { - None - } + }) } diff --git a/src/main.rs b/src/main.rs index 3373f60..233126b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,11 @@ //! # Tuxcord Reverse Proxy Header Authenthication #![feature( - rwlock_downgrade, - try_blocks, + anonymous_lifetime_in_impl_trait, iterator_try_collect, - anonymous_lifetime_in_impl_trait + rwlock_downgrade, + try_blocks )] +#![allow(clippy::missing_errors_doc)] use std::{ fs::File, @@ -14,20 +15,22 @@ use std::{ thread, }; +use anyhow::Context; use clap::Parser; +use thiserror::Error; use crate::utils::headers::HeadersExt; pub mod args; pub mod config; +pub mod constants; #[cfg(feature = "ipc")] pub mod ipc; pub mod utils; use args::Args; -const DEFAULT_CONFIG: &[u8] = include_bytes!("../config.default.toml"); -fn main() { +fn main() -> anyhow::Result<()> { let args = Args::parse(); if !args.config.exists() { @@ -36,27 +39,27 @@ fn main() { &args.config ); File::create(args.config) - .expect("failure creating the config file") - .write_all(DEFAULT_CONFIG) - .expect("failure writting the contents to the config file"); - return; + .context("failure creating the config file")? + .write_all(constants::DEFAULT_CONFIG) + .context("failure writting the contents to the config file")?; + return Ok(()); } - let mut config_file = File::open(&args.config).expect("failure opening the config file"); + let mut config_file = File::open(&args.config).context("failure opening the config file")?; let mut config = String::new(); config_file .read_to_string(&mut config) - .expect("failure reading the config file"); - let config: config::Schema = toml::from_str(&config).expect("invalid config file"); + .context("failure reading the config file")?; + let config: config::Schema = toml::from_str(&config).context("invalid config file")?; #[cfg(feature = "ipc")] if let Some(args::Commands::Ipc) = args.command { - ipc::start_client(config); - return; + ipc::start_client(config)?; + return Ok(()); } println!("config: {config:#?}"); - let listener = TcpListener::bind(config.listen_at).expect("failure tcp listening"); + let listener = TcpListener::bind(config.listen_at).context("failure tcp listening")?; let config_arc = Arc::new(config); // will also serve as a counter #[cfg(feature = "ipc")] @@ -68,7 +71,7 @@ fn main() { let config_arc = config_arc.clone(); thread::spawn(|| { if let Err(err) = handle_client(&mut client, config_arc.as_ref()) { - eprintln!("err: invalid req head ({err:?}), closing..."); + eprintln!("err: invalid req head ({err}), closing..."); _ = client.shutdown(Shutdown::Both); } drop(client); @@ -82,7 +85,23 @@ fn main() { unreachable!("listener had to be killed unexpectedly"); } -fn handle_client(client: &mut TcpStream, config: &config::Schema) -> Result<(), &'static str> { +#[derive(Error, Debug)] +pub enum ClientError { + #[error("error reading request head: {0}")] + HeadReadError(io::Error), + #[error("failed to find \"host\" header")] + HostHeaderNotFound, + #[error("requested host {0:?}, but it's not registered")] + HostNotRegistered(String), + #[error("failed to connect to backend: {0}")] + BackendConnectFail(io::Error), + #[error("io error exchanging client and backend: {0}")] + ExchangeIoError(io::Error), +} + +fn handle_client(client: &mut TcpStream, config: &config::Schema) -> Result<(), ClientError> { + use ClientError as E; + let mut header_buf = [0u8; 1024 * 8]; let mut read_pos = 0usize; @@ -91,9 +110,9 @@ fn handle_client(client: &mut TcpStream, config: &config::Schema) -> Result<(), headers = [httparse::EMPTY_HEADER; 16]; let mut req = httparse::Request::new(&mut headers); - let Ok(n) = client.read(&mut header_buf[read_pos..]) else { - return Err("error reading stream"); - }; + let n = client + .read(&mut header_buf[read_pos..]) + .map_err(E::HeadReadError)?; read_pos += n; if let Ok(httparse::Status::Complete(n)) = req.parse(&header_buf[0..read_pos]) { @@ -103,31 +122,28 @@ fn handle_client(client: &mut TcpStream, config: &config::Schema) -> Result<(), let theres_body = req.headers.has_any(["conten-length", "transfer-encoding"]); - let Some(host_header) = req.headers.get("host") else { - return Err("failed to find \"host\" header"); - }; + let host_header = req.headers.get("host").ok_or(E::HostHeaderNotFound)?; // Now find that header and pas everything let read_hosts = config.hosts.read(); - let Some((addr, _, _)) = read_hosts.get(host_header.as_ref()) else { - return Err("host not in hashmap"); - }; + let (addr, _, _) = read_hosts + .get(host_header.as_ref()) + .ok_or_else(|| E::HostNotRegistered(host_header.to_string()))?; - let Ok(mut stream) = TcpStream::connect(addr) else { - return Err("failed to connect to the hashmap address"); - }; + let mut stream = TcpStream::connect(addr).map_err(E::BackendConnectFail)?; drop(read_hosts); - let Ok(_): io::Result<()> = (try { + let r: io::Result<()> = try { stream.write_all(&header_buf[0..pos])?; // here we sent all original headers, append our own (TODO) - stream.write_all(&header_buf[pos..read_pos])?; // send our overhead + + // send our overhead + stream.write_all(&header_buf[pos..read_pos])?; if theres_body { io::copy(client, &mut stream)?; } io::copy(&mut stream, client)?; - }) else { - return Err("io error exchanging head and/or body"); }; + r.map_err(E::ExchangeIoError)?; Ok(()) }