text: # ---------------------------------------------------------------------------
  #
  # Copyright (c) 2005, Greg Stein
  #
  #   Licensed under the Apache License, Version 2.0 (the "License");
  #   you may not use this file except in compliance with the License.
  #   You may obtain a copy of the License at
  #
  #       http://www.apache.org/licenses/LICENSE-2.0
  #
  #   Unless required by applicable law or agreed to in writing, software
  #   distributed under the License is distributed on an "AS IS" BASIS,
  #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  #   See the License for the specific language governing permissions and
  #   limitations under the License.
  #
  # ---------------------------------------------------------------------------
  #
  # This software lives at:
  #    http://gstein.googlecode.com/svn/trunk/python/daemonize.py
  #
  
  import os
  import signal
  import sys
  import time
  import stat
  import multiprocessing  # requires Python 2.6
  
  
  # possible return values from Daemon.daemonize()
  DAEMON_RUNNING = 'The daemon is running'
  DAEMON_NOT_RUNNING = 'The daemon is not running'
  DAEMON_COMPLETE = 'The daemon has completed its operations'
  DAEMON_STARTED = 'The daemon has been started'
  
  
  class Daemon(object):
  
    def __init__(self, logfile, pidfile):
      self.logfile = logfile
      self.pidfile = pidfile
  
    def foreground(self):
      "Run in the foreground."
      ### we should probably create a pidfile. other systems may try to detect
      ### the pidfile to see if this "daemon" is running.
      self.setup()
      self.run()
      ### remove the pidfile
  
    def daemonize_exit(self):
      try:
        result = self.daemonize()
      except (ChildFailed, DaemonFailed) as e:
        # duplicate the exit code
        sys.exit(e.code)
      except (ChildTerminatedAbnormally, ChildForkFailed,
              DaemonTerminatedAbnormally, DaemonForkFailed), e:
        sys.stderr.write('ERROR: %s\n' % e)
        sys.exit(1)
      except ChildResumedIncorrectly:
        sys.stderr.write('ERROR: continued after receiving unknown signal.\n')
        sys.exit(1)
  
      if result == DAEMON_STARTED or result == DAEMON_COMPLETE:
        sys.exit(0)
      elif result == DAEMON_NOT_RUNNING:
        sys.stderr.write('ERROR: the daemon exited with a success code '
                         'without signalling its startup.\n')
        sys.exit(1)
  
      # in original process. daemon is up and running. we're done.
  
    def daemonize(self):
      ### review error situations. map to backwards compat. ??
      ### be mindful of daemonize_exit().
      ### we should try and raise ChildFailed / ChildTerminatedAbnormally.
      ### ref: older revisions. OR: remove exceptions.
  
      child_is_ready = multiprocessing.Event()
      child_completed = multiprocessing.Event()
  
      p = multiprocessing.Process(target=self._first_child,
                                  args=(child_is_ready, child_completed))
      p.start()
  
      # Wait for the child to finish setting things up (in case we need
      # to communicate with it). It will only exit when ready.
      ### use a timeout here! (parameterized, of course)
      p.join()
  
      ### need to propagate errors, to adjust the return codes
      if child_completed.is_set():
        ### what was the exit status?
        return DAEMON_COMPLETE
      if child_is_ready.is_set():
        return DAEMON_RUNNING
  
      ### how did we get here?! the immediate child should not exit without
      ### signalling ready/complete. some kind of error.
      return DAEMON_STARTED
  
    def _first_child(self, child_is_ready, child_completed):
      # we're in the child.
  
      ### NOTE: the original design was a bit bunk. Exceptions raised from
      ### this point are within the child processes. We need to signal the
      ### errors to the parent in other ways.
  
      # decouple from the parent process
      os.chdir('/')
      os.umask(0)
      os.setsid()
  
      # remember this pid so the second child can signal it.
      thispid = os.getpid()
  
      # if the daemon process exits before signalling readiness, then we
      # need to see the problem. trap SIGCHLD with a SignalCatcher.
      daemon_exit = SignalCatcher(signal.SIGCHLD)
  
      # perform the second fork
      try:
        pid = os.fork()
      except OSError as e:
        ### this won't make it to the parent process
        raise DaemonForkFailed(e.errno, e.strerror)
  
      if pid > 0:
        # in the parent.
  
  
        # Wait for the child to be ready for operation.
        while True:
          # The readiness event will invariably be signalled early/first.
          # If it *doesn't* get signalled because the child has prematurely
          # exited, then we will pause 10ms before noticing the exit. The
          # pause is acceptable since that is aberrant/unexpected behavior.
          ### is there a way to break this wait() on a signal such as SIGCHLD?
          ### parameterize this wait, in case the app knows children may
          ### fail quickly?
          if child_is_ready.wait(timeout=0.010):
            # The child signalled readiness. Yay!
            break
          if daemon_exit.signalled:
            # Whoops. The child exited without signalling :-(
            break
          # Python 2.6 compat: .wait() may exit when set, but return None
          if child_is_ready.is_set():
            break
          # A simple timeout. The child is taking a while to prepare. Go
          # back and wait for readiness.
  
        if daemon_exit.signalled:
          # Tell the parent that the child has exited.
          ### we need to communicate the exit status, if possible.
          child_completed.set()
  
          # reap the daemon process, getting its exit code. bubble it up.
          cpid, status = os.waitpid(pid, 0)
          assert pid == cpid
          if os.WIFEXITED(status):
            code = os.WEXITSTATUS(status)
            if code:
              ### this won't make it to the parent process
              raise DaemonFailed(code)
            ### this return value is ignored
            return DAEMON_NOT_RUNNING
  
          # the daemon did not exit cleanly.
          ### this won't make it to the parent process
          raise DaemonTerminatedAbnormally(status)
  
        # child_is_ready got asserted. the daemon is up and running, so
        # save the pid and return success.
        if self.pidfile:
          # Be wary of symlink attacks
          try:
            os.remove(self.pidfile)
          except OSError:
            pass
          fd = os.open(self.pidfile, os.O_WRONLY | os.O_CREAT | os.O_EXCL,
                       stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)
          os.write(fd, '%d\n' % pid)
          os.close(fd)
  
        ### this return value is ignored
        return DAEMON_STARTED
  
        ### old code. what to do with this? throw ChildResumedIncorrectly
        ### or just toss this and the exception.
        # some other signal popped us out of the pause. the daemon might not
        # be running.
        ### this won't make it to the parent process
        raise ChildResumedIncorrectly()
  
      # we're a daemon now. get rid of the final remnants of the parent:
      # restore the signal handlers and switch std* to the proper files.
      signal.signal(signal.SIGUSR1, signal.SIG_DFL)
      signal.signal(signal.SIGCHLD, signal.SIG_DFL)
      sys.stdout.flush()
      sys.stderr.flush()
      si = open('/dev/null', 'r')
      so = open(self.logfile, 'a+')
      se = open(self.logfile, 'a+', 0)  # unbuffered
      os.dup2(si.fileno(), sys.stdin.fileno())
      os.dup2(so.fileno(), sys.stdout.fileno())
      os.dup2(se.fileno(), sys.stderr.fileno())
      # note: we could not inline the open() calls. after the fileno() completed,
      # the file would be closed, making the fileno invalid. gotta hold them
      # open until now:
      si.close()
      so.close()
      se.close()
  
      ### TEST: don't release the parent immediately. the whole parent stack
      ###       should pause along with this sleep.
      #time.sleep(10)
  
      # everything is set up. call the initialization function.
      self.setup()
  
      ### TEST: exit before signalling.
      #sys.exit(0)
      #sys.exit(1)
  
      # the child is now ready for parent/anyone to communicate with it.
      child_is_ready.set()
  
      # start the daemon now.
      self.run()
  
      # The daemon is shutting down, so toss the pidfile.
      if self.pidfile:
        try:
          os.remove(self.pidfile)
        except OSError:
          pass
  
      ### this return value is ignored
      return DAEMON_COMPLETE
  
    def setup(self):
      raise NotImplementedError
  
    def run(self):
      raise NotImplementedError
  
  
  class _Detacher(Daemon):
    def __init__(self, target, logfile='/dev/null', pidfile=None,
                 args=(), kwargs={}):
      Daemon.__init__(self, logfile, pidfile)
      self.target = target
      self.args = args
      self.kwargs = kwargs
  
    def setup(self):
      pass
  
    def run(self):
      self.target(*self.args, **self.kwargs)
  
  
  def run_detached(target, *args, **kwargs):
    """Simple function to run TARGET as a detached daemon.
  
    The additional arguments/keywords will be passed along. This function
    does not return -- sys.exit() will be called as appropriate.
  
    (capture SystemExit if logging/reporting is necessary)
    ### if needed, a variant of this func could be written to not exit
    """
    d = _Detacher(target, args=args, kwargs=kwargs)
    d.daemonize_exit()
  
  
  class SignalCatcher(object):
    def __init__(self, signum):
      self.signalled = False
      signal.signal(signum, self.sig_handler)
  
    def sig_handler(self, signum, frame):
      self.signalled = True
  
  
  class ChildTerminatedAbnormally(Exception):
    "The child process terminated abnormally."
    def __init__(self, status):
      Exception.__init__(self, status)
      self.status = status
    def __str__(self):
      return 'child terminated abnormally (0x%04x)' % self.status
  
  class ChildFailed(Exception):
    "The child process exited with a failure code."
    def __init__(self, code):
      Exception.__init__(self, code)
      self.code = code
    def __str__(self):
      return 'child failed with exit code %d' % self.code
  
  class ChildForkFailed(Exception):
    "The child process could not be forked."
    def __init__(self, errno, strerror):
      Exception.__init__(self, errno, strerror)
      self.errno = errno
      self.strerror = strerror
    def __str__(self):
      return 'child fork failed with error %d (%s)' % self.args
  
  class ChildResumedIncorrectly(Exception):
    "The child resumed its operation incorrectly."
  
  class DaemonTerminatedAbnormally(Exception):
    "The daemon process terminated abnormally."
    def __init__(self, status):
      Exception.__init__(self, status)
      self.status = status
    def __str__(self):
      return 'daemon terminated abnormally (0x%04x)' % self.status
  
  class DaemonFailed(Exception):
    "The daemon process exited with a failure code."
    def __init__(self, code):
      Exception.__init__(self, code)
      self.code = code
    def __str__(self):
      return 'daemon failed with exit code %d' % self.code
  
  class DaemonForkFailed(Exception):
    "The daemon process could not be forked."
    def __init__(self, errno, strerror):
      Exception.__init__(self, errno, strerror)
      self.errno = errno
      self.strerror = strerror
    def __str__(self):
      return 'daemon fork failed with error %d (%s)' % self.args
  