From cce6e058c27d7184ffd900c7cf89bcc286aa51c5 Mon Sep 17 00:00:00 2001 From: Stefan Harmuth Date: Sun, 5 Dec 2021 10:19:36 +0100 Subject: [PATCH] the famous daemon class from Joseph Ernest --- daemon.py | 153 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 daemon.py diff --git a/daemon.py b/daemon.py new file mode 100644 index 0000000..780846d --- /dev/null +++ b/daemon.py @@ -0,0 +1,153 @@ +# Shamelessly stolen from https://gist.github.com/josephernest/77fdb0012b72ebdf4c9d19d6256a1119 +# +# From "A simple unix/linux daemon in Python" by Sander Marechal +# See http://stackoverflow.com/a/473702/1422096 and +# http://web.archive.org/web/20131017130434/http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/ +# +# Modified to add quit() that allows to run some code before closing the daemon +# See http://stackoverflow.com/a/40423758/1422096 +# +# Modified for Python 3 +# (see also: http://web.archive.org/web/20131017130434/http://www.jejik.com/files/examples/daemon3x.py) +# +# Joseph Ernest, 20200507_1220 + +import atexit +import os +import sys +import time +from signal import SIGTERM, signal + + +class Daemon: + """ + A generic daemon class. + + Usage: subclass the Daemon class and override the run() method + """ + + def __init__(self, pidfile='_.pid', stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'): + self.stdin = stdin + self.stdout = stdout + self.stderr = stderr + self.pidfile = pidfile + + def daemonize(self): + """ + do the UNIX double-fork magic, see Stevens' "Advanced + Programming in the UNIX Environment" for details (ISBN 0201563177) + http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16 + """ + try: + pid = os.fork() + if pid > 0: + # exit first parent + sys.exit(0) + except OSError as e: + sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror)) + sys.exit(1) + + # decouple from parent environment + os.setsid() + os.umask(0) + + # do second fork + try: + pid = os.fork() + if pid > 0: + # exit from second parent + sys.exit(0) + except OSError as e: + sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror)) + sys.exit(1) + + # redirect standard file descriptors + sys.stdout.flush() + sys.stderr.flush() + si = open(os.devnull, 'r') + so = open(os.devnull, 'a+') + se = open(os.devnull, 'a+') + os.dup2(si.fileno(), sys.stdin.fileno()) + os.dup2(so.fileno(), sys.stdout.fileno()) + os.dup2(se.fileno(), sys.stderr.fileno()) + + atexit.register(self.onstop) + signal(SIGTERM, lambda signum, stack_frame: exit()) + + # write pidfile + pid = str(os.getpid()) + open(self.pidfile, 'w+').write("%s\n" % pid) + + def onstop(self): + self.quit() + os.remove(self.pidfile) + + def start(self): + """ + Start the daemon + """ + # Check for a pidfile to see if the daemon already runs + try: + pf = open(self.pidfile, 'r') + pid = int(pf.read().strip()) + pf.close() + except IOError: + pid = None + + if pid: + message = "pidfile %s already exist. Daemon already running?\n" + sys.stderr.write(message % self.pidfile) + sys.exit(1) + + # Start the daemon + self.daemonize() + self.run() + + def stop(self): + """ + Stop the daemon + """ + # Get the pid from the pidfile + try: + pf = open(self.pidfile, 'r') + pid = int(pf.read().strip()) + pf.close() + except IOError: + pid = None + + if not pid: + message = "pidfile %s does not exist. Daemon not running?\n" + sys.stderr.write(message % self.pidfile) + return # not an error in a restart + + # Try killing the daemon process + try: + while 1: + os.kill(pid, SIGTERM) + time.sleep(0.1) + except OSError as err: + err = str(err) + if err.find("No such process") > 0: + if os.path.exists(self.pidfile): + os.remove(self.pidfile) + else: + print(str(err)) + sys.exit(1) + + def restart(self): + """ + Restart the daemon + """ + self.stop() + self.start() + + def run(self): + """ + You should override this method when you subclass Daemon. It will be called after the process has been + daemonized by start() or restart(). + """ + + def quit(self): + """ + You should override this method when you subclass Daemon. It will be called before the process is stopped. + """