ckill.py
Go to the documentation of this file.
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)


ckill
Author(s): Scott Hassan
autogenerated on Wed Apr 23 2014 10:35:51