[Zope-Checkins] CVS: Zope/lib/python/Zope/Startup - ZctlLib.py:1.1.2.1 __init__.py:1.1.2.1 datatypes.py:1.1.2.1 handlers.py:1.1.2.1 zopeschema.xml:1.1.2.1

Chris McDonough chrism@zope.com
Sat, 4 Jan 2003 23:01:41 -0500


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

Added Files:
      Tag: chrism-install-branch
	ZctlLib.py __init__.py datatypes.py handlers.py zopeschema.xml 
Log Message:
Adding Startup to Zope package (moved from software_home).


=== Added File Zope/lib/python/Zope/Startup/ZctlLib.py === (644/744 lines abridged)
##############################################################################
#
# 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: 1.1.2.1 $'[11:-2]

import getopt
import os
import sys
import re
import signal
import cmd
import time
try:
    import readline
except:
    readline = None
from Zope.Startup import getSchemaKeys, configure
from Zope.Startup.misc import TextBlockFormatter
from Zope.Startup.misc.lock_file import lock_file

_marker = []

USAGE = """\

zctl:  Zope appserver controller

Usage:
    zctl [-h | --help] [--config=filepath or url]

Options:
    -h or --help       Print this message.  Not compatible with the 'start'
                       or 'restart' command.

    --config           Use an alternate configuration from a local file.
                       or a URL.  Default: 'zope.conf'.

[-=- -=- -=- 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


=== Added File Zope/lib/python/Zope/Startup/__init__.py ===
""" Startup package.  Responsible for startup configuration of Zope """

import os
import sys
import socket
import re
import types

from ZConfig import ConfigurationError
from ZConfig import loadSchema, loadConfig
from misc.lock_file import lock_file

# global to hold config structure
_configuration = None

def getConfiguration():
    return _configuration

def getSchemaLocation():
    here = os.path.dirname(__file__)
    schema = os.path.join(here, 'zopeschema.xml')
    return schema

def getSchemaKeys():
    f = getSchemaLocation()
    schema = loadSchema(f)
    return schema.getchildnames()

def configure(config_location, overrides):
    f = getSchemaLocation()
    schema = loadSchema(f)
    import handlers
    config, handler = loadConfig(schema, config_location)
    handlers.handleConfig(config, handler)
##     for name, value in overrides.items():
##         key = config.getSub(KEY_TYPE, KEY_TYPE, name)
##         key.setValue(value)
    global _configuration
    _configuration = config
    return _configuration
    
def start_zope(config_location, overrides):
    # caller must set configuration
    check_python_version()
    cfg = configure(config_location, overrides)

    if cfg.zope_home not in sys.path:
        sys.path.insert(0, cfg.zope_home)

    if cfg.software_home not in sys.path:
        sys.path.insert(0, cfg.software_home)

    sys.path = filter(None, sys.path) # strip empties out of sys.path

    # 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
    import logging
    
    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 = logging.getLogger('event')
    event_logger.addHandler(startup_handler)

    # set a locale if one has been specified in the config
    cfg.locale and do_locale(cfg.locale)

    # goofy source_port clients business
    dav_clients = cfg.webdav_source_user_agents
    if dav_clients:
        sys.WEBDAV_SOURCE_PORT_CLIENTS = re.compile(dav_clients).search

    # make sure to import zdaemon before zserver or weird things
    # begin to happen
    import zdaemon

    # Import ZServer before we open the database or get at interesting
    # application code so that ZServer's asyncore gets to be the
    # official one. Also gets SOFTWARE_HOME, INSTANCE_HOME, and CLIENT_HOME
    import ZServer

    # Increase the number of threads
    from ZServer import setNumberOfThreads
    setNumberOfThreads(cfg.zserver_threads)

    # if we're not using ZDaemon or if we're the child of a Zdaemon process,
    # start ZServer servers before we setuid so we can bind to low ports
    if not cfg.use_daemon_process or (
        cfg.use_daemon_process and os.environ.get('ZDAEMON_MANAGED')
        ):
        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.'
            )
        for server_type, server in cfg.servers:
            # create the server from the server factory
            # set up in the config
            try:
                server()
            except socket.error:
                raise ConfigurationError(socket_err % server_type)

    # 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
        lock_filename      = (cfg.lock_filename or
                              os.path.join(cfg.instance_home,'var', 'Z2.lock'))

        try:
            if os.path.exists(lock_filename):
                os.unlink(lock_filename)
            LOCK_FILE = open(lock_filename, 'w')
            lock_file(LOCK_FILE)
        except IOError:
            pass
            
        # write pid file if zdaemon didn't do it already
        if not cfg.use_daemon_process:
            pf = open(cfg.pid_filename, 'w')
            pid='%s\n' % os.getpid()
            pf.write(pid)
            pf.close()

        # now that we've successfully setuid'd, we can log to
        # somewhere other than stderr.  We rely on config
        # to set up our logging properly.
        for logger_name in ('event', 'access', 'trace'):
            factory = getattr(cfg, logger_name, None)
            if factory:
                logger = factory() # activate the logger
                # flush buffered startup messages to event logger
                if logger_name == 'event':
                    startup_handler.flushBufferTo(logger)

        event_logger.removeHandler(startup_handler)

    zLOG.LOG('Zope', zLOG.INFO, 'Ready to handle requests')

    # Start Medusa, Ye Hass!
    sys.ZServerExitCode=0
    try:
        import Lifetime
        Lifetime.loop()
        sys.exit(sys.ZServerExitCode)
    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
    # too chicken to preclude 2.1 yet
    python_version = sys.version.split()[0]
    optimum_version = '2.2.2'
    if python_version < '2.1':
        raise ConfigurationError, 'Invalid python version %s' % python_version
    if python_version[:3] == '2.1':
        err = ('You are running Python version %s.  Zope may work under this '
               'Python version, but it may not.  Consider upgrading to '
               'Python %s\n' % (python_version, optimum_version))
        sys.stderr.write(err)
    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 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, types.StringType):
            uid = pwd.getpwnam(UID)[2]
            gid = pwd.getpwnam(UID)[3]
        elif isinstance(UID, types.IntType):
            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 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
                ))

    # try to use a management daemon process.  We do this after we setuid so
    # we don't write our pidfile out as root.
    if cfg.use_daemon_process and not cfg.zserver_read_only_mode:
        import App.FindHomes
        sys.ZMANAGED=1
        # zdaemon.run creates a process which "manages" the actual Zope
        # process (restarts it if it dies).  The management process passes
        # along signals that it receives to its child.
        zdaemon.run(sys.argv, pidfile=cfg.pid_filename)

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 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 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
                )

    


=== Added File Zope/lib/python/Zope/Startup/datatypes.py ===
from misc.factory import Factory

# 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

# log-related datatypes

def log_format(value):
    d = {
        'name':'',
        'levelno':'3',
        'levelname':'DEBUG',
        'pathname':'apath',
        'filename':'afile',
        'module':'amodule',
        'lineno':1,
        'created':1.1,
        'asctime':'atime',
        'msecs':1,
        'relativeCreated':1,
        'thread':1,
        'message':'amessage',
        }
    try:
        value % d
    except (ValueError, KeyError):
        raise ValueError, 'Invalid log format string %s' % value
    value = ctrl_char_insert(value)
    return value

def ctrl_char_insert(value):
    chars = {r'\n':'\n', r'\t':'\t', r'\b':'\b', r'\f':'\f', r'\r':'\r'}
    realvalue = value
    for char in chars.keys():
        l = realvalue.split(char)
        realvalue = chars[char].join(l)
    return realvalue

def file_handler(section):
    file       = section.file
    format     = section.format
    dateformat = section.dateformat
    level      = section.level

    formatter = Factory('logging.Formatter', None, format, dateformat)

    def callback(inst, formatter=formatter, level=level):
        inst.setFormatter(formatter())
        inst.setLevel(level)

    return Factory('zLOG.LogHandlers.FileHandler', callback, file)

def syslog_facility(value):
    d = {
        "auth":1,
        "authpriv":1,
        "cron":1,
        "daemon":1,
        "kern":1,
        "lpr":1,
        "mail":1,
        "news":1,
        "security":1,
        "syslog":1,
        "user":1,
        "uucp":1,
        "local0":1,
        "local1":1,
        "local2":1,
        "local3":1,
        "local4":1,
        "local5":1,
        "local6":1,
        "local7":1,
        }
    value = value.lower()
    if not d.has_key(value):
        raise ValueError, "Syslog facility must be one of %s" % d.keys()
    return value

def syslog_handler(section):
    facility = section.facility
    socket = section.socket
    host = section.host
    port = section.port
    format = section.format
    dateformat = section.dateformat
    level = section.level

    if socket and host:
        raise ValueError, ('Only one of "socket" or "host" may be '
                           'specified in a syslog_handler section')
    if not (socket or host):
        raise ValueError, ('One of "socket" or "host" must be '
                           'specified in a syslog_handler section')
        
    formatter = Factory('logging.Formatter', None, format, dateformat)

    def callback(inst, formatter=formatter, level=level):
        inst.setFormatter(formatter())
        inst.setLevel(level)

    klass = 'zLOG.LogHandlers.SysLogHandler'
    if socket:
        handler = Factory(klass, callback, socket, facility)
    else:
        handler = Factory(klass, callback, (host, port), facility)

    return handler

def nteventlog_handler(section):
    appname = section.appname
    format = section.format
    dateformat = section.dateformat
    level = section.level
    
    formatter = Factory('logging.Formatter', None, format, dateformat)

    def callback(inst, formatter=formatter, level=level):
        inst.setFormatter(formatter())
        inst.setLevel(level)

    return Factory('zLOG.LogHandlers.NTEventLogHandler', callback, appname)

def http_handler_url(value):
    import urlparse
    scheme, netloc, path, query, fragment = urlparse.urlsplit(value)
    if scheme != 'http':
        raise ValueError, 'url must be an http url'
    if not netloc:
        raise ValueError, 'url must specify a location'
    if not path:
        raise ValueError, 'url must specify a path'
    q = []
    if query:
        q.append('?')
        q.append(query)
    if fragment:
        q.append('#')
        q.append(fragment)
    return (netloc, path + ''.join(q))

def get_or_post(value):
    value = value.upper()
    if value not in ('GET', 'POST'):
        raise ValueError, ('method must be "GET" or "POST", instead received '
                           '%s' % value)
    return value

def http_handler(section):
    host, url = section.url
    method     = section.method
    format     = section.format
    dateformat = section.dateformat
    level      = section.level
    
    formatter = Factory('logging.Formatter', None, format, dateformat)

    def callback(inst, formatter=formatter, level=level):
        inst.setFormatter(formatter())
        inst.setLevel(level)

    return Factory('zLOG.LogHandlers.HTTPHandler', callback, host, url, method)

def smtp_handler(section):
    fromaddr   = section.fromaddr
    toaddrs    = section.toaddrs
    subject    = section.subject
    host, port = section.host
    format     = section.format
    dateformat = section.dateformat
    level      = section.level
    
    if not port:
        mailhost = host
    else:
        mailhost = host, port
    formatter = Factory('logging.Formatter', None, format, dateformat)

    def callback(inst, formatter=formatter, level=level):
        inst.setFormatter(formatter())
        inst.setLevel(level)

    return Factory('zLOG.LogHandlers.SMTPHandler', callback,
                   mailhost, fromaddr, toaddrs, subject)

def null_handler(section):
    return Factory('zLOG.LogHandlers.NullHandler', None)

def custom_handler(section):
    formatter_klass, formatter_pos, formatter_kw = section.formatter
    handler_klass, handler_pos, handler_kw = section.constructor
    level = section.level

    formatter = Factory(formatter_klass, None, formatter_pos, formatter_kw)

    def callback(inst, formatter=formatter, level=level):
        inst.setFormatter(formatter())
        inst.setLevel(level)

    return Factory(handler_klass, callback, *handler_pos, **handler_kw)

def logger(section):
    return LoggerWrapper(section.getSectionName(),
                         section.level,
                         section.handlers)

# database-related datatypes

def mount_point(value):
    if value.startswith('/'):
        return value
    raise ValueError, (
        'Invalid mount_point "%s" (must start with a slash)' % value
        )

def database(section):
    if len(section.storages) > 1:
        raise ValueError, ('Current database support limits database '
                           'instances to a single storage')
    if len(section.storages) < 1:
        raise ValueError, 'Must name one storage in a database section'
    klass = section.db_class
    mounts = section.mount_points
    dbfactory = Factory(
        klass, None,
        pool_size=section.pool_size,
        cache_size=section.cache_size,
        cache_deactivate_after=section.cache_deactivate_after,
        version_pool_size=section.version_pool_size,
        version_cache_size=section.version_cache_size,
        version_cache_deactivate_after=section.version_cache_deactivate_after)
    storagefactory = section.storages[0]
    return mounts, DBWrapper(dbfactory, storagefactory)

def filestorage(section):
    file_name = section.file_name
    kw = {
        'create':section.create,
        'read_only':section.read_only,
        'stop':section.stop,
        'quota':section.quota,
        }
    return Factory('ZODB.FileStorage.FileStorage', None, file_name, **kw)

def mappingstorage(section):
    name = section.name
    return Factory('ZODB.MappingStorage.MappingStorage', None, name)

def clientstorage(section):
    addr = section.addr
    kw = {
        'storage':section.create,
        'cache_size':section.read_only,
        'name':section.stop,
        'client':section.quota,
        'debug':section.quota,
        'var':section.quota,
        'min_disconnect_poll':section.quota,
        'max_disconnect_poll':section.quota,
        'wait':section.wait,
        'read_only':section.read_only,
        'read_only_fallback':section.read_only_fallback,
        }
    return Factory('ZEO.ClientStorage.ClientStorage', None, addr, **kw)

_marker = []

class LoggerWrapper:
    """
    A wrapper 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, name, level, handler_factories):
        self.name = name
        self.level = level
        self.handler_factories = handler_factories
        self.resolved = _marker

    def __call__(self):
        if self.resolved is _marker:
            # 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

class DBWrapper:
    """
    A wrapper used to create ZODB databases while delaying the underlying
    storage instance construction.  We need to do this because we may
    want to reference a database before actually instantiating it (for
    example, in order to delay database construction until after an
    effective user is set or until all configuration parsing is done).
    An instance of this wrapper is a callable which, when called, returns a
    database object.
    """
    def __init__(self, dbfactory, storagefactory):
        self.dbfactory = dbfactory
        self.storagefactory = storagefactory
        self.resolved = _marker

    def __call__(self):
        if self.resolved is _marker:
            args, kw= self.dbfactory.getArgs()
            args = [self.storagefactory()] + list(args)
            self.dbfactory.setArgs(args, kw)
            self.resolved = self.dbfactory()
        return self.resolved


=== Added File Zope/lib/python/Zope/Startup/handlers.py ===
from misc.factory import Factory
import os
from os.path import dirname
pjoin = os.path.join
import types

# top-level key handlers

def _setenv(name, value):
    if (  type(value) is types.StringType or
          type(value) is types.UnicodeType ):
        os.environ[name] = value
    else:
        os.environ[name] = `value`

def _append_slash(value):
    if value is None:
        return None
    if not value.endswith('/'):
        return value + '/'
    return value

def null(value):
    return value

def software_home(value):
    value = _append_slash(value)
    value and _setenv('SOFTWARE_HOME', value)
    return value

def zope_home(value):
    value = _append_slash(value)
    value and _setenv('ZOPE_HOME', value)
    return value

def instance_home(value):
    value = _append_slash(value)
    value and _setenv('INSTANCE_HOME', value)
    return value

def client_home(value):
    value = _append_slash(value)
    value and _setenv('CLIENT_HOME', value)
    return 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 use_daemon_process(value):
    value and _setenv('Z_DEBUG_MODE', '1')
    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

class _RootHandler:
    def __call__(self, config):
        """ Mutate the configuration with defaults and perform
        fixups of values that require knowledge about configuration
        values outside of their context. """

        self._config = config

        # alias some things for use in server handlers
        from zLOG.AccessLogger import access_logger
        self._logger = access_logger
        import ZODB # :-( required to import user
        from AccessControl.User import emergency_user
        if hasattr(emergency_user, '__null_user__'):
            self._pw = None
        else:
            self._pw = emergency_user._getPassword()
        self._resolver = self.get_dns_resolver()
        self._read_only = config.zserver_read_only_mode
        self._default_ip = config.ip_address
        self._module = 'Zope' # no longer settable

        # set up cgi overrides
        self._env = {}
        for pair in config.cgi_environment_variables:
            key, value = pair
            self._env[key] = value
        config.cgi_environment_variables = self._env

        # set up server factories via dispatch
        l = []
        for section in config.servers:
            server_type = section.__type__.name
            for server_factory in getattr(self, server_type)(section):
                l.append((server_type, server_factory))

        # if no servers are defined, create default http server and ftp server
        if not l:
            class dummy:
                pass
            http, ftp = dummy(), dummy()

            http.ports = [('', 8080)]
            http.force_connection_close = 0
            ftp.ports = [('', 8021)]

            http = self.http_server(http)[0]
            ftp = self.ftp_server(ftp)[0]
            l.extend([('http_server', http), ('ftp_server', ftp)])

        config.servers = l

        # set up defaults for zope_home and client_home if they're
        # not in the config
        if config.zope_home is None:
            config.zope_home   = zope_home(
                dirname(dirname(config.software_home))
                )
        if config.client_home is None:
            config.client_home = client_home(
                pjoin(config.instance_home, 'var')
                )

        # set up defaults for pid_filename and lock_filename if they're
        # not in the config
        if config.pid_filename is None:
            config.pid_filename = pjoin(config.client_home, 'Z2.pid')
        if config.lock_filename is None:
            config.lock_filename = pjoin(config.client_home, 'Z2.lock')

        # set up a default root filestorage if there are no root storages
        # mentioned in the config
        databases = config.databases
        root_mounts = [ ('/' in db.mount_points) for db in databases ]
        if not True in root_mounts:
            from datatypes import DBWrapper, Factory
            storagefactory = Factory(
                'ZODB.FileStorage.FileStorage', None,
                *[pjoin(config.client_home, 'Data.fs')], **{}
                )
            dbfactory = Factory('ZODB.DB', None, *[], **{})
            databases.append((['/'], DBWrapper(dbfactory, storagefactory)))

    def get_dns_resolver(self):
        if self._config.dns_ip_address:
            from ZServer import resolver
            return resolver.caching_resolver(self._config.dns_ip_address)

    def http_server(self, section):
        l = []
        from ZServer import zhttp_server, zhttp_handler
        for addr, port in section.ports:
            kw = make_dict(ip=addr, port=port,
                           resolver=self._resolver,
                           logger_object=self._logger)
            def callback(inst, self=self):
                handler = zhttp_handler(self._module, '', self._env)
                inst.install_handler(handler)
                if section.force_connection_close:
                    handler._force_connection_close = 1
            serverfactory = Factory(
                'ZServer.zhttp_server', callback, *[], **kw)
            l.append(serverfactory)
        return l

    def webdav_source_server(self, section):
        l = []
        for addr, port in section.ports:
            kw = make_dict(ip=addr, port=port,
                           resolver=self._resolver,
                           logger_object=self._logger)
            def callback(inst, self=self):
                from ZServer.WebDAVSrcHandler import WebDAVSrcHandler
                handler = WebDAVSrcHandler(self._module, '', self._env)
                inst.install_handler(handler)
                if section.force_connection_close:
                    handler._force_connection_close = 1
            serverfactory = Factory(
                'ZServer.zhttp_server', callback, *[], **kw)
            l.append(serverfactory)
        return l

    def ftp_server(self, section):
        l = []
        for addr, port in section.ports:
            kw = make_dict(module=self._module, ip=addr, port=port,
                             resolver=self._resolver,
                             logger_object=self._logger)
            serverfactory=Factory('ZServer.FTPServer', None, *[], **kw)
            l.append(serverfactory)
        return l

    def pcgi_server(self, section):
        if not self._read_only:
            kw = make_dict(module=self._module,
                           ip=self._default_ip, pcgi_file=section.file,
                           resolver=self._resolver,
                           logger_object=self._logger)
            serverfactory = Factory('ZServer.PCGIServer', None, *[], **kw)
            return [serverfactory]
        return []

    def fcgi_server(self, section):
        if section.file and section.port:
            raise ValueError, ("Must specify either 'port' or 'file' in "
                               "fcgi server configuration, but not both")
        if section.port:
            addr = section.port[0]
            port = section.port[1]
        else:
            addr = port = None
        file = section.file
        if not self._read_only:
            kw = make_dict(module=self._module, ip=addr, port=port,
                           socket_file=file, resolver=self._resolver,
                           logger_object=self._logger)
            serverfactory = Factory('ZServer.FCGIServer', None, *[], **kw)
            return [serverfactory]
        return []

    def monitor_server(self, section):
        if self._pw is None:
            import zLOG
            zLOG.LOG("z2", zLOG.WARNING, 'Monitor server not started'
                     ' because no emergency user exists.')
            return []
        l = []
        for addr, port in section.ports:
            kw = make_dict(password=self._pw, hostname=addr, port=port)
            serverfactory = Factory(
                'ZServer.secure_monitor_server', None, *[], **kw)
            l.append(serverfactory)
        return l

    def icp_server(self, section):
        l = []
        for addr, port in section.ports:
            serverfactory = Factory('ZServer.ICPServer.ICPServer', None,
                                    *(addr, port), **{})
            l.append(serverfactory)
        return l

root_handler = _RootHandler()

def handleConfig(config, multihandler):
    handlers = {}
    for name, value in globals().items():
        if not name.startswith('_'):
            handlers[name] = value
    return multihandler(handlers)
    
def make_dict(**kw):
    return kw


=== Added File Zope/lib/python/Zope/Startup/zopeschema.xml ===
<schema prefix="Zope.Startup.datatypes" handler="root_handler">

  <!-- type definitions -->

  <sectiongroup type="handler">

    <sectiontype type="file_handler" datatype=".file_handler">
      <key name="file" required="yes"/>
      <key name="format" default="------\n%(asctime)s %(message)s"
           datatype=".log_format"/>
      <key name="dateformat" default="%Y-%m-%dT%H:%M:%S"/>
      <key name="level" default="info" datatype="logging-level"/>
    </sectiontype>

    <sectiontype type="syslog_handler" datatype=".syslog_handler">
      <key name="facility" default="user" datatype=".syslog_facility"/>
      <key name="socket" datatype="existing-file"/>
      <key name="port" default="514" datatype="port-number"/>
      <key name="host" datatype="ipaddr-or-hostname"/>
      <key name="format" default="%(message)s"
           datatype=".log_format"/>
      <key name="dateformat" default="%Y-%m-%dT%H:%M:%S"/>
      <key name="level" default="info" datatype="logging-level"/>
    </sectiontype>

    <sectiontype type="nteventlog_handler" datatype=".nteventlog_handler">
      <key name="appname" default="Zope"/>
      <key name="format" default="%(message)s"
           datatype=".log_format"/>
      <key name="dateformat" default="%Y-%m-%dT%H:%M:%S"/>
      <key name="level" default="info" datatype="logging-level"/>
    </sectiontype>

    <sectiontype type="http_handler" datatype=".http_handler">
      <key name="url" default="localhost" datatype=".http_handler_url"/>
      <key name="method" default="GET" datatype=".get_or_post"/>
      <key name="format" default="%(asctime)s %(message)s"
           datatype=".log_format"/>
      <key name="dateformat" default="%Y-%m-%dT%H:%M:%S"/>
      <key name="level" default="info" datatype="logging-level"/>
    </sectiontype>

    <sectiontype type="smtp_handler" datatype=".smtp_handler">
      <key name="fromaddr" required="yes"/>
      <multikey name="toaddr" required="yes" attribute="toaddrs"/>
      <key name="subject" default="Message from Zope"/>
      <key name="host" default="localhost" datatype="inet-address"/>
      <key name="format" default="%(asctime)s %(message)s"
           datatype=".log_format"/>
      <key name="dateformat" default="%Y-%m-%dT%H:%M:%S"/>
      <key name="level" default="info" datatype="logging-level"/>
    </sectiontype>

    <sectiontype type="null_handler" datatype=".null_handler">
    </sectiontype>

    <sectiontype type="custom_handler" datatype=".custom_handler">
      <key name="constructor" datatype="constructor" required="yes"/>
      <key name="formatter" datatype="constructor"
           default="logging.Formatter()"/>
      <key name="level" default="info" datatype="logging-level"/>
    </sectiontype>

  </sectiongroup>

  <sectiontype type="logger" datatype=".logger">
     <key name="level" datatype="logging-level" default="info"/>
     <multisection type="handler" attribute="handlers" name="*"/>
  </sectiontype>

  <sectiongroup type="server">

    <sectiontype type="http_server">
       <multikey name="port" attribute="ports" datatype="inet-address"/>
       <key name="force_connection_close" datatype="boolean" default="off"/>
    </sectiontype>

    <sectiontype type="ftp_server">
       <multikey name="port" attribute="ports" datatype="inet-address"/>
    </sectiontype>

    <sectiontype type="webdav_source_server">
       <multikey name="port" attribute="ports" datatype="inet-address"/>
       <key name="force_connection_close" datatype="boolean" default="off"/>
    </sectiontype>

    <sectiontype type="pcgi_server">
      <key name="file" datatype="existing-file"/>
    </sectiontype>

    <sectiontype type="fcgi_server">
      <key name="port" datatype="inet-address"/>
      <key name="file" datatype="existing-dirpath"/>
    </sectiontype>

    <sectiontype type="monitor_server">
       <multikey name="port" attribute="ports" datatype="inet-address"/>
    </sectiontype>

    <sectiontype type="icp_server">
       <multikey name="port" attribute="ports" datatype="inet-address"/>
    </sectiontype>

  </sectiongroup>

  <sectiongroup type="storage">

    <sectiontype type="filestorage" datatype=".filestorage">
      <key name="file_name" required="yes"/>
      <key name="create" datatype="boolean"/>
      <key name="read_only" datatype="boolean"/>
      <key name="stop"/>
      <key name="quota" datatype="integer"/>
    </sectiontype>

    <sectiontype type="mappingstorage" datatype=".mappingstorage">
      <key name="name" default="Mapping Storage"/>
    </sectiontype>

    <sectiontype type="clientstorage" datatype=".clientstorage">
      <key name="addr" datatype="inet-address" required="yes"/>
      <key name="storage" default="1"/>
      <key name="cache_size" datatype="integer" default="20000000"/>
      <key name="name" default=""/>
      <key name="client"/>
      <key name="debug" datatype="boolean"/>
      <key name="var" datatype="existing-directory"/>
      <key name="min_disconnect_poll" datatype="integer" default="5"/>
      <key name="max_disconnect_poll" datatype="integer" default="300"/>
      <key name="wait" datatype="boolean" default="on"/>
      <key name="read_only" datatype="boolean" default="off"/>
      <key name="read_only_fallback" datatype="boolean" default="off"/>
    </sectiontype>

  </sectiongroup>

  <sectiontype type="database" datatype=".database">

      <multisection type="storage" name="*" attribute="storages"/>
      <multikey name="mount_point" attribute="mount_points"
                datatype=".mount_point"/>
      <key name="db_class" default="ZODB.DB"/>
      <key name="cache_size" datatype="integer" default="5000"/>
      <key name="pool_size" datatype="integer" default="7"/>
      <key name="cache_deactivate_after" datatype="integer" default="60"/>
      <key name="version_pool_size" datatype="integer" default="3"/>
      <key name="version_cache_size" datatype="integer" default="100"/>
      <key name="version_cache_deactivate_after" datatype="integer"
         default="10"/>

  </sectiontype>

  <!-- end of type definitions -->

  <!-- schema begins  -->

  <key name="instance_home" datatype="existing-directory"
       required="yes"/>

  <key name="software_home" datatype="existing-directory"
       required="yes"/>

  <key name="zope_home" datatype="existing-directory"/>

  <key name="client_home" datatype="existing-directory"/>

  <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"/>

  <key name="use_daemon_process" datatype="boolean" default="on"
       handler="use_daemon_process"/>

  <key name="zserver_read_only_mode" datatype="boolean" default="off"
       handler="zserver_read_only_mode"/>

  <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"/>

  <key name="webdav_source_user_agents"/>

  <multikey name="cgi_environment_variable" datatype="key-value"
       attribute="cgi_environment_variables"/>

  <key name="dns_ip_address" datatype="ipaddr-or-hostname"/>

  <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="integer"
     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="logger" name="event"/>

  <section type="logger" name="access"/>
 
  <section type="logger" name="trace"/>

  <multisection type="server" name="*" attribute="servers"/>

  <multisection type="database" name="*" attribute="databases"/>

  <!-- schema ends  -->

</schema>