]> kaliko git repositories - python-daemon.git/blob - src/daemon.py
tagging Daemon class with version string
[python-daemon.git] / src / daemon.py
1 # -*- coding: utf-8 -*-
2
3 # Public Domain
4 #
5 # Copyright 2007, 2009 Sander Marechal <s.marechal@jejik.com>
6 # Copyright 2010, 2011 Jack Kaliko <efrim@azylum.org>
7 #
8 # http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/
9
10 import atexit
11 import os
12 import sys
13 import time
14 from signal import signal, SIGTERM
15
16
17 class Daemon(object):
18     """
19     A generic daemon class.
20
21     Usage: subclass the Daemon class and override the run() method
22     """
23     version = "0.2"
24
25     def __init__(self, pidfile,
26             stdin='/dev/null',
27             stdout='/dev/null',
28             stderr='/dev/null'):
29         self.stdin = stdin
30         self.stdout = stdout
31         self.stderr = stderr
32         self.pidfile = pidfile
33         self.umask = 0
34
35     def daemonize(self):
36         """
37         do the UNIX double-fork magic, see Stevens' "Advanced
38         Programming in the UNIX Environment" for details (ISBN 0201563177)
39         http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
40         """
41         try:
42             pid = os.fork()
43             if pid > 0:
44                 # exit first parent
45                 sys.exit(0)
46         except OSError, e:
47             sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror))
48             sys.exit(1)
49
50         # Decouple from parent environment
51         os.chdir("/")
52         os.setsid()
53         self.umask = os.umask(0)
54
55         # Do second fork
56         try:
57             pid = os.fork()
58             if pid > 0:
59                 # exit from second parent
60                 sys.exit(0)
61         except OSError, e:
62             sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror))
63             sys.exit(1)
64
65         self.write_pid()
66         # redirect standard file descriptors
67         sys.stdout.flush()
68         sys.stderr.flush()
69         si = file(self.stdin, 'r')
70         so = file(self.stdout, 'a+')
71         se = file(self.stderr, 'a+', 0)
72         os.dup2(si.fileno(), sys.stdin.fileno())
73         os.dup2(so.fileno(), sys.stdout.fileno())
74         os.dup2(se.fileno(), sys.stderr.fileno())
75
76         atexit.register(self.shutdown)
77         self.signal_management()
78
79     def write_pid(self):
80         # write pidfile
81         if not self.pidfile:
82             return
83         pid = str(os.getpid())
84         try:
85             os.umask(self.umask)
86             file(self.pidfile, 'w').write('%s\n' % pid)
87             #except IOError, wpid_err:
88         except Exception, wpid_err:
89             sys.stderr.write(u'Error trying to write pid file to %s: %s\n' %
90                    (unicode(self.pidfile, 'utf-8'), wpid_err))
91             sys.exit(1)
92         os.umask(0)
93         atexit.register(self.delpid)
94
95     def signal_management(self):
96         # Declare signal handlers
97         signal(SIGTERM, self.exit_handler)
98
99     def exit_handler(self, signum, frame):
100         sys.exit(1)
101
102     def delpid(self):
103         os.unlink(self.pidfile)
104
105     def start(self):
106         """
107         Start the daemon
108         """
109         # Check for a pidfile to see if the daemon already runs
110         try:
111             pf = file(self.pidfile, 'r')
112             pid = int(pf.read().strip())
113             pf.close()
114         except IOError:
115             pid = None
116
117         if pid:
118             message = "pidfile %s already exist. Daemon already running?\n"
119             sys.stderr.write(message % self.pidfile)
120             sys.exit(1)
121
122         # Start the daemon
123         self.daemonize()
124         self.run()
125
126     def foreground(self):
127         """
128         Foreground/debug mode
129         """
130         self.write_pid()
131         atexit.register(self.shutdown)
132         self.run()
133
134     def stop(self):
135         """
136         Stop the daemon
137         """
138         # Get the pid from the pidfile
139         try:
140             pf = file(self.pidfile, 'r')
141             pid = int(pf.read().strip())
142             pf.close()
143         except IOError:
144             pid = None
145
146         if not pid:
147             message = "pidfile %s does not exist. Daemon not running?\n"
148             sys.stderr.write(message % self.pidfile)
149             return  # not an error in a restart
150
151         # Try killing the daemon process
152         try:
153             os.kill(pid, SIGTERM)
154             time.sleep(0.1)
155         except OSError, err:
156             err = str(err)
157             if err.find("No such process") > 0:
158                 if os.path.exists(self.pidfile):
159                     message = "Daemon not running? removing pid file %s.\n"
160                     sys.stderr.write(message % self.pidfile)
161                     os.remove(self.pidfile)
162             else:
163                 print str(err)
164                 sys.exit(1)
165
166     def restart(self):
167         """
168         Restart the daemon
169         """
170         self.stop()
171         self.start()
172
173     def shutdown(self):
174         """
175         You should override this method when you subclass Daemon. It will be
176         called when the process is being stopped.
177         Pay attention:
178         Daemon() uses atexit to call Daemon().shutdown(), as a consequence
179         shutdown and any other functions registered via this module are not
180         called when the program is killed by an un-handled/unknown signal.
181         This is the reason of Daemon().signal_management() existence.
182         """
183
184     def run(self):
185         """
186         You should override this method when you subclass Daemon. It will be
187         called after the process has been daemonized by start() or restart().
188         """
189
190
191 def main():
192     pass
193
194 # Script starts here
195 if __name__ == '__main__':
196     main()
197
198 # VIM MODLINE
199 # vim: ai ts=4 sw=4 sts=4 expandtab