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