3
3
package os
4
4
5
5
import (
6
+ "bufio"
6
7
"fmt"
8
+ "os/exec"
9
+ "strconv"
10
+ "strings"
7
11
"syscall"
8
12
9
13
"github.com/shirou/gopsutil/process"
@@ -13,26 +17,36 @@ import (
13
17
// KillTree sends signal to whole process tree, starting from given pid as root.
14
18
// Order of signalling in process tree is undefined.
15
19
func KillTree (signal syscall.Signal , pid int32 ) error {
16
- proc , err := process . NewProcess (pid )
20
+ pgids , err := getProcessGroupsInTree (pid )
17
21
if err != nil {
18
22
return err
19
23
}
20
24
25
+ signals := wrapWithStopAndCont (signal )
26
+ return sendSignalsToProcessGroups (signals , pgids )
27
+ }
28
+
29
+ func getProcessGroupsInTree (pid int32 ) ([]int , error ) {
30
+ proc , err := process .NewProcess (pid )
31
+ if err != nil {
32
+ return nil , err
33
+ }
34
+
21
35
processes := getAllChildren (proc )
22
36
processes = append (processes , proc )
23
37
24
38
curPid := syscall .Getpid ()
25
39
curPgid , err := syscall .Getpgid (curPid )
26
40
if err != nil {
27
- return fmt .Errorf ("error getting current process pgid: %s" , err )
41
+ return nil , fmt .Errorf ("error getting current process pgid: %s" , err )
28
42
}
29
43
30
44
var pgids []int
31
45
pgidsSeen := map [int ]bool {}
32
46
for _ , proc := range processes {
33
47
pgid , err := syscall .Getpgid (int (proc .Pid ))
34
48
if err != nil {
35
- return fmt .Errorf ("error getting child process pgid: %s" , err )
49
+ return nil , fmt .Errorf ("error getting child process pgid: %s" , err )
36
50
}
37
51
if pgid == curPgid {
38
52
continue
@@ -42,8 +56,7 @@ func KillTree(signal syscall.Signal, pid int32) error {
42
56
pgidsSeen [pgid ] = true
43
57
}
44
58
}
45
-
46
- return wrapWithStopAndCont (signal , pgids )
59
+ return pgids , nil
47
60
}
48
61
49
62
// getAllChildren gets whole descendants tree of given process. Order of returned
@@ -61,27 +74,124 @@ func getAllChildren(proc *process.Process) []*process.Process {
61
74
// wrapWithStopAndCont wraps original process tree signal sending with SIGSTOP and
62
75
// SIGCONT to prevent processes from forking during termination, so we will not
63
76
// have orphaned processes after.
64
- func wrapWithStopAndCont (signal syscall.Signal , pgids [] int ) error {
77
+ func wrapWithStopAndCont (signal syscall.Signal ) []syscall. Signal {
65
78
signals := []syscall.Signal {syscall .SIGSTOP , signal }
66
79
if signal != syscall .SIGKILL { // no point in sending any signal after SIGKILL
67
80
signals = append (signals , syscall .SIGCONT )
68
81
}
82
+ return signals
83
+ }
69
84
70
- for _ , currentSignal := range signals {
71
- if err := sendSignalToProcessGroups (currentSignal , pgids ); err != nil {
72
- return err
85
+ func sendSignalsToProcessGroups (signals []syscall.Signal , pgids []int ) error {
86
+ for _ , signal := range signals {
87
+ for _ , pgid := range pgids {
88
+ log .Infof ("Sending signal %s to pgid %d" , signal , pgid )
89
+ err := syscall .Kill (- pgid , signal )
90
+ if err != nil {
91
+ log .Infof ("Error sending signal to pgid %d: %s" , pgid , err )
92
+ }
73
93
}
74
94
}
75
95
return nil
76
96
}
77
97
78
- func sendSignalToProcessGroups (signal syscall.Signal , pgids []int ) error {
98
+ // KillTreeWithExcludes sends signal to whole process tree, starting from given pid as root.
99
+ // Omits processes matching names specified in processesToExclude. Kills using pids instead of pgids.
100
+ func KillTreeWithExcludes (signal syscall.Signal , pid int32 , processesToExclude []string ) error {
101
+ log .Infof ("Will send signal %s to tree starting from %d" , signal .String (), pid )
102
+
103
+ if len (processesToExclude ) == 0 {
104
+ return KillTree (signal , pid )
105
+ }
106
+
107
+ pgids , err := getProcessGroupsInTree (pid )
108
+ if err != nil {
109
+ return err
110
+ }
111
+
112
+ log .Infof ("Found process groups: %v" , pgids )
113
+
114
+ pids , err := findProcessesInGroups (pgids )
115
+ if err != nil {
116
+ return err
117
+ }
118
+
119
+ log .Infof ("Found processes in groups: %v" , pids )
120
+
121
+ pids , err = excludeProcesses (pids , processesToExclude )
122
+ if err != nil {
123
+ return err
124
+ }
125
+
126
+ signals := wrapWithStopAndCont (signal )
127
+ return sendSignalsToProcesses (signals , pids )
128
+ }
129
+
130
+ func findProcessesInGroups (pgids []int ) ([]int , error ) {
131
+ var pids []int
79
132
for _ , pgid := range pgids {
80
- log .Infof ("Sending signal %s to pgid %d" , signal , pgid )
81
- err := syscall .Kill (- pgid , signal )
133
+ cmd := exec .Command ("pgrep" , "-g" , strconv .Itoa (pgid ))
134
+ output , err := cmd .CombinedOutput ()
135
+ if err != nil {
136
+ return nil , fmt .Errorf ("'pgrep -g %d' failed: %s" , pgid , err )
137
+ }
138
+ if ! cmd .ProcessState .Success () {
139
+ return nil , fmt .Errorf ("'pgrep -g %d' failed, output was: '%s'" , pgid , output )
140
+ }
141
+
142
+ scanner := bufio .NewScanner (strings .NewReader (string (output )))
143
+ for scanner .Scan () {
144
+ pid , err := strconv .Atoi (scanner .Text ())
145
+ if err != nil {
146
+ return nil , fmt .Errorf ("cannot convert pgrep output: %s. Output was '%s'" , err , output )
147
+ }
148
+ pids = append (pids , pid )
149
+ }
150
+ }
151
+
152
+ return pids , nil
153
+ }
154
+
155
+ func excludeProcesses (pids []int , processesToExclude []string ) ([]int , error ) {
156
+ var retainedPids []int
157
+ for _ , pid := range pids {
158
+ proc , err := process .NewProcess (int32 (pid ))
159
+ if err != nil {
160
+ return nil , err
161
+ }
162
+
163
+ name , err := proc .Name ()
82
164
if err != nil {
83
- log .Infof ("Error sending signal to pgid %d: %s" , pgid , err )
84
- return err
165
+ log .Infof ("Could not get process name of %d, will not exclude it from kill" , pid )
166
+ } else if isExcluded (name , processesToExclude ) {
167
+ log .Infof ("Excluding process %s with pid %d from kill" , name , pid )
168
+ continue
169
+ }
170
+
171
+ retainedPids = append (retainedPids , pid )
172
+ }
173
+
174
+ return retainedPids , nil
175
+ }
176
+
177
+ func isExcluded (name string , namesToExclude []string ) bool {
178
+ for _ , exclude := range namesToExclude {
179
+ if strings .ToLower (name ) == strings .ToLower (exclude ) {
180
+ return true
181
+ }
182
+ }
183
+
184
+ return false
185
+ }
186
+
187
+ func sendSignalsToProcesses (signals []syscall.Signal , pids []int ) error {
188
+ for _ , signal := range signals {
189
+ for _ , pid := range pids {
190
+ log .Infof ("Sending signal %s to pid %d" , signal , pid )
191
+ err := syscall .Kill (pid , signal )
192
+ if err != nil {
193
+ log .Infof ("Error sending signal to pid %d: %s" , pid , err )
194
+ }
85
195
}
86
196
}
87
197
return nil
0 commit comments