$search
00001 #!/usr/bin/env python 00002 # Software License Agreement (BSD License) 00003 # 00004 # Copyright (c) 2008, Willow Garage, Inc. 00005 # All rights reserved. 00006 # 00007 # Redistribution and use in source and binary forms, with or without 00008 # modification, are permitted provided that the following conditions 00009 # are met: 00010 # 00011 # * Redistributions of source code must retain the above copyright 00012 # notice, this list of conditions and the following disclaimer. 00013 # * Redistributions in binary form must reproduce the above 00014 # copyright notice, this list of conditions and the following 00015 # disclaimer in the documentation and/or other materials provided 00016 # with the distribution. 00017 # * Neither the name of Willow Garage, Inc. nor the names of its 00018 # contributors may be used to endorse or promote products derived 00019 # from this software without specific prior written permission. 00020 # 00021 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 00022 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 00023 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 00024 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 00025 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 00026 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 00027 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 00028 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 00029 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 00030 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 00031 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 00032 # POSSIBILITY OF SUCH DAMAGE. 00033 # 00034 # Revision $Id$ 00035 00036 ## 00037 00038 """ 00039 usage: %(prog)s [list|kill|save|halt] 00040 """ 00041 00042 PKG = 'ckill' # this package name 00043 NAME = 'ckill' 00044 00045 import os, sys, string, time 00046 import re 00047 import subprocess 00048 import UserDict 00049 from optparse import OptionParser 00050 import pwd 00051 00052 class ProcessError(Exception): 00053 pass 00054 00055 def spawn(cmd): 00056 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 00057 00058 stdout, stderr = p.communicate() 00059 if p.returncode != 0: 00060 raise ProcessError, "Error: %s" % stderr 00061 00062 return stdout 00063 00064 class Process: 00065 def __init__(self, host, username, uid, pid, ppid, cmd, args): 00066 self.host = host 00067 self.pid = pid 00068 self.ppid = ppid 00069 self.username = username 00070 self.uid = int(uid) 00071 self.cmd = cmd 00072 self.arg_line = args 00073 self.args = args.split() 00074 00075 00076 if self.cmd.startswith("python"): 00077 self.cmd2 = string.join(self.args[:2]) 00078 else: 00079 self.cmd2 = self.args[0] 00080 00081 def display(self): 00082 print "%-6s %-6s %-6s %-12s %-6s %-20s -- %s" % (self.host, self.pid, self.ppid, self.username, self.uid, self.cmd, self.cmd2) 00083 00084 def __repr__(self): 00085 return "<Process %s %s %s %s %s %s>" % (self.host, self.pid, self.ppid, self.username, self.uid, self.cmd) 00086 00087 class ProcessList(UserDict.UserDict): 00088 def __init__(self, hosts): 00089 UserDict.UserDict.__init__(self) 00090 self.hosts = hosts 00091 self.fetch() 00092 00093 def fetch(self): 00094 self.clear() 00095 00096 for host in self.hosts: 00097 self.fetch_process_list(host) 00098 00099 def fetch_process_list(self, host): 00100 cmd = ['ps', '-e', '--no-headers', '-o', 'pid=', '-o', 'ppid=', '-o', 'user=', '-o', 'uid=', '-o', 'comm=', '-o', 'args=', ] 00101 ppid = None 00102 if host != "localhost": 00103 ppid = ['python', '-c', "'import os; print os.getppid();print'", ";"] 00104 cmd = ['ssh', host, ] + ppid + cmd 00105 00106 stdout = spawn(cmd) 00107 00108 lines = stdout.split("\n") 00109 00110 thispid = None 00111 if ppid: 00112 thispid = int(lines[0].strip()) 00113 00114 for line in lines[1:]: 00115 if not line: continue 00116 00117 parts = line.split(None, 5) 00118 if len(parts) != 6: continue 00119 00120 pid = int(parts[0]) 00121 ppid = int(parts[1]) 00122 username = parts[2] 00123 uid = parts[3] 00124 cmd = parts[4] 00125 00126 p = Process(host, username, uid, pid, ppid, cmd, parts[5]) 00127 self[pid] = p 00128 00129 if thispid: 00130 ## remove the ssh processes from the process table. 00131 p = self.get(thispid, None) 00132 while p: 00133 del self[p.pid] 00134 p = self.get(p.ppid, None) 00135 00136 00137 00138 class RosInit: 00139 def __init__(self, hosts, minuid, maxuid, whitelistfn, blacklistfn, uid, regex): 00140 self.whitelist = {} 00141 self.blacklist = {} 00142 00143 self.minuid = minuid 00144 self.maxuid = maxuid 00145 self.hosts = hosts 00146 self.uid = uid 00147 self.regex = re.compile(regex) 00148 00149 if whitelistfn: 00150 self.whitelistfn = whitelistfn 00151 self.whitelistpath, fn = os.path.split(self.whitelistfn) 00152 self.readWhitelist(self.whitelistfn) 00153 00154 if blacklistfn: 00155 self.blacklistfn = blacklistfn 00156 self.blacklistpath, fn = os.path.split(self.blacklistfn) 00157 self.readBlacklist(blacklistfn) 00158 00159 def readWhitelist(self, fn): 00160 if os.path.exists(fn): 00161 lines = open(fn).readlines() 00162 for line in lines: 00163 if line.startswith("#include"): 00164 parts = line.split(' ', 1) 00165 ifn = parts[1].strip() 00166 if ifn: 00167 ifn = os.path.join(self.whitelistpath, ifn) 00168 self.readWhitelist(ifn) 00169 00170 self.whitelist[line.strip()] = 1 00171 00172 def readBlacklist(self, fn): 00173 lines = open(fn).readlines() 00174 for line in lines: 00175 if line.startswith("#include"): 00176 parts = line.split(' ', 1) 00177 ifn = parts[1].strip() 00178 if ifn: 00179 ifn = os.path.join(self.blacklistpath, ifn) 00180 self.readBlacklist(ifn) 00181 00182 elif not line.startswith("#"): 00183 self.blacklist[line.strip()] = 1 00184 00185 00186 def kill_list(self, host): 00187 pl = ProcessList([host]) 00188 00189 ## generate a list of pids so that we don't accidentally kill this process. 00190 thislist = [] 00191 if host == "localhost": 00192 p = pl.get(os.getpid(), None) 00193 while p: 00194 thislist.append(p.pid) 00195 p = pl.get(p.ppid, None) 00196 00197 todo = [] 00198 for p in pl.values(): 00199 if p.cmd2[0] == '[' and p.cmd2[-1] == ']': 00200 ## skip all kernel threads 00201 continue 00202 if p.host == "localhost" and p.pid in thislist: 00203 ## skip this process 00204 continue 00205 # if we have a blacklist, filter out anything not in it 00206 if self.blacklist: 00207 include_cmd = False 00208 for term in self.blacklist.keys(): 00209 if re.search(term, p.cmd2): 00210 include_cmd = True 00211 if not include_cmd: 00212 continue 00213 else: 00214 todo.append(p) 00215 continue 00216 if p.uid < self.minuid and p.uid != self.uid: 00217 # skip a likely system process 00218 continue 00219 if p.uid > self.maxuid and p.uid != self.uid: 00220 # skip a likely system process 00221 continue 00222 if p.cmd in self.whitelist: 00223 ## skip whitelisted process based on 'ps -o comm=' 00224 continue 00225 if p.cmd2 in self.whitelist: 00226 ## skip whitelisted process based on 'ps -o args=' 00227 continue 00228 00229 # Don't kill process if it doesn't match the regex 00230 if self.regex.match(p.cmd) is None and self.regex.match(p.cmd2) is None: 00231 continue 00232 00233 todo.append(p) 00234 00235 return todo 00236 00237 def kill(self, signal=15): 00238 if os.getuid() != 0: 00239 print "you need to be root to run this." 00240 return 00241 00242 for host in self.hosts: 00243 todo = self.kill_list(host) 00244 todo = map(lambda x: str(x.pid), todo) 00245 00246 if len(todo) == 0: continue 00247 00248 cmd = ['kill'] 00249 cmd = cmd + ["-%s" % signal] 00250 cmd = cmd + todo 00251 if host != "localhost": cmd = ['ssh', host] + cmd 00252 00253 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 00254 stdout, stderr = p.communicate() 00255 if p.returncode != 0: 00256 if stderr.find("No such process") == -1: 00257 print "Error killing processes:", host, stderr 00258 00259 def list(self): 00260 for host in self.hosts: 00261 todo = self.kill_list(host) 00262 00263 for p in todo: 00264 #print p.host, p.pid, p.username, p.cmd2 00265 p.display() 00266 00267 def save(self): 00268 whitelist = {} 00269 pl = ProcessList(self.hosts) 00270 for p in pl.values(): 00271 if p.cmd2[0] == '[' and p.cmd2[-1] == ']': 00272 ## skip the kernel threads 00273 continue 00274 whitelist[p.cmd2] = 1 00275 00276 whitelist = whitelist.keys() 00277 whitelist.sort() 00278 for c in whitelist: 00279 print c 00280 00281 def halt(self): 00282 for host in self.hosts: 00283 cmd = ['shutdown', '-h', 'now'] 00284 if host != "localhost": cmd = ['ssh', host] + cmd 00285 00286 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 00287 stdout, stderr = p.communicate() 00288 if p.returncode != 0: 00289 if stderr.find("No such process") == -1: 00290 print "Error:", host, stderr 00291 00292 def reboot(self): 00293 for host in self.hosts: 00294 cmd = ['shutdown', '-r', 'now'] 00295 if host != "localhost": cmd = ['ssh', host] + cmd 00296 00297 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 00298 stdout, stderr = p.communicate() 00299 if p.returncode != 0: 00300 if stderr.find("No such process") == -1: 00301 print "Error:", host, stderr 00302 00303 00304 def usage(prog): 00305 print __doc__ % vars() 00306 00307 def main(argv, stdout, environ): 00308 progname = argv[0] 00309 00310 parser = OptionParser(usage="""%prog [list|kill|save|halt|reboot] 00311 00312 'kill' will send the signal specified by --sig to all processes. 00313 """ 00314 00315 ) 00316 00317 parser.add_option("--blacklist", action="store", dest="blacklist", default=None, help="List of programs to always kill") 00318 parser.add_option("--whitelist", action="store", dest="whitelist", default=None, help="List of programs to never kill (Default: /etc/ckill/whitelist)") 00319 parser.add_option("--minuid", action="store", type="int", dest="minuid", default=1000, help="Never kill programs run by users below this UID (Default: 1000 -- generally considered first user)") 00320 parser.add_option("--maxuid", action="store", type="int", dest="maxuid", default=29999, help="Never kill programs run by users above this UID (Default: 29999 -- generally considered last user)") 00321 parser.add_option("--user", action="store", type="string", dest="user", default='ros', help="Explicitly include user. (Defaults to user: ros)") 00322 parser.add_option("--conf", action="store", dest="confpath", default="/etc/ckill", help="Specify an alternative conf location (Default: /etc/ckill)") 00323 parser.add_option("--sig", action="store", dest="signum", default="TERM", help="Specify the signal to send (Default: TERM)") 00324 parser.add_option("--regex", action="store", dest="regex", default=".*", help="Specify a regular expression for processes to kill.") 00325 00326 # Parse arguments 00327 (options, args) = parser.parse_args() 00328 00329 sigvals = { 'HUP':1, 'SIGHUP':1, 00330 'INT':2, 'SIGINT':2, 00331 'QUIT':3, 'SIGQUIT':3, 00332 'KILL':9, 'SIGKILL': 9, 00333 'TERM':15, 'SIGTERM':15 } 00334 00335 signum = 15 00336 00337 try: 00338 signum = int(options.signum) 00339 except ValueError: 00340 try: 00341 signum = sigvals[options.signum] 00342 except KeyError: 00343 parser.error("Unknown signal: %s"%options.signum) 00344 00345 # Require a command 00346 if len(args) == 0: 00347 parser.error("You must specify a command.") 00348 return 00349 00350 # Read in hosts 00351 hostfn = os.path.join(options.confpath, "hosts") 00352 hosts = [] 00353 if os.path.exists(hostfn): 00354 hostlines = open(hostfn).readlines() 00355 for host in hostlines: hosts.append(host.strip()) 00356 else: 00357 hosts.append('localhost') 00358 00359 if options.whitelist is None: 00360 options.whitelist = os.path.join(options.confpath, "whitelist") 00361 00362 if options.user: 00363 try: 00364 uid = pwd.getpwnam(options.user).pw_uid 00365 except KeyError: 00366 # There should never be a -1 uid, so this is a safe place to default this to 00367 uid = -1 00368 00369 ri = RosInit(hosts, options.minuid, options.maxuid, options.whitelist, options.blacklist, uid, options.regex) 00370 00371 cmd = args[0] 00372 00373 if cmd == "term": 00374 ri.kill(15) 00375 elif cmd == "kill": 00376 ri.kill(signum) 00377 elif cmd == "save": 00378 ri.save() 00379 elif cmd == "list": 00380 ri.list() 00381 elif cmd == "halt": 00382 ri.halt() 00383 elif cmd == "reboot": 00384 ri.reboot() 00385 else: 00386 parser.error('Uknown command: %s'%cmd) 00387 00388 00389 if __name__ == "__main__": 00390 main(sys.argv, sys.stdout, os.environ)