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