# -*- 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"""
<par>This module can be used on UNIX to fork a daemon process. It is based
on <link href="http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66012">Jürgen Hermann's Cookbook recipe</link>.</par>

<par>An example script might look like this:</par>

<example>
<prog>
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)
</prog>
</example>
"""


__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 <class>Daemon</class> class provides methods for <pyref method="start">starting</pyref>
	and <pyref method="stop">stopping</pyref> a daemon process as well as
	<pyref method="service">handling command line arguments</pyref>.
	"""
	def __init__(self, stdin="/dev/null", stdout="/dev/null", stderr="/dev/null", pidfile=None, user=None, group=None):
		"""
		<par>The <arg>stdin</arg>, <arg>stdout</arg>, and <arg>stderr</arg> arguments
		are file names that will be opened and be used to replace the standard file
		descriptors in <lit>sys.stdin</lit>, <lit>sys.stdout</lit>, and
		<lit>sys.stderr</lit>. These arguments are optional and default to
		<lit>"/dev/null"</lit>. 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.</par>

		<par><arg>pidfile</arg> must be the name of a file. <method>start</method>
		will write the pid of the newly forked daemon to this file. <method>stop</method>
		uses this file to kill the daemon.</par>

		<par><arg>user</arg> can be the name or uid of a user. <method>start</method>
		will switch to this user for running the service. If <arg>user</arg> is
		<lit>None</lit> no user switching will be done.</par>

		<par>In the same way <arg>group</arg> can be the name or gid of a group.
		<method>start</method> will switch to this group.</par>
		"""
		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 <lit>SIG_HUP</lit> signal: Reopen standard file descriptors.
		"""
		self.openstreams()

	def handlesigterm(self, signum, frame):
		"""
		Handle a <lit>SIG_TERM</lit> 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 <arg>user</arg> is <lit>None</lit>
		and <arg>group</arg> is nothing will be done. <arg>user</arg> and <arg>group</arg>
		can be an <class>int</class> (i.e. a user/group id) or <class>str</class>
		(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 <lit>SIGTERM</lit> 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):
		"""
		<par>Handle command line arguments and start or stop the daemon accordingly.</par>

		<par><arg>args</arg> must be a list of command line arguments (including the
		program name in <lit>args[0]</lit>). If <arg>args</arg> is <lit>None</lit>
		or unspecified <lit>sys.argv</lit> is used.</par>

		<par>The return value is true, if <option>start</option> has been specified
		as the command line argument, i.e. if the daemon should be started.</par>
		"""
		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


syntax highlighted by Code2HTML, v. 0.9.1