use core::{net, str}; use std::{ fs, io::{self, BufRead, BufReader, Write, stdout}, os::unix::net::{UnixListener, UnixStream}, process, sync::Arc, thread::{self, JoinHandle}, }; use anyhow::Context; use parking_lot::RwLockWriteGuard; use crate::config; // 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().context("failed to clone stream")?; let mut reader = BufReader::new(stream2); let stdin = io::stdin(); print!("> "); _ = stdout().flush(); for line in stdin.lines() { let Ok(line) = line else { break; }; _ = stream.write_all(line.as_bytes()); _ = stream.write_all(b"\n"); for line in (&mut reader).lines() { let Ok(line) = line else { return Ok(()); }; if line == "EOF" { break; } println!("{line}"); } print!("> "); _ = stdout().flush(); } Ok(()) } pub fn handle_daemon_client(mut stream: UnixStream, config: &Arc) { let Ok(stream2) = stream.try_clone() else { return; }; for line in BufReader::new(stream2).lines() { let Ok(line) = line else { return; }; let args = shlex::Shlex::new(line.trim()).collect::>(); // i hate it too if let [arg0, args @ ..] = &args[..] { match arg0.as_str() { "help" => { _ = writeln!( stream, "avaliable commands:\n help\n configcount\n confdump\n hosts ..." ); } "configcount" => { _ = writeln!( stream, "weak {}, strong {}", Arc::weak_count(config), Arc::strong_count(config) ); } "confdump" => _ = writeln!(stream, "{:#?}", config.as_ref()), "hosts" => { if let [arg0, args @ ..] = args { match arg0.as_str() { "list" => { _ = writeln!(stream, "aquiring read lock"); let rlock = config.hosts.read(); _ = writeln!(stream, "{rlock:#?}"); } "create" => { if let [name, value] = args { if let Ok([value, id, csrf]) = <_ as TryInto<[&str; 3]>>::try_into( value.split(',').take(3).collect::>(), ) { match value.parse::() { Ok(addr) => { _ = writeln!(stream, "aquiring write lock"); let mut wlock = config.hosts.write(); wlock.insert( name.to_string(), (addr, id.to_string(), csrf.to_string()), ); let rlock = RwLockWriteGuard::downgrade(wlock); _ = writeln!(stream, "{rlock:#?}"); } Err(err) => { _ = writeln!( stream, "err: parsing value as socket addres {err:?}" ); } } } else { _ = writeln!( stream, "err: value wasn't 3 comma separated values" ); } } else { _ = writeln!( stream, "invalid arg count, expected name and value" ); } } "delete" => { if let [name] = args { _ = writeln!(stream, "aquiring write lock"); let mut wlock = config.hosts.write(); wlock.remove(name); let lock = RwLockWriteGuard::downgrade(wlock); _ = writeln!(stream, "{lock:#?}"); } else { _ = writeln!(stream, "invalid arg count, expected a name"); } } _ => _ = writeln!(stream, "err: invalid argument {arg0:?}"), } } 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>> { config.ipc.clone().map(|ipc_path| { println!("starting ipc daemon at {ipc_path:#?}"); let listener = UnixListener::bind(&ipc_path).context("failed to bind to ipc socket")?; ctrlc::try_set_handler(move || { _ = fs::remove_file(&ipc_path); process::exit(2); }) .context("failed to set exit handler")?; println!("ipc daemon started"); Ok(thread::spawn(move || { for stream in listener.incoming() { match stream { Ok(stream) => { handle_daemon_client(stream, &config); } Err(err) => eprintln!("ipc daemon error: {err:#?}"), } } })) }) }