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