Difference between revisions of "Daemonize Python"

From Noah.org
Jump to navigationJump to search
m
 
(9 intermediate revisions by the same user not shown)
Line 1: Line 1:
 
[[Category:Engineering]]
 
[[Category:Engineering]]
 
[[Category:Python]]
 
[[Category:Python]]
== Python Daemonize source ==
+
[[Category:Free_Software]]
 +
 
 +
This short module shows the correct way to create a UNIX daemon process in Python. Note that there are modules for this available.
 +
 
 +
* check for existing PID lock file to determine if daemon is already running. This is a convention. This falls into the application level. This is not a UNIX daemon process requirement.
 +
* fork/exec
 +
* create independent session and group and detach from parent (setsid, between fork and exec)
 +
* detach from controlling tty
 +
* clear signal handlers.
 +
* create PID lock file to indicate new running daemon.
 +
* set uid and gid.
 +
* drop suid and sgid privileges.
 +
* close inherited file descriptors (including stdin, stdout, stderr).
 +
* open file descriptors for stdin, stdout, and stderr as necessary.
 +
* set umask (027).
 +
* set environment for running inside a chroot.
 +
* change working directory (relative to chroot if applicable).
 +
* register exit function (at least delete the daemon PID lock file).
 +
 
 +
Note that the following example does not handle all of the above requirements! The missing bits are left as an exercise.
 
<pre>
 
<pre>
 
#!/usr/bin/env python
 
#!/usr/bin/env python
import sys, os, signal
+
import sys, os  
  
 
'''This module is used to fork the current process into a daemon.
 
'''This module is used to fork the current process into a daemon.
  
Almost none of this is necessary (or advisable) if your daemon  
+
Almost none of this is necessary (or advisable) if your daemon is being started
is being started by inetd. In that case, stdin, stdout and stderr are  
+
by inetd. In that case, stdin, stdout and stderr are all set up for you to
all set up for you to refer to the network connection, and the fork()s  
+
refer to the network connection, and the fork()s and session manipulation
and session manipulation should not be done (to avoid confusing inetd).  
+
should not be done (to avoid confusing inetd). Only the chdir() and umask()
Only the chdir() and umask() steps remain as useful.  
+
steps remain useful.  
  
 
References:
 
References:
 +
 
     UNIX Programming FAQ
 
     UNIX Programming FAQ
 
         1.7 How do I get my program to act like a daemon?
 
         1.7 How do I get my program to act like a daemon?
         http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
+
         http://www.unixguide.net/unix/programming/1.7.shtml
 +
        http://www.faqs.org/faqs/unix-faq/programmer/faq/
 
    
 
    
 
     Advanced Programming in the Unix Environment
 
     Advanced Programming in the Unix Environment
 
         W. Richard Stevens, 1992, Addison-Wesley, ISBN 0-201-56317-7.
 
         W. Richard Stevens, 1992, Addison-Wesley, ISBN 0-201-56317-7.
 +
 +
$Id: daemonize.py 251 2008-05-05 21:51:11Z noah $
 
'''
 
'''
  
 
def daemonize (stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
 
def daemonize (stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
     '''This forks the current process into a daemon.
+
 
    The stdin, stdout, and stderr arguments are file names that
+
     '''This forks the current process into a daemon. The stdin, stdout, and
    will be opened and be used to replace the standard file descriptors
+
    stderr arguments are file names that will be opened and be used to replace
    in sys.stdin, sys.stdout, and sys.stderr.
+
    the standard file descriptors in sys.stdin, sys.stdout, and sys.stderr.
     These arguments are optional and default to /dev/null.
+
     These arguments are optional and default to /dev/null. Note that stderr is
    Note that stderr is opened unbuffered, so
+
    opened unbuffered, so if it shares a file with stdout then interleaved
    if it shares a file with stdout then interleaved output
+
     output may not appear in the order that you expect. '''
     may not appear in the order that you expect.
 
    '''
 
  
 
     # Do first fork.
 
     # Do first fork.
Line 38: Line 59:
 
         pid = os.fork()  
 
         pid = os.fork()  
 
         if pid > 0:
 
         if pid > 0:
             return pid
+
             sys.exit(0)  # Exit first parent.
 
     except OSError, e:  
 
     except OSError, e:  
return (e.errno, e.strerror) # Fork failed.
+
        sys.stderr.write ("fork #1 failed: (%d) %s\n" % (e.errno, e.strerror) )
 +
        sys.exit(1)
  
 
     # Decouple from parent environment.
 
     # Decouple from parent environment.
 +
    os.chdir("/")
 +
    os.umask(0)
 
     os.setsid()  
 
     os.setsid()  
 
    # When the first child terminates, all processes in the second child
 
    # are sent a SIGHUP. This causes it to be ignored.
 
    signal.signal(signal.SIGHUP, signal.SIG_IGN)
 
  
 
     # Do second fork.
 
     # Do second fork.
Line 53: Line 73:
 
         pid = os.fork()  
 
         pid = os.fork()  
 
         if pid > 0:
 
         if pid > 0:
             os._exit(0)  # Exit second parent.
+
             sys.exit(0)  # Exit second parent.
 
     except OSError, e:  
 
     except OSError, e:  
 
         sys.stderr.write ("fork #2 failed: (%d) %s\n" % (e.errno, e.strerror) )
 
         sys.stderr.write ("fork #2 failed: (%d) %s\n" % (e.errno, e.strerror) )
         os._exit(1)
+
         sys.exit(1)
  
 
     # Now I am a daemon!
 
     # Now I am a daemon!
 
      
 
      
    os.chdir("/") # don't hold open any directories
 
    os.umask(0)
 
 
    # Close open files. Try the system configuration variable, SC_OPEN_MAX,
 
    # for the maximum number of open files to close. If it doesn't exist, use 1024.
 
    try:
 
        maxfd = os.sysconf("SC_OPEN_MAX")
 
    except (AttributeError, ValueError):
 
        maxfd = 1024
 
    for fd in range(3, maxfd):
 
        try:
 
            os.close(fd)
 
        except OSError:  # ERROR (ignore)
 
            pass
 
 
 
     # Redirect standard file descriptors.
 
     # Redirect standard file descriptors.
 
     si = open(stdin, 'r')
 
     si = open(stdin, 'r')
Line 82: Line 87:
 
     os.dup2(so.fileno(), sys.stdout.fileno())
 
     os.dup2(so.fileno(), sys.stdout.fileno())
 
     os.dup2(se.fileno(), sys.stderr.fileno())
 
     os.dup2(se.fileno(), sys.stderr.fileno())
    return 0
 
  
 
def main ():
 
def main ():
 +
 
     '''This is an example main function run by the daemon.
 
     '''This is an example main function run by the daemon.
 
     This prints a count and timestamp once per second.
 
     This prints a count and timestamp once per second.
 
     '''
 
     '''
 +
 
     import time
 
     import time
 
     sys.stdout.write ('Daemon started with pid %d\n' % os.getpid() )
 
     sys.stdout.write ('Daemon started with pid %d\n' % os.getpid() )
Line 100: Line 106:
  
 
if __name__ == "__main__":
 
if __name__ == "__main__":
     print str(daemonize('/dev/null','/tmp/daemon.log','/tmp/daemon.log'))
+
     daemonize('/dev/null','/tmp/daemon.log','/tmp/daemon.log')
 
     main()
 
     main()
 +
 
</pre>
 
</pre>
 +
Click here to download: [http://www.noah.org/engineering/src/python/daemonize.py daemonize.py]
 +
<include src="/home/noahspurrier/noah.org/engineering/src/python/daemonize.py" highlight="python" />

Latest revision as of 00:45, 7 January 2019


This short module shows the correct way to create a UNIX daemon process in Python. Note that there are modules for this available.

  • check for existing PID lock file to determine if daemon is already running. This is a convention. This falls into the application level. This is not a UNIX daemon process requirement.
  • fork/exec
  • create independent session and group and detach from parent (setsid, between fork and exec)
  • detach from controlling tty
  • clear signal handlers.
  • create PID lock file to indicate new running daemon.
  • set uid and gid.
  • drop suid and sgid privileges.
  • close inherited file descriptors (including stdin, stdout, stderr).
  • open file descriptors for stdin, stdout, and stderr as necessary.
  • set umask (027).
  • set environment for running inside a chroot.
  • change working directory (relative to chroot if applicable).
  • register exit function (at least delete the daemon PID lock file).

Note that the following example does not handle all of the above requirements! The missing bits are left as an exercise.

#!/usr/bin/env python
import sys, os 

'''This module is used to fork the current process into a daemon.

Almost none of this is necessary (or advisable) if your daemon is being started
by inetd. In that case, stdin, stdout and stderr are all set up for you to
refer to the network connection, and the fork()s and session manipulation
should not be done (to avoid confusing inetd). Only the chdir() and umask()
steps remain useful. 

References:

    UNIX Programming FAQ
        1.7 How do I get my program to act like a daemon?
        http://www.unixguide.net/unix/programming/1.7.shtml
        http://www.faqs.org/faqs/unix-faq/programmer/faq/
   
    Advanced Programming in the Unix Environment
        W. Richard Stevens, 1992, Addison-Wesley, ISBN 0-201-56317-7.

$Id: daemonize.py 251 2008-05-05 21:51:11Z noah $
'''

def daemonize (stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):

    '''This forks the current process into a daemon. The stdin, stdout, and
    stderr arguments are file names that will be opened and be used to replace
    the standard file descriptors in sys.stdin, sys.stdout, and sys.stderr.
    These arguments are optional and default to /dev/null. Note that stderr is
    opened unbuffered, so if it shares a file with stdout then interleaved
    output may not appear in the order that you expect. '''

    # Do first fork.
    try: 
        pid = os.fork() 
        if pid > 0:
            sys.exit(0)   # Exit first parent.
    except OSError, e: 
        sys.stderr.write ("fork #1 failed: (%d) %s\n" % (e.errno, e.strerror) )
        sys.exit(1)

    # Decouple from parent environment.
    os.chdir("/") 
    os.umask(0) 
    os.setsid() 

    # Do second fork.
    try: 
        pid = os.fork() 
        if pid > 0:
            sys.exit(0)   # Exit second parent.
    except OSError, e: 
        sys.stderr.write ("fork #2 failed: (%d) %s\n" % (e.errno, e.strerror) )
        sys.exit(1)

    # Now I am a daemon!
    
    # Redirect standard file descriptors.
    si = open(stdin, 'r')
    so = open(stdout, 'a+')
    se = open(stderr, 'a+', 0)
    os.dup2(si.fileno(), sys.stdin.fileno())
    os.dup2(so.fileno(), sys.stdout.fileno())
    os.dup2(se.fileno(), sys.stderr.fileno())

def main ():

    '''This is an example main function run by the daemon.
    This prints a count and timestamp once per second.
    '''

    import time
    sys.stdout.write ('Daemon started with pid %d\n' % os.getpid() )
    sys.stdout.write ('Daemon stdout output\n')
    sys.stderr.write ('Daemon stderr output\n')
    c = 0
    while 1:
        sys.stdout.write ('%d: %s\n' % (c, time.ctime(time.time())) )
        sys.stdout.flush()
        c = c + 1
        time.sleep(1)

if __name__ == "__main__":
    daemonize('/dev/null','/tmp/daemon.log','/tmp/daemon.log')
    main()

Click here to download: daemonize.py <include src="/home/noahspurrier/noah.org/engineering/src/python/daemonize.py" highlight="python" />