ovs-bugtool: Make available outside of Debian packages.
authorBen Pfaff <blp@nicira.com>
Tue, 12 Jul 2011 16:37:08 +0000 (09:37 -0700)
committerBen Pfaff <blp@nicira.com>
Tue, 12 Jul 2011 16:37:08 +0000 (09:37 -0700)
ovs-bugtool is no longer Debian-specific, so install it everywhere.  (On
XenServer, specifically, we do not install it, because there xen-bugtool
already exists.)

debian/automake.mk
debian/copyright.in
debian/openvswitch-common.install
debian/openvswitch-common.manpages
debian/ovs-bugtool [deleted file]
debian/ovs-bugtool.8 [deleted file]
rhel/openvswitch.spec.in
utilities/automake.mk
utilities/ovs-bugtool [new file with mode: 0755]
utilities/ovs-bugtool.8 [new file with mode: 0644]
xenserver/openvswitch-xen.spec

index 74ff5fc22f581cd61f835ad4335b06eedc7cb913..26db1cb541752c53a7329006aed61d1644206b67 100644 (file)
@@ -36,8 +36,6 @@ EXTRA_DIST += \
        debian/openvswitch-switch.template \
        debian/ovsdbmonitor.install \
        debian/ovsdbmonitor.manpages \
-       debian/ovs-bugtool \
-       debian/ovs-bugtool.8 \
        debian/ovs-monitor-ipsec \
        debian/python-openvswitch.dirs \
        debian/python-openvswitch.install \
index 354ab804ebf4bd0fb361588a21c235bd681338a6..2f2cc48ba2192ab43a44984d9e27c327bd665422 100644 (file)
@@ -8,7 +8,7 @@ Upstream Authors (from AUTHORS):
 
 Upstream Copyright Holders:
 
-       Copyright (c) 2007, 2008, 2009, 2010 Nicira Networks.
+       Copyright (c) 2007, 2008, 2009, 2010, 2011 Nicira Networks.
        Copyright (c) 2010 Jean Tourrilhes - HP-Labs.
        Copyright (c) 2008,2009,2010 Citrix Systems, Inc.
        and authors listed above.
@@ -52,7 +52,7 @@ License:
 * The following components are licensed under the
   GNU Lesser General Public Licence version 2.1.
 
-       debian/ovs-bugtool
+       utilities/ovs-bugtool
        xenserver/etc_xensource_bugtool_kernel-info_openvswitch.xml [*]
        xenserver/etc_xensource_bugtool_network-status_openvswitch.xml [*]
        xenserver/etc_xensource_bugtool_system-configuration.xml [*]
index 1733612aeb7bc34d6f22e0e770cecd4435bf2729..43434eeba8bb8cbbc6bef219c6d6ccffe43648af 100644 (file)
@@ -4,6 +4,5 @@ _debian/utilities/ovs-appctl usr/sbin
 _debian/utilities/ovs-ofctl usr/sbin
 _debian/utilities/ovs-parse-leaks usr/bin
 _debian/utilities/ovs-pki usr/sbin
-debian/ovs-bugtool usr/sbin
-debian/ovs-bugtool.8 usr/share/man/man8
+utilities/ovs-bugtool usr/sbin
 vswitchd/vswitch.ovsschema usr/share/openvswitch
index 8f7e64df74331316425b66c5a3cb6a68e950d729..a5d5b253e39425d82f30aed389ae58abcc864c07 100644 (file)
@@ -3,4 +3,5 @@ _debian/ovsdb/ovsdb-tool.1
 _debian/utilities/ovs-appctl.8
 _debian/utilities/ovs-ofctl.8
 _debian/utilities/ovs-pki.8
+utilities/ovs-bugtool.8
 utilities/ovs-parse-leaks.8
diff --git a/debian/ovs-bugtool b/debian/ovs-bugtool
deleted file mode 100755 (executable)
index 4f0038e..0000000
+++ /dev/null
@@ -1,1243 +0,0 @@
-#!/usr/bin/env python
-
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of version 2.1 of the GNU Lesser General Public
-# License as published by the Free Software Foundation.
-#
-# This library 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
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-#
-# Copyright (c) 2005, 2007 XenSource Ltd.
-# Copyright (c) 2010, 2011 Nicira Networks.
-
-#
-# To add new entries to the bugtool, you need to:
-#
-# Create a new capability.  These declare the new entry to the GUI, including
-# the expected size, time to collect, privacy implications, and whether the
-# capability should be selected by default.  One capability may refer to
-# multiple files, assuming that they can be reasonably grouped together, and
-# have the same privacy implications.  You need:
-#
-#   A new CAP_ constant.
-#   A cap() invocation to declare the capability.
-#
-# You then need to add calls to main() to collect the files.  These will
-# typically be calls to the helpers file_output(), tree_output(), cmd_output(),
-# or func_output().
-#
-
-import warnings
-warnings.filterwarnings(action="ignore", category=DeprecationWarning)
-
-import getopt
-import re
-import os
-import StringIO
-import sys
-import tarfile
-import time
-import commands
-import pprint
-from xml.dom.minidom import parse, getDOMImplementation
-import zipfile
-from subprocess import Popen, PIPE
-from select import select
-from signal import SIGTERM, SIGUSR1
-import md5
-import platform
-import fcntl
-import glob
-import urllib
-import socket
-import base64
-
-sys.path.append('/usr/lib/python')
-sys.path.append('/usr/lib64/python')
-
-OS_RELEASE = platform.release()
-
-#
-# Files & directories
-#
-
-BUG_DIR = "/var/log/ovs-bugtool"
-PLUGIN_DIR = "/etc/openvswitch/bugtool"
-GRUB_CONFIG = '/boot/grub/menu.lst'
-BOOT_KERNEL = '/boot/vmlinuz-' + OS_RELEASE
-BOOT_INITRD = '/boot/initrd-' + OS_RELEASE + '.img'
-PROC_PARTITIONS = '/proc/partitions'
-FSTAB = '/etc/fstab'
-PROC_MOUNTS = '/proc/mounts'
-ISCSI_CONF = '/etc/iscsi/iscsid.conf'
-ISCSI_INITIATOR = '/etc/iscsi/initiatorname.iscsi'
-LVM_CACHE = '/etc/lvm/cache/.cache'
-LVM_CONFIG = '/etc/lvm/lvm.conf'
-PROC_CPUINFO = '/proc/cpuinfo'
-PROC_MEMINFO = '/proc/meminfo'
-PROC_IOPORTS = '/proc/ioports'
-PROC_INTERRUPTS = '/proc/interrupts'
-PROC_SCSI = '/proc/scsi/scsi'
-PROC_VERSION = '/proc/version'
-PROC_MODULES = '/proc/modules'
-PROC_DEVICES = '/proc/devices'
-PROC_FILESYSTEMS = '/proc/filesystems'
-PROC_CMDLINE = '/proc/cmdline'
-PROC_CONFIG = '/proc/config.gz'
-PROC_USB_DEV = '/proc/bus/usb/devices'
-PROC_XEN_BALLOON = '/proc/xen/balloon'
-PROC_NET_BONDING_DIR = '/proc/net/bonding'
-IFCFG_RE = re.compile(r'^.*/ifcfg-.*')
-ROUTE_RE = re.compile(r'^.*/route-.*')
-SYSCONFIG_HWCONF = '/etc/sysconfig/hwconf'
-SYSCONFIG_NETWORK = '/etc/sysconfig/network'
-SYSCONFIG_NETWORK_SCRIPTS = '/etc/sysconfig/network-scripts'
-PROC_NET_VLAN_DIR = '/proc/net/vlan'
-PROC_NET_SOFTNET_STAT = '/proc/net/softnet_stat'
-MODPROBE_CONF = '/etc/modprobe.conf'
-MODPROBE_DIR = '/etc/modprobe.d'
-RESOLV_CONF = '/etc/resolv.conf'
-MPP_CONF = '/etc/mpp.conf'
-MULTIPATH_CONF = '/etc/multipath.conf'
-NSSWITCH_CONF = '/etc/nsswitch.conf'
-NTP_CONF = '/etc/ntp.conf'
-IPTABLES_CONFIG = '/etc/sysconfig/iptables-config'
-HOSTS = '/etc/hosts'
-HOSTS_ALLOW = '/etc/hosts.allow'
-HOSTS_DENY = '/etc/hosts.deny'
-DHCP_LEASE_DIR = ['/var/lib/dhclient', '/var/lib/dhcp3']
-OPENVSWITCH_LOG_DIR = '/var/log/openvswitch'
-OPENVSWITCH_DEFAULT_SWITCH = '/etc/default/openvswitch-switch' # Debian
-OPENVSWITCH_SYSCONFIG_SWITCH = '/etc/sysconfig/openvswitch'    # RHEL
-OPENVSWITCH_DEFAULT_CONTROLLER = '/etc/default/openvswitch-controller'
-OPENVSWITCH_CONF_DB = '/etc/openvswitch/conf.db'
-OPENVSWITCH_VSWITCHD_PID = '/var/run/openvswitch/ovs-vswitchd.pid'
-COLLECTD_LOGS_DIR = '/var/lib/collectd/rrd'
-VAR_LOG_DIR = '/var/log/'
-VAR_LOG_CORE_DIR = '/var/log/core'
-X11_LOGS_DIR = VAR_LOG_DIR
-X11_LOGS_RE = re.compile(r'.*/Xorg\..*$')
-X11_AUTH_DIR = '/root/'
-X11_AUTH_RE = re.compile(r'.*/\.((Xauthority)|(serverauth\.[0-9]*))$')
-YUM_LOG = '/var/log/yum.log'
-YUM_REPOS_DIR = '/etc/yum.repos.d'
-PAM_DIR = '/etc/pam.d'
-KRB5_CONF = '/etc/krb5.conf'
-
-#
-# External programs
-#
-
-os.environ['PATH'] = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
-ARP = 'arp'
-BRCTL = 'brctl'
-CAT = 'cat'
-CHKCONFIG = 'chkconfig'
-DF = 'df'
-DMESG = 'dmesg'
-DMIDECODE = 'dmidecode'
-DMSETUP = 'dmsetup'
-ETHTOOL = 'ethtool'
-FDISK = 'fdisk'
-FIND = 'find'
-HDPARM = 'hdparm'
-IFCONFIG = 'ifconfig'
-IPTABLES = 'iptables'
-ISCSIADM = 'iscsiadm'
-LOSETUP = 'losetup'
-LS = 'ls'
-LSPCI = 'lspci'
-LVDISPLAY = 'lvdisplay'
-LVS = 'lvs'
-MD5SUM = 'md5sum'
-MODINFO = 'modinfo'
-MPPUTIL = 'mppUtil'
-MULTIPATHD = 'multipathd'
-NETSTAT = 'netstat'
-OVS_DPCTL = 'ovs-dpctl'
-OVS_OFCTL = 'ovs-ofctl'
-OVS_VSCTL = 'ovs-vsctl'
-OVS_APPCTL = 'ovs-appctl'
-PS = 'ps'
-PVS = 'pvs'
-ROUTE = 'route'
-RPM = 'rpm'
-SG_MAP = 'sg_map'
-SYSCTL = 'sysctl'
-TC = 'tc'
-UPTIME = 'uptime'
-VGS = 'vgs'
-VGSCAN = 'vgscan'
-ZCAT = 'zcat'
-
-#
-# PII -- Personally identifiable information.  Of particular concern are
-# things that would identify customers, or their network topology.
-# Passwords are never to be included in any bug report, regardless of any PII
-# declaration.
-#
-# NO            -- No PII will be in these entries.
-# YES           -- PII will likely or certainly be in these entries.
-# MAYBE         -- The user may wish to audit these entries for PII.
-# IF_CUSTOMIZED -- If the files are unmodified, then they will contain no PII,
-# but since we encourage customers to edit these files, PII may have been
-# introduced by the customer.  This is used in particular for the networking
-# scripts in dom0.
-#
-
-PII_NO            = 'no'
-PII_YES           = 'yes'
-PII_MAYBE         = 'maybe'
-PII_IF_CUSTOMIZED = 'if_customized'
-KEY      = 0
-PII      = 1
-MIN_SIZE = 2
-MAX_SIZE = 3
-MIN_TIME = 4
-MAX_TIME = 5
-MIME     = 6
-CHECKED  = 7
-HIDDEN   = 8
-
-MIME_DATA = 'application/data'
-MIME_TEXT = 'text/plain'
-
-INVENTORY_XML_ROOT = "system-status-inventory"
-INVENTORY_XML_SUMMARY = 'system-summary'
-INVENTORY_XML_ELEMENT = 'inventory-entry'
-CAP_XML_ROOT = "system-status-capabilities"
-CAP_XML_ELEMENT = 'capability'
-
-
-CAP_BLOBS                = 'blobs'
-CAP_BOOT_LOADER          = 'boot-loader'
-CAP_COLLECTD_LOGS        = 'collectd-logs'
-CAP_DISK_INFO            = 'disk-info'
-CAP_FIRSTBOOT            = 'firstboot'
-CAP_HARDWARE_INFO        = 'hardware-info'
-CAP_HDPARM_T             = 'hdparm-t'
-CAP_HIGH_AVAILABILITY    = 'high-availability'
-CAP_KERNEL_INFO          = 'kernel-info'
-CAP_LOSETUP_A            = 'loopback-devices'
-CAP_MULTIPATH            = 'multipath'
-CAP_NETWORK_CONFIG       = 'network-config'
-CAP_NETWORK_STATUS       = 'network-status'
-CAP_OEM                  = 'oem'
-CAP_PAM                  = 'pam'
-CAP_PROCESS_LIST         = 'process-list'
-CAP_PERSISTENT_STATS     = 'persistent-stats'
-CAP_SYSTEM_LOGS          = 'system-logs'
-CAP_SYSTEM_SERVICES      = 'system-services'
-CAP_VNCTERM              = 'vncterm'
-CAP_WLB                  = 'wlb'
-CAP_X11_LOGS             = 'X11'
-CAP_X11_AUTH             = 'X11-auth'
-CAP_YUM                  = 'yum'
-
-KB = 1024
-MB = 1024 * 1024
-
-caps = {}
-cap_sizes = {}
-unlimited_data = False
-dbg = False
-
-def cap(key, pii=PII_MAYBE, min_size=-1, max_size=-1, min_time=-1,
-        max_time=-1, mime=MIME_TEXT, checked=True, hidden=False):
-    caps[key] = (key, pii, min_size, max_size, min_time, max_time, mime,
-                 checked, hidden)
-    cap_sizes[key] = 0
-
-
-cap(CAP_BLOBS,               PII_NO,                    max_size=5*MB)
-cap(CAP_BOOT_LOADER,         PII_NO,                    max_size=3*KB,
-    max_time=5)
-cap(CAP_COLLECTD_LOGS,       PII_MAYBE,                 max_size=50*MB,
-    max_time=5)
-cap(CAP_DISK_INFO,           PII_MAYBE,                 max_size=50*KB,
-    max_time=20)
-cap(CAP_FIRSTBOOT,           PII_YES,   min_size=60*KB, max_size=80*KB)
-cap(CAP_HARDWARE_INFO,       PII_MAYBE,                 max_size=30*KB,
-    max_time=20)
-cap(CAP_HDPARM_T,            PII_NO,    min_size=0,     max_size=5*KB,
-    min_time=20, max_time=90, checked=False, hidden=True)
-cap(CAP_HIGH_AVAILABILITY,   PII_MAYBE,                 max_size=5*MB)
-cap(CAP_KERNEL_INFO,         PII_MAYBE,                 max_size=120*KB,
-    max_time=5)
-cap(CAP_LOSETUP_A,           PII_MAYBE,                 max_size=KB, max_time=5)
-cap(CAP_MULTIPATH,           PII_MAYBE,                 max_size=20*KB,
-    max_time=10)
-cap(CAP_NETWORK_CONFIG,      PII_IF_CUSTOMIZED,
-                                        min_size=0,     max_size=40*KB)
-cap(CAP_NETWORK_STATUS,      PII_YES,                   max_size=50*KB,
-    max_time=30)
-cap(CAP_PAM,                 PII_NO,                    max_size=50*KB)
-cap(CAP_PERSISTENT_STATS,    PII_MAYBE,                 max_size=50*MB,
-    max_time=60)
-cap(CAP_PROCESS_LIST,        PII_YES,                   max_size=30*KB,
-    max_time=20)
-cap(CAP_SYSTEM_LOGS,         PII_MAYBE,                 max_size=50*MB,
-    max_time=5)
-cap(CAP_SYSTEM_SERVICES,     PII_NO,                    max_size=5*KB,
-    max_time=20)
-cap(CAP_VNCTERM,             PII_MAYBE, checked = False)
-cap(CAP_WLB,                 PII_NO,                    max_size=3*MB,
-    max_time=20)
-cap(CAP_X11_LOGS,            PII_NO,                    max_size=100*KB)
-cap(CAP_X11_AUTH,            PII_NO,                    max_size=100*KB)
-cap(CAP_YUM,                 PII_IF_CUSTOMIZED,         max_size=10*KB,
-    max_time=30)
-
-ANSWER_YES_TO_ALL = False
-SILENT_MODE = False
-entries = None
-data = {}
-dev_null = open('/dev/null', 'r+')
-
-def output(x):
-    global SILENT_MODE
-    if not SILENT_MODE:
-        print x
-
-def output_ts(x):
-    output("[%s]  %s" % (time.strftime("%x %X %Z"), x))
-
-def cmd_output(cap, args, label = None, filter = None):
-    if cap in entries:
-        if not label:
-            if isinstance(args, list):
-                a = [aa for aa in args]
-                a[0] = os.path.basename(a[0])
-                label = ' '.join(a)
-            else:
-                label = args
-        data[label] = {'cap': cap, 'cmd_args': args, 'filter': filter}
-
-def file_output(cap, path_list):
-    if cap in entries:
-        for p in path_list:
-            if os.path.exists(p):
-                if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
-                        cap_sizes[cap] < caps[cap][MAX_SIZE]:
-                    data[p] = {'cap': cap, 'filename': p}
-                    try:
-                        s = os.stat(p)
-                        cap_sizes[cap] += s.st_size
-                    except:
-                        pass
-                else:
-                    output("Omitting %s, size constraint of %s exceeded" % (p, cap))
-
-def tree_output(cap, path, pattern = None, negate = False):
-    if cap in entries:
-        if os.path.exists(path):
-            for f in os.listdir(path):
-                fn = os.path.join(path, f)
-                if os.path.isfile(fn) and matches(fn, pattern, negate):
-                    file_output(cap, [fn])
-                elif os.path.isdir(fn):
-                    tree_output(cap, fn, pattern, negate)
-
-def func_output(cap, label, func):
-    if cap in entries:
-        t = str(func).split()
-        data[label] = {'cap': cap, 'func': func}
-
-def collect_data():
-    process_lists = {}
-
-    for (k, v) in data.items():
-        cap = v['cap']
-        if v.has_key('cmd_args'):
-            v['output'] = StringIOmtime()
-            if not process_lists.has_key(cap):
-                process_lists[cap] = []
-            process_lists[cap].append(ProcOutput(v['cmd_args'], caps[cap][MAX_TIME], v['output'], v['filter']))
-        elif v.has_key('filename') and v['filename'].startswith('/proc/'):
-            # proc files must be read into memory
-            try:
-                f = open(v['filename'], 'r')
-                s = f.read()
-                f.close()
-                if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
-                        cap_sizes[cap] < caps[cap][MAX_SIZE]:
-                    v['output'] = StringIOmtime(s)
-                    cap_sizes[cap] += len(s)
-                else:
-                    output("Omitting %s, size constraint of %s exceeded" % (v['filename'], cap))
-            except:
-                pass
-        elif v.has_key('func'):
-            try:
-                s = v['func'](cap)
-            except Exception, e:
-                s = str(e)
-            if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
-                    cap_sizes[cap] < caps[cap][MAX_SIZE]:
-                v['output'] = StringIOmtime(s)
-                cap_sizes[cap] += len(s)
-            else:
-                output("Omitting %s, size constraint of %s exceeded" % (k, cap))
-
-    run_procs(process_lists.values())
-
-
-def main(argv = None):
-    global ANSWER_YES_TO_ALL, SILENT_MODE
-    global entries, data, dbg
-
-    # we need access to privileged files, exit if we are not running as root
-    if os.getuid() != 0:
-        print >>sys.stderr, "Error: ovs-bugtool must be run as root"
-        return 1
-
-    output_file = None
-    output_type = 'tar.bz2'
-    output_fd = -1
-
-    if argv is None:
-        argv = sys.argv
-
-    try:
-        (options, params) = getopt.gnu_getopt(
-            argv, 'sy', ['capabilities', 'silent', 'yestoall', 'entries=',
-                         'output=', 'outfd=', 'outfile=', 'all', 'unlimited',
-                         'debug'])
-    except getopt.GetoptError, opterr:
-        print >>sys.stderr, opterr
-        return 2
-
-    try:
-        load_plugins(True)
-    except:
-        pass
-
-    entries = [e for e in caps.keys() if caps[e][CHECKED]]
-
-    for (k, v) in options:
-        if k == '--capabilities':
-            update_capabilities()
-            print_capabilities()
-            return 0
-
-        if k == '--output':
-            if  v in ['tar', 'tar.bz2', 'tar.gz', 'zip']:
-                output_type = v
-            else:
-                print >>sys.stderr, "Invalid output format '%s'" % v
-                return 2
-
-        # "-s" or "--silent" means suppress output (except for the final
-        # output filename at the end)
-        if k in ['-s', '--silent']:
-            SILENT_MODE = True
-
-        if k == '--entries' and v != '':
-            entries = v.split(',')
-
-        # If the user runs the script with "-y" or "--yestoall" we don't ask
-        # all the really annoying questions.
-        if k in ['-y', '--yestoall']:
-            ANSWER_YES_TO_ALL = True
-
-        if k == '--outfd':
-            output_fd = int(v)
-            try:
-                old = fcntl.fcntl(output_fd, fcntl.F_GETFD)
-                fcntl.fcntl(output_fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
-            except:
-                print >>sys.stderr, "Invalid output file descriptor", output_fd
-                return 2
-
-        if k == '--outfile':
-            output_file = v
-
-        elif k == '--all':
-            entries = caps.keys()
-        elif k == '--unlimited':
-            unlimited_data = True
-        elif k == '--debug':
-            dbg = True
-            ProcOutput.debug = True
-
-    if len(params) != 1:
-        print >>sys.stderr, "Invalid additional arguments", str(params)
-        return 2
-
-    if output_fd != -1 and output_type != 'tar':
-        print >>sys.stderr, "Option '--outfd' only valid with '--output=tar'"
-        return 2
-
-    if output_fd != -1 and output_file is not None:
-        print >>sys.stderr, "Cannot set both '--outfd' and '--outfile'"
-        return 2
-
-    if ANSWER_YES_TO_ALL:
-        output("Warning: '--yestoall' argument provided, will not prompt for individual files.")
-
-    output('''
-This application will collate dmesg output, details of the
-hardware configuration of your machine, information about the build of
-openvswitch that you are using, plus, if you allow it, various logs.
-
-The collated information will be saved as a .%s for archiving or
-sending to a Technical Support Representative.
-
-The logs may contain private information, and if you are at all
-worried about that, you should exit now, or you should explicitly
-exclude those logs from the archive.
-
-''' % output_type)
-
-    # assemble potential data
-
-    file_output(CAP_BOOT_LOADER, [GRUB_CONFIG])
-    cmd_output(CAP_BOOT_LOADER, [LS, '-lR', '/boot'])
-    cmd_output(CAP_BOOT_LOADER, [MD5SUM, BOOT_KERNEL, BOOT_INITRD], label='vmlinuz-initrd.md5sum')
-
-    tree_output(CAP_COLLECTD_LOGS, COLLECTD_LOGS_DIR)
-    cmd_output(CAP_DISK_INFO, [FDISK, '-l'])
-    file_output(CAP_DISK_INFO, [PROC_PARTITIONS, PROC_MOUNTS])
-    file_output(CAP_DISK_INFO, [FSTAB, ISCSI_CONF, ISCSI_INITIATOR])
-    cmd_output(CAP_DISK_INFO, [DF, '-alT'])
-    cmd_output(CAP_DISK_INFO, [DF, '-alTi'])
-    for d in disk_list():
-        cmd_output(CAP_DISK_INFO, [HDPARM, '-I', '/dev/%s' % d])
-    if len(pidof('iscsid')) != 0:
-        cmd_output(CAP_DISK_INFO, [ISCSIADM, '-m', 'node'])
-    cmd_output(CAP_DISK_INFO, [VGSCAN])
-    cmd_output(CAP_DISK_INFO, [PVS])
-    cmd_output(CAP_DISK_INFO, [VGS])
-    cmd_output(CAP_DISK_INFO, [LVS])
-    file_output(CAP_DISK_INFO, [LVM_CACHE, LVM_CONFIG])
-    cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_host'])
-    cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_disk'])
-    cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/fc_transport'])
-    cmd_output(CAP_DISK_INFO, [SG_MAP, '-x'])
-    func_output(CAP_DISK_INFO, 'scsi-hosts', dump_scsi_hosts)
-    cmd_output(CAP_DISK_INFO, [LVDISPLAY, '--map'])
-
-    file_output(CAP_HARDWARE_INFO, [PROC_CPUINFO, PROC_MEMINFO, PROC_IOPORTS, PROC_INTERRUPTS])
-    cmd_output(CAP_HARDWARE_INFO, [DMIDECODE])
-    cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-n'])
-    cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-vv'])
-    file_output(CAP_HARDWARE_INFO, [PROC_USB_DEV, PROC_SCSI])
-    file_output(CAP_HARDWARE_INFO, [SYSCONFIG_HWCONF])
-    cmd_output(CAP_HARDWARE_INFO, [LS, '-lR', '/dev'])
-    # FIXME IDE?
-
-    for d in disk_list():
-        cmd_output(CAP_HDPARM_T, [HDPARM, '-tT', '/dev/%s' % d])
-
-    file_output(CAP_KERNEL_INFO, [PROC_VERSION, PROC_MODULES, PROC_DEVICES, 
-                                  PROC_FILESYSTEMS, PROC_CMDLINE])
-    cmd_output(CAP_KERNEL_INFO, [ZCAT, PROC_CONFIG], label='config')
-    cmd_output(CAP_KERNEL_INFO, [SYSCTL, '-A'])
-    file_output(CAP_KERNEL_INFO, [MODPROBE_CONF])
-    tree_output(CAP_KERNEL_INFO, MODPROBE_DIR)
-    func_output(CAP_KERNEL_INFO, 'modinfo', module_info)
-
-    cmd_output(CAP_LOSETUP_A, [LOSETUP, '-a'])
-
-    file_output(CAP_MULTIPATH, [MULTIPATH_CONF, MPP_CONF])
-    cmd_output(CAP_MULTIPATH, [DMSETUP, 'table'])
-    func_output(CAP_MULTIPATH, 'multipathd_topology', multipathd_topology)
-    cmd_output(CAP_MULTIPATH, [MPPUTIL, '-a'])
-    if CAP_MULTIPATH in entries:
-        dump_rdac_groups(CAP_MULTIPATH)
-
-    tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, IFCFG_RE)
-    tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, ROUTE_RE)
-    file_output(CAP_NETWORK_CONFIG, [SYSCONFIG_NETWORK, RESOLV_CONF, NSSWITCH_CONF, HOSTS])
-    file_output(CAP_NETWORK_CONFIG, [NTP_CONF, IPTABLES_CONFIG, HOSTS_ALLOW, HOSTS_DENY])
-    file_output(CAP_NETWORK_CONFIG, [OPENVSWITCH_CONF_DB])
-
-    cmd_output(CAP_NETWORK_STATUS, [IFCONFIG, '-a'])
-    cmd_output(CAP_NETWORK_STATUS, [ROUTE, '-n'])
-    cmd_output(CAP_NETWORK_STATUS, [ARP, '-n'])
-    cmd_output(CAP_NETWORK_STATUS, [NETSTAT, '-an'])
-    for dir in DHCP_LEASE_DIR:
-        tree_output(CAP_NETWORK_STATUS, dir)
-    cmd_output(CAP_NETWORK_STATUS, [BRCTL, 'show'])
-    cmd_output(CAP_NETWORK_STATUS, [IPTABLES, '-nL'])
-    for p in os.listdir('/sys/class/net/'):
-        try:
-            f = open('/sys/class/net/%s/type' % p, 'r')
-            t = f.readline()
-            f.close()
-            if int(t) == 1:
-                # ARPHRD_ETHER
-                cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, p])
-                cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-S', p])
-                cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-k', p])
-                cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-i', p])
-                cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-c', p])
-                cmd_output(CAP_NETWORK_STATUS,
-                           [TC, '-s', '-d', 'class', 'show', 'dev', p])
-        except:
-            pass
-    tree_output(CAP_NETWORK_STATUS, PROC_NET_BONDING_DIR)
-    tree_output(CAP_NETWORK_STATUS, PROC_NET_VLAN_DIR)
-    cmd_output(CAP_NETWORK_STATUS, [TC, '-s', 'qdisc'])
-    file_output(CAP_NETWORK_STATUS, [PROC_NET_SOFTNET_STAT])
-    tree_output(CAP_NETWORK_STATUS, OPENVSWITCH_LOG_DIR)
-    if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
-        cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show'])
-        for d in dp_list():
-            cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'show', d])
-            cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'dump-flows', d])
-            cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
-        try:
-            vspidfile = open(OPENVSWITCH_VSWITCHD_PID)
-            vspid = int(vspidfile.readline().strip())
-            vspidfile.close()
-            for b in bond_list(vspid):
-                cmd_output(CAP_NETWORK_STATUS,
-                           [OVS_APPCTL, '-t', '/var/run/ovs-vswitchd.%s.ctl' % vspid, '-e' 'bond/show %s' % b],
-                           'ovs-appctl-bond-show-%s.out' % b)
-        except e:
-            pass
-
-    tree_output(CAP_PAM, PAM_DIR)
-    file_output(CAP_PAM, [KRB5_CONF])
-
-    cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
-    func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
-
-    file_output(CAP_SYSTEM_LOGS,
-         [ VAR_LOG_DIR + x for x in
-           [ 'crit.log', 'kern.log', 'daemon.log', 'user.log', 'syslog', 
-             'messages', 'secure', 'debug', 'dmesg', 'boot'] +
-           [ f % n for n in range(1, 20) \
-                 for f in ['crit.log.%d', 'crit.log.%d.gz',
-                           'kern.log.%d', 'kern.log.%d.gz',
-                           'daemon.log.%d', 'daemon.log.%d.gz',
-                           'user.log.%d', 'user.log.%d.gz',
-                           'messages.%d', 'messages.%d.gz',
-                           'syslog.%d', 'syslog.%d.gz']]])
-    if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
-        cmd_output(CAP_SYSTEM_LOGS, [DMESG])
-
-    cmd_output(CAP_SYSTEM_SERVICES, [CHKCONFIG, '--list'])
-
-    tree_output(CAP_X11_LOGS, X11_LOGS_DIR, X11_LOGS_RE)
-    tree_output(CAP_X11_AUTH, X11_AUTH_DIR, X11_AUTH_RE)
-    tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
-
-    file_output(CAP_YUM, [YUM_LOG])
-    tree_output(CAP_YUM, YUM_REPOS_DIR)
-    cmd_output(CAP_YUM, [RPM, '-qa'])
-
-    try:
-        load_plugins()
-    except:
-        pass
-    
-    # permit the user to filter out data
-    for k in sorted(data.keys()):
-        if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % k):
-            del data[k]
-
-    # collect selected data now
-    output_ts('Running commands to collect data')
-    collect_data()
-
-    subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
-
-    # include inventory
-    data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
-
-    # create archive
-    if output_fd == -1:
-        if output_file is None:
-            dirname = BUG_DIR
-        else:
-            dirname = os.path.dirname(output_file)
-        if dirname and not os.path.exists(dirname):
-            try:
-                os.makedirs(dirname)
-            except:
-                pass
-
-    if output_fd == -1:
-        output_ts('Creating output file')
-
-    if output_type.startswith('tar'):
-        make_tar(subdir, output_type, output_fd, output_file)
-    else:
-        make_zip(subdir, output_file)
-
-    clean_tapdisk_logs()
-
-    if dbg:
-        print >>sys.stderr, "Category sizes (max, actual):\n"
-        for c in caps.keys():
-            print >>sys.stderr, "    %s (%d, %d)" % (c, caps[c][MAX_SIZE],
-                                                     cap_sizes[c])
-    return 0
-
-def find_tapdisk_logs():
-    return glob.glob('/var/log/blktap/*.log*')
-
-def generate_tapdisk_logs():
-    for pid in pidof('tapdisk'):
-       try:
-           os.kill(pid, SIGUSR1)
-            output_ts("Including logs for tapdisk process %d" % pid)
-        except :
-            pass
-    # give processes a second to write their logs
-    time.sleep(1)
-
-def clean_tapdisk_logs():
-    for filename in find_tapdisk_logs():
-        try:
-            os.remove(filename)
-        except :
-            pass
-
-def filter_db_pii(str, state):
-    if 'in_secret_table' not in state:
-        state['in_secret_table'] = False
-
-    if str.startswith('<table ') and 'name="secret"' in str:
-        state['in_secret_table'] = True
-    elif str.startswith('</table>'):
-        state['in_secret_table'] = False
-
-    if state['in_secret_table'] and str.startswith("<row"): # match only on DB rows
-        str = re.sub(r'(value=")[^"]+(")', r'\1REMOVED\2', str)
-    return str
-
-def dump_scsi_hosts(cap):
-    output = ''
-    l = os.listdir('/sys/class/scsi_host')
-    l.sort()
-
-    for h in l:
-        procname = ''
-        try:
-                f = open('/sys/class/scsi_host/%s/proc_name' % h)
-                procname = f.readline().strip("\n")
-                f.close()
-        except:
-                pass
-        modelname = None
-        try:
-                f = open('/sys/class/scsi_host/%s/model_name' % h)
-                modelname = f.readline().strip("\n")
-                f.close()
-        except:
-                pass
-
-        output += "%s:\n" %h
-        output += "    %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
-
-    return output
-
-def module_info(cap):
-    output = StringIO.StringIO()
-    modules = open(PROC_MODULES, 'r')
-    procs = []
-
-    for line in modules:
-        module = line.split()[0]
-        procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
-    modules.close()
-
-    run_procs([procs])
-
-    return output.getvalue()
-
-
-def multipathd_topology(cap):
-    pipe = Popen([MULTIPATHD, '-k'], bufsize=1, stdin=PIPE, 
-                     stdout=PIPE, stderr=dev_null)
-    stdout, stderr = pipe.communicate('show topology')
-
-    return stdout
-
-def dp_list():
-    output = StringIO.StringIO()
-    procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
-
-    run_procs([procs])
-
-    if not procs[0].timed_out:
-        return output.getvalue().splitlines()
-    return []
-
-def bond_list(pid):
-    output = StringIO.StringIO()
-    procs = [ProcOutput([OVS_APPCTL, '-t', '/var/run/ovs-vswitchd.%s.ctl' % pid, '-e' 'bond/list'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
-
-    run_procs([procs])
-
-    if not procs[0].timed_out:
-        bonds = output.getvalue().splitlines()[1:]
-        return [x.split('\t')[1] for x in bonds]
-    return []
-
-def fd_usage(cap):
-    output = ''
-    fd_dict = {}
-    for d in [p for p in os.listdir('/proc') if p.isdigit()]:
-        try:
-            fh = open('/proc/'+d+'/cmdline')
-            name = fh.readline()
-            num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
-            if num_fds > 0:
-                if not num_fds in fd_dict:
-                    fd_dict[num_fds] = []
-                fd_dict[num_fds].append(name.replace('\0', ' ').strip())
-        finally:
-            fh.close()
-    keys = fd_dict.keys()
-    keys.sort(lambda a, b: int(b) - int(a))
-    for k in keys:
-        output += "%s: %s\n" % (k, str(fd_dict[k]))
-    return output
-
-def dump_rdac_groups(cap):
-    output = StringIO.StringIO()
-    procs = [ProcOutput([MPPUTIL, '-a'], caps[cap][MAX_TIME], output)]
-
-    run_procs([procs])
-
-    if not procs[0].timed_out:
-        proc_line = 0
-        for line in output.getvalue().splitlines():
-            if line.startswith('ID'):
-                proc_line = 2
-            elif line.startswith('----'):
-                proc_line -= 1
-            elif proc_line > 0:
-                group, _ = line.split(None, 1)
-                cmd_output(cap, [MPPUTIL, '-g', group])
-
-def load_plugins(just_capabilities = False):
-    def getText(nodelist):
-        rc = ""
-        for node in nodelist:
-            if node.nodeType == node.TEXT_NODE:
-                rc += node.data
-        return rc.encode()
-
-    def getBoolAttr(el, attr, default = False):
-        ret = default
-        val = el.getAttribute(attr).lower()
-        if val in ['true', 'false', 'yes', 'no']:
-            ret = val in ['true', 'yes']
-        return ret
-        
-    for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
-        if not caps.has_key(dir):
-            if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
-                continue
-            xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
-            assert xmldoc.documentElement.tagName == "capability"
-
-            pii, min_size, max_size, min_time, max_time, mime = \
-                 PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
-
-            if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
-                pii = xmldoc.documentElement.getAttribute("pii")
-            if xmldoc.documentElement.getAttribute("min_size") != '':
-                min_size = long(xmldoc.documentElement.getAttribute("min_size"))
-            if xmldoc.documentElement.getAttribute("max_size") != '':
-                max_size = long(xmldoc.documentElement.getAttribute("max_size"))
-            if xmldoc.documentElement.getAttribute("min_time") != '':
-                min_time = int(xmldoc.documentElement.getAttribute("min_time"))
-            if xmldoc.documentElement.getAttribute("max_time") != '':
-                max_time = int(xmldoc.documentElement.getAttribute("max_time"))
-            if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
-                mime = xmldoc.documentElement.getAttribute("mime")
-            checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
-            hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
-
-            cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
-
-        if just_capabilities:
-            continue
-                    
-        plugdir = os.path.join(PLUGIN_DIR, dir)
-        for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
-            xmldoc = parse(os.path.join(plugdir, file))
-            assert xmldoc.documentElement.tagName == "collect"
-
-            for el in xmldoc.documentElement.getElementsByTagName("*"):
-                if el.tagName == "files":
-                    file_output(dir, getText(el.childNodes).split())
-                elif el.tagName == "directory":
-                    pattern = el.getAttribute("pattern")
-                    if pattern == '': pattern = None
-                    negate = getBoolAttr(el, 'negate')
-                    tree_output(dir, getText(el.childNodes), pattern and re.compile(pattern) or None, negate)
-                elif el.tagName == "command":
-                    label = el.getAttribute("label")
-                    if label == '': label = None
-                    cmd_output(dir, getText(el.childNodes), label)
-
-def make_tar(subdir, suffix, output_fd, output_file):
-    global SILENT_MODE, data
-
-    mode = 'w'
-    if suffix == 'tar.bz2':
-        mode = 'w:bz2'
-    elif suffix == 'tar.gz':
-        mode = 'w:gz'
-
-    if output_fd == -1:
-        if output_file is None:
-            filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
-        else:
-            filename = output_file
-        tf = tarfile.open(filename, mode)
-    else:
-        tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
-
-    try:
-        for (k, v) in data.items():
-            try:
-                tar_filename = os.path.join(subdir, construct_filename(k, v))
-                ti = tarfile.TarInfo(tar_filename)
-
-                ti.uname = 'root'
-                ti.gname = 'root'
-
-                if v.has_key('output'):
-                    ti.mtime = v['output'].mtime
-                    ti.size = len(v['output'].getvalue())
-                    v['output'].seek(0)
-                    tf.addfile(ti, v['output'])
-                elif v.has_key('filename'):
-                    s = os.stat(v['filename'])
-                    ti.mtime = s.st_mtime
-                    ti.size = s.st_size
-                    tf.addfile(ti, file(v['filename']))
-            except:
-                pass
-    finally:
-        tf.close()
-
-    if output_fd == -1:
-        output ('Writing tarball %s successful.' % filename)
-        if SILENT_MODE:
-            print filename
-
-
-def make_zip(subdir, output_file):
-    global SILENT_MODE, data
-
-    if output_file is None:
-        filename = "%s/%s.zip" % (BUG_DIR, subdir)
-    else:
-        filename = output_file
-    zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
-
-    try:
-        for (k, v) in data.items():
-            try:
-                dest = os.path.join(subdir, construct_filename(k, v))
-
-                if v.has_key('output'):
-                    zf.writestr(dest, v['output'].getvalue())
-                else:
-                    if os.stat(v['filename']).st_size < 50:
-                        compress_type = zipfile.ZIP_STORED
-                    else:
-                        compress_type = zipfile.ZIP_DEFLATED
-                    zf.write(v['filename'], dest, compress_type)
-            except:
-                pass
-    finally:
-        zf.close()
-    
-    output ('Writing archive %s successful.' % filename)
-    if SILENT_MODE:
-        print filename
-
-
-def make_inventory(inventory, subdir):
-    document = getDOMImplementation().createDocument(
-        None, INVENTORY_XML_ROOT, None)
-
-    # create summary entry
-    s = document.createElement(INVENTORY_XML_SUMMARY)
-    user = os.getenv('SUDO_USER', os.getenv('USER'))
-    if user:
-        s.setAttribute('user', user)
-    s.setAttribute('date', time.strftime('%c'))
-    s.setAttribute('hostname', platform.node())
-    s.setAttribute('uname', ' '.join(platform.uname()))
-    s.setAttribute('uptime', commands.getoutput(UPTIME))
-    document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
-
-    map(lambda (k, v): inventory_entry(document, subdir, k, v),
-        inventory.items())
-    return document.toprettyxml()
-
-def inventory_entry(document, subdir, k, v):
-    try:
-        el = document.createElement(INVENTORY_XML_ELEMENT)
-        el.setAttribute('capability', v['cap'])
-        el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
-        el.setAttribute('md5sum', md5sum(v))
-        document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
-    except:
-        pass
-
-
-def md5sum(d):
-    m = md5.new()
-    if d.has_key('filename'):
-        f = open(d['filename'])
-        data = f.read(1024)
-        while len(data) > 0:
-            m.update(data)
-            data = f.read(1024)
-        f.close()
-    elif d.has_key('output'):
-        m.update(d['output'].getvalue())
-    return m.hexdigest()
-
-
-def construct_filename(k, v):
-    if v.has_key('filename'):
-        if v['filename'][0] == '/':
-            return v['filename'][1:]
-        else:
-            return v['filename']
-    s = k.replace(' ', '-')
-    s = s.replace('--', '-')
-    s = s.replace('/', '%')
-    if s.find('.') == -1:
-        s += '.out'
-
-    return s
-
-def update_capabilities():
-    pass
-
-def update_cap_size(cap, size):
-    update_cap(cap, MIN_SIZE, size)
-    update_cap(cap, MAX_SIZE, size)
-    update_cap(cap, CHECKED, size > 0)
-
-
-def update_cap(cap, k, v):
-    global caps
-    l = list(caps[cap])
-    l[k] = v
-    caps[cap] = tuple(l)
-
-
-def size_of_dir(d, pattern = None, negate = False):
-    if os.path.isdir(d):
-        return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
-                           pattern, negate)
-    else:
-        return 0
-
-
-def size_of_all(files, pattern = None, negate = False):
-    return sum([size_of(f, pattern, negate) for f in files])
-
-
-def matches(f, pattern, negate):
-    if negate:
-        return not matches(f, pattern, False)
-    else:
-        return pattern is None or pattern.match(f)
-
-
-def size_of(f, pattern, negate):
-    if os.path.isfile(f) and matches(f, pattern, negate):
-        return os.stat(f)[6]
-    else:
-        return size_of_dir(f, pattern, negate)
-
-
-def print_capabilities():
-    document = getDOMImplementation().createDocument(
-        "ns", CAP_XML_ROOT, None)
-    map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
-    print document.toprettyxml()
-
-def capability(document, key):
-    c = caps[key]
-    el = document.createElement(CAP_XML_ELEMENT)
-    el.setAttribute('key', c[KEY])
-    el.setAttribute('pii', c[PII])
-    el.setAttribute('min-size', str(c[MIN_SIZE]))
-    el.setAttribute('max-size', str(c[MAX_SIZE]))
-    el.setAttribute('min-time', str(c[MIN_TIME]))
-    el.setAttribute('max-time', str(c[MAX_TIME]))
-    el.setAttribute('content-type', c[MIME])
-    el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
-    document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
-
-
-def prettyDict(d):
-    format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
-    return '\n'.join([format % i for i in d.items()]) + '\n'
-
-
-def yes(prompt):
-    yn = raw_input(prompt)
-
-    return len(yn) == 0 or yn.lower()[0] == 'y'
-
-
-partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
-
-def disk_list():
-    disks = []
-    try:
-        f = open('/proc/partitions')
-        f.readline()
-        f.readline()
-        for line in f.readlines():
-            (major, minor, blocks, name) = line.split()
-            if int(major) < 254 and not partition_re.match(name):
-                disks.append(name)
-        f.close()
-    except:
-        pass
-    return disks
-
-
-class ProcOutput:
-    debug = False
-
-    def __init__(self, command, max_time, inst=None, filter=None):
-        self.command = command
-        self.max_time = max_time
-        self.inst = inst
-        self.running = False
-        self.status = None
-        self.timed_out = False
-        self.failed = False
-        self.timeout = int(time.time()) + self.max_time
-        self.filter = filter
-        self.filter_state = {}
-
-    def __del__(self):
-        self.terminate()
-
-    def cmdAsStr(self):
-        return isinstance(self.command, list) and ' '.join(self.command) or self.command
-
-    def run(self):
-        self.timed_out = False
-        try:
-            if ProcOutput.debug:
-                output_ts("Starting '%s'" % self.cmdAsStr())
-            self.proc = Popen(self.command, bufsize=1, stdin=dev_null, stdout=PIPE, stderr=dev_null, shell=isinstance(self.command, str))
-            old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
-            fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
-            self.running = True
-            self.failed = False
-        except:
-            output_ts("'%s' failed" % self.cmdAsStr())
-            self.running = False
-            self.failed = True
-
-    def terminate(self):
-        if self.running:
-            try:
-                os.kill(self.proc.pid, SIGTERM)
-            except:
-                pass
-            self.proc = None
-            self.running = False
-            self.status = SIGTERM
-
-    def read_line(self):
-        assert self.running
-        line = self.proc.stdout.readline()
-        if line == '':
-            # process exited
-            self.status = self.proc.wait()
-            self.proc = None
-            self.running = False
-        else:
-            if self.filter:
-                line = self.filter(line, self.filter_state)
-            if self.inst:
-                self.inst.write(line)
-
-def run_procs(procs):
-    while True:
-        pipes = []
-        active_procs = []
-
-        for pp in procs:
-            for p in pp:
-                if p.running:
-                    active_procs.append(p)
-                    pipes.append(p.proc.stdout)
-                    break
-                elif p.status == None and not p.failed and not p.timed_out:
-                    p.run()
-                    if p.running:
-                        active_procs.append(p)
-                        pipes.append(p.proc.stdout)
-                        break
-
-        if len(pipes) == 0:
-            # all finished
-            break
-
-        (i, o, x) = select(pipes, [], [], 1.0)
-        now = int(time.time())
-
-        # handle process output
-        for p in active_procs:
-            if p.proc.stdout in i:
-                p.read_line()
-
-            # handle timeout
-            if p.running and now > p.timeout:
-                output_ts("'%s' timed out" % p.cmdAsStr())
-                if p.inst:
-                    p.inst.write("\n** timeout **\n")
-                p.timed_out = True
-                p.terminate()
-
-
-def pidof(name):
-    pids = []
-
-    for d in [p for p in os.listdir('/proc') if p.isdigit()]:
-        try:
-            if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
-                pids.append(int(d))
-        except:
-            pass
-
-    return pids
-
-
-class StringIOmtime(StringIO.StringIO):
-    def __init__(self, buf = ''):
-        StringIO.StringIO.__init__(self, buf)
-        self.mtime = time.time()
-
-    def write(self, s):
-        StringIO.StringIO.write(self, s)
-        self.mtime = time.time()
-
-
-if __name__ == "__main__":
-    try:
-        sys.exit(main())
-    except KeyboardInterrupt:
-        print "\nInterrupted."
-        sys.exit(3)
diff --git a/debian/ovs-bugtool.8 b/debian/ovs-bugtool.8
deleted file mode 100644 (file)
index 6755d6a..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-.\" -*- nroff -*-
-.de IQ
-.  br
-.  ns
-.  IP "\\$1"
-..
-.TH ovs\-bugtool 8 "June 2011" "Open vSwitch" "Open vSwitch Manual"
-.\" This program's name:
-.ds PN ovs\-bugtool
-.
-.SH NAME
-ovs\-bugtool \- Open vSwitch bug reporting utility
-.
-.SH SYNOPSIS
-.B ovs\-bugtool
-.
-.SH DESCRIPTION
-Generate a debug bundle with useful information about Open vSwitch on this
-system and places it in \fB/var/log/ovs-bugtool\fR.
-.
-.SH OPTIONS
-.
-.IP "\fB\-\-all\fR"
-Use all available capabilities.
-.
-.IP "\fB\-\-capabilities\fR"
-List \fBovs\-bugtool\fR capabilities.
-.
-.IP "\fB\-\-debug\fR"
-Print verbose debugging output.
-.
-.IP "\fB\-\-entries=\fIlist\fR"
-Use the capabilities specified in a comma-separated list.
-.
-.IP "\fB\-\-output=\fIfiletype\fR"
-Generate a debug bundle with the specified file type.  Options include
-\fBtar\fR, \fBtar.gz\fR, \fBtar.bz2\fR, and \fBzip\fR.
-.
-.IP "\fB\-\-silent\fR"
-Suppress output.
-.
-.IP "\fB\-\-unlimited\fR"
-Do not exclude files which are too large.
-.
-.IP "\fB\-\-yestoall\fR"
-Answer yes to all prompts.
-.
-.SH BUGS
-\fBovs\-bugtool\fR makes many assumptions about file locations and the
-availability of system utilities.  It has been tested on Debian and
-Red Hat and derived distributions.  On other distributions it is
-likely to be less useful.
index d64374549594eb3c02a0d12da23bbd05033da09a..bdedb9cd88509aaa77cbe7b0937010d7288f7cfc 100644 (file)
@@ -111,6 +111,7 @@ exit 0
 /usr/bin/ovsdb-client
 /usr/bin/ovsdb-tool
 /usr/sbin/ovs-brcompatd
+/usr/sbin/ovs-bugtool
 /usr/sbin/ovs-vswitchd
 /usr/sbin/ovsdb-server
 /usr/share/man/man1/ovs-pcap.1.gz
index 69cc5d2484cd3bcd18877f1c0be7451c6646d264..8bea123e86bbe3a19f18a95857c35e2d63d17240 100644 (file)
@@ -7,15 +7,19 @@ bin_PROGRAMS += \
 bin_SCRIPTS += utilities/ovs-pki utilities/ovs-vsctl
 if HAVE_PYTHON
 bin_SCRIPTS += \
+       utilities/ovs-bugtool \
        utilities/ovs-pcap \
        utilities/ovs-tcpundump \
        utilities/ovs-vlan-test
+man_MANS += utilities/ovs-bugtool.8
 endif
 noinst_SCRIPTS += utilities/ovs-pki-cgi utilities/ovs-parse-leaks
 scripts_SCRIPTS += utilities/ovs-ctl utilities/ovs-lib.sh utilities/ovs-save
 
 EXTRA_DIST += \
        utilities/ovs-appctl.8.in \
+       utilities/ovs-bugtool \
+       utilities/ovs-bugtool.8 \
        utilities/ovs-controller.8.in \
        utilities/ovs-ctl.in \
        utilities/ovs-dpctl.8.in \
diff --git a/utilities/ovs-bugtool b/utilities/ovs-bugtool
new file mode 100755 (executable)
index 0000000..4f0038e
--- /dev/null
@@ -0,0 +1,1243 @@
+#!/usr/bin/env python
+
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of version 2.1 of the GNU Lesser General Public
+# License as published by the Free Software Foundation.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#
+# Copyright (c) 2005, 2007 XenSource Ltd.
+# Copyright (c) 2010, 2011 Nicira Networks.
+
+#
+# To add new entries to the bugtool, you need to:
+#
+# Create a new capability.  These declare the new entry to the GUI, including
+# the expected size, time to collect, privacy implications, and whether the
+# capability should be selected by default.  One capability may refer to
+# multiple files, assuming that they can be reasonably grouped together, and
+# have the same privacy implications.  You need:
+#
+#   A new CAP_ constant.
+#   A cap() invocation to declare the capability.
+#
+# You then need to add calls to main() to collect the files.  These will
+# typically be calls to the helpers file_output(), tree_output(), cmd_output(),
+# or func_output().
+#
+
+import warnings
+warnings.filterwarnings(action="ignore", category=DeprecationWarning)
+
+import getopt
+import re
+import os
+import StringIO
+import sys
+import tarfile
+import time
+import commands
+import pprint
+from xml.dom.minidom import parse, getDOMImplementation
+import zipfile
+from subprocess import Popen, PIPE
+from select import select
+from signal import SIGTERM, SIGUSR1
+import md5
+import platform
+import fcntl
+import glob
+import urllib
+import socket
+import base64
+
+sys.path.append('/usr/lib/python')
+sys.path.append('/usr/lib64/python')
+
+OS_RELEASE = platform.release()
+
+#
+# Files & directories
+#
+
+BUG_DIR = "/var/log/ovs-bugtool"
+PLUGIN_DIR = "/etc/openvswitch/bugtool"
+GRUB_CONFIG = '/boot/grub/menu.lst'
+BOOT_KERNEL = '/boot/vmlinuz-' + OS_RELEASE
+BOOT_INITRD = '/boot/initrd-' + OS_RELEASE + '.img'
+PROC_PARTITIONS = '/proc/partitions'
+FSTAB = '/etc/fstab'
+PROC_MOUNTS = '/proc/mounts'
+ISCSI_CONF = '/etc/iscsi/iscsid.conf'
+ISCSI_INITIATOR = '/etc/iscsi/initiatorname.iscsi'
+LVM_CACHE = '/etc/lvm/cache/.cache'
+LVM_CONFIG = '/etc/lvm/lvm.conf'
+PROC_CPUINFO = '/proc/cpuinfo'
+PROC_MEMINFO = '/proc/meminfo'
+PROC_IOPORTS = '/proc/ioports'
+PROC_INTERRUPTS = '/proc/interrupts'
+PROC_SCSI = '/proc/scsi/scsi'
+PROC_VERSION = '/proc/version'
+PROC_MODULES = '/proc/modules'
+PROC_DEVICES = '/proc/devices'
+PROC_FILESYSTEMS = '/proc/filesystems'
+PROC_CMDLINE = '/proc/cmdline'
+PROC_CONFIG = '/proc/config.gz'
+PROC_USB_DEV = '/proc/bus/usb/devices'
+PROC_XEN_BALLOON = '/proc/xen/balloon'
+PROC_NET_BONDING_DIR = '/proc/net/bonding'
+IFCFG_RE = re.compile(r'^.*/ifcfg-.*')
+ROUTE_RE = re.compile(r'^.*/route-.*')
+SYSCONFIG_HWCONF = '/etc/sysconfig/hwconf'
+SYSCONFIG_NETWORK = '/etc/sysconfig/network'
+SYSCONFIG_NETWORK_SCRIPTS = '/etc/sysconfig/network-scripts'
+PROC_NET_VLAN_DIR = '/proc/net/vlan'
+PROC_NET_SOFTNET_STAT = '/proc/net/softnet_stat'
+MODPROBE_CONF = '/etc/modprobe.conf'
+MODPROBE_DIR = '/etc/modprobe.d'
+RESOLV_CONF = '/etc/resolv.conf'
+MPP_CONF = '/etc/mpp.conf'
+MULTIPATH_CONF = '/etc/multipath.conf'
+NSSWITCH_CONF = '/etc/nsswitch.conf'
+NTP_CONF = '/etc/ntp.conf'
+IPTABLES_CONFIG = '/etc/sysconfig/iptables-config'
+HOSTS = '/etc/hosts'
+HOSTS_ALLOW = '/etc/hosts.allow'
+HOSTS_DENY = '/etc/hosts.deny'
+DHCP_LEASE_DIR = ['/var/lib/dhclient', '/var/lib/dhcp3']
+OPENVSWITCH_LOG_DIR = '/var/log/openvswitch'
+OPENVSWITCH_DEFAULT_SWITCH = '/etc/default/openvswitch-switch' # Debian
+OPENVSWITCH_SYSCONFIG_SWITCH = '/etc/sysconfig/openvswitch'    # RHEL
+OPENVSWITCH_DEFAULT_CONTROLLER = '/etc/default/openvswitch-controller'
+OPENVSWITCH_CONF_DB = '/etc/openvswitch/conf.db'
+OPENVSWITCH_VSWITCHD_PID = '/var/run/openvswitch/ovs-vswitchd.pid'
+COLLECTD_LOGS_DIR = '/var/lib/collectd/rrd'
+VAR_LOG_DIR = '/var/log/'
+VAR_LOG_CORE_DIR = '/var/log/core'
+X11_LOGS_DIR = VAR_LOG_DIR
+X11_LOGS_RE = re.compile(r'.*/Xorg\..*$')
+X11_AUTH_DIR = '/root/'
+X11_AUTH_RE = re.compile(r'.*/\.((Xauthority)|(serverauth\.[0-9]*))$')
+YUM_LOG = '/var/log/yum.log'
+YUM_REPOS_DIR = '/etc/yum.repos.d'
+PAM_DIR = '/etc/pam.d'
+KRB5_CONF = '/etc/krb5.conf'
+
+#
+# External programs
+#
+
+os.environ['PATH'] = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
+ARP = 'arp'
+BRCTL = 'brctl'
+CAT = 'cat'
+CHKCONFIG = 'chkconfig'
+DF = 'df'
+DMESG = 'dmesg'
+DMIDECODE = 'dmidecode'
+DMSETUP = 'dmsetup'
+ETHTOOL = 'ethtool'
+FDISK = 'fdisk'
+FIND = 'find'
+HDPARM = 'hdparm'
+IFCONFIG = 'ifconfig'
+IPTABLES = 'iptables'
+ISCSIADM = 'iscsiadm'
+LOSETUP = 'losetup'
+LS = 'ls'
+LSPCI = 'lspci'
+LVDISPLAY = 'lvdisplay'
+LVS = 'lvs'
+MD5SUM = 'md5sum'
+MODINFO = 'modinfo'
+MPPUTIL = 'mppUtil'
+MULTIPATHD = 'multipathd'
+NETSTAT = 'netstat'
+OVS_DPCTL = 'ovs-dpctl'
+OVS_OFCTL = 'ovs-ofctl'
+OVS_VSCTL = 'ovs-vsctl'
+OVS_APPCTL = 'ovs-appctl'
+PS = 'ps'
+PVS = 'pvs'
+ROUTE = 'route'
+RPM = 'rpm'
+SG_MAP = 'sg_map'
+SYSCTL = 'sysctl'
+TC = 'tc'
+UPTIME = 'uptime'
+VGS = 'vgs'
+VGSCAN = 'vgscan'
+ZCAT = 'zcat'
+
+#
+# PII -- Personally identifiable information.  Of particular concern are
+# things that would identify customers, or their network topology.
+# Passwords are never to be included in any bug report, regardless of any PII
+# declaration.
+#
+# NO            -- No PII will be in these entries.
+# YES           -- PII will likely or certainly be in these entries.
+# MAYBE         -- The user may wish to audit these entries for PII.
+# IF_CUSTOMIZED -- If the files are unmodified, then they will contain no PII,
+# but since we encourage customers to edit these files, PII may have been
+# introduced by the customer.  This is used in particular for the networking
+# scripts in dom0.
+#
+
+PII_NO            = 'no'
+PII_YES           = 'yes'
+PII_MAYBE         = 'maybe'
+PII_IF_CUSTOMIZED = 'if_customized'
+KEY      = 0
+PII      = 1
+MIN_SIZE = 2
+MAX_SIZE = 3
+MIN_TIME = 4
+MAX_TIME = 5
+MIME     = 6
+CHECKED  = 7
+HIDDEN   = 8
+
+MIME_DATA = 'application/data'
+MIME_TEXT = 'text/plain'
+
+INVENTORY_XML_ROOT = "system-status-inventory"
+INVENTORY_XML_SUMMARY = 'system-summary'
+INVENTORY_XML_ELEMENT = 'inventory-entry'
+CAP_XML_ROOT = "system-status-capabilities"
+CAP_XML_ELEMENT = 'capability'
+
+
+CAP_BLOBS                = 'blobs'
+CAP_BOOT_LOADER          = 'boot-loader'
+CAP_COLLECTD_LOGS        = 'collectd-logs'
+CAP_DISK_INFO            = 'disk-info'
+CAP_FIRSTBOOT            = 'firstboot'
+CAP_HARDWARE_INFO        = 'hardware-info'
+CAP_HDPARM_T             = 'hdparm-t'
+CAP_HIGH_AVAILABILITY    = 'high-availability'
+CAP_KERNEL_INFO          = 'kernel-info'
+CAP_LOSETUP_A            = 'loopback-devices'
+CAP_MULTIPATH            = 'multipath'
+CAP_NETWORK_CONFIG       = 'network-config'
+CAP_NETWORK_STATUS       = 'network-status'
+CAP_OEM                  = 'oem'
+CAP_PAM                  = 'pam'
+CAP_PROCESS_LIST         = 'process-list'
+CAP_PERSISTENT_STATS     = 'persistent-stats'
+CAP_SYSTEM_LOGS          = 'system-logs'
+CAP_SYSTEM_SERVICES      = 'system-services'
+CAP_VNCTERM              = 'vncterm'
+CAP_WLB                  = 'wlb'
+CAP_X11_LOGS             = 'X11'
+CAP_X11_AUTH             = 'X11-auth'
+CAP_YUM                  = 'yum'
+
+KB = 1024
+MB = 1024 * 1024
+
+caps = {}
+cap_sizes = {}
+unlimited_data = False
+dbg = False
+
+def cap(key, pii=PII_MAYBE, min_size=-1, max_size=-1, min_time=-1,
+        max_time=-1, mime=MIME_TEXT, checked=True, hidden=False):
+    caps[key] = (key, pii, min_size, max_size, min_time, max_time, mime,
+                 checked, hidden)
+    cap_sizes[key] = 0
+
+
+cap(CAP_BLOBS,               PII_NO,                    max_size=5*MB)
+cap(CAP_BOOT_LOADER,         PII_NO,                    max_size=3*KB,
+    max_time=5)
+cap(CAP_COLLECTD_LOGS,       PII_MAYBE,                 max_size=50*MB,
+    max_time=5)
+cap(CAP_DISK_INFO,           PII_MAYBE,                 max_size=50*KB,
+    max_time=20)
+cap(CAP_FIRSTBOOT,           PII_YES,   min_size=60*KB, max_size=80*KB)
+cap(CAP_HARDWARE_INFO,       PII_MAYBE,                 max_size=30*KB,
+    max_time=20)
+cap(CAP_HDPARM_T,            PII_NO,    min_size=0,     max_size=5*KB,
+    min_time=20, max_time=90, checked=False, hidden=True)
+cap(CAP_HIGH_AVAILABILITY,   PII_MAYBE,                 max_size=5*MB)
+cap(CAP_KERNEL_INFO,         PII_MAYBE,                 max_size=120*KB,
+    max_time=5)
+cap(CAP_LOSETUP_A,           PII_MAYBE,                 max_size=KB, max_time=5)
+cap(CAP_MULTIPATH,           PII_MAYBE,                 max_size=20*KB,
+    max_time=10)
+cap(CAP_NETWORK_CONFIG,      PII_IF_CUSTOMIZED,
+                                        min_size=0,     max_size=40*KB)
+cap(CAP_NETWORK_STATUS,      PII_YES,                   max_size=50*KB,
+    max_time=30)
+cap(CAP_PAM,                 PII_NO,                    max_size=50*KB)
+cap(CAP_PERSISTENT_STATS,    PII_MAYBE,                 max_size=50*MB,
+    max_time=60)
+cap(CAP_PROCESS_LIST,        PII_YES,                   max_size=30*KB,
+    max_time=20)
+cap(CAP_SYSTEM_LOGS,         PII_MAYBE,                 max_size=50*MB,
+    max_time=5)
+cap(CAP_SYSTEM_SERVICES,     PII_NO,                    max_size=5*KB,
+    max_time=20)
+cap(CAP_VNCTERM,             PII_MAYBE, checked = False)
+cap(CAP_WLB,                 PII_NO,                    max_size=3*MB,
+    max_time=20)
+cap(CAP_X11_LOGS,            PII_NO,                    max_size=100*KB)
+cap(CAP_X11_AUTH,            PII_NO,                    max_size=100*KB)
+cap(CAP_YUM,                 PII_IF_CUSTOMIZED,         max_size=10*KB,
+    max_time=30)
+
+ANSWER_YES_TO_ALL = False
+SILENT_MODE = False
+entries = None
+data = {}
+dev_null = open('/dev/null', 'r+')
+
+def output(x):
+    global SILENT_MODE
+    if not SILENT_MODE:
+        print x
+
+def output_ts(x):
+    output("[%s]  %s" % (time.strftime("%x %X %Z"), x))
+
+def cmd_output(cap, args, label = None, filter = None):
+    if cap in entries:
+        if not label:
+            if isinstance(args, list):
+                a = [aa for aa in args]
+                a[0] = os.path.basename(a[0])
+                label = ' '.join(a)
+            else:
+                label = args
+        data[label] = {'cap': cap, 'cmd_args': args, 'filter': filter}
+
+def file_output(cap, path_list):
+    if cap in entries:
+        for p in path_list:
+            if os.path.exists(p):
+                if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
+                        cap_sizes[cap] < caps[cap][MAX_SIZE]:
+                    data[p] = {'cap': cap, 'filename': p}
+                    try:
+                        s = os.stat(p)
+                        cap_sizes[cap] += s.st_size
+                    except:
+                        pass
+                else:
+                    output("Omitting %s, size constraint of %s exceeded" % (p, cap))
+
+def tree_output(cap, path, pattern = None, negate = False):
+    if cap in entries:
+        if os.path.exists(path):
+            for f in os.listdir(path):
+                fn = os.path.join(path, f)
+                if os.path.isfile(fn) and matches(fn, pattern, negate):
+                    file_output(cap, [fn])
+                elif os.path.isdir(fn):
+                    tree_output(cap, fn, pattern, negate)
+
+def func_output(cap, label, func):
+    if cap in entries:
+        t = str(func).split()
+        data[label] = {'cap': cap, 'func': func}
+
+def collect_data():
+    process_lists = {}
+
+    for (k, v) in data.items():
+        cap = v['cap']
+        if v.has_key('cmd_args'):
+            v['output'] = StringIOmtime()
+            if not process_lists.has_key(cap):
+                process_lists[cap] = []
+            process_lists[cap].append(ProcOutput(v['cmd_args'], caps[cap][MAX_TIME], v['output'], v['filter']))
+        elif v.has_key('filename') and v['filename'].startswith('/proc/'):
+            # proc files must be read into memory
+            try:
+                f = open(v['filename'], 'r')
+                s = f.read()
+                f.close()
+                if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
+                        cap_sizes[cap] < caps[cap][MAX_SIZE]:
+                    v['output'] = StringIOmtime(s)
+                    cap_sizes[cap] += len(s)
+                else:
+                    output("Omitting %s, size constraint of %s exceeded" % (v['filename'], cap))
+            except:
+                pass
+        elif v.has_key('func'):
+            try:
+                s = v['func'](cap)
+            except Exception, e:
+                s = str(e)
+            if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
+                    cap_sizes[cap] < caps[cap][MAX_SIZE]:
+                v['output'] = StringIOmtime(s)
+                cap_sizes[cap] += len(s)
+            else:
+                output("Omitting %s, size constraint of %s exceeded" % (k, cap))
+
+    run_procs(process_lists.values())
+
+
+def main(argv = None):
+    global ANSWER_YES_TO_ALL, SILENT_MODE
+    global entries, data, dbg
+
+    # we need access to privileged files, exit if we are not running as root
+    if os.getuid() != 0:
+        print >>sys.stderr, "Error: ovs-bugtool must be run as root"
+        return 1
+
+    output_file = None
+    output_type = 'tar.bz2'
+    output_fd = -1
+
+    if argv is None:
+        argv = sys.argv
+
+    try:
+        (options, params) = getopt.gnu_getopt(
+            argv, 'sy', ['capabilities', 'silent', 'yestoall', 'entries=',
+                         'output=', 'outfd=', 'outfile=', 'all', 'unlimited',
+                         'debug'])
+    except getopt.GetoptError, opterr:
+        print >>sys.stderr, opterr
+        return 2
+
+    try:
+        load_plugins(True)
+    except:
+        pass
+
+    entries = [e for e in caps.keys() if caps[e][CHECKED]]
+
+    for (k, v) in options:
+        if k == '--capabilities':
+            update_capabilities()
+            print_capabilities()
+            return 0
+
+        if k == '--output':
+            if  v in ['tar', 'tar.bz2', 'tar.gz', 'zip']:
+                output_type = v
+            else:
+                print >>sys.stderr, "Invalid output format '%s'" % v
+                return 2
+
+        # "-s" or "--silent" means suppress output (except for the final
+        # output filename at the end)
+        if k in ['-s', '--silent']:
+            SILENT_MODE = True
+
+        if k == '--entries' and v != '':
+            entries = v.split(',')
+
+        # If the user runs the script with "-y" or "--yestoall" we don't ask
+        # all the really annoying questions.
+        if k in ['-y', '--yestoall']:
+            ANSWER_YES_TO_ALL = True
+
+        if k == '--outfd':
+            output_fd = int(v)
+            try:
+                old = fcntl.fcntl(output_fd, fcntl.F_GETFD)
+                fcntl.fcntl(output_fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
+            except:
+                print >>sys.stderr, "Invalid output file descriptor", output_fd
+                return 2
+
+        if k == '--outfile':
+            output_file = v
+
+        elif k == '--all':
+            entries = caps.keys()
+        elif k == '--unlimited':
+            unlimited_data = True
+        elif k == '--debug':
+            dbg = True
+            ProcOutput.debug = True
+
+    if len(params) != 1:
+        print >>sys.stderr, "Invalid additional arguments", str(params)
+        return 2
+
+    if output_fd != -1 and output_type != 'tar':
+        print >>sys.stderr, "Option '--outfd' only valid with '--output=tar'"
+        return 2
+
+    if output_fd != -1 and output_file is not None:
+        print >>sys.stderr, "Cannot set both '--outfd' and '--outfile'"
+        return 2
+
+    if ANSWER_YES_TO_ALL:
+        output("Warning: '--yestoall' argument provided, will not prompt for individual files.")
+
+    output('''
+This application will collate dmesg output, details of the
+hardware configuration of your machine, information about the build of
+openvswitch that you are using, plus, if you allow it, various logs.
+
+The collated information will be saved as a .%s for archiving or
+sending to a Technical Support Representative.
+
+The logs may contain private information, and if you are at all
+worried about that, you should exit now, or you should explicitly
+exclude those logs from the archive.
+
+''' % output_type)
+
+    # assemble potential data
+
+    file_output(CAP_BOOT_LOADER, [GRUB_CONFIG])
+    cmd_output(CAP_BOOT_LOADER, [LS, '-lR', '/boot'])
+    cmd_output(CAP_BOOT_LOADER, [MD5SUM, BOOT_KERNEL, BOOT_INITRD], label='vmlinuz-initrd.md5sum')
+
+    tree_output(CAP_COLLECTD_LOGS, COLLECTD_LOGS_DIR)
+    cmd_output(CAP_DISK_INFO, [FDISK, '-l'])
+    file_output(CAP_DISK_INFO, [PROC_PARTITIONS, PROC_MOUNTS])
+    file_output(CAP_DISK_INFO, [FSTAB, ISCSI_CONF, ISCSI_INITIATOR])
+    cmd_output(CAP_DISK_INFO, [DF, '-alT'])
+    cmd_output(CAP_DISK_INFO, [DF, '-alTi'])
+    for d in disk_list():
+        cmd_output(CAP_DISK_INFO, [HDPARM, '-I', '/dev/%s' % d])
+    if len(pidof('iscsid')) != 0:
+        cmd_output(CAP_DISK_INFO, [ISCSIADM, '-m', 'node'])
+    cmd_output(CAP_DISK_INFO, [VGSCAN])
+    cmd_output(CAP_DISK_INFO, [PVS])
+    cmd_output(CAP_DISK_INFO, [VGS])
+    cmd_output(CAP_DISK_INFO, [LVS])
+    file_output(CAP_DISK_INFO, [LVM_CACHE, LVM_CONFIG])
+    cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_host'])
+    cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_disk'])
+    cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/fc_transport'])
+    cmd_output(CAP_DISK_INFO, [SG_MAP, '-x'])
+    func_output(CAP_DISK_INFO, 'scsi-hosts', dump_scsi_hosts)
+    cmd_output(CAP_DISK_INFO, [LVDISPLAY, '--map'])
+
+    file_output(CAP_HARDWARE_INFO, [PROC_CPUINFO, PROC_MEMINFO, PROC_IOPORTS, PROC_INTERRUPTS])
+    cmd_output(CAP_HARDWARE_INFO, [DMIDECODE])
+    cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-n'])
+    cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-vv'])
+    file_output(CAP_HARDWARE_INFO, [PROC_USB_DEV, PROC_SCSI])
+    file_output(CAP_HARDWARE_INFO, [SYSCONFIG_HWCONF])
+    cmd_output(CAP_HARDWARE_INFO, [LS, '-lR', '/dev'])
+    # FIXME IDE?
+
+    for d in disk_list():
+        cmd_output(CAP_HDPARM_T, [HDPARM, '-tT', '/dev/%s' % d])
+
+    file_output(CAP_KERNEL_INFO, [PROC_VERSION, PROC_MODULES, PROC_DEVICES, 
+                                  PROC_FILESYSTEMS, PROC_CMDLINE])
+    cmd_output(CAP_KERNEL_INFO, [ZCAT, PROC_CONFIG], label='config')
+    cmd_output(CAP_KERNEL_INFO, [SYSCTL, '-A'])
+    file_output(CAP_KERNEL_INFO, [MODPROBE_CONF])
+    tree_output(CAP_KERNEL_INFO, MODPROBE_DIR)
+    func_output(CAP_KERNEL_INFO, 'modinfo', module_info)
+
+    cmd_output(CAP_LOSETUP_A, [LOSETUP, '-a'])
+
+    file_output(CAP_MULTIPATH, [MULTIPATH_CONF, MPP_CONF])
+    cmd_output(CAP_MULTIPATH, [DMSETUP, 'table'])
+    func_output(CAP_MULTIPATH, 'multipathd_topology', multipathd_topology)
+    cmd_output(CAP_MULTIPATH, [MPPUTIL, '-a'])
+    if CAP_MULTIPATH in entries:
+        dump_rdac_groups(CAP_MULTIPATH)
+
+    tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, IFCFG_RE)
+    tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, ROUTE_RE)
+    file_output(CAP_NETWORK_CONFIG, [SYSCONFIG_NETWORK, RESOLV_CONF, NSSWITCH_CONF, HOSTS])
+    file_output(CAP_NETWORK_CONFIG, [NTP_CONF, IPTABLES_CONFIG, HOSTS_ALLOW, HOSTS_DENY])
+    file_output(CAP_NETWORK_CONFIG, [OPENVSWITCH_CONF_DB])
+
+    cmd_output(CAP_NETWORK_STATUS, [IFCONFIG, '-a'])
+    cmd_output(CAP_NETWORK_STATUS, [ROUTE, '-n'])
+    cmd_output(CAP_NETWORK_STATUS, [ARP, '-n'])
+    cmd_output(CAP_NETWORK_STATUS, [NETSTAT, '-an'])
+    for dir in DHCP_LEASE_DIR:
+        tree_output(CAP_NETWORK_STATUS, dir)
+    cmd_output(CAP_NETWORK_STATUS, [BRCTL, 'show'])
+    cmd_output(CAP_NETWORK_STATUS, [IPTABLES, '-nL'])
+    for p in os.listdir('/sys/class/net/'):
+        try:
+            f = open('/sys/class/net/%s/type' % p, 'r')
+            t = f.readline()
+            f.close()
+            if int(t) == 1:
+                # ARPHRD_ETHER
+                cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, p])
+                cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-S', p])
+                cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-k', p])
+                cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-i', p])
+                cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-c', p])
+                cmd_output(CAP_NETWORK_STATUS,
+                           [TC, '-s', '-d', 'class', 'show', 'dev', p])
+        except:
+            pass
+    tree_output(CAP_NETWORK_STATUS, PROC_NET_BONDING_DIR)
+    tree_output(CAP_NETWORK_STATUS, PROC_NET_VLAN_DIR)
+    cmd_output(CAP_NETWORK_STATUS, [TC, '-s', 'qdisc'])
+    file_output(CAP_NETWORK_STATUS, [PROC_NET_SOFTNET_STAT])
+    tree_output(CAP_NETWORK_STATUS, OPENVSWITCH_LOG_DIR)
+    if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
+        cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show'])
+        for d in dp_list():
+            cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'show', d])
+            cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'dump-flows', d])
+            cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
+        try:
+            vspidfile = open(OPENVSWITCH_VSWITCHD_PID)
+            vspid = int(vspidfile.readline().strip())
+            vspidfile.close()
+            for b in bond_list(vspid):
+                cmd_output(CAP_NETWORK_STATUS,
+                           [OVS_APPCTL, '-t', '/var/run/ovs-vswitchd.%s.ctl' % vspid, '-e' 'bond/show %s' % b],
+                           'ovs-appctl-bond-show-%s.out' % b)
+        except e:
+            pass
+
+    tree_output(CAP_PAM, PAM_DIR)
+    file_output(CAP_PAM, [KRB5_CONF])
+
+    cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
+    func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
+
+    file_output(CAP_SYSTEM_LOGS,
+         [ VAR_LOG_DIR + x for x in
+           [ 'crit.log', 'kern.log', 'daemon.log', 'user.log', 'syslog', 
+             'messages', 'secure', 'debug', 'dmesg', 'boot'] +
+           [ f % n for n in range(1, 20) \
+                 for f in ['crit.log.%d', 'crit.log.%d.gz',
+                           'kern.log.%d', 'kern.log.%d.gz',
+                           'daemon.log.%d', 'daemon.log.%d.gz',
+                           'user.log.%d', 'user.log.%d.gz',
+                           'messages.%d', 'messages.%d.gz',
+                           'syslog.%d', 'syslog.%d.gz']]])
+    if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
+        cmd_output(CAP_SYSTEM_LOGS, [DMESG])
+
+    cmd_output(CAP_SYSTEM_SERVICES, [CHKCONFIG, '--list'])
+
+    tree_output(CAP_X11_LOGS, X11_LOGS_DIR, X11_LOGS_RE)
+    tree_output(CAP_X11_AUTH, X11_AUTH_DIR, X11_AUTH_RE)
+    tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
+
+    file_output(CAP_YUM, [YUM_LOG])
+    tree_output(CAP_YUM, YUM_REPOS_DIR)
+    cmd_output(CAP_YUM, [RPM, '-qa'])
+
+    try:
+        load_plugins()
+    except:
+        pass
+    
+    # permit the user to filter out data
+    for k in sorted(data.keys()):
+        if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % k):
+            del data[k]
+
+    # collect selected data now
+    output_ts('Running commands to collect data')
+    collect_data()
+
+    subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
+
+    # include inventory
+    data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
+
+    # create archive
+    if output_fd == -1:
+        if output_file is None:
+            dirname = BUG_DIR
+        else:
+            dirname = os.path.dirname(output_file)
+        if dirname and not os.path.exists(dirname):
+            try:
+                os.makedirs(dirname)
+            except:
+                pass
+
+    if output_fd == -1:
+        output_ts('Creating output file')
+
+    if output_type.startswith('tar'):
+        make_tar(subdir, output_type, output_fd, output_file)
+    else:
+        make_zip(subdir, output_file)
+
+    clean_tapdisk_logs()
+
+    if dbg:
+        print >>sys.stderr, "Category sizes (max, actual):\n"
+        for c in caps.keys():
+            print >>sys.stderr, "    %s (%d, %d)" % (c, caps[c][MAX_SIZE],
+                                                     cap_sizes[c])
+    return 0
+
+def find_tapdisk_logs():
+    return glob.glob('/var/log/blktap/*.log*')
+
+def generate_tapdisk_logs():
+    for pid in pidof('tapdisk'):
+       try:
+           os.kill(pid, SIGUSR1)
+            output_ts("Including logs for tapdisk process %d" % pid)
+        except :
+            pass
+    # give processes a second to write their logs
+    time.sleep(1)
+
+def clean_tapdisk_logs():
+    for filename in find_tapdisk_logs():
+        try:
+            os.remove(filename)
+        except :
+            pass
+
+def filter_db_pii(str, state):
+    if 'in_secret_table' not in state:
+        state['in_secret_table'] = False
+
+    if str.startswith('<table ') and 'name="secret"' in str:
+        state['in_secret_table'] = True
+    elif str.startswith('</table>'):
+        state['in_secret_table'] = False
+
+    if state['in_secret_table'] and str.startswith("<row"): # match only on DB rows
+        str = re.sub(r'(value=")[^"]+(")', r'\1REMOVED\2', str)
+    return str
+
+def dump_scsi_hosts(cap):
+    output = ''
+    l = os.listdir('/sys/class/scsi_host')
+    l.sort()
+
+    for h in l:
+        procname = ''
+        try:
+                f = open('/sys/class/scsi_host/%s/proc_name' % h)
+                procname = f.readline().strip("\n")
+                f.close()
+        except:
+                pass
+        modelname = None
+        try:
+                f = open('/sys/class/scsi_host/%s/model_name' % h)
+                modelname = f.readline().strip("\n")
+                f.close()
+        except:
+                pass
+
+        output += "%s:\n" %h
+        output += "    %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
+
+    return output
+
+def module_info(cap):
+    output = StringIO.StringIO()
+    modules = open(PROC_MODULES, 'r')
+    procs = []
+
+    for line in modules:
+        module = line.split()[0]
+        procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
+    modules.close()
+
+    run_procs([procs])
+
+    return output.getvalue()
+
+
+def multipathd_topology(cap):
+    pipe = Popen([MULTIPATHD, '-k'], bufsize=1, stdin=PIPE, 
+                     stdout=PIPE, stderr=dev_null)
+    stdout, stderr = pipe.communicate('show topology')
+
+    return stdout
+
+def dp_list():
+    output = StringIO.StringIO()
+    procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
+
+    run_procs([procs])
+
+    if not procs[0].timed_out:
+        return output.getvalue().splitlines()
+    return []
+
+def bond_list(pid):
+    output = StringIO.StringIO()
+    procs = [ProcOutput([OVS_APPCTL, '-t', '/var/run/ovs-vswitchd.%s.ctl' % pid, '-e' 'bond/list'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
+
+    run_procs([procs])
+
+    if not procs[0].timed_out:
+        bonds = output.getvalue().splitlines()[1:]
+        return [x.split('\t')[1] for x in bonds]
+    return []
+
+def fd_usage(cap):
+    output = ''
+    fd_dict = {}
+    for d in [p for p in os.listdir('/proc') if p.isdigit()]:
+        try:
+            fh = open('/proc/'+d+'/cmdline')
+            name = fh.readline()
+            num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
+            if num_fds > 0:
+                if not num_fds in fd_dict:
+                    fd_dict[num_fds] = []
+                fd_dict[num_fds].append(name.replace('\0', ' ').strip())
+        finally:
+            fh.close()
+    keys = fd_dict.keys()
+    keys.sort(lambda a, b: int(b) - int(a))
+    for k in keys:
+        output += "%s: %s\n" % (k, str(fd_dict[k]))
+    return output
+
+def dump_rdac_groups(cap):
+    output = StringIO.StringIO()
+    procs = [ProcOutput([MPPUTIL, '-a'], caps[cap][MAX_TIME], output)]
+
+    run_procs([procs])
+
+    if not procs[0].timed_out:
+        proc_line = 0
+        for line in output.getvalue().splitlines():
+            if line.startswith('ID'):
+                proc_line = 2
+            elif line.startswith('----'):
+                proc_line -= 1
+            elif proc_line > 0:
+                group, _ = line.split(None, 1)
+                cmd_output(cap, [MPPUTIL, '-g', group])
+
+def load_plugins(just_capabilities = False):
+    def getText(nodelist):
+        rc = ""
+        for node in nodelist:
+            if node.nodeType == node.TEXT_NODE:
+                rc += node.data
+        return rc.encode()
+
+    def getBoolAttr(el, attr, default = False):
+        ret = default
+        val = el.getAttribute(attr).lower()
+        if val in ['true', 'false', 'yes', 'no']:
+            ret = val in ['true', 'yes']
+        return ret
+        
+    for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
+        if not caps.has_key(dir):
+            if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
+                continue
+            xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
+            assert xmldoc.documentElement.tagName == "capability"
+
+            pii, min_size, max_size, min_time, max_time, mime = \
+                 PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
+
+            if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
+                pii = xmldoc.documentElement.getAttribute("pii")
+            if xmldoc.documentElement.getAttribute("min_size") != '':
+                min_size = long(xmldoc.documentElement.getAttribute("min_size"))
+            if xmldoc.documentElement.getAttribute("max_size") != '':
+                max_size = long(xmldoc.documentElement.getAttribute("max_size"))
+            if xmldoc.documentElement.getAttribute("min_time") != '':
+                min_time = int(xmldoc.documentElement.getAttribute("min_time"))
+            if xmldoc.documentElement.getAttribute("max_time") != '':
+                max_time = int(xmldoc.documentElement.getAttribute("max_time"))
+            if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
+                mime = xmldoc.documentElement.getAttribute("mime")
+            checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
+            hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
+
+            cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
+
+        if just_capabilities:
+            continue
+                    
+        plugdir = os.path.join(PLUGIN_DIR, dir)
+        for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
+            xmldoc = parse(os.path.join(plugdir, file))
+            assert xmldoc.documentElement.tagName == "collect"
+
+            for el in xmldoc.documentElement.getElementsByTagName("*"):
+                if el.tagName == "files":
+                    file_output(dir, getText(el.childNodes).split())
+                elif el.tagName == "directory":
+                    pattern = el.getAttribute("pattern")
+                    if pattern == '': pattern = None
+                    negate = getBoolAttr(el, 'negate')
+                    tree_output(dir, getText(el.childNodes), pattern and re.compile(pattern) or None, negate)
+                elif el.tagName == "command":
+                    label = el.getAttribute("label")
+                    if label == '': label = None
+                    cmd_output(dir, getText(el.childNodes), label)
+
+def make_tar(subdir, suffix, output_fd, output_file):
+    global SILENT_MODE, data
+
+    mode = 'w'
+    if suffix == 'tar.bz2':
+        mode = 'w:bz2'
+    elif suffix == 'tar.gz':
+        mode = 'w:gz'
+
+    if output_fd == -1:
+        if output_file is None:
+            filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
+        else:
+            filename = output_file
+        tf = tarfile.open(filename, mode)
+    else:
+        tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
+
+    try:
+        for (k, v) in data.items():
+            try:
+                tar_filename = os.path.join(subdir, construct_filename(k, v))
+                ti = tarfile.TarInfo(tar_filename)
+
+                ti.uname = 'root'
+                ti.gname = 'root'
+
+                if v.has_key('output'):
+                    ti.mtime = v['output'].mtime
+                    ti.size = len(v['output'].getvalue())
+                    v['output'].seek(0)
+                    tf.addfile(ti, v['output'])
+                elif v.has_key('filename'):
+                    s = os.stat(v['filename'])
+                    ti.mtime = s.st_mtime
+                    ti.size = s.st_size
+                    tf.addfile(ti, file(v['filename']))
+            except:
+                pass
+    finally:
+        tf.close()
+
+    if output_fd == -1:
+        output ('Writing tarball %s successful.' % filename)
+        if SILENT_MODE:
+            print filename
+
+
+def make_zip(subdir, output_file):
+    global SILENT_MODE, data
+
+    if output_file is None:
+        filename = "%s/%s.zip" % (BUG_DIR, subdir)
+    else:
+        filename = output_file
+    zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
+
+    try:
+        for (k, v) in data.items():
+            try:
+                dest = os.path.join(subdir, construct_filename(k, v))
+
+                if v.has_key('output'):
+                    zf.writestr(dest, v['output'].getvalue())
+                else:
+                    if os.stat(v['filename']).st_size < 50:
+                        compress_type = zipfile.ZIP_STORED
+                    else:
+                        compress_type = zipfile.ZIP_DEFLATED
+                    zf.write(v['filename'], dest, compress_type)
+            except:
+                pass
+    finally:
+        zf.close()
+    
+    output ('Writing archive %s successful.' % filename)
+    if SILENT_MODE:
+        print filename
+
+
+def make_inventory(inventory, subdir):
+    document = getDOMImplementation().createDocument(
+        None, INVENTORY_XML_ROOT, None)
+
+    # create summary entry
+    s = document.createElement(INVENTORY_XML_SUMMARY)
+    user = os.getenv('SUDO_USER', os.getenv('USER'))
+    if user:
+        s.setAttribute('user', user)
+    s.setAttribute('date', time.strftime('%c'))
+    s.setAttribute('hostname', platform.node())
+    s.setAttribute('uname', ' '.join(platform.uname()))
+    s.setAttribute('uptime', commands.getoutput(UPTIME))
+    document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
+
+    map(lambda (k, v): inventory_entry(document, subdir, k, v),
+        inventory.items())
+    return document.toprettyxml()
+
+def inventory_entry(document, subdir, k, v):
+    try:
+        el = document.createElement(INVENTORY_XML_ELEMENT)
+        el.setAttribute('capability', v['cap'])
+        el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
+        el.setAttribute('md5sum', md5sum(v))
+        document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
+    except:
+        pass
+
+
+def md5sum(d):
+    m = md5.new()
+    if d.has_key('filename'):
+        f = open(d['filename'])
+        data = f.read(1024)
+        while len(data) > 0:
+            m.update(data)
+            data = f.read(1024)
+        f.close()
+    elif d.has_key('output'):
+        m.update(d['output'].getvalue())
+    return m.hexdigest()
+
+
+def construct_filename(k, v):
+    if v.has_key('filename'):
+        if v['filename'][0] == '/':
+            return v['filename'][1:]
+        else:
+            return v['filename']
+    s = k.replace(' ', '-')
+    s = s.replace('--', '-')
+    s = s.replace('/', '%')
+    if s.find('.') == -1:
+        s += '.out'
+
+    return s
+
+def update_capabilities():
+    pass
+
+def update_cap_size(cap, size):
+    update_cap(cap, MIN_SIZE, size)
+    update_cap(cap, MAX_SIZE, size)
+    update_cap(cap, CHECKED, size > 0)
+
+
+def update_cap(cap, k, v):
+    global caps
+    l = list(caps[cap])
+    l[k] = v
+    caps[cap] = tuple(l)
+
+
+def size_of_dir(d, pattern = None, negate = False):
+    if os.path.isdir(d):
+        return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
+                           pattern, negate)
+    else:
+        return 0
+
+
+def size_of_all(files, pattern = None, negate = False):
+    return sum([size_of(f, pattern, negate) for f in files])
+
+
+def matches(f, pattern, negate):
+    if negate:
+        return not matches(f, pattern, False)
+    else:
+        return pattern is None or pattern.match(f)
+
+
+def size_of(f, pattern, negate):
+    if os.path.isfile(f) and matches(f, pattern, negate):
+        return os.stat(f)[6]
+    else:
+        return size_of_dir(f, pattern, negate)
+
+
+def print_capabilities():
+    document = getDOMImplementation().createDocument(
+        "ns", CAP_XML_ROOT, None)
+    map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
+    print document.toprettyxml()
+
+def capability(document, key):
+    c = caps[key]
+    el = document.createElement(CAP_XML_ELEMENT)
+    el.setAttribute('key', c[KEY])
+    el.setAttribute('pii', c[PII])
+    el.setAttribute('min-size', str(c[MIN_SIZE]))
+    el.setAttribute('max-size', str(c[MAX_SIZE]))
+    el.setAttribute('min-time', str(c[MIN_TIME]))
+    el.setAttribute('max-time', str(c[MAX_TIME]))
+    el.setAttribute('content-type', c[MIME])
+    el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
+    document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
+
+
+def prettyDict(d):
+    format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
+    return '\n'.join([format % i for i in d.items()]) + '\n'
+
+
+def yes(prompt):
+    yn = raw_input(prompt)
+
+    return len(yn) == 0 or yn.lower()[0] == 'y'
+
+
+partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
+
+def disk_list():
+    disks = []
+    try:
+        f = open('/proc/partitions')
+        f.readline()
+        f.readline()
+        for line in f.readlines():
+            (major, minor, blocks, name) = line.split()
+            if int(major) < 254 and not partition_re.match(name):
+                disks.append(name)
+        f.close()
+    except:
+        pass
+    return disks
+
+
+class ProcOutput:
+    debug = False
+
+    def __init__(self, command, max_time, inst=None, filter=None):
+        self.command = command
+        self.max_time = max_time
+        self.inst = inst
+        self.running = False
+        self.status = None
+        self.timed_out = False
+        self.failed = False
+        self.timeout = int(time.time()) + self.max_time
+        self.filter = filter
+        self.filter_state = {}
+
+    def __del__(self):
+        self.terminate()
+
+    def cmdAsStr(self):
+        return isinstance(self.command, list) and ' '.join(self.command) or self.command
+
+    def run(self):
+        self.timed_out = False
+        try:
+            if ProcOutput.debug:
+                output_ts("Starting '%s'" % self.cmdAsStr())
+            self.proc = Popen(self.command, bufsize=1, stdin=dev_null, stdout=PIPE, stderr=dev_null, shell=isinstance(self.command, str))
+            old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
+            fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
+            self.running = True
+            self.failed = False
+        except:
+            output_ts("'%s' failed" % self.cmdAsStr())
+            self.running = False
+            self.failed = True
+
+    def terminate(self):
+        if self.running:
+            try:
+                os.kill(self.proc.pid, SIGTERM)
+            except:
+                pass
+            self.proc = None
+            self.running = False
+            self.status = SIGTERM
+
+    def read_line(self):
+        assert self.running
+        line = self.proc.stdout.readline()
+        if line == '':
+            # process exited
+            self.status = self.proc.wait()
+            self.proc = None
+            self.running = False
+        else:
+            if self.filter:
+                line = self.filter(line, self.filter_state)
+            if self.inst:
+                self.inst.write(line)
+
+def run_procs(procs):
+    while True:
+        pipes = []
+        active_procs = []
+
+        for pp in procs:
+            for p in pp:
+                if p.running:
+                    active_procs.append(p)
+                    pipes.append(p.proc.stdout)
+                    break
+                elif p.status == None and not p.failed and not p.timed_out:
+                    p.run()
+                    if p.running:
+                        active_procs.append(p)
+                        pipes.append(p.proc.stdout)
+                        break
+
+        if len(pipes) == 0:
+            # all finished
+            break
+
+        (i, o, x) = select(pipes, [], [], 1.0)
+        now = int(time.time())
+
+        # handle process output
+        for p in active_procs:
+            if p.proc.stdout in i:
+                p.read_line()
+
+            # handle timeout
+            if p.running and now > p.timeout:
+                output_ts("'%s' timed out" % p.cmdAsStr())
+                if p.inst:
+                    p.inst.write("\n** timeout **\n")
+                p.timed_out = True
+                p.terminate()
+
+
+def pidof(name):
+    pids = []
+
+    for d in [p for p in os.listdir('/proc') if p.isdigit()]:
+        try:
+            if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
+                pids.append(int(d))
+        except:
+            pass
+
+    return pids
+
+
+class StringIOmtime(StringIO.StringIO):
+    def __init__(self, buf = ''):
+        StringIO.StringIO.__init__(self, buf)
+        self.mtime = time.time()
+
+    def write(self, s):
+        StringIO.StringIO.write(self, s)
+        self.mtime = time.time()
+
+
+if __name__ == "__main__":
+    try:
+        sys.exit(main())
+    except KeyboardInterrupt:
+        print "\nInterrupted."
+        sys.exit(3)
diff --git a/utilities/ovs-bugtool.8 b/utilities/ovs-bugtool.8
new file mode 100644 (file)
index 0000000..6755d6a
--- /dev/null
@@ -0,0 +1,52 @@
+.\" -*- nroff -*-
+.de IQ
+.  br
+.  ns
+.  IP "\\$1"
+..
+.TH ovs\-bugtool 8 "June 2011" "Open vSwitch" "Open vSwitch Manual"
+.\" This program's name:
+.ds PN ovs\-bugtool
+.
+.SH NAME
+ovs\-bugtool \- Open vSwitch bug reporting utility
+.
+.SH SYNOPSIS
+.B ovs\-bugtool
+.
+.SH DESCRIPTION
+Generate a debug bundle with useful information about Open vSwitch on this
+system and places it in \fB/var/log/ovs-bugtool\fR.
+.
+.SH OPTIONS
+.
+.IP "\fB\-\-all\fR"
+Use all available capabilities.
+.
+.IP "\fB\-\-capabilities\fR"
+List \fBovs\-bugtool\fR capabilities.
+.
+.IP "\fB\-\-debug\fR"
+Print verbose debugging output.
+.
+.IP "\fB\-\-entries=\fIlist\fR"
+Use the capabilities specified in a comma-separated list.
+.
+.IP "\fB\-\-output=\fIfiletype\fR"
+Generate a debug bundle with the specified file type.  Options include
+\fBtar\fR, \fBtar.gz\fR, \fBtar.bz2\fR, and \fBzip\fR.
+.
+.IP "\fB\-\-silent\fR"
+Suppress output.
+.
+.IP "\fB\-\-unlimited\fR"
+Do not exclude files which are too large.
+.
+.IP "\fB\-\-yestoall\fR"
+Answer yes to all prompts.
+.
+.SH BUGS
+\fBovs\-bugtool\fR makes many assumptions about file locations and the
+availability of system utilities.  It has been tested on Debian and
+Red Hat and derived distributions.  On other distributions it is
+likely to be less useful.
index f9d7464b05574eb47806707695461f7dd8ab0ff3..77afeb2d08fd92eefd8455896d283f04055eef1f 100644 (file)
@@ -121,8 +121,10 @@ install xenserver/uuid.py $RPM_BUILD_ROOT/usr/share/openvswitch/python
 
 # Get rid of stuff we don't want to make RPM happy.
 rm \
+    $RPM_BUILD_ROOT/usr/bin/ovs-bugtool \
     $RPM_BUILD_ROOT/usr/bin/ovs-controller \
     $RPM_BUILD_ROOT/usr/bin/ovs-pki \
+    $RPM_BUILD_ROOT/usr/share/man/man8/ovs-bugtool.8 \
     $RPM_BUILD_ROOT/usr/share/man/man8/ovs-controller.8 \
     $RPM_BUILD_ROOT/usr/share/man/man8/ovs-pki.8