diff --git a/cli/htmgo/tasks/process/pid_unix.go b/cli/htmgo/tasks/process/pid_unix.go new file mode 100644 index 0000000..d85321c --- /dev/null +++ b/cli/htmgo/tasks/process/pid_unix.go @@ -0,0 +1,47 @@ +//go:build linux || darwin + +package process + +import ( + "errors" + "os" + "os/exec" + "syscall" +) + +func KillProcess(process *os.Process) error { + return syscall.Kill(-process.Pid, syscall.SIGKILL) +} + +func PrepareCommand(command *exec.Cmd) { + command.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} +} + +func PidExists(pid int32) bool { + if pid <= 0 { + return false + } + proc, err := os.FindProcess(int(pid)) + if err != nil { + return false + } + err = proc.Signal(syscall.Signal(0)) + if err == nil { + return true + } + if err.Error() == "os: process already finished" { + return false + } + var errno syscall.Errno + ok := errors.As(err, &errno) + if !ok { + return false + } + switch errno { + case syscall.ESRCH: + return false + case syscall.EPERM: + return true + } + return false +} diff --git a/cli/htmgo/tasks/process/pid_windows.go b/cli/htmgo/tasks/process/pid_windows.go new file mode 100644 index 0000000..4dcb74a --- /dev/null +++ b/cli/htmgo/tasks/process/pid_windows.go @@ -0,0 +1,31 @@ +package process + +import ( + "os" + "os/exec" +) +import "golang.org/x/sys/windows" + +func KillProcess(process *os.Process) error { + return process.Kill() +} + +func PrepareCommand(command *exec.Cmd) { + +} + +func PidExists(pid int32) bool { + var handle, err = windows.OpenProcess(windows.PROCESS_QUERY_INFORMATION, false, uint32(pid)) + if err != nil { + return false + } + defer windows.CloseHandle(handle) + + var exitCode uint32 + err = windows.GetExitCodeProcess(handle, &exitCode) + if err != nil { + return false + } + + return exitCode == 259 +} diff --git a/cli/htmgo/tasks/process/process.go b/cli/htmgo/tasks/process/process.go index c476c38..ee0b011 100644 --- a/cli/htmgo/tasks/process/process.go +++ b/cli/htmgo/tasks/process/process.go @@ -1,16 +1,13 @@ package process import ( - "errors" "fmt" "log/slog" "os" "os/exec" "path/filepath" - "runtime" "slices" "strings" - "syscall" "time" ) @@ -84,8 +81,7 @@ func KillAll(skipFlag ...RunFlag) { if shouldSkipKilling(command.flags, skipFlag) { continue } - pid := command.cmd.Process.Pid - err := syscall.Kill(-pid, syscall.SIGKILL) + err := KillProcess(command.cmd.Process) if err != nil { continue } @@ -100,12 +96,9 @@ func KillAll(skipFlag ...RunFlag) { if shouldSkipKilling(c.flags, skipFlag) { continue } - exists, err := PidExists(int32(c.cmd.Process.Pid)) - if err != nil { - finished = false - } + exists := PidExists(int32(c.cmd.Process.Pid)) if exists { - syscall.Kill(-c.cmd.Process.Pid, syscall.SIGKILL) + KillProcess(c.cmd.Process) finished = false } } @@ -122,35 +115,6 @@ func KillAll(skipFlag ...RunFlag) { slog.Debug("all processes killed\n") } -func PidExists(pid int32) (bool, error) { - if pid <= 0 { - return false, fmt.Errorf("invalid pid %v", pid) - } - proc, err := os.FindProcess(int(pid)) - if err != nil { - return false, err - } - err = proc.Signal(syscall.Signal(0)) - if err == nil { - return true, nil - } - if err.Error() == "os: process already finished" { - return false, nil - } - var errno syscall.Errno - ok := errors.As(err, &errno) - if !ok { - return false, err - } - switch errno { - case syscall.ESRCH: - return false, nil - case syscall.EPERM: - return true, nil - } - return false, err -} - func RunOrExit(command string) { _ = Run(command, ExitOnError) } @@ -180,9 +144,7 @@ func Run(command string, flags ...RunFlag) error { parts := strings.Fields(command) cmd := exec.Command(parts[0], parts[1:]...) - if runtime.GOOS != "windows" { - cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} - } + PrepareCommand(cmd) if slices.Contains(flags, Silent) { cmd.Stdout = nil