|
19 | 19 | package util |
20 | 20 |
|
21 | 21 | import ( |
| 22 | + "errors" |
22 | 23 | "os" |
| 24 | + "syscall" |
23 | 25 | "unsafe" |
24 | 26 |
|
25 | 27 | "golang.org/x/sys/windows" |
26 | 28 | ) |
27 | 29 |
|
28 | | -// KillProcess kills a running OS process |
29 | | -func KillProcess(pid int, _ os.Signal) error { |
| 30 | +const ( |
| 31 | + ERROR_INVALID_PARAMETER = syscall.Errno(87) |
| 32 | + |
| 33 | + STATUS_CANCELLED = uint32(0xC0000120) |
| 34 | + |
| 35 | + processTerminateWaitInMs = 1000 |
| 36 | + |
| 37 | + killChildsPassCount = 4 |
| 38 | +) |
| 39 | + |
| 40 | +var ( |
| 41 | + errFinishedProcess = errors.New("os: process already finished") |
| 42 | +) |
30 | 43 |
|
31 | | - p, err := os.FindProcess(pid) |
| 44 | +// FindProcess looks for a running process by its pid |
| 45 | +func FindProcess(pid int) (*os.Process, error) { |
| 46 | + var h syscall.Handle |
| 47 | + |
| 48 | + process, err := os.FindProcess(pid) |
| 49 | + if err != nil { |
| 50 | + if isInvalidParameterError(err) { // NOTE: See function definition for details |
| 51 | + return nil, nil |
| 52 | + } |
| 53 | + return nil, err |
| 54 | + } |
| 55 | + |
| 56 | + // If we have a process, check if it is terminated |
| 57 | + h, err = syscall.OpenProcess(syscall.SYNCHRONIZE, false, uint32(pid)) |
32 | 58 | if err == nil { |
| 59 | + defer func() { |
| 60 | + _ = syscall.CloseHandle(h) |
| 61 | + }() |
33 | 62 |
|
34 | | - for _, v := range getChildrenProcesses(pid) { |
35 | | - _ = v.Kill() |
| 63 | + ret, e2 := syscall.WaitForSingleObject(h, 0) |
| 64 | + if e2 == nil && ret == syscall.WAIT_OBJECT_0 { |
| 65 | + return nil, nil |
36 | 66 | } |
| 67 | + } else { |
| 68 | + if isInvalidParameterError(err) { // NOTE: See function definition for details |
| 69 | + return nil, nil |
| 70 | + } |
| 71 | + } |
37 | 72 |
|
38 | | - err = p.Kill() |
| 73 | + return process, nil |
| 74 | +} |
| 75 | + |
| 76 | +// KillProcess kills a running OS process |
| 77 | +func KillProcess(pid int, signal os.Signal) error { |
| 78 | + // Signal(0) only checks if we have access to kill a process and if it is really dead |
| 79 | + if signal == syscall.Signal(0) { |
| 80 | + return isProcessAlive(pid) |
39 | 81 | } |
| 82 | + |
| 83 | + return killProcessTree(pid) |
| 84 | +} |
| 85 | + |
| 86 | +func isProcessAlive(pid int) error { |
| 87 | + var ret uint32 |
| 88 | + |
| 89 | + h, err := syscall.OpenProcess(syscall.SYNCHRONIZE|syscall.PROCESS_TERMINATE, false, uint32(pid)) |
| 90 | + if err != nil { |
| 91 | + if isInvalidParameterError(err) { // NOTE: See function definition for details |
| 92 | + return errFinishedProcess |
| 93 | + } |
| 94 | + return err |
| 95 | + } |
| 96 | + ret, err = syscall.WaitForSingleObject(h, 0) |
| 97 | + if err == nil && ret == syscall.WAIT_OBJECT_0 { |
| 98 | + err = errFinishedProcess |
| 99 | + } |
| 100 | + |
| 101 | + _ = syscall.CloseHandle(h) |
40 | 102 | return err |
41 | 103 | } |
42 | 104 |
|
43 | | -func getChildrenProcesses(parentPid int) []*os.Process { |
44 | | - out := []*os.Process{} |
| 105 | +func killProcessTree(pid int) error { |
| 106 | + err := killProcess(pid) |
| 107 | + if err != nil { |
| 108 | + return err |
| 109 | + } |
| 110 | + |
| 111 | + // We do several passes just in case the process being killed spawns a new one |
| 112 | + for pass := 1; pass <= killChildsPassCount; pass++ { |
| 113 | + childProcessList := getChildProcesses(pid) |
| 114 | + if len(childProcessList) == 0 { |
| 115 | + break |
| 116 | + } |
| 117 | + for _, childPid := range childProcessList { |
| 118 | + killProcessTree(childPid) |
| 119 | + } |
| 120 | + } |
| 121 | + |
| 122 | + return nil |
| 123 | +} |
| 124 | + |
| 125 | +func getChildProcesses(pid int) []int { |
| 126 | + var pe32 windows.ProcessEntry32 |
| 127 | + |
| 128 | + out := make([]int, 0) |
| 129 | + |
45 | 130 | snap, err := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, uint32(0)) |
46 | | - if err == nil { |
47 | | - var pe32 windows.ProcessEntry32 |
48 | | - |
49 | | - defer windows.CloseHandle(snap) |
50 | | - |
51 | | - pe32.Size = uint32(unsafe.Sizeof(pe32)) |
52 | | - if err := windows.Process32First(snap, &pe32); err == nil { |
53 | | - for { |
54 | | - if pe32.ParentProcessID == uint32(parentPid) { |
55 | | - p, err := os.FindProcess(int(pe32.ProcessID)) |
56 | | - if err == nil { |
57 | | - out = append(out, p) |
58 | | - } |
59 | | - } |
60 | | - if err = windows.Process32Next(snap, &pe32); err != nil { |
61 | | - break |
62 | | - } |
63 | | - } |
| 131 | + if err != nil { |
| 132 | + return out |
| 133 | + } |
| 134 | + |
| 135 | + defer func() { |
| 136 | + _ = windows.CloseHandle(snap) |
| 137 | + }() |
| 138 | + |
| 139 | + pe32.Size = uint32(unsafe.Sizeof(pe32)) |
| 140 | + err = windows.Process32First(snap, &pe32) |
| 141 | + for err != nil { |
| 142 | + if pe32.ParentProcessID == uint32(pid) { |
| 143 | + // Add to list |
| 144 | + out = append(out, int(pe32.ProcessID)) |
64 | 145 | } |
| 146 | + |
| 147 | + err = windows.Process32Next(snap, &pe32) |
65 | 148 | } |
| 149 | + |
66 | 150 | return out |
67 | 151 | } |
| 152 | + |
| 153 | +func killProcess(pid int) error { |
| 154 | + h, err := syscall.OpenProcess(syscall.SYNCHRONIZE | syscall.PROCESS_TERMINATE, false, uint32(pid)) |
| 155 | + if err == nil { |
| 156 | + err = syscall.TerminateProcess(h, STATUS_CANCELLED) |
| 157 | + if err == nil { |
| 158 | + _, _ = syscall.WaitForSingleObject(h, processTerminateWaitInMs) |
| 159 | + } |
| 160 | + |
| 161 | + _ = syscall.CloseHandle(h) |
| 162 | + } |
| 163 | + |
| 164 | + return err |
| 165 | +} |
| 166 | + |
| 167 | +// NOTE: Unlike Unix, Windows tries to open the target process in order to kill it. |
| 168 | +// ERROR_INVALID_PARAMETER is returned if the process does not exists. |
| 169 | +// To mimic other OS behavior, if the process does not exist, don't return an error |
| 170 | +func isInvalidParameterError(err error) bool { |
| 171 | + var syscallError syscall.Errno |
| 172 | + |
| 173 | + if errors.As(err, &syscallError) { |
| 174 | + if syscallError == ERROR_INVALID_PARAMETER { |
| 175 | + return true |
| 176 | + } |
| 177 | + } |
| 178 | + return false |
| 179 | +} |
0 commit comments