133e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org#!/usr/bin/env python
233e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org
333e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org# This code has been written by Sander Marechal and published at:
433e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org# http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/
533e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org# where the author has placed it in the public domain (see comment #6 at
633e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org# http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/#c6
733e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org# ).
833e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org# Some minor modifications have been made by the V8 authors. The work remains
933e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org# in the public domain.
1033e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org
1133e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.orgimport atexit
1233e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.orgimport os
1333e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.orgfrom signal import SIGTERM
1433e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.orgfrom signal import SIGINT
1533e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.orgimport sys
1633e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.orgimport time
1733e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org
1833e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org
1933e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.orgclass Daemon(object):
2033e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org  """
2133e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org  A generic daemon class.
2233e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org
2333e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org  Usage: subclass the Daemon class and override the run() method
2433e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org  """
2533e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org  def __init__(self, pidfile, stdin='/dev/null',
2633e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org               stdout='/dev/null', stderr='/dev/null'):
2733e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    self.stdin = stdin
2833e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    self.stdout = stdout
2933e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    self.stderr = stderr
3033e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    self.pidfile = pidfile
3133e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org
3233e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org  def daemonize(self):
3333e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    """
3433e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    do the UNIX double-fork magic, see Stevens' "Advanced
3533e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    Programming in the UNIX Environment" for details (ISBN 0201563177)
3633e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
3733e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    """
3833e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    try:
3933e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org      pid = os.fork()
4033e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org      if pid > 0:
4133e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org        # exit first parent
4233e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org        sys.exit(0)
4333e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    except OSError, e:
4433e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org      sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror))
4533e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org      sys.exit(1)
4633e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org
4733e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    # decouple from parent environment
4833e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    os.chdir("/")
4933e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    os.setsid()
5033e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    os.umask(0)
5133e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org
5233e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    # do second fork
5333e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    try:
5433e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org      pid = os.fork()
5533e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org      if pid > 0:
5633e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org        # exit from second parent
5733e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org        sys.exit(0)
5833e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    except OSError, e:
5933e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org      sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror))
6033e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org      sys.exit(1)
6133e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org
6233e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    # redirect standard file descriptors
6333e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    sys.stdout.flush()
6433e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    sys.stderr.flush()
6533e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    si = file(self.stdin, 'r')
6633e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    so = file(self.stdout, 'a+')
6733e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    se = file(self.stderr, 'a+', 0)
6833e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    # TODO: (debug) re-enable this!
6933e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    #os.dup2(si.fileno(), sys.stdin.fileno())
7033e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    #os.dup2(so.fileno(), sys.stdout.fileno())
7133e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    #os.dup2(se.fileno(), sys.stderr.fileno())
7233e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org
7333e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    # write pidfile
7433e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    atexit.register(self.delpid)
7533e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    pid = str(os.getpid())
7633e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    file(self.pidfile, 'w+').write("%s\n" % pid)
7733e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org
7833e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org  def delpid(self):
7933e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    os.remove(self.pidfile)
8033e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org
8133e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org  def start(self):
8233e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    """
8333e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    Start the daemon
8433e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    """
8533e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    # Check for a pidfile to see if the daemon already runs
8633e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    try:
8733e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org      pf = file(self.pidfile, 'r')
8833e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org      pid = int(pf.read().strip())
8933e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org      pf.close()
9033e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    except IOError:
9133e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org      pid = None
9233e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org
9333e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    if pid:
9433e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org      message = "pidfile %s already exist. Daemon already running?\n"
9533e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org      sys.stderr.write(message % self.pidfile)
9633e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org      sys.exit(1)
9733e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org
9833e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    # Start the daemon
9933e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    self.daemonize()
10033e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    self.run()
10133e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org
10233e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org  def stop(self):
10333e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    """
10433e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    Stop the daemon
10533e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    """
10633e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    # Get the pid from the pidfile
10733e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    try:
10833e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org      pf = file(self.pidfile, 'r')
10933e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org      pid = int(pf.read().strip())
11033e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org      pf.close()
11133e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    except IOError:
11233e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org      pid = None
11333e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org
11433e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    if not pid:
11533e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org      message = "pidfile %s does not exist. Daemon not running?\n"
11633e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org      sys.stderr.write(message % self.pidfile)
11733e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org      return # not an error in a restart
11833e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org
11933e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    # Try killing the daemon process
12033e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    try:
12133e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org      # Give the process a one-second chance to exit gracefully.
12233e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org      os.kill(pid, SIGINT)
12333e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org      time.sleep(1)
12433e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org      while 1:
12533e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org        os.kill(pid, SIGTERM)
12633e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org        time.sleep(0.1)
12733e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    except OSError, err:
12833e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org      err = str(err)
12933e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org      if err.find("No such process") > 0:
13033e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org        if os.path.exists(self.pidfile):
13133e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org          os.remove(self.pidfile)
13233e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org      else:
13333e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org        print str(err)
13433e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org        sys.exit(1)
13533e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org
13633e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org  def restart(self):
13733e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    """
13833e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    Restart the daemon
13933e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    """
14033e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    self.stop()
14133e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    self.start()
14233e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org
14333e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org  def run(self):
14433e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    """
14533e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    You should override this method when you subclass Daemon. It will be
14633e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    called after the process has been daemonized by start() or restart().
14733e09c8efd078308de3c77a88301566f65c07befverwaest@chromium.org    """
148