Module daemon
[hide private]
[frames] | no frames]

Source Code for Module daemon

  1  #!/usr/bin/env python 
  2  # 
  3  # $Id: daemon.py 7237 2008-02-15 22:24:48Z bmc $ 
  4   
  5  # NOTE: Documentation is intended to be processed by epydoc and contains 
  6  # epydoc markup. 
  7   
  8  """ 
  9  Overview 
 10  ======== 
 11   
 12  Convert the calling process to a daemon. To make the current Python process 
 13  into a daemon process, you need two lines of code:: 
 14   
 15      import daemon 
 16      daemon.daemonize() 
 17   
 18  If C{daemonize()} fails for any reason, it throws an exception. It also 
 19  logs debug messages, using the standard Python 'logging' package, to 
 20  channel 'daemon'. 
 21   
 22  Adapted from: 
 23   
 24    - U{http://www.clapper.org/software/daemonize/} 
 25   
 26  See Also 
 27  ======== 
 28   
 29  Stevens, W. Richard. I{Unix Network Programming} (Addison-Wesley, 1990). 
 30  """ 
 31   
 32  __version__   = "1.0" 
 33  __author__    = "Brian Clapper, bmc@clapper.org" 
 34  __url__       = "http://www.clapper.org/software/python/daemon/" 
 35  __copyright__ = "(c) 2008 Brian M. Clapper" 
 36  __license__   = "BSD-style license" 
 37   
 38  __all__ = ['daemonize', 'DaemonException'] 
 39   
 40  # --------------------------------------------------------------------------- 
 41  # Imports 
 42  # --------------------------------------------------------------------------- 
 43   
 44  import logging 
 45  import os 
 46  import sys 
 47   
 48  # --------------------------------------------------------------------------- 
 49  # Constants 
 50  # --------------------------------------------------------------------------- 
 51   
 52  # Default daemon parameters. 
 53  # File mode creation mask of the daemon. 
 54  UMASK = 0 
 55   
 56  # Default working directory for the daemon. 
 57  WORKDIR = "/" 
 58   
 59  # Default maximum for the number of available file descriptors. 
 60  MAXFD = 1024 
 61   
 62  # The standard I/O file descriptors are redirected to /dev/null by default. 
 63  if (hasattr(os, "devnull")): 
 64     NULL_DEVICE = os.devnull 
 65  else: 
 66     NULL_DEVICE = "/dev/null" 
 67   
 68   
 69  # --------------------------------------------------------------------------- 
 70  # Logging 
 71  # --------------------------------------------------------------------------- 
 72   
 73  logger = logging.getLogger('daemonize') 
 74   
 75  # --------------------------------------------------------------------------- 
 76  # Public classes 
 77  # --------------------------------------------------------------------------- 
 78   
79 -class DaemonException(Exception):
80 """ 81 Thrown by C{daemonize()} when an error occurs while attempting to create 82 a daemon. A C{DaemonException} object always contains a single string 83 value that contains an error message describing the problem. 84 """
85 - def __init__(self, errorMessage):
86 """ 87 Create a new C{DaemonException}. 88 89 @type errorMessage: string 90 @param errorMessage: the error message 91 """ 92 self.errorMessage = errorMessage
93
94 - def __str__(self):
95 """ 96 Get a string version of the exception. 97 98 @return: a string representing the exception 99 """ 100 return self.errorMessage
101 102 # --------------------------------------------------------------------------- 103 # Public functions 104 # --------------------------------------------------------------------------- 105
106 -def daemonize(noClose=False):
107 """ 108 Convert the calling process into a daemon. 109 110 @type noClose: boolean 111 @param noClose: If True, don't close the file descriptors. Useful 112 if the calling process has already redirected file 113 descriptors to an output file. WARNING: Only set this 114 parameter to True if you're SURE there are no open file 115 descriptors to the calling terminal. Otherwise, you'll 116 risk having the daemon re-acquire a control terminal, 117 which can cause it to be killed if someone logs off that 118 terminal. 119 120 @raise DaemonException: Error during daemonizing 121 """ 122 global logger 123 124 if os.name != 'posix': 125 logger.warn('Daemon is only supported on Posix-compliant systems.') 126 return 127 128 try: 129 # Fork once to go into the background. 130 131 logger.debug('Forking first child.') 132 pid = _fork() 133 if pid != 0: 134 # Parent. Exit using os._exit(), which doesn't fire any atexit 135 # functions. 136 os._exit(0) 137 138 # First child. Create a new session. os.setsid() creates the session 139 # and makes this (child) process the process group leader. The process 140 # is guaranteed not to have a control terminal. 141 logger.debug('Creating new session') 142 os.setsid() 143 144 # Fork a second child to ensure that the daemon never reacquires 145 # a control terminal. 146 logger.debug('Forking second child.') 147 pid = _fork() 148 if pid != 0: 149 # Original child. Exit. 150 os._exit(0) 151 152 # This is the second child. Set the umask. 153 logger.debug('Setting umask') 154 os.umask(UMASK) 155 156 # Go to a neutral corner (i.e., the primary file system, so 157 # the daemon doesn't prevent some other file system from being 158 # unmounted). 159 logger.debug('Changing working directory to "%s"' % WORKDIR) 160 os.chdir(WORKDIR) 161 162 # Unless noClose was specified, close all file descriptors. 163 if not noClose: 164 logger.debug('Redirecting file descriptors') 165 _redirectFileDescriptors() 166 167 except DaemonException: 168 raise 169 170 except OSError, e: 171 raise DaemonException('Error during daemonizing: %s [%d]' %\ 172 (e.strerror, e.errno))
173 174 175 # --------------------------------------------------------------------------- 176 # Private functions 177 # --------------------------------------------------------------------------- 178
179 -def _fork():
180 try: 181 return os.fork() 182 except OSError, e: 183 raise DaemonException, 'Cannot fork: %s [%d]' % (e.strerror, e.errno)
184
185 -def _redirectFileDescriptors():
186 import resource # POSIX resource information 187 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1] 188 if maxfd == resource.RLIM_INFINITY: 189 maxfd = MAXFD 190 191 # Close all file descriptors. 192 193 for fd in range(0, maxfd): 194 try: 195 os.close(fd) 196 except OSError: 197 # File descriptor wasn't open. Ignore. 198 pass 199 200 # Redirect standard input, output and error to something safe. 201 # os.open() is guaranteed to return the lowest available file 202 # descriptor (0, or standard input). Then, we can dup that descriptor 203 # for standard output and standard error. 204 205 os.open(NULL_DEVICE, os.O_RDWR) 206 os.dup2(0, 1) 207 os.dup2(0, 2)
208 209 # --------------------------------------------------------------------------- 210 # Main program (for testing) 211 # --------------------------------------------------------------------------- 212 213 if __name__ == '__main__': 214 215 logger = logging.getLogger('daemon') 216 hdlr = logging.StreamHandler(sys.stdout) 217 formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s', '%T') 218 hdlr.setFormatter(formatter) 219 logger.addHandler(hdlr) 220 logger.setLevel(logging.DEBUG) 221 222 logger.debug('Before daemonizing, PID=%d' % os.getpid()) 223 daemonize(noClose=True) 224 logger.debug('After daemonizing, PID=%d' % os.getpid()) 225 logger.debug('Daemon is sleeping for 10 seconds') 226 227 import time 228 time.sleep(10) 229 230 logger.debug('Daemon exiting') 231 sys.exit(0) 232