311 lines
9.9 KiB
Rust
311 lines
9.9 KiB
Rust
use std::process::Command;
|
|
use std::collections::HashMap;
|
|
use std::fmt;
|
|
use std::error::Error;
|
|
use std::io;
|
|
|
|
/// Error type for process management operations
|
|
///
|
|
/// This enum represents various errors that can occur during process management
|
|
/// operations such as listing, finding, or killing processes.
|
|
#[derive(Debug)]
|
|
pub enum ProcessError {
|
|
/// An error occurred while executing a command
|
|
CommandExecutionFailed(io::Error),
|
|
/// A command executed successfully but returned an error
|
|
CommandFailed(String),
|
|
/// No process was found matching the specified pattern
|
|
NoProcessFound(String),
|
|
/// Multiple processes were found matching the specified pattern
|
|
MultipleProcessesFound(String, usize),
|
|
}
|
|
|
|
/// Implement Display for ProcessError to provide human-readable error messages
|
|
impl fmt::Display for ProcessError {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
ProcessError::CommandExecutionFailed(e) => write!(f, "Failed to execute command: {}", e),
|
|
ProcessError::CommandFailed(e) => write!(f, "{}", e),
|
|
ProcessError::NoProcessFound(pattern) => write!(f, "No processes found matching '{}'", pattern),
|
|
ProcessError::MultipleProcessesFound(pattern, count) =>
|
|
write!(f, "Multiple processes ({}) found matching '{}'", count, pattern),
|
|
}
|
|
}
|
|
}
|
|
|
|
// Implement Error trait for ProcessError
|
|
impl Error for ProcessError {
|
|
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
|
match self {
|
|
ProcessError::CommandExecutionFailed(e) => Some(e),
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
// Define a struct to represent process information
|
|
#[derive(Debug, Clone)]
|
|
pub struct ProcessInfo {
|
|
pub pid: i64,
|
|
pub name: String,
|
|
pub memory: f64,
|
|
pub cpu: f64,
|
|
}
|
|
|
|
/**
|
|
* Check if a command exists in PATH.
|
|
*
|
|
* # Arguments
|
|
*
|
|
* * `cmd` - The command to check
|
|
*
|
|
* # Returns
|
|
*
|
|
* * `Option<String>` - The full path to the command if found, None otherwise
|
|
*
|
|
* # Examples
|
|
*
|
|
* ```
|
|
* match which("git") {
|
|
* Some(path) => println!("Git is installed at: {}", path),
|
|
* None => println!("Git is not installed"),
|
|
* }
|
|
* ```
|
|
*/
|
|
pub fn which(cmd: &str) -> Option<String> {
|
|
#[cfg(target_os = "windows")]
|
|
let which_cmd = "where";
|
|
|
|
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
|
let which_cmd = "which";
|
|
|
|
let output = Command::new(which_cmd)
|
|
.arg(cmd)
|
|
.output();
|
|
|
|
match output {
|
|
Ok(out) => {
|
|
if out.status.success() {
|
|
let path = String::from_utf8_lossy(&out.stdout).trim().to_string();
|
|
Some(path)
|
|
} else {
|
|
None
|
|
}
|
|
},
|
|
Err(_) => None
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Kill processes matching a pattern.
|
|
*
|
|
* # Arguments
|
|
*
|
|
* * `pattern` - The pattern to match against process names
|
|
*
|
|
* # Returns
|
|
*
|
|
* * `Ok(String)` - A success message indicating processes were killed or none were found
|
|
* * `Err(ProcessError)` - An error if the kill operation failed
|
|
*
|
|
* # Examples
|
|
*
|
|
* ```
|
|
* // Kill all processes with "server" in their name
|
|
* let result = kill("server")?;
|
|
* println!("{}", result);
|
|
* ```
|
|
*/
|
|
pub fn kill(pattern: &str) -> Result<String, ProcessError> {
|
|
// Platform specific implementation
|
|
#[cfg(target_os = "windows")]
|
|
{
|
|
// On Windows, use taskkill with wildcard support
|
|
let mut args = vec!["/F"]; // Force kill
|
|
|
|
if pattern.contains('*') {
|
|
// If it contains wildcards, use filter
|
|
args.extend(&["/FI", &format!("IMAGENAME eq {}", pattern)]);
|
|
} else {
|
|
// Otherwise use image name directly
|
|
args.extend(&["/IM", pattern]);
|
|
}
|
|
|
|
let output = Command::new("taskkill")
|
|
.args(&args)
|
|
.output()
|
|
.map_err(ProcessError::CommandExecutionFailed)?;
|
|
|
|
if output.status.success() {
|
|
Ok("Successfully killed processes".to_string())
|
|
} else {
|
|
let error = String::from_utf8_lossy(&output.stderr);
|
|
if error.is_empty() {
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
if stdout.contains("No tasks") {
|
|
Ok("No matching processes found".to_string())
|
|
} else {
|
|
Err(ProcessError::CommandFailed(format!("Failed to kill processes: {}", stdout)))
|
|
}
|
|
} else {
|
|
Err(ProcessError::CommandFailed(format!("Failed to kill processes: {}", error)))
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
|
{
|
|
// On Unix-like systems, use pkill which has built-in pattern matching
|
|
let output = Command::new("pkill")
|
|
.arg("-f") // Match against full process name/args
|
|
.arg(pattern)
|
|
.output()
|
|
.map_err(ProcessError::CommandExecutionFailed)?;
|
|
|
|
// pkill returns 0 if processes were killed, 1 if none matched
|
|
if output.status.success() {
|
|
Ok("Successfully killed processes".to_string())
|
|
} else if output.status.code() == Some(1) {
|
|
Ok("No matching processes found".to_string())
|
|
} else {
|
|
let error = String::from_utf8_lossy(&output.stderr);
|
|
Err(ProcessError::CommandFailed(format!("Failed to kill processes: {}", error)))
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* List processes matching a pattern (or all if pattern is empty).
|
|
*
|
|
* # Arguments
|
|
*
|
|
* * `pattern` - The pattern to match against process names (empty string for all processes)
|
|
*
|
|
* # Returns
|
|
*
|
|
* * `Ok(Vec<ProcessInfo>)` - A vector of process information for matching processes
|
|
* * `Err(ProcessError)` - An error if the list operation failed
|
|
*
|
|
* # Examples
|
|
*
|
|
* ```
|
|
* // List all processes
|
|
* let processes = process_list("")?;
|
|
*
|
|
* // List processes with "server" in their name
|
|
* let processes = process_list("server")?;
|
|
* for proc in processes {
|
|
* println!("PID: {}, Name: {}", proc.pid, proc.name);
|
|
* }
|
|
* ```
|
|
*/
|
|
pub fn process_list(pattern: &str) -> Result<Vec<ProcessInfo>, ProcessError> {
|
|
let mut processes = Vec::new();
|
|
|
|
// Platform specific implementations
|
|
#[cfg(target_os = "windows")]
|
|
{
|
|
// Windows implementation using wmic
|
|
let output = Command::new("wmic")
|
|
.args(&["process", "list", "brief"])
|
|
.output()
|
|
.map_err(ProcessError::CommandExecutionFailed)?;
|
|
|
|
if output.status.success() {
|
|
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
|
|
|
// Parse output (assuming format: Handle Name Priority)
|
|
for line in stdout.lines().skip(1) { // Skip header
|
|
let parts: Vec<&str> = line.trim().split_whitespace().collect();
|
|
if parts.len() >= 2 {
|
|
let pid = parts[0].parse::<i64>().unwrap_or(0);
|
|
let name = parts[1].to_string();
|
|
|
|
// Filter by pattern if provided
|
|
if !pattern.is_empty() && !name.contains(pattern) {
|
|
continue;
|
|
}
|
|
|
|
processes.push(ProcessInfo {
|
|
pid,
|
|
name,
|
|
memory: 0.0, // Placeholder
|
|
cpu: 0.0, // Placeholder
|
|
});
|
|
}
|
|
}
|
|
} else {
|
|
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
|
|
return Err(ProcessError::CommandFailed(format!("Failed to list processes: {}", stderr)));
|
|
}
|
|
}
|
|
|
|
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
|
{
|
|
// Unix implementation using ps
|
|
let output = Command::new("ps")
|
|
.args(&["-eo", "pid,comm"])
|
|
.output()
|
|
.map_err(ProcessError::CommandExecutionFailed)?;
|
|
|
|
if output.status.success() {
|
|
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
|
|
|
// Parse output (assuming format: PID COMMAND)
|
|
for line in stdout.lines().skip(1) { // Skip header
|
|
let parts: Vec<&str> = line.trim().split_whitespace().collect();
|
|
if parts.len() >= 2 {
|
|
let pid = parts[0].parse::<i64>().unwrap_or(0);
|
|
let name = parts[1].to_string();
|
|
|
|
// Filter by pattern if provided
|
|
if !pattern.is_empty() && !name.contains(pattern) {
|
|
continue;
|
|
}
|
|
|
|
processes.push(ProcessInfo {
|
|
pid,
|
|
name,
|
|
memory: 0.0, // Placeholder
|
|
cpu: 0.0, // Placeholder
|
|
});
|
|
}
|
|
}
|
|
} else {
|
|
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
|
|
return Err(ProcessError::CommandFailed(format!("Failed to list processes: {}", stderr)));
|
|
}
|
|
}
|
|
|
|
Ok(processes)
|
|
}
|
|
|
|
/**
|
|
* Get a single process matching the pattern (error if 0 or more than 1 match).
|
|
*
|
|
* # Arguments
|
|
*
|
|
* * `pattern` - The pattern to match against process names
|
|
*
|
|
* # Returns
|
|
*
|
|
* * `Ok(ProcessInfo)` - Information about the matching process
|
|
* * `Err(ProcessError)` - An error if no process or multiple processes match
|
|
*
|
|
* # Examples
|
|
*
|
|
* ```
|
|
* let process = process_get("unique-server-name")?;
|
|
* println!("Found process: {} (PID: {})", process.name, process.pid);
|
|
* ```
|
|
*/
|
|
pub fn process_get(pattern: &str) -> Result<ProcessInfo, ProcessError> {
|
|
let processes = process_list(pattern)?;
|
|
|
|
match processes.len() {
|
|
0 => Err(ProcessError::NoProcessFound(pattern.to_string())),
|
|
1 => Ok(processes[0].clone()),
|
|
_ => Err(ProcessError::MultipleProcessesFound(pattern.to_string(), processes.len())),
|
|
}
|
|
}
|