//go:build windows // +build windows package cleanup import ( "fmt" "os" "os/exec" "strconv" "strings" "syscall" "dbbackup/internal/logger" ) // KillOrphanedProcesses finds and kills any orphaned pg_dump, pg_restore, gzip, or pigz processes (Windows implementation) func KillOrphanedProcesses(log logger.Logger) error { processNames := []string{"pg_dump.exe", "pg_restore.exe", "gzip.exe", "pigz.exe", "gunzip.exe"} myPID := os.Getpid() var killed []string var errors []error for _, procName := range processNames { pids, err := findProcessesByNameWindows(procName, myPID) if err != nil { log.Warn("Failed to search for processes", "process", procName, "error", err) continue } for _, pid := range pids { if err := killProcessWindows(pid); err != nil { errors = append(errors, fmt.Errorf("failed to kill %s (PID %d): %w", procName, pid, err)) } else { killed = append(killed, fmt.Sprintf("%s (PID %d)", procName, pid)) } } } if len(killed) > 0 { log.Info("Cleaned up orphaned processes", "count", len(killed), "processes", strings.Join(killed, ", ")) } if len(errors) > 0 { return fmt.Errorf("some processes could not be killed: %v", errors) } return nil } // findProcessesByNameWindows returns PIDs of processes matching the given name (Windows implementation) func findProcessesByNameWindows(name string, excludePID int) ([]int, error) { // Use tasklist command for Windows cmd := exec.Command("tasklist", "/FO", "CSV", "/NH", "/FI", fmt.Sprintf("IMAGENAME eq %s", name)) output, err := cmd.Output() if err != nil { // No processes found or command failed return []int{}, nil } var pids []int lines := strings.Split(strings.TrimSpace(string(output)), "\n") for _, line := range lines { if line == "" { continue } // Parse CSV output: "name","pid","session","mem" fields := strings.Split(line, ",") if len(fields) < 2 { continue } // Remove quotes from PID field pidStr := strings.Trim(fields[1], `"`) pid, err := strconv.Atoi(pidStr) if err != nil { continue } // Don't kill our own process if pid == excludePID { continue } pids = append(pids, pid) } return pids, nil } // killProcessWindows kills a process on Windows func killProcessWindows(pid int) error { // Use taskkill command cmd := exec.Command("taskkill", "/F", "/PID", strconv.Itoa(pid)) return cmd.Run() } // SetProcessGroup sets up process group for Windows (no-op, Windows doesn't use Unix process groups) func SetProcessGroup(cmd *exec.Cmd) { // Windows doesn't support Unix-style process groups // We can set CREATE_NEW_PROCESS_GROUP flag instead cmd.SysProcAttr = &syscall.SysProcAttr{ CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP, } } // KillCommandGroup kills a command on Windows func KillCommandGroup(cmd *exec.Cmd) error { if cmd.Process == nil { return nil } // On Windows, just kill the process directly return cmd.Process.Kill() }