]> kaliko git repositories - mpd-sima.git/blob - sima/lib/daemon.py
Catches SIGHUP/SIGUSR1 to trigger conf reload
[mpd-sima.git] / sima / lib / daemon.py
1 # -*- coding: utf-8 -*-
2
3 # Public Domain
4 #
5 # Copyright 2007, 2009 Sander Marechal <s.marechal@jejik.com>
6 # http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/
7 #
8 # Copyright 2010, 2011 Jack Kaliko <efrim@azylum.org>
9 # https://gitorious.org/python-daemon
10 #
11 #  This file is part of MPD_sima
12 #
13 #  MPD_sima is free software: you can redistribute it and/or modify
14 #  it under the terms of the GNU General Public License as published by
15 #  the Free Software Foundation, either version 3 of the License, or
16 #  (at your option) any later version.
17 #
18 #  MPD_sima is distributed in the hope that it will be useful,
19 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
20 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21 #  GNU General Public License for more details.
22 #
23 #  You should have received a copy of the GNU General Public License
24 #  along with MPD_sima.  If not, see <http://www.gnu.org/licenses/>.
25
26 import atexit
27 import os
28 import sys
29 import time
30 from signal import signal, SIGTERM, SIGHUP, SIGUSR1
31
32
33 class Daemon(object):
34     """
35     A generic daemon class.
36
37     Usage: subclass the Daemon class and override the run() method
38
39         Daemon([pidfile[, stdin[, stdout[, stderr]]]])
40
41             pidfile : file to write pid to (default no pid file writen)
42             stdin   : standard input file descriptor (default to /dev/null)
43             stdout  : standard output file descriptor (default to /dev/null)
44             stderr  : standard error file descriptorr (default to /dev/null)
45     """
46     version = '0.6'
47
48     def __init__(self, pidfile,
49             stdin = os.devnull,
50             stdout = os.devnull,
51             stderr = os.devnull):
52         self.stdin = stdin
53         self.stdout = stdout
54         self.stderr = stderr
55         self.pidfile = pidfile
56         self.umask = 0
57
58     def daemonize(self):
59         """
60         Do the UNIX double-fork magic.
61         see W. Richard Stevens, "Advanced Programming in the Unix Environment"
62         for details (ISBN 0201563177)
63
64         Short explanation:
65             Unix processes belong to "process group" which in turn lies within a "session".
66             A session can have a controlling tty.
67             Forking twice allows to detach the session from a possible tty.
68             The process lives then within the init process.
69         """
70         try:
71             pid = os.fork()
72             if pid > 0:
73                 # exit first parent
74                 sys.exit(0)
75         except OSError as e:
76             sys.stderr.write('fork #1 failed: {0.errno:d} ({0.strerror})\n'.format(e))
77             sys.exit(1)
78
79         # Decouple from parent environment
80         os.chdir('/')
81         os.setsid()
82         self.umask = os.umask(0)
83
84         # Do second fork
85         try:
86             pid = os.fork()
87             if pid > 0:
88                 # exit from second parent
89                 sys.exit(0)
90         except OSError as e:
91             sys.stderr.write('fork #2 failed: {0.errno:d} ({0.strerror})\n'.format(e))
92             sys.exit(1)
93
94         self.write_pid()
95         # redirect standard file descriptors
96         sys.stdout.flush()
97         sys.stderr.flush()
98         # TODO: binary or txt mode?
99         si = open(self.stdin,  mode='rb')
100         so = open(self.stdout, mode='ab+')
101         se = open(self.stderr, mode='ab+', buffering=0)
102         os.dup2(si.fileno(), sys.stdin.fileno())
103         os.dup2(so.fileno(), sys.stdout.fileno())
104         os.dup2(se.fileno(), sys.stderr.fileno())
105
106         atexit.register(self.shutdown)
107         self.signal_management()
108
109     def write_pid(self):
110         # write pidfile
111         if not self.pidfile:
112             return
113         pid = str(os.getpid())
114         try:
115             os.umask(self.umask)
116             open(self.pidfile, 'w').write('%s\n' % pid)
117         except Exception as wpid_err:
118             sys.stderr.write('Error trying to write pid file: {}\n'.format(wpid_err))
119             sys.exit(1)
120         os.umask(0)
121         atexit.register(self.delpid)
122
123     def signal_management(self):
124         """Declare signal handlers
125         """
126         signal(SIGTERM, self.exit_handler)
127         signal(SIGHUP, self.hup_handler)
128         signal(SIGUSR1, self.hup_handler)
129
130     def exit_handler(self, signum, frame):
131         sys.exit(1)
132
133     def hup_handler(self, signum, frame):
134         """SIGHUP handler"""
135         pass
136
137     def delpid(self):
138         """Remove PID file"""
139         try:
140             os.unlink(self.pidfile)
141         except OSError as err:
142             message = 'Error trying to remove PID file: {}\n'
143             sys.stderr.write(message.format(err))
144
145     def start(self):
146         """
147         Start the daemon
148         """
149         # Check for a pidfile to see if the daemon already runs
150         try:
151             pf = open(self.pidfile, 'r')
152             pid = int(pf.read().strip())
153             pf.close()
154         except IOError:
155             pid = None
156
157         if pid:
158             message = 'pidfile {0.pidfile} already exist. Daemon already running?\n'
159             sys.stderr.write(message.format(self))
160             sys.exit(1)
161
162         # Start the daemon
163         self.daemonize()
164         self.run()
165
166     def foreground(self):
167         """
168         Foreground/debug mode
169         """
170         self.write_pid()
171         atexit.register(self.shutdown)
172         self.run()
173
174     def stop(self):
175         """
176         Stop the daemon
177         """
178         # Get the pid from the pidfile
179         try:
180             pf = open(self.pidfile, 'r')
181             pid = int(pf.read().strip())
182             pf.close()
183         except IOError:
184             pid = None
185
186         if not pid:
187             message = 'pidfile {0.pidfile} does not exist. Is the Daemon running?\n'
188             sys.stderr.write(message.format(self))
189             return  # not an error in a restart
190
191         # Try killing the daemon process
192         try:
193             os.kill(pid, SIGTERM)
194             time.sleep(0.1)
195         except OSError as err:
196             if err.errno == 3:
197                 if os.path.exists(self.pidfile):
198                     message = "Daemon's not running? removing pid file {0.pidfile}.\n"
199                     sys.stderr.write(message.format(self))
200                     os.remove(self.pidfile)
201             else:
202                 sys.stderr.write(err.strerror)
203                 sys.exit(1)
204
205     def restart(self):
206         """
207         Restart the daemon
208         """
209         self.stop()
210         self.start()
211
212     def shutdown(self):
213         """
214         You should override this method when you subclass Daemon. It will be
215         called when the process is being stopped.
216         Pay attention:
217         Daemon() uses atexit to call Daemon().shutdown(), as a consequence
218         shutdown and any other functions registered via this module are not
219         called when the program is killed by an un-handled/unknown signal.
220         This is the reason of Daemon().signal_management() existence.
221         """
222
223     def run(self):
224         """
225         You should override this method when you subclass Daemon. It will be
226         called after the process has been daemonized by start() or restart().
227         """
228
229 # VIM MODLINE
230 # vim: ai ts=4 sw=4 sts=4 expandtab