use anyhow::{Result, anyhow};
use std::path::PathBuf;
use std::thread;
use std::time::Duration;
use sysinfo::{ProcessRefreshKind, ProcessesToUpdate, RefreshKind, System};
pub mod encoding;
pub mod lock;
pub use lock::{FileLockInfo, FileLockProcess, find_processes_by_file, is_file_locked};
fn create_process_system() -> System {
System::new_with_specifics(RefreshKind::new().with_processes(ProcessRefreshKind::everything()))
}
fn refresh_processes(sys: &mut System) {
sys.refresh_processes_specifics(ProcessesToUpdate::All, ProcessRefreshKind::everything());
}
pub fn kill_process(pid: u32) -> Result<()> {
let sys = create_process_system();
let pid_obj = sysinfo::Pid::from_u32(pid);
if let Some(process) = sys.process(pid_obj) {
if process.kill() {
Ok(())
} else {
Err(anyhow!(
"Failed to kill process {pid} (administrator privileges may be required)"
))
}
} else {
Err(anyhow!("Process {pid} does not exist"))
}
}
pub fn kill_processes(pids: &[u32]) -> Vec<(u32, Result<()>)> {
pids.iter().map(|&pid| (pid, kill_process(pid))).collect()
}
pub fn kill_process_force(pid: u32) -> Result<()> {
let mut sys = create_process_system();
let pid_obj = sysinfo::Pid::from_u32(pid);
if sys.process(pid_obj).is_none() {
return Ok(());
}
for attempt in 1..=3 {
if let Some(process) = sys.process(pid_obj) {
if process.kill() {
thread::sleep(Duration::from_millis(500));
refresh_processes(&mut sys);
if !sys.processes().contains_key(&pid_obj) {
return Ok(());
}
} else if attempt == 3 {
return Err(anyhow!(
"Failed to force kill process {pid} (administrator privileges may be required)"
));
}
} else {
return Ok(());
}
if attempt < 3 {
thread::sleep(Duration::from_millis(1000));
refresh_processes(&mut sys);
}
}
Err(anyhow!(
"Force kill of process {pid} failed, the process may still be running"
))
}
pub fn kill_processes_force(pids: &[u32]) -> Vec<(u32, Result<()>)> {
pids.iter()
.map(|&pid| (pid, kill_process_force(pid)))
.collect()
}
pub fn inspect_file_locks(paths: &[PathBuf]) -> Result<Vec<FileLockInfo>> {
let sys = create_process_system();
let mut results = Vec::new();
for path in paths {
let mut locked = is_file_locked(path);
let mut pids = find_processes_by_file(path).unwrap_or_default();
pids.sort_unstable();
pids.dedup();
let mut processes = Vec::new();
for pid in pids {
if let Some(process) = sys.process(sysinfo::Pid::from_u32(pid)) {
let name = process.name().to_string_lossy().to_string();
let cmd = process
.cmd()
.iter()
.map(|s| s.to_string_lossy().to_string())
.collect::<Vec<String>>()
.join(" ");
processes.push(FileLockProcess { pid, name, cmd });
} else {
processes.push(FileLockProcess {
pid,
name: "unknown".to_string(),
cmd: String::new(),
});
}
}
if !processes.is_empty() {
locked = true;
}
results.push(FileLockInfo {
path: path.clone(),
locked,
processes,
});
}
Ok(results)
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
#[test]
fn test_inspect_file_locks_for_temp_file() {
let temp_dir = std::env::temp_dir();
let file_path = temp_dir.join(format!(
"ziro_lock_test_{}_{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos()
));
fs::write(&file_path, b"test").unwrap();
let infos = inspect_file_locks(std::slice::from_ref(&file_path)).unwrap();
assert_eq!(infos.len(), 1);
assert_eq!(infos[0].path, file_path);
if infos[0].processes.is_empty() {
assert!(!infos[0].locked);
} else {
assert!(infos[0].locked);
}
let _ = fs::remove_file(&file_path);
}
}