xenserver: Merge upstream changes and drop pre-5.6.100 support.
[openvswitch] / xenserver / usr_sbin_xen-bugtool
1 #!/usr/bin/env python
2
3 # This library is free software; you can redistribute it and/or
4 # modify it under the terms of version 2.1 of the GNU Lesser General Public
5 # License as published by the Free Software Foundation.
6 #
7 # This library is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
10 # Lesser General Public License for more details.
11 #
12 # You should have received a copy of the GNU Lesser General Public
13 # License along with this library; if not, write to the Free Software
14 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
15 #
16 # Copyright (c) 2005, 2007 XenSource Ltd.
17
18
19 #
20 # To add new entries to the bugtool, you need to:
21 #
22 # Create a new capability.  These declare the new entry to the GUI, including
23 # the expected size, time to collect, privacy implications, and whether the
24 # capability should be selected by default.  One capability may refer to
25 # multiple files, assuming that they can be reasonably grouped together, and
26 # have the same privacy implications.  You need:
27 #
28 #   A new CAP_ constant.
29 #   A cap() invocation to declare the capability.
30 #
31 # You then need to add calls to main() to collect the files.  These will
32 # typically be calls to the helpers file_output(), tree_output(), cmd_output(),
33 # or func_output().
34 #
35
36 import getopt
37 import re
38 import os
39 import StringIO
40 import sys
41 import tarfile
42 import time
43 import commands
44 import pprint
45 from xml.dom.minidom import parse, getDOMImplementation
46 import zipfile
47 from subprocess import Popen, PIPE
48 from select import select
49 from signal import SIGTERM, SIGUSR1
50 import md5
51 import platform
52 import fcntl
53 import glob
54 import urllib
55 import socket
56 import base64
57
58 sys.path.append('/usr/lib/python')
59 sys.path.append('/usr/lib64/python')
60
61 import xen.lowlevel.xc
62 import XenAPI
63
64 OS_RELEASE = platform.release()
65
66 #
67 # Files & directories
68 #
69
70 BUG_DIR = "/var/opt/xen/bug-report"
71 PLUGIN_DIR = "/etc/xensource/bugtool"
72 XAPI_BLOBS = '/var/xapi/blobs'
73 EXTLINUX_CONFIG = '/boot/extlinux.conf'
74 GRUB_CONFIG = '/boot/grub/menu.lst'
75 BOOT_KERNEL = '/boot/vmlinuz-' + OS_RELEASE
76 BOOT_INITRD = '/boot/initrd-' + OS_RELEASE + '.img'
77 PROC_PARTITIONS = '/proc/partitions'
78 FSTAB = '/etc/fstab'
79 PROC_MOUNTS = '/proc/mounts'
80 ISCSI_CONF = '/etc/iscsi/iscsid.conf'
81 ISCSI_INITIATOR = '/etc/iscsi/initiatorname.iscsi'
82 LVM_CACHE = '/etc/lvm/cache/.cache'
83 LVM_CONFIG = '/etc/lvm/lvm.conf'
84 PROC_CPUINFO = '/proc/cpuinfo'
85 PROC_MEMINFO = '/proc/meminfo'
86 PROC_IOPORTS = '/proc/ioports'
87 PROC_INTERRUPTS = '/proc/interrupts'
88 PROC_SCSI = '/proc/scsi/scsi'
89 FIRSTBOOT_DIR = '/etc/firstboot.d'
90 PROC_VERSION = '/proc/version'
91 PROC_MODULES = '/proc/modules'
92 PROC_DEVICES = '/proc/devices'
93 PROC_FILESYSTEMS = '/proc/filesystems'
94 PROC_CMDLINE = '/proc/cmdline'
95 PROC_CONFIG = '/proc/config.gz'
96 PROC_USB_DEV = '/proc/bus/usb/devices'
97 PROC_XEN_BALLOON = '/proc/xen/balloon'
98 PROC_NET_BONDING_DIR = '/proc/net/bonding'
99 PROC_NET_VLAN_DIR = '/proc/net/vlan'
100 PROC_NET_SOFTNET_STAT = '/proc/net/softnet_stat'
101 PROC_DRIVER_CCISS_DIR = '/proc/driver/cciss'
102 MODPROBE_CONF = '/etc/modprobe.conf'
103 MODPROBE_DIR = '/etc/modprobe.d'
104 BOOT_TIME_CPUS = '/etc/xensource/boot_time_cpus'
105 BOOT_TIME_MEMORY = '/etc/xensource/boot_time_memory'
106 SYSCONFIG_HWCONF = '/etc/sysconfig/hwconf'
107 SYSCONFIG_NETWORK = '/etc/sysconfig/network'
108 SYSCONFIG_NETWORK_SCRIPTS = '/etc/sysconfig/network-scripts'
109 IFCFG_RE = re.compile(r'^.*/ifcfg-.*')
110 ROUTE_RE = re.compile(r'^.*/route-.*')
111 NETWORK_DBCACHE = '/var/xapi/network.dbcache'
112 RESOLV_CONF = '/etc/resolv.conf'
113 MPP_CONF = '/etc/mpp.conf'
114 MULTIPATH_CONF = '/etc/multipath.conf'
115 NSSWITCH_CONF = '/etc/nsswitch.conf'
116 NTP_CONF = '/etc/ntp.conf'
117 IPTABLES_CONFIG = '/etc/sysconfig/iptables-config'
118 HOSTS = '/etc/hosts'
119 HOSTS_ALLOW = '/etc/hosts.allow'
120 HOSTS_DENY = '/etc/hosts.deny'
121 DHCP_LEASE_DIR = '/var/lib/dhclient'
122 OPENVSWITCH_CORE_DIR = '/var/xen/openvswitch'
123 OPENVSWITCH_CONF = '/etc/ovs-vswitchd.conf'
124 OPENVSWITCH_CONF_DB = '/etc/openvswitch/conf.db'
125 OPENVSWITCH_VSWITCHD_PID = '/var/run/openvswitch/ovs-vswitchd.pid'
126 VAR_LOG_DIR = '/var/log/'
127 VNCTERM_CORE_DIR = '/var/xen/vncterm'
128 XENSOURCE_INVENTORY = '/etc/xensource-inventory'
129 OEM_CONFIG_DIR = '/var/xsconfig'
130 OEM_CONFIG_FILES_RE = re.compile(r'^.*xensource-inventory$')
131 OEM_DB_FILES_RE = re.compile(r'^.*state\.db')
132 INITIAL_INVENTORY = '/opt/xensource/etc/initial-inventory'
133 VENDORKERNEL_INVENTORY = '/etc/vendorkernel-inventory'
134 STATIC_VDIS = '/etc/xensource/static-vdis'
135 POOL_CONF = '/etc/xensource/pool.conf'
136 NETWORK_CONF = '/etc/xensource/network.conf'
137 PTOKEN = '/etc/xensource/ptoken'
138 XAPI_CONF = '/etc/xensource/xapi.conf'
139 XAPI_SSL_CONF = '/etc/xensource/xapi-ssl.conf'
140 DB_CONF = '/etc/xensource/db.conf'
141 DB_CONF_RIO = '/etc/xensource/db.conf.rio'
142 DB_DEFAULT_FIELDS = '/etc/xensource/db-default-fields'
143 DB_SCHEMA_SQL = '/etc/xensource/db_schema.sql'
144 XENSTORED_DB = '/var/lib/xenstored/tdb'
145 HOST_CRASHDUMPS_DIR = '/var/crash'
146 HOST_CRASHDUMP_LOGS_RE = re.compile(r'^.*\.log$')
147 X11_LOGS_DIR = VAR_LOG_DIR
148 X11_LOGS_RE = re.compile(r'.*/Xorg\..*$')
149 X11_AUTH_DIR = '/root/'
150 X11_AUTH_RE = re.compile(r'.*/\.((Xauthority)|(serverauth\.[0-9]*))$')
151 XAPI_DEBUG_DIR = '/var/xapi/debug'
152 LOG_CONF = '/etc/xensource/log.conf'
153 INSTALLED_REPOS_DIR = '/etc/xensource/installed-repos'
154 PATCH_APPLIED_DIR = '/var/patch/applied'
155 XENSERVER_LOGS = \
156     [ VAR_LOG_DIR + x for x in
157       ['xensource.log', 'audit.log', 'xenstored-access.log', 'SMlog', 'VMPRlog', 'xen/xenstored-trace.log', 
158        'xen/xen-hotplug.log', 'xen/domain-builder-ng.log', 'squeezed.log',
159        'openvswitch/ovs-brcompatd.log', 'openvswitch/ovs-vswitchd.log', 'openvswitch/ovsdb-server.log' ] +
160       [ f % n for n in range(1, 20) \
161             for f in ['xensource.log.%d', 'xensource.log.%d.gz','SMlog.%d', 'SMlog.%d.gz', 'VMPRlog.%d', 'VMPRlog.%d.gz',
162                       'audit.log.%d', 'audit.log.%d.gz', 'xenstored-access.log.%d', 'xenstored-access.log.%d.gz', \
163                       'xen/xenstored-access.log.%d', 'xen/xenstored-access.log.%d.gz', 'squeezed.log.%d', \
164                       'openvswitch/ovs-brcompatd.log.%d', 'openvswitch/ovs-brcompatd.log.%d.gz', \
165                       'openvswitch/ovs-vswitchd.log.%d', 'openvswitch/ovs-vswitchd.log.%d.gz', \
166                       'openvswitch/ovsdb-server.log.%d', 'openvswitch/ovsdb-server.log.%d.gz']]] \
167       + glob.glob('/tmp/qemu.[0-9]*')
168 OEM_XENSERVER_LOGS_RE = re.compile(r'^.*xensource\.log$')
169 XHA_LOG = '/var/log/xha.log'
170 XHAD_CONF = '/etc/xensource/xhad.conf'
171 YUM_LOG = '/var/log/yum.log'
172 YUM_REPOS_DIR = '/etc/yum.repos.d'
173 PAM_DIR = '/etc/pam.d'
174 FIST_RE = re.compile(r'.*/fist_')
175 LICENSE_LOGS = \
176              [ VAR_LOG_DIR + x for x in
177                ['v6d.log'] +
178                [ f % n for n in range(1, 20) \
179                  for f in ['v6d.log.%d', 'v6d.log.%d.gz' ]]]
180 LWIDENTITY_JOIN_LOG = '/tmp/lwidentity.join.log'
181 HOSTS_LWIDENTITY_ORIG = '/etc/hosts.lwidentity.orig'
182 KRB5_CONF = '/etc/krb5.conf'
183 LIKEWISE_DIR = '/var/lib/likewise'
184 XENGUEST_LOG = '/tmp/xenguest.log'
185
186 #
187 # External programs
188 #
189
190 ARP = '/sbin/arp'
191 BIOSDEVNAME = '/sbin/biosdevname'
192 BRCTL = '/usr/sbin/brctl'
193 CAT = '/bin/cat'
194 CHKCONFIG = '/sbin/chkconfig'
195 CSL = '/opt/Citrix/StorageLink/bin/csl'
196 DF = '/bin/df'
197 DMESG = '/bin/dmesg'
198 DMIDECODE = '/usr/sbin/dmidecode'
199 DMSETUP = '/sbin/dmsetup'
200 ETHTOOL = '/sbin/ethtool'
201 FDISK = '/sbin/fdisk'
202 FIND = '/usr/bin/find'
203 HA_QUERY_LIVESET = '/opt/xensource/debug/debug_ha_query_liveset'
204 HDPARM = '/sbin/hdparm'
205 IFCONFIG = '/sbin/ifconfig'
206 IPTABLES = '/sbin/iptables'
207 ISCSIADM = '/sbin/iscsiadm'
208 LIST_DOMAINS = '/opt/xensource/bin/list_domains'
209 LOSETUP = '/sbin/losetup'
210 LS = '/bin/ls'
211 LSPCI = '/sbin/lspci'
212 LVS = '/usr/sbin/lvs'
213 LVDISPLAY = '/usr/sbin/lvdisplay'
214 MD5SUM = '/usr/bin/md5sum'
215 MODINFO = '/sbin/modinfo'
216 MPPUTIL = '/usr/sbin/mppUtil'
217 MULTIPATHD = '/sbin/multipathd'
218 NETSTAT = '/bin/netstat'
219 OVS_DPCTL = '/usr/bin/ovs-dpctl'
220 OVS_OFCTL = '/usr/bin/ovs-ofctl'
221 OVS_VSCTL = '/usr/bin/ovs-vsctl'
222 OVS_APPCTL = '/usr/bin/ovs-appctl'
223 PS = '/bin/ps'
224 PVS = '/usr/sbin/pvs'
225 ROUTE = '/sbin/route'
226 RPM = '/bin/rpm'
227 SG_MAP = '/usr/bin/sg_map'
228 SQLITE = '/usr/bin/sqlite3'
229 BIN_STATIC_VDIS = '/opt/xensource/bin/static-vdis'
230 SYSCTL = '/sbin/sysctl'
231 TC = '/sbin/tc'
232 UPTIME = '/usr/bin/uptime'
233 VGS = '/usr/sbin/vgs'
234 VGSCAN = '/sbin/vgscan'
235 XAPI_DB_PROCESS = '/opt/xensource/bin/xapi-db-process'
236 XE = '/opt/xensource/bin/xe'
237 XS = '/opt/xensource/debug/xs'
238 XENSTORE_LS = '/usr/bin/xenstore-ls'
239 ZCAT = '/bin/zcat'
240
241 #
242 # PII -- Personally identifiable information.  Of particular concern are
243 # things that would identify customers, or their network topology.
244 # Passwords are never to be included in any bug report, regardless of any PII
245 # declaration.
246 #
247 # NO            -- No PII will be in these entries.
248 # YES           -- PII will likely or certainly be in these entries.
249 # MAYBE         -- The user may wish to audit these entries for PII.
250 # IF_CUSTOMIZED -- If the files are unmodified, then they will contain no PII,
251 # but since we encourage customers to edit these files, PII may have been
252 # introduced by the customer.  This is used in particular for the networking
253 # scripts in dom0.
254 #
255
256 PII_NO            = 'no'
257 PII_YES           = 'yes'
258 PII_MAYBE         = 'maybe'
259 PII_IF_CUSTOMIZED = 'if_customized'
260 KEY      = 0
261 PII      = 1
262 MIN_SIZE = 2
263 MAX_SIZE = 3
264 MIN_TIME = 4
265 MAX_TIME = 5
266 MIME     = 6
267 CHECKED  = 7
268 HIDDEN   = 8
269
270 MIME_DATA = 'application/data'
271 MIME_TEXT = 'text/plain'
272
273 INVENTORY_XML_ROOT = "system-status-inventory"
274 INVENTORY_XML_SUMMARY = 'system-summary'
275 INVENTORY_XML_ELEMENT = 'inventory-entry'
276 CAP_XML_ROOT = "system-status-capabilities"
277 CAP_XML_ELEMENT = 'capability'
278
279
280 CAP_BLOBS                = 'blobs'
281 CAP_BOOT_LOADER          = 'boot-loader'
282 CAP_CVSM                 = 'CVSM'
283 CAP_DISK_INFO            = 'disk-info'
284 CAP_FIRSTBOOT            = 'firstboot'
285 CAP_HARDWARE_INFO        = 'hardware-info'
286 CAP_HDPARM_T             = 'hdparm-t'
287 CAP_HIGH_AVAILABILITY    = 'high-availability'
288 CAP_HOST_CRASHDUMP_DUMPS = 'host-crashdump-dumps'
289 CAP_HOST_CRASHDUMP_LOGS  = 'host-crashdump-logs'
290 CAP_KERNEL_INFO          = 'kernel-info'
291 CAP_LOSETUP_A            = 'loopback-devices'
292 CAP_MULTIPATH            = 'multipath'
293 CAP_NETWORK_CONFIG       = 'network-config'
294 CAP_NETWORK_STATUS       = 'network-status'
295 CAP_OEM                  = 'oem'
296 CAP_PAM                  = 'pam'
297 CAP_PROCESS_LIST         = 'process-list'
298 CAP_PERSISTENT_STATS     = 'persistent-stats'
299 CAP_SYSTEM_LOGS          = 'system-logs'
300 CAP_SYSTEM_SERVICES      = 'system-services'
301 CAP_TAPDISK_LOGS         = 'tapdisk-logs'
302 CAP_VNCTERM              = 'vncterm'
303 CAP_WLB                  = 'wlb'
304 CAP_X11_LOGS             = 'X11'
305 CAP_X11_AUTH             = 'X11-auth'
306 CAP_XAPI_DEBUG           = 'xapi-debug'
307 CAP_XAPI_SUBPROCESS      = 'xapi-subprocess'
308 CAP_XENRT                = 'xenrt'
309 CAP_XENSERVER_CONFIG     = 'xenserver-config'
310 CAP_XENSERVER_DOMAINS    = 'xenserver-domains'
311 CAP_XENSERVER_DATABASES  = 'xenserver-databases'
312 CAP_XENSERVER_INSTALL    = 'xenserver-install'
313 CAP_XENSERVER_LOGS       = 'xenserver-logs'
314 CAP_XEN_INFO             = 'xen-info'
315 CAP_XHA_LIVESET          = 'xha-liveset'
316 CAP_YUM                  = 'yum'
317
318 KB = 1024
319 MB = 1024 * 1024
320
321 caps = {}
322 cap_sizes = {}
323 unlimited_data = False
324 dbg = False
325
326 def cap(key, pii=PII_MAYBE, min_size=-1, max_size=-1, min_time=-1,
327         max_time=-1, mime=MIME_TEXT, checked=True, hidden=False):
328     if  os.getenv('XEN_RT') and max_time > 0:
329         max_time *= 5
330     caps[key] = (key, pii, min_size, max_size, min_time, max_time, mime,
331                  checked, hidden)
332     cap_sizes[key] = 0
333
334
335 cap(CAP_BLOBS,               PII_NO,                    max_size=5*MB)
336 cap(CAP_BOOT_LOADER,         PII_NO,                    max_size=3*KB,
337     max_time=5)
338 cap(CAP_CVSM,                PII_NO,                    max_size=3*MB,
339     max_time=120)
340 cap(CAP_DISK_INFO,           PII_MAYBE,                 max_size=50*KB,
341     max_time=20)
342 cap(CAP_FIRSTBOOT,           PII_YES,   min_size=60*KB, max_size=80*KB)
343 cap(CAP_HARDWARE_INFO,       PII_MAYBE,                 max_size=50*KB,
344     max_time=20)
345 cap(CAP_HDPARM_T,            PII_NO,    min_size=0,     max_size=5*KB,
346     min_time=20, max_time=90, checked=False, hidden=True)
347 cap(CAP_HIGH_AVAILABILITY,   PII_MAYBE,                 max_size=5*MB)
348 cap(CAP_HOST_CRASHDUMP_DUMPS,PII_YES, checked = False)
349 cap(CAP_HOST_CRASHDUMP_LOGS, PII_NO)
350 cap(CAP_KERNEL_INFO,         PII_MAYBE,                 max_size=120*KB,
351     max_time=5)
352 cap(CAP_LOSETUP_A,           PII_MAYBE,                 max_size=KB, max_time=5)
353 cap(CAP_MULTIPATH,           PII_MAYBE,                 max_size=20*KB,
354     max_time=10)
355 cap(CAP_NETWORK_CONFIG,      PII_IF_CUSTOMIZED,
356                                         min_size=0,     max_size=40*KB)
357 cap(CAP_NETWORK_STATUS,      PII_YES,                   max_size=19*KB,
358     max_time=30)
359 cap(CAP_PAM,                 PII_NO,                    max_size=50*KB)
360 cap(CAP_PERSISTENT_STATS,    PII_MAYBE,                 max_size=50*MB,
361     max_time=60)
362 cap(CAP_PROCESS_LIST,        PII_YES,                   max_size=30*KB,
363     max_time=20)
364 cap(CAP_SYSTEM_LOGS,         PII_MAYBE,                 max_size=50*MB,
365     max_time=5)
366 cap(CAP_SYSTEM_SERVICES,     PII_NO,                    max_size=5*KB,
367     max_time=20)
368 cap(CAP_TAPDISK_LOGS,        PII_NO,                    max_size=64*KB)
369 cap(CAP_VNCTERM,             PII_MAYBE, checked = False)
370 cap(CAP_WLB,                 PII_NO,                    max_size=3*MB,
371     max_time=20)
372 cap(CAP_X11_LOGS,            PII_NO,                    max_size=100*KB)
373 cap(CAP_X11_AUTH,            PII_NO,                    max_size=100*KB)
374 cap(CAP_XAPI_DEBUG,          PII_MAYBE,                 max_size=10*MB)
375 cap(CAP_XAPI_SUBPROCESS,     PII_NO,                    max_size=5*KB,
376     max_time=10)
377 cap(CAP_XENRT,               PII_NO,    min_size=0,     max_size=500*MB,
378     checked=False, hidden=True)
379 cap(CAP_XENSERVER_CONFIG,    PII_MAYBE,                 max_size=80*KB,
380     max_time=5)
381 cap(CAP_XENSERVER_DOMAINS,   PII_NO,                    max_size=1*KB,
382     max_time=5)
383 cap(CAP_XENSERVER_DATABASES, PII_YES,   min_size=500*KB,max_size=2*MB,
384     max_time=40)
385 cap(CAP_XENSERVER_INSTALL,   PII_MAYBE, min_size=10*KB, max_size=300*KB)
386 cap(CAP_XENSERVER_LOGS,      PII_MAYBE, min_size=0,     max_size=70*MB)
387 cap(CAP_XEN_INFO,            PII_MAYBE,                 max_size=20*KB,
388     max_time=10)
389 cap(CAP_XHA_LIVESET,         PII_MAYBE,                 max_size=10*KB,
390     max_time=10)
391 cap(CAP_YUM,                 PII_IF_CUSTOMIZED,         max_size=10*KB,
392     max_time=30)
393
394 ANSWER_YES_TO_ALL = False
395 SILENT_MODE = False
396 entries = None
397 data = {}
398 dev_null = open('/dev/null', 'r+')
399
400 def output(x):
401     global SILENT_MODE
402     if not SILENT_MODE:
403         print x
404
405 def output_ts(x):
406     output("[%s]  %s" % (time.strftime("%x %X %Z"), x))
407
408 def cmd_output(cap, args, label = None, filter = None):
409     if cap in entries:
410         if not label:
411             if isinstance(args, list):
412                 a = [aa for aa in args]
413                 a[0] = os.path.basename(a[0])
414                 label = ' '.join(a)
415             else:
416                 label = args
417         data[label] = {'cap': cap, 'cmd_args': args, 'filter': filter}
418
419 def file_output(cap, path_list):
420     if cap in entries:
421         for p in path_list:
422             if os.path.exists(p):
423                 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
424                         cap_sizes[cap] < caps[cap][MAX_SIZE]:
425                     data[p] = {'cap': cap, 'filename': p}
426                     try:
427                         s = os.stat(p)
428                         cap_sizes[cap] += s.st_size
429                     except:
430                         pass
431                 else:
432                     output("Omitting %s, size constraint of %s exceeded" % (p, cap))
433
434 def tree_output(cap, path, pattern = None, negate = False):
435     if cap in entries:
436         if os.path.exists(path):
437             for f in os.listdir(path):
438                 fn = os.path.join(path, f)
439                 if os.path.isfile(fn) and matches(fn, pattern, negate):
440                     file_output(cap, [fn])
441                 elif os.path.isdir(fn):
442                     tree_output(cap, fn, pattern, negate)
443
444 def func_output(cap, label, func):
445     if cap in entries:
446         t = str(func).split()
447         data[label] = {'cap': cap, 'func': func}
448
449 def collect_data():
450     process_lists = {}
451
452     for (k, v) in data.items():
453         cap = v['cap']
454         if v.has_key('cmd_args'):
455             v['output'] = StringIOmtime()
456             if not process_lists.has_key(cap):
457                 process_lists[cap] = []
458             process_lists[cap].append(ProcOutput(v['cmd_args'], caps[cap][MAX_TIME], v['output'], v['filter']))
459         elif v.has_key('filename') and v['filename'].startswith('/proc/'):
460             # proc files must be read into memory
461             try:
462                 f = open(v['filename'], 'r')
463                 s = f.read()
464                 f.close()
465                 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
466                         cap_sizes[cap] < caps[cap][MAX_SIZE]:
467                     v['output'] = StringIOmtime(s)
468                     cap_sizes[cap] += len(s)
469                 else:
470                     output("Omitting %s, size constraint of %s exceeded" % (v['filename'], cap))
471             except:
472                 pass
473         elif v.has_key('func'):
474             try:
475                 s = v['func'](cap)
476             except Exception, e:
477                 s = str(e)
478             if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
479                     cap_sizes[cap] < caps[cap][MAX_SIZE]:
480                 v['output'] = StringIOmtime(s)
481                 cap_sizes[cap] += len(s)
482             else:
483                 output("Omitting %s, size constraint of %s exceeded" % (k, cap))
484
485     run_procs(process_lists.values())
486
487
488 def main(argv = None):
489     global ANSWER_YES_TO_ALL, SILENT_MODE
490     global entries, data, dbg
491
492     # we need access to privileged files, exit if we are not running as root
493     if os.getuid() != 0:
494         print >>sys.stderr, "Error: xen-bugtool must be run as root"
495         return 1
496
497     output_type = 'tar.bz2'
498     output_fd = -1
499     
500     if argv is None:
501         argv = sys.argv
502
503     try:
504         (options, params) = getopt.gnu_getopt(
505             argv, 'sy', ['capabilities', 'silent', 'yestoall', 'entries=',
506                          'output=', 'outfd=', 'all', 'unlimited', 'debug'])
507     except getopt.GetoptError, opterr:
508         print >>sys.stderr, opterr
509         return 2
510
511     try:
512         load_plugins(True)
513     except:
514         pass
515
516     inventory = readKeyValueFile(XENSOURCE_INVENTORY)
517     if inventory.has_key('OEM_BUILD_NUMBER'):
518         cap(CAP_OEM,                 PII_MAYBE,                 max_size=5*MB,
519             max_time=90)
520
521     if  os.getenv('XEN_RT'):
522         entries = [CAP_BLOBS, CAP_BOOT_LOADER, CAP_CVSM, CAP_DISK_INFO, CAP_FIRSTBOOT, CAP_HARDWARE_INFO, 
523                    CAP_HOST_CRASHDUMP_DUMPS, CAP_HOST_CRASHDUMP_LOGS, CAP_KERNEL_INFO, CAP_LOSETUP_A,
524                    CAP_NETWORK_CONFIG, CAP_NETWORK_STATUS, CAP_PROCESS_LIST, CAP_HIGH_AVAILABILITY,
525                    CAP_PAM, CAP_PERSISTENT_STATS, CAP_MULTIPATH,
526                    CAP_SYSTEM_LOGS, CAP_SYSTEM_SERVICES, CAP_TAPDISK_LOGS,
527                    CAP_VNCTERM, CAP_WLB, CAP_X11_LOGS, CAP_X11_AUTH, CAP_XAPI_DEBUG, CAP_XAPI_SUBPROCESS, 
528                    CAP_XENRT, CAP_XENSERVER_CONFIG, CAP_XENSERVER_DOMAINS, CAP_XENSERVER_DATABASES, 
529                    CAP_XENSERVER_INSTALL, CAP_XENSERVER_LOGS, CAP_XEN_INFO, CAP_XHA_LIVESET, CAP_YUM]
530     else:
531         entries = [e for e in caps.keys() if caps[e][CHECKED]]
532
533     for (k, v) in options:
534         if k == '--capabilities':
535             update_capabilities()
536             print_capabilities()
537             return 0
538
539         if k == '--output':
540             if  v in ['tar', 'tar.bz2', 'zip']:
541                 output_type = v
542             else:
543                 print >>sys.stderr, "Invalid output format '%s'" % v
544                 return 2
545
546         # "-s" or "--silent" means suppress output (except for the final
547         # output filename at the end)
548         if k in ['-s', '--silent']:
549             SILENT_MODE = True
550
551         if k == '--entries' and v != '':
552             entries = v.split(',')
553
554         # If the user runs the script with "-y" or "--yestoall" we don't ask
555         # all the really annoying questions.
556         if k in ['-y', '--yestoall']:
557             ANSWER_YES_TO_ALL = True
558
559         if k == '--outfd':
560             output_fd = int(v)
561             try:
562                 old = fcntl.fcntl(output_fd, fcntl.F_GETFD)
563                 fcntl.fcntl(output_fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
564             except:
565                 print >>sys.stderr, "Invalid output file descriptor", output_fd
566                 return 2
567
568         elif k == '--all':
569             entries = caps.keys()
570         elif k == '--unlimited':
571             unlimited_data = True
572         elif k == '--debug':
573             dbg = True
574             ProcOutput.debug = True
575
576     if len(params) != 1:
577         print >>sys.stderr, "Invalid additional arguments", str(params)
578         return 2
579
580     if output_fd != -1 and output_type != 'tar':
581         print >>sys.stderr, "Option '--outfd' only valid with '--output=tar'"
582         return 2
583
584     if ANSWER_YES_TO_ALL:
585         output("Warning: '--yestoall' argument provided, will not prompt for individual files.")
586
587     output('''
588 This application will collate the Xen dmesg output, details of the
589 hardware configuration of your machine, information about the build of
590 Xen that you are using, plus, if you allow it, various logs.
591
592 The collated information will be saved as a .%s for archiving or
593 sending to a Technical Support Representative.
594
595 The logs may contain private information, and if you are at all
596 worried about that, you should exit now, or you should explicitly
597 exclude those logs from the archive.
598
599 ''' % output_type)
600
601     # assemble potential data
602     tree_output(CAP_BLOBS, XAPI_BLOBS)
603
604     file_output(CAP_BOOT_LOADER, [GRUB_CONFIG, EXTLINUX_CONFIG])
605     cmd_output(CAP_BOOT_LOADER, [LS, '-lR', '/boot'])
606     cmd_output(CAP_BOOT_LOADER, [MD5SUM, BOOT_KERNEL, BOOT_INITRD], label='vmlinuz-initrd.md5sum')
607
608     func_output(CAP_CVSM, 'csl_logs', csl_logs)
609
610     cmd_output(CAP_DISK_INFO, [FDISK, '-l'])
611     file_output(CAP_DISK_INFO, [PROC_PARTITIONS, PROC_MOUNTS])
612     file_output(CAP_DISK_INFO, [FSTAB, ISCSI_CONF, ISCSI_INITIATOR])
613     cmd_output(CAP_DISK_INFO, [DF, '-alT'])
614     cmd_output(CAP_DISK_INFO, [DF, '-alTi'])
615     for d in disk_list():
616         cmd_output(CAP_DISK_INFO, [HDPARM, '-I', '/dev/%s' % d])
617     if len(pidof('iscsid')) != 0:
618         cmd_output(CAP_DISK_INFO, [ISCSIADM, '-m', 'node'])
619     cmd_output(CAP_DISK_INFO, [VGSCAN])
620     cmd_output(CAP_DISK_INFO, [PVS])
621     cmd_output(CAP_DISK_INFO, [VGS])
622     cmd_output(CAP_DISK_INFO, [LVS])
623     file_output(CAP_DISK_INFO, [LVM_CACHE, LVM_CONFIG])
624     cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_host'])
625     cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_disk'])
626     cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/fc_transport'])
627     cmd_output(CAP_DISK_INFO, [SG_MAP, '-x'])
628     func_output(CAP_DISK_INFO, 'scsi-hosts', dump_scsi_hosts)
629     tree_output(CAP_DISK_INFO, PROC_DRIVER_CCISS_DIR)
630     cmd_output(CAP_DISK_INFO, [LVDISPLAY, '--map'])
631
632     tree_output(CAP_FIRSTBOOT, FIRSTBOOT_DIR)
633
634     file_output(CAP_HARDWARE_INFO, [PROC_CPUINFO, PROC_MEMINFO, PROC_IOPORTS, PROC_INTERRUPTS])
635     cmd_output(CAP_HARDWARE_INFO, [DMIDECODE])
636     cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-n'])
637     cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-vv'])
638     file_output(CAP_HARDWARE_INFO, [PROC_USB_DEV, PROC_SCSI])
639     file_output(CAP_HARDWARE_INFO, [BOOT_TIME_CPUS, BOOT_TIME_MEMORY])
640     file_output(CAP_HARDWARE_INFO, [SYSCONFIG_HWCONF])
641     cmd_output(CAP_HARDWARE_INFO, [LS, '-lR', '/dev'])
642     # FIXME IDE?
643
644     for d in disk_list():
645         cmd_output(CAP_HDPARM_T, [HDPARM, '-tT', '/dev/%s' % d])
646
647     file_output(CAP_HIGH_AVAILABILITY, [XHAD_CONF, XHA_LOG])
648
649     tree_output(CAP_HOST_CRASHDUMP_DUMPS, HOST_CRASHDUMPS_DIR,
650                 HOST_CRASHDUMP_LOGS_RE, True)
651     tree_output(CAP_HOST_CRASHDUMP_LOGS, HOST_CRASHDUMPS_DIR,
652                 HOST_CRASHDUMP_LOGS_RE, False)
653
654     file_output(CAP_KERNEL_INFO, [PROC_VERSION, PROC_MODULES, PROC_DEVICES, 
655                                   PROC_FILESYSTEMS, PROC_CMDLINE])
656     cmd_output(CAP_KERNEL_INFO, [ZCAT, PROC_CONFIG], label='config')
657     cmd_output(CAP_KERNEL_INFO, [SYSCTL, '-A'])
658     file_output(CAP_KERNEL_INFO, [MODPROBE_CONF])
659     tree_output(CAP_KERNEL_INFO, MODPROBE_DIR)
660     func_output(CAP_KERNEL_INFO, 'modinfo', module_info)
661
662     cmd_output(CAP_LOSETUP_A, [LOSETUP, '-a'])
663
664     file_output(CAP_MULTIPATH, [MULTIPATH_CONF, MPP_CONF])
665     cmd_output(CAP_MULTIPATH, [DMSETUP, 'table'])
666     func_output(CAP_MULTIPATH, 'multipathd_topology', multipathd_topology)
667     cmd_output(CAP_MULTIPATH, [MPPUTIL, '-a'])
668     if CAP_MULTIPATH in entries:
669         dump_rdac_groups(CAP_MULTIPATH)
670
671     file_output(CAP_NETWORK_CONFIG, [NETWORK_CONF])
672     file_output(CAP_NETWORK_CONFIG, [NETWORK_DBCACHE])
673     tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, IFCFG_RE)
674     tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, ROUTE_RE)
675     file_output(CAP_NETWORK_CONFIG, [SYSCONFIG_NETWORK, RESOLV_CONF, NSSWITCH_CONF, HOSTS])
676     file_output(CAP_NETWORK_CONFIG, [NTP_CONF, IPTABLES_CONFIG, HOSTS_ALLOW, HOSTS_DENY])
677     file_output(CAP_NETWORK_CONFIG, [OPENVSWITCH_CONF, OPENVSWITCH_CONF_DB])
678
679     cmd_output(CAP_NETWORK_STATUS, [IFCONFIG, '-a'])
680     cmd_output(CAP_NETWORK_STATUS, [ROUTE, '-n'])
681     cmd_output(CAP_NETWORK_STATUS, [ARP, '-n'])
682     cmd_output(CAP_NETWORK_STATUS, [NETSTAT, '-an'])
683     tree_output(CAP_NETWORK_STATUS, DHCP_LEASE_DIR)
684     cmd_output(CAP_NETWORK_STATUS, [IPTABLES, '-nL'])
685     cmd_output(CAP_NETWORK_STATUS, [BRCTL, 'show'])
686     cmd_output(CAP_NETWORK_STATUS, [BIOSDEVNAME, '-d'])
687     for p in os.listdir('/sys/class/net/'):
688         if os.path.isdir('/sys/class/net/%s/bridge' % p):
689             cmd_output(CAP_NETWORK_STATUS, [BRCTL, 'showmacs', p])
690         else:
691             try:
692                 f = open('/sys/class/net/%s/type' % p, 'r')
693                 t = f.readline()
694                 f.close()
695                 if int(t) == 1:
696                     # ARPHRD_ETHER
697                     cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, p])
698                     cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-S', p])
699                     cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-k', p])
700                     cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-i', p])
701                     cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-c', p])
702                     cmd_output(CAP_NETWORK_STATUS,
703                                [TC, '-s', '-d', 'class', 'show', 'dev', p])
704             except:
705                 pass
706     tree_output(CAP_NETWORK_STATUS, PROC_NET_BONDING_DIR)
707     tree_output(CAP_NETWORK_STATUS, PROC_NET_VLAN_DIR)
708     cmd_output(CAP_NETWORK_STATUS, [TC, '-s', 'qdisc'])
709     file_output(CAP_NETWORK_STATUS, [PROC_NET_SOFTNET_STAT])
710     tree_output(CAP_NETWORK_STATUS, OPENVSWITCH_CORE_DIR)
711     if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
712         cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show'])
713         for d in dp_list():
714             cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'show', d])
715             cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'status', d])
716             cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'dump-flows', d])
717             cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
718         try:
719             vspidfile = open(OPENVSWITCH_VSWITCHD_PID)
720             vspid = int(vspidfile.readline().strip())
721             vspidfile.close()
722             for b in bond_list(vspid):
723                 cmd_output(CAP_NETWORK_STATUS,
724                            [OVS_APPCTL, '-t', '/var/run/ovs-vswitchd.%s.ctl' % vspid, '-e' 'bond/show %s' % b],
725                            'ovs-appctl-bond-show-%s.out' % b)
726         except e:
727             pass
728
729     tree_output(CAP_PAM, PAM_DIR)
730     file_output(CAP_PAM, [KRB5_CONF])
731     tree_output(CAP_PAM, LIKEWISE_DIR)
732
733     func_output(CAP_PERSISTENT_STATS, 'xapi_rrd-host', dump_xapi_rrds)
734
735     cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
736     func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
737
738     file_output(CAP_SYSTEM_LOGS,
739          [ VAR_LOG_DIR + x for x in
740            [ 'crit.log', 'kern.log', 'daemon.log', 'user.log', 'syslog', 'messages',
741              'monitor_memory.log', 'secure', 'debug', 'dmesg', 'boot.msg', 'blktap.log' ] +
742            [ f % n for n in range(1, 20) \
743                  for f in ['crit.log.%d', 'crit.log.%d.gz',
744                            'kern.log.%d', 'kern.log.%d.gz',
745                            'daemon.log.%d', 'daemon.log.%d.gz',
746                            'user.log.%d', 'user.log.%d.gz',
747                            'messages.%d', 'messages.%d.gz',
748                            'monitor_memory.log.%d', 'monitor_memory.log.%d.gz',
749                            'secure.%d', 'secure.%d.gz',
750                            'blktap.log.%d']]])
751     if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot.msg'):
752         cmd_output(CAP_SYSTEM_LOGS, [DMESG])
753     file_output(CAP_SYSTEM_LOGS, [LWIDENTITY_JOIN_LOG, HOSTS_LWIDENTITY_ORIG])
754
755     cmd_output(CAP_SYSTEM_SERVICES, [CHKCONFIG, '--list'])
756
757     if CAP_TAPDISK_LOGS in entries:
758         generate_tapdisk_logs()
759
760     tree_output(CAP_VNCTERM, VNCTERM_CORE_DIR)
761
762     cmd_output(CAP_WLB, [XE, 'pool-retrieve-wlb-configuration'])
763     cmd_output(CAP_WLB, [XE, 'pool-retrieve-wlb-diagnostics'])
764
765     tree_output(CAP_X11_LOGS, X11_LOGS_DIR, X11_LOGS_RE)
766     tree_output(CAP_X11_AUTH, X11_AUTH_DIR, X11_AUTH_RE)
767
768     tree_output(CAP_XAPI_DEBUG, XAPI_DEBUG_DIR)
769
770     func_output(CAP_XAPI_SUBPROCESS, 'xapi_subprocesses', dump_xapi_subprocess_info)
771
772     tree_output(CAP_XENRT, '/tmp', FIST_RE)
773     # CA-45540: capture QEMU core files
774     tree_output(CAP_XENRT, '/var/xen/qemu')
775     tree_output(CAP_XENRT, '/tmp', re.compile(r'^.*xen\.qemu-dm\.'))
776
777     file_output(CAP_XENSERVER_CONFIG, [INITIAL_INVENTORY])
778     file_output(CAP_XENSERVER_CONFIG, [POOL_CONF, PTOKEN, XAPI_CONF, XAPI_SSL_CONF, 
779                                        XENSOURCE_INVENTORY, VENDORKERNEL_INVENTORY])
780     cmd_output(CAP_XENSERVER_CONFIG, [LS, '-lR', '/opt/xensource'])
781     cmd_output(CAP_XENSERVER_CONFIG, [BIN_STATIC_VDIS, 'list'])
782     tree_output(CAP_XENSERVER_CONFIG, OEM_CONFIG_DIR, OEM_CONFIG_FILES_RE)
783     tree_output(CAP_XENSERVER_CONFIG, STATIC_VDIS)
784     cmd_output(CAP_XENSERVER_CONFIG, [LS, '-lR', STATIC_VDIS])
785
786     func_output(CAP_XENSERVER_DATABASES, 'xapi-db.xml', dump_filtered_xapi_db)
787     cmd_output(CAP_XENSERVER_DATABASES, [XENSTORE_LS, '-f'])
788     file_output(CAP_XENSERVER_DATABASES, [DB_CONF, DB_CONF_RIO, DB_DEFAULT_FIELDS, DB_SCHEMA_SQL])
789     tree_output(CAP_XENSERVER_DATABASES, OEM_CONFIG_DIR, OEM_DB_FILES_RE)
790     file_output(CAP_XENSERVER_DATABASES, [XENSTORED_DB, XENSTORED_DB + '.bak'])
791     cmd_output(CAP_XENSERVER_DATABASES, [XE, 'pool-dump-database', 'file-name='], 
792                label="xapi-db-dumped.xml", filter=filter_db_pii)
793     cmd_output(CAP_XENSERVER_DATABASES, [XS, 'debug', 'watches'])
794     cmd_output(CAP_XENSERVER_DATABASES, [XS, 'debug', 'quotas'])
795
796     cmd_output(CAP_XENSERVER_DOMAINS, [LIST_DOMAINS])
797
798     tree_output(CAP_XENSERVER_INSTALL, VAR_LOG_DIR + 'installer')
799     file_output(CAP_XENSERVER_INSTALL,
800                 [ VAR_LOG_DIR + x for x in 
801                   [ 'firstboot-SR-commands-log', 
802                     'upgrade-commands-log', 'generate-iscsi-iqn-log']] +
803                 [ '/root/' + x for x in 
804                   [ 'blockdevs-log', 'cmdline-log', 'devcontents-log',
805                     'dmesg-log', 'install-log', 'lspci-log', 'modules-log',
806                     'pci-log', 'processes-log', 'tty-log', 'uname-log',
807                     'vgscan-log']])
808     tree_output(CAP_XENSERVER_INSTALL, INSTALLED_REPOS_DIR)
809     tree_output(CAP_XENSERVER_INSTALL, PATCH_APPLIED_DIR)
810
811     file_output(CAP_XENSERVER_LOGS, [LOG_CONF, XENGUEST_LOG])
812     file_output(CAP_XENSERVER_LOGS, XENSERVER_LOGS)
813     file_output(CAP_XENSERVER_LOGS, LICENSE_LOGS)
814     tree_output(CAP_XENSERVER_LOGS, OEM_CONFIG_DIR, OEM_XENSERVER_LOGS_RE)
815
816     try:
817         def xen_dmesg(xc):
818             data = xc.readconsolering()
819             xc.send_debug_keys('q')
820             time.sleep(1)
821             return data
822
823         xc = xen.lowlevel.xc.xc()
824
825         func_output(CAP_XEN_INFO, 'xen-dmesg', lambda x: xen_dmesg(xc))
826         func_output(CAP_XEN_INFO, 'physinfo', lambda x: prettyDict(xc.physinfo()))
827         func_output(CAP_XEN_INFO, 'xeninfo', lambda x: prettyDict(xc.xeninfo()))
828     except:
829         pass
830     file_output(CAP_XEN_INFO, [PROC_XEN_BALLOON])
831
832     cmd_output(CAP_XHA_LIVESET, [HA_QUERY_LIVESET])
833
834     file_output(CAP_YUM, [YUM_LOG])
835     tree_output(CAP_YUM, YUM_REPOS_DIR)
836     cmd_output(CAP_YUM, [RPM, '-qa'])
837
838     try:
839         load_plugins()
840     except:
841         pass
842     
843     # permit the user to filter out data
844     for k in sorted(data.keys()):
845         if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % k):
846             del data[k]
847
848     # collect selected data now
849     output_ts('Running commands to collect data')
850     collect_data()
851
852     subdir = os.getenv('XENRT_BUGTOOL_BASENAME')
853     if subdir:
854         subdir = os.path.basename(subdir)
855         if subdir == '..' or subdir == '.':
856             subdir = None
857     if not subdir:
858         subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
859
860     # include inventory
861     data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
862
863     # create archive
864     if output_fd == -1 and not os.path.exists(BUG_DIR):
865         try:
866             os.makedirs(BUG_DIR)
867         except:
868             pass
869
870     if output_fd == -1:
871         output_ts('Creating output file')
872
873     if output_type.startswith('tar'):
874         make_tar(subdir, output_type, output_fd)
875     else:
876         make_zip(subdir)
877
878     clean_tapdisk_logs()
879
880     if dbg:
881         print >>sys.stderr, "Category sizes (max, actual):\n"
882         for c in caps.keys():
883             print >>sys.stderr, "    %s (%d, %d)" % (c, caps[c][MAX_SIZE], 
884                                                      cap_sizes[c])
885     return 0
886
887 def find_tapdisk_logs():
888     return glob.glob('/var/log/blktap/*.log*')
889
890 def generate_tapdisk_logs():
891     for pid in pidof('tapdisk'):
892         try:
893             os.kill(pid, SIGUSR1)
894             output_ts("Including logs for tapdisk process %d" % pid)
895         except :
896             pass
897     # give processes a second to write their logs
898     time.sleep(1)
899     file_output(CAP_TAPDISK_LOGS, find_tapdisk_logs())
900
901 def clean_tapdisk_logs():
902     for filename in find_tapdisk_logs():
903         try:
904             os.remove(filename)
905         except :
906             pass
907
908 def dump_xapi_subprocess_info(cap):
909     """Check which fds are open by xapi and its subprocesses to diagnose faults like CA-10543.
910        Returns a string containing a pretty-printed pstree-like structure. """
911     pids = filter(lambda x: x.isdigit(), os.listdir("/proc"))
912     def readlines(filename):
913         lines = ''
914         try:
915             f = open(filename, "r")
916             lines = f.readlines()
917             f.close()
918         except:
919             pass
920         return lines
921     def cmdline(pid):
922         all = readlines("/proc/" + pid + "/cmdline")
923         if all == []:
924            return ""
925         else:
926            return all[0].replace('\x00', ' ')
927     def parent(pid):
928         for i in readlines("/proc/" + pid + "/status"):
929             if i.startswith("PPid:"):
930                return i.split()[-1]
931         return None
932     def pstree(pid):
933         result = { "cmdline": cmdline(pid) }
934         child_pids = filter(lambda x:parent(x) == pid, pids)
935         children = { }
936         for child in child_pids:
937             children[child] = pstree(child)
938         result['children'] = children
939         fds = { }
940         for fd in os.listdir("/proc/" + pid + "/fd"):
941             try:
942                 fds[fd] = os.readlink("/proc/" + pid + "/fd/" + fd)
943             except:
944                 pass
945         result['fds'] = fds
946         return result   
947     xapis = filter(lambda x: cmdline(x).startswith("/opt/xensource/bin/xapi"), pids)
948     xapis = filter(lambda x: parent(x) == "1", xapis)
949     result = {}
950     for xapi in xapis:
951         result[xapi] = pstree(xapi)
952     pp = pprint.PrettyPrinter(indent=4)
953     return pp.pformat(result)
954
955 def dump_xapi_rrds(cap):
956     socket.setdefaulttimeout(5)
957     session = XenAPI.xapi_local()
958     session.xenapi.login_with_password('', '')
959     this_host = session.xenapi.session.get_this_host(session._session)
960     # better way to find pool master?
961     pool = session.xenapi.pool.get_all_records().values()[0]
962     i_am_master = (this_host == pool['master'])
963
964     for vm in session.xenapi.VM.get_all_records().values():
965         if vm['is_a_template']:
966             continue
967         if vm['resident_on'] == this_host or (i_am_master and vm['power_state'] in ['Suspended', 'Halted']):
968             rrd = urllib.urlopen('http://localhost/vm_rrd?session_id=%s&uuid=%s' % (session._session, vm['uuid']))
969             try:
970                 (i, o, x) = select([rrd], [], [], 5.0)
971                 if len(i) == 1:
972                     data['xapi_rrd-%s' % vm['uuid']] = {'cap': cap, 
973                                                         'output': StringIOmtime(rrd.read())}
974             finally:
975                 rrd.close()
976
977     output = ''
978     rrd = urllib.urlopen('http://localhost/host_rrd?session_id=%s' % session._session)
979     try:
980         for line in rrd:
981             output += line
982     finally:
983         rrd.close()
984         
985     session.xenapi.session.logout()
986     return output
987
988 '''Filter a Xapi XML database.
989
990     There is one important assumption made in this class:
991     - the XML document does not contain any characters between the end of one
992       tag and the beginning of the next, ie every > is immediately followed by
993       a <
994 '''
995 class DBFilter:
996     def __init__(self):
997         self.result = ''
998         self.rest = ''
999         self.state = {}
1000
1001     def filter_secrets(self, s):
1002         if 'in_secret_table' not in self.state:
1003             self.state['in_secret_table'] = False
1004
1005         # this logic doesn't deal with <table name="secret" /> properly!!!
1006         if s.startswith('<table ') and 'name="secret"' in s:
1007             self.state['in_secret_table'] = True
1008         elif s.startswith('</table>'):
1009             self.state['in_secret_table'] = False
1010         
1011         if self.state['in_secret_table'] and s.startswith("<row"): # match only on DB rows
1012             s = re.sub(r'(value=")[^"]+(")', r'\1REMOVED\2', s)
1013         return s
1014
1015     def feed(self, s):
1016         rem = self.rest + s
1017         p = rem.find('>')
1018         while p != -1:
1019             s = rem[:p+1]
1020             rem = rem[p+1:]
1021             self.result  += self.filter_secrets(s)
1022             p = rem.find('>')
1023         self.rest = rem
1024
1025     def output(self):
1026         r = self.result + self.filter_secrets(self.rest)
1027         self.result, self.rest = '', ''
1028         self.state = {}
1029         return r
1030
1031 def filter_db_pii(s, state):
1032     dbfilter = DBFilter()
1033     dbfilter.feed(s)
1034     return dbfilter.output()
1035
1036 def dump_filtered_xapi_db(cap):
1037     db_file = None
1038     format = None
1039     state = {}
1040
1041     # determine db format
1042     c = open(DB_CONF, 'r')
1043     try:
1044         for line in c:
1045             l = line.rstrip('\n')
1046             if l.startswith('['):
1047                 db_file = l[1:-1]
1048             if l.startswith('format:'):
1049                 format = l[7:]
1050                 break
1051     finally:
1052         c.close()
1053
1054     pipe = None
1055     ih = None
1056     output = ''
1057
1058     if format == 'sqlite':
1059         pipe = Popen([XAPI_DB_PROCESS, '-xmltostdout'], bufsize=1, stdin=dev_null, 
1060                      stdout=PIPE, stderr=dev_null)
1061         ih = pipe.stdout
1062     elif db_file:
1063         ih = open(db_file, 'r')
1064
1065     if not ih:
1066         return ''
1067
1068     dbfilter = DBFilter()
1069     rec = ih.read(2048)
1070     while rec != '':
1071         dbfilter.feed(rec)
1072         rec = ih.read(2048)
1073     output = dbfilter.output()
1074
1075     if pipe:
1076         pipe.wait()
1077     else:
1078         ih.close()
1079     return output
1080
1081 def dump_scsi_hosts(cap):
1082     output = ''
1083     l = os.listdir('/sys/class/scsi_host')
1084     l.sort()
1085
1086     for h in l:
1087         procname = ''
1088         try:
1089                 f = open('/sys/class/scsi_host/%s/proc_name' % h)
1090                 procname = f.readline().strip("\n")
1091                 f.close()
1092         except:
1093                 pass
1094         modelname = None
1095         try:
1096                 f = open('/sys/class/scsi_host/%s/model_name' % h)
1097                 modelname = f.readline().strip("\n")
1098                 f.close()
1099         except:
1100                 pass
1101
1102         output += "%s:\n" %h
1103         output += "    %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
1104
1105     return output
1106
1107 def module_info(cap):
1108     output = StringIO.StringIO()
1109     modules = open(PROC_MODULES, 'r')
1110     procs = []
1111
1112     for line in modules:
1113         module = line.split()[0]
1114         procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
1115     modules.close()
1116
1117     run_procs([procs])
1118
1119     return output.getvalue()
1120
1121 def csl_logs(cap):
1122     socket.setdefaulttimeout(5)
1123     session = XenAPI.xapi_local()
1124     session.xenapi.login_with_password('', '')
1125     this_host = session.xenapi.session.get_this_host(session._session)
1126     # better way to find pool master?
1127     pool = session.xenapi.pool.get_all_records().values()[0]
1128     i_am_master = (this_host == pool['master'])
1129
1130     output = StringIO.StringIO()
1131     procs = []
1132     csl_targets_fetched = []
1133
1134     for pbd in session.xenapi.PBD.get_all_records().values():
1135         if pbd.has_key('device_config') and pbd['device_config'].has_key('target'):
1136             if pbd['device_config']['target'] in csl_targets_fetched:
1137                 continue
1138             sr = session.xenapi.SR.get_record(pbd['SR'])
1139             if sr.has_key('type') and sr['type'] == 'cslg':
1140                 if sr['shared'] and pbd['host'] != this_host and not i_am_master:
1141                     continue
1142                 
1143                 dev_cfg = pbd['device_config']
1144                 server = "server=%s" % socket.gethostbyname(dev_cfg['target'])
1145                 if dev_cfg.has_key('port'):
1146                     server += ':' + dev_cfg['port']
1147                 if dev_cfg.has_key('username'):
1148                     server += ',' + dev_cfg['username']
1149                 if dev_cfg.has_key('password_secret'):
1150                     sec_ref = session.xenapi.secret.get_by_uuid(dev_cfg['password_secret'])
1151                     server += ',' + session.xenapi.secret.get_value(sec_ref)
1152                 procs.append(ProcOutput([CSL, server, 'srv-log-get'], caps[cap][MAX_TIME], output))
1153                 csl_targets_fetched.append(dev_cfg['target'])
1154
1155     session.xenapi.session.logout()
1156
1157     run_procs([procs])
1158
1159     return output.getvalue()
1160
1161 def multipathd_topology(cap):
1162     pipe = Popen([MULTIPATHD, '-k'], bufsize=1, stdin=PIPE, 
1163                      stdout=PIPE, stderr=dev_null)
1164     stdout, stderr = pipe.communicate('show topology')
1165
1166     return stdout
1167
1168 def dp_list():
1169     output = StringIO.StringIO()
1170     procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
1171
1172     run_procs([procs])
1173
1174     if not procs[0].timed_out:
1175         return output.getvalue().splitlines()
1176     return []
1177
1178 def bond_list(pid):
1179     output = StringIO.StringIO()
1180     procs = [ProcOutput([OVS_APPCTL, '-t', '/var/run/ovs-vswitchd.%s.ctl' % pid, '-e' 'bond/list'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
1181
1182     run_procs([procs])
1183
1184     if not procs[0].timed_out:
1185         bonds = output.getvalue().splitlines()[1:]
1186         return [x.split('\t')[1] for x in bonds]
1187     return []
1188
1189 def fd_usage(cap):
1190     output = ''
1191     fd_dict = {}
1192     for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1193         try:
1194             fh = open('/proc/'+d+'/cmdline')
1195             name = fh.readline()
1196             num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
1197             if num_fds > 0:
1198                 if not num_fds in fd_dict:
1199                     fd_dict[num_fds] = []
1200                 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
1201         finally:
1202             fh.close()
1203     keys = fd_dict.keys()
1204     keys.sort(lambda a, b: int(b) - int(a))
1205     for k in keys:
1206         output += "%s: %s\n" % (k, str(fd_dict[k]))
1207     return output
1208
1209 def dump_rdac_groups(cap):
1210     output = StringIO.StringIO()
1211     procs = [ProcOutput([MPPUTIL, '-a'], caps[cap][MAX_TIME], output)]
1212
1213     run_procs([procs])
1214
1215     if not procs[0].timed_out:
1216         proc_line = 0
1217         for line in output.getvalue().splitlines():
1218             if line.startswith('ID'):
1219                 proc_line = 2
1220             elif line.startswith('----'):
1221                 proc_line -= 1
1222             elif proc_line > 0:
1223                 group, _ = line.split(None, 1)
1224                 cmd_output(cap, [MPPUTIL, '-g', group])
1225
1226 def load_plugins(just_capabilities = False):
1227     def getText(nodelist):
1228         rc = ""
1229         for node in nodelist:
1230             if node.nodeType == node.TEXT_NODE:
1231                 rc += node.data
1232         return rc.encode()
1233
1234     def getBoolAttr(el, attr, default = False):
1235         ret = default
1236         val = el.getAttribute(attr).lower()
1237         if val in ['true', 'false', 'yes', 'no']:
1238             ret = val in ['true', 'yes']
1239         return ret
1240         
1241     for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
1242         if not caps.has_key(dir):
1243             if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
1244                 continue
1245             xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
1246             assert xmldoc.documentElement.tagName == "capability"
1247
1248             pii, min_size, max_size, min_time, max_time, mime = \
1249                  PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
1250
1251             if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
1252                 pii = xmldoc.documentElement.getAttribute("pii")
1253             if xmldoc.documentElement.getAttribute("min_size") != '':
1254                 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
1255             if xmldoc.documentElement.getAttribute("max_size") != '':
1256                 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
1257             if xmldoc.documentElement.getAttribute("min_time") != '':
1258                 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
1259             if xmldoc.documentElement.getAttribute("max_time") != '':
1260                 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
1261             if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
1262                 mime = xmldoc.documentElement.getAttribute("mime")
1263             checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
1264             hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
1265
1266             cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
1267
1268         if just_capabilities:
1269             continue
1270                     
1271         plugdir = os.path.join(PLUGIN_DIR, dir)
1272         for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
1273             xmldoc = parse(os.path.join(plugdir, file))
1274             assert xmldoc.documentElement.tagName == "collect"
1275
1276             for el in xmldoc.documentElement.getElementsByTagName("*"):
1277                 if el.tagName == "files":
1278                     file_output(dir, getText(el.childNodes).split())
1279                 elif el.tagName == "directory":
1280                     pattern = el.getAttribute("pattern")
1281                     if pattern == '': pattern = None
1282                     negate = getBoolAttr(el, 'negate')
1283                     tree_output(dir, getText(el.childNodes), pattern and re.compile(pattern) or None, negate)
1284                 elif el.tagName == "command":
1285                     label = el.getAttribute("label")
1286                     if label == '': label = None
1287                     cmd_output(dir, getText(el.childNodes), label)
1288
1289 def make_tar(subdir, suffix, output_fd):
1290     global SILENT_MODE, data
1291
1292     mode = 'w'
1293     if suffix == 'tar.bz2':
1294         mode = 'w:bz2'
1295     filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
1296
1297     if output_fd == -1:
1298         tf = tarfile.open(filename, mode)
1299     else:
1300         tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
1301
1302     try:
1303         for (k, v) in data.items():
1304             try:
1305                 tar_filename = os.path.join(subdir, construct_filename(k, v))
1306                 ti = tarfile.TarInfo(tar_filename)
1307
1308                 ti.uname = 'root'
1309                 ti.gname = 'root'
1310
1311                 if v.has_key('output'):
1312                     ti.mtime = v['output'].mtime
1313                     ti.size = len(v['output'].getvalue())
1314                     v['output'].seek(0)
1315                     tf.addfile(ti, v['output'])
1316                 elif v.has_key('filename'):
1317                     s = os.stat(v['filename'])
1318                     ti.mtime = s.st_mtime
1319                     ti.size = s.st_size
1320                     tf.addfile(ti, file(v['filename']))
1321             except:
1322                 pass
1323     finally:
1324         tf.close()
1325
1326     if output_fd == -1:
1327         output ('Writing tarball %s successful.' % filename)
1328         if SILENT_MODE:
1329             print filename
1330
1331
1332 def make_zip(subdir):
1333     global SILENT_MODE, data
1334
1335     filename = "%s/%s.zip" % (BUG_DIR, subdir)
1336     zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
1337
1338     try:
1339         for (k, v) in data.items():
1340             try:
1341                 dest = os.path.join(subdir, construct_filename(k, v))
1342             
1343                 if v.has_key('output'):
1344                     zf.writestr(dest, v['output'].getvalue())
1345                 elif v.has_key('filename'):
1346                     if os.stat(v['filename']).st_size < 50:
1347                         compress_type = zipfile.ZIP_STORED
1348                     else:
1349                         compress_type = zipfile.ZIP_DEFLATED
1350                     zf.write(v['filename'], dest, compress_type)
1351             except:
1352                 pass
1353     finally:
1354         zf.close()
1355     
1356     output ('Writing archive %s successful.' % filename)
1357     if SILENT_MODE:
1358         print filename
1359
1360
1361 def make_inventory(inventory, subdir):
1362     document = getDOMImplementation().createDocument(
1363         None, INVENTORY_XML_ROOT, None)
1364
1365     # create summary entry
1366     s = document.createElement(INVENTORY_XML_SUMMARY)
1367     user = os.getenv('SUDO_USER', os.getenv('USER'))
1368     if user:
1369         s.setAttribute('user', user)
1370     s.setAttribute('date', time.strftime('%c'))
1371     s.setAttribute('hostname', platform.node())
1372     s.setAttribute('uname', ' '.join(platform.uname()))
1373     s.setAttribute('uptime', commands.getoutput(UPTIME))
1374     document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
1375
1376     map(lambda (k, v): inventory_entry(document, subdir, k, v),
1377         inventory.items())
1378     return document.toprettyxml()
1379
1380 def inventory_entry(document, subdir, k, v):
1381     try:
1382         el = document.createElement(INVENTORY_XML_ELEMENT)
1383         el.setAttribute('capability', v['cap'])
1384         el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
1385         el.setAttribute('md5sum', md5sum(v))
1386         document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
1387     except:
1388         pass
1389
1390
1391 def md5sum(d):
1392     m = md5.new()
1393     if d.has_key('filename'):
1394         f = open(d['filename'])
1395         data = f.read(1024)
1396         while len(data) > 0:
1397             m.update(data)
1398             data = f.read(1024)
1399         f.close()
1400     elif d.has_key('output'):
1401         m.update(d['output'].getvalue())
1402     return m.hexdigest()
1403
1404
1405 def construct_filename(k, v):
1406     if v.has_key('filename'):
1407         if v['filename'][0] == '/':
1408             return v['filename'][1:]
1409         else:
1410             return v['filename']
1411     s = k.replace(' ', '-')
1412     s = s.replace('--', '-')
1413     s = s.replace('/', '%')
1414     if s.find('.') == -1:
1415         s += '.out'
1416
1417     return s
1418
1419
1420 def update_capabilities():
1421     update_cap_size(CAP_HOST_CRASHDUMP_LOGS,
1422                     size_of_dir(HOST_CRASHDUMPS_DIR, HOST_CRASHDUMP_LOGS_RE))
1423     update_cap_size(CAP_HOST_CRASHDUMP_DUMPS,
1424                     size_of_dir(HOST_CRASHDUMPS_DIR, HOST_CRASHDUMP_LOGS_RE,
1425                                 True))
1426     update_cap_size(CAP_XAPI_DEBUG, size_of_dir(XAPI_DEBUG_DIR))
1427     update_cap_size(CAP_XENSERVER_LOGS, size_of_all(XENSERVER_LOGS))
1428
1429
1430 def update_cap_size(cap, size):
1431     update_cap(cap, MIN_SIZE, size)
1432     update_cap(cap, MAX_SIZE, size)
1433     update_cap(cap, CHECKED, size > 0)
1434
1435
1436 def update_cap(cap, k, v):
1437     global caps
1438     l = list(caps[cap])
1439     l[k] = v
1440     caps[cap] = tuple(l)
1441
1442
1443 def size_of_dir(d, pattern = None, negate = False):
1444     if os.path.isdir(d):
1445         return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
1446                            pattern, negate)
1447     else:
1448         return 0
1449
1450
1451 def size_of_all(files, pattern = None, negate = False):
1452     return sum([size_of(f, pattern, negate) for f in files])
1453
1454
1455 def matches(f, pattern, negate):
1456     if negate:
1457         return not matches(f, pattern, False)
1458     else:
1459         return pattern is None or pattern.match(f)
1460
1461
1462 def size_of(f, pattern, negate):
1463     if os.path.isfile(f) and matches(f, pattern, negate):
1464         return os.stat(f)[6]
1465     else:
1466         return size_of_dir(f, pattern, negate)
1467
1468
1469 def print_capabilities():
1470     document = getDOMImplementation().createDocument(
1471         "ns", CAP_XML_ROOT, None)
1472     map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
1473     print document.toprettyxml()
1474
1475 def capability(document, key):
1476     c = caps[key]
1477     el = document.createElement(CAP_XML_ELEMENT)
1478     el.setAttribute('key', c[KEY])
1479     el.setAttribute('pii', c[PII])
1480     el.setAttribute('min-size', str(c[MIN_SIZE]))
1481     el.setAttribute('max-size', str(c[MAX_SIZE]))
1482     el.setAttribute('min-time', str(c[MIN_TIME]))
1483     el.setAttribute('max-time', str(c[MAX_TIME]))
1484     el.setAttribute('content-type', c[MIME])
1485     el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
1486     document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
1487
1488
1489 def prettyDict(d):
1490     format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
1491     return '\n'.join([format % i for i in d.items()]) + '\n'
1492
1493
1494 def yes(prompt):
1495     yn = raw_input(prompt)
1496
1497     return len(yn) == 0 or yn.lower()[0] == 'y'
1498
1499
1500 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
1501
1502 def disk_list():
1503     disks = []
1504     try:
1505         f = open('/proc/partitions')
1506         f.readline()
1507         f.readline()
1508         for line in f.readlines():
1509             (major, minor, blocks, name) = line.split()
1510             if int(major) < 254 and not partition_re.match(name):
1511                 disks.append(name)
1512         f.close()
1513     except:
1514         pass
1515     return disks
1516
1517
1518 class ProcOutput:
1519     debug = False
1520
1521     def __init__(self, command, max_time, inst=None, filter=None):
1522         self.command = command
1523         self.max_time = max_time
1524         self.inst = inst
1525         self.running = False
1526         self.status = None
1527         self.timed_out = False
1528         self.failed = False
1529         self.timeout = int(time.time()) + self.max_time
1530         self.filter = filter
1531         self.filter_state = {}
1532
1533     def __del__(self):
1534         self.terminate()
1535
1536     def cmdAsStr(self):
1537         return isinstance(self.command, list) and ' '.join(self.command) or self.command
1538
1539     def run(self):
1540         self.timed_out = False
1541         try:
1542             if ProcOutput.debug:
1543                 output_ts("Starting '%s'" % self.cmdAsStr())
1544             self.proc = Popen(self.command, bufsize=1, stdin=dev_null, stdout=PIPE, stderr=dev_null, shell=isinstance(self.command, str))
1545             old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1546             fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1547             self.running = True
1548             self.failed = False
1549         except:
1550             output_ts("'%s' failed" % self.cmdAsStr())
1551             self.running = False
1552             self.failed = True
1553
1554     def terminate(self):
1555         if self.running:
1556             try:
1557                 os.kill(self.proc.pid, SIGTERM)
1558             except:
1559                 pass
1560             self.proc = None
1561             self.running = False
1562             self.status = SIGTERM
1563
1564     def read_line(self):
1565         assert self.running
1566         line = self.proc.stdout.readline()
1567         if line == '':
1568             # process exited
1569             self.status = self.proc.wait()
1570             self.proc = None
1571             self.running = False
1572         else:
1573             if self.filter:
1574                 line = self.filter(line, self.filter_state)
1575             if self.inst:
1576                 self.inst.write(line)
1577
1578 def run_procs(procs):
1579     while True:
1580         pipes = []
1581         active_procs = []
1582
1583         for pp in procs:
1584             for p in pp:
1585                 if p.running:
1586                     active_procs.append(p)
1587                     pipes.append(p.proc.stdout)
1588                     break
1589                 elif p.status == None and not p.failed and not p.timed_out:
1590                     p.run()
1591                     if p.running:
1592                         active_procs.append(p)
1593                         pipes.append(p.proc.stdout)
1594                         break
1595
1596         if len(pipes) == 0:
1597             # all finished
1598             break
1599
1600         (i, o, x) = select(pipes, [], [], 1.0)
1601         now = int(time.time())
1602
1603         # handle process output
1604         for p in active_procs:
1605             if p.proc.stdout in i:
1606                 p.read_line()
1607
1608             # handle timeout
1609             if p.running and now > p.timeout:
1610                 output_ts("'%s' timed out" % p.cmdAsStr())
1611                 if p.inst:
1612                     p.inst.write("\n** timeout **\n")
1613                 p.timed_out = True
1614                 p.terminate()
1615
1616
1617 def pidof(name):
1618     pids = []
1619
1620     for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1621         try:
1622             if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1623                 pids.append(int(d))
1624         except:
1625             pass
1626         
1627     return pids
1628
1629
1630 def readKeyValueFile(filename, allowed_keys = None, strip_quotes = True, assert_quotes = True):
1631     """ Reads a KEY=Value style file (e.g. xensource-inventory). Returns a 
1632     dictionary of key/values in the file.  Not designed for use with large files
1633     as the file is read entirely into memory."""
1634
1635     f = open(filename, "r")
1636     lines = [x.strip("\n") for x in f.readlines()]
1637     f.close()
1638
1639     # remove lines contain
1640     if allowed_keys:
1641         lines = filter(lambda x: True in [x.startswith(y) for y in allowed_keys],
1642                        lines)
1643     
1644     defs = [ (l[:l.find("=")], l[(l.find("=") + 1):]) for l in lines ]
1645
1646     if strip_quotes:
1647         def quotestrip(x):
1648             if assert_quotes:
1649                 assert x.startswith("'") and x.endswith("'")
1650             return x.strip("'")
1651         defs = [ (a, quotestrip(b)) for (a,b) in defs ]
1652
1653     return dict(defs)
1654
1655
1656 class StringIOmtime(StringIO.StringIO):
1657     def __init__(self, buf = ''):
1658         StringIO.StringIO.__init__(self, buf)
1659         self.mtime = time.time()
1660
1661     def write(self, s):
1662         StringIO.StringIO.write(self, s)
1663         self.mtime = time.time()
1664
1665
1666 if __name__ == "__main__":
1667     try:
1668         sys.exit(main())
1669     except KeyboardInterrupt:
1670         print "\nInterrupted."
1671         sys.exit(3)