use std::process::Command;
pub fn process_alive(pid: u32) -> bool {
#[cfg(target_os = "linux")]
{
std::path::Path::new(&format!("/proc/{pid}")).exists()
}
#[cfg(all(unix, not(target_os = "linux")))]
{
Command::new("kill")
.args(["-0", &pid.to_string()])
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.map(|s| s.success())
.unwrap_or(false)
}
#[cfg(windows)]
{
let out = Command::new("tasklist.exe")
.args(["/FI", &format!("PID eq {pid}"), "/FO", "CSV", "/NH"])
.output();
match out {
Ok(o) if o.status.success() => {
let s = String::from_utf8_lossy(&o.stdout);
let trimmed = s.trim();
!trimmed.is_empty() && !trimmed.starts_with("INFO:")
}
_ => false,
}
}
}
#[cfg_attr(not(windows), allow(dead_code))]
pub(crate) fn cmdline_role(pattern: &str) -> &str {
pattern.strip_prefix("wire ").unwrap_or(pattern)
}
pub fn find_processes_by_cmdline(pattern: &str) -> Vec<u32> {
#[cfg(unix)]
{
Command::new("pgrep")
.args(["-f", pattern])
.output()
.ok()
.filter(|o| o.status.success())
.map(|o| {
String::from_utf8_lossy(&o.stdout)
.split_whitespace()
.filter_map(|s| s.parse::<u32>().ok())
.collect()
})
.unwrap_or_default()
}
#[cfg(windows)]
{
let role = cmdline_role(pattern);
let escaped = role.replace('\'', "''");
let ps = format!(
"Get-CimInstance Win32_Process | \
Where-Object {{ $_.Name -like 'wire*' -and $_.ProcessId -ne $PID -and $_.CommandLine -like '*{escaped}*' }} | \
Select-Object -ExpandProperty ProcessId"
);
Command::new("powershell.exe")
.args(["-NoProfile", "-NonInteractive", "-Command", &ps])
.output()
.ok()
.filter(|o| o.status.success())
.map(|o| {
String::from_utf8_lossy(&o.stdout)
.split_whitespace()
.filter_map(|s| s.parse::<u32>().ok())
.collect()
})
.unwrap_or_default()
}
#[cfg(not(any(unix, windows)))]
{
let _ = pattern;
Vec::new()
}
}
pub fn pid_cmdline(pid: u32) -> Option<String> {
#[cfg(target_os = "linux")]
{
let path = format!("/proc/{pid}/cmdline");
let bytes = std::fs::read(&path).ok()?;
let s: String = bytes
.into_iter()
.map(|b| if b == 0 { b' ' } else { b })
.map(|b| b as char)
.collect();
let trimmed = s.trim().to_string();
if trimmed.is_empty() {
None
} else {
Some(trimmed)
}
}
#[cfg(all(unix, not(target_os = "linux")))]
{
let out = Command::new("ps")
.args(["-p", &pid.to_string(), "-o", "command="])
.output()
.ok()?;
if !out.status.success() {
return None;
}
let s = String::from_utf8_lossy(&out.stdout).trim().to_string();
if s.is_empty() { None } else { Some(s) }
}
#[cfg(windows)]
{
let ps = format!(
"Get-CimInstance Win32_Process | \
Where-Object {{ $_.ProcessId -eq {pid} }} | \
Select-Object -ExpandProperty CommandLine"
);
let out = Command::new("powershell.exe")
.args(["-NoProfile", "-NonInteractive", "-Command", &ps])
.output()
.ok()?;
if !out.status.success() {
return None;
}
let s = String::from_utf8_lossy(&out.stdout).trim().to_string();
if s.is_empty() { None } else { Some(s) }
}
#[cfg(not(any(unix, windows)))]
{
let _ = pid;
None
}
}
pub fn parse_session_arg(cmdline: &str) -> Option<&str> {
let parts: Vec<&str> = cmdline.split_whitespace().collect();
let i = parts.iter().position(|p| *p == "--session")?;
parts.get(i + 1).copied()
}
pub fn kill_process(pid: u32, force: bool) -> bool {
#[cfg(unix)]
{
let sig = if force { "-9" } else { "-15" };
Command::new("kill")
.args([sig, &pid.to_string()])
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.map(|s| s.success())
.unwrap_or(false)
}
#[cfg(windows)]
{
let pid_str = pid.to_string();
let mut args: Vec<&str> = vec!["/PID", &pid_str, "/T"];
if force {
args.push("/F");
}
Command::new("taskkill.exe")
.args(&args)
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.map(|s| s.success())
.unwrap_or(false)
}
#[cfg(not(any(unix, windows)))]
{
let _ = (pid, force);
false
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cmdline_role_strips_wire_prefix() {
assert_eq!(cmdline_role("wire daemon"), "daemon");
assert_eq!(cmdline_role("wire relay-server"), "relay-server");
assert_eq!(cmdline_role("daemon"), "daemon");
assert_eq!(cmdline_role("relay-server"), "relay-server");
}
#[test]
fn process_alive_returns_true_for_self() {
let me = std::process::id();
assert!(
process_alive(me),
"process_alive should return true for self pid {me}"
);
}
#[test]
fn process_alive_returns_false_for_clearly_dead_pid() {
let dead = 4_000_000_001;
assert!(
!process_alive(dead),
"process_alive should return false for synthetic dead pid {dead}"
);
}
#[test]
fn parse_session_arg_extracts_following_value() {
assert_eq!(
parse_session_arg("wire daemon --session slancha-mesh --interval 5"),
Some("slancha-mesh")
);
assert_eq!(
parse_session_arg("wire daemon --interval 5 --session wire-dev"),
Some("wire-dev")
);
assert_eq!(
parse_session_arg("/path/to/wire daemon --session foo"),
Some("foo")
);
}
#[test]
fn parse_session_arg_returns_none_without_flag() {
assert_eq!(parse_session_arg("wire daemon --interval 5"), None);
assert_eq!(
parse_session_arg("wire daemon --all-sessions --interval 5"),
None
);
assert_eq!(parse_session_arg(""), None);
}
#[test]
fn parse_session_arg_returns_none_when_flag_is_last_token() {
assert_eq!(parse_session_arg("wire daemon --session"), None);
}
#[test]
fn pid_cmdline_returns_something_for_self() {
let me = std::process::id();
let cmd = pid_cmdline(me);
assert!(
cmd.is_some() && !cmd.as_ref().unwrap().is_empty(),
"pid_cmdline(self) should return a non-empty cmdline, got {cmd:?}"
);
}
#[test]
fn pid_cmdline_returns_none_for_dead_pid() {
let dead = 4_000_000_003;
assert_eq!(
pid_cmdline(dead),
None,
"pid_cmdline should return None for synthetic dead pid"
);
}
#[test]
fn kill_process_on_nonexistent_pid_returns_false_or_noop() {
let dead = 4_000_000_002;
let _ = kill_process(dead, false);
}
}