Add setup.py and moved to seth namespace :)
[python-seth.git] / seth.py
1 # -*- coding: utf-8 -*-
2
3 # Public Domain
4 #
5 # Copyright 2007, 2009 Sander Marechal <s.marechal@jejik.com>
6 # Copyright 2010, 2011, 2012 Jack Kaliko <kaliko@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.5"
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.
38         see W. Richard Stevens, "Advanced Programming in the Unix Environment"
39         for details (ISBN 0201563177)
40
41         Short explanation:
42             Unix processes belong to "process group" which in turn lies within a "session".
43             A session can have a controlling tty.
44             Forking twice allows to detach the session from a possible tty.
45             The process lives then within the init process.
46         """
47         try:
48             pid = os.fork()
49             if pid > 0:
50                 # exit first parent
51                 sys.exit(0)
52         except OSError, e:
53             sys.stderr.write('fork #1 failed: %d (%s)\n' % (e.errno, e.strerror))
54             sys.exit(1)
55
56         # Decouple from parent environment
57         os.chdir('/')
58         os.setsid()
59         self.umask = os.umask(0)
60
61         # Do second fork
62         try:
63             pid = os.fork()
64             if pid > 0:
65                 # exit from second parent
66                 sys.exit(0)
67         except OSError, e:
68             sys.stderr.write('fork #2 failed: %d (%s)\n' % (e.errno, e.strerror))
69             sys.exit(1)
70
71         self.write_pid()
72         # redirect standard file descriptors
73         sys.stdout.flush()
74         sys.stderr.flush()
75         si = file(self.stdin, 'r')
76         so = file(self.stdout, 'a+')
77         se = file(self.stderr, 'a+', 0)
78         os.dup2(si.fileno(), sys.stdin.fileno())
79         os.dup2(so.fileno(), sys.stdout.fileno())
80         os.dup2(se.fileno(), sys.stderr.fileno())
81
82         atexit.register(self.shutdown)
83         self.signal_management()
84
85     def write_pid(self):
86         # write pidfile
87         if not self.pidfile:
88             return
89         pid = str(os.getpid())
90         try:
91             os.umask(self.umask)
92             file(self.pidfile, 'w').write('%s\n' % pid)
93         except Exception, wpid_err:
94             sys.stderr.write(u'Error trying to write pid file: %s\n' % wpid_err)
95             sys.exit(1)
96         os.umask(0)
97         atexit.register(self.delpid)
98
99     def signal_management(self):
100         # Declare signal handlers
101         signal(SIGTERM, self.exit_handler)
102
103     def exit_handler(self, signum, frame):
104         sys.exit(1)
105
106     def delpid(self):
107         try:
108             os.unlink(self.pidfile)
109         except OSError as err:
110             message = 'Error trying to remove PID file: %s\n'
111             sys.stderr.write(message % err)
112
113     def start(self):
114         """
115         Start the daemon
116         """
117         # Check for a pidfile to see if the daemon already runs
118         try:
119             pf = file(self.pidfile, 'r')
120             pid = int(pf.read().strip())
121             pf.close()
122         except IOError:
123             pid = None
124
125         if pid:
126             message = 'pidfile %s already exist. Daemon already running?\n'
127             sys.stderr.write(message % self.pidfile)
128             sys.exit(1)
129
130         # Start the daemon
131         self.daemonize()
132         self.run()
133
134     def foreground(self):
135         """
136         Foreground/debug mode
137         """
138         self.write_pid()
139         atexit.register(self.shutdown)
140         self.run()
141
142     def stop(self):
143         """
144         Stop the daemon
145         """
146         # Get the pid from the pidfile
147         try:
148             pf = file(self.pidfile, 'r')
149             pid = int(pf.read().strip())
150             pf.close()
151         except IOError:
152             pid = None
153
154         if not pid:
155             message = 'pidfile %s does not exist. Is the Daemon running?\n'
156             sys.stderr.write(message % self.pidfile)
157             return  # not an error in a restart
158
159         # Try killing the daemon process
160         try:
161             os.kill(pid, SIGTERM)
162             time.sleep(0.1)
163         except OSError, err:
164             if err.errno == 3:
165                 if os.path.exists(self.pidfile):
166                     message = "Daemon's not running? removing pid file %s.\n"
167                     sys.stderr.write(message % self.pidfile)
168                     os.remove(self.pidfile)
169             else:
170                 sys.stderr.write(err.strerror)
171                 sys.exit(1)
172
173     def restart(self):
174         """
175         Restart the daemon
176         """
177         self.stop()
178         self.start()
179
180     def shutdown(self):
181         """
182         You should override this method when you subclass Daemon. It will be
183         called when the process is being stopped.
184         Pay attention:
185         Daemon() uses atexit to call Daemon().shutdown(), as a consequence
186         shutdown and any other functions registered via this module are not
187         called when the program is killed by an un-handled/unknown signal.
188         This is the reason of Daemon().signal_management() existence.
189         """
190
191     def run(self):
192         """
193         You should override this method when you subclass Daemon. It will be
194         called after the process has been daemonized by start() or restart().
195         """
196
197 # VIM MODLINE
198 # vim: ai ts=4 sw=4 sts=4 expandtab