# -*- coding: iso-8859-1 -*- ## Copyright 2006 by LivingLogic AG, Bayreuth/Germany. ## Copyright 2006 by Walter Dörwald ## ## All Rights Reserved ## ## Permission to use, copy, modify, and distribute this software and its documentation ## for any purpose and without fee is hereby granted, provided that the above copyright ## notice appears in all copies and that both that copyright notice and this permission ## notice appear in supporting documentation, and that the name of LivingLogic AG or ## the author not be used in advertising or publicity pertaining to distribution of the ## software without specific, written prior permission. ## ## LIVINGLOGIC AG AND THE AUTHOR DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, ## INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL ## LIVINGLOGIC AG OR THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL ## DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER ## IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR ## IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ur""" This module can be used on UNIX to fork a daemon process. It is based on Jürgen Hermann's Cookbook recipe. An example script might look like this: from ll import daemon counter = daemon.Daemon( stdin="/dev/null", stdout="/tmp/daemon.log", stderr="/tmp/daemon.log", pidfile="/var/run/counter/counter.pid", user="nobody" ) if __name__ == "__main__": if counter.service(): 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 True: sys.stdout.write('%d: %s\n' % (c, time.ctime(time.time()))) sys.stdout.flush() c += 1 time.sleep(1) """ __version__ = "$Revision: 1.13 $"[11:-2] # $Source: /data/cvsroot/LivingLogic/Python/core/src/ll/daemon.py,v $ import sys, os, signal, pwd, grp class Daemon(object): """ The Daemon class provides methods for starting and stopping a daemon process as well as handling command line arguments. """ def __init__(self, stdin="/dev/null", stdout="/dev/null", stderr="/dev/null", pidfile=None, user=None, group=None): """ 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. pidfile must be the name of a file. start will write the pid of the newly forked daemon to this file. stop uses this file to kill the daemon. user can be the name or uid of a user. start will switch to this user for running the service. If user is None no user switching will be done. In the same way group can be the name or gid of a group. start will switch to this group. """ self.stdin = stdin self.stdout = stdout self.stderr = stderr self.pidfile = pidfile self.user = user self.group = group def openstreams(self): """ Open the standard file descriptors stdin, stdout and stderr as specified in the constructor. """ si = open(self.stdin, "r") so = open(self.stdout, "a+") se = open(self.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 handlesighup(self, signum, frame): """ Handle a SIG_HUP signal: Reopen standard file descriptors. """ self.openstreams() def handlesigterm(self, signum, frame): """ Handle a SIG_TERM signal: Remove the pid file and exit. """ if self.pidfile is not None: try: os.remove(self.pidfile) except (KeyboardInterrupt, SystemExit): raise except Exception: pass sys.exit(0) def switchuser(self, user, group): """ Switch the effective user and group. If user is None and group is nothing will be done. user and group can be an int (i.e. a user/group id) or str (a user/group name). """ if group is not None: if isinstance(group, basestring): group = grp.getgrnam(group).gr_gid os.setegid(group) if user is not None: if isinstance(user, basestring): user = pwd.getpwnam(user).pw_uid os.seteuid(user) if "HOME" in os.environ: os.environ["HOME"] = pwd.getpwuid(user).pw_dir def start(self): """ Daemonize the running script. When this method returns the process is completely decoupled from the parent environment. """ # Finish up with the current stdout/stderr sys.stdout.flush() sys.stderr.flush() # Do first fork try: pid = os.fork() if pid > 0: sys.stdout.close() sys.exit(0) # Exit first parent except OSError, exc: sys.exit("%s: fork #1 failed: (%d) %s\n" % (sys.argv[0], exc.errno, exc.strerror)) # Decouple from parent environment os.chdir("/") os.umask(0) os.setsid() # Do second fork try: pid = os.fork() if pid > 0: sys.stdout.close() sys.exit(0) # Exit second parent except OSError, exc: sys.exit("%s: fork #2 failed: (%d) %s\n" % (sys.argv[0], exc.errno, exc.strerror)) # Now I am a daemon! # Switch user self.switchuser(self.user, self.group) # Redirect standard file descriptors (will belong to the new user) self.openstreams() # Write pid file (will belong to the new user) if self.pidfile is not None: open(self.pidfile, "wb").write(str(os.getpid())) # Reopen file descriptions on SIGHUP signal.signal(signal.SIGHUP, self.handlesighup) # Remove pid file and exit on SIGTERM signal.signal(signal.SIGTERM, self.handlesigterm) def stop(self): """ Send a SIG_TERM signal to a running daemon. The pid of the daemon will be read from the pidfile specified in the constructor. """ if self.pidfile is None: sys.exit("no pidfile specified") try: pidfile = open(self.pidfile, "rb") except IOError, exc: sys.exit("can't open pidfile %s: %s" % (self.pidfile, str(exc))) data = pidfile.read() try: pid = int(data) except ValueError: sys.exit("mangled pidfile %s: %r" % (self.pidfile, data)) os.kill(pid, signal.SIGTERM) def service(self, args=None): """ Handle command line arguments and start or stop the daemon accordingly. args must be a list of command line arguments (including the program name in args[0]). If args is None or unspecified sys.argv is used. The return value is true, if has been specified as the command line argument, i.e. if the daemon should be started. """ if args is None: args = sys.argv if len(args) < 2 or args[1] not in ("start", "stop"): sys.exit("Usage: %s (start|stop)" % args[0]) if args[1] == "start": self.start() return True else: self.stop() return False