    #!/usr/bin/env python

    """Launch.py

    DESCRIPTION

    Python launch script for the WebKit application server.

    This launch script will run in its standard location in the Webware/WebKit
    directory as well as in a WebKit work directory outside of the Webware tree.

    USAGE

    Launch.py [StartOptions] [AppServer [AppServerOptions]]

    StartOptions:
      -d, --work-dir=...     Set the path to the app server working directory.
                             By default this is the directory containing Lauch.py.
      -w, --webware-dir=...  Set the path to the Webware root directory.
                             By default this is the parent directory.
      -l, --library=...      Other directories to be included in the search path.
                             You may specify this option multiple times.
      -p, --run-profile      Set this to get profiling going (see Profiler.py).
      -o, --log-file=...     Redirect standard output and error to this file.
      -i, --pid-file=...     Set the file path to hold the app server process id.
                             This option is fully supported under Unix only.
      -u, --user=...         The name or uid of the user to run the app server.
                             This option is supported under Unix only.
      -g, --group=...        The name or gid of the group to run the app server.
                             This option is supported under Unix only.

    AppServer:
      The name of the application server module.
      By default, the ThreadedAppServer will be used.

    AppServerOptions:
      Options that shall be passed to the application server.
      For instance, the ThreadedAppServer accepts: start, stop, daemon
      You can also change configuration settings here by passing
      arguments of the form ClassName.SettingName=value

    Please note that the default values for the StartOptions and the AppServer
    can be easily changed at the top of the Launch.py script.

    """

    # FUTURE
    # * This shares a lot of code with ThreadedAppServer.py and AppServer.py.
    #   Try to consolidate these things. The default settings below in the
    #   global variables could go completely into AppServer.config.
    # CREDITS
    # * Contributed to Webware for Python by Chuck Esterbrook
    # * Improved by Ian Bicking
    # * Improved by Christoph Zwerschke

    ## Default options ##

    # You can change the following default values:

    # The path to the app server working directory, if you do not
    # want to use the directory containing this script:
    workDir = None

    # The path to the Webware root directory; by default this will
    # be the parent directory of the directory containing this script:
    webwareDir = None

    # A list of additional directories (usually some libraries)
    # that you want to include into the search path for modules:
    libraryDirs = []

    # To get profiling going, set runProfile = True (see also
    # the description in the docstring of Profiler.py):
    runProfile = False

    # The path to the log file, if you want to redirect the
    # standard output and error to a log file:
    logFile = None

    # The pass to the pid file, if you want to check and terminate
    # a running server by storing its server process id:
    pidFile = None

    # The name or uid of the server process user, if you want
    # to run the server under a different user:
    user = None

    # The name or uid of the server process group, if you want
    # to run the server under a different group:
    group = None

    # The default app server to be used:
    appServer = 'ThreadedAppServer'

    ## Launch app server ##

    import os, sys

    def usage():
        """Print the docstring and exit with error."""
        sys.stdout.write(__doc__)
        sys.exit(2)

    def launchWebKit(appServer=appServer, workDir=None, args=None):
        """Import and launch the specified WebKit app server.

        appServer  -- the name of the WebKit app server module
        workDir -- the server-side work directory of the app server
        args -- other options that will be given to the app server

        """
        # Set up the arguments for the app server:
        if args is None:
            args = []
        if workDir:
            args.append('workdir=' + workDir)
        # Allow for a .py on the server name:
        if appServer[-3:] == '.py':
            appServer = appServer[:-3]
        # Import the app server's main() function:
        try:
            appServerMain = __import__('WebKit.' + appServer, None, None, 'main').main
        except ImportError, e:
            print 'Error: Cannot import the AppServer module.'
            print 'Reason:', str(e)
            sys.exit(1)
        # Set Profiler.startTime if this has not been done already:
        from WebKit import Profiler
        if Profiler.startTime is None:
            from time import time
            Profiler.startTime = time()
        # Run the app server:
        return appServerMain(args) # go!

    ## Main ##

    def main(args=None):
        """Evaluate the command line arguments and call launchWebKit."""
        global workDir, webwareDir, libraryDirs, runProfile, \
            logFile, pidFile, user, group, appServer
        if args is None:
            args = sys.argv[1:]
        # Accept AppServer even if placed before StartOptions:
        if args and not args[0].startswith('-'):
            arg2 = args.pop(0)
        else:
            arg2 = None
        # Accept AppServerOptions that look like StartOptions:
        args1 = []
        args2 = []
        while args:
            arg = args.pop(0)
            if (arg.startswith('--')
                    and 2 < arg.find('.', 2) < arg.find('=', 5)):
                args2.append(arg)
            else:
                args1.append(arg)
        # Get all launch options:
        from getopt import getopt, GetoptError
        try:
            opts, args1 = getopt(args1, 'd:w:l:po:i:u:g:', [
                'work-dir=', 'webware-dir=', 'library=', 'run-profile',
                'log-file=', 'pid-file=', 'user=', 'group='])
        except GetoptError, error:
            print str(error)
            print
            usage()
        for opt, arg in opts:
            if opt in ('-d', '--work-dir'):
                workDir = arg
            elif opt in ('-w', '--webware-dir'):
                webwareDir = arg
            elif opt in ('-l', '--library'):
                libraryDirs.append(arg)
            elif opt in ('-p', '--run-profile'):
                runProfile = True
            elif opt in ('-o', '--log-file'):
                logFile = arg
            elif opt in ('-i', '--pid-file'):
                pidFile = arg
            elif opt in ('-u', '--user'):
                user = arg
            elif opt in ('-g', '--group'):
                group = arg
        if arg2:
            appServer = arg2
        elif args1 and not args1[0].startswith('-') and '=' not in args1[0]:
            appServer = args1.pop(0)
        args = args2 + args1
        # Figure out the group id:
        gid = group
        if gid is not None:
            try:
                gid = int(gid)
            except ValueError:
                try:
                    import grp
                    entry = grp.getgrnam(gid)
                except KeyError:
                    print 'Error: Group %r does not exist.' % gid
                    sys.exit(2)
                except ImportError:
                    print 'Error: Group names are not supported.'
                    sys.exit(2)
                gid = entry[2]
        # Figure out the user id:
        uid = user
        if uid is not None:
            try:
                uid = int(uid)
            except ValueError:
                try:
                    import pwd
                    entry = pwd.getpwnam(uid)
                except KeyError:
                    print 'Error: User %r does not exist.' % uid
                    sys.exit(2)
                except ImportError:
                    print 'Error: User names are not supported.'
                    sys.exit(2)
                if not gid:
                    gid = entry[3]
                uid = entry[2]
        # Figure out the work directory and make it the current directory:
        if workDir:
            workDir = os.path.expanduser(workDir)
        else:
            scriptName = sys.argv and sys.argv[0]
            if not scriptName or scriptName == '-c':
                scriptName = 'Launch.py'
            workDir = os.path.dirname(os.path.abspath(scriptName))
        try:
            os.chdir(workDir)
        except OSError, error:
            print 'Error: Could not set working directory.'
            print 'The path %r cannot be used.' % workDir
            print error.strerror
            print 'Check the --work-dir option.'
            sys.exit(1)
        workDir = os.curdir
        # Expand user components in directories:
        if webwareDir:
            webwareDir = os.path.expanduser(webwareDir)
        else:
            webwareDir = os.pardir
        if libraryDirs:
            libraryDirs = map(os.path.expanduser, libraryDirs)
        # If this module is inside a package, make it a standalone module,
        # because otherwise the package path will be used for imports, too:
        global __name__, __package__
        name = __name__.rsplit('.', 1)[-1]
        if name != __name__:
            sys.modules[name] = sys.modules[__name__]
            del sys.modules[__name__]
            __name__ = name
            __package__ = None
        # Check the validity of the Webware directory:
        sysPath = sys.path # memorize the standard Python search path
        sys.path = [webwareDir] # now include only the Webware directory
        try: # check whether Webware is really located here
            from Properties import name as webwareName
            from WebKit.Properties import name as webKitName
        except ImportError:
            webwareName = None
        if webwareName != 'Webware for Python' or webKitName != 'WebKit':
            print 'Error: Cannot find the Webware directory.'
            print 'The path %r seems to be wrong.' % webwareDir
            print 'Check the --webware-dir option.'
            sys.exit(1)
        if not os.path.exists(os.path.join(webwareDir, 'install.log')):
            print 'Error: Webware has not been installed.'
            print 'Please run install.py in the Webware directory:'
            print '> cd', os.path.abspath(webwareDir)
            print '> python install.py'
            sys.exit(1)
        # Now assemble a new clean Python search path:
        path = [] # the new search path will be collected here
        webKitDir = os.path.abspath(os.path.join(webwareDir, 'WebKit'))
        for p in [workDir, webwareDir] + libraryDirs + sysPath:
            if not p:
                continue # do not include the empty ("current") directory
            p = os.path.abspath(p)
            if p == webKitDir or p in path or not os.path.exists(p):
                continue # do not include WebKit and duplicates
            path.append(p)
        sys.path = path # set the new search path
        # Prepare the arguments for launchWebKit:
        args = (appServer, workDir, args)
        # Handle special case where app server shall be stopped:
        if 'stop' in args[2]:
            print 'Stopping WebKit.%s...' % appServer
            errorlevel = launchWebKit(*args)
            if not errorlevel:
                if pidFile and os.path.exists(pidFile):
                    try:
                        os.remove(pidFile)
                    except Exception:
                        print 'The pid file could not be removed.'
                        print
            sys.exit(errorlevel)
        # Handle the pid file:
        if pidFile:
            pidFile = os.path.expanduser(pidFile)
            # Read the old pid file:
            try:
                pid = int(open(pidFile).read())
            except Exception:
                pid = None
            if pid is not None:
                print 'According to the pid file, the server is still running.'
                # Try to kill an already running server:
                killed = False
                try:
                    from signal import SIGTERM, SIGKILL
                    print 'Trying to terminate the server with pid %d...' % pid
                    os.kill(pid, SIGTERM)
                except OSError, error:
                    from errno import ESRCH
                    if error.errno == ESRCH: # no such process
                        print 'The pid file was stale, continuing with startup...'
                        killed = True
                    else:
                        print 'Cannot terminate server with pid %d.' % pid
                        print error.strerror
                        sys.exit(1)
                except (ImportError, AttributeError):
                    print 'Cannot check or terminate server with pid %d.' % pid
                    sys.exit(1)
                if not killed:
                    from time import sleep
                    try:
                        for i in range(100):
                            sleep(0.1)
                            os.kill(pid, SIGTERM)
                    except OSError, error:
                        from errno import ESRCH
                        if error.errno == ESRCH:
                            print 'Server with pid %d has been terminated.' % pid
                            killed = True
                if not killed:
                    try:
                        for i in range(100):
                            sleep(0.1)
                            os.kill(pid, SIGKILL)
                    except OSError, error:
                        from errno import ESRCH
                        if error.errno == ESRCH:
                            print 'Server with pid %d has been killed by force.' % pid
                            killed = True
                if not killed:
                    print 'Server with pid %d cannot be terminated.' % pid
                    sys.exit(1)
            # Write a new pid file:
            try:
                open(pidFile, 'w').write(str(os.getpid()))
            except Exception:
                print 'The pid file %r could not be written.' % pidFile
                sys.exit(1)
        olduid = oldgid = stdout = stderr = log = None
        errorlevel = 1
        try:
            # Change server process group:
            if gid is not None:
                try:
                    oldgid = os.getgid()
                    if gid != oldgid:
                        os.setgid(gid)
                        if group:
                            print 'Changed server process group to %r.' % group
                    else:
                        oldgid = None
                except Exception:
                    if group:
                        print 'Could not set server process group to %r.' % group
                        oldgid = None
                        sys.exit(1)
            # Change server process user:
            if uid is not None:
                try:
                    olduid = os.getuid()
                    if uid != olduid:
                        os.setuid(uid)
                        print 'Changed server process user to %r.' % user
                    else:
                        olduid = None
                except Exception:
                    print 'Could not change server process user to %r.' % user
                    olduid = None
                    sys.exit(1)
            msg = 'WebKit.' + appServer
            if args[2]:
                msg = '%s %s' % (msg, ' '.join(args[2]))
            else:
                msg = 'Starting %s...' % msg
            print msg
            # Handle the log file:
            if logFile:
                logFile = os.path.expanduser(logFile)
                try:
                    log = open(logFile, 'a', 1) # append, line buffered mode
                    print 'Output has been redirected to %r...' % logFile
                    stdout, stderr = sys.stdout, sys.stderr
                    sys.stdout = sys.stderr = log
                except IOError, error:
                    print 'Cannot redirect output to %r.' % logFile
                    print error.strerror
                    log = None
                    sys.exit(1)
            else:
                print
            # Set up a reference to our profiler so apps can import and use it:
            from WebKit import Profiler
            if Profiler.startTime is None:
                from time import time
                Profiler.startTime = time()
            # Now start the app server:
            if runProfile:
                print ('Profiling is on. '
                    'See docstring in Profiler.py for more info.')
                print
                try:
                    from cProfile import Profile
                except ImportError:
                    from profile import Profile
                profiler = Profile()
                Profiler.profiler = profiler
                errorlevel = Profiler.runCall(launchWebKit, *args)
                print
                print 'Writing profile stats to %s...' % Profiler.statsFilename
                Profiler.dumpStats()
                print 'WARNING: Applications run much slower when profiled,'
                print 'so turn off profiling in Launch.py when you are finished.'
            else:
                errorlevel = launchWebKit(*args)
        finally:
            print
            # Close the log file properly:
            if log:
                sys.stdout, sys.stderr = stdout, stderr
                log.close()
            # Restore server process group and user.
            # Note that because we changed the real group and
            # user id of the process (most secure thing), we
            # cannot change them back now, but we try anyway:
            if oldgid is not None:
                try:
                    os.setgid(oldgid)
                except Exception:
                    pass
                else:
                    oldgid = None
            if olduid is not None:
                try:
                    os.setuid(olduid)
                except Exception:
                    pass
                else:
                    olduid = None
            # Remove the pid file again.
            # Note that this may fail when the group or user id
            # has been changed, but we try anyway:
            if pidFile and os.path.exists(pidFile):
                try:
                    os.remove(pidFile)
                except Exception:
                    if oldgid is None and olduid is None:
                        print 'The pid file could not be removed.'
                        print
        sys.exit(errorlevel)

    if __name__ == '__main__':
        main()