From 1c2e20c56d7fdbb0f7b21d12137ec7d58cd839c8 Mon Sep 17 00:00:00 2001 From: diogo464 Date: Thu, 19 Jun 2025 09:18:18 +0100 Subject: Format code with rustfmt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Apply standard Rust formatting conventions - Improve code readability and consistency - Reorganize imports alphabetically - Fix line lengths and indentation - All tests continue to pass 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/main.rs | 302 ++++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 170 insertions(+), 132 deletions(-) (limited to 'src') diff --git a/src/main.rs b/src/main.rs index 37a7cdb..ac6e305 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,13 @@ -use clap::{Parser, Subcommand, Args}; +use anyhow::{Context, Result}; +use clap::{Args, Parser, Subcommand}; +use notify::{Config, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher}; use std::fs::File; -use std::io::{Write, Read, Seek, SeekFrom}; +use std::io::{Read, Seek, SeekFrom, Write}; +use std::path::Path; use std::process::{Command, Stdio}; +use std::sync::mpsc::channel; use std::thread; use std::time::Duration; -use std::path::Path; -use notify::{Config, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher}; -use std::sync::mpsc::channel; -use anyhow::{Result, Context}; #[derive(Parser)] #[command(name = "demon")] @@ -23,25 +23,25 @@ struct Cli { enum Commands { /// Spawn a background process and redirect stdout/stderr to files Run(RunArgs), - + /// Stop a running daemon process Stop(StopArgs), - + /// Tail daemon logs in real-time Tail(TailArgs), - + /// Display daemon log contents Cat(CatArgs), - + /// List all running daemon processes List(ListArgs), - + /// Check status of a daemon process Status(StatusArgs), - + /// Clean up orphaned pid and log files Clean, - + /// Output comprehensive usage guide for LLMs Llm, } @@ -51,7 +51,7 @@ struct RunArgs { /// Process identifier #[arg(long)] id: String, - + /// Command and arguments to execute command: Vec, } @@ -61,7 +61,7 @@ struct StopArgs { /// Process identifier #[arg(long)] id: String, - + /// Timeout in seconds before sending SIGKILL after SIGTERM #[arg(long, default_value = "10")] timeout: u64, @@ -72,11 +72,11 @@ struct TailArgs { /// Process identifier #[arg(long)] id: String, - + /// Only tail stdout #[arg(long)] stdout: bool, - + /// Only tail stderr #[arg(long)] stderr: bool, @@ -87,11 +87,11 @@ struct CatArgs { /// Process identifier #[arg(long)] id: String, - + /// Only show stdout #[arg(long)] stdout: bool, - + /// Only show stderr #[arg(long)] stderr: bool, @@ -117,7 +117,7 @@ fn main() { .init(); let cli = Cli::parse(); - + if let Err(e) = run_command(cli.command) { tracing::error!("Error: {}", e); std::process::exit(1); @@ -132,9 +132,7 @@ fn run_command(command: Commands) -> Result<()> { } run_daemon(&args.id, &args.command) } - Commands::Stop(args) => { - stop_daemon(&args.id, args.timeout) - } + Commands::Stop(args) => stop_daemon(&args.id, args.timeout), Commands::Tail(args) => { let show_stdout = !args.stderr || args.stdout; let show_stderr = !args.stdout || args.stderr; @@ -145,15 +143,9 @@ fn run_command(command: Commands) -> Result<()> { let show_stderr = !args.stdout || args.stderr; cat_logs(&args.id, show_stdout, show_stderr) } - Commands::List(args) => { - list_daemons(args.quiet) - } - Commands::Status(args) => { - status_daemon(&args.id) - } - Commands::Clean => { - clean_orphaned_files() - } + Commands::List(args) => list_daemons(args.quiet), + Commands::Status(args) => status_daemon(&args.id), + Commands::Clean => clean_orphaned_files(), Commands::Llm => { print_llm_guide(); Ok(()) @@ -165,26 +157,30 @@ fn run_daemon(id: &str, command: &[String]) -> Result<()> { let pid_file = format!("{}.pid", id); let stdout_file = format!("{}.stdout", id); let stderr_file = format!("{}.stderr", id); - + // Check if process is already running if is_process_running(&pid_file)? { return Err(anyhow::anyhow!("Process '{}' is already running", id)); } - + tracing::info!("Starting daemon '{}' with command: {:?}", id, command); - + // Truncate/create output files File::create(&stdout_file)?; File::create(&stderr_file)?; - + // Open files for redirection let stdout_redirect = File::create(&stdout_file)?; let stderr_redirect = File::create(&stderr_file)?; - + // Spawn the process let program = &command[0]; - let args = if command.len() > 1 { &command[1..] } else { &[] }; - + let args = if command.len() > 1 { + &command[1..] + } else { + &[] + }; + let child = Command::new(program) .args(args) .stdout(Stdio::from(stdout_redirect)) @@ -192,16 +188,16 @@ fn run_daemon(id: &str, command: &[String]) -> Result<()> { .stdin(Stdio::null()) .spawn() .with_context(|| format!("Failed to start process '{}' with args {:?}", program, args))?; - + // Write PID to file let mut pid_file_handle = File::create(&pid_file)?; writeln!(pid_file_handle, "{}", child.id())?; - + // Don't wait for the child - let it run detached std::mem::forget(child); - + println!("Started daemon '{}' with PID written to {}", id, pid_file); - + Ok(()) } @@ -211,26 +207,26 @@ fn is_process_running(pid_file: &str) -> Result { Ok(f) => f, Err(_) => return Ok(false), // No PID file means no running process }; - + let mut contents = String::new(); file.read_to_string(&mut contents)?; - + let pid: u32 = match contents.trim().parse() { Ok(p) => p, Err(_) => return Ok(false), // Invalid PID file }; - + // Check if process is still running using kill -0 let output = Command::new("kill") .args(&["-0", &pid.to_string()]) .output()?; - + Ok(output.status.success()) } fn stop_daemon(id: &str, timeout: u64) -> Result<()> { let pid_file = format!("{}.pid", id); - + // Check if PID file exists let mut file = match File::open(&pid_file) { Ok(f) => f, @@ -239,11 +235,11 @@ fn stop_daemon(id: &str, timeout: u64) -> Result<()> { return Ok(()); } }; - + // Read PID let mut contents = String::new(); file.read_to_string(&mut contents)?; - + let pid: u32 = match contents.trim().parse() { Ok(p) => p, Err(_) => { @@ -252,26 +248,34 @@ fn stop_daemon(id: &str, timeout: u64) -> Result<()> { return Ok(()); } }; - - tracing::info!("Stopping daemon '{}' (PID: {}) with timeout {}s", id, pid, timeout); - + + tracing::info!( + "Stopping daemon '{}' (PID: {}) with timeout {}s", + id, + pid, + timeout + ); + // Check if process is running if !is_process_running_by_pid(pid) { - println!("Process '{}' (PID: {}) is not running, cleaning up PID file", id, pid); + println!( + "Process '{}' (PID: {}) is not running, cleaning up PID file", + id, pid + ); std::fs::remove_file(&pid_file)?; return Ok(()); } - + // Send SIGTERM tracing::info!("Sending SIGTERM to PID {}", pid); let output = Command::new("kill") .args(&["-TERM", &pid.to_string()]) .output()?; - + if !output.status.success() { return Err(anyhow::anyhow!("Failed to send SIGTERM to PID {}", pid)); } - + // Wait for the process to terminate for i in 0..timeout { if !is_process_running_by_pid(pid) { @@ -279,34 +283,41 @@ fn stop_daemon(id: &str, timeout: u64) -> Result<()> { std::fs::remove_file(&pid_file)?; return Ok(()); } - + if i == 0 { tracing::info!("Waiting for process to terminate gracefully..."); } - + thread::sleep(Duration::from_secs(1)); } - + // Process didn't terminate, send SIGKILL - tracing::warn!("Process {} didn't terminate after {}s, sending SIGKILL", pid, timeout); + tracing::warn!( + "Process {} didn't terminate after {}s, sending SIGKILL", + pid, + timeout + ); let output = Command::new("kill") .args(&["-KILL", &pid.to_string()]) .output()?; - + if !output.status.success() { return Err(anyhow::anyhow!("Failed to send SIGKILL to PID {}", pid)); } - + // Wait a bit more for SIGKILL to take effect thread::sleep(Duration::from_secs(1)); - + if is_process_running_by_pid(pid) { - return Err(anyhow::anyhow!("Process {} is still running after SIGKILL", pid)); + return Err(anyhow::anyhow!( + "Process {} is still running after SIGKILL", + pid + )); } - + println!("Process '{}' (PID: {}) terminated forcefully", id, pid); std::fs::remove_file(&pid_file)?; - + Ok(()) } @@ -314,7 +325,7 @@ fn is_process_running_by_pid(pid: u32) -> bool { let output = Command::new("kill") .args(&["-0", &pid.to_string()]) .output(); - + match output { Ok(output) => output.status.success(), Err(_) => false, @@ -324,9 +335,9 @@ fn is_process_running_by_pid(pid: u32) -> bool { fn cat_logs(id: &str, show_stdout: bool, show_stderr: bool) -> Result<()> { let stdout_file = format!("{}.stdout", id); let stderr_file = format!("{}.stderr", id); - + let mut files_found = false; - + if show_stdout { if let Ok(contents) = std::fs::read_to_string(&stdout_file) { if !contents.is_empty() { @@ -340,7 +351,7 @@ fn cat_logs(id: &str, show_stdout: bool, show_stderr: bool) -> Result<()> { tracing::warn!("Could not read {}", stdout_file); } } - + if show_stderr { if let Ok(contents) = std::fs::read_to_string(&stderr_file) { if !contents.is_empty() { @@ -354,21 +365,22 @@ fn cat_logs(id: &str, show_stdout: bool, show_stderr: bool) -> Result<()> { tracing::warn!("Could not read {}", stderr_file); } } - + if !files_found { println!("No log files found for daemon '{}'", id); } - + Ok(()) } fn tail_logs(id: &str, show_stdout: bool, show_stderr: bool) -> Result<()> { let stdout_file = format!("{}.stdout", id); let stderr_file = format!("{}.stderr", id); - + // First, display existing content and set up initial positions - let mut file_positions: std::collections::HashMap = std::collections::HashMap::new(); - + let mut file_positions: std::collections::HashMap = + std::collections::HashMap::new(); + if show_stdout && Path::new(&stdout_file).exists() { let mut file = File::open(&stdout_file)?; let initial_content = read_file_content(&mut file)?; @@ -381,7 +393,7 @@ fn tail_logs(id: &str, show_stdout: bool, show_stderr: bool) -> Result<()> { let position = file.seek(SeekFrom::Current(0))?; file_positions.insert(stdout_file.clone(), position); } - + if show_stderr && Path::new(&stderr_file).exists() { let mut file = File::open(&stderr_file)?; let initial_content = read_file_content(&mut file)?; @@ -396,28 +408,31 @@ fn tail_logs(id: &str, show_stdout: bool, show_stderr: bool) -> Result<()> { let position = file.seek(SeekFrom::Current(0))?; file_positions.insert(stderr_file.clone(), position); } - + if file_positions.is_empty() { - println!("No log files found for daemon '{}'. Watching for new files...", id); + println!( + "No log files found for daemon '{}'. Watching for new files...", + id + ); } - + tracing::info!("Watching for changes to log files... Press Ctrl+C to stop."); - + // Set up file watcher let (tx, rx) = channel(); let mut watcher = RecommendedWatcher::new(tx, Config::default())?; - + // Watch the current directory for new files and changes watcher.watch(Path::new("."), RecursiveMode::NonRecursive)?; - + // Handle Ctrl+C gracefully let running = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(true)); let r = running.clone(); - + ctrlc::set_handler(move || { r.store(false, std::sync::atomic::Ordering::SeqCst); })?; - + while running.load(std::sync::atomic::Ordering::SeqCst) { match rx.recv_timeout(Duration::from_millis(100)) { Ok(res) => { @@ -429,11 +444,15 @@ fn tail_logs(id: &str, show_stdout: bool, show_stderr: bool) -> Result<()> { }) => { for path in paths { let path_str = path.to_string_lossy().to_string(); - - if (show_stdout && path_str == stdout_file) || - (show_stderr && path_str == stderr_file) { - - if let Err(e) = handle_file_change(&path_str, &mut file_positions, show_stdout && show_stderr) { + + if (show_stdout && path_str == stdout_file) + || (show_stderr && path_str == stderr_file) + { + if let Err(e) = handle_file_change( + &path_str, + &mut file_positions, + show_stdout && show_stderr, + ) { tracing::error!("Error handling file change: {}", e); } } @@ -447,14 +466,18 @@ fn tail_logs(id: &str, show_stdout: bool, show_stderr: bool) -> Result<()> { // Handle file creation for path in paths { let path_str = path.to_string_lossy().to_string(); - - if (show_stdout && path_str == stdout_file) || - (show_stderr && path_str == stderr_file) { - + + if (show_stdout && path_str == stdout_file) + || (show_stderr && path_str == stderr_file) + { tracing::info!("New file detected: {}", path_str); file_positions.insert(path_str.clone(), 0); - - if let Err(e) = handle_file_change(&path_str, &mut file_positions, show_stdout && show_stderr) { + + if let Err(e) = handle_file_change( + &path_str, + &mut file_positions, + show_stdout && show_stderr, + ) { tracing::error!("Error handling new file: {}", e); } } @@ -473,7 +496,7 @@ fn tail_logs(id: &str, show_stdout: bool, show_stderr: bool) -> Result<()> { } } } - + println!("\nTailing stopped."); Ok(()) } @@ -485,32 +508,32 @@ fn read_file_content(file: &mut File) -> Result { } fn handle_file_change( - file_path: &str, + file_path: &str, positions: &mut std::collections::HashMap, - show_headers: bool + show_headers: bool, ) -> Result<()> { let mut file = File::open(file_path)?; let current_pos = positions.get(file_path).copied().unwrap_or(0); - + // Seek to the last read position file.seek(SeekFrom::Start(current_pos))?; - + // Read new content let mut new_content = String::new(); file.read_to_string(&mut new_content)?; - + if !new_content.is_empty() { if show_headers { println!("==> {} <==", file_path); } print!("{}", new_content); std::io::Write::flush(&mut std::io::stdout())?; - + // Update position let new_pos = file.seek(SeekFrom::Current(0))?; positions.insert(file_path.to_string(), new_pos); } - + Ok(()) } @@ -519,18 +542,18 @@ fn list_daemons(quiet: bool) -> Result<()> { println!("{:<20} {:<8} {:<10} {}", "ID", "PID", "STATUS", "COMMAND"); println!("{}", "-".repeat(50)); } - + let mut found_any = false; - + // Find all .pid files in current directory for entry in find_pid_files()? { found_any = true; let path = entry.path(); let path_str = path.to_string_lossy(); - + // Extract ID from filename (remove .pid extension) let id = path_str.strip_suffix(".pid").unwrap_or(&path_str); - + // Read PID from file match std::fs::read_to_string(&path) { Ok(contents) => { @@ -542,7 +565,7 @@ fn list_daemons(quiet: bool) -> Result<()> { } else { "DEAD" }; - + if quiet { println!("{}:{}:{}", id, pid, status); } else { @@ -556,7 +579,10 @@ fn list_daemons(quiet: bool) -> Result<()> { if quiet { println!("{}:INVALID:ERROR", id); } else { - println!("{:<20} {:<8} {:<10} {}", id, "INVALID", "ERROR", "Invalid PID file"); + println!( + "{:<20} {:<8} {:<10} {}", + id, "INVALID", "ERROR", "Invalid PID file" + ); } } } @@ -565,16 +591,22 @@ fn list_daemons(quiet: bool) -> Result<()> { if quiet { println!("{}:ERROR:ERROR", id); } else { - println!("{:<20} {:<8} {:<10} {}", id, "ERROR", "ERROR", format!("Cannot read: {}", e)); + println!( + "{:<20} {:<8} {:<10} {}", + id, + "ERROR", + "ERROR", + format!("Cannot read: {}", e) + ); } } } } - + if !found_any && !quiet { println!("No daemon processes found."); } - + Ok(()) } @@ -582,16 +614,16 @@ fn status_daemon(id: &str) -> Result<()> { let pid_file = format!("{}.pid", id); let stdout_file = format!("{}.stdout", id); let stderr_file = format!("{}.stderr", id); - + println!("Daemon: {}", id); println!("PID file: {}", pid_file); - + // Check if PID file exists if !Path::new(&pid_file).exists() { println!("Status: NOT FOUND (no PID file)"); return Ok(()); } - + // Read PID from file match std::fs::read_to_string(&pid_file) { Ok(contents) => { @@ -599,10 +631,10 @@ fn status_daemon(id: &str) -> Result<()> { match pid_str.parse::() { Ok(pid) => { println!("PID: {}", pid); - + if is_process_running_by_pid(pid) { println!("Status: RUNNING"); - + // Show file information if Path::new(&stdout_file).exists() { let metadata = std::fs::metadata(&stdout_file)?; @@ -610,7 +642,7 @@ fn status_daemon(id: &str) -> Result<()> { } else { println!("Stdout file: {} (not found)", stdout_file); } - + if Path::new(&stderr_file).exists() { let metadata = std::fs::metadata(&stderr_file)?; println!("Stderr file: {} ({} bytes)", stderr_file, metadata.len()); @@ -631,21 +663,21 @@ fn status_daemon(id: &str) -> Result<()> { println!("Status: ERROR (cannot read PID file: {})", e); } } - + Ok(()) } fn clean_orphaned_files() -> Result<()> { tracing::info!("Scanning for orphaned daemon files..."); - + let mut cleaned_count = 0; - + // Find all .pid files in current directory for entry in find_pid_files()? { let path = entry.path(); let path_str = path.to_string_lossy(); let id = path_str.strip_suffix(".pid").unwrap_or(&path_str); - + // Read PID from file match std::fs::read_to_string(&path) { Ok(contents) => { @@ -655,14 +687,14 @@ fn clean_orphaned_files() -> Result<()> { // Check if process is still running if !is_process_running_by_pid(pid) { println!("Cleaning up orphaned files for '{}' (PID: {})", id, pid); - + // Remove PID file if let Err(e) = std::fs::remove_file(&path) { tracing::warn!("Failed to remove {}: {}", path_str, e); } else { tracing::info!("Removed {}", path_str); } - + // Remove stdout file if it exists let stdout_file = format!("{}.stdout", id); if Path::new(&stdout_file).exists() { @@ -672,7 +704,7 @@ fn clean_orphaned_files() -> Result<()> { tracing::info!("Removed {}", stdout_file); } } - + // Remove stderr file if it exists let stderr_file = format!("{}.stderr", id); if Path::new(&stderr_file).exists() { @@ -682,10 +714,14 @@ fn clean_orphaned_files() -> Result<()> { tracing::info!("Removed {}", stderr_file); } } - + cleaned_count += 1; } else { - tracing::info!("Skipping '{}' (PID: {}) - process is still running", id, pid); + tracing::info!( + "Skipping '{}' (PID: {}) - process is still running", + id, + pid + ); } } Err(_) => { @@ -710,18 +746,19 @@ fn clean_orphaned_files() -> Result<()> { } } } - + if cleaned_count == 0 { println!("No orphaned files found."); } else { println!("Cleaned up {} orphaned daemon(s).", cleaned_count); } - + Ok(()) } fn print_llm_guide() { - println!(r#"# Demon - Daemon Process Management CLI + println!( + r#"# Demon - Daemon Process Management CLI ## Overview Demon is a command-line tool for spawning, managing, and monitoring background processes (daemons) on Linux systems. It redirects process stdout/stderr to files and provides commands to control and observe these processes. @@ -948,7 +985,8 @@ demon list --quiet > process_status.txt - Use standard Unix signals for process control - Log rotation should be handled by the application itself -This tool is designed for Linux environments and provides a simple interface for managing background processes with persistent logging."#); +This tool is designed for Linux environments and provides a simple interface for managing background processes with persistent logging."# + ); } fn find_pid_files() -> Result> { -- cgit