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