trpha/src/ipc/mod.rs

178 lines
6.8 KiB
Rust

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<config::Schema>) {
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::<Vec<_>>();
// 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 <list|create|delete> ..."
);
}
"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::<Vec<_>>(),
)
{
match value.parse::<net::SocketAddr>() {
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<config::Schema>) -> Option<anyhow::Result<JoinHandle<()>>> {
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:#?}"),
}
}
}))
})
}