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:
Jean Chevronnet 2026-08-23 02:40:53 +00:00
parent 3052e47212
commit 12a4ddb749
No known key found for this signature in database
GPG key ID: 439666D63A9477E4

View file

@ -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)
.ok()
.and_then(|s| s.trim().parse::<u32>().ok())
.filter(|&p| is_echoircd(p));
let Some(pid) = from_file.or_else(|| find_server(cfgpath, self_pid)) else {
eprintln!("echoircd: no running echoircd for {cfgpath} — start the server first.");
return 1; return 1;
}
}; };
// Verify the pid is a live echoircd — guards a stale pidfile or a reused pid.
let comm = std::fs::read_to_string(format!("/proc/{pid}/comm")).unwrap_or_default();
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;
}
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
} }