Subversion Repositories HomeAutomation

Rev

Blame | Last modification | View Log | SVN | RSS feed

  1. """Disk And Execution MONitor (Daemon)
  2.  
  3. Configurable daemon behaviors:
  4.  
  5.   1.) The current working directory set to the "/" directory.
  6.   2.) The current file creation mode mask set to 0.
  7.   3.) Close all open files (1024).
  8.   4.) Redirect standard I/O streams to "/dev/null".
  9.  
  10. A failed call to fork() now raises an exception.
  11.  
  12. References:
  13.   1) Advanced Programming in the Unix Environment: W. Richard Stevens
  14.   2) Unix Programming Frequently Asked Questions:
  15.         http://www.erlenstar.demon.co.uk/unix/faq_toc.html
  16. """
  17.  
  18. __author__ = "Chad J. Schroeder"
  19. __copyright__ = "Copyright (C) 2005 Chad J. Schroeder"
  20.  
  21. __revision__ = "$Id$"
  22. __version__ = "0.2"
  23.  
  24. # Standard Python modules.
  25. import os               # Miscellaneous OS interfaces.
  26. import sys              # System-specific parameters and functions.
  27. import Settings
  28.  
  29. # Default daemon parameters.
  30. # File mode creation mask of the daemon.
  31. UMASK = 0
  32.  
  33. # Default working directory for the daemon.
  34. WORKDIR = "/"
  35.  
  36. # Default maximum for the number of available file descriptors.
  37. MAXFD = 1024
  38.  
  39. # The standard I/O file descriptors are redirected to /dev/null by default.
  40. if (hasattr(os, "devnull")):
  41.    REDIRECT_TO = os.devnull
  42. else:
  43.    REDIRECT_TO = "/dev/null"
  44.  
  45. def createDaemon():
  46.    """Detach a process from the controlling terminal and run it in the
  47.   background as a daemon.
  48.   """
  49.  
  50.    try:
  51.       # Fork a child process so the parent can exit.  This returns control to
  52.       # the command-line or shell.  It also guarantees that the child will not
  53.       # be a process group leader, since the child receives a new process ID
  54.       # and inherits the parent's process group ID.  This step is required
  55.       # to insure that the next call to os.setsid is successful.
  56.       pid = os.fork()
  57.    except (KeyboardInterrupt, SystemExit):
  58.       raise
  59.    except OSError, e:
  60.       raise Exception, "%s [%d]" % (e.strerror, e.errno)
  61.  
  62.    if (pid == 0):   # The first child.
  63.       # To become the session leader of this new session and the process group
  64.       # leader of the new process group, we call os.setsid().  The process is
  65.       # also guaranteed not to have a controlling terminal.
  66.       os.setsid()
  67.  
  68.       # Is ignoring SIGHUP necessary?
  69.       #
  70.       # It's often suggested that the SIGHUP signal should be ignored before
  71.       # the second fork to avoid premature termination of the process.  The
  72.       # reason is that when the first child terminates, all processes, e.g.
  73.       # the second child, in the orphaned group will be sent a SIGHUP.
  74.       #
  75.       # "However, as part of the session management system, there are exactly
  76.       # two cases where SIGHUP is sent on the death of a process:
  77.       #
  78.       #   1) When the process that dies is the session leader of a session that
  79.       #      is attached to a terminal device, SIGHUP is sent to all processes
  80.       #      in the foreground process group of that terminal device.
  81.       #   2) When the death of a process causes a process group to become
  82.       #      orphaned, and one or more processes in the orphaned group are
  83.       #      stopped, then SIGHUP and SIGCONT are sent to all members of the
  84.       #      orphaned group." [2]
  85.       #
  86.       # The first case can be ignored since the child is guaranteed not to have
  87.       # a controlling terminal.  The second case isn't so easy to dismiss.
  88.       # The process group is orphaned when the first child terminates and
  89.       # POSIX.1 requires that every STOPPED process in an orphaned process
  90.       # group be sent a SIGHUP signal followed by a SIGCONT signal.  Since the
  91.       # second child is not STOPPED though, we can safely forego ignoring the
  92.       # SIGHUP signal.  In any case, there are no ill-effects if it is ignored.
  93.       #
  94.       # import signal           # Set handlers for asynchronous events.
  95.       # signal.signal(signal.SIGHUP, signal.SIG_IGN)
  96.  
  97.       try:
  98.          # Fork a second child and exit immediately to prevent zombies.  This
  99.          # causes the second child process to be orphaned, making the init
  100.          # process responsible for its cleanup.  And, since the first child is
  101.          # a session leader without a controlling terminal, it's possible for
  102.          # it to acquire one by opening a terminal in the future (System V-
  103.          # based systems).  This second fork guarantees that the child is no
  104.          # longer a session leader, preventing the daemon from ever acquiring
  105.          # a controlling terminal.
  106.          pid = os.fork()    # Fork a second child.
  107.       except (KeyboardInterrupt, SystemExit):
  108.          raise
  109.       except OSError, e:
  110.          raise Exception, "%s [%d]" % (e.strerror, e.errno)
  111.  
  112.       if (pid == 0):    # The second child.
  113.          # Since the current working directory may be a mounted filesystem, we
  114.          # avoid the issue of not being able to unmount the filesystem at
  115.          # shutdown time by changing it to the root directory.
  116.          os.chdir(WORKDIR)
  117.          # We probably don't want the file mode creation mask inherited from
  118.          # the parent, so we give the child complete control over permissions.
  119.          os.umask(UMASK)
  120.       else:
  121.          # exit() or _exit()?  See below.
  122.          os._exit(0)    # Exit parent (the first child) of the second child.
  123.    else:
  124.       # exit() or _exit()?
  125.       # _exit is like exit(), but it doesn't call any functions registered
  126.       # with atexit (and on_exit) or any registered signal handlers.  It also
  127.       # closes any open file descriptors.  Using exit() may cause all stdio
  128.       # streams to be flushed twice and any temporary files may be unexpectedly
  129.       # removed.  It's therefore recommended that child branches of a fork()
  130.       # and the parent branch(es) of a daemon use _exit().
  131.       os._exit(0)   # Exit parent of the first child.
  132.  
  133.    # Close all open file descriptors.  This prevents the child from keeping
  134.    # open any file descriptors inherited from the parent.  There is a variety
  135.    # of methods to accomplish this task.  Three are listed below.
  136.    #
  137.    # Try the system configuration variable, SC_OPEN_MAX, to obtain the maximum
  138.    # number of open file descriptors to close.  If it doesn't exists, use
  139.    # the default value (configurable).
  140.    #
  141.    # try:
  142.    #    maxfd = os.sysconf("SC_OPEN_MAX")
  143.    # except (AttributeError, ValueError):
  144.    #    maxfd = MAXFD
  145.    #
  146.    # OR
  147.    #
  148.    # if (os.sysconf_names.has_key("SC_OPEN_MAX")):
  149.    #    maxfd = os.sysconf("SC_OPEN_MAX")
  150.    # else:
  151.    #    maxfd = MAXFD
  152.    #
  153.    # OR
  154.    #
  155.    # Use the getrlimit method to retrieve the maximum file descriptor number
  156.    # that can be opened by this process.  If there is not limit on the
  157.    # resource, use the default value.
  158.    #
  159.    import resource      # Resource usage information.
  160.    maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
  161.    if (maxfd == resource.RLIM_INFINITY):
  162.       maxfd = MAXFD
  163.  
  164.    # Iterate through and close all file descriptors.
  165.    for fd in range(0, maxfd):
  166.       try:
  167.          os.close(fd)
  168.       except (KeyboardInterrupt, SystemExit):
  169.          raise
  170.       except OSError:   # ERROR, fd wasn't open to begin with (ignored)
  171.          pass
  172.  
  173.    # Redirect the standard I/O file descriptors to the specified file.  Since
  174.    # the daemon has no controlling terminal, most daemons redirect stdin,
  175.    # stdout, and stderr to /dev/null.  This is done to prevent side-effects
  176.    # from reads and writes to the standard I/O file descriptors.
  177.  
  178.    os.open(Settings.Settings["LOGPATH"], os.O_CREAT|os.O_APPEND|os.O_RDONLY) # stdin
  179.    os.open(Settings.Settings["LOGPATH"], os.O_CREAT|os.O_APPEND|os.O_RDWR)   # stdout
  180.    os.open(Settings.Settings["LOGPATH"], os.O_CREAT|os.O_APPEND|os.O_RDWR)   # stderr
  181.  
  182.    # This call to open is guaranteed to return the lowest file descriptor,
  183.    # which will be 0 (stdin), since it was closed above.
  184.    #os.open(REDIRECT_TO, os.O_RDWR) # standard input (0)
  185.  
  186.    # Duplicate standard input to standard output and standard error.
  187.    #os.dup2(0, 1)           # standard output (1)
  188.    #os.dup2(0, 2)           # standard error (2)
  189.  
  190.    return(0)
  191.