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