Skip to main content

kasl/libs/
daemon.rs

1//! Daemon management functionality for the kasl watch command.
2//!
3//! Provides comprehensive background process management for the kasl activity
4//! monitoring system including spawning, signal handling, and graceful shutdown.
5//!
6//! ## Features
7//!
8//! - **Process Spawning**: Creates detached background processes for continuous monitoring
9//! - **Signal Handling**: Responds to system signals for graceful shutdown and restart
10//! - **PID Management**: Tracks running processes and prevents duplicate instances
11//! - **Cross-Platform Support**: Handles platform differences between Unix and Windows
12//! - **Resource Cleanup**: Ensures proper cleanup of database connections and system resources
13//! - **Error Recovery**: Manages process failures and provides meaningful error messages
14//!
15//! ## Usage
16//!
17//! ```rust,no_run
18//! use kasl::libs::daemon;
19//!
20//! daemon::spawn()?;                           // Start background monitoring
21//! daemon::stop()?;                            // Stop background monitoring
22//! daemon::run_with_signal_handling().await?;  // Run with signal handling
23//! ```
24
25use crate::libs::config::Config;
26use crate::libs::data_storage::DataStorage;
27use crate::libs::messages::Message;
28use crate::libs::monitor::Monitor;
29use crate::{msg_bail_anyhow, msg_error, msg_error_anyhow, msg_info, msg_warning};
30use anyhow::Result;
31use std::time::Duration;
32use tracing::{debug, info, instrument, warn};
33
34/// PID file name used for tracking the daemon process.
35///
36/// This constant defines the filename used to store the process ID of the
37/// running daemon. The file is created in the application data directory
38/// when the daemon starts and removed when it shuts down gracefully.
39///
40/// The PID file serves multiple purposes:
41/// - **Process Tracking**: Allows the main process to find and communicate with the daemon
42/// - **Duplicate Prevention**: Prevents multiple daemon instances from running simultaneously
43/// - **Status Checking**: Enables status queries about the daemon's running state
44/// - **Cleanup Detection**: Helps identify when the daemon terminates unexpectedly
45const PID_FILE: &str = "kasl-watch.pid";
46
47/// Runs the daemon with proper signal handling for graceful shutdown.
48///
49/// Sets up comprehensive signal handling and runs the activity monitor in a
50/// controlled environment. Designed to be the main entry point for daemon operation.
51///
52/// # Returns
53///
54/// Returns `Ok(())` when the daemon shuts down cleanly, or an error if
55/// initialization fails or a critical error occurs during operation.
56///
57/// - **Signal Handler Setup**: Platform signal APIs not available
58/// - **Monitor Initialization**: Database connection or configuration errors
59/// - **Runtime Errors**: Critical failures during monitoring operation
60/// - **Cleanup Failures**: Unable to remove PID file or close resources
61///
62/// # Usage Context
63///
64/// This function is typically called from:
65/// - Background daemon processes spawned by [`spawn()`]
66/// - Foreground monitoring mode for debugging
67/// - Test environments requiring controlled shutdown
68#[instrument]
69pub async fn run_with_signal_handling() -> Result<()> {
70    info!("Starting daemon with signal handling");
71
72    // Set up a channel to handle shutdown signals
73    // This allows coordinated shutdown between signal handlers and the monitor
74    let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel();
75
76    // Spawn the signal handler in a separate task
77    // This ensures signal handling doesn't block the main monitoring loop
78    #[cfg(unix)]
79    {
80        tokio::spawn(async move {
81            use tokio::signal::unix::{signal, SignalKind};
82
83            // Set up handlers for standard Unix termination signals
84            let mut sigterm = signal(SignalKind::terminate()).expect(&Message::FailedToCreateSigtermHandler.to_string());
85            let mut sigint = signal(SignalKind::interrupt()).expect(&Message::FailedToCreateSigintHandler.to_string());
86
87            // Wait for any termination signal
88            tokio::select! {
89                _ = sigterm.recv() => {
90                    msg_info!(Message::WatcherReceivedSigterm);
91                }
92                _ = sigint.recv() => {
93                    msg_info!(Message::WatcherReceivedSigint);
94                }
95            }
96
97            // Signal the main loop to shut down gracefully
98            let _ = shutdown_tx.send(());
99        });
100    }
101
102    #[cfg(windows)]
103    {
104        tokio::spawn(async move {
105            // Handle Windows console events
106            match tokio::signal::ctrl_c().await {
107                Ok(()) => {
108                    msg_info!(Message::WatcherReceivedCtrlC);
109                }
110                Err(e) => {
111                    msg_error!(Message::WatcherCtrlCListenFailed(e.to_string()));
112                }
113            }
114
115            // Signal the main loop to shut down gracefully
116            let _ = shutdown_tx.send(());
117        });
118    }
119
120    #[cfg(not(any(unix, windows)))]
121    {
122        // For other platforms, just run without signal handling
123        // This ensures the application still works on unsupported platforms
124        msg_warning!(Message::WatcherSignalHandlingNotSupported);
125    }
126
127    // Run the monitor in a separate task
128    // This allows concurrent execution with signal handling
129    let monitor_handle = tokio::spawn(async move {
130        match run_monitor().await {
131            Ok(()) => Ok(()),
132            Err(e) => Err(Message::MonitorError(e.to_string())),
133        }
134    });
135
136    // Wait for either the monitor to finish or a shutdown signal
137    // This provides coordinated shutdown between different components
138    tokio::select! {
139        result = monitor_handle => {
140            // Monitor task completed (either successfully or with error)
141            match result {
142                Ok(Ok(())) => msg_info!(Message::MonitorExitedNormally),
143                Ok(Err(e)) => msg_error!(Message::MonitorError(e.to_string())),
144                Err(e) => msg_error!(Message::MonitorTaskPanicked(e.to_string())),
145            }
146        }
147        _ = shutdown_rx => {
148            // Received shutdown signal
149            msg_info!(Message::MonitorShuttingDown);
150            // The monitor will be dropped when this function exits
151        }
152    }
153
154    // Clean up PID file on exit
155    // This ensures the PID file doesn't become stale
156    let pid_path = DataStorage::new().get_path(PID_FILE)?;
157    if pid_path.exists() {
158        let _ = std::fs::remove_file(&pid_path);
159    }
160
161    Ok(())
162}
163
164/// The core logic that initializes and runs the activity monitor.
165///
166/// This function handles the complete lifecycle of the activity monitoring
167/// system, from configuration loading through monitor initialization to
168/// the main monitoring loop execution.
169///
170/// ## Initialization Process
171///
172/// 1. **Configuration Loading**: Reads monitor settings from the config file
173/// 2. **Default Application**: Applies sensible defaults for missing configuration
174/// 3. **Monitor Creation**: Initializes the monitor with the loaded configuration
175/// 4. **Loop Execution**: Starts the continuous activity monitoring loop
176///
177/// ## Configuration Handling
178///
179/// The function uses a robust configuration loading strategy:
180/// - **Primary Source**: User configuration file
181/// - **Fallback**: Built-in default values
182/// - **Validation**: Ensures configuration values are within valid ranges
183/// - **Error Recovery**: Continues with defaults if configuration is invalid
184///
185/// ## Monitor Components
186///
187/// The initialized monitor includes:
188/// - **Input Detection**: Keyboard and mouse activity tracking
189/// - **Database Interface**: Connection to SQLite database for data storage
190/// - **State Management**: Activity state tracking and transition logic
191/// - **Timing Control**: Configurable polling intervals and thresholds
192///
193/// ## Error Propagation
194///
195/// This function properly propagates errors from:
196/// - Configuration loading failures
197/// - Database connection issues
198/// - Monitor initialization problems
199/// - Runtime monitoring errors
200///
201/// # Returns
202///
203/// Returns `Ok(())` when monitoring completes successfully, or an error
204/// if any part of the initialization or execution process fails.
205///
206/// # Error Scenarios
207///
208/// - **Configuration Errors**: Invalid or corrupted configuration file
209/// - **Database Errors**: Cannot connect to or initialize the SQLite database
210/// - **Permission Errors**: Insufficient privileges for input device monitoring
211/// - **Resource Errors**: System resource exhaustion or availability issues
212///
213/// # Usage Context
214///
215/// This function is called by:
216/// - [`run_with_signal_handling()`] for daemon operation
217/// - Foreground monitoring mode for interactive debugging
218/// - Test environments for controlled monitoring scenarios
219async fn run_monitor() -> Result<()> {
220    // Load configuration with defaults for missing values
221    // This ensures the monitor can start even with minimal configuration
222    let config = Config::read()?;
223    let monitor_config = config.monitor.unwrap_or_default();
224
225    // Initialize the activity monitor with configuration
226    // This sets up all necessary components for activity tracking
227    let mut monitor = Monitor::new(monitor_config)?;
228
229    // Start the main monitoring loop
230    // This will run indefinitely until stopped or an error occurs
231    monitor.run().await
232}
233
234/// Spawns the application as a detached background process.
235///
236/// This function creates a new background process that runs independently
237/// of the parent process. It handles platform-specific process creation,
238/// PID file management, and ensures only one daemon instance runs at a time.
239///
240/// ## Process Management
241///
242/// 1. **Existing Process Check**: Verifies no daemon is already running
243/// 2. **Process Termination**: Stops any existing daemon before starting new one
244/// 3. **Process Creation**: Spawns new daemon with platform-specific flags
245/// 4. **PID Recording**: Saves the new process ID for future management
246/// 5. **Status Reporting**: Provides feedback about the spawning operation
247///
248/// ## Platform-Specific Spawning
249///
250/// ### Unix Systems
251/// ```rust,no_run
252/// std::process::Command::new(current_exe)
253///     .arg("--daemon-run")
254///     .before_exec(|| {
255///         nix::unistd::setsid()?; // Create new session
256///         Ok(())
257///     })
258///     .spawn()?;
259/// ```
260///
261/// ### Windows
262/// ```rust,no_run
263/// std::process::Command::new(current_exe)
264///     .arg("--daemon-run")
265///     .creation_flags(CREATE_NO_WINDOW) // Hide console window
266///     .spawn()?;
267/// ```
268///
269/// ## Duplicate Prevention
270///
271/// The function prevents multiple daemon instances by:
272/// - Checking for existing PID files
273/// - Validating that the process in the PID file is actually running
274/// - Terminating stale processes before starting new ones
275/// - Cleaning up orphaned PID files
276///
277/// ## Error Recovery
278///
279/// If stopping an existing daemon fails:
280/// - Issues a warning but continues with spawning
281/// - Removes stale PID files to prevent conflicts
282/// - Allows a brief delay for process cleanup
283/// - Proceeds with new daemon creation
284///
285/// # Returns
286///
287/// Returns `Ok(())` if the daemon was successfully spawned and the PID file
288/// was created, or an error if the spawning process fails.
289///
290/// # Error Scenarios
291///
292/// - **Executable Not Found**: Cannot locate the current executable
293/// - **Permission Denied**: Insufficient privileges for process creation
294/// - **Resource Exhaustion**: System cannot create new processes
295/// - **PID File Creation**: Cannot write PID file to application directory
296/// - **Platform Unsupported**: Daemon mode not available on the current platform
297///
298/// # Usage Examples
299///
300/// ```rust,no_run
301/// use kasl::libs::daemon;
302///
303/// // Start background monitoring
304/// daemon::spawn()?;
305/// println!("Background monitoring started");
306/// ```
307///
308/// # Security Considerations
309///
310/// - The spawned process runs with the same privileges as the parent
311/// - PID files are created with user-readable permissions only
312/// - No sensitive information is passed via command line arguments
313/// - Process isolation is maintained through session separation (Unix)
314#[instrument]
315pub fn spawn() -> Result<()> {
316    debug!("Attempting to spawn daemon process");
317    let pid_path = DataStorage::new().get_path(PID_FILE)?;
318
319    // Check if a daemon is already running and stop it
320    // This ensures only one daemon instance is active at a time
321    if pid_path.exists() {
322        if let Ok(pid_str) = std::fs::read_to_string(&pid_path) {
323            msg_info!(Message::WatcherStoppingExisting(pid_str.trim().to_string()));
324
325            // Try to stop the existing daemon
326            if let Err(e) = stop_internal() {
327                msg_warning!(Message::WatcherFailedToStopExisting(e.to_string()));
328                // Remove the PID file anyway in case the process is already dead
329                let _ = std::fs::remove_file(&pid_path);
330            }
331
332            // Give the old process time to clean up
333            std::thread::sleep(Duration::from_millis(1000));
334        }
335    }
336
337    // Get the current executable path for spawning
338    let current_exe = std::env::current_exe().expect(&Message::FailedToGetCurrentExecutable.to_string());
339
340    #[cfg(unix)]
341    {
342        use std::os::unix::process::CommandExt;
343
344        // Spawn daemon process with session detachment
345        let child = std::process::Command::new(current_exe)
346            .arg("--daemon-run")
347            .before_exec(|| {
348                // Detach from the current session to become a daemon
349                // This ensures the process continues running after parent exits
350                nix::unistd::setsid()?;
351                Ok(())
352            })
353            .spawn()?;
354
355        let pid = child.id();
356        std::fs::write(pid_path, pid.to_string())?;
357        msg_info!(Message::WatcherStarted(pid));
358    }
359
360    #[cfg(windows)]
361    {
362        use std::os::windows::process::CommandExt;
363
364        // Windows-specific flags for background process creation
365        const CREATE_NO_WINDOW: u32 = 0x08000000;
366
367        // Spawn daemon process without console window
368        let child = std::process::Command::new(current_exe)
369            .arg("--daemon-run")
370            .creation_flags(CREATE_NO_WINDOW)
371            .spawn()?;
372
373        let pid = child.id();
374        std::fs::write(pid_path, pid.to_string())?;
375        msg_info!(Message::WatcherStarted(pid));
376    }
377
378    #[cfg(not(any(unix, windows)))]
379    {
380        // Platform not supported for daemon mode
381        msg_bail_anyhow!(Message::DaemonModeNotSupported);
382    }
383
384    Ok(())
385}
386
387/// Finds and stops the running daemon process.
388///
389/// This function provides a user-friendly interface for stopping the daemon
390/// process. It handles cases where no daemon is running gracefully and
391/// provides appropriate feedback to the user.
392///
393/// ## Operation Flow
394///
395/// 1. **Process Lookup**: Searches for running daemon using PID file
396/// 2. **Termination**: Attempts to terminate the found process
397/// 3. **Cleanup**: Removes PID file and other resources
398/// 4. **Status Reporting**: Provides feedback about the operation result
399///
400/// ## Error Handling Strategy
401///
402/// This function uses a forgiving error handling approach:
403/// - **Process Not Found**: Reports "not running" instead of error
404/// - **Stale PID File**: Cleans up orphaned files without complaint
405/// - **Permission Issues**: Reports specific error details
406/// - **Cleanup Failures**: Continues operation, reports warnings
407///
408/// ## User Experience
409///
410/// The function prioritizes clear user communication:
411/// - Success messages confirm the daemon was stopped
412/// - "Not running" messages avoid unnecessary error reports
413/// - Specific error messages help with troubleshooting
414/// - Consistent behavior across multiple invocations
415///
416/// # Returns
417///
418/// Returns `Ok(())` in most cases, including when no daemon is running.
419/// Only returns errors for serious system-level failures that require
420/// user attention.
421///
422/// # Error Scenarios
423///
424/// - **Permission Denied**: Insufficient privileges to terminate the process
425/// - **System Errors**: Platform-specific process management failures
426/// - **Resource Issues**: System resource exhaustion during termination
427///
428/// # Usage Examples
429///
430/// ```rust,no_run
431/// use kasl::libs::daemon;
432///
433/// // Stop background monitoring
434/// daemon::stop()?;
435/// println!("Monitoring stopped");
436/// ```
437///
438/// # Idempotent Operation
439///
440/// This function is safe to call multiple times and will not produce
441/// errors if called when no daemon is running. This makes it suitable
442/// for use in cleanup scripts and automated scenarios.
443/// Checks if the daemon is currently running.
444///
445/// This function determines whether a daemon process is currently active by
446/// checking for the existence and validity of the PID file and verifying
447/// that the corresponding process is still running.
448///
449/// # Returns
450///
451/// Returns `true` if the daemon is running, `false` otherwise.
452/// This function does not return errors - it treats any failure to
453/// verify the daemon as "not running".
454pub fn is_running() -> bool {
455    let pid_path = match DataStorage::new().get_path(PID_FILE) {
456        Ok(path) => path,
457        Err(_) => return false,
458    };
459
460    // Check if PID file exists
461    if !pid_path.exists() {
462        return false;
463    }
464
465    // Read and parse the PID from the file
466    let pid_str = match std::fs::read_to_string(&pid_path) {
467        Ok(content) => content,
468        Err(_) => return false,
469    };
470
471    let pid: u32 = match pid_str.trim().parse() {
472        Ok(pid) => pid,
473        Err(_) => return false,
474    };
475
476    // Check if process is actually running
477    is_process_running(pid)
478}
479
480/// Checks if a process with the given PID is currently running.
481///
482/// This function uses platform-specific methods to verify if a process
483/// exists and is running. It's used internally by daemon management
484/// functions to validate process state.
485///
486/// # Arguments
487///
488/// * `pid` - The process ID to check
489///
490/// # Returns
491///
492/// Returns `true` if the process is running, `false` otherwise.
493fn is_process_running(pid: u32) -> bool {
494    #[cfg(windows)]
495    {
496        use winapi::um::errhandlingapi::GetLastError;
497        use winapi::um::handleapi::CloseHandle;
498        use winapi::um::processthreadsapi::OpenProcess;
499        use winapi::um::winnt::PROCESS_QUERY_INFORMATION;
500
501        unsafe {
502            let handle = OpenProcess(PROCESS_QUERY_INFORMATION, 0, pid);
503            if handle.is_null() {
504                let error = GetLastError();
505                // ERROR_INVALID_PARAMETER (87) means process doesn't exist
506                return error != 87;
507            }
508            CloseHandle(handle);
509            true
510        }
511    }
512    
513    #[cfg(unix)]
514    {
515        use std::process::Command;
516        
517        // Use ps command to check if process exists
518        match Command::new("ps").arg("-p").arg(pid.to_string()).output() {
519            Ok(output) => output.status.success(),
520            Err(_) => false,
521        }
522    }
523    
524    #[cfg(not(any(unix, windows)))]
525    {
526        // For unsupported platforms, assume not running
527        false
528    }
529}
530
531pub fn stop() -> Result<()> {
532    match stop_internal() {
533        Ok(()) => Ok(()),
534        Err(e) => {
535            // If the daemon wasn't running, that's okay
536            // This provides a better user experience than reporting errors
537            if e.to_string().contains("not found") || e.to_string().contains("not running") {
538                msg_info!(Message::WatcherNotRunning);
539                Ok(())
540            } else {
541                Err(e)
542            }
543        }
544    }
545}
546
547/// Internal function to stop the daemon, used by both stop and spawn.
548///
549/// This function performs the actual daemon termination logic without
550/// the user-friendly error handling of the public [`stop()`] function.
551/// It's used internally when precise error information is needed.
552///
553/// ## Termination Process
554///
555/// 1. **PID File Validation**: Checks that PID file exists and is readable
556/// 2. **PID Parsing**: Validates that PID file contains a valid process ID
557/// 3. **Process Termination**: Uses platform-specific termination methods
558/// 4. **File Cleanup**: Removes PID file regardless of termination result
559/// 5. **Result Validation**: Confirms the process was actually terminated
560///
561/// ## Error Propagation
562///
563/// Unlike the public interface, this function propagates all errors:
564/// - **File Not Found**: PID file doesn't exist
565/// - **Invalid Content**: PID file contains invalid data
566/// - **Process Not Found**: Process ID is not running
567/// - **Termination Failed**: Process couldn't be terminated
568///
569/// ## Cleanup Guarantee
570///
571/// The function guarantees PID file cleanup even if process termination
572/// fails. This prevents stale PID files from interfering with future
573/// daemon operations.
574///
575/// # Returns
576///
577/// Returns `Ok(())` if the daemon was successfully terminated, or an
578/// error describing the specific failure encountered.
579///
580/// # Error Scenarios
581///
582/// - **No PID File**: Daemon is not running or PID file was removed
583/// - **Invalid PID**: PID file contains corrupted or invalid data
584/// - **Process Not Found**: Process ID does not correspond to running process
585/// - **Termination Failed**: Process exists but couldn't be terminated
586///
587/// # Usage Context
588///
589/// This function is used internally by:
590/// - [`stop()`] for user-initiated daemon termination
591/// - [`spawn()`] for replacing existing daemon instances
592/// - Test utilities for controlled daemon lifecycle management
593fn stop_internal() -> Result<()> {
594    let pid_path = DataStorage::new().get_path(PID_FILE)?;
595
596    // Check if PID file exists
597    if !pid_path.exists() {
598        msg_bail_anyhow!(Message::WatcherNotRunningPidNotFound);
599    }
600
601    // Read and parse the PID from the file
602    let pid_str = std::fs::read_to_string(&pid_path)?;
603    let pid: u32 = pid_str.trim().parse().map_err(|_| msg_error_anyhow!(Message::InvalidPidFileContent))?;
604
605    // Attempt to terminate the process
606    let killed = kill_process(pid)?;
607
608    // Clean up the PID file regardless of whether the process was found
609    // This prevents stale PID files from interfering with future operations
610    std::fs::remove_file(pid_path)?;
611
612    if killed {
613        msg_info!(Message::WatcherStopped(pid));
614        Ok(())
615    } else {
616        msg_bail_anyhow!(Message::WatcherFailedToStop(pid));
617    }
618}
619
620/// Cross-platform process termination for Windows systems.
621///
622/// This function uses Windows-specific APIs to terminate a process by its
623/// process ID. It handles Windows process management through the WinAPI
624/// and provides detailed error information for troubleshooting.
625///
626/// ## Windows Process Management
627///
628/// The function uses these WinAPI functions:
629/// - `OpenProcess()`: Opens a handle to the target process
630/// - `TerminateProcess()`: Forcibly terminates the process
631/// - `CloseHandle()`: Releases the process handle
632/// - `GetLastError()`: Retrieves detailed error information
633///
634/// ## Error Handling
635///
636/// Windows-specific error codes are handled:
637/// - **ERROR_INVALID_PARAMETER (87)**: Process doesn't exist
638/// - **ACCESS_DENIED**: Insufficient privileges
639/// - **INVALID_HANDLE**: Process handle creation failed
640///
641/// ## Termination Strategy
642///
643/// The function uses forceful termination (`TerminateProcess`) rather than
644/// graceful shutdown signals. While less elegant than Unix signals, this
645/// ensures reliable process termination on Windows systems.
646///
647/// ## Safety Considerations
648///
649/// - Process handles are properly closed to prevent resource leaks
650/// - Error conditions are checked after each API call
651/// - Brief delay allows for process cleanup before returning
652///
653/// # Arguments
654///
655/// * `pid` - The process ID of the target process to terminate
656///
657/// # Returns
658///
659/// Returns `Ok(true)` if the process was successfully terminated,
660/// `Ok(false)` if the process doesn't exist, or an error if termination fails.
661///
662/// # Error Scenarios
663///
664/// - **Access Denied**: Insufficient privileges to terminate the process
665/// - **Invalid Handle**: Cannot open process handle
666/// - **Termination Failed**: Process exists but termination failed
667///
668/// # Platform Availability
669///
670/// This function is only available on Windows platforms and will not
671/// compile on Unix-like systems.
672#[cfg(windows)]
673fn kill_process(pid: u32) -> Result<bool> {
674    use winapi::um::errhandlingapi::GetLastError;
675    use winapi::um::handleapi::CloseHandle;
676    use winapi::um::processthreadsapi::{OpenProcess, TerminateProcess};
677    use winapi::um::winnt::PROCESS_TERMINATE;
678
679    unsafe {
680        // Open a handle to the target process with termination rights
681        let handle = OpenProcess(PROCESS_TERMINATE, 0, pid);
682        if handle.is_null() {
683            let error = GetLastError();
684            if error == 87 {
685                // ERROR_INVALID_PARAMETER - process doesn't exist
686                return Ok(false);
687            }
688            msg_bail_anyhow!(Message::FailedToOpenProcess(error));
689        }
690
691        // Attempt to terminate the process
692        let result = TerminateProcess(handle, 0);
693
694        // Always close the handle to prevent resource leaks
695        CloseHandle(handle);
696
697        if result == 0 {
698            // Termination failed - get error details
699            let error = GetLastError();
700            msg_bail_anyhow!(Message::FailedToTerminateProcess(error));
701        } else {
702            // Give the process time to actually terminate
703            std::thread::sleep(Duration::from_millis(100));
704            Ok(true)
705        }
706    }
707}
708
709/// Cross-platform process termination for Unix-like systems.
710///
711/// This function uses Unix command-line tools to terminate a process by its
712/// process ID. It implements a graceful termination strategy that attempts
713/// polite shutdown before resorting to forceful termination.
714///
715/// ## Termination Strategy
716///
717/// 1. **Process Validation**: Uses `ps` to verify the process exists
718/// 2. **Graceful Termination**: Sends SIGTERM for clean shutdown
719/// 3. **Wait Period**: Allows time for graceful shutdown (1 second)
720/// 4. **Forced Termination**: Sends SIGKILL if graceful shutdown fails
721/// 5. **Final Validation**: Confirms the process was terminated
722///
723/// ## Signal Handling
724///
725/// - **SIGTERM**: Requests graceful shutdown, allows cleanup
726/// - **SIGKILL**: Forces immediate termination, no cleanup possible
727///
728/// ## Command Dependencies
729///
730/// This function requires standard Unix utilities:
731/// - `ps`: Process status checking
732/// - `kill`: Signal sending
733///
734/// These are available on virtually all Unix-like systems including
735/// Linux, macOS, BSD variants, and Solaris.
736///
737/// ## Graceful Shutdown Benefits
738///
739/// The graceful termination approach provides several advantages:
740/// - Allows proper cleanup of resources
741/// - Enables database transaction completion
742/// - Provides opportunity for state saving
743/// - Reduces risk of data corruption
744///
745/// # Arguments
746///
747/// * `pid` - The process ID of the target process to terminate
748///
749/// # Returns
750///
751/// Returns `Ok(true)` if the process was successfully terminated,
752/// `Ok(false)` if the process doesn't exist, or an error if termination fails.
753///
754/// # Error Scenarios
755///
756/// - **Process Not Found**: Process ID doesn't correspond to running process
757/// - **Permission Denied**: Insufficient privileges to send signals
758/// - **Command Failed**: `ps` or `kill` commands not available or failed
759/// - **Persistent Process**: Process survives both SIGTERM and SIGKILL
760///
761/// # Platform Availability
762///
763/// This function is only available on Unix-like platforms and will not
764/// compile on Windows systems.
765#[cfg(unix)]
766fn kill_process(pid: u32) -> Result<bool> {
767    use std::process::Command;
768
769    // Check if process exists using ps
770    let output = Command::new("ps").arg("-p").arg(pid.to_string()).output()?;
771
772    if !output.status.success() {
773        // Process doesn't exist
774        return Ok(false);
775    }
776
777    // Send SIGTERM for graceful shutdown
778    Command::new("kill").arg("-TERM").arg(pid.to_string()).output()?;
779
780    // Give the process time to terminate gracefully
781    for _ in 0..10 {
782        std::thread::sleep(Duration::from_millis(100));
783
784        // Check if process still exists
785        let check = Command::new("ps").arg("-p").arg(pid.to_string()).output()?;
786
787        if !check.status.success() {
788            // Process terminated gracefully
789            return Ok(true);
790        }
791    }
792
793    // Process didn't terminate gracefully, force kill
794    Command::new("kill").arg("-9").arg(pid.to_string()).output()?;
795
796    // Give a brief moment for forced termination
797    std::thread::sleep(Duration::from_millis(100));
798    Ok(true)
799}
800
801#[cfg(not(any(unix, windows)))]
802fn kill_process(_pid: u32) -> Result<bool> {
803    msg_bail_anyhow!(Message::ProcessTerminationNotSupported);
804}