Advertisement
Guest User

Untitled

a guest
Oct 29th, 2016
2,954
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 5.16 KB | None | 0 0
  1. #!/usr/bin/env python2
  2.  
  3. # so what's the deal and what is this?
  4.  
  5. # it turns out that Linux has an interesting feature in prctl that allows any process to register itself as the child set_child_subreaper
  6. # you know when a parent's process dies before the process and how it gets reparented to pid 1 then?
  7. # it apprently turns out any process can take that role
  8. # as in any decendants of this process will now reparent to this process instead of pid 1
  9. # allowing this process to wait for them
  10.  
  11. # this essentially means you can use this to create a wrapper process that 'undaemonzies' a daemon, id est it angelizes it.
  12. # that is this python script that should really be written in C.
  13. # it expects a command line invocation as argument that double forks, but the double fork now reparents itself to the process itself
  14. # allowing it to wait on it and collect its exit status.
  15. # so you can use this with daemontools et alia to deal with those pesky services that always background themselves
  16. # similarly to djb's fghack except it's not a hack, it's reliable
  17. # it works 100% of the time in theory and collects the exit status and forwards it
  18. # on Linux anyway, this uses Linux specific features
  19.  
  20. # in its simplest form, it just takes as argument a command to run, runs it, expects this command to daemonize itself to the background
  21. # and then waits for _all_ daemons it generates and exits when the last one exits with its exist status
  22.  
  23. # if you want to wait for a specific daemon there are two options:
  24. #   you provide a --pidfile PIDFILE argument before the actual command line
  25. #   it will read the pidfile to determine the pid to wait on after the command returns and has daemonized itself
  26. #   and written itself to the pidfile, it MUST thus write to the pidfile before it returns
  27. # - this pidfile can contain multiple pids, each on a single line
  28.  
  29. #   or you provide a --shcode SHELLCODE argument before the actual command line
  30. # - this code will be executed with as sh -c and its stdout will be interpreted as pids to wait on
  31. # - this can again contain multiple pids per line
  32.  
  33. # apart from that, this program just forwards the signals it gets to the processes it is waiting on.
  34.  
  35. import prctl
  36. import subprocess
  37. import os
  38. import sys
  39. import signal
  40. import setproctitle
  41. import errno
  42.  
  43. usage = "usage: [ --pidfile PIDFILE | --shcode SHELLCODE ] command args ..."
  44. # list of signals we are going to forward to the daemons
  45. forwarding_signals = [signal.SIGINT, signal.SIGTERM, signal.SIGHUP, signal.SIGUSR1, signal.SIGUSR2]
  46.  
  47. def improper_args ():
  48.     sys.stderr.write(usage + '\n')
  49.     exit(2)
  50.  
  51. def print_help ():
  52.     sys.stdout.write(usage + '\n')
  53.  
  54. # takes a list of pids we are waiting on and produces a signal handler
  55. # this signal handler just forwards the signal to all the pids
  56. # if given None instead a list then we forward to all children
  57. def make_signal_forwarder ( waitpids ):
  58.     def signal_forwarder ( signum, _ ):
  59.         for waitpid in waitpids:
  60.             os.kill(waitpid, signum)
  61.    
  62.     return signal_forwarder
  63.  
  64. # wait for appropriate processes and return the exit status of the last one to die
  65. # if there is no list of processes supplied or None then we simply wait for all child processes
  66. def wait_processes ( waitpids = None ):
  67.     exitstat  = 111
  68.     for pid in waitpids:
  69.         try:
  70.             _, exitstat = os.waitpid(pid, 0)
  71.        
  72.         # the exception handling is because we need to try again if os.waitpid is interrupted
  73.         # this happens when we forward the signal
  74.         except OSError as e:
  75.             if e.errno != errno.EINTR: raise
  76.             else:
  77.                 exitstat = wait_processes ( waitpids )
  78.    
  79.     return exitstat
  80.  
  81. def main ():
  82.     setproctitle.setproctitle('angelize')
  83.     global waidpids
  84.    
  85.     pidfile   = None
  86.     shcode    = None
  87.     waitpids  = None
  88.    
  89.     args = sys.argv[1:]
  90.  
  91.     if not args:
  92.         improper_args()
  93.    
  94.     first = args[0]
  95.    
  96.     if first == '--pidfile':
  97.         if not args[1:]:
  98.             improper_args()
  99.        
  100.         pidfile = args[1]
  101.         commandline = args[2:]
  102.    
  103.     elif first == '--shcode':
  104.         if not args[1:]:
  105.             improper_args()
  106.        
  107.         shcode = args[1]
  108.         commandline  = args[2:]
  109.    
  110.     elif first == '--help':
  111.         print_help()
  112.         exit()
  113.    
  114.     elif first[0:1] == '-':
  115.         improper_args()
  116.    
  117.     else:
  118.         commandline = args
  119.    
  120.    
  121.     if not commandline:
  122.         improper_args()
  123.    
  124.     # very important, we register with Linux to become the subreaper of the the descendant process tree
  125.     # anything double forking from this point will reparent to this process, not pid1  
  126.     prctl.set_child_subreaper(True)
  127.    
  128.     # we call the actual command that is expected to daemonize itself
  129.     # if it exits with an error we assume the damonization some-how failed and exit with the same error
  130.     errcode = subprocess.call(commandline)
  131.     if errcode != 0: exit(errcode)
  132.    
  133.     if pidfile:
  134.         with open(pidfile) as fp:
  135.             waitpids = [ int(line.strip()) for line in fp ]
  136.  
  137.     elif shcode:
  138.         waitpids = [ int(line.strip()) for line in subprocess.check_output(['sh', '-c', shcode]).split('\n') if line.strip() ]
  139.    
  140.     else:
  141.         import psutil
  142.         waitpids = [ child.pid for child in psutil.Process().children() ]
  143.    
  144.     signal_forwarder = make_signal_forwarder(waitpids)
  145.    
  146.     for signum in forwarding_signals:
  147.         signal.signal(signum, signal_forwarder)
  148.  
  149.     exit(wait_processes(waitpids))
  150.  
  151. if __name__ == '__main__': main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement