1
2
3
4
5
6
7
8 """
9 Overview
10 ========
11
12 Convert the calling process to a daemon. To make the current Python process
13 into a daemon process, you need two lines of code::
14
15 import daemon
16 daemon.daemonize()
17
18 If C{daemonize()} fails for any reason, it throws an exception. It also
19 logs debug messages, using the standard Python 'logging' package, to
20 channel 'daemon'.
21
22 Adapted from:
23
24 - U{http://www.clapper.org/software/daemonize/}
25
26 See Also
27 ========
28
29 Stevens, W. Richard. I{Unix Network Programming} (Addison-Wesley, 1990).
30 """
31
32 __version__ = "1.0"
33 __author__ = "Brian Clapper, bmc@clapper.org"
34 __url__ = "http://www.clapper.org/software/python/daemon/"
35 __copyright__ = "(c) 2008 Brian M. Clapper"
36 __license__ = "BSD-style license"
37
38 __all__ = ['daemonize', 'DaemonException']
39
40
41
42
43
44 import logging
45 import os
46 import sys
47
48
49
50
51
52
53
54 UMASK = 0
55
56
57 WORKDIR = "/"
58
59
60 MAXFD = 1024
61
62
63 if (hasattr(os, "devnull")):
64 NULL_DEVICE = os.devnull
65 else:
66 NULL_DEVICE = "/dev/null"
67
68
69
70
71
72
73 logger = logging.getLogger('daemonize')
74
75
76
77
78
80 """
81 Thrown by C{daemonize()} when an error occurs while attempting to create
82 a daemon. A C{DaemonException} object always contains a single string
83 value that contains an error message describing the problem.
84 """
86 """
87 Create a new C{DaemonException}.
88
89 @type errorMessage: string
90 @param errorMessage: the error message
91 """
92 self.errorMessage = errorMessage
93
95 """
96 Get a string version of the exception.
97
98 @return: a string representing the exception
99 """
100 return self.errorMessage
101
102
103
104
105
107 """
108 Convert the calling process into a daemon.
109
110 @type noClose: boolean
111 @param noClose: If True, don't close the file descriptors. Useful
112 if the calling process has already redirected file
113 descriptors to an output file. WARNING: Only set this
114 parameter to True if you're SURE there are no open file
115 descriptors to the calling terminal. Otherwise, you'll
116 risk having the daemon re-acquire a control terminal,
117 which can cause it to be killed if someone logs off that
118 terminal.
119
120 @raise DaemonException: Error during daemonizing
121 """
122 global logger
123
124 if os.name != 'posix':
125 logger.warn('Daemon is only supported on Posix-compliant systems.')
126 return
127
128 try:
129
130
131 logger.debug('Forking first child.')
132 pid = _fork()
133 if pid != 0:
134
135
136 os._exit(0)
137
138
139
140
141 logger.debug('Creating new session')
142 os.setsid()
143
144
145
146 logger.debug('Forking second child.')
147 pid = _fork()
148 if pid != 0:
149
150 os._exit(0)
151
152
153 logger.debug('Setting umask')
154 os.umask(UMASK)
155
156
157
158
159 logger.debug('Changing working directory to "%s"' % WORKDIR)
160 os.chdir(WORKDIR)
161
162
163 if not noClose:
164 logger.debug('Redirecting file descriptors')
165 _redirectFileDescriptors()
166
167 except DaemonException:
168 raise
169
170 except OSError, e:
171 raise DaemonException('Error during daemonizing: %s [%d]' %\
172 (e.strerror, e.errno))
173
174
175
176
177
178
180 try:
181 return os.fork()
182 except OSError, e:
183 raise DaemonException, 'Cannot fork: %s [%d]' % (e.strerror, e.errno)
184
186 import resource
187 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
188 if maxfd == resource.RLIM_INFINITY:
189 maxfd = MAXFD
190
191
192
193 for fd in range(0, maxfd):
194 try:
195 os.close(fd)
196 except OSError:
197
198 pass
199
200
201
202
203
204
205 os.open(NULL_DEVICE, os.O_RDWR)
206 os.dup2(0, 1)
207 os.dup2(0, 2)
208
209
210
211
212
213 if __name__ == '__main__':
214
215 logger = logging.getLogger('daemon')
216 hdlr = logging.StreamHandler(sys.stdout)
217 formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s', '%T')
218 hdlr.setFormatter(formatter)
219 logger.addHandler(hdlr)
220 logger.setLevel(logging.DEBUG)
221
222 logger.debug('Before daemonizing, PID=%d' % os.getpid())
223 daemonize(noClose=True)
224 logger.debug('After daemonizing, PID=%d' % os.getpid())
225 logger.debug('Daemon is sleeping for 10 seconds')
226
227 import time
228 time.sleep(10)
229
230 logger.debug('Daemon exiting')
231 sys.exit(0)
232