r873 - zope-common/branches/etch

lunar at alioth.debian.org lunar at alioth.debian.org
Mon May 14 11:51:32 UTC 2007


Author: lunar
Date: 2007-05-14 11:51:31 +0000 (Mon, 14 May 2007)
New Revision: 873

Added:
   zope-common/branches/etch/debian/
   zope-common/branches/etch/dzhandle
   zope-common/branches/etch/dzhandle.sgml
Log:
zope-common=0.5.31 is the version currently in etch.


Copied: zope-common/branches/etch/debian (from rev 871, zope-common/tags/0.5.31/debian)

Copied: zope-common/branches/etch/dzhandle (from rev 871, zope-common/tags/0.5.31/dzhandle)
===================================================================
--- zope-common/branches/etch/dzhandle	                        (rev 0)
+++ zope-common/branches/etch/dzhandle	2007-05-14 11:51:31 UTC (rev 873)
@@ -0,0 +1,2394 @@
+#! /usr/bin/python
+
+# Copyright (C) 2005-2006 Matthias Klose <doko at ubuntu.com>
+#                         Fabio Tranchitella <kobold at debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#
+
+import os
+import sys
+import re
+import glob
+import fnmatch
+import shutil
+import pwd, grp
+import logging
+import subprocess
+
+from optparse import OptionParser
+
+program = os.path.basename(sys.argv[0])
+
+# Debian/Ubuntu zope packages:
+# ----------------------------
+
+zope_packages = (
+    { 'name': 'zope2.6',
+      'version': '2.6',
+      'prefix': '/usr/lib/zope',
+      'instance': '/var/lib/zope/instance',
+      'zeoinstance': '/var/lib/zope/zeo',
+      'pyver': '2.2'
+      },
+    { 'name': 'zope2.7',
+      'version': '2.7',
+      'prefix': '/usr/lib/zope2.7',
+      'instance': '/var/lib/zope2.7/instance',
+      'zeoinstance': '/var/lib/zope2.7/zeo',
+      'pyver': '2.3'
+      },
+    { 'name': 'zope2.8',
+      'version': '2.8',
+      'prefix': '/usr/lib/zope2.8',
+      'instance': '/var/lib/zope2.8/instance',
+      'zeoinstance': '/var/lib/zope2.8/zeo',
+      'pyver': '2.3'
+      },
+    { 'name': 'zope2.9',
+      'version': '2.9',
+      'prefix': '/usr/lib/zope2.9',
+      'instance': '/var/lib/zope2.9/instance',
+      'zeoinstance': '/var/lib/zope2.9/zeo',
+      'pyver': '2.4'
+      },
+    { 'name': 'zope2.10',
+      'version': '2.10',
+      'prefix': '/usr/lib/zope2.10',
+      'instance': '/var/lib/zope2.10/instance',
+      'zeoinstance': '/var/lib/zope2.10/zeo',
+      'pyver': '2.4'
+      },
+    { 'name': 'zope3',
+      'version': '3',
+      'prefix': '/usr/lib/zope3',
+      'instance': '/var/lib/zope3/instance',
+      'zeoinstance': '/var/lib/zope3/zeo',
+      'pyver': '2.4'
+      }
+    )
+
+# installation types for addons:
+# ------------------------------
+# ADDON_MASTER      addon from Debian package in /usr/lib/zope
+# ADDON_LINKED      addon in instance, addon directory symlinked to ADDON_MASTER
+# ADDON_TREELINKED  addon in instance, all files in addon symlinked to ADDON_MASTER
+# ADDON_COPIED      addon in instance, copied from ADDON_MASTER
+# ADDON_MANUAL      addon in instance without .dzfile, manually copied?
+
+NO_ADDON, ADDON_MASTER, ADDON_LINKED, ADDON_TREELINKED, ADDON_COPIED, ADDON_MANUAL = range(6)
+addon_options = ['-', 'master', 'linked', 'tree-linked', 'copied', 'manually installed']
+addon_techniques = addon_options[ADDON_LINKED:ADDON_COPIED+1]
+addon_modes = ['all', 'manual']
+
+def addon_technique_name(code):
+    return addon_options[code]
+
+def addon_technique_code(name):
+    return addon_options.index(name)
+
+known_actions = {}
+def register_action(action_class):
+    known_actions[action_class.name] = action_class
+
+personal_conf = {}
+
+class DZError(Exception):
+    pass
+
+# --------------------------------------------------------------------------- #
+
+def is_root():
+    return os.getuid() == 0
+
+def strlist(sequence):
+    return ', '.join(["`%s'" % s for s in sequence])
+
+def available_zope_versions():
+    return [z['version'] for z in zope_packages if os.path.isdir(z['prefix'])]
+
+def filter_zope_version(version):
+    return [z for z in zope_packages if z['version'] == version][0]
+
+def read_config_file(fn, required=None):
+    attrs = {}
+    for line in file(fn):
+        line = line.strip()
+        if not line or line.startswith('#'):
+            continue
+        try:
+            key, value = [field.strip() for field in line.split(':', 1)]
+	    if value.count('#') > 0: value = value.split('#')[0].strip()
+        except:
+            raise DZError, "error reading %s\n\t%s" % (fn, line)
+        if attrs.has_key(key):
+            raise DZError, "duplicate field `%s' in %s" % (key, fn)
+        if value == '':
+            raise DZError, "missing value for field `%s' in %s" % (key, fn)
+        attrs[key] = value
+    if required:
+        for attr in required:
+            if not attrs.has_key(attr):
+                raise DZError, "missing field `%s' in %s" % (attr, fn)
+    return attrs
+
+def write_config_file(fn, attrs, uid=None, gid=None):
+    fd = file(fn, 'w')
+    for key, value in attrs.items():
+        if value != None:
+            fd.write('%s: %s\n' % (key, value))
+    fd.close()
+
+# --------------------------------------------------------------------------- #
+
+class InstanceListFile:
+    def __init__(self, mode, addon_name, oklist=True):
+        if oklist:
+            self.fn = '/var/lib/zope/dzhandle/%s__%s' % (mode, addon_name)
+        else:
+            self.fn = '/var/lib/zope/dzhandle/%s_failed__%s' % (mode, addon_name)
+        self.read()
+
+    def read(self):
+        """read file with <instance name> <instance version> lines,
+        return list of fields, empty list, if file does not exist"""
+        self.lines = []
+        try:
+            fd = file(self.fn)
+            for line in fd.readlines():
+                fields = line.split()
+                if not (fields[:2]) in self.lines:
+                    self.lines.append(fields[:2])
+            self.exists = True
+            fd.close()
+        except IOError:
+            self.exists = False
+
+    def write(self):
+        if self.lines:
+            fd = file(self.fn, 'w')
+            for name, version in self.lines:
+                fd.write("%s %s\n" % (name, version))
+            fd.close()
+        else:
+            os.unlink(self.fn)
+
+    def remove(self, instance):
+        """remove instance from list and write file,
+        unlink file, if list is empty"""
+        try:
+            self.lines.remove([instance.name, instance.version])
+        except ValueError:
+            return
+        self.write()
+
+    def append(self, instance):
+        """append instance information to instance list"""
+        info = [instance.name, instance.version]
+        if info in self.lines:
+            return
+        self.lines.append(info)
+        self.write()
+
+    def get_lines(self):
+        for line in self.lines:
+            yield line
+
+class Addon:
+    def __init__(self, path, dzfile_required=True):
+        self.type = None
+        if not os.path.isdir(path):
+            raise DZError, "%s directory doesn't exist: `%s'" % (self.kind, path)
+        self.path = path
+        if not os.path.isfile(os.path.join(path, self.dzname)):
+            if dzfile_required:
+                raise DZError, "`%s' file not found`%s'" \
+                      % (self.dzname, os.path.join(self.path, self.dzname))
+            self.type = ADDON_MANUAL
+            self.name = os.path.basename(path)
+            self.package = None
+            self.version = None
+            self.depends = None
+            self.recommends = None
+            self.suggests = None
+            self.is_global = False
+            self.zopeversions = " ".join([z['version'] for z in zope_packages])
+        else:
+            self.read_dzfile()
+        if self.type == None:
+            if path.startswith('/usr/share/zope') or \
+               path.startswith('/usr/lib/zope'):
+                self.type = ADDON_MASTER
+            elif os.path.islink(path):
+                self.type = ADDON_LINKED
+            elif os.path.islink(os.path.join(path, self.dzname)):
+                self.type = ADDON_TREELINKED
+            else:
+                self.type = ADDON_COPIED
+
+    def read_dzfile(self):
+        fn = os.path.join(self.path, self.dzname)
+        attrs = read_config_file(fn, ('Name', 'Package', 'Version'))
+        self.name = attrs['Name']
+        self.package = attrs['Package']
+        self.version = attrs['Version']
+        self.directory = attrs.get('Directory', self.name)
+        if not os.path.basename(self.path) in (self.name, self.directory):
+            raise DZError, "Product or Directory field doesn't match path in `%s'" % fn
+        self.depends = attrs.get('Depends', None)
+        self.recommends = attrs.get('Recommends', None)
+        self.suggests = attrs.get('Suggests', None)
+        self.zopeversions = attrs.get('ZopeVersions', \
+                            " ".join([z['version'] for z in zope_packages]))
+        self.is_global = (attrs.get('Global', None) == 'yes')
+    
+    def write_dzfile(self, fn=None):
+        fn = fn or os.path.join(self.path, self.dzname)
+        fd = file(fn, 'w')
+        fd.write('Package: %s\n' % self.package)
+        fd.write('Name: %s\n' % self.name)
+        fd.write('Version: %s\n' % self.version)
+        if self.name != self.directory:
+            fd.write('Directory: %s\n' % self.directory)
+        if self.depends:
+            fd.write('Depends: %s\n' % self.depends)
+        if self.recommends:
+            fd.write('Recommends: %s\n' % self.recommends)
+        if self.suggests:
+            fd.write('Suggests: %s\n' % self.suggests)
+        if self.zopeversions:
+            fd.write('ZopeVersions: %s\n' % self.zopeversions)
+        fd.close()
+
+    def available_in_instance(self, instance):
+        """checks if the addon is available in the instance
+        """
+
+        if self.is_global:
+            ipath = os.path.join(instance.home, self.subdir, self.name + '.installed')
+            return os.path.exists(ipath)
+
+        ipath = os.path.join(instance.home, self.subdir, self.name)
+        return os.path.isdir(ipath)
+
+    def installed_in_instance(self, instance):
+        """If the addon is available in the instance, return it, else return None.
+        """
+        
+        if self.is_global:
+            ipath = os.path.join(instance.home, self.subdir, self.name + '.installed')
+            return os.path.exists(ipath) and self or None
+
+        ipath = os.path.join(instance.home, self.subdir, self.name)
+        if not os.path.isdir(ipath):
+            return None
+        iaddon = self.__class__(ipath,
+                                dzfile_required=\
+                                os.path.isfile(os.path.join(ipath, self.dzname)))
+        return iaddon
+    
+    def installed_by_in_instances(self, instances, exclude=[]):
+        """Return a list of addons, which are installed by this master addon in
+        instances.  Filter out all instances with a addon type found in exclude.
+        """
+        assert self.type == ADDON_MASTER
+
+        iaddons = []
+        for instance in instances:
+            iaddon = self.installed_in_instance(instance)
+            if iaddon and not iaddon.type in exclude and iaddon.installed_by(self):
+                iaddons.append(iaddon)
+        return iaddons
+
+    def find_master(self):
+        """locate the addon, that this addon was installed from"""
+
+        if self.type == ADDON_MANUAL:
+            return None
+        master = None
+        for prefix in ('/usr/share/zope', '/usr/lib/zope'):
+            path = os.path.join(prefix, self.subdir, self.directory)
+            try:
+                master = self.addonClass(path)
+            except DZError, msg:
+                pass
+            else:
+                break
+        if not master:
+            raise DZError, msg
+        return master
+        
+    def installed_by(self, master):
+        """checks if the installed addon was installed by the `master' addon
+        """
+        assert self.type != ADDON_MASTER and master.type == ADDON_MASTER or self.is_global
+        
+        if self.is_global:
+            return True
+        elif self.name != master.name or self.package != master.package:
+            # other addon, or same addon installed by other package
+            return False
+        elif self.type == ADDON_COPIED:
+            # copied from master to instance, version may mismatch
+            return True
+        
+        # consistency checks ...
+        realpath = os.path.realpath(self.path)
+        master_realpath = os.path.realpath(master.path)
+        if self.type == ADDON_LINKED and realpath != master_realpath:
+            print "CONSISTENCY CHECK FAILED: %s != %s" % (realpath, master_realpath)
+            return False
+        realpath = os.path.realpath(os.path.join(self.path, self.dzname))
+        master_realpath = os.path.realpath(os.path.join(master.path, self.dzname))
+        if self.type == ADDON_TREELINKED and realpath != master_realpath:
+            print "CONSISTENCY CHECK FAILED: %s != %s" % (realpath, master_realpath)
+            return False
+        
+        return True
+        
+    def printit(self, stream=sys.stdout):
+        print >>stream, self.kind, self.name, self.directory, \
+            self.package, self.version, self.path, self.depends
+        
+class ProductAttributes:
+    kind = 'product'
+    subdir = 'Products'
+    dzname = '.dzproduct'
+
+class ExtensionAttributes:
+    kind = 'extension'
+    subdir = 'Extensions'
+    dzname = '.dzextension'
+
+class Product(ProductAttributes, Addon):
+    pass
+
+class Extension(ExtensionAttributes, Addon):
+    pass
+
+ProductAttributes.addonClass = Product
+ExtensionAttributes.addonClass = Extension
+
+# --------------------------------------------------------------------------- #
+
+class DZAction:
+    _option_parser = None
+    name = None
+    help = ""
+    def __init__(self):
+        self.errors_occured = 0
+        parser = self.get_option_parser()
+        parser.set_usage(
+            'usage: %s [<option> ...] %s [<option> ...]' % (program, self.name))
+
+    def get_option_parser(self):
+        if not self._option_parser:
+            p = OptionParser()
+            self._option_parser = p
+        return self._option_parser
+
+    def info(self, msg, stream=sys.stderr):
+        logging.info('%s %s: %s', program, self.name, msg)
+
+    def warn(self, msg, stream=sys.stderr):
+        logging.warn('%s %s: %s', program, self.name, msg)
+
+    def error(self, msg, stream=sys.stderr, go_on=False):
+        logging.error('%s %s: %s', program, self.name, msg)
+        self.errors_occured += 1
+        if not go_on:
+            sys.exit(1)
+
+    def parse_args(self, arguments):
+        self.options, self.args = self._option_parser.parse_args(arguments)
+        return self.options, self.args
+
+    def check_args(self, global_options):
+        return self.errors_occured
+
+    def run(self, global_opts):
+        pass
+
+# --------------------------------------------------------------------------- #
+
+class DZPreinst(DZAction):
+    kind = None
+    def get_option_parser(self):
+        if not self._option_parser:
+            p = OptionParser()
+            p.add_option('-m', '--mode',
+                         action='store', dest='mode')
+            self._option_parser = p
+        return self._option_parser
+
+    def check_args(self, global_options):
+        if len(self.args) != 3:
+            self.error('--mode=<script args> <package> <directory> <%s name>' % self.kind)
+        if self.options.mode == None:
+            self.error("missing `--mode=<script args>'")
+        self.mode_args = self.options.mode.split()
+        if not self.mode_args[0] in ('install', 'upgrade'):
+            self.error("unknown mode `%s'" % self.options.mode)
+        self.mode = self.mode_args[0]
+        del self.mode_args[0]
+        package, directory, addon_name = self.args
+
+        ilist = InstanceListFile(self.mode, addon_name)
+        if ilist.exists:
+            self.info("file `%s' exists" % ilist.fn)
+        ilist = InstanceListFile(self.mode, addon_name, oklist=False)
+        if ilist.exists:
+            self.info("file `%s' exists" % ilist.fn)
+
+        # nothing to check for upgrades
+        if self.mode == 'upgrade':
+            return self.errors_occured
+        
+        instances = locate_instances(versions=global_options.zversion)
+        for instance in instances:
+            addon_path = os.path.join(instance.home, self.subdir, addon_name)
+            
+            if instance.is_addon_excluded(addon_name):
+                continue
+            if not os.path.exists(addon_path):
+                # not installed in instance
+                continue
+            try:
+                iaddon = self.addonClass(directory, dzfile_required=False)
+            except DZError, msg:
+                # some error reading the dzfile ... 
+                # maybe the product hasn't been unpacked yet, let's skip it
+                continue
+            if iaddon.type == ADDON_MANUAL:
+                if instance.addon_installation_required():
+                    self.error("keep manually installed %s `%s' in instance "
+                              "`%s' (%s), ignoring %s `%s' from package `%s'." %
+                              (self.kind, iaddon.name, instance.name, instance.version,
+                               self.kind, addon_name, package),
+                               go_on=True)
+                else:
+                    self.info("keep manually installed %s `%s' in instance "
+                              "`%s' (%s), ignoring %s `%s' from package `%s'." %
+                              (self.kind, iaddon.name, instance.name, instance.version,
+                               self.kind, addon_name, package))
+                continue
+            if (iaddon.package, iaddon.name, iaddon.directory) \
+                   != (package, addon_name, os.path.basename(directory)):
+                # not installed from this product
+                self.info("found %s `%s' installed from package `%s' in instance "
+                          "`%s' (%s) while installing package `%s'." %
+                          (self.kind, iaddon.name, iaddon.package,
+                           instance.name, instance.version, package))
+                continue
+            if iaddon.type == ADDON_COPIED:
+                if instance.addon_installation_required():
+                    self.error("keep copied %s `%s' in instance "
+                               "`%s' (%s), ignoring %s `%s' from package `%s'." %
+                               (self.kind, iaddon.name, instance.name, instance.version,
+                                self.kind, addon_name, package),
+                               go_on=True)
+                    self.warn("keep copied %s `%s' in instance "
+                              "`%s' (%s), ignoring %s `%s' from package `%s'." %
+                              (self.kind, iaddon.name, instance.name, instance.version,
+                               self.kind, addon_name, package))
+                continue
+            assert iaddon.type in (ADDON_LINKED, ADDON_TREELINKED)
+
+        return self.errors_occured
+
+    def run(self, global_options):
+        pass
+        
+class DZPreinstProduct(ProductAttributes, DZPreinst):
+    name = 'preinst-%s' % ProductAttributes.kind
+    help = 'handle preinst of a packaged %s' % ProductAttributes.kind
+
+class DZPreinstExtension(ExtensionAttributes, DZPreinst):
+    name = 'preinst-%s' % ExtensionAttributes.kind
+    help = 'handle preinst of a packaged %s' % ExtensionAttributes.kind
+
+register_action(DZPreinstProduct)
+register_action(DZPreinstExtension)
+
+
+class DZPostinst(DZAction):
+    kind = None
+    def get_option_parser(self):
+        if not self._option_parser:
+            p = OptionParser()
+            p.add_option('-m', '--mode',
+                         action='store', dest='mode')
+            self._option_parser = p
+        return self._option_parser
+
+    def check_args(self, global_options):
+        if len(self.args) != 1:
+            self.error('--mode=<script args> <directory>')
+        if self.options.mode == None:
+            self.error("missing `--mode=<script arguments>'")
+        self.mode_args = self.options.mode.split()
+        if not self.mode_args[0] in ('configure', 'abort-upgrade', 'abort-remove'):
+            self.error("unknown mode `%s'" % self.options.mode)
+        self.mode = self.mode_args[0]
+        del self.mode_args[0]
+        
+        directory = self.args[0]
+        try:
+            self.addon = addon = self.addonClass(directory)
+        except DZError, msg:
+            self.error(msg)
+
+        all_instances = locate_instances(versions=global_options.zversion)
+
+        if self.mode == 'configure':
+            # instances which are beeing upgraded go first
+            self.upgrade_ilist = InstanceListFile('upgrade', addon.directory)
+            self.toupgrade = []
+            for i_name, i_version in self.upgrade_ilist.get_lines():
+                match = [i for i in all_instances
+                         if i.name == i_name and i.version == i_version]
+                if len(match) > 0:
+                    self.toupgrade.append(match[0])
+            
+            self.toinstall = self.toupgrade[:]
+
+            # new installations, same checks as in 'preinst install'
+            for instance in all_instances:
+                addon_path = os.path.join(instance.home, self.subdir, addon.name)
+
+                if instance in self.toupgrade:
+                    continue
+                if instance.is_addon_excluded(addon.name):
+                    continue
+                if not instance.addon_installation_required():
+                    continue
+                if not os.path.exists(addon_path):
+                    # not installed in instance
+                    if instance.version in self.addon.zopeversions:
+                        self.toinstall.append(instance)
+                    continue
+                try:
+                    iaddon = self.addonClass(addon_path, dzfile_required=False)
+                except DZError, msg:
+                    # some error reading the dzfile ...
+                    self.error(msg, go_on=True)
+                    continue
+                if iaddon.type == ADDON_MANUAL:
+                    if instance.addon_installation_required():
+                        self.error("keep manually installed %s `%s' in instance "
+                                  "`%s' (%s), ignoring %s `%s' from package `%s'." %
+                                  (self.kind, iaddon.name, instance.name, instance.version,
+                                   self.kind, addon.name, addon.package),
+                                   go_on=True)
+                    else:
+                        self.info("keep manually installed %s `%s' in instance "
+                                  "`%s' (%s), ignoring %s `%s' from package `%s'." %
+                                  (self.kind, iaddon.name, instance.name, instance.version,
+                                   self.kind, addon.name, addon.package))
+                    continue
+                if (iaddon.package, iaddon.name, iaddon.directory) \
+                       != (addon.package, addon.name, os.path.basename(directory)):
+                    # not installed from this product
+                    self.info("found %s `%s' installed from package `%s' in instance "
+                              "`%s' (%s) while installing package `%s'." %
+                              (self.kind, iaddon.name, iaddon.package,
+                               instance.name, instance.version, addon.package))
+                    continue
+                if iaddon.type == ADDON_COPIED:
+                    if instance.addon_installation_required():
+                        self.error("keep copied %s `%s' in instance "
+                                   "`%s' (%s), ignoring %s `%s' from package `%s'." %
+                                   (self.kind, iaddon.name, instance.name, instance.version,
+                                    self.kind, addon.name, addon.package),
+                                   go_on=True)
+                        self.warn("keep copied %s `%s' in instance "
+                                  "`%s' (%s), ignoring %s `%s' from package `%s'." %
+                                  (self.kind, iaddon.name, instance.name, instance.version,
+                                   self.kind, addon.name, addon.package))
+                    continue
+                assert iaddon.type in (ADDON_LINKED, ADDON_TREELINKED)
+                self.toinstall.append(instance)
+                
+        elif self.mode == 'abort-upgrade':
+            self.info("%s nyi" % self.mode)
+        elif self.mode == 'abort-remove':
+            self.info("%s nyi" % self.mode)
+
+        return self.errors_occured
+        
+    def run(self, global_options):
+        if self.mode == 'configure':
+            for instance in self.toinstall:
+                if instance in self.toupgrade:
+                    self.toupgrade.remove(instance)
+                    try:
+                        instance.add_addon(self.addon, global_options)
+                    except DZError, msg:
+                        failed_ilist = InstanceListFile('upgrade', self.addon.directory, oklist=False)
+                        failed_ilist.append(instance)
+                        self.error(msg)
+                    else:
+                        self.upgrade_ilist.remove(instance)
+                else:
+                    try:
+                        instance.add_addon(self.addon, global_options)
+                    except DZError, msg:
+                        self.error(msg)
+                instance.handle_restart_policy()
+                
+        elif self.mode == 'abort-upgrade':
+            self.info("%s nyi" % self.mode)
+        elif self.mode == 'abort-remove':
+            self.info("%s nyi" % self.mode)
+        
+class DZPostinstProduct(ProductAttributes, DZPostinst):
+    name = 'postinst-%s' % ProductAttributes.kind
+    help = 'handle postinst of a packaged %s' % ProductAttributes.kind
+
+class DZPostinstExtension(ExtensionAttributes, DZPostinst):
+    name = 'postinst-%s' % ExtensionAttributes.kind
+    help = 'handle postinst of a packaged %s' % ExtensionAttributes.kind
+
+register_action(DZPostinstProduct)
+register_action(DZPostinstExtension)
+
+
+class DZPrerm(DZAction):
+    kind = None
+    def get_option_parser(self):
+        if not self._option_parser:
+            p = OptionParser()
+            p.add_option('-m', '--mode',
+                         action='store', dest='mode')
+            self._option_parser = p
+        return self._option_parser
+
+    def check_args(self, global_options):
+        if len(self.args) != 1:
+            self.error('--mode=<script args> <directory>')
+        if self.options.mode == None:
+            self.error("missing `--mode=<script arguments>'")
+        self.mode_args = self.options.mode.split()
+        if not self.mode_args[0] in ('upgrade', 'remove'):
+            self.error("unknown mode `%s'" % self.options.mode)
+        self.mode = self.mode_args[0]
+        del self.mode_args[0]
+
+        directory = self.args[0]
+        try:
+            self.addon = self.addonClass(directory)
+        except DZError, msg:
+            self.error(msg)
+
+        instances = locate_instances(versions=global_options.zversion)
+        self.toremove = []
+        for instance in instances:
+            iaddon = self.addon.installed_in_instance(instance)
+            if not iaddon:
+                continue
+            elif iaddon.installed_by(self.addon):
+                # consider removal
+                if iaddon.type == ADDON_COPIED:
+                    if global_options.force:
+                        self.toremove.append((iaddon, instance))
+                    else:
+                        self.warn("keep copy of %s `%s' in `%s'"
+                                  % (iaddon.kind, iaddon.name, instance.name))
+                        
+                else:
+                    assert iaddon.type in (ADDON_LINKED, ADDON_TREELINKED) or \
+                           iaddon.is_global
+                    if self.mode == 'remove' and instance.addon_mode == 'manual':
+                        self.warn("denied removal of %s `%s' from instance `%s' (%s)"
+                                   " in addon-mode `%s'"
+                                   % (iaddon.kind, iaddon.name, instance.name,
+                                      instance.version, instance.addon_mode))
+                    else:
+                        self.toremove.append((iaddon, instance))
+            else:
+                # manually installed addon, or installed by another package
+                self.warn("keep %s `%s' in instance `%s' not installed from `%s'" %
+                          (iaddon.kind, iaddon.name, instance.name, self.addon.path))
+
+        return self.errors_occured
+
+    def run(self, global_options):
+        for iaddon, instance in self.toremove:
+            try:
+                instance.remove_addon(self.addon, global_options)
+            except DZError, msg:
+                failed_ilist = InstanceListFile(self.mode,
+                                                self.addon.directory,
+                                                oklist=False)
+                failed_ilist.append(instance)
+                self.error(msg)
+            else:
+                if self.mode == 'upgrade':
+                    ilist = InstanceListFile(self.mode, self.addon.directory)
+                    ilist.append(instance)
+
+        
+class DZPrermProduct(ProductAttributes, DZPrerm):
+    name = 'prerm-%s' % ProductAttributes.kind
+    help = 'handle prerm of a packaged %s' % ProductAttributes.kind
+
+class DZPrermExtension(ExtensionAttributes, DZPrerm):
+    name = 'prerm-%s' % ExtensionAttributes.kind
+    help = 'handle prerm of a packaged %s' % ExtensionAttributes.kind
+
+register_action(DZPrermProduct)
+register_action(DZPrermExtension)
+
+
+class DZPostrm(DZAction):
+    kind = None
+    def get_option_parser(self):
+        if not self._option_parser:
+            p = OptionParser()
+            p.add_option('-m', '--mode',
+                         action='store', dest='mode')
+            self._option_parser = p
+        return self._option_parser
+
+    def check_args(self, global_options):
+        if len(self.args) != 3:
+            self.error('--mode=<script args> <package> <directory> <%s name>' % self.kind)
+        if self.options.mode == None:
+            self.error("missing `--mode=<script arguments>'")
+        self.mode_args = self.options.mode.split()
+        if not self.mode_args[0] in ('remove'):
+            self.error("unknown mode `%s'" % self.options.mode)
+        self.mode = self.mode_args[0]
+        del self.mode_args[0]
+        package, directory, addon_name = self.args
+        return self.errors_occured
+    
+    def run(self, global_options):
+        pass
+        
+class DZPostrmProduct(ProductAttributes, DZPostrm):
+    name = 'postrm-%s' % ProductAttributes.kind
+    help = 'handle postrm of a packaged %s' % ProductAttributes.kind
+
+class DZPostrmExtension(ExtensionAttributes, DZPostrm):
+    name = 'postrm-%s' % ExtensionAttributes.kind
+    help = 'handle postrm of a packaged %s' % ExtensionAttributes.kind
+
+register_action(DZPostrmProduct)
+register_action(DZPostrmExtension)
+
+
+class DZDinstall(DZAction):
+    kind = None
+    def get_option_parser(self):
+        if not self._option_parser:
+            p = OptionParser()
+            p.add_option('-r', '--restart',
+                         action='store', dest='restart', default='end')
+            self._option_parser = p
+        return self._option_parser
+
+    def check_args(self, global_options):
+        if not self.args:
+            self.error('no %ss to install' % self.kind)
+        if not self.options.restart in ('configuring', 'end', 'manually'):
+            self.error("invalid restart argument `%s'" % self.options.restart)
+        self.addons = []
+        for arg in self.args:
+            try:
+                addon = self.addonClass(arg)
+            except DZError, msg:
+                self.error(msg)
+            self.addons.append(addon)
+
+        instances = locate_instances()
+        # consider instances with Addon-Mode 'all' only
+        self.instances = [i for i in instances if i.addon_installation_required()]
+        self.toinstall = []
+        for addon in self.addons:
+            for instance in self.instances:
+                iaddon = addon.installed_in_instance(instance)
+                if iaddon:
+                    # verify it's from the same package
+                    if iaddon.installed_by(addon):
+                        # safe to upgrade
+                        self.toinstall.append((addon, iaddon, instance))
+                    elif iaddon.type == ADDON_MANUAL:
+                        self.error("found manually installed %s `%s' in instance "
+                                   "`%s' (%s) while installing package `%s'. Keep it" %
+                                   (self.kind, iaddon.name, instance.name, instance.version,
+                                    addon.package),
+                                   go_on=True)
+                    elif iaddon.package != addon.package:
+                        self.error("found %s `%s' installed from package `%s' in instance "
+                                   "`%s' (%s) while installing package `%s'. Keep it" %
+                                   (self.kind, iaddon.name, iaddon.package,
+                                    instance.name, instance.version, addon.package),
+                                   go_on=True)
+                else:
+                    self.toinstall.append((addon, None, instance))
+        return self.errors_occured
+
+    def run(self, global_options):
+        for addon, old_addon, instance in self.toinstall:
+            if old_addon:
+                instance.remove_addon(old_addon, global_options)
+            instance.add_addon(addon, global_options)
+            instance.handle_restart_policy()
+
+class DZDinstallProduct(ProductAttributes, DZDinstall):
+    name = 'dinstall-product'
+    help = 'install a packaged product'
+
+class DZDinstallExtension(ExtensionAttributes, DZDinstall):
+    name = 'dinstall-extension'
+    help = 'install a packaged extension'
+
+register_action(DZDinstallProduct)
+register_action(DZDinstallExtension)
+
+
+class DZDremove(DZAction):
+    kind = None
+    def get_option_parser(self):
+        if not self._option_parser:
+            p = OptionParser()
+            self._option_parser = p
+        return self._option_parser
+
+    def check_args(self, global_options):
+        if not self.args:
+            self.error('no %ss to remove' % self.kind)
+        self.addons = []
+        for arg in self.args:
+            try:
+                addon = self.addonClass(arg)
+            except DZError, msg:
+                self.error(msg)
+            else:
+                self.addons.append(addon)
+
+        instances = locate_instances()
+        self.toremove = []
+        for addon in self.addons:
+            for instance in instances:
+                iaddon = addon.installed_in_instance(instance)
+                if not iaddon:
+                    continue
+                if iaddon.installed_by(addon):
+                    # consider removal
+                    if iaddon.type == ADDON_COPIED:
+                        if global_options.force:
+                            self.toremove.append((addon, iaddon, instance))
+                        else:
+                            self.warn("leaving copy of %s `%s' in `%s'"
+                                      % (iaddon.kind, iaddon.name, instance.name))
+                    else:
+                        assert iaddon.type in (ADDON_LINKED, ADDON_TREELINKED)
+                        self.toremove.append((addon, iaddon, instance))
+                else:
+                    # manually installed addon, or installed by another package
+                    self.warn("leaving %s `%s' in instance `%s' not installed from `%s'" %
+                              (iaddon.kind, iaddon.name, instance.name, addon.path))
+        return self.errors_occured
+                    
+    def run(self, global_options):
+        for addon, iaddon, instance in self.toremove:
+            instance.remove_addon(addon, global_options)
+
+
+class DZDremoveProduct(ProductAttributes, DZDremove):
+    name = 'dremove-product'
+    help = 'remove a packaged product'
+    
+class DZDremoveExtension(ExtensionAttributes, DZDremove):
+    name = 'dremove-extension'
+    help = 'remove a packaged extension'
+
+register_action(DZDremoveProduct)
+register_action(DZDremoveExtension)
+
+
+class DZAddAddon(DZAction):
+    kind = None
+    def get_option_parser(self):
+        if not self._option_parser:
+            p = OptionParser()
+            p.add_option('-l', '--lazy',
+                         help="add missing addons only (error on manually installed addons)",
+                         default=False, action='store_true', dest='lazy')
+            p.add_option('-t', '--installation-technique',
+                         help="how to install addons (linked, tree-linked, copied)",
+                         default=None, action='store', dest='atechnique')
+            self._option_parser = p
+        return self._option_parser
+
+    def find_addon(self, arg, lazy = False):
+        candidates = [addon for addon in self.locatedaddons
+                      if (getattr(addon, 'directory', None) == arg) and \
+                         addon.zopeversions.count(self.instance.version) > 0]
+
+        if not candidates:
+            candidates = [addon for addon in self.locatedaddons
+                          if (addon.name == arg) and \
+                              addon.zopeversions.count(self.instance.version) > 0]
+
+        if candidates: return candidates[0]
+        elif not lazy: self.error("unknown %s `%s'" % (self.kind, arg))
+        else: return None
+
+    def find_related(self, related, installed):
+        if not related:
+            return []
+
+        depends = []
+        for d in related.replace(' ','').split(','):
+            if d.count('|') > 0:
+                match = None
+                for alt in d.split('|'):
+                    match = [i for i in installed if i.name == alt or \
+                             getattr(i, 'directory', None) == alt]
+                    if match: break
+                if match: continue
+
+                for alt in d.split('|'):
+                    match = self.find_addon(alt, True)
+                    if match: break
+            else:
+                match = self.find_addon(d)
+
+            if match and match not in (installed + depends):
+                depends.append(match)
+                depends += self.find_related(match.depends, installed + depends)
+
+        return depends
+
+    def check_args(self, global_options):
+        if len(self.args) < 2:
+            self.error('<instance> <%s> [<%s>]' % (self.kind, self.kind))
+        try:
+            instance = locate_instance(self.args[0],
+                                       versions=global_options.zversion)
+        except DZError, msg:
+            self.error(msg)
+
+        self.instance = instance
+
+        if self.options.atechnique:
+            if not self.options.atechnique in addon_techniques:
+                self.error("unknown addon technique `%s'" % self.options.atechnique)
+            self.options.atechnique = addon_technique_code(self.options.atechnique)
+            if self.options.atechnique != instance.addon_technique \
+                   and not global_options.force:
+                self.error("addon technique `%s' doesn't match "
+                           "the instance's default (`%s'). Use -f to override"
+                           % (self.options.atechnique, instance.addon_technique))
+
+        self.addons = []
+        self.locatedaddons = locate_addons(self.addonClass, zopeversions=global_options.zversion)
+        for arg in self.args[1:]:
+            self.addons.append(self.find_addon(arg))
+
+        to_be_installed = [i.name for i in self.addons]
+
+        self.installed = instance.installed_addons()
+        for i in self.addons:
+            self.addons += self.find_related(i.depends, self.installed + self.addons)
+
+        for addon in self.addons[:]:
+            iaddon = addon.installed_in_instance(instance)
+            if not iaddon:
+                continue
+            if iaddon.installed_by(addon):
+                if self.options.lazy:
+                    # warn about copied addons
+                    if iaddon.type == ADDON_COPIED:
+                        self.warn("%s `%s' (%s, %s) in instance `%s' (%s) . Keep it" %
+                                  (self.kind, iaddon.name, iaddon.version,
+                                   addon_technique_name(iaddon.type),
+                                   instance.name, instance.version))
+                    self.addons.remove(addon)
+                elif addon.name not in to_be_installed:
+                    self.addons.remove(addon)
+                else:
+                    self.error("%s `%s' (%s, %s) in instance `%s' (%s) . Keep it" %
+                           (self.kind, iaddon.name, iaddon.version,
+                            addon_technique_name(iaddon.type),
+                            instance.name, instance.version),
+                           go_on=True)
+            elif iaddon.type == ADDON_MANUAL:
+                self.error("manually installed %s `%s' in instance `%s' (%s). Keep it" %
+                           (self.kind, iaddon.name, instance.name, instance.version),
+                           go_on=True)
+            else:
+                self.error("found %s `%s' installed from package `%s' in instance "
+                           "`%s' (%s) while adding adding %s from `%s'. Keep it" %
+                           (self.kind, iaddon.name, iaddon.package,
+                            instance.name, instance.version, self.kind, addon.package),
+                           go_on=True)
+
+        return self.errors_occured
+    
+    def run(self, global_options):
+        for addon in self.addons:
+            self.instance.add_addon(addon, global_options, self.options.atechnique)
+            self.info("added %s `%s' to instance `%s' (%s)"
+                      % (self.kind, addon.name, self.instance.name, self.instance.version))
+
+
+class DZAddProduct(ProductAttributes, DZAddAddon):
+    name = 'add-product'
+    help = 'add a product to an instance'
+
+class DZAddExtension(ExtensionAttributes, DZAddAddon):
+    name = 'add-extension'
+    help = 'add an extension to an instance'
+
+register_action(DZAddProduct)
+register_action(DZAddExtension)
+
+
+class DZRemoveAddon(DZAction):
+    kind = None
+    def get_option_parser(self):
+        if not self._option_parser:
+            p = OptionParser()
+            p.add_option('-f', '--force',
+                         help="force removal of %ss" % self.kind,
+                         default=False, action='store_true', dest='force')
+            p.add_option('-l', '--lazy',
+                         help="don't complain about already removed addons",
+                         default=False, action='store_true', dest='lazy')
+            self._option_parser = p
+        return self._option_parser
+
+    def check_args(self, global_options):
+        if len(self.args) < 2:
+            self.error('<instance> <%s> [<%s>]' % (self.kind, self.kind))
+        try:
+            self.instance = locate_instance(self.args[0],
+                                            versions=global_options.zversion)
+        except DZError, msg:
+            self.error(msg)
+
+        addons = []
+        for arg in self.args[1:]:
+            if ':' in arg:
+                addon_base, addon_suffix = arg.split(':', 1)
+            else:
+                addon_base, addon_suffix = arg, ''
+            ipath = os.path.join(self.instance.home, self.subdir, addon_base)
+            if os.path.exists(ipath + '.installed'):
+                path = open(ipath + '.installed', 'r').read()
+                addon = self.addonClass(path, dzfile_required=False)
+                addons.append((addon, addon))
+                continue
+            elif not os.path.isdir(ipath):
+                if not self.options.lazy:
+                    self.error("%s `%s' not found in instance `%s' (%s)"
+                               % (self.kind, addon_base, self.instance.name, self.instance.version),
+                               go_on=True)
+                continue
+            try:
+                iaddon = self.addonClass(ipath, dzfile_required=False)
+            except DZError, msg:
+                self.error(msg, go_on=True)
+            else:
+                try:
+                    addon = iaddon.find_master()
+                except DZError, msg:
+                    self.error(msg, go_on=True)
+                else:
+                    addons.append((addon, iaddon))
+
+        for addon, iaddon in addons:
+            if global_options.force or self.options.force:
+                continue
+            if iaddon.type in (ADDON_COPIED, ADDON_MANUAL):
+                self.error("not removing %s %s `%s' from instance `%s', use -f to override"
+                           % (addon_technique_name(iaddon.type), self.kind, iaddon.name,
+                              self.instance.name),
+                           go_on=True)
+            elif self.instance.addon_mode == 'manual':
+                # linked or tree-linked
+                self.error("not removing %s %s `%s' from manually managed instance `%s', use -f to override"
+                           % (addon_technique_name(iaddon.type), self.kind, iaddon.name,
+                              self.instance.name),
+                           go_on=True)
+                    
+        self.addons = addons
+        return self.errors_occured
+
+    def run(self, global_options):
+        for addon, iaddon in self.addons:
+            try:
+                self.instance.remove_addon(addon, global_options)
+                self.info("removed %s `%s' from instance `%s' (%s)"
+                          % (self.kind, addon.directory, self.instance.name, self.instance.version))
+            except DZError, msg:
+                self.error(msg, go_on=True)
+
+        if self.errors_occured:
+            sys.exit(1)
+
+class DZRemoveProduct(ProductAttributes, DZRemoveAddon):
+    name = 'remove-product'
+    help = 'remove a product from an instance'
+
+class DZRemoveExtension(ExtensionAttributes, DZRemoveAddon):
+    name = 'remove-extension'
+    help = 'remove an extension from an instance'
+
+register_action(DZRemoveProduct)
+register_action(DZRemoveExtension)
+
+
+class DZListAddons(DZAction):
+    def get_option_parser(self):
+        if not self._option_parser:
+            p = OptionParser()
+            self._option_parser = p
+        return self._option_parser
+
+    def check_args(self, global_options):
+        if not self.args:
+            # list available products
+            self.instance = None
+        elif len(self.args) == 1:
+            try:
+                self.instance = locate_instance(self.args[0],
+                                                versions=global_options.zversion)
+            except DZError, msg:
+                self.error(msg)
+        else:
+            self.error('[<instance>]')
+        return self.errors_occured
+
+    def run(self, global_options):
+        if self.instance:
+            print 'Listing products installed for instance', self.instance.name
+            addons = self.instance.installed_addons()
+        else:
+            if len(global_options.zversion) > 1:
+                print 'Listing products available for all version(s):', ' '.join(global_options.zversion)
+            addons = locate_addons(self.addonClass, zopeversions=global_options.zversion)
+        for addon in addons:
+            addon.printit()
+
+class DZListProducts(ProductAttributes, DZListAddons):
+    name = 'list-products'
+    help = 'show all products managed by dzhandle'
+
+class DZListExtensions(ExtensionAttributes, DZListAddons):
+    name = 'list-extensions'
+    help = 'show all extensions managed by dzhandle'
+
+register_action(DZListProducts)
+register_action(DZListExtensions)
+
+
+class DZListInstances(DZAction):
+    name = 'list-instances'
+    help = 'print list of instances'
+
+    def check_args(self, global_options):
+        if len(self.args) > 1:
+            self.error("[<instance pattern>]")
+        self.instances = locate_instances(versions=global_options.zversion)
+        if self.args:
+            pattern = self.args[0]
+            instances = [i for i in self.instances if fnmatch.fnmatch(i.name, pattern)]
+            if not instances:
+                self.error("no instance matching pattern `%s'" % pattern)
+            self.instances = instances
+        return self.errors_occured
+
+    def run(self, global_options):
+        for instance in self.instances:
+            print instance.formatted_str(global_options.verbose)
+            if global_options.verbose:
+                pass
+
+register_action(DZListInstances)
+ 
+
+class DZListZeoInstances(DZAction):
+    name = 'list-zeoinstances'
+    help = 'print list of ZEO instances'
+
+    def check_args(self, global_options):
+        if len(self.args) > 1:
+            self.error("[<instance pattern>]")
+        self.instances = locate_zeoinstances(versions=global_options.zversion)
+        if self.args:
+            pattern = self.args[0]
+            instances = [i for i in self.instances if fnmatch.fnmatch(i.name, pattern)]
+            if not instances:
+                self.error("no ZEO instance matching pattern `%s'" % pattern)
+            self.instances = instances
+        return self.errors_occured
+
+    def run(self, global_options):
+        for instance in self.instances:
+            print instance.formatted_str(global_options.verbose)
+            if global_options.verbose:
+                pass
+
+register_action(DZListZeoInstances)
+
+
+class DZRestartPendingInstances(DZAction):
+    name = 'restart-pending-instances'
+    help = "restart instances with `restart-pending' markers"
+
+    def check_args(self, global_options):
+        if self.args:
+            self.error("no arguments required")
+        return self.errors_occured
+
+    def run(self, global_options):
+        for zversion in available_zope_versions():
+            zope = filter_zope_version(version=zversion)
+            pattern = os.path.join(zope['instance'], '*', 'var', 'restart-pending')
+            for pending in glob.glob(pattern):
+                home = os.path.dirname(os.path.dirname(pending))
+                instance = ZopeInstance(home, zope['version'])
+                if instance.is_running():
+                    instance.zopectl('restart')
+                os.unlink(pending)
+
+        # compatibility with old 2.6 installations
+        if os.path.exists('/var/run/zope.restart'):
+            subprocess.call(['/usr/sbin/invoke-rc.d', 'zope', 'restart'])
+            os.unlink('/var/run/zope.restart')
+
+register_action(DZRestartPendingInstances)
+
+
+class DZShowInstance(DZAction):
+    name = 'show-instance'
+    help = 'print information about an instance'
+
+    def check_args(self, global_options):
+        if not self.args:
+            self.error('no instance to list')
+        if len(self.args) > 1:
+            self.error('<instance>')
+        try:
+            self.instance = locate_instance(self.args[0],
+                                            versions=global_options.zversion)
+        except DZError, msg:
+            self.error(msg)
+        return self.errors_occured
+
+    def run(self, global_options):
+        print self.instance.formatted_str(global_options.verbose)
+
+register_action(DZShowInstance)
+
+
+class DZInstanceRestartPolicy(DZAction):
+    name = 'instance-restart-policy'
+    help = 'get/set a policy on addon installation for an instance'
+
+    def check_args(self, global_options):
+        if not self.args:
+            self.error('<instance> [<policy>]')
+        self.policy = None
+        if len(self.args) > 1:
+            self.policy = self.args[1]
+        elif len(self.args) > 2:
+            self.error('<instance> [<policy>]')
+        try:
+            self.instance = locate_instance(self.args[0],
+                                            versions=global_options.zversion)
+        except DZError, msg:
+            self.error(msg)
+        if self.policy:
+            try:
+                self.instance.set_restart_policy(self.policy)
+            except DZError, msg:
+                self.error(msg)
+        return self.errors_occured
+
+    def run(self, global_options):
+        if self.policy:
+            self.instance.set_restart_policy(self.policy)
+            self.instance.write_dzfile(global_options.uid, global_options.gid)
+        else:
+            print self.instance.restart_policy
+
+register_action(DZInstanceRestartPolicy)
+
+
+class DZInstanceAddonTechnique(DZAction):
+    name = 'instance-addon-technique'
+    help = 'get/set an addon install technique for an instance'
+
+    def check_args(self, global_options):
+        if not self.args:
+            self.error('<instance> [<addon technique>]')
+        self.technique = None
+        if len(self.args) > 1:
+            self.technique = self.args[1]
+            if not self.technique in addon_techniques:
+                self.error("unknown addon technique `%s'" % self.technique)
+        elif len(self.args) > 2:
+            self.error('<instance> [<addon technique>]')
+        try:
+            self.instance = locate_instance(self.args[0],
+                                            versions=global_options.zversion)
+        except DZError, msg:
+            self.error(msg)
+        return self.errors_occured
+
+    def run(self, global_options):
+        if self.technique:
+            # set technique
+            self.instance.set_addon_technique(self.technique)
+            self.instance.write_dzfile(global_options.uid, global_options.gid)
+        else:
+            print self.instance.addon_technique
+
+register_action(DZInstanceAddonTechnique)
+
+
+class DZInstanceAddonMode(DZAction):
+    name = 'instance-addon-mode'
+    help = 'set an addon mode for an instance'
+
+    def check_args(self, global_options):
+        if not self.args:
+            self.error('<instance> [<addon mode>]')
+        self.addon_mode = None
+        if len(self.args) > 1:
+            self.addon_mode = self.args[1]
+            if not self.addon_mode in addon_modes:
+                self.error("unknown addon mode `%s'" % self.addon_mode)
+        elif len(self.args) > 2:
+            self.error('<instance> [<addon mode>]')
+        try:
+            self.instance = locate_instance(self.args[0],
+                                            versions=global_options.zversion)
+        except DZError, msg:
+            self.error(msg)
+        return self.errors_occured
+
+    def run(self, global_options):
+        if self.addon_mode:
+            # set addon_mode
+            self.instance.set_addon_mode(self.addon_mode)
+            self.instance.write_dzfile(global_options.uid, global_options.gid)
+            # TODO: manual -> all: add addons
+        else:
+            print self.instance.addon_mode
+
+register_action(DZInstanceAddonMode)
+
+
+class DZMakeInstance(DZAction):
+    name = 'make-instance'
+    help = 'run zope version mkzopeinstance'
+
+    def __init__(self):
+        DZAction.__init__(self)
+        self.get_option_parser().set_usage(
+            'usage: %s [<option> ...] %s <instance-name> [<option> ...]' % (program, self.name))
+
+    def get_option_parser(self):
+        if not self._option_parser:
+            p = OptionParser()
+            p.add_option('-m', '--addon-mode',
+                         help="which products and extension to install (all, manual)",
+                         action='store', dest='amode')
+            p.add_option('-t', '--addon-install-technique',
+                         help="how to install addons (linked, tree-linked, copied)",
+                         default="tree-linked", action='store', dest='atechnique')
+            p.add_option('-r', '--restart',
+                         help="when to restart on configuration (configuring, end, manually)",
+                         default="end", action='store', dest='restart')
+            p.add_option('-u', '--user', help="user and password for the initial user (user:password)",
+                         action='store', dest='user')
+            p.add_option('', '--service-user', help="system user used to run this instance (user:group)",
+                         action='store', dest='srvuser', default='zope:zope')
+            p.add_option('', '--service-port', help="HTTP port used to run this instance",
+                         action='store', dest='srvport', default='9673')
+            self._option_parser = p
+        return self._option_parser
+
+    def check_args(self, global_options):
+        if len(self.args) != 1:
+            self._option_parser.print_help()
+            sys.exit(1)
+
+        if not global_options.zversion:
+            self.error('no zope version to make instance for')
+
+        if len(global_options.zversion) > 1:
+            self.error('ambiguous zope version to make instance for: %s'
+                       % strlist(global_options.zversion))
+
+        if self.options.user and not ":" in self.options.user:
+            self.error('user must be specified as name:password')
+
+        if not is_root() and self.options.srvuser == 'zope:zope':
+            self.uid = os.getuid()
+            self.gid = os.getgid()
+            self.options.srvuser = "%s:%s" % \
+                (pwd.getpwuid(os.getuid())[0], grp.getgrgid(os.getgid())[0])
+        elif not ":" in self.options.srvuser:
+            self.error('service user must be specified as user:group')
+        else:
+            try:
+                uid, gid = self.options.srvuser.split(":")
+                self.uid = pwd.getpwnam(uid)[2]
+                self.gid = grp.getgrnam(gid)[2]
+            except KeyError, msg:
+                self.error(msg)
+
+        self.zversion = global_options.zversion[0]
+        self.instance_name = self.args[0]
+
+        if not self.options.amode:
+            self.error("missing option -m <addon-mode>")
+        if not self.options.amode in addon_modes:
+            self.error("unknown addon mode `%s'" % self.options.amode)
+
+        if not self.options.atechnique in addon_techniques:
+            self.error("unknown addon technique `%s'" % self.options.atechnique)
+    
+        if not self.options.restart in ('configuring', 'end', 'manually'):
+            self.error("unknown restart policy `%s'" % self.options.restart)
+
+        self.zope = filter_zope_version(version=self.zversion)
+        if is_root():
+            self.instance_home = os.path.join(self.zope['instance'], self.instance_name)
+        else:
+            self.instance_home = os.path.join(personal_conf['instances'], self.zope['name'], self.instance_name)
+        if os.path.exists(self.instance_home):
+            self.error("instance home `%s' already exists" % self.instance_home)
+        return self.errors_occured
+
+    def run(self, global_options):
+        if self.zversion.startswith('2.'):
+            cmd = [os.path.join(self.zope['prefix'], 'bin', 'mkzopeinstance.py')]
+        else:
+            cmd = [os.path.join(self.zope['prefix'], 'bin', 'mkzopeinstance'), '--password-manager=MD5']
+        cmd.append('--dir=%s' % self.instance_home)
+        cmd.append('--layout=%s' % (is_root() and 'fhs' or 'zope'))
+        if self.options.user:
+            cmd.append('--user=' + self.options.user)
+        if self.options.srvuser:
+            cmd.append('--service-user=' + self.options.srvuser)
+        if self.options.srvport:
+            cmd.append('--service-port=' + self.options.srvport)
+
+        # zope3's mkzopeinstance doesn't create the parents dir
+        if self.zope['version'] == '3' and not os.path.isdir(self.instance_home):
+            os.makedirs(self.instance_home)
+            os.rmdir(self.instance_home)
+
+        rv = subprocess.call(cmd)
+        if rv:
+            subprocess.call(['/bin/rm', '-fr', self.instance_home])
+            sys.exit(rv)
+        instance = ZopeInstance(self.instance_home, self.zversion, read_dzfile=False)
+        instance.set_addon_mode(self.options.amode)
+        instance.set_addon_technique(self.options.atechnique)
+        instance.set_restart_policy(self.options.restart)
+        instance.write_dzfile(uid=self.uid, gid=self.gid)
+        if not instance.addon_installation_required():
+            return
+        for addon in locate_addons(zopeversions=global_options.zversion):
+            try:
+                instance.add_addon(addon, global_options, None)
+            except DZError, msg:
+                # more than one product version ..., ignore it for now
+                self.warn(msg)
+
+register_action(DZMakeInstance)
+
+
+class DZMakeZeoInstance(DZAction):
+    name = 'make-zeoinstance'
+    help = 'run zope version mkzeoinstance'
+
+    def __init__(self):
+        DZAction.__init__(self)
+        self.get_option_parser().set_usage(
+            'usage: %s [<option> ...] %s <zeoinstance-name> [<port>]' % (program, self.name))
+
+    def get_option_parser(self):
+        if not self._option_parser:
+            p = OptionParser()
+            self._option_parser = p
+        return self._option_parser
+
+    def check_args(self, global_options):
+        if len(self.args) not in (1, 2):
+            self._option_parser.print_help()
+            sys.exit(1)
+
+        if not global_options.zversion:
+            self.error('no zope version to make instance for')
+
+        if len(global_options.zversion) > 1:
+            self.error('ambiguous zope version to make instance for: %s'
+                       % strlist(global_options.zversion))
+
+        self.zversion = global_options.zversion[0]
+        self.instance_name = self.args[0]
+        if len(self.args) > 1:
+            try:
+                self.port = int(self.args[1])
+            except ValueError:
+                self.error('the zeo-instance port number has to be integer')
+        else:
+            self.port = None
+        self.zope = filter_zope_version(version=self.zversion)
+
+        if is_root():
+            self.instance_home = os.path.join(self.zope['zeoinstance'], self.instance_name)
+        else:
+            self.instance_home = os.path.join(personal_conf['zeoinstances'], self.zope['name'], self.instance_name)
+        if os.path.exists(self.instance_home):
+            self.error("instance home `%s' already exists" % self.instance_home)
+        return self.errors_occured
+
+    def run(self, global_options):
+        if self.zversion.startswith('2.'):
+            cmd = [os.path.join(self.zope['prefix'], 'bin', 'mkzeoinstance.py')]
+        else:
+            cmd = [os.path.join(self.zope['prefix'], 'bin', 'mkzeoinstance')]
+
+        cmd.append(self.instance_home)
+        if self.port != None:
+            cmd.append(str(self.port))
+            
+        rv = subprocess.call(cmd)
+        if rv:
+            subprocess.call(['/bin/rm', '-fr', self.instance_home])
+            sys.exit(rv)
+
+register_action(DZMakeZeoInstance)
+
+
+class DZRemoveInstance(DZAction):
+    name = 'remove-instance'
+    help = 'remove addons from an instance (except data files), mark it as removed'
+
+    def check_args(self, global_options):
+        if len(self.args) != 1:
+            self.error('<instance>')
+        try:
+            self.instance = locate_instance(self.args[0],
+                                            versions=global_options.zversion)
+        except DZError, msg:
+            self.error(msg)
+        return self.errors_occured
+
+    def run(self, global_options):
+        cmd = ["rm", "-rf", 
+               self.instance.home + "/Products",
+               self.instance.home + "/Extensions",
+               self.instance.home + "/lib",
+               self.instance.home + "/bin",
+               self.instance.home + "/inituser",
+               ]
+        rv = subprocess.call(cmd)
+        if rv:
+            sys.exit(rv)
+
+register_action(DZRemoveInstance)
+
+
+class DZPurgeInstance(DZAction):
+    name = 'purge-instance'
+    help = 'purge files in an instance (including data files)'
+
+    def check_args(self, global_options):
+        if len(self.args) != 1:
+            self.error('<instance>')
+        try:
+            self.instance = locate_instance(self.args[0],
+                                            versions=global_options.zversion)
+        except DZError, msg:
+            self.error(msg)
+        return self.errors_occured
+
+    def run(self, global_options):
+        cmd = ["rm", "-rf", 
+               self.instance.home,
+               "/etc/zope%s/%s" % (self.instance.version, self.instance.name),
+               "/var/log/zope%s/%s" % (self.instance.version, self.instance.name),
+               ]
+        rv = subprocess.call(cmd)
+        if rv:
+            sys.exit(rv)
+
+register_action(DZPurgeInstance)
+
+
+class DZPurgeZeoInstance(DZAction):
+    name = 'purge-zeoinstance'
+    help = 'purge files in a ZEO instance (including data files)'
+
+    def check_args(self, global_options):
+        if len(self.args) != 1:
+            self.error('<zeo_instance>')
+        try:
+            self.instance = locate_zeoinstance(self.args[0],
+                                            versions=global_options.zversion)
+        except DZError, msg:
+            self.error(msg)
+        return self.errors_occured
+
+    def run(self, global_options):
+        cmd = ["rm", "-rf", 
+               self.instance.home]
+        rv = subprocess.call(cmd)
+        if rv:
+            sys.exit(rv)
+
+register_action(DZPurgeZeoInstance)
+
+
+class DZZopectl(DZAction):
+    name = 'zopectl'
+    help = 'call zopectl for a given instance'
+
+    def get_option_parser(self):
+        if not self._option_parser:
+            class UnknownOptionParser(OptionParser):
+                def parse_args(self, args):
+                    opts, tmp = OptionParser.parse_args(self, [])
+                    return opts, args
+            p = UnknownOptionParser()
+            self._option_parser = p
+        return self._option_parser
+
+    def check_args(self, global_options):
+        if len(self.args) < 1 or self.args[0].startswith('-'):
+            self.error('<instance> <zdctl-action> [<zdctl options>]')
+        try:
+            self.instance = locate_instance(self.args[0],
+                                            versions=global_options.zversion)
+        except DZError, msg:
+            self.error(msg)
+        if len(self.args) > 1 and self.args[1].startswith('-'):
+            print >>sys.stderr, ("missing zdctl action, entering interactive mode ...")
+        if [arg for arg in self.args if arg in ('-i', '--interactive')]:
+            print >>sys.stderr, ("entering interactive mode after executing command ...")
+        return self.errors_occured
+
+    def run(self, global_options):
+        rv = self.instance.zopectl(self.args[1:])
+        if rv:
+            sys.exit(rv)
+
+register_action(DZZopectl)
+
+
+class DZZeoctl(DZAction):
+    name = 'zeoctl'
+    help = 'call zeoctl for a given instance'
+
+    def get_option_parser(self):
+        if not self._option_parser:
+            class UnknownOptionParser(OptionParser):
+                def parse_args(self, args):
+                    opts, tmp = OptionParser.parse_args(self, [])
+                    return opts, args
+            p = UnknownOptionParser()
+            self._option_parser = p
+        return self._option_parser
+
+    def check_args(self, global_options):
+        if len(self.args) < 1 or self.args[0].startswith('-'):
+            self.error('<instance> <zeoctl-action> [<zeoctl options>]')
+        try:
+            self.instance = locate_zeoinstance(self.args[0],
+                                            versions=global_options.zversion)
+        except DZError, msg:
+            self.error(msg)
+        if len(self.args) > 1 and self.args[1].startswith('-'):
+            print >>sys.stderr, ("missing zeoctl action, entering interactive mode ...")
+        if [arg for arg in self.args if arg in ('-i', '--interactive')]:
+            print >>sys.stderr, ("entering interactive mode after executing command ...")
+        return self.errors_occured
+
+    def run(self, global_options):
+        rv = self.instance.zeoctl(self.args[1:])
+        if rv:
+            sys.exit(rv)
+
+register_action(DZZeoctl)
+
+
+class ZopeInstance:
+    def __init__(self, home, version, read_dzfile = True):
+        self.home = home
+        self.name = os.path.basename(home)
+        self.version = version
+        self.addon_technique = None
+        self.restart_policy = None
+        self.addon_mode = None
+        self.excluded_addons = []
+        self.new_layout = os.path.exists(os.path.join(home, 'bin', 'runzope'))
+        self.is_purged = False
+        self.is_purged |= not os.path.exists(os.path.join(home, 'var', 'Data.fs'))
+        self.is_purged |= not os.path.exists(os.path.join(home, 'var', 'Data.fs.index'))
+        self.is_removed = False
+        self.is_removed |= not os.path.exists(os.path.join(home, 'bin', 'zopectl'))
+        if os.path.isfile(os.path.join(home, 'inituser')):
+            self.userfile = 'inituser'
+        elif os.path.isfile(os.path.join(home, 'access')):
+            self.userfile = 'access'
+        else:
+            self.userfile = None
+        if read_dzfile: self.read_dzfile()
+        self._installed_addons = None
+
+    def set_addon_technique(self, addon_technique):
+        old_technique = self.addon_technique
+        if isinstance(addon_technique, str):
+            self.addon_technique = list(addon_options).index(addon_technique)
+        elif isinstance(addon_technique, int):
+            self.addon_technique = addon_technique
+        else:
+            raise TypeError, "unknown type for addon technique"
+        if old_technique != None and self.addon_technique != old_technique:
+            print >>sys.stderr, "WARNING: change of instance addon technique not yet supported"
+        # manage addons
+        # self.write_dzfile()
+
+    def set_restart_policy(self, policy):
+        if policy in (None, 'configuring', 'end', 'manually'):
+            self.restart_policy = policy
+        else:
+            raise ValueError, "unknown restart policy `%s'" % policy
+
+    def set_addon_mode(self, addon_mode):
+        if addon_mode in addon_modes:
+            self.addon_mode = addon_mode
+        else:
+            raise ValueError, "unknown addon_mode `%s'" % addon_mode
+
+    def addon_installation_required(self, addon=None):
+        return self.addon_mode == 'all'
+
+    def is_addon_excluded(self, name):
+        return name in self.excluded_addons
+
+    def read_dzfile(self):
+        fn = os.path.join(self.home, 'etc', 'debian_policy')
+        self.dzfile_exists = os.path.exists(fn)
+        if not self.dzfile_exists:
+            self.set_addon_mode('manual')
+            self.set_addon_technique(ADDON_MANUAL)
+            self.set_restart_policy('manually')
+            return
+        attrs = read_config_file(fn, required=['Name', 'Addon-Mode',
+                                               'Addon-Technique', 'Restart-Policy'])
+
+        try:
+            self.set_addon_technique(attrs['Addon-Technique'])
+        except ValueError:
+            raise DZError, \
+                  "unknown addon technique `%s' in `%s'" % (attrs['Addon-Technique'], fn)
+
+        try:
+            self.set_addon_mode(attrs['Addon-Mode'])
+        except ValueError:
+            raise DZError, \
+                  "unknown addon mode `%s' in `%s'" % (attrs['Addon-Mode'], fn)
+
+        try:
+            self.set_restart_policy(attrs['Restart-Policy'])
+        except KeyError:
+            pass
+        except ValueError, msg:
+            raise DZError, "%s in `%s'" % (attrs['Restart-Policy'], fn)
+
+        try:
+            self.excluded_addons = attrs['Excluded-Addons'].split()
+        except KeyError:
+            pass
+
+        if attrs['Name'] != self.name:
+            raise DZError, "instance name `%s' doesn't match in `%s'" % (attrs['Name'], fn)
+            
+    def write_dzfile(self, uid=None, gid=None):
+        fn = os.path.join(self.home, 'etc', 'debian_policy')
+        if os.path.exists(fn):
+            try:
+                os.path.unlink(fn + '.old')
+            except:
+                pass
+            os.rename(fn, fn + '.old')
+        attrs = {'Name': self.name,
+                 'Addon-Technique': addon_technique_name(self.addon_technique),
+                 'Addon-Mode': self.addon_mode,
+                 'Restart-Policy': self.restart_policy
+                 }
+        if self.excluded_addons:
+            attrs['Excluded-Addons'] = ' '.join(self.excluded_addons)
+        write_config_file(fn, attrs, uid=uid, gid=gid)
+
+    def installed_addons(self):
+        if self._installed_addons == None:
+            self._installed_addons = locate_addons(instance=self)
+        return self._installed_addons
+
+    def add_addon(self, addon, global_options, addon_technique=None):
+        installed = [a for a in self.installed_addons() if addon.name == a.name]
+        if installed:
+            a = installed[0]
+            print "%s `%s' already available in instance `%s'" % (a.kind, a.name, self.name)
+            return
+
+        addon_technique = addon_technique or self.addon_technique
+        if addon_technique == ADDON_MANUAL:
+            return
+        assert addon_technique in (ADDON_LINKED, ADDON_TREELINKED, ADDON_COPIED)
+
+        if self.version.startswith("3"):
+            files = []
+            files.extend(glob.glob(os.path.join(addon.path, '*-configure.zcml')))
+            files.extend(glob.glob(os.path.join(addon.path, '*-meta.zcml')))
+            files.extend(glob.glob(os.path.join(addon.path, '*-ftesting.zcml')))
+            for f in files:
+                filename = os.path.split(f)[1]
+                os.symlink(f, os.path.join(self.home, "etc/package-includes/", filename))
+
+        target_path = os.path.join(self.home, addon.subdir, addon.name)
+        if addon.is_global:
+            open(target_path + '.installed', 'w').write(addon.path)
+            target_path = addon.path
+        elif addon_technique == ADDON_LINKED:
+            os.symlink(addon.path, target_path)
+            if global_options.verbose:
+                print "linked: %s -> %s" % (target_path, addon.path)
+        elif addon_technique == ADDON_TREELINKED:
+            copytree(addon.path, target_path, copy_all=False,
+                     uid=global_options.uid, gid=global_options.gid)
+            if global_options.verbose:
+                print "tree linked: %s -> %s" % (target_path, addon.path)
+        elif addon_technique == ADDON_COPIED:
+            copytree(addon.path, target_path, copy_all=True,
+                     uid=global_options.uid, gid=global_options.gid)
+            if global_options.verbose:
+                print "copied: %s -> %s" % (addon.path, target_path)
+
+        added_addon = addon.addonClass(target_path, False)
+        self._installed_addons.append(added_addon)
+        logging.info("added %s `%s'", added_addon.kind, added_addon.path)
+
+    def remove_addon(self, addon, global_options):
+        installed = [a for a in self.installed_addons() if getattr(addon, 'name', None) == a.name]
+        if len(installed) != 1:
+            raise DZError, "%s `%s' not installed in instance `%s'" \
+                  % (addon.kind, addon.name, self.name)
+        installed = installed[0]
+        assert addon.name == installed.name
+
+        if self.version.startswith("3"):
+            files = []
+            files.extend(glob.glob(os.path.join(addon.path, '*-configure.zcml')))
+            files.extend(glob.glob(os.path.join(addon.path, '*-meta.zcml')))
+            files.extend(glob.glob(os.path.join(addon.path, '*-ftesting.zcml')))
+            for f in files:
+                filename = os.path.split(f)[1]
+                target = os.path.join(self.home, "etc/package-includes/", filename)
+                if os.path.exists(target):
+                    os.unlink(target)
+
+        if addon.is_global:
+            os.unlink(os.path.join(self.home, addon.subdir, addon.name) + '.installed')
+        elif installed.type == ADDON_LINKED:
+            os.unlink(installed.path)
+        elif installed.type in (ADDON_MANUAL, ADDON_COPIED) and not global_options.force:
+            raise DZError, "not removing copied or manually installed %s `%s'" \
+                  % (installed.kind, installed.name)
+        elif installed.type in (ADDON_TREELINKED, ADDON_COPIED) and not global_options.force:
+            if os.path.islink(installed.path):
+                os.unlink(installed.path)
+            else:
+                try:
+                    deltree(addon.path, installed.path)
+                except Exception, msg:
+                    raise DZError, msg
+        elif global_options.force:
+            if os.path.islink(installed.path):
+                os.unlink(installed.path)
+            else:
+                shutil.rmtree(installed.path)
+        else:
+            return
+
+        self._installed_addons.remove(installed)
+        logging.info("removed %s `%s'", installed.kind, installed.path)
+
+    def handle_restart_policy(self, policy='end', reason='-'):
+        # instance restart policy overwrites option
+        if self.restart_policy:
+            policy = self.restart_policy
+
+        if not self.is_running():
+            return
+
+        if policy == 'manually':
+            return
+        elif policy == 'end' and self.is_running():
+            fd = file(os.path.join(self.home, 'var', 'restart-pending'), 'a+')
+            fd.write(reason + '\n')
+            fd.close()
+        elif policy == 'configuring':
+            rv = self.zopectl('restart')
+            if rv:
+                sys.exit(rv)
+
+    def is_running(self):
+        cmd = [os.path.join(self.home, 'bin', 'zopectl'), 'status']
+        return os.path.isfile(cmd[0]) and \
+            "program running;" in subprocess.Popen(args=cmd,
+            stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout.read()
+
+    def zopectl(self, *args):
+        cmd = [os.path.join(self.home, 'bin', 'zopectl')]
+        os.chdir(self.home)
+        if args and isinstance(args[0], list):
+            cmd.extend(args[0])
+        else:
+            cmd.extend(args)
+        return subprocess.call(cmd)
+        
+    def formatted_str(self, verbose):
+        s = '%(name)-20s %(version)-5s ' % self.__dict__
+        s += ' addon-mode=%s' % self.addon_mode
+        if isinstance(self.addon_technique, int):
+            addon_technique = addon_options[self.addon_technique]
+        else:
+            addon_technique = self.addon_technique
+        s += ' addon-technique=%s' % addon_technique
+        if self.userfile: s += ' userfile=' + self.userfile
+        if not self.new_layout: s += ' layout=(2.5 and before)'
+        if self.is_purged: s += ' purged'
+        elif self.is_removed: s += ' removed'
+        if verbose:
+            pass
+        return s
+
+
+class ZeoInstance:
+
+    def __init__(self, home, version):
+        self.home = home
+        self.name = os.path.basename(home)
+        self.version = version
+
+    def is_running(self):
+        cmd = [os.path.join(self.home, 'bin', 'zeoctl'), 'status']
+        return not "not running" in subprocess.Popen(args=cmd,
+            stdout=subprocess.PIPE, stderr=subprocess.STDOUT).read()
+
+    def zeoctl(self, *args):
+        cmd = [os.path.join(self.home, 'bin', 'zeoctl')]
+        os.chdir(self.home)
+        if args and isinstance(args[0], list):
+            cmd.extend(args[0])
+        else:
+            cmd.extend(args)
+        return subprocess.call(cmd)
+        
+    def formatted_str(self, verbose):
+        s = '%(name)-20s %(version)-5s ' % self.__dict__
+        if verbose:
+            pass
+        return s
+
+
+def copytree(sourcedir, destdir, copy_all=True, uid=None, gid=None):
+    os.mkdir(destdir)
+    destdir = os.path.abspath(destdir)
+    shutil.copymode(sourcedir, destdir)
+    if not uid is None:
+        os.chown(destdir, uid, gid)
+    saved_pwd = os.getcwd()
+    os.chdir(sourcedir)
+    for root, dirs, files in os.walk(sourcedir):
+        if root == sourcedir:
+            relroot = ''
+        else:
+            relroot = root[len(sourcedir)+1:]
+        for name in files:
+            source = os.path.join(root, name)
+            target = os.path.join(destdir, relroot, name)
+            if copy_all and os.path.islink(source):
+                print "copy target of symlink %s" % source
+            if copy_all:
+                shutil.copy2(source, target)
+                if not uid is None:
+                    os.chown(target, uid, gid)
+            else:
+                os.symlink(source, target)
+        for name in dirs:
+            source = os.path.join(root, name)
+            target = os.path.join(destdir, relroot, name)
+            if os.path.islink(source):
+                print "symlink replaced by directory %s" % source
+            os.mkdir(target)
+            shutil.copymode(sourcedir, destdir)
+            if not uid is None:
+                os.chown(target, uid, gid)
+    os.chdir(saved_pwd)
+
+def deltree(sourcedir, destdir):
+    """remove files found in sourcedir in destdir, remove
+    corresponding .'py[co]' files as well"""
+
+    destdir = os.path.abspath(destdir)
+    dzfile = None
+    if os.path.exists(os.path.join(destdir, '.dzproduct')):
+        dzfile = os.path.join(destdir, '.dzproduct')
+    elif os.path.exists(os.path.join(destdir, '.dzextension')):
+        dzfile = os.path.join(destdir, '.dzextension')
+    if dzfile:
+        saved_dzfile = os.path.join(os.path.dirname(destdir),
+                                    '.dz.%d' % os.getpid())
+        shutil.move(dzfile, saved_dzfile)
+    try:
+        saved_pwd = os.getcwd()
+        os.chdir(sourcedir)
+        for root, dirs, files in os.walk(sourcedir, topdown=False):
+            if root == sourcedir:
+                relroot = ''
+            else:
+                relroot = root[len(sourcedir)+1:]
+            for name in files:
+                #source = os.path.join(root, name)
+                target = os.path.join(destdir, relroot, name)
+
+                if os.path.exists(target):
+                    os.unlink(target)
+                if target.endswith('.py'):
+                    if os.path.exists(target + 'c'):
+                        os.unlink(target + 'c')
+                    if os.path.exists(target + 'o'):
+                        os.unlink(target + 'o')
+            for name in dirs:
+                #source = os.path.join(root, name)
+                target = os.path.join(destdir, relroot, name)
+                if os.path.islink(target):
+                    # should not happen
+                    os.unlink(target)
+                else:
+                    try:
+                        os.rmdir(target)
+                    except OSError, msg:
+                        pass
+                        #print >>sys.stderr, "rmdir `%s' failed: %s" % (target, msg)
+        os.rmdir(destdir)
+    except (IOError, OSError), msg:
+        print >>sys.stderr, msg
+        if dzfile:
+            shutil.move(saved_dzfile, dzfile)
+        raise
+    else:
+        os.unlink(saved_dzfile)
+    os.chdir(saved_pwd)
+
+def locate_instances(versions=None):
+    instances = []
+    pkgs = [(z['version'], z['instance']) for z in zope_packages
+            if not versions or z['version'] in versions]
+    for version, instance_dir in pkgs:
+        inst_list = glob.glob(os.path.join(instance_dir, '*'))
+        if not is_root():
+            inst_list.extend(glob.glob(os.path.join(
+                personal_conf['instances'], 'zope%s' % version, '*')))
+        for home in inst_list:
+            if os.path.isdir(os.path.join(home, 'var')):
+                instance = ZopeInstance(home, version)
+                instances.append(instance)
+    return instances
+
+def locate_zeoinstances(versions=None):
+    instances = []
+    pkgs = [(z['version'], z['zeoinstance']) for z in zope_packages
+            if not versions or z['version'] in versions]
+    for version, instance_dir in pkgs:
+        inst_list = glob.glob(os.path.join(instance_dir, '*'))
+        if not is_root():
+            inst_list.extend(glob.glob(os.path.join(
+                personal_conf['zeoinstances'], 'zope%s' % version, '*')))
+        for home in inst_list:
+            if os.path.isdir(os.path.join(home, 'var')):
+                instance = ZeoInstance(home, version)
+                instances.append(instance)
+    return instances
+
+def locate_instance(name, versions=None):
+    instances = locate_instances(versions=versions)
+    known = [i for i in instances if i.name == name]
+    if not known:
+        raise DZError, "unknown instance `%s'" % name
+    elif len(known) > 1:
+        s = ', '.join(["`%s' (%s)" % (i.name, i.version) for i in known])
+        raise DZError, "ambiguous instance name `%s' matching %s" % (name, s)
+    return known[0]
+
+def locate_zeoinstance(name, versions=None):
+    instances = locate_zeoinstances(versions=versions)
+    known = [i for i in instances if i.name == name]
+    if not known:
+        raise DZError, "unknown ZEO instance `%s'" % name
+    elif len(known) > 1:
+        s = ', '.join(["`%s' (%s)" % (i.name, i.version) for i in known])
+        raise DZError, "ambiguous ZEO instance name `%s' matching %s" % (name, s)
+    return known[0]
+
+def locate_addons(addonClass=None, arch=None, instance=None, zopeversions=None):
+    if instance:
+        prefixes = [instance.home]
+    elif arch == 'any':
+        prefixes = ['/usr/lib/zope']
+    elif arch == 'all':
+        prefixes = ['/usr/share/zope', '/usr/local/share/zope']
+    else:
+        prefixes = ['/usr/share/zope', '/usr/lib/zope', '/usr/local/share/zope']
+    if addonClass == Product:
+        subdirs = [Product.subdir]
+    elif addonClass == Extension:
+        subdirs = [Extension.subdir]
+    else:
+        subdirs = [Product.subdir, Extension.subdir]
+    locals = []
+    if not instance and not is_root():
+        locals.append(personal_conf['products'])
+    addons = []
+    for d in locals + [os.path.join(pf, sd) for pf in prefixes for sd in subdirs]:
+        for path in glob.glob(os.path.join(d, '*')):
+            if os.path.isfile(path) and path.endswith('.installed'):
+                path = open(path, 'r').read()
+                addonClass = Product
+            elif not os.path.isdir(path):
+                continue
+            elif not addonClass:
+                if os.path.basename(os.path.dirname(path)) == Product.subdir:
+                    addonClass = Product
+                else:
+                    addonClass = Extension
+            addon = addonClass(path, False)
+            zver = False
+            if zopeversions != None:
+                for i in addon.zopeversions.split():
+                    if i in zopeversions:
+                        zver = True
+                        break
+            else:
+                zver = True
+            if zver:
+                addons.append(addon)
+    return addons
+
+# --------------------------------------------------------------------------- #
+
+# print an error message
+def usage(stream, msg=None):
+    print >>stream, msg
+    print >>stream, "use `%s help' for help on actions and arguments" % program
+    print >>stream
+    sys.exit(1)
+
+# match a string with the list of available actions
+def action_matches(action, actions):
+    prog = re.compile('[^-]*?-'.join(action.split('-')))
+    return [a for a in actions if prog.match(a)]
+
+# parse command line arguments
+def parse_options(args):
+    shortusage = 'usage: %prog [<option> ...] <action> [<option> ...]'
+    parser = OptionParser(usage=shortusage)
+    parser.disable_interspersed_args()
+
+    # setup the parsers object
+    parser.remove_option('-h')
+    parser.add_option('-h', '--help',
+                      help='help screen',
+                      action='store_true', dest='help')
+    parser.add_option('-v', '--verbose',
+                      help='verbose mode',
+                      action='store_true', dest='verbose')
+    parser.add_option('-f', '--force',
+                      help='force things, i.e. overwriting, removing stuff',
+                      action='store_true', dest='force')
+    parser.add_option('-z', '--zope-version',
+                      help='limit actions to comma separated list of zope versions',
+                      action='store', dest='zversion')
+    parser.add_option('-n', '--dry-run',
+                      help='do not execute commands, print only (not yet implemented)',
+                      action='store', default=False, dest='dryrun')
+    parser.add_option('-u', '--user',
+                      help='<user>[:<group>] ownership for new and copied files',
+                      action='store', dest='user')
+    parser.add_option('-c', '--config-file',
+                      help='configuration file (default is /etc/dzhandle.conf)',
+                      action='store', dest='conffile', default="/etc/dzhandle.conf")
+    global_options, args = parser.parse_args()
+
+    # Print the help screen and exit
+    if len(args) == 0 or args[0].lower() == 'help' or global_options.help:
+        parser.print_help()
+        print "\nactions:"
+        for n, a in sorted(known_actions.items()):
+            print "  %-21s %s" % (n, a.help)
+        print ""
+        sys.exit(1)
+
+    # dry-run option not yet implemented
+    if global_options.dryrun:
+        print >>sys.stderr, "option --dry-run not yet implemented"
+        sys.exit(1)
+
+    # check if the specified zope versions really exist
+    known_versions = [z['version'] for z in zope_packages]
+    if global_options.zversion:
+        zversion = global_options.zversion.split(',')
+        unknown_versions = [v for v in zversion if not v in known_versions]
+        if unknown_versions:
+            usage(sys.stderr, 'unknown zope version(s) %s' % strlist(unknown_versions))
+        global_options.zversion = zversion
+    else:
+        global_options.zversion = known_versions
+
+    # get the uid/gid for zope user/group
+    global_options.uid = None
+    global_options.gid = None
+    if global_options.user:
+        if ':' in global_options.user:
+            user, group = global_options.user.split(':')
+        else:
+            user = global_options.user
+            group = None
+    elif not is_root():
+        user = pwd.getpwuid(os.getuid())[0]
+        group = grp.getgrgid(os.getgid())[0]
+    else:
+        user = 'zope'
+        group = 'zope'
+    try:
+        user_info = pwd.getpwnam(user)
+        if group:
+            group_info = grp.getgrnam(group)
+        else:
+            group_info = grp.getgrgid(user_info[3])
+    except KeyError, msg:
+        usage(sys.stderr, msg)
+    global_options.uid = user_info[2]
+    global_options.gid = group_info[2]
+
+    # check if the specified action really exists
+    action_name = args[0]
+    del args[0]
+    matching_actions = action_matches(action_name, known_actions.keys())
+    if len(matching_actions) == 0:
+        usage(sys.stderr, "unknown action `%s'" % action_name)
+    elif len(matching_actions) > 1:
+        usage(sys.stderr,
+              "ambiguous action `%s', matching actions: %s"
+              % (action_name, strlist(matching_actions)))
+    else:
+        action_name = matching_actions[0]
+
+    # instantiate an object for the action and parse the remaining arguments
+    action = known_actions[action_name]()
+    action_options, action_names = action.parse_args(args)
+
+    return global_options, action
+
+# setup logging stuff
+def setup_logging(logfile=None, logfilelevel='INFO', loglevel='WARN'):
+    env_level = os.environ.get('DZHANDLE', None)
+    if env_level != None:
+        loglevel = logging.getLevelName(env_level)
+        if isinstance(loglevel, str):
+            loglevel = logging.INFO
+    
+    logging.basicConfig(format='%(message)s',
+                        level=loglevel,
+                        stream=sys.stderr)
+
+    # are we logging to a file?
+    if logfile:
+        logfilelevel = logging.getLevelName(env_level)
+        if isinstance(logfilelevel, str):
+            logfilelevel = logging.INFO
+
+        from logging.handlers import TimedRotatingFileHandler
+        try:
+            fhandler = TimedRotatingFileHandler(logfile, when='D', backupCount=3)
+        except IOError, msg:
+            print >>sys.stderr, msg
+        else:
+            fhandler.setLevel(logfilelevel)
+            formatter = logging.Formatter('%(asctime)s %(levelname)-8s %(message)s')
+            fhandler.setFormatter(formatter)
+            logging.getLogger('').addHandler(fhandler)
+
+# main routine
+def main():
+    global_options, action = parse_options(sys.argv[1:])
+
+    # read config file
+    try:
+        config = read_config_file(global_options.conffile)
+    except IOError:
+        config = {}
+    except DZError, msg:
+        print >>sys.stderr, msg
+        sys.exit(1)
+
+    # read personal configuration file
+    uid = os.getuid()
+    if not is_root():
+        home_dir = pwd.getpwuid(uid)[5]
+        conf = os.path.join(home_dir, '.dzhandle.conf')
+        personal_conf['instances'] = os.path.join(home_dir, 'zope/instance')
+        personal_conf['products'] = os.path.join(home_dir, 'zope/products')
+        personal_conf['zeoinstances'] = os.path.join(home_dir, 'zope/zeo')
+        if os.path.exists(conf):
+            personal_conf.update(read_config_file(conf))
+        else:
+            write_config_file(conf, personal_conf)
+
+    # setup logging stuff
+    setup_logging(config.get('logfile', None),
+                  config.get('logfilelevel', logging.INFO),
+                  config.get('loglevel', logging.WARN)
+                  )
+    logging.debug('dzhandle ' + ' '.join(sys.argv[1:]))
+
+    # check the arguments according to the action called
+    if action.check_args(global_options):
+        sys.exit(1)
+
+    # run the action and exit
+    rv = action.run(global_options)
+    sys.exit(rv)
+
+# call the main routine
+if __name__ == '__main__':
+    main()

Copied: zope-common/branches/etch/dzhandle.sgml (from rev 871, zope-common/tags/0.5.31/dzhandle.sgml)
===================================================================
--- zope-common/branches/etch/dzhandle.sgml	                        (rev 0)
+++ zope-common/branches/etch/dzhandle.sgml	2007-05-14 11:51:31 UTC (rev 873)
@@ -0,0 +1,326 @@
+<!doctype refentry PUBLIC "-//Davenport//DTD DocBook V3.0//EN" [
+
+  <!-- Fill in your name for FIRSTNAME and SURNAME. -->
+  <!ENTITY dhfirstname "<firstname>Fabio</firstname>">
+  <!ENTITY dhsurname   "<surname>Tranchitella</surname>">
+  <!-- Please adjust the date whenever revising the manpage. -->
+  <!ENTITY dhdate      "<date>Aug 19, 2005</date>">
+  <!-- SECTION should be 1-8, maybe w/ subsection other parameters are
+       allowed: see man(7), man(1). -->
+  <!ENTITY dhsection   "<manvolnum>1</manvolnum>">
+  <!ENTITY dhemail     "<email>kobold at debian.org</email>">
+  <!ENTITY dhusername  "Fabio Tranchitella">
+  <!ENTITY dhucpackage "<refentrytitle>dzhandle</refentrytitle>">
+  <!ENTITY dhpackage   "dzhandle">
+
+  <!ENTITY debian      "<productname>Debian GNU/Linux</productname> and <productname>Ubuntu Linux</productname>">
+  <!ENTITY gnu         "<acronym>GNU</acronym>">
+]>
+
+<refentry>
+    <docinfo>
+        <address>&dhemail;</address>
+        <author>&dhfirstname; &dhsurname;</author>
+        <copyright>
+            <year>2005</year>
+            <holder>&dhusername;</holder>
+        </copyright>
+        &dhdate;
+    </docinfo>
+
+    <refmeta>
+        &dhucpackage;
+        &dhsection;
+    </refmeta>
+
+    <refnamediv>
+        <refname>&dhpackage;</refname>
+        <refpurpose>Debian/Ubuntu Zope packages handling command line utility </refpurpose>
+    </refnamediv>
+
+
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>&dhpackage;</command>
+      <arg>options</arg>
+      <command>action</command>
+      <arg>action options</arg>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <refsect1>
+    <title>DESCRIPTION</title>
+        
+    <para>
+      <command>&dhpackage;</command> is the command-line utility for handling 
+      Zope servers, instances and products. It is a system administration tool and a 
+      Debian/Ubuntu maintainer's helper script at the same time: using &dhpackage; 
+      you can manage your Zope installations, create instances, add Zope products to 
+      them, start and stop them, but it is also used by the Debian/Ubuntu Zope 
+      packages for their installation and removal.
+    </para>
+  </refsect1>
+
+  <refsect1>
+    <title>OPTIONS</title>
+    <variablelist>
+      <varlistentry>
+        <term><option>-h, --help</option></term>
+        <listitem><para>Print usage information and exit</para></listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><option>-f, --force</option></term>
+        <listitem><para>Force things, for example file overwriting or removing</para></listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><option>-z, --zope-version=ZVERSIONS</option></term>
+        <listitem><para>limit actions to a comma separated list of zope versions; default behaviour is to act on all zope versions available</para></listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><option>-u, --user=USER[:GROUP]</option></term>
+        <listitem><para>User/Group ownership for new and copied files</para></listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><option>-c, --config-file=FILE</option></term>
+        <listitem><para>Configuration file; default is /etc/dzhandle.conf</para></listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><option>-v, --verbose</option></term>
+        <listitem><para>Enable verbose mode (not yet implemented)</para></listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><option>-n, --dry-run</option></term>
+        <listitem><para>Do not execute commands, print only them (not yet implemented)</para></listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+
+  <refsect1>
+    <title>ACTIONS (instances handling)</title>
+    <variablelist>
+      <varlistentry>
+        <term><option>make-instance</option> &lt;instance&gt;</term>
+        <listitem>
+          <para>Create a new instance running Zope version specific mkzopeinstance.</para>
+          <variablelist>
+            <varlistentry>
+              <term>-m AMODE, --addon-mode=AMODE</term>
+              <listitem><para>Which products and extensions will be installed: `all' means every product 
+              or extension available will be automatically installed, `manual' means that the products
+              or extensions will be installed manually through &dhpackage;.</para></listitem>
+            </varlistentry>
+            <varlistentry>
+              <term>-t ATECHNIQUE, --addon-install-technique=ATECHNIQUE</term>
+              <listitem><para>How to install addons: `linked' means the product or extension directory
+              will be symlinked into the instance home, `tree-linked' means the directory structure will 
+              be re-created and then files symlinked, `copied' means the file will be copied into the 
+              instance home.</para></listitem>
+            </varlistentry>
+            <varlistentry>
+              <term>-r RESTART, --restart=RESTART</term>
+              <listitem><para>when to restart on configuration of new products or extensions: `configuring'
+              means after every product or extension configuration, `end' means at the end of the installation
+              of all packages, `manually' means no automatic restart will happen.</para></listitem>
+            </varlistentry>
+            <varlistentry>
+              <term>-u USER, --user=USER</term>
+              <listitem><para>user and password for the initial user (in the form `user:password'); these 
+              settigs can be modified later using zpasswd.py utility from Zope.</para></listitem>
+            </varlistentry>
+            <varlistentry>
+              <term>--service-user=SRVUSER</term>
+              <listitem><para>system user used to run this instance (in the form `user:groupdefault', the
+              default is `zope:zope'); this setting can be modified later editing the zope.conf file of
+              the instance.</para></listitem>
+            </varlistentry>
+            <varlistentry>
+              <term>--service-port=SRVPORT</term>
+              <listitem><para>HTTP port used to run this instance (default 9673); this setting can be modified
+              later editing the zope.conf file of the instance.</para></listitem>
+            </varlistentry>
+          </variablelist>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><option>remove-instance</option> &lt;instance&gt;</term>
+        <listitem><para>Remove an instance (except data files) and mark it as removed.</para></listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><option>purge-instance</option> &lt;instance&gt;</term>
+        <listitem><para>Purge files for an instance (including data files).</para></listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><option>show-instance</option> &lt;instance&gt;</term>
+        <listitem><para>Print a short summary about an instance.</para></listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><option>list-instances</option></term>
+        <listitem><para>Print the list of available instances.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>instance-addon-mode</option> &lt;instance&gt; [&lt;mode&gt;]</term>
+        <listitem><para>Get or set an addon-mode for an instance. </para></listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><option>instance-addon-technique</option> &lt;instance&gt; [&lt;technique&gt;]</term>
+        <listitem><para>Get or set an addon-install-technique for an instance.</para></listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><option>instance-restart-policy</option> &lt;instance&gt; [&lt;restart-policy&gt;]</term>
+        <listitem><para>Get or set a policy on addon-installation for an instance.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>zopectl</option> &lt;instance&gt; &lt;zdctl-action&gt; [&lt;zdctl options&gt;]</term>
+        <listitem><para>Call a zopectl action (e.g. `start', `stop' or `restart') for a given instance.</para></listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><option>restart-pending-instances</option></term>
+        <listitem><para>Restart instances with `restart-pending' markers.</para></listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+
+  <refsect1>
+    <title>ACTIONS (ZEO instances handling)</title>
+    <variablelist>
+      <varlistentry>
+        <term><option>make-zeoinstance</option> &lt;instance&gt;</term>
+        <listitem>
+          <para>Create a new instance running Zope version specific mkzeoinstance.</para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><option>purge-zeoinstance</option> &lt;instance&gt;</term>
+        <listitem><para>Purge files for a ZEO instance (including data files).</para></listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><option>list-zeoinstances</option></term>
+        <listitem><para>Print the list of available ZEO instances.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>zeoctl</option> &lt;instance&gt; &lt;zeotl-action&gt; [&lt;zeotl options&gt;]</term>
+        <listitem><para>Call a zeoctl action (e.g. `start', `stop' or `restart') for a given ZEO instance.</para></listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+
+
+  <refsect1>
+    <title>ACTIONS (products and extensions handling)</title>
+    <variablelist>
+      <varlistentry>
+        <term><option>list-products</option>, <option>list-extensions</option></term>
+        <listitem><para>show all products or extensions managed by dzhandle</para></listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><option>add-product</option>, <option>add-extension</option></term>
+        <listitem>
+          <para>add a product or extension to an instance</para>
+          <variablelist>
+            <varlistentry>
+              <term>-l, --lazy</term>
+              <listitem><para>Add missing addons only (error on manually installed addons).</para></listitem>
+            </varlistentry>
+            <varlistentry>
+              <term>-t ATECHNIQUE, --addon-install-technique=ATECHNIQUE</term>
+              <listitem><para>How to install the specified addons.</para></listitem>
+            </varlistentry>
+          </variablelist>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><option>remove-product</option>, <option>remove-extension</option></term>
+        <listitem>
+          <para>remove a product or extension from an instance</para>
+          <variablelist>
+            <varlistentry>
+              <term>-l, --lazy</term>
+              <listitem><para>Do not complain about already removed addons.</para></listitem>
+            </varlistentry>
+            <varlistentry>
+              <term>-f, --force</term>
+              <listitem><para>Force removal of the addons.</para></listitem>
+            </varlistentry>
+          </variablelist>
+        </listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+
+  <refsect1>
+    <title>ACTIONS (for Zope products maintainers)</title>
+    <para>
+      The following actions should be used inside the maintainer scripts of Debian/Ubuntu
+      packages of Zope products and extensions. If the package uses zope-debhelper's
+      <productname>dh_installzope</productname>, these actions will be automatically included.
+    </para>
+    <variablelist>
+      <varlistentry>
+        <term><option>dinstall-extension, dinstall-product</option></term>
+        <listitem><para>install a packaged extension/product</para></listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><option>dremove-extension, dremove-product</option></term>
+        <listitem><para>remove a packaged extension/product</para></listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><option>postinst-extension, postinst-product</option></term>
+        <listitem><para>handle postinst of a packaged extension/product</para></listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><option>postrm-extension, postrm-product</option></term>
+        <listitem><para>handle postrm of a packaged extension/product</para></listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><option>preinst-extension, preinst-product</option></term>
+        <listitem><para>handle preinst of a packaged extension/product</para></listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><option>prerm-extension, prerm-product</option></term>
+        <listitem><para>handle prerm of a packaged extension/product</para></listitem>
+      </varlistentry>
+
+    </variablelist>
+  </refsect1>
+
+  <refsect1>
+    <title>SEE ALSO</title>
+    <para>dh_installzope(1), dh_installzopeinstance(1)</para>
+  </refsect1>
+
+  <refsect1>
+    <title>AUTHOR</title>
+
+    <para>This manual page was written by &dhusername; &lt;&dhemail;&gt; for
+    the &debian; systems (but may be used by others).</para>
+
+    <para>Permission is granted to copy, distribute and/or modify
+    this document under the terms of the <acronym>GNU</acronym> Free
+    Documentation License, Version 1.1 or any later version
+    published by the Free Software Foundation; with no Invariant
+    Sections, no Front-Cover Texts and no Back-Cover Texts.  A copy
+    of the license can be found under
+    <filename>/usr/share/common-licenses/FDL</filename>.</para>
+  </refsect1>
+
+</refentry>
+
+<!-- Keep this comment at the end of the file
+Local variables:
+mode: sgml
+sgml-omittag:t
+sgml-shorttag:t
+sgml-minimize-attributes:nil
+sgml-always-quote-attributes:t
+sgml-indent-step:2
+sgml-indent-data:t
+sgml-parent-document:nil
+sgml-default-dtd-file:nil
+sgml-exposed-tags:nil
+sgml-local-catalogs:nil
+sgml-local-ecat-files:nil
+End:
+-->




More information about the pkg-zope-commits mailing list