Package grizzled :: Module os
[hide private]
[frames] | no frames]

Source Code for Module grizzled.os

  1  #!/usr/bin/env python 
  2   
  3  # NOTE: Documentation is intended to be processed by epydoc and contains 
  4  # epydoc markup. 
  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  # Imports 
 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  # Exports 
 32  # --------------------------------------------------------------------------- 
 33   
 34  __all__ = ['daemonize', 'DaemonError', 'working_directory', 
 35             'file_separator', 'path_separator'] 
 36   
 37  # --------------------------------------------------------------------------- 
 38  # Constants 
 39  # --------------------------------------------------------------------------- 
 40   
 41  # Default daemon parameters. 
 42  # File mode creation mask of the daemon. 
 43  UMASK = 0 
 44   
 45  # Default working directory for the daemon. 
 46  WORKDIR = "/" 
 47   
 48  # Default maximum for the number of available file descriptors. 
 49  MAXFD = 1024 
 50   
 51  # The standard I/O file descriptors are redirected to /dev/null by default. 
 52  if (hasattr(_os, "devnull")): 
 53      NULL_DEVICE = _os.devnull 
 54  else: 
 55      NULL_DEVICE = "/dev/null" 
 56   
 57  # The path separator for the operating system. 
 58   
 59  PATH_SEPARATOR = {'nt' : ';', 'posix' : ':', 'java' : ':'} 
 60  FILE_SEPARATOR = {'nt' : '\\', 'posix' : '/', 'java' :'/'} 
 61   
 62  # --------------------------------------------------------------------------- 
 63  # Logging 
 64  # --------------------------------------------------------------------------- 
 65   
 66  log = logging.getLogger('grizzled.os') 
67 68 # --------------------------------------------------------------------------- 69 # Public classes 70 # --------------------------------------------------------------------------- 71 72 -class DaemonError(OSError):
73 """ 74 Thrown by ``daemonize()`` when an error occurs while attempting to create 75 a daemon. 76 """ 77 pass
78
79 # --------------------------------------------------------------------------- 80 # Public functions 81 # --------------------------------------------------------------------------- 82 83 -def path_separator():
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
95 @deprecated(since='0.7', message='Use os.path.sep, instead.') 96 -def file_separator():
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
110 -def path_elements(path):
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
122 @contextmanager 123 -def working_directory(directory):
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
161 -def find_command(command_name, path=None):
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
230 -def daemonize(no_close=False, pidfile=None):
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 # POSIX resource information 278 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1] 279 if maxfd == resource.RLIM_INFINITY: 280 maxfd = MAXFD 281 282 # Close all file descriptors. 283 284 for fd in range(0, maxfd): 285 # Only close TTYs. 286 try: 287 _os.ttyname(fd) 288 except: 289 continue 290 291 try: 292 _os.close(fd) 293 except OSError: 294 # File descriptor wasn't open. Ignore. 295 pass 296 297 # Redirect standard input, output and error to something safe. 298 # os.open() is guaranteed to return the lowest available file 299 # descriptor (0, or standard input). Then, we can dup that 300 # descriptor for standard output and standard error. 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 # Fork once to go into the background. 315 316 log.debug('Forking first child.') 317 pid = __fork() 318 if pid != 0: 319 # Parent. Exit using os._exit(), which doesn't fire any atexit 320 # functions. 321 _os._exit(0) 322 323 # First child. Create a new session. os.setsid() creates the session 324 # and makes this (child) process the process group leader. The process 325 # is guaranteed not to have a control terminal. 326 log.debug('Creating new session') 327 _os.setsid() 328 329 # Fork a second child to ensure that the daemon never reacquires 330 # a control terminal. 331 log.debug('Forking second child.') 332 pid = __fork() 333 if pid != 0: 334 # Original child. Exit. 335 _os._exit(0) 336 337 # This is the second child. Set the umask. 338 log.debug('Setting umask') 339 _os.umask(UMASK) 340 341 # Go to a neutral corner (i.e., the primary file system, so 342 # the daemon doesn't prevent some other file system from being 343 # unmounted). 344 log.debug('Changing working directory to "%s"' % WORKDIR) 345 _os.chdir(WORKDIR) 346 347 # Unless no_close was specified, close all file descriptors. 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 # Main program (for testing) 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