rehash: fall back to a /proc scan for the running server when the pidfile is stale, and self-heal it
This commit is contained in:
parent
3052e47212
commit
12a4ddb749
1 changed files with 60 additions and 16 deletions
76
src/main.rs
76
src/main.rs
|
|
@ -20,9 +20,51 @@ use echoircd::ircd::{Event, Ircd};
|
||||||
use echoircd::socketengine;
|
use echoircd::socketengine;
|
||||||
use echoircd::tls::{OpensslBackend, TlsBackend};
|
use echoircd::tls::{OpensslBackend, TlsBackend};
|
||||||
|
|
||||||
/// `echoircd rehash [config]`: read the running server's pidfile and send it
|
/// Is `pid` a live echoircd process?
|
||||||
/// SIGHUP so it reloads its config in place, then exit. No unsafe — the signal is
|
fn is_echoircd(pid: u32) -> bool {
|
||||||
/// sent via the `kill` command.
|
std::fs::read_to_string(format!("/proc/{pid}/comm"))
|
||||||
|
.map(|c| c.trim() == "echoircd")
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find the running echoircd server for `cfgpath` by scanning /proc — the fallback
|
||||||
|
/// when the pidfile is missing or stale (a throwaway instance clobbered it).
|
||||||
|
/// Prefers a process whose command line names the same config; else a lone server.
|
||||||
|
fn find_server(cfgpath: &str, self_pid: u32) -> Option<u32> {
|
||||||
|
let want = std::fs::canonicalize(cfgpath).ok();
|
||||||
|
let mut servers: Vec<(u32, bool)> = Vec::new();
|
||||||
|
for entry in std::fs::read_dir("/proc").ok()?.flatten() {
|
||||||
|
let Ok(pid) = entry.file_name().to_string_lossy().parse::<u32>() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if pid == self_pid || !is_echoircd(pid) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let raw = std::fs::read(format!("/proc/{pid}/cmdline")).unwrap_or_default();
|
||||||
|
let args: Vec<String> = raw
|
||||||
|
.split(|&b| b == 0)
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.map(|s| String::from_utf8_lossy(s).into_owned())
|
||||||
|
.collect();
|
||||||
|
if args.iter().any(|a| a == "rehash") {
|
||||||
|
continue; // a `rehash` CLI invocation, not the server
|
||||||
|
}
|
||||||
|
let matches = args
|
||||||
|
.iter()
|
||||||
|
.skip(1)
|
||||||
|
.any(|a| a == cfgpath || (want.is_some() && std::fs::canonicalize(a).ok() == want));
|
||||||
|
servers.push((pid, matches));
|
||||||
|
}
|
||||||
|
servers
|
||||||
|
.iter()
|
||||||
|
.find(|(_, m)| *m)
|
||||||
|
.map(|(p, _)| *p)
|
||||||
|
.or_else(|| (servers.len() == 1).then(|| servers[0].0))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `echoircd rehash [config]`: locate the running server (pidfile fast-path, else
|
||||||
|
/// a /proc scan) and send it SIGHUP so it reloads its config in place. No unsafe —
|
||||||
|
/// the signal is sent via the `kill` command.
|
||||||
fn rehash_cli(cfgpath: &str) -> i32 {
|
fn rehash_cli(cfgpath: &str) -> i32 {
|
||||||
let cfg = Config::load(cfgpath);
|
let cfg = Config::load(cfgpath);
|
||||||
let pidfile = cfg
|
let pidfile = cfg
|
||||||
|
|
@ -31,22 +73,20 @@ fn rehash_cli(cfgpath: &str) -> i32 {
|
||||||
.and_then(|v| v.first())
|
.and_then(|v| v.first())
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or_else(|| "echoircd.pid".to_string());
|
.unwrap_or_else(|| "echoircd.pid".to_string());
|
||||||
let pid = match std::fs::read_to_string(&pidfile) {
|
let self_pid = std::process::id();
|
||||||
Ok(s) => s.trim().to_string(),
|
// Trust the pidfile only if it names a live echoircd; otherwise find the server
|
||||||
Err(_) => {
|
// via /proc, since a throwaway instance sharing this config may have clobbered it.
|
||||||
eprintln!("echoircd: no pidfile at {pidfile} — is the server running?");
|
let from_file = std::fs::read_to_string(&pidfile)
|
||||||
return 1;
|
.ok()
|
||||||
}
|
.and_then(|s| s.trim().parse::<u32>().ok())
|
||||||
};
|
.filter(|&p| is_echoircd(p));
|
||||||
// Verify the pid is a live echoircd — guards a stale pidfile or a reused pid.
|
let Some(pid) = from_file.or_else(|| find_server(cfgpath, self_pid)) else {
|
||||||
let comm = std::fs::read_to_string(format!("/proc/{pid}/comm")).unwrap_or_default();
|
eprintln!("echoircd: no running echoircd for {cfgpath} — start the server first.");
|
||||||
if pid.parse::<u32>().is_err() || comm.trim() != "echoircd" {
|
|
||||||
eprintln!("echoircd: no running echoircd for pid {pid} (stale {pidfile}?) — start the server first.");
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
};
|
||||||
println!("rehashing server config file.");
|
println!("rehashing server config file.");
|
||||||
let sent = std::process::Command::new("kill")
|
let sent = std::process::Command::new("kill")
|
||||||
.args(["-s", "HUP", &pid])
|
.args(["-s", "HUP", &pid.to_string()])
|
||||||
.stderr(std::process::Stdio::null())
|
.stderr(std::process::Stdio::null())
|
||||||
.status()
|
.status()
|
||||||
.map(|st| st.success())
|
.map(|st| st.success())
|
||||||
|
|
@ -55,6 +95,10 @@ fn rehash_cli(cfgpath: &str) -> i32 {
|
||||||
eprintln!("echoircd: could not signal pid {pid}.");
|
eprintln!("echoircd: could not signal pid {pid}.");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
// Self-heal a stale/clobbered pidfile so the fast path works next time.
|
||||||
|
if from_file != Some(pid) && !pidfile.is_empty() {
|
||||||
|
let _ = std::fs::write(&pidfile, format!("{pid}\n"));
|
||||||
|
}
|
||||||
println!("server configuration is reloaded.");
|
println!("server configuration is reloaded.");
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue