| Trees | Indices | Help |
|
|---|
|
|
1 # -*- coding: utf-8 -*-
2 """Contains routines required to initialize mrv"""
3 import os
4 import sys
5 import subprocess
6 from mrv.path import make_path, BasePath
7 import logging
8 import optparse
9
10 log = logging.getLogger("mrv.cmd.base")
11
12 __docformat__ = "restructuredtext"
13
14 __all__ = [ 'is_supported_maya_version', 'python_version_of', 'parse_maya_version', 'update_env_path',
15 'maya_location', 'update_maya_environment', 'exec_python_interpreter', 'uses_mayapy',
16 'exec_maya_binary', 'available_maya_versions', 'python_executable', 'find_mrv_script',
17 'log_exception', 'SpawnedHelpFormatter', 'SpawnedOptionParser', 'SpawnedCommand']
18
19 #{ Globals
20 maya_to_py_version_map = {
21 8.5 : 2.4,
22 2008: 2.5,
23 2009: 2.5,
24 2010: 2.6,
25 2011: 2.6,
26 2012: 2.6
27 }
35 """:return: True if version is a supported maya version
36 :param version: float which is either 8.5 or 2008 to 20XX"""
37 if version == 8.5:
38 return True
39
40 return str(version)[:2] == "20"
41
43 """:return: True if the executable is mayapy"""
44 try:
45 mayapy_maya_version()
46 return True
47 except EnvironmentError:
48 return False
49 # END handle exceptions
52 """:return: float representing the maya version of the currently running
53 mayapy interpreter.
54 :raise EnvironmentError: If called from a 'normal' python interpreter"""
55 if 'maya' not in sys.executable.lower():
56 raise EnvironmentError("Not running mayapy")
57 # END quick first check
58
59 exec_path = make_path(os.path.realpath(sys.executable)) # Maya is capitalized on windows
60 try:
61 version_token = [ t[4:] for t in exec_path.splitall() if t.lower().startswith('maya') ][0]
62 except IndexError:
63 raise EnvironmentError("Not running mayapy or invalid path mayapy path: %s" % exec_path)
64 # END handle errors
65
66 if version_token.endswith('-x64'):
67 version_token = version_token[:-4]
68 # END handle 64 bit paths
69
70 return float(version_token)
71
73 """:return: tuple(bool, version) tuple of bool indicating whether the version could
74 be parsed and was valid, and a float representing the parsed or default version.
75 :param default: The desired default maya version"""
76 parsed_arg = False
77 version = default
78 try:
79 candidate = float(arg)
80 if is_supported_maya_version(candidate):
81 parsed_arg, version = True, candidate
82 else:
83 pass
84 # in that case, we don't claim the arg and just use the default
85 # END handle candidate
86 except ValueError:
87 pass
88 # END exception handling
89
90 return parsed_arg, version
91
93 """:return: python version matching the given maya version
94 :raise EnvironmentError: If there is no known matching python version"""
95 try:
96 return maya_to_py_version_map[maya_version]
97 except KeyError:
98 raise EnvironmentError("Do not know python version matching the given maya version %g" % maya_version)
99
101 """Set the given env_var to the given value, but append the existing value
102 to it using the system path separator
103
104 :param append: if True, value will be appended to existing values, otherwise it will
105 be prepended"""
106 curval = environment.get(env_var, None)
107 # rule out empty strings
108 if curval:
109 if append:
110 value = curval + os.pathsep + value
111 else:
112 value = value + os.pathsep + curval
113 # END handle append
114 # END handle existing value
115 environment[env_var] = value
116
118 """:return: list of installed maya versions which are locally available -
119 they can be used in methods that require the maya_version to be given.
120 Versions are ordered such that the latest version is given last."""
121 versions = list()
122 for version_candidate in sorted(maya_to_py_version_map.keys()):
123 try:
124 loc = maya_location(version_candidate)
125 versions.append(version_candidate)
126 except Exception:
127 pass
128 # END check maya location
129 # END for each version
130 return versions
131
133 """:return: string path to the existing maya installation directory for the
134 given maya version
135 :raise EnvironmentError: if it was not found"""
136 mayaroot = None
137 suffix = ''
138
139 if sys.platform.startswith('linux'):
140 mayaroot = "/usr/autodesk/maya"
141 if os.path.isdir('/lib64'):
142 suffix = "-x64"
143 # END handle 64 bit systems
144 elif sys.platform == 'darwin':
145 mayaroot = "/Applications/Autodesk/maya"
146 elif sys.platform.startswith('win'):
147 # try to find it in all kinds of program files, prefer 64 bit versions
148 tried_paths = list()
149 for envvar in ('PROGRAMW6432', 'PROGRAMFILES','PROGRAMFILES(X86)'):
150 if envvar not in os.environ:
151 continue
152 basepath = make_path(os.environ[envvar]) / "Autodesk"
153 if basepath.isdir():
154 mayaroot = basepath / 'Maya'
155 break
156 # END if we have found Autodesk installations
157 tried_paths.append(basepath)
158 # END for each envvar
159 if mayaroot is None:
160 raise EnvironmentError("Could not find any maya installation, searched %s" % (', '.join(tried_paths)))
161 # END os specific adjustments
162
163 if mayaroot is None:
164 raise EnvironmentError("Current platform %r is unsupported" % sys.platform)
165 # END assure existance of maya root
166
167 mayalocation = "%s%g%s" % (mayaroot, maya_version, suffix)
168
169 # OSX special handling
170 if sys.platform == 'darwin':
171 mayalocation=os.path.join(mayalocation, 'Maya.app', 'Contents')
172
173 if not os.path.isdir(mayalocation):
174 raise EnvironmentError("Could not find maya installation at %r" % mayalocation)
175 # END verfy maya location
176
177 return mayalocation
178
180 """Configure os.environ to allow Maya to run in standalone mode
181 :param maya_version: The maya version to prepare to run, either 8.5 or 2008 to
182 20XX. This requires the respective maya version to be installed in a default location.
183 :raise EnvironmentError: If the platform is unsupported or if the maya installation could not be found"""
184 py_version = python_version_of(maya_version)
185
186 pylibdir = None
187 envppath = "PYTHONPATH"
188
189 if sys.platform.startswith('linux'):
190 pylibdir = "lib"
191 elif sys.platform == 'darwin':
192 pylibdir = "Frameworks/Python.framework/Versions/Current/lib"
193 elif sys.platform.startswith('win'):
194 pylibdir = "Python"
195 # END os specific adjustments
196
197
198 # GET MAYA LOCATION
199 ###################
200 mayalocation = maya_location(maya_version)
201
202 if not os.path.isdir(mayalocation):
203 raise EnvironmentError("Could not find maya installation at %r" % mayalocation)
204 # END verfy maya location
205
206
207 env = os.environ
208
209 # ADJUST LD_LIBRARY_PATH or PATH
210 ################################
211 # Note: if you need something like LD_PRELOAD or equivalent, add the respective
212 # variables to the environment of this process before starting it
213 if sys.platform.startswith('linux'):
214 envld = "LD_LIBRARY_PATH"
215 ldpath = os.path.join(mayalocation, 'lib')
216 update_env_path(env, envld, ldpath)
217 elif sys.platform == 'darwin':
218 # adjust maya location to point to the actual directtoy
219 dldpath = os.path.join(mayalocation, 'MacOS')
220 update_env_path(env, "DYLD_LIBRARY_PATH", dldpath)
221
222 dldframeworkpath = os.path.join(mayalocation, 'Frameworks')
223 update_env_path(env, "DYLD_FRAMEWORK_PATH", dldframeworkpath)
224
225 env['MAYA_NO_BUNDLE_RESOURCES'] = "1"
226
227 # on osx, python will only use the main frameworks path and ignore
228 # its own sitelibraries. We put them onto the PYTHONPATH for that reason
229 # MayaRV will take care of the initialization
230 ppath = "/Library/Python/%s/site-packages" % py_version
231 update_env_path(env, envppath, ppath, append=True)
232
233 elif sys.platform.startswith('win'):
234 mayadll = os.path.join(mayalocation, 'bin')
235 mayapydll = os.path.join(mayalocation, 'Python', 'DLLs')
236 update_env_path(env, 'PATH', mayadll+os.pathsep+mayapydll, append=False)
237 else:
238 raise EnvironmentError("Current platform %s is unsupported" % sys.platform)
239 # END handle os's
240
241
242 # ADJUST PYTHON PATH
243 ####################
244 # root project is already in the path, we add additional paths as well
245 ospd = os.path.dirname
246 if not sys.platform.startswith('win'):
247 ppath = os.path.join(mayalocation, pylibdir, "python%s"%py_version, "site-packages")
248 else:
249 ppath = os.path.join(mayalocation, pylibdir, "lib", "site-packages")
250 # END windows special handling
251
252 # don't prepend, otherwise system-interpreter mrv versions will not be able
253 # to override the possibly existing mrv version installed in maya.
254 update_env_path(env, envppath, ppath, append=True)
255
256 # SET MAYA LOCATION
257 ###################
258 # its important to do it here as osx adjusts it
259 env['MAYA_LOCATION'] = mayalocation
260
261 # export the actual maya version to allow scripts to pick it up even before maya is launched
262 env['MRV_MAYA_VERSION'] = "%g" % maya_version
263
265 """Enclose arguments in quotes if they contain spaces ... on windows only
266 :return: tuple of possibly modified arguments
267
268 :todo: remove this function, its unused"""
269 if not sys.platform.startswith('win'):
270 return args
271
272 newargs = list()
273 for arg in args:
274 if ' ' in arg:
275 arg = '"%s"' % arg
276 # END put quotes around strings with spaces
277 newargs.append(arg)
278 # END for each arg
279 return tuple(newargs)
280
282 """:return: possibly adjusted path to executable in order to allow its execution
283 This currently only kicks in on windows as we can't handle spaces properly.
284
285 :note: Will change working dir
286 :todo: remove this function, its unused"""
287 if not sys.platform.startswith('win'):
288 return executable
289
290 # execv appears to call the shell, hence we make sure we handle whitespaecs
291 # in the path, which usually happens on windows !
292 # Problem here is that it cannot find the executable if it has a space in the
293 # path as it will split it, and if quotes are put around, it can't find
294 # it either. Hence we chdir into it and use a relative path
295 if ' ' in executable:
296 os.chdir(os.path.dirname(executable))
297 executable = os.path.basename(executable)
298 # END handle freakin' spaces
299 return executable
300
302 """Intialize MRV up to the point where we can replace this process with the
303 one we prepared
304
305 :param args: commandline arguments excluding the executable ( usually first arg )
306 :return: tuple(use_this_interpreter, maya_version, args) tuple of Bool, maya_version, and the remaining args
307 The boolean indicates whether we have to reuse this interpreter, as it is mayapy"""
308 # see if first argument is the maya version
309 maya_version=None
310 if args:
311 parsed_successfully, maya_version = parse_maya_version(args[0], default=None)
312 if parsed_successfully:
313 # if the user wants a specific maya version, he should get it no matter what
314 args = args[1:]
315 # END cut version arg
316 # END if there are args at all
317
318 # choose the newest available maya version if none was specified
319 if maya_version is None:
320 # If there is no version given, and we are in mayapy, we use the maypy
321 # version. Otherwise we use the newest available version
322 if uses_mayapy():
323 maya_version = mayapy_maya_version()
324 # in that case, we have a valid maya environment already. This also means
325 # that we must use this interpreter !
326 return (True, maya_version, tuple(args))
327 else:
328 versions = available_maya_versions()
329 if versions:
330 maya_version = versions[-1]
331 log.info("Using newest available maya version: %g" % maya_version)
332 # END set latest
333 # END handle maya version
334 # END set maya version
335
336 # The user has specified a maya version to start. As mayapy sets up everything in
337 # a distinctive way, we are kind of too late to alter that.
338 # Hence we must prevent the user from starting different maya versions than
339 # the one defined by mayapy.
340 # NOTE: If we use mayapy, the things mentioned above are an issue. If we
341 # use the delivered python-bin (linux) or Python (osx) executables, this
342 # is not an issue. This way, its consistent among all platforms though as
343 # windows always uses mayapy.
344 if uses_mayapy():
345 mayapy_version = mayapy_maya_version()
346 if mayapy_version != maya_version:
347 raise EnvironmentError("If using mayapy, you cannot run any other maya version than the one mayapy uses: %g" % mayapy_version)
348 # END assert version
349
350 if maya_version is None:
351 raise EnvironmentError("Maya version not specified on the commandline, couldn't find any maya version on this system")
352 # END abort if not installed
353
354 update_maya_environment(maya_version)
355 return (False, maya_version, tuple(args))
356
358 """Perform the actual execution of the executable with the given args.
359 This method does whatever is required to get it right on windows, which is
360 the only reason this method exists !
361
362 :param args: arguments, without the executable as first argument
363 :note: does not return """
364 # on windows we spawn, otherwise we don't get the interactive input right
365 actual_args = (executable, ) + args
366 if sys.platform.startswith('win'):
367 p = subprocess.Popen(actual_args, stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr)
368 sys.exit(p.wait())
369 else:
370 os.execvp(executable, actual_args)
371 # END handle windows
374 """:return: name or path to python executable in this system, deals with
375 linux and windows specials"""
376 if py_version is None:
377 return 'python'
378 # END handle simple case
379
380 py_executable = "python%g" % py_version
381 if sys.platform.startswith('win'):
382 # so, on windows the executables don't have a . in their name, most likely
383 # because windows threats the '.' in a special way as ... anyway.
384 py_executable = "python%g" % (py_version*10)
385 # END win specials
386 return py_executable
387
389 """Find an mrv script of the given name. This method should be used if you
390 want to figure out where the mrv executable with the given name is located.
391 The returned path is either relative or absolute.
392
393 :return: Path to script
394 :raise EnvironmentError: if the executable could not be found
395 :note: Currently it only looks for executables, but handles projects
396 which use mrv as a subproject"""
397 import mrv
398 mrvroot = os.path.dirname(mrv.__file__)
399
400 tried_paths = list()
401 for base in ('', 'ext', mrvroot):
402 for subdir in ('bin', 'doc', os.path.join('test', 'bin')):
403 path = None
404 if base:
405 path = os.path.join(base, subdir, name)
406 else:
407 path = os.path.join(subdir, name)
408 # END handle base
409 if os.path.isfile(path):
410 return make_path(path)
411 tried_paths.append(path)
412 # END for each subdir
413 # END for each base
414
415 raise EnvironmentError("Script named %s not found, looked at %s" % (name, ', '.join(tried_paths)))
416
418 """Replace this process with a python process as determined by the given options.
419 This will either be the respective python interpreter, or mayapy.
420 If it works, the function does not return
421
422 :param args: remaining arguments which should be passed to the process
423 :param maya_version: float indicating the maya version to use
424 :param mayapy_only: If True, only mayapy will be considered for startup.
425 Use this option in case the python interpreter crashes for some reason.
426 :raise EnvironmentError: If no suitable executable could be started"""
427 py_version = python_version_of(maya_version)
428 py_executable = python_executable(py_version)
429
430 args = tuple(args)
431 tried_paths = list()
432 try:
433 if mayapy_only:
434 raise OSError()
435 tried_paths.append(py_executable)
436 _execute(py_executable, args)
437 except OSError:
438 if not mayapy_only:
439 print "Python interpreter named %r not found, trying mayapy ..." % py_executable
440 # END print error message
441 mayalocation = maya_location(maya_version)
442 mayapy_executable = os.path.join(mayalocation, "bin", "mayapy")
443
444 try:
445 tried_paths.append(mayapy_executable)
446 _execute(mayapy_executable, args)
447 except OSError, e:
448 raise EnvironmentError("Could not find suitable python interpreter at paths %s : %s" % (', '.join(tried_paths), e))
449 # END final exception handling
453 """Replace this process with the maya executable as specified by maya_version.
454
455 :param args: The arguments to be provided to maya
456 :param maya_version: Float identifying the maya version to be launched
457 :rase EnvironmentError: if the respective maya version could not be found"""
458 mayalocation = maya_location(maya_version)
459 mayabin = os.path.join(mayalocation, 'bin', 'maya')
460
461 # although execv would work on windows, we use our specialized _execute method
462 # in order to keep things consistent
463 _execute(mayabin, tuple(args))
464
471 """Assures that exceptions result in a logging message.
472 Currently only works with a SpawnedCommand as we need a log instance.
473 On error, the server exits with status 64"""
474 def wrapper(self, *args, **kwargs):
475 try:
476 return func(self, *args, **kwargs)
477 except Exception, e:
478 if self.parser.spawned:
479 self.log.critical("Program %r aborted with a unhandled exception: %s" % (self.k_log_application_id, str(e)), exc_info=True)
480 sys.exit(64)
481 else:
482 raise
483 # END handle sysexit gracefully
484 # END wrapper
485
486 wrapper.__name__ = func.__name__
487 return wrapper
488
489 #} END decorators
490
491 #{ Classes
492
493 -class SpawnedHelpFormatter(optparse.TitledHelpFormatter):
494 """Formatter assuring our help looks good"""
495
497 """Don't wrap the text at all"""
498 if self.parser:
499 text = self.parser.expand_prog_name(text)
500 # END program name expansion if possible
501
502 if self.level == 0:
503 return text
504 lines = text.splitlines(True)
505 return ''.join(' '*self.level + l for l in lines)
506
509
512 """Customized version to ease use of SpawnedCommand
513
514 Initialized with the 'spawned' keyword in addition
515 to the default keywords to prevent a system exit"""
516
518 self.spawned = kwargs.pop('spawned', False)
519 kwargs['formatter'] = SpawnedHelpFormatter()
520 optparse.OptionParser.__init__(self, *args, **kwargs)
521
535 # END options
540 """Implements a command which can be started easily by specifying a class path
541 such as package.cmd.module.CommandClass whose instance should be started in
542 a standalone process.
543
544 The target command must be derived from this class and must implement the
545 'execute' method.
546
547 To use this class, derive from it and change the configuration variables
548 accordingly.
549
550 The instance will always own a logger instance at its member called 'log',
551 the configuration will be applied according to k_log_application_id
552
553 The parser used to parse all options is vailable at its member called 'parser',
554 its set during ``option_parser``
555
556 The instance may also be created within an existing process and
557 executed manually - in that case it will not exit automatically if a
558 serious event occours"""
559
560 #{ Configuration
561 # If not None, the name will be available for printing help text, and other tasks
562 # such as application specific initialization of modules
563 k_log_application_id = None
564
565 # path at which your class is located. It must be derived from SpawnedCommand
566 k_class_path = "package.module.YourClass"
567
568 # An identifier for the version of your command
569 k_version = None
570
571 # Path to the executable
572 _exec_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'bin', 'mrv')
573
574 # If set, its the directory containing your project's info file. It must be set from the
575 # derived class, as it knows its actual position
576 _mrv_info_dir = None
577
578 # additional arguments to pass on to the newly created process
579 _add_args = ['--mrv-no-maya']
580
581 # The usage of your command in BMF
582 # i.e. %prog [options]
583 k_usage = None
584
585 # a short description of your command printed below its usage
586 k_description = None
587
588 # the name of your program's actual executable
589 k_program_name = None
590
591 # File mode creation mask of the daemon.
592 # If None, current one will not be changed
593 _daemon_umask = None
594
595 # Default working directory for the daemon.
596 # If None, the current one will not be changed
597 _daemon_workdir = None
598
599 # Default maximum for the number of available file descriptors that we try to close
600 _daemon_maxfd = 64
601
602 # The standard I/O file descriptors are redirected to /dev/null by default.
603 if (hasattr(os, "devnull")):
604 _daemon_redirect_to = os.devnull
605 else:
606 _daemon_redirect_to = "/dev/null"
607
608 #} END configuration
609
610 __slots__ = ('parser', 'log')
611
612
614 """
615 :param _spawned: If True, default False, we assume we have our own process.
616 Otherwise we will do nothing that would adjust the current process, such as:
617
618 * sys.exit
619 * change configuration of logging system"""
620 try:
621 super(SpawnedCommand, self).__init__(*args, **kwargs)
622 except TypeError:
623 # in python 2.6 and newer, object will not accept to be called with
624 # arguments anymore. Problem is that we don't know whether super is an
625 # object or some mixed in type, which is a design flaw, and which breaks
626 # code as well.
627 pass
628 # END py 2.6 special case
629
630
631 spawned = kwargs.get('_spawned', False)
632 self.parser = SpawnedOptionParser( usage=self.k_usage, version=self.k_version,
633 description=self.k_description, add_help_option=True,
634 prog=self.k_program_name, spawned=spawned)
635 self.log = logging.getLogger(self.k_program_name)
636
637 @classmethod
639 """Spawn a new standalone process of this command type
640 Additional arguments passed to the command process
641
642 :param kwargs: Additional keyword arguments to be passed to Subprocess.Popen,
643 use it to configure your IO
644
645 Returns: Subprocess.Popen instance"""
646 import spcmd
647 margs = [cls._exec_path, spcmd.__file__, cls.k_class_path]
648 margs.extend(args)
649 margs.extend(cls._add_args)
650
651 if os.name == 'nt':
652 margs.insert(0, sys.executable)
653 # END handle windows inabilitiess
654
655 old_mrvinfo_val = None
656 env_mrv_info = 'MRV_INFO_DIR'
657 if cls._mrv_info_dir is not None:
658 old_mrvinfo_val = os.environ.get(env_mrv_info)
659 os.environ[env_mrv_info] = cls._mrv_info_dir
660 #END handle mrv_info
661
662 try:
663 return subprocess.Popen(margs, **kwargs)
664 finally:
665 if old_mrvinfo_val is not None:
666 os.environ[env_mrv_info] = old_mrvinfo_val
667 #END reset prevous value
668 #END handle environment change
669
670 @classmethod
672 """
673 Damonize the spawned command, passing *args to the instanciated command's
674 execute method.
675
676 :return: None in calling process, no return in the daemon
677 as sys.exit will be called.
678 :note: see configuration variables prefixed with _daemon_
679 :note: based on Chad J. Schroeder createDaemon method,
680 see http://code.activestate.com/recipes/278731-creating-a-daemon-the-python-way
681 """
682 if sys.platform.startswith("win"):
683 raise OSError("Cannot daemonize on windows")
684 # END handle operating system
685
686 try:
687 # Fork a child process so the parent can exit. This returns control to
688 # the command-line or shell. It also guarantees that the child will not
689 # be a process group leader, since the child receives a new process ID
690 # and inherits the parent's process group ID. This step is required
691 # to insure that the next call to os.setsid is successful.
692 pid = os.fork()
693 except OSError, e:
694 raise Exception, "%s [%d]" % (e.strerror, e.errno)
695
696 if (pid != 0):
697 # exit() or _exit()?
698 # _exit is like exit(), but it doesn't call any functions registered
699 # with atexit (and on_exit) or any registered signal handlers. It also
700 # closes any open file descriptors. Using exit() may cause all stdio
701 # streams to be flushed twice and any temporary files may be unexpectedly
702 # removed. It's therefore recommended that child branches of a fork()
703 # and the parent branch(es) of a daemon use _exit().
704 return None
705 # END exit
706
707 ##################
708 # The first child.
709 ##################
710 # To become the session leader of this new session and the process group
711 # leader of the new process group, we call os.setsid(). The process is
712 # also guaranteed not to have a controlling terminal.
713 os.setsid()
714
715 # Is ignoring SIGHUP necessary?
716 #
717 # It's often suggested that the SIGHUP signal should be ignored before
718 # the second fork to avoid premature termination of the process. The
719 # reason is that when the first child terminates, all processes, e.g.
720 # the second child, in the orphaned group will be sent a SIGHUP.
721 #
722 # "However, as part of the session management system, there are exactly
723 # two cases where SIGHUP is sent on the death of a process:
724 #
725 # 1) When the process that dies is the session leader of a session that
726 # is attached to a terminal device, SIGHUP is sent to all processes
727 # in the foreground process group of that terminal device.
728 # 2) When the death of a process causes a process group to become
729 # orphaned, and one or more processes in the orphaned group are
730 # stopped, then SIGHUP and SIGCONT are sent to all members of the
731 # orphaned group." [2]
732 #
733 # The first case can be ignored since the child is guaranteed not to have
734 # a controlling terminal. The second case isn't so easy to dismiss.
735 # The process group is orphaned when the first child terminates and
736 # POSIX.1 requires that every STOPPED process in an orphaned process
737 # group be sent a SIGHUP signal followed by a SIGCONT signal. Since the
738 # second child is not STOPPED though, we can safely forego ignoring the
739 # SIGHUP signal. In any case, there are no ill-effects if it is ignored.
740 #
741 # import signal # Set handlers for asynchronous events.
742 # signal.signal(signal.SIGHUP, signal.SIG_IGN)
743
744 try:
745 # Fork a second child and exit immediately to prevent zombies. This
746 # causes the second child process to be orphaned, making the init
747 # process responsible for its cleanup. And, since the first child is
748 # a session leader without a controlling terminal, it's possible for
749 # it to acquire one by opening a terminal in the future (System V-
750 # based systems). This second fork guarantees that the child is no
751 # longer a session leader, preventing the daemon from ever acquiring
752 # a controlling terminal.
753 pid = os.fork() # Fork a second child.
754 except OSError, e:
755 raise Exception, "%s [%d]" % (e.strerror, e.errno)
756
757 if (pid != 0):
758 # exit() or _exit()? See below.
759 os._exit(0) # Exit parent (the first child) of the second child.
760 # END exit second child
761
762 ###################
763 # The second child.
764 ###################
765 # Since the current working directory may be a mounted filesystem, we
766 # avoid the issue of not being able to unmount the filesystem at
767 # shutdown time by changing it to the root directory.
768 if cls._daemon_workdir is not None:
769 os.chdir(cls._daemon_workdir)
770 # END set working dir
771
772 # We probably don't want the file mode creation mask inherited from
773 # the parent, so we give the child complete control over permissions.
774 if cls._daemon_umask is not None:
775 os.umask(cls._daemon_umask)
776 # END set umask
777
778
779
780 # Close all open file descriptors. This prevents the child from keeping
781 # open any file descriptors inherited from the parent. There is a variety
782 # of methods to accomplish this task. Three are listed below.
783 #
784 # Try the system configuration variable, SC_OPEN_MAX, to obtain the maximum
785 # number of open file descriptors to close. If it doesn't exists, use
786 # the default value (configurable).
787 #
788 # try:
789 # maxfd = os.sysconf("SC_OPEN_MAX")
790 # except (AttributeError, ValueError):
791 # maxfd = MAXFD
792 #
793 # OR
794 #
795 # if (os.sysconf_names.has_key("SC_OPEN_MAX")):
796 # maxfd = os.sysconf("SC_OPEN_MAX")
797 # else:
798 # maxfd = MAXFD
799 #
800 # OR
801 #
802 # Use the getrlimit method to retrieve the maximum file descriptor number
803 # that can be opened by this process. If there is not limit on the
804 # resource, use the default value.
805 #
806 import resource # Resource usage information.
807 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
808 if (maxfd == resource.RLIM_INFINITY):
809 maxfd = cls._daemon_maxfd
810
811 debug_daemon = False
812
813 # Iterate through and close all file descriptors.
814 for fd in range(debug_daemon*3, maxfd):
815 try:
816 os.close(fd)
817 except OSError: # ERROR, fd wasn't open to begin with (ignored)
818 pass
819 # END for each fd in range
820
821 # Redirect the standard I/O file descriptors to the specified file. Since
822 # the daemon has no controlling terminal, most daemons redirect stdin,
823 # stdout, and stderr to /dev/null. This is done to prevent side-effects
824 # from reads and writes to the standard I/O file descriptors.
825
826 # This call to open is guaranteed to return the lowest file descriptor,
827 # which will be 0 (stdin), since it was closed above.
828 if not debug_daemon:
829 os.open(cls._daemon_redirect_to, os.O_RDWR) # standard input (0)
830
831 # Duplicate standard input to standard output and standard error.
832 os.dup2(0, 1) # standard output (1)
833 os.dup2(0, 2) # standard error (2)
834 # END handle standard descriptors
835
836 # RUN THE SPAWNED COMMAND
837 #########################
838 cmdinstance = cls(_spawned=True)
839 return cmdinstance._execute(*args)
840
842 """:return: tuple(options, args) tuple of parsed options and remaining args
843 The arguments can be preprocessed"""
844 return options, args
845
847 """internal method handling the basic arguments in a pre-process before
848 calling ``execute``
849
850 We will parse all options, process the default ones and pass on the
851 call to the ``execute`` method"""
852 options, args = self._preprocess_args(*self.option_parser().parse_args(list(args)))
853
854 # make sure we have a default setup at least !
855 logging.basicConfig()
856
857 # call the subclassed method
858 try:
859 return self.execute(options, args)
860 except Exception, e:
861 if self.parser.spawned:
862 sys.stderr.write('%s: %s\n' % (type(e).__name__, str(e)))
863 sys.exit(1)
864 else:
865 raise
866 # END help the user in case he provides invalid options
867
868 #{ Overridable
869 @log_exception
871 """Method implementing the actual functionality of the command
872 :param options: Values instance of the optparse module
873 :param args: remaining positional arguments passed to the process on the commandline
874 :note: if you like to terminate, raise an exception"""
875 pass
876
878 """:return: OptionParser Instance containing all supported options
879 :note: Should be overridden by subclass to add additional options and
880 option groups themselves after calling the base class implementation"""
881 return self.parser
882 #} END needing subclass
883
884
885 #} END classes
886
| Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Tue Apr 19 18:00:19 2011 | http://epydoc.sourceforge.net |