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` - 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 { #[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 { // 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)` - 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, 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::().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::().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 { 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())), } }