- Add new documentation sections for PostgreSQL installer functions and usage examples. Improves clarity and completeness of the documentation. - Add new files and patterns to .gitignore to prevent unnecessary files from being committed to the repository. Improves repository cleanliness and reduces clutter.
352 lines
10 KiB
Rust
352 lines
10 KiB
Rust
use std::error::Error;
|
|
use std::fmt;
|
|
use std::io;
|
|
use std::process::Command;
|
|
|
|
/// 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
|
|
*
|
|
* ```
|
|
* use sal::process::which;
|
|
*
|
|
* 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
|
|
* use sal::process::kill;
|
|
*
|
|
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
* let result = kill("server")?;
|
|
* println!("{}", result);
|
|
* Ok(())
|
|
* }
|
|
* ```
|
|
*/
|
|
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
|
|
* use sal::process::process_list;
|
|
*
|
|
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
* 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);
|
|
* }
|
|
* Ok(())
|
|
* }
|
|
* ```
|
|
*/
|
|
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
|
|
*
|
|
* ```no_run
|
|
* use sal::process::process_get;
|
|
*
|
|
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
* let process = process_get("unique-server-name")?;
|
|
* println!("Found process: {} (PID: {})", process.name, process.pid);
|
|
* Ok(())
|
|
* }
|
|
* ```
|
|
*/
|
|
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(),
|
|
)),
|
|
}
|
|
}
|