use anyhow::{Result, anyhow};
use std::path::Path;
use std::thread;
use std::time::Duration;
use sysinfo::{ProcessRefreshKind, RefreshKind, System};
fn safe_command_output_to_string(stdout: &[u8]) -> String {
if let Ok(text) = std::str::from_utf8(stdout) {
return text.to_string();
}
#[cfg(target_os = "windows")]
{
if let Some(text) = try_decode_as_gbk(stdout) {
return text;
}
if let Some(text) = try_decode_as_windows_1252(stdout) {
return text;
}
}
let lossy = String::from_utf8_lossy(stdout);
if lossy.contains('�') {
eprintln!("警告: 命令输出包含非 UTF-8 字符,可能影响显示效果");
}
lossy.to_string()
}
#[cfg(target_os = "windows")]
fn try_decode_as_gbk(data: &[u8]) -> Option<String> {
if data.len() >= 2 {
let mut valid_gbk = true;
let mut i = 0;
while i < data.len() - 1 {
if data[i] >= 0x81 && data[i] <= 0xFE && data[i + 1] >= 0x40 && data[i + 1] <= 0xFE {
i += 2;
} else if data[i] <= 0x7F {
i += 1;
} else {
valid_gbk = false;
break;
}
}
if valid_gbk {
return Some(format!("[GBK编码数据,长度: {}]", data.len()));
}
}
None
}
#[cfg(target_os = "windows")]
fn try_decode_as_windows_1252(data: &[u8]) -> Option<String> {
for &byte in data {
if byte == 0x81 || byte == 0x8D || byte == 0x8F || byte == 0x90 || byte == 0x9D {
return Some(format!("[Windows-1252编码数据,长度: {}]", data.len()));
}
}
None
}
pub fn kill_process(pid: u32) -> Result<()> {
let mut sys = System::new_with_specifics(
RefreshKind::new().with_processes(ProcessRefreshKind::everything()),
);
sys.refresh_all();
let pid_obj = sysinfo::Pid::from_u32(pid);
if let Some(process) = sys.process(pid_obj) {
if process.kill() {
Ok(())
} else {
Err(anyhow!("无法终止进程 {pid} (可能需要管理员权限)"))
}
} else {
Err(anyhow!("进程 {pid} 不存在"))
}
}
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 = System::new_with_specifics(
RefreshKind::new().with_processes(ProcessRefreshKind::everything()),
);
sys.refresh_all();
let pid_obj = sysinfo::Pid::from_u32(pid);
if sys.process(pid_obj).is_none() {
return Ok(());
}
}
for attempt in 1..=3 {
{
let mut sys = System::new_with_specifics(
RefreshKind::new().with_processes(ProcessRefreshKind::everything()),
);
sys.refresh_all();
let pid_obj = sysinfo::Pid::from_u32(pid);
if let Some(process) = sys.process(pid_obj) {
if process.kill() {
thread::sleep(Duration::from_millis(500));
sys.refresh_processes(sysinfo::ProcessesToUpdate::All);
if !sys.processes().contains_key(&pid_obj) {
return Ok(());
}
} else {
if attempt == 3 {
return Err(anyhow!("无法强制终止进程 {pid} (可能需要管理员权限)"));
}
}
} else {
return Ok(());
}
}
if attempt < 3 {
thread::sleep(Duration::from_millis(1000));
}
}
Err(anyhow!("强制终止进程 {pid} 失败,进程可能仍在运行"))
}
pub fn kill_processes_force(pids: &[u32]) -> Vec<(u32, Result<()>)> {
pids.iter()
.map(|&pid| (pid, kill_process_force(pid)))
.collect()
}
pub fn is_file_locked(path: &Path) -> bool {
if !path.exists() {
return false;
}
if cfg!(target_os = "windows") {
use std::fs::OpenOptions;
match OpenOptions::new().write(true).create(false).open(path) {
Ok(_) => {
false
}
Err(_) => {
true
}
}
} else {
let path_str = match path.to_str() {
Some(s) => s,
None => return false,
};
match std::process::Command::new("lsof").arg(path_str).output() {
Ok(output) => output.status.success(),
Err(_) => false, }
}
}
pub fn find_processes_by_file(path: &Path) -> Result<Vec<u32>> {
let mut pids = Vec::new();
if !path.exists() {
return Ok(pids);
}
if cfg!(target_os = "windows") {
let path_str = path.to_string_lossy();
match std::process::Command::new("powershell")
.args([
"-Command",
&format!("Get-Process | Where-Object {{ $_.MainModule.FileName -eq '{path_str}' }} | Select-Object Id")
])
.output()
{
Ok(output) => {
if output.status.success() {
let output_str = safe_command_output_to_string(&output.stdout);
for line in output_str.lines() {
if line.trim().is_empty() {
continue;
}
if let Ok(pid) = line.trim().parse::<u32>() {
pids.push(pid);
}
}
}
}
Err(_) => {
}
}
} else {
let path_str = match path.to_str() {
Some(s) => s,
None => return Ok(pids),
};
match std::process::Command::new("lsof")
.arg("-t")
.arg(path_str)
.output()
{
Ok(output) => {
if output.status.success() {
let output_str = safe_command_output_to_string(&output.stdout);
for line in output_str.lines() {
if let Ok(pid) = line.trim().parse::<u32>() {
pids.push(pid);
}
}
}
}
Err(_) => {
}
}
}
Ok(pids)
}