[Zope-Checkins] CVS: Zope/lib/python/Zope/Startup - ZctlLib.py:1.4 __init__.py:1.4 cmdline.py:1.4 datatypes.py:1.4 handlers.py:1.4 options.py:1.2 zopeschema.xml:1.4

Fred L. Drake, Jr. fred@zope.com
Tue, 18 Mar 2003 16:38:20 -0500


Update of /cvs-repository/Zope/lib/python/Zope/Startup
In directory cvs.zope.org:/tmp/cvs-serv27358

Added Files:
	ZctlLib.py __init__.py cmdline.py datatypes.py handlers.py 
	options.py zopeschema.xml 
Log Message:
Merge startup code from the new-install-branch.

=== Zope/lib/python/Zope/Startup/ZctlLib.py 1.3 => 1.4 === (644/744 lines abridged)
--- /dev/null	Tue Mar 18 16:38:19 2003
+++ Zope/lib/python/Zope/Startup/ZctlLib.py	Tue Mar 18 16:37:49 2003
@@ -0,0 +1,741 @@
+##############################################################################
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+    Zope appserver controller.  This is akin to apache's apachectl,
+    except with an interactive interpreter if no commands are specified
+    on the command-line.
+"""
+
+__version__ = '$Revision$'[11:-2]
+
+import cmd
+import getopt
+import os
+import signal
+import sys
+import time
+
+try:
+    import readline
+except:
+    readline = None
+
+from Zope.Startup import getOptions, getOptionDescriptions, configure
+from Zope.Startup.misc import TextBlockFormatter
+from Zope.Startup.misc.lock_file import lock_file
+
+USAGE = """\
+
+zopectl:  Zope appserver controller
+
+Usage:
+    zopectl [-h | --help] [--config=filepath or url] [additional options]
+
+Options:
+    -h or --help       Print this message.  Not compatible with the 'start'
+                       or 'restart' command.
+

[-=- -=- -=- 644 lines omitted -=- -=- -=-]

+            self.cmdqueue.append(' '.join(args))
+            self.cmdqueue.append('EOF')
+
+        self.cmdloop()
+
+def get_pids(filename):
+    for line in open(filename).readlines():
+        pids = line.split()
+        if pids:
+            return [ int(x.strip()) for x in pids ]
+
+def win32kill(pid, sig):
+    # we ignore the signal on win32
+    try:
+        import win32api
+        import pywintypes
+    except:
+        print ("Could not open win32api module, have you installed the "
+               "'win32all' package?")
+        return 1
+    try:
+        handle = win32api.OpenProcess(1, 0, pid)
+    except pywintypes.error, why:
+        # process named by pid not running
+        return 1
+    try:
+        status = win32api.TerminateProcess(handle, 0)
+    except:
+        return 1
+    if status is None:
+        return 0
+    return 1
+
+def kill(pid, sig):
+    try:
+        os.kill(pid, sig)
+    except OSError, why:
+        return 1
+    else:
+        return 0
+
+def cmdquote(cmd):
+    if sys.platform == 'win32':
+        # ugh.  win32 requires the command to be quoted.  unix requires
+        # that the command *not* be quoted.
+        cmd = '"%s"' % cmd
+    return cmd
+
+if sys.platform == 'win32':
+    kill = win32kill


=== Zope/lib/python/Zope/Startup/__init__.py 1.3 => 1.4 ===
--- /dev/null	Tue Mar 18 16:38:20 2003
+++ Zope/lib/python/Zope/Startup/__init__.py	Tue Mar 18 16:37:49 2003
@@ -0,0 +1,275 @@
+##############################################################################
+#
+# Copyright (c) 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+
+""" Startup package.  Responsible for startup configuration of Zope """
+
+import os
+import sys
+import socket
+import re
+
+import ZConfig
+
+from cmdline import getOptions, getOptionDescriptions # exported
+
+# global to hold config structures
+_schema = None
+_configuration = None
+
+def getConfiguration():
+    return _configuration
+
+def getSchema():
+    global _schema
+    if _schema is None:
+        here = os.path.dirname(__file__)
+        path = os.path.join(here, 'zopeschema.xml')
+        _schema = ZConfig.loadSchema(path)
+    return _schema
+
+def configure(config_location, options):
+    global _configuration
+    import handlers
+    schema = getSchema()
+    _configuration, handler = ZConfig.loadConfig(schema, config_location)
+    handlers.handleConfig(_configuration, handler, options)
+    return _configuration
+
+def start_zope(cfg):
+    # set up our initial logging environment (log everything to stderr
+    # if we're not in debug mode).
+    import zLOG
+
+    # don't initialize the event logger from the environment
+    zLOG._call_initialize = 0
+
+    from zLOG.LogHandlers import StartupHandler
+
+    # we log events to the root logger, which is backed by a
+    # "StartupHandler" log handler.  The "StartupHandler" outputs to
+    # stderr but also buffers log messages.  When the "real" loggers
+    # are set up, we flush accumulated messages in StartupHandler's
+    # buffers to the real logger.
+    startup_handler = StartupHandler(sys.stderr)
+    formatter = zLOG.EventLogger.formatters['file']
+    startup_handler.setFormatter(formatter)
+    if not cfg.debug_mode:
+        # prevent startup messages from going to stderr if we're not
+        # in debug mode
+        if os.path.exists('/dev/null'): # unix
+            devnull = '/dev/null'
+        else: # win32
+            devnull = 'nul:'
+        startup_handler = StartupHandler(open(devnull, 'w'))
+
+    # set up our event logger temporarily with a startup handler
+    event_logger = zLOG.EventLogger.EventLogger.logger
+    event_logger.addHandler(startup_handler)
+
+    # set a locale if one has been specified in the config
+    if cfg.locale:
+        do_locale(cfg.locale)
+
+    # Increase the number of threads
+    import ZServer
+    ZServer.setNumberOfThreads(cfg.zserver_threads)
+
+    # Start ZServer servers before we setuid so we can bind to low ports:
+    socket_err = (
+        'There was a problem starting a server of type "%s". '
+        'This may mean that your user does not have permission to '
+        'bind to the port which the server is trying to use or the '
+        'port may already be in use by another application.'
+        )
+    servers = []
+    for server in cfg.servers:
+        # create the server from the server factory
+        # set up in the config
+        try:
+            servers.append(server.create())
+        except socket.error:
+            raise ZConfig.ConfigurationError(socket_err
+                                             % server.servertype())
+    cfg.servers = servers
+
+    # do stuff that only applies to posix platforms (setuid, daemonizing)
+    if os.name == 'posix':
+        do_posix_stuff(cfg)
+
+    # Import Zope
+    import Zope
+    Zope.startup()
+
+    if not cfg.zserver_read_only_mode:
+        # lock_file is used for the benefit of zctl, so it can tell whether
+        # Zope is already running before attempting to fire it off again.
+        # We aren't concerned about locking the file to protect against
+        # other Zope instances running from our CLIENT_HOME, we just
+        # try to lock the file to signal that zctl should not try to
+        # start Zope if *it* can't lock the file; we don't panic
+        # if we can't lock it.
+        # we need a separate lock file because on win32, locks are not
+        # advisory, otherwise we would just use the pid file
+        from Zope.Startup.misc.lock_file import lock_file
+        lock_filename = cfg.lock_filename
+        try:
+            if os.path.exists(lock_filename):
+                os.unlink(lock_filename)
+            LOCK_FILE = open(lock_filename, 'w')
+            lock_file(LOCK_FILE)
+        except IOError:
+            pass
+
+        # Now that we've successfully setuid'd, we can log to
+        # somewhere other than stderr.  Activate the configured logs:
+        if cfg.access is not None:
+            cfg.access()
+        if cfg.trace is not None:
+            cfg.trace()
+
+        # flush buffered startup messages to event logger
+        event_logger.removeHandler(startup_handler)
+        if cfg.eventlog is not None:
+            logger = cfg.eventlog()
+            startup_handler.flushBufferTo(logger)
+
+    zLOG.LOG('Zope', zLOG.INFO, 'Ready to handle requests')
+
+    # Start Medusa, Ye Hass!
+    try:
+        import Lifetime
+        Lifetime.loop()
+        sys.exit(ZServer.exit_code)
+    finally:
+        if not cfg.zserver_read_only_mode:
+            try:
+                os.unlink(cfg.pid_filename)
+            except OSError:
+                pass
+            try:
+                LOCK_FILE.close()
+                os.unlink(lock_filename)
+            except OSError:
+                pass
+
+def _warn_nobody():
+    import zLOG
+    zLOG.LOG("Zope", zLOG.INFO, ("Running Zope as 'nobody' can compromise "
+                                 "your Zope files; consider using a "
+                                 "dedicated user account for Zope"))
+
+def check_python_version():
+    # check for Python version
+    python_version = sys.version.split()[0]
+    optimum_version = '2.2.2'
+    if python_version < '2.2':
+        raise ZConfig.ConfigurationError(
+            'Invalid python version ' + python_version)
+    if python_version[:3] == '2.2':
+        if python_version[4:5] < '2':
+            err = ('You are running Python version %s.  This Python version '
+                   'has known bugs that may cause Zope to run improperly. '
+                   'Consider upgrading to Python %s\n' %
+                   (python_version, optimum_version))
+            sys.stderr.write(err)
+
+def do_posix_stuff(cfg):
+    import zLOG
+    import zdaemon
+    import pwd
+    from Signals import Signals
+    Signals.registerZopeSignals()
+
+    # Warn if we were started as nobody.
+    if os.getuid():
+        if pwd.getpwuid(os.getuid())[0] == 'nobody':
+            _warn_nobody()
+
+    # Drop root privileges if we have them, and do some sanity checking
+    # to make sure we're not starting with an obviously insecure setup.
+    if os.getuid() == 0:
+        UID  = cfg.effective_user
+        if UID == None:
+            msg = ('A user was not specified to setuid to; fix this to '
+                   'start as root (change the effective_user directive '
+                   'in zope.conf)')
+            zLOG.LOG('Zope', zLOG.PANIC, msg)
+            raise ZConfig.ConfigurationError(msg)
+        # stuff about client home faults removed (real effective user
+        # support now)
+        try:
+            UID = int(UID)
+        except (TypeError, ValueError):
+            pass
+        gid = None
+        if isinstance(UID, str):
+            uid = pwd.getpwnam(UID)[2]
+            gid = pwd.getpwnam(UID)[3]
+        elif isinstance(UID, int):
+            uid = pwd.getpwuid(UID)[2]
+            gid = pwd.getpwuid(UID)[3]
+            UID = pwd.getpwuid(UID)[0]
+        else:
+            zLOG.LOG("Zope", zLOG.ERROR, ("Can't find UID %s" % UID))
+            raise ZConfig.ConfigurationError('Cant find UID %s' % UID)
+        if UID == 'nobody':
+            _warn_nobody()
+        if gid is not None:
+            try:
+                import initgroups
+                initgroups.initgroups(UID, gid)
+                os.setgid(gid)
+            except OSError:
+                zLOG.LOG("Zope", zLOG.INFO,
+                         'Could not set group id of effective user',
+                         error=sys.exc_info())
+        os.setuid(uid)
+        zLOG.LOG("Zope", zLOG.INFO,
+                 'Set effective user to "%s"' % UID)
+
+    if not cfg.debug_mode:
+        # umask is silly, blame POSIX.  We have to set it to get its value.
+        current_umask = os.umask(0)
+        os.umask(current_umask)
+        if current_umask != 077:
+            current_umask = '%03o' % current_umask
+            zLOG.LOG("Zope", zLOG.INFO, (
+                'Your umask of %s may be too permissive; for the security of '
+                'your Zope data, it is recommended you use 077' % current_umask
+                ))
+
+
+def do_locale(locale_id):
+    # workaround to allow unicode encoding conversions in DTML
+    import codecs
+    dummy = codecs.lookup('iso-8859-1')
+
+    if locale_id is not None:
+        try:
+            import locale
+        except:
+            raise ZConfig.ConfigurationError(
+                'The locale module could not be imported.\n'
+                'To use localization options, you must ensure\n'
+                'that the locale module is compiled into your\n'
+                'Python installation.'
+                )
+        try:
+            locale.setlocale(locale.LC_ALL, locale_id)
+        except:
+            raise ZConfig.ConfigurationError(
+                'The specified locale "%s" is not supported by your system.\n'
+                'See your operating system documentation for more\n'
+                'information on locale support.' % locale_id
+                )


=== Zope/lib/python/Zope/Startup/cmdline.py 1.3 => 1.4 ===
--- /dev/null	Tue Mar 18 16:38:20 2003
+++ Zope/lib/python/Zope/Startup/cmdline.py	Tue Mar 18 16:37:49 2003
@@ -0,0 +1,172 @@
+import getopt
+
+def getOptionDescriptions():
+    """ Temporary implementation """
+
+    short, long = getOptions()
+    n = 0
+    short_d = {}
+    long_d = {}
+
+    last = 0
+    n = 0
+
+    print short
+
+    if short:
+        while 1:
+            try:
+                opt = short[n]
+            except IndexError:
+                next = None
+            try:
+                next = short[n+1]
+            except IndexError:
+                next = None
+            if next == ':':
+                short_d[opt] = 1
+                n = n + 2
+            else:
+                if next is None and short.endswith(':'):
+                    short_d[opt] = 1
+                else:
+                    short_d[opt] = 0
+                n = n + 1
+            if next is None:
+                break
+
+    for opt in long:
+        if opt.endswith('='):
+            long_d[opt[:-1]] = 1
+        else:
+            long_d[opt] = 0
+
+    opts = []
+
+    short_l = short_d.items()
+    short_l.sort()
+    for k, v in short_l:
+        opts.append('    -%s%s' % (k, (v and ' <value>' or '')))
+
+    long_l = long_d.items()
+    long_l.sort()
+    for k, v in long_l:
+        opts.append('    --%s%s' % (k, (v and ' <value>' or '')))
+    return '\n'.join(opts)
+
+def getOptions():
+    short = 'Z:t:i:D:a:d:u:L:l:M:E:Xw:W:f:p:F:m:'
+    long  = [
+             'zserver-threads=',
+             'python-check-interval=',
+             'debug-mode=',
+             'ip-address=',
+             'dns-ip-address=',
+             'effective-user=',
+             'locale=',
+             'access-log=',
+             'trace-log=',
+             'event-log=',
+             'disable-servers',
+             'http-server=',
+             'webdav-source-server=',
+# XXX need to finish these
+#             'ftp-server=',
+#             'pcgi-server=',
+#             'fcgi-server=',
+#             'monitor-server=',
+#             'icp-server=',
+             ]
+    return short, long
+
+class CommandLineOptions:
+
+    def __call__(self, cfg, options):
+        import Zope.Startup.datatypes
+        import Zope.Startup.handlers
+        import ZConfig.datatypes
+
+        short, long = getOptions()
+        opts, args = getopt.getopt(options, short, long)
+
+        for k, v in opts:
+            # set up data that servers may rely on
+            if k in ('-t', '--zserver-threads'):
+                cfg.zserver_threads = int(v)
+            elif k in ('-i', '--python-check-interval'):
+                cfg.python_check_interval = int(v)
+            elif k in ('-D', '--debug-mode'):
+                datatype = ZConfig.datatypes.asBoolean
+                handler = Zope.Startup.handlers.debug_mode
+                v = datatype(v)
+                handler(v)
+                cfg.debug_mode = v
+            elif k in ('-i', '--ip-address'):
+                datatype = ZConfig.datatypes.IpaddrOrHostname()
+                cfg.ip_address = datatype(v)
+            elif k in ('-d', '--dns-ip-address'):
+                datatype = ZConfig.datatypes.IpaddrOrHostname()
+                cfg.dns_ip_address = datatype(v)
+            elif k in ('-u', '--effective-user'):
+                cfg.effective_user = v
+            elif k in ('-L', '--locale'):
+                datatype = ZConfig.datatypes.check_locale
+                cfg.locale = datatype(v)
+            elif k in ('-l', '--access-log'):
+                cfg.access = default_logger('access', v,
+                                            '%(message)s',
+                                            '%Y-%m-%dT%H:%M:%S')
+            elif k in ('-M', '--trace-log'):
+                cfg.trace = default_logger('trace', v,
+                                           '%(message)s',
+                                           '%Y-%m-%dT%H:%M:%S')
+            elif k in ('-E', '--event-log'):
+                cfg.trace = default_logger('event', v,
+                                           '------\n%(asctime)s %(message)s',
+                                           '%Y-%m-%dT%H:%M:%S')
+            elif k in ('-X', '--disable-servers'):
+                cfg.servers = []
+            else:
+                # continue if we've not matched, otherwise
+                # fall through to the pop statement below
+                continue
+
+            opts.pop(0) # pop non-server data from opts
+
+        factory = Zope.Startup.handlers.ServerFactoryFactory(cfg)
+
+        for k, v in opts:
+            # set up server data from what's left in opts,
+            # using repopulated cfg
+            if k in ('-w', '--http-server'):
+                datatype = ZConfig.datatypes.inet_address
+                host, port = datatype(v)
+                section = dummy()
+                section.ports = [(host, port)]
+                section.force_connection_close = 0
+                cfg.servers.append(['http_server',
+                                    factory.http_server(section)[0]])
+            if k in ('-W', '--webdav-source-server'):
+                datatype = ZConfig.datatypes.inet_address
+                host, port = datatype(v)
+                section = dummy()
+                section.ports = [(host, port)]
+                section.force_connection_close = 0
+                cfg.servers.append(['webdav_source_server',
+                                    factory.webdav_source_server(section)[0]])
+
+class dummy:
+    # used as a namespace generator
+    pass
+            
+def default_logger(name, file, format, dateformat):
+    import Zope.Startup.datatypes
+    logger = dummy()
+    logger.level = 20
+    handler = dummy()
+    handler.file = file
+    handler.format = format
+    handler.dateformat = dateformat
+    handler.level = 20
+    handlers = [Zope.Startup.datatypes.file_handler(handler)]
+    return Zope.Startup.datatypes.LoggerWrapper(name, 20, handlers)


=== Zope/lib/python/Zope/Startup/datatypes.py 1.3 => 1.4 ===
--- /dev/null	Tue Mar 18 16:38:20 2003
+++ Zope/lib/python/Zope/Startup/datatypes.py	Tue Mar 18 16:37:49 2003
@@ -0,0 +1,91 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+
+"""Datatypes for the Zope schema for use with ZConfig."""
+
+import os
+
+# generic datatypes
+
+def security_policy_implementation(value):
+    value = value.upper()
+    ok = ('PYTHON', 'C')
+    if value not in ok:
+        raise ValueError, (
+            "security_policy_implementation must be one of %s" % ok
+            )
+    return value
+
+def cgi_environment(section):
+    return section.environ
+
+# Datatype for the access and trace logs
+# (the loghandler datatypes come from the zLOG package)
+
+class LoggerFactory:
+    """
+    A factory used to create loggers while delaying actual logger
+    instance construction.  We need to do this because we may want to
+    reference a logger before actually instantiating it (for example,
+    to allow the app time to set an effective user).  An instance of
+    this wrapper is a callable which, when called, returns a logger
+    object.
+    """
+    def __init__(self, section):
+        self.name = section.getSectionName()
+        self.level = section.level
+        self.handler_factories = section.handlers
+        self.resolved = None
+
+    def __call__(self):
+        if self.resolved is None:
+            # set the logger up
+            import logging
+            logger = logging.getLogger(self.name)
+            logger.handlers = []
+            logger.propagate = 0
+            logger.setLevel(self.level)
+            for handler_factory in self.handler_factories:
+                handler = handler_factory()
+                logger.addHandler(handler)
+            self.resolved = logger
+        return self.resolved
+
+# DNS resolver
+
+def dns_resolver(hostname):
+    from ZServer.medusa import resolver
+    return resolver.caching_resolver(hostname)
+
+
+# Datatype for the root configuration object
+# (adds the softwarehome and zopehome fields; default values for some
+#  computed paths)
+
+def root_config(section):
+    here = os.path.dirname(os.path.abspath(__file__))
+    swhome = os.path.dirname(os.path.dirname(here))
+    section.softwarehome = swhome
+    section.zopehome = os.path.dirname(os.path.dirname(swhome))
+    if section.cgi_environment is None:
+        section.cgi_environment = {}
+    if section.clienthome is None:
+        section.clienthome = os.path.join(section.instancehome, "var")
+    # set up defaults for pid_filename and lock_filename if they're
+    # not in the config
+    if section.pid_filename is None:
+        section.pid_filename = os.path.join(section.clienthome, 'Z2.pid')
+    if section.lock_filename is None:
+        section.lock_filename = os.path.join(section.clienthome, 'Z2.lock')
+    return section


=== Zope/lib/python/Zope/Startup/handlers.py 1.3 => 1.4 ===
--- /dev/null	Tue Mar 18 16:38:20 2003
+++ Zope/lib/python/Zope/Startup/handlers.py	Tue Mar 18 16:37:49 2003
@@ -0,0 +1,138 @@
+import os
+
+# top-level key handlers
+
+def _setenv(name, value):
+    if isinstance(value, str):
+        os.environ[name] = value
+    else:
+        os.environ[name] = `value`
+
+def debug_mode(value):
+    value and _setenv('Z_DEBUG_MODE', '1')
+    return value
+
+def enable_product_installation(value):
+    value and _setenv('FORCE_PRODUCT_LOAD', '1')
+    return value
+
+def locale(value):
+    import locale
+    locale.setlocale(locale.LC_ALL, value)
+    return value
+
+def zserver_read_only_mode(value):
+    value and _setenv('ZOPE_READ_ONLY', '1')
+    return value
+
+def automatically_quote_dtml_request_data(value):
+    not value and _setenv('ZOPE_DTML_REQUEST_AUTOQUOTE', '0')
+    return value
+
+def skip_authentication_checking(value):
+    value and _setenv('ZSP_AUTHENTICATED_SKIP', '1')
+    return value
+
+def skip_ownership_checking(value):
+    value and _setenv('ZSP_OWNEROUS_SKIP', '1')
+    return value
+
+def maximum_number_of_session_objects(value):
+    default = 1000
+    value not in (None, default) and _setenv('ZSESSION_OBJECT_LIMIT', value)
+    return value
+
+def session_add_notify_script_path(value):
+    value is not None and _setenv('ZSESSION_ADD_NOTIFY', value)
+    return value
+
+def session_delete_notify_script_path(value):
+    value is not None and _setenv('ZSESSION_DEL_NOTIFY', value)
+    return value
+
+def session_timeout_minutes(value):
+    default = 20
+    value not in (None, default) and _setenv('ZSESSION_TIMEOUT_MINS', value)
+    return value
+
+def suppress_all_access_rules(value):
+    value and _setenv('SUPPRESS_ACCESSRULE', value)
+    return value
+
+def suppress_all_site_roots(value):
+    value and _setenv('SUPPRESS_SITEROOT', value)
+    return value
+
+def database_quota_size(value):
+    value and _setenv('ZOPE_DATABASE_QUOTA', value)
+    return value
+
+def read_only_database(value):
+    value and _setenv('ZOPE_READ_ONLY', '1')
+    return value
+
+def zeo_client_name(value):
+    value and _setenv('ZEO_CLIENT', value)
+    return value
+
+def structured_text_header_level(value):
+    value is not None and _setenv('STX_DEFAULT_LEVEL', value)
+    return value
+
+def maximum_security_manager_stack_size(value):
+    value is not None and _setenv('Z_MAX_STACK_SIZE', value)
+    return value
+
+def publisher_profile_file(value):
+    value is not None and _setenv('PROFILE_PUBLISHER', value)
+    return value
+
+def http_realm(value):
+    value is not None and _setenv('Z_REALM', value)
+    return value
+
+def security_policy_implementation(value):
+    value not in ('C', None) and _setenv('ZOPE_SECURITY_POLICY', value)
+
+# server handlers
+
+def root_handler(config):
+    """ Mutate the configuration with defaults and perform
+    fixups of values that require knowledge about configuration
+    values outside of their context. """
+
+    # if no servers are defined, create default http server and ftp server
+    if not config.servers:
+        import ZServer.datatypes
+        config.servers = [
+            ZServer.datatypes.HTTPServerFactory(_DummyServerConfig(8080)),
+            ZServer.datatypes.FTPServerFactory(_DummyServerConfig(8021)),
+            ]
+
+    # prepare servers:
+    for factory in config.servers:
+        factory.prepare(config.ip_address or '',
+                        config.dns_resolver,
+                        "Zope",
+                        config.cgi_environment,
+                        config.port_base)
+
+
+class _DummyServerConfig:
+    class _Thing:
+        pass
+
+    def __init__(self, port):
+        import socket
+        self.address = self._Thing()
+        self.address.family = socket.AF_INET
+        self.address.address = '', port
+        self.force_connection_close = 0
+
+
+def handleConfig(config, multihandler):
+    handlers = {}
+    for name, value in globals().items():
+        if not name.startswith('_'):
+            handlers[name] = value
+    return multihandler(handlers)


=== Zope/lib/python/Zope/Startup/options.py 1.1 => 1.2 ===
--- /dev/null	Tue Mar 18 16:38:20 2003
+++ Zope/lib/python/Zope/Startup/options.py	Tue Mar 18 16:37:49 2003
@@ -0,0 +1,25 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+
+"""Command-line processor for Zope."""
+
+import os
+
+import zdaemon.zdoptions
+
+
+class ZopeOptions(zdaemon.zdoptions.ZDOptions):
+
+    schemadir = os.path.dirname(os.path.abspath(__file__))
+    schemafile = "zopeschema.xml"


=== Zope/lib/python/Zope/Startup/zopeschema.xml 1.3 => 1.4 ===
--- /dev/null	Tue Mar 18 16:38:20 2003
+++ Zope/lib/python/Zope/Startup/zopeschema.xml	Tue Mar 18 16:37:49 2003
@@ -0,0 +1,260 @@
+<schema prefix="Zope.Startup.datatypes"
+        datatype=".root_config"
+        handler="root_handler">
+
+  <!-- type definitions -->
+
+  <import package="zLOG"/>
+  <import package="ZODB"/>
+  <import package="ZServer"/>
+
+  <sectiontype name="logger" datatype=".LoggerFactory">
+    <description>
+      This "logger" type only applies to access and request ("trace")
+      logging; event logging is handled by the zLOG package, which
+      provides the loghandler type used here.
+    </description>
+    <key name="level" datatype="zLOG.datatypes.logging_level" default="info"/>
+    <multisection type="loghandler" attribute="handlers" name="*"
+                  required="yes"/>
+  </sectiontype>
+
+  <sectiontype name="cgi-environment"
+               datatype=".cgi_environment"
+               keytype="identifier">
+    <key name="+" attribute="environ"/>
+  </sectiontype>
+
+  <sectiontype name="zoperunner">
+    <description>
+      This section describes the options for zopectl.  These options
+      have no default value specified in the schema; in some cases,
+      zopectl calculates a dynamic default, in others, the feature
+      associated with the option is disabled.
+
+      For those options that also have corresponding command-line
+      options, the command line option (short and long form) are given
+      here too.
+    </description>
+
+    <key name="daemon" datatype="boolean"
+	 required="no" default="false">
+      <description>
+        Command-line option: -d or --daemon.
+
+        If this option is true, zdrun.py runs in the background as a
+        true daemon.  It forks an child process which becomes the
+        subprocess manager, while the parent exits (making the shell
+        that started it believe it is done).  The child process also
+        does the following:
+
+        - if the directory option is set, change into that directory
+
+        - redirect stdin, stdout and stderr to /dev/null
+
+        - call setsid() so it becomes a session leader
+
+        - call umask(022)
+      </description>
+    </key>
+
+    <key name="backoff-limit" datatype="integer"
+	 required="no" default="10">
+      <description>
+        Command-line option: -b or --backoff-limit.
+
+        When the subprocess crashes, zdrun.py inserts a one-second
+        delay before it restarts it.  When the subprocess crashes
+        again right away, the delay is incremented by one second, and
+        so on.  What happens when the delay has reached the value of
+        backoff-limit (in seconds), depends on the value of the
+        forever option.  If forever is false, zdrun.py gives up at
+        this point, and exits.  An always-crashing subprocess will
+        have been restarted exactly backoff-limit times in this case.
+        If forever is true, zdrun.py continues to attempt to restart
+        the process, keeping the delay at backoff-limit seconds.
+
+        If the subprocess stays up for more than backoff-limit
+        seconds, the delay is reset to 1 second.
+      </description>
+    </key>
+
+    <key name="forever" datatype="boolean"
+	 required="no" default="false">
+      <description>
+        Command-line option: -f or --forever.
+
+        If this option is true, zdrun.py will keep restarting a
+        crashing subprocess forever.  If it is false, it will give up
+        after backoff-limit crashes in a row.  See the description of
+        backoff-limit for details.
+      </description>
+    </key>
+
+    <key name="hang-around" datatype="boolean"
+	 required="no" default="false">
+      <description>
+        If this option is true, the zdrun.py process will remain even
+        when the daemon subprocess is stopped.  In this case, zopectl
+        will restart zdrun.py as necessary.  If this option is false,
+        zdrun.py will exit when the daemon subprocess is stopped
+        (unless zdrun.py intends to restart it).
+      </description>
+    </key>
+
+    <key name="default-to-interactive" datatype="boolean"
+	 required="no" default="true">
+      <description>
+        If this option is true, zopectl enters interactive mode
+        when it is invoked without a positional command argument.  If
+        it is false, you must use the -i or --interactive command line
+        option to zopectl to enter interactive mode.
+      </description>
+    </key>
+
+    <key name="prompt" datatype="string"
+         required="no" default="zopectl>">
+       <description>
+         The prompt shown by zopectl program.
+       </description>
+    </key>
+
+  </sectiontype>
+
+  <!-- end of type definitions -->
+
+  <!-- schema begins  -->
+
+  <key name="instancehome" datatype="existing-directory"
+       required="yes">
+    <description>
+      The top-level directory which contains the "instance" of the
+      application server.
+    </description>
+  </key>
+
+  <key name="clienthome" datatype="existing-directory">
+    <description>
+      The directory used to store the file-storage used to back the
+      ZODB database by default, as well as other files used by the
+      Zope application server to control the run-time behavior.
+    </description>
+    <metadefault>$INSTANCE_HOME/var</metadefault>
+  </key>
+
+  <key name="pid-filename" datatype="existing-dirpath"/>
+
+  <key name="lock-filename" datatype="existing-dirpath"/>
+
+  <key name="debug-mode" datatype="boolean" default="on"
+       handler="debug_mode"/>
+
+  <key name="effective-user"/>
+
+  <key name="enable-product-installation" datatype="boolean" default="on"
+       handler="enable_product_installation"/>
+
+  <key name="locale" datatype="locale" handler="locale"/>
+
+  <key name="zserver-threads" datatype="integer" default="4"/>
+
+  <key name="python-check-interval" datatype="integer" default="500">
+    <description>
+      Value passed to Python's sys.setcheckinterval() function.  The
+      higher this is, the less frequently the Python interpreter
+      checks for keyboard interrupts.  Setting this to higher values
+      also reduces the frequency of potential thread switches, which
+      can improve the performance of a busy server.
+    </description>
+  </key>
+
+  <key name="zserver-read-only-mode" datatype="boolean" default="off"
+       handler="zserver_read_only_mode">
+    <description>
+      If this variable is set, then the database is opened in read
+      only mode.  If this variable is set to a string parsable by
+      DateTime.DateTime, then the database is opened read-only as of
+      the time given.  Note that changes made by another process after
+      the database has been opened are not visible.
+    </description>
+  </key>
+
+  <key name="structured-text-header-level" datatype="integer" default="3"
+       handler="structured_text_header_level"/>
+
+  <key name="maximum-security-manager-stack-size" datatype="integer"
+       default="100" handler="maximum_security_manager_stack_size"/>
+
+  <key name="publisher-profile-file" handler="publisher_profile_file"/>
+
+  <section type="cgi-environment" attribute="cgi_environment" name="*"/>
+
+  <key name="dns-server" datatype=".dns_resolver" attribute="dns_resolver"/>
+
+  <key name="ip-address" datatype="ipaddr-or-hostname"/>
+
+  <key name="http-realm" default="Zope" handler="http_realm"/>
+
+  <key name="automatically-quote-dtml-request-data" datatype="boolean"
+       default="on" handler="automatically_quote_dtml_request_data"/>
+
+  <key name="security-policy-implementation"
+       datatype=".security_policy_implementation"
+       default="C" handler="security_policy_implementation"/>
+
+  <key name="skip-authentication-checking" datatype="boolean"
+       default="off" handler="skip_authentication_checking"/>
+
+  <key name="skip-ownership-checking" datatype="boolean"
+       default="off" handler="skip_ownership_checking"/>
+
+  <key name="maximum-number-of-session-objects" datatype="integer"
+       default="1000" handler="maximum_number_of_session_objects"/>
+
+  <key name="session-add-notify-script-path"
+       handler="session_add_notify_script_path"/>
+
+  <key name="session-delete-notify-script-path"
+       handler="session_add_notify_script_path"/>
+
+  <key name="session-timeout-minutes" datatype="integer"
+       default="20" handler="session_timeout_minutes"/>
+
+  <key name="suppress-all-access-rules" datatype="boolean"
+       default="off" handler="suppress_all_access_rules"/>
+
+  <key name="suppress-all-site-roots" datatype="boolean"
+       default="off" handler="suppress_all_site_roots"/>
+
+  <key name="database-quota-size" datatype="byte-size"
+       handler="database_quota_size"/>
+
+  <key name="read-only-database" datatype="boolean"
+       handler="read_only_database"/>
+
+  <key name="zeo-client-name"
+       handler="zeo_client_name"/>
+
+  <section type="eventlog" name="*" attribute="eventlog">
+    <description>
+      Describes the logging performed by zLOG.LOG() calls.
+    </description>
+  </section>
+
+  <section type="logger" name="access"/>
+
+  <section type="logger" name="trace"/>
+
+  <multisection type="server" name="*" attribute="servers"/>
+  <key name="port-base" datatype="integer" default="0">
+    <description>
+      Base port number that gets added to the specific port numbers
+      specified for the individual servers.
+    </description>
+  </key>
+
+  <multisection type="database" name="*" attribute="databases"/>
+
+  <section type="zoperunner" name="*" attribute="runner"/>
+
+</schema>