-
Notifications
You must be signed in to change notification settings - Fork 0
/
killer
executable file
·103 lines (91 loc) · 3 KB
/
killer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#!/bin/sh
# kill everything, as gracefully and as correctly as possible
# usage:
# killer ["nofork"] [OPTION]... [PID]...
# "nofork" prevents forking
# PID will not be killed
#
# options:
# -c: send CONT signal after TERM
# -d: dry run, echos kill commands instead of running them
# -p: also kill parent process, only applicable with nofork
# -t <t>: timeout time. default is 15s
# -u <users>: set users to kill, comma delimited
# -v: invert users to match
#
# examples:
# `killer` without root will fork, and kill all processes owned by $USER, except login shells.
# 'killer -l` will kill login shells after killing everything else.
# `killer` as root kills every process owned by every user (including root) except their login shells.
# `killer nofork` will prevent forking. this can cause the script to kill a distant parent that it needs to survive, like Xorg hosting the terminal that runs `killer`.
if [ "$1" = "nofork" ]; then shift
else
exec setsid -f "$0" nofork "$@"
fi
# hide kernel processes, needs procps version >= 4
export LIBPROC_HIDE_KERNEL=
kill="kill"
timeout=15000
[ "$(whoami)" = root ] || users=$USER
while true; do
[ "$1" = "-c" ] && { continue=true; shift; continue; }
[ "$1" = "-d" ] && { kill="echo -> kill"; shift; continue; }
[ "$1" = "-p" ] && { killppid=true; shift; continue; }
[ "$1" = "-t" ] && { timeout=$(($2 * 1000)); shift 2; continue; }
[ "$1" = "-u" ] && { users=$2; shift 2; continue; }
[ "$1" = "-v" ] && { invert=true; shift; continue; }
break
done
sedscript=$(mktemp)
trap 'rm "$sedscript"; exit' INT TERM
addpid(){
printf '/^%s$/d\n' "$@" >> "$sedscript"
}
[ "$killppid" ] || ppid=$(cut -d' ' -f4 < "/proc/$$/stat")
addpid "$@" 1 $ppid
if [ "$users" ]; then
alias getpids='pgrep '"$([ "$invert" ] && echo '-v ')"'-u "$users"'
else
alias getpids='pgrep -v -P2'
fi
pidxarger(){
addpid $(cut -d' ' -f4 </proc/self/stat) # like $$ but not main process
tmp=$(getpids)
echo "$tmp" | sed -f "$sedscript" | xargs -d'\n' "$@"
}
# kill as much as possible to reduce process count
pidxarger ps -o pid= -p >/dev/null 2>&1 && {
echo 'killing everything...'
pidxarger $kill -s TERM 2>/dev/null
[ "$continue" ] && pidxarger $kill -s CONT 2>/dev/null
sleep 1
list=$(pidxarger ps -p 2>/dev/null) && {
echo 'remaining processes:'
echo "$list"
echo
# of remaining/newly-spawned processes, give them one last chance
if env kill --version | grep -qF 'pidfd'; then
echo 'killing leftovers after timeout...'
echo "timeout time: $((timeout / 1000))s"
pidxarger -P0 -L1 $kill --timeout "$timeout" KILL -s HUP 2>/dev/null &
time=$(date +%s.%N)
wait
echo "took $(echo "$(date +%s.%N) - $time" | bc)s"
else
pidxarger $kill -s HUP 2>/dev/null # handle TTY logins
echo 'kill util not installed with pidfd support. sleeping...'
sleep 7 &
wait
fi
list=$(pidxarger ps -p 2>/dev/null) && {
# if something is somehow still up
echo 'remaining processes:'
echo "$list"
echo
echo 'kill -9ing anything left...'
env $kill -s KILL -- -1
}
}
}
echo 'done'
rm "$sedscript"