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