1
2
3
4
5
6 """
7 Overview
8 ========
9
10 The ``grizzled.os`` module contains some operating system-related functions and
11 classes. It is a conceptual extension of the standard Python ``os`` module.
12 """
13
14 from __future__ import absolute_import
15
16 __docformat__ = "restructuredtext en"
17
18
19
20
21
22 import logging
23 import os as _os
24 import sys
25 import glob
26 from contextlib import contextmanager
27
28 from grizzled.decorators import deprecated
29
30
31
32
33
34 __all__ = ['daemonize', 'DaemonError', 'working_directory',
35 'file_separator', 'path_separator']
36
37
38
39
40
41
42
43 UMASK = 0
44
45
46 WORKDIR = "/"
47
48
49 MAXFD = 1024
50
51
52 if (hasattr(_os, "devnull")):
53 NULL_DEVICE = _os.devnull
54 else:
55 NULL_DEVICE = "/dev/null"
56
57
58
59 PATH_SEPARATOR = {'nt' : ';', 'posix' : ':', 'java' : ':'}
60 FILE_SEPARATOR = {'nt' : '\\', 'posix' : '/', 'java' :'/'}
61
62
63
64
65
66 log = logging.getLogger('grizzled.os')
73 """
74 Thrown by ``daemonize()`` when an error occurs while attempting to create
75 a daemon.
76 """
77 pass
78
84 """
85 Get the path separator for the current operating system. The path
86 separator is used to separate elements of a path string, such as
87 "PATH" or "CLASSPATH". (It's a ":" on Unix-like systems and a ";"
88 on Windows.)
89
90 :rtype: str
91 :return: the path separator
92 """
93 return PATH_SEPARATOR[_os.name]
94
97 """
98 Get the file separator for the current operating system. The file
99 separator is used to separate file elements in a pathname. (It's
100 "/" on Unix-like systems and a "\\\\" on Windows.)
101
102 **Deprecated**. Use the standard Python ``os.path.sep`` variable,
103 instead.
104
105 :rtype: str
106 :return: the file separator
107 """
108 return FILE_SEPARATOR[_os.name]
109
111 """
112 Given a path string value (e.g., the value of the environment variable
113 ``PATH``), this generator function yields each item in the path.
114
115 :Parameters:
116 path
117 the path to break up
118 """
119 for p in path.split(path_separator()):
120 yield p
121
124 """
125 This function is intended to be used as a ``with`` statement context
126 manager. It allows you to replace code like this:
127
128 .. python::
129
130 original_directory = _os.getcwd()
131 try:
132 _os.chdir(some_dir)
133 ... bunch of code ...
134 finally:
135 _os.chdir(original_directory)
136
137 with something simpler:
138
139 .. python ::
140
141 from __future__ import with_statement
142 from grizzled.os import working_directory
143
144 with working_directory(some_dir):
145 ... bunch of code ...
146
147 :Parameters:
148 directory : str
149 directory in which to execute
150
151 :return: yields the ``directory`` parameter
152 """
153 original_directory = _os.getcwd()
154 try:
155 _os.chdir(directory)
156 yield directory
157
158 finally:
159 _os.chdir(original_directory)
160
162 """
163 Determine whether the specified system command exists in the specified
164 path.
165
166 :Parameters:
167 command_name
168 The (simple) filename of the command to find. May be a glob
169 string.
170
171 path
172 The path to search, as a list or a string. If this parameter
173 is a string, then it is split using the operating system-specific
174 path separator. If this parameter is missing, then the ``PATH``
175 environment variable is used
176
177 :rtype: str
178 :return: The path to the first command that matches ``command_name``, or
179 ``None`` if not found
180 """
181 if not path:
182 path = _os.environ.get('PATH', '.')
183
184 if type(path) == str:
185 path = path.split(path_separator())
186 elif type(path) == list:
187 pass
188 else:
189 assert False, 'path parameter must be a list or a string'
190
191 found = None
192 for p in path:
193 full_path = _os.path.join(p, command_name)
194 for p2 in glob.glob(full_path):
195 if _os.access(p2, _os.X_OK):
196 found = p2
197 break
198
199 if found:
200 break
201
202 return found
203
204 -def spawnd(path, args, pidfile=None):
205 """
206 Run a command as a daemon. This method is really just shorthand for the
207 following code:
208
209 .. python::
210
211 daemonize(pidfile=pidfile)
212 _os.execv(path, args)
213
214 :Parameters:
215 path : str
216 Full path to program to run
217
218 args : list
219 List of command arguments. The first element in this list must
220 be the command name (i.e., arg0).
221
222 pidfile : str
223 Path to file to which to write daemon's process ID. The string may
224 contain a ``${pid}`` token, which is replaced with the process ID
225 of the daemon. e.g.: ``/var/run/myserver-${pid}``
226 """
227 daemonize(no_close=True, pidfile=pidfile)
228 _os.execv(path, args)
229
231 """
232 Convert the calling process into a daemon. To make the current Python
233 process into a daemon process, you need two lines of code:
234
235 .. python::
236
237 from grizzled.os import daemonize
238 daemonize.daemonize()
239
240 If ``daemonize()`` fails for any reason, it throws a ``DaemonError``,
241 which is a subclass of the standard ``OSError`` exception. also logs debug
242 messages, using the standard Python ``logging`` package, to channel
243 "grizzled.os.daemon".
244
245 **Adapted from:** http://software.clapper.org/daemonize/
246
247 **See Also:**
248
249 - Stevens, W. Richard. *Unix Network Programming* (Addison-Wesley, 1990).
250
251 :Parameters:
252 no_close : bool
253 If ``True``, don't close the file descriptors. Useful if the
254 calling process has already redirected file descriptors to an
255 output file. **Warning**: Only set this parameter to ``True`` if
256 you're *sure* there are no open file descriptors to the calling
257 terminal. Otherwise, you'll risk having the daemon re-acquire a
258 control terminal, which can cause it to be killed if someone logs
259 off that terminal.
260
261 pidfile : str
262 Path to file to which to write daemon's process ID. The string may
263 contain a ``${pid}`` token, which is replaced with the process ID
264 of the daemon. e.g.: ``/var/run/myserver-${pid}``
265
266 :raise DaemonError: Error during daemonizing
267 """
268 log = logging.getLogger('grizzled.os.daemon')
269
270 def __fork():
271 try:
272 return _os.fork()
273 except OSError, e:
274 raise DaemonError, ('Cannot fork', e.errno, e.strerror)
275
276 def __redirect_file_descriptors():
277 import resource
278 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
279 if maxfd == resource.RLIM_INFINITY:
280 maxfd = MAXFD
281
282
283
284 for fd in range(0, maxfd):
285
286 try:
287 _os.ttyname(fd)
288 except:
289 continue
290
291 try:
292 _os.close(fd)
293 except OSError:
294
295 pass
296
297
298
299
300
301
302 _os.open(NULL_DEVICE, _os.O_RDWR)
303 _os.dup2(0, 1)
304 _os.dup2(0, 2)
305
306
307 if _os.name != 'posix':
308 import errno
309 raise DaemonError, \
310 ('daemonize() is only supported on Posix-compliant systems.',
311 errno.ENOSYS, _os.strerror(errno.ENOSYS))
312
313 try:
314
315
316 log.debug('Forking first child.')
317 pid = __fork()
318 if pid != 0:
319
320
321 _os._exit(0)
322
323
324
325
326 log.debug('Creating new session')
327 _os.setsid()
328
329
330
331 log.debug('Forking second child.')
332 pid = __fork()
333 if pid != 0:
334
335 _os._exit(0)
336
337
338 log.debug('Setting umask')
339 _os.umask(UMASK)
340
341
342
343
344 log.debug('Changing working directory to "%s"' % WORKDIR)
345 _os.chdir(WORKDIR)
346
347
348 if not no_close:
349 log.debug('Redirecting file descriptors')
350 __redirect_file_descriptors()
351
352 if pidfile:
353 from string import Template
354 t = Template(pidfile)
355 pidfile = t.safe_substitute(pid=str(_os.getpid()))
356 open(pidfile, 'w').write(str(_os.getpid()) + '\n')
357
358 except DaemonError:
359 raise
360
361 except OSError, e:
362 raise DaemonError, ('Unable to daemonize()', e.errno, e.strerror)
363
364
365
366
367
368 if __name__ == '__main__':
369
370 log = logging.getLogger('grizzled.os')
371 hdlr = logging.StreamHandler(sys.stdout)
372 formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s', '%T')
373 hdlr.setFormatter(formatter)
374 log.addHandler(hdlr)
375 log.setLevel(logging.DEBUG)
376
377 log.debug('Before daemonizing, PID=%d' % _os.getpid())
378 daemonize(no_close=True)
379 log.debug('After daemonizing, PID=%d' % _os.getpid())
380 log.debug('Daemon is sleeping for 10 seconds')
381
382 import time
383 time.sleep(10)
384
385 log.debug('Daemon exiting')
386 sys.exit(0)
387