项目文件夹

文件
wehub-resource-sync 26f897c1ec
release / release-please (push) Failing after 1m49s
docs / build (push) Failing after 6m34s
release / build-and-upload (arm64, linux) (push) Has been cancelled
release / build-and-upload (arm64, windows) (push) Has been cancelled
release / build-darwin (amd64, darwin) (push) Has been cancelled
release / checksums (push) Has been cancelled
release / finalize (push) Has been cancelled
release / build-darwin (arm64, darwin) (push) Has been cancelled
release / build-and-upload (amd64, linux) (push) Has been cancelled
release / build-and-upload (amd64, windows) (push) Has been cancelled
docs / deploy (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:34:16 +08:00

106 行
3.3 KiB
Go

//go:build windows
package daemon
import (
"fmt"
"os/exec"
"strconv"
"strings"
"syscall"
"time"
"github.com/kunchenguid/no-mistakes/internal/winproc"
"golang.org/x/sys/windows"
)
const windowsStillActive = 259
// psListDaemonProcessesScript is the PowerShell that emits one
// "<pid>\t<commandline>" line per process with a command line, consumed by
// listDaemonProcesses. The backtick-t is PowerShell's tab inside the
// double-quoted interpolated string.
const psListDaemonProcessesScript = "$ErrorActionPreference='SilentlyContinue'; Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -ne $null } | ForEach-Object { \"$($_.ProcessId)`t$($_.CommandLine)\" }"
// listDaemonProcesses enumerates running processes via PowerShell CIM and
// returns the ones that look like `no-mistakes daemon run --root <root>`. It
// powers the collision detection in reconcileCollidingDaemons on Windows.
// Failures (e.g. PowerShell absent) fail open in the caller.
func listDaemonProcesses() ([]daemonProcessInfo, error) {
cmd := exec.Command("powershell", "-NoProfile", "-NonInteractive", "-Command", psListDaemonProcessesScript)
winproc.Harden(cmd)
out, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("enumerate processes: %w", err)
}
return parseDaemonProcessOutput(string(out), splitWindowsProcessLine), nil
}
// splitWindowsProcessLine parses one "<pid>\t<commandline>" line emitted by the
// PowerShell CIM script.
func splitWindowsProcessLine(line string) (int, string, bool) {
trimmed := strings.TrimLeft(line, " \t")
idx := strings.IndexByte(trimmed, '\t')
if idx <= 0 {
return 0, "", false
}
pid, err := strconv.Atoi(trimmed[:idx])
if err != nil || pid <= 0 {
return 0, "", false
}
return pid, strings.TrimLeft(trimmed[idx+1:], " \t"), true
}
// detachedDaemonCreationFlags detaches the spawned daemon from the parent's
// console and process group so the parent CLI can exit cleanly even if the
// caller (e.g. PowerShell `Start-Process -Wait`) is waiting on the whole
// console-attached process tree. See issue #164.
const detachedDaemonCreationFlags = windows.DETACHED_PROCESS | windows.CREATE_NEW_PROCESS_GROUP
func setSysProcAttr(cmd *exec.Cmd) {
cmd.SysProcAttr = &syscall.SysProcAttr{
CreationFlags: detachedDaemonCreationFlags,
HideWindow: true,
}
}
func processRunning(pid int) (bool, error) {
if pid <= 0 {
return false, nil
}
handle, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pid))
if err != nil {
if err == windows.ERROR_INVALID_PARAMETER {
return false, nil
}
return false, err
}
defer windows.CloseHandle(handle)
var exitCode uint32
if err := windows.GetExitCodeProcess(handle, &exitCode); err != nil {
return false, err
}
return exitCode == windowsStillActive, nil
}
func processStartTime(pid int) (time.Time, error) {
if pid <= 0 {
return time.Time{}, windows.ERROR_INVALID_PARAMETER
}
handle, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pid))
if err != nil {
return time.Time{}, err
}
defer windows.CloseHandle(handle)
var created windows.Filetime
var exited windows.Filetime
var kernel windows.Filetime
var user windows.Filetime
if err := windows.GetProcessTimes(handle, &created, &exited, &kernel, &user); err != nil {
return time.Time{}, err
}
return time.Unix(0, created.Nanoseconds()), nil
}