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