# -*- coding: iso-8859-1 -*-
## Copyright 2007 by LivingLogic AG, Bayreuth/Germany.
## Copyright 2007 by Walter Dörwald
##
## All Rights Reserved
##
## See __init__.py for the license
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 sys, os, 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.18 $"[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 SIGTERM 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