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