Package park :: Package util :: Module daemon

Source Code for Module park.util.daemon

  1  #!/usr/bin/env python 
  2  # -*- coding: iso-8859-15 -*- 
  3  ''' 
  4  Disk And Execution MONitor (Daemon) 
  5   
  6  daemonize() 
  7    Detach the current process from the calling terminal so it can 
  8    run as a service. 
  9  startstop() 
 10    Check the first argument to the program. 
 11   
 12       start:    start the daemon 
 13       stop:    stop the daemon 
 14       restart: stop the daemon if running, then start it again 
 15       status:  display running status 
 16       run:     run but not in daemon mode 
 17   
 18  Configurable daemon behaviors: 
 19   
 20     1.) The current working directory set to the "/" directory. 
 21     2.) The current file creation mode mask set to 0. 
 22     3.) Close all open files (1024). 
 23     4.) Redirect standard I/O streams to "/dev/null". 
 24   
 25  Almost none of this is necessary (or advisable) if your daemon 
 26  is being started by inetd. In that case, stdin, stdout and stderr are 
 27  all set up for you to refer to the network connection, and the fork()s 
 28  and session manipulation should not be done (to avoid confusing inetd). 
 29  Only the chdir() and umask() steps remain as useful. 
 30   
 31  References:: 
 32   
 33  UNIX Programming FAQ 1.7 How do I get my program to act like a daemon? 
 34      http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16 
 35  Advanced Programming in the Unix Environment 
 36      W. Richard Stevens, 1992, Addison-Wesley, ISBN 0-201-56317-7. 
 37   
 38  History: 
 39      2001/07/10 by JÃŒrgen Hermann 
 40      2002/08/28 by Noah Spurrier 
 41      2003/02/24 by Clark Evans 
 42      2005/10/03 by Chad J. Schroeder 
 43      2008/11/05 by Paul Kienzle 
 44   
 45  Based on http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66012 
 46  ''' 
 47   
 48  # TODO: generalize for windows and os x 
 49  # 
 50  # Need to extend daemon to handle Windows and OS X. 
 51  # 
 52  # startstop should be renamed control 
 53  # control should take additional commands for install/remove, presumably 
 54  # by querying the filename of the caller, assuming we can track that. 
 55  # 
 56  # Should support control from xinetd as well as init.d.  The run command 
 57  # may do so already. 
 58  # 
 59  # Mac OS 10.4 and above uses launchd.  The service will need an info.plist 
 60  # to describe the interactions and the script to run. 
 61  # 
 62  # Windows services can be created and manipulated from python, as shown in:: 
 63  #    http://essiene.blogspot.com/2005/04/python-windows-services.html 
 64  #    http://code.activestate.com/recipes/59872/ 
 65  #    http://code.activestate.com/recipes/551780/ 
 66  # 
 67  # Need to isolate other system dependencies such as /var/log and /var/run 
 68  # so the caller doesn't care where the system normally puts the services. 
 69  # Should be able to run the service as a user, so first try putting files 
 70  # in the traditional place, and if that doesn't work, put them in ~/.service 
 71   
 72  import sys, os, time, errno 
 73  from signal import SIGTERM 
 74   
 75  UMASK = 0     # Default to completely private files 
 76  WORKDIR = "/" # Default to running in '/' 
 77  MAXFD = 1024  # Maximum number of file descriptors 
 78   
 79  if hasattr(os, "devnull"): 
 80      REDIRECT_TO = os.devnull 
 81  else: 
 82      REDIRECT_TO = "/dev/null" 
 83   
 84  # sys.exit() or os._exit()? 
 85  # _exit is like exit(), but it doesn't call any functions registered 
 86  # with atexit (and on_exit) or any registered signal handlers.  It also 
 87  # closes any open file descriptors.  Using exit() may cause all stdio 
 88  # streams to be flushed twice and any temporary files may be unexpectedly 
 89  # removed.  It's therefore recommended that child branches of a fork() 
 90  # and the parent branch(es) of a daemon use _exit(). 
 91  exit = os._exit 
 92   
93 -def _close_all():
94 """ 95 Close all open file descriptors. This prevents the child from keeping 96 open any file descriptors inherited from the parent. 97 """ 98 99 # There is a variety of methods to accomplish this task. 100 # 101 # Try the system configuration variable, SC_OPEN_MAX, to obtain the maximum 102 # number of open file descriptors to close. If it doesn't exists, use 103 # the default value (configurable). 104 # 105 # try: 106 # maxfd = os.sysconf("SC_OPEN_MAX") 107 # except (AttributeError, ValueError): 108 # maxfd = MAXFD 109 # 110 # OR 111 # 112 # if (os.sysconf_names.has_key("SC_OPEN_MAX")): 113 # maxfd = os.sysconf("SC_OPEN_MAX") 114 # else: 115 # maxfd = MAXFD 116 # 117 # OR 118 # 119 # Use the getrlimit method to retrieve the maximum file descriptor number 120 # that can be opened by this process. If there is not limit on the 121 # resource, use the default value. 122 123 import resource # Resource usage information. 124 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1] 125 if maxfd == resource.RLIM_INFINITY: 126 maxfd = MAXFD 127 128 # Iterate through and close all the file descriptors 129 for fd in range(0, maxfd): 130 try: os.close(fd) 131 except OSError: pass # (ignored) fd wasn't open to begin with
132 133
134 -def daemonize(stdout=REDIRECT_TO, stderr=None, stdin=REDIRECT_TO, 135 pidfile=None, startmsg = 'started with pid %s' ):
136 """ 137 This forks the current process into a daemon. 138 139 The stdin, stdout, and stderr arguments are file names that will 140 be opened and be used to replace the standard file descriptors 141 in sys.stdin, sys.stdout, and sys.stderr. 142 These arguments are optional and default to /dev/null. 143 144 Note that stderr is opened unbuffered, so if it shares a file with 145 stdout then interleaved output may not appear in the order you expect. 146 """ 147 148 # Fork a child process so the parent can exit. This returns control to 149 # the command-line or shell. It also guarantees that the child will not 150 # be a process group leader, since the child receives a new process ID 151 # and inherits the parent's process group ID. This step is required 152 # to insure that the next call to os.setsid is successful. 153 try: 154 pid = os.fork() 155 if pid > 0: exit(0) # Exit first parent. 156 except OSError, e: 157 raise Exception, "[%d] %s" % (e.errno, e.strerror) 158 159 # Decouple from parent environment. 160 os.chdir(WORKDIR) # Make sure we are not holding a directory open 161 os.umask(UMASK) # Clear the inherited mask 162 os.setsid() # Make this the session leader 163 164 # Fork a second child and exit immediately to prevent zombies. This 165 # causes the second child process to be orphaned, making the init 166 # process responsible for its cleanup. And, since the first child is 167 # a session leader without a controlling terminal, it's possible for 168 # it to acquire one by opening a terminal in the future (System V- 169 # based systems). This second fork guarantees that the child is no 170 # longer a session leader, preventing the daemon from ever acquiring 171 # a controlling terminal. 172 try: 173 pid = os.fork() 174 if pid > 0: exit(0) # Exit second parent. 175 except OSError, e: 176 raise Exception, "[%d] %s" % (e.errno, e.strerror) 177 178 # Save pid 179 pid = str(os.getpid()) 180 if pidfile: # Make sure pidfile is written cleanly before close_all 181 fd = open(pidfile,'w+') 182 fd.write("%s\n" % pid) 183 fd.flush() 184 fd.close() 185 186 # Print start message and flush output 187 if startmsg: sys.stderr.write("\n%s\n" % startmsg % pid) 188 sys.stdout.flush() 189 sys.stderr.flush() 190 191 # Close all but the standard file descriptors. 192 #_close_all() # hmmm...interferes with file output selection 193 194 # Redirect standard file descriptors. 195 if not stderr: stderr = stdout 196 fin = file(stdin, "r") 197 fout = file(stdout, "a+") 198 ferr = file(stderr, "a+") 199 os.dup2(fin.fileno(), 0) 200 os.dup2(fout.fileno(), 1) 201 os.dup2(ferr.fileno(), 2)
202
203 -def readpid(pidfile):
204 try: 205 pf = file(pidfile,'r') 206 pid = int(pf.read().strip()) 207 pf.close() 208 except IOError: 209 pid = None 210 return pid
211
212 -def process_is_running(pid):
213 """ 214 Check if the given process is running. 215 216 Note that this just checks that the pid is in use; since process 217 ids can be reused, this isn't a reliable test. 218 """ 219 # Signal the process with 0. If successful or if fails because 220 # you don't have permission, then the process is alive otherwise 221 # the process is dead. 222 try: 223 os.kill(pid, 0) 224 return 1 225 except OSError, err: 226 return err.errno == errno.EPERM
227
228 -def startstop(stdout=REDIRECT_TO, stderr=None, stdin=REDIRECT_TO, 229 pidfile='pid.txt', startmsg = 'started with pid %s' ):
230 """ 231 Process start/stop/restart/status/run commands. 232 233 Start/stop/restart allow the process to be used as an init.d service. 234 Run runs the process without daemonizing, e.g., from inittab. 235 """ 236 if len(sys.argv) > 1: 237 action = sys.argv[1] 238 pid = readpid(pidfile) 239 if 'stop' == action or ('restart' == action and pid): 240 if not pid: 241 msg = "Could not stop, pid file '%s' missing."%pidfile 242 sys.stderr.write('%s\n'%msg) 243 sys.exit(1) 244 245 try: 246 while 1: 247 os.kill(pid,SIGTERM) 248 time.sleep(1) 249 except OSError, err: 250 err = str(err) 251 if err.find("No such process") > 0: 252 os.remove(pidfile) 253 if 'stop' == action: 254 sys.exit(0) 255 # Fall through to the start action 256 pid = None 257 else: 258 raise # Reraise if it is not a "No such process" error 259 260 if action in ['start', 'restart', 'run']: 261 if pid: 262 msg = "Start aborted since pid file '%s' exists."%pidfile 263 sys.stderr.write('%s\n'%msg) 264 sys.exit(1) 265 # Only return when we are ready to run the main program 266 # Otherwise use sys.exit to end. 267 if action != 'run': 268 daemonize(stdout=stdout,stderr=stderr,stdin=stdin, 269 pidfile=pidfile,startmsg=startmsg) 270 return 271 272 if action == 'status': 273 if not pid: 274 status='stopped' 275 elif not process_is_running(pid): 276 status="failed, but pid file '%s' still exists."%pidfile 277 else: 278 status='running with pid %d'%pid 279 sys.stderr.write('Status: %s\n'%status) 280 sys.exit(0) 281 282 print "usage: %s start|stop|restart|status|run" % sys.argv[0] 283 sys.exit(2)
284
285 -def test():
286 """ 287 This is an example main function run by the daemon. 288 This prints a count and timestamp once per second. 289 """ 290 sys.stdout.write ('Message to stdout...\n') 291 sys.stderr.write ('Message to stderr...\n') 292 c = 0 293 while 1: 294 sys.stdout.write ('%d: %s\n' % (c, time.ctime(time.time())) ) 295 sys.stdout.flush() 296 c = c + 1 297 time.sleep(1)
298 299 if __name__ == "__main__": 300 startstop(stdout='/tmp/daemon.log', pidfile='/tmp/daemon.pid') 301 test() 302