00001 
00002 
00003 
00004 
00005 
00006 
00007 
00008 
00009 
00010 
00011 
00012 
00013 
00014 
00015 
00016 
00017 
00018 
00019 
00020 
00021 
00022 
00023 
00024 
00025 
00026 
00027 
00028 
00029 
00030 
00031 
00032 
00033 
00034 
00035 
00036 
00037 
00038 """
00039 usage: %(prog)s [list|kill|save|halt]
00040 """
00041 
00042 PKG = 'ckill' 
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       
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     
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         
00201         continue
00202       if p.host == "localhost" and p.pid in thislist:
00203         
00204         continue        
00205       
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         
00218         continue
00219       if p.uid > self.maxuid and p.uid != self.uid:
00220         
00221         continue
00222       if p.cmd in self.whitelist:
00223         
00224         continue
00225       if p.cmd2 in self.whitelist: 
00226         
00227         continue
00228 
00229       
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         
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         
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   
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   
00346   if len(args) == 0:
00347     parser.error("You must specify a command.")
00348     return
00349 
00350   
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       
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)