Merge commit 'origin/citrix'
[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 OVS_VSWITCH_DBCACHE = '/etc/ovs-vswitch.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', '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
723     cmd_output(CAP_VSWITCH_STATUS, [OVS_DPCTL, 'show'])
724     tree_output(CAP_VSWITCH_STATUS, VSWITCH_CORE_DIR)
725     for d in dp_list():
726         cmd_output(CAP_VSWITCH_STATUS, [OVS_OFCTL, 'show', d])
727         cmd_output(CAP_VSWITCH_STATUS, [OVS_OFCTL, 'status', d])
728         cmd_output(CAP_VSWITCH_STATUS, [OVS_OFCTL, 'dump-flows', d])
729         cmd_output(CAP_VSWITCH_STATUS, [OVS_DPCTL, 'dump-flows', d])
730
731     cmd_output(CAP_WLB, [XE, 'pool-retrieve-wlb-diagnostics'])
732
733     tree_output(CAP_X11_LOGS, X11_LOGS_DIR, X11_LOGS_RE)
734     tree_output(CAP_X11_AUTH, X11_AUTH_DIR, X11_AUTH_RE)
735
736     tree_output(CAP_XAPI_DEBUG, XAPI_DEBUG_DIR)
737
738     func_output(CAP_XAPI_SUBPROCESS, 'xapi_subprocesses', dump_xapi_subprocess_info)
739
740     file_output(CAP_XENSERVER_CONFIG, [INITIAL_INVENTORY])
741     file_output(CAP_XENSERVER_CONFIG, [POOL_CONF, PTOKEN, XAPI_CONF, XAPI_SSL_CONF, STATIC_VDIS, 
742                                        XENSOURCE_INVENTORY, VENDORKERNEL_INVENTORY])
743     cmd_output(CAP_XENSERVER_CONFIG, [LS, '-lR', '/opt/xensource'])
744     cmd_output(CAP_XENSERVER_CONFIG, [BIN_STATIC_VDIS, 'list'])
745     tree_output(CAP_XENSERVER_CONFIG, OEM_CONFIG_DIR, OEM_CONFIG_FILES_RE)
746
747     func_output(CAP_XENSERVER_DATABASES, 'xapi-db.xml', dump_filtered_xapi_db)
748     cmd_output(CAP_XENSERVER_DATABASES, [XENSTORE_LS])
749     file_output(CAP_XENSERVER_DATABASES, [DB_CONF, DB_CONF_RIO, DB_DEFAULT_FIELDS, DB_SCHEMA_SQL])
750     tree_output(CAP_XENSERVER_DATABASES, OEM_CONFIG_DIR, OEM_DB_FILES_RE)
751     file_output(CAP_XENSERVER_DATABASES, [XENSTORED_DB, XENSTORED_DB + '.bak'])
752     cmd_output(CAP_XENSERVER_DATABASES, [XE, 'pool-dump-database', 'file-name='], 
753                label="xapi-db-dumped.xml", filter=filter_db_pii)
754     cmd_output(CAP_XENSERVER_DATABASES, [XS, 'debug', 'watches'])
755     cmd_output(CAP_XENSERVER_DATABASES, [XS, 'debug', 'quotas'])
756
757     cmd_output(CAP_XENSERVER_DOMAINS, [LIST_DOMAINS])
758
759     tree_output(CAP_XENSERVER_INSTALL, VAR_LOG_DIR + 'installer')
760     file_output(CAP_XENSERVER_INSTALL,
761                 [ VAR_LOG_DIR + x for x in 
762                   [ 'firstboot-SR-commands-log', 
763                     'upgrade-commands-log', 'generate-iscsi-iqn-log']] +
764                 [ '/root/' + x for x in 
765                   [ 'blockdevs-log', 'cmdline-log', 'devcontents-log',
766                     'dmesg-log', 'install-log', 'lspci-log', 'modules-log',
767                     'pci-log', 'processes-log', 'tty-log', 'uname-log',
768                     'vgscan-log']])
769     tree_output(CAP_XENSERVER_INSTALL, INSTALLED_REPOS_DIR)
770     tree_output(CAP_XENSERVER_INSTALL, PATCH_APPLIED_DIR)
771
772     file_output(CAP_XENSERVER_LOGS, [LOG_CONF])
773     file_output(CAP_XENSERVER_LOGS, XENSERVER_LOGS)
774     tree_output(CAP_XENSERVER_LOGS, OEM_CONFIG_DIR, OEM_XENSERVER_LOGS_RE)
775
776     try:
777         def xen_dmesg(xc):
778             data = xc.readconsolering()
779             xc.send_debug_keys('q')
780             time.sleep(1)
781             return data
782
783         xc = xen.lowlevel.xc.xc()
784
785         func_output(CAP_XEN_INFO, 'xen-dmesg', lambda x: xen_dmesg(xc))
786         func_output(CAP_XEN_INFO, 'physinfo', lambda x: prettyDict(xc.physinfo()))
787         func_output(CAP_XEN_INFO, 'xeninfo', lambda x: prettyDict(xc.xeninfo()))
788     except:
789         pass
790     file_output(CAP_XEN_INFO, [PROC_XEN_BALLOON])
791
792     cmd_output(CAP_XHA_LIVESET, [HA_QUERY_LIVESET])
793
794     file_output(CAP_YUM, [YUM_LOG])
795     tree_output(CAP_YUM, YUM_REPOS_DIR)
796     cmd_output(CAP_YUM, [RPM, '-qa'])
797     
798     # permit the user to filter out data
799     for k in sorted(data.keys()):
800         if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % k):
801             del data[k]
802
803     # collect selected data now
804     output_ts('Running commands to collect data')
805     collect_data()
806
807     subdir = os.getenv('XENRT_BUGTOOL_BASENAME')
808     if subdir:
809         subdir = os.path.basename(subdir)
810         if subdir == '..' or subdir == '.':
811             subdir = None
812     if not subdir:
813         subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
814
815     # include inventory
816     data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
817
818     # create archive
819     if output_fd == -1 and not os.path.exists(BUG_DIR):
820         try:
821             os.makedirs(BUG_DIR)
822         except:
823             pass
824
825     if output_fd == -1:
826         output_ts('Creating output file')
827
828     if output_type.startswith('tar'):
829         make_tar(subdir, output_type, output_fd)
830     else:
831         make_zip(subdir)
832
833     clean_tapdisk_logs()
834
835     if dbg:
836         print >>sys.stderr, "Category sizes (max, actual):\n"
837         for c in caps.keys():
838             print >>sys.stderr, "    %s (%d, %d)" % (c, caps[c][MAX_SIZE], 
839                                                      cap_sizes[c])
840     return 0
841     
842 def generate_tapdisk_logs():
843     for pid in pidof('tapdisk'):
844         try:
845             os.kill(pid, SIGUSR1)
846             output_ts("Including logs for tapdisk process %d" % pid)
847         except :
848             pass
849     # give processes a second to write their logs
850     time.sleep(1)
851     file_output(CAP_TAPDISK_LOGS, ['/tmp/tapdisk.log.%d' % pid for pid in pidof('tapdisk')])
852
853 def clean_tapdisk_logs():
854     for filename in [f for f in os.listdir('/tmp') if f.startswith('tapdisk.log.')]:
855         try:
856             os.remove(os.path.join('tmp', filename))
857         except :
858             pass
859
860 def dump_xapi_subprocess_info(cap):
861     """Check which fds are open by xapi and its subprocesses to diagnose faults like CA-10543.
862        Returns a string containing a pretty-printed pstree-like structure. """
863     pids = filter(lambda x: x.isdigit(), os.listdir("/proc"))
864     def readlines(filename):
865         lines = ''
866         try:
867             f = open(filename, "r")
868             lines = f.readlines()
869             f.close()
870         except:
871             pass
872         return lines
873     def cmdline(pid):
874         all = readlines("/proc/" + pid + "/cmdline")
875         if all == []:
876            return ""
877         else:
878            return all[0].replace('\x00', ' ')
879     def parent(pid):
880         for i in readlines("/proc/" + pid + "/status"):
881             if i.startswith("PPid:"):
882                return i.split()[-1]
883         return None
884     def pstree(pid):
885         result = { "cmdline": cmdline(pid) }
886         child_pids = filter(lambda x:parent(x) == pid, pids)
887         children = { }
888         for child in child_pids:
889             children[child] = pstree(child)
890         result['children'] = children
891         fds = { }
892         for fd in os.listdir("/proc/" + pid + "/fd"):
893             try:
894                 fds[fd] = os.readlink("/proc/" + pid + "/fd/" + fd)
895             except:
896                 pass
897         result['fds'] = fds
898         return result   
899     xapis = filter(lambda x: cmdline(x).startswith("/opt/xensource/bin/xapi"), pids)
900     xapis = filter(lambda x: parent(x) == "1", xapis)
901     result = {}
902     for xapi in xapis:
903         result[xapi] = pstree(xapi)
904     pp = pprint.PrettyPrinter(indent=4)
905     return pp.pformat(result)
906
907 def dump_xapi_rrds(cap):
908     socket.setdefaulttimeout(5)
909     session = XenAPI.xapi_local()
910     session.xenapi.login_with_password('', '')
911     this_host = session.xenapi.session.get_this_host(session._session)
912     # better way to find pool master?
913     pool = session.xenapi.pool.get_all_records().values()[0]
914     i_am_master = (this_host == pool['master'])
915
916     for vm in session.xenapi.VM.get_all_records().values():
917         if vm['is_a_template']:
918             continue
919         if vm['resident_on'] == this_host or (i_am_master and vm['power_state'] in ['Suspended', 'Halted']):
920             rrd = urllib.urlopen('http://localhost/vm_rrd?session_id=%s&uuid=%s' % (session._session, vm['uuid']))
921             try:
922                 (i, o, x) = select([rrd], [], [], 5.0)
923                 if len(i) == 1:
924                     data['xapi_rrd-%s' % vm['uuid']] = {'cap': cap, 
925                                                         'output': StringIOmtime(rrd.read())}
926             finally:
927                 rrd.close()
928
929     output = ''
930     rrd = urllib.urlopen('http://localhost/host_rrd?session_id=%s' % session._session)
931     try:
932         for line in rrd:
933             output += line
934     finally:
935         rrd.close()
936         
937     session.xenapi.session.logout()
938     return output
939
940 def filter_db_pii(str):
941     str = re.sub(r'(password_transformed&quot; &quot;)[^ ]+(&quot;)', r'\1REMOVED\2', str)
942     str = re.sub(r'(wlb_password=")[^"]+(")', r'\1REMOVED\2', str)
943     return str
944
945 def dump_filtered_xapi_db(cap):
946     db_file = None
947     format = None
948
949     # determine db format
950     c = open(DB_CONF, 'r')
951     try:
952         for line in c:
953             l = line.rstrip('\n')
954             if l.startswith('['):
955                 db_file = l[1:-1]
956             if l.startswith('format:'):
957                 format = l[7:]
958                 break
959     finally:
960         c.close()
961
962     pipe = None
963     ih = None
964     output = ''
965
966     if format == 'sqlite':
967         pipe = Popen([XAPI_DB_PROCESS, '-xmltostdout'], bufsize=1, stdin=dev_null, 
968                      stdout=PIPE, stderr=dev_null)
969         ih = pipe.stdout
970     elif db_file:
971         ih = open(db_file, 'r')
972
973     if not ih:
974         return ''
975
976     remain = ''
977     rec = ih.read(2048)
978     while rec != '':
979         remain += rec
980         p = remain.find('>')
981         while p != -1:
982             str = remain[:p+1]
983             remain = remain[p+1:]
984             output += filter_db_pii(str)
985             p = remain.find('>')
986         rec = ih.read(2048)
987     output += remain
988
989     if pipe:
990         pipe.wait()
991     else:
992         ih.close()
993     return output
994
995 def dump_scsi_hosts(cap):
996     output = ''
997     l = os.listdir('/sys/class/scsi_host')
998     l.sort()
999
1000     for h in l:
1001         procname = ''
1002         try:
1003                 f = open('/sys/class/scsi_host/%s/proc_name' % h)
1004                 procname = f.readline().strip("\n")
1005                 f.close()
1006         except:
1007                 pass
1008         modelname = None
1009         try:
1010                 f = open('/sys/class/scsi_host/%s/model_name' % h)
1011                 modelname = f.readline().strip("\n")
1012                 f.close()
1013         except:
1014                 pass
1015
1016         output += "%s:\n" %h
1017         output += "    %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
1018
1019     return output
1020
1021 def csl_logs(cap):
1022     socket.setdefaulttimeout(5)
1023     session = XenAPI.xapi_local()
1024     session.xenapi.login_with_password('', '')
1025     this_host = session.xenapi.session.get_this_host(session._session)
1026     # better way to find pool master?
1027     pool = session.xenapi.pool.get_all_records().values()[0]
1028     i_am_master = (this_host == pool['master'])
1029
1030     output = StringIO.StringIO()
1031     procs = []
1032
1033     def rotate_string(x, n):
1034         transtbl = ""
1035         for a in range(0, 256):
1036             transtbl = transtbl + chr(a)
1037         transtbl = transtbl[n:] + transtbl[0:n]
1038         return x.translate(transtbl)
1039
1040     def _untransform_string(str, remove_trailing_nulls=False):
1041         """De-obfuscate string. To cope with an obfuscation bug in Rio, the argument
1042         remove_trailing_nulls should be set to True"""
1043         tmp = base64.decodestring(str)
1044         if remove_trailing_nulls:
1045             tmp = tmp.rstrip('\x00')
1046         return rotate_string(tmp, -13)
1047
1048     for pbd in session.xenapi.PBD.get_all_records().values():
1049         if pbd.has_key('device_config') and pbd['device_config'].has_key('target'):
1050             sr = session.xenapi.SR.get_record(pbd['SR'])
1051             if sr.has_key('type') and sr['type'] == 'cslg':
1052                 if sr['shared'] and pbd['host'] != this_host and not i_am_master:
1053                     continue
1054                 
1055                 dev_cfg = pbd['device_config']
1056                 server = "server=%s" % socket.gethostbyname(dev_cfg['target'])
1057                 if dev_cfg.has_key('port'):
1058                     server += ':' + dev_cfg['port']
1059                 if dev_cfg.has_key('username'):
1060                     server += ',' + dev_cfg['username']
1061                 if dev_cfg.has_key('password_transformed'):
1062                     server += ',' + _untransform_string(dev_cfg['password_transformed'])
1063                 procs.append(ProcOutput([CSL, server, 'srv-log-get'], caps[cap][MAX_TIME], output))
1064
1065     session.xenapi.session.logout()
1066
1067     run_procs([procs])
1068
1069     return output.getvalue()
1070
1071 def multipathd_topology(cap):
1072     pipe = Popen([MULTIPATHD, '-k'], bufsize=1, stdin=PIPE, 
1073                      stdout=PIPE, stderr=dev_null)
1074     stdout, stderr = pipe.communicate('show topology')
1075
1076     return stdout
1077
1078 def make_tar(subdir, suffix, output_fd):
1079     global SILENT_MODE, data
1080
1081     mode = 'w'
1082     if suffix == 'tar.bz2':
1083         mode = 'w:bz2'
1084     filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
1085
1086     if output_fd == -1:
1087         tf = tarfile.open(filename, mode)
1088     else:
1089         tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
1090
1091     try:
1092         for (k, v) in data.items():
1093             try:
1094                 tar_filename = os.path.join(subdir, construct_filename(k, v))
1095                 ti = tarfile.TarInfo(tar_filename)
1096
1097                 ti.uname = 'root'
1098                 ti.gname = 'root'
1099
1100                 if v.has_key('output'):
1101                     ti.mtime = v['output'].mtime
1102                     ti.size = len(v['output'].getvalue())
1103                     v['output'].seek(0)
1104                     tf.addfile(ti, v['output'])
1105                 elif v.has_key('filename'):
1106                     s = os.stat(v['filename'])
1107                     ti.mtime = s.st_mtime
1108                     ti.size = s.st_size
1109                     tf.addfile(ti, file(v['filename']))
1110             except:
1111                 pass
1112     finally:
1113         tf.close()
1114
1115     if output_fd == -1:
1116         output ('Writing tarball %s successful.' % filename)
1117         if SILENT_MODE:
1118             print filename
1119
1120
1121 def make_zip(subdir):
1122     global SILENT_MODE, data
1123
1124     filename = "%s/%s.zip" % (BUG_DIR, subdir)
1125     zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
1126
1127     try:
1128         for (k, v) in data.items():
1129             try:
1130                 dest = os.path.join(subdir, construct_filename(k, v))
1131             
1132                 if v.has_key('output'):
1133                     zf.writestr(dest, v['output'].getvalue())
1134                 else:
1135                     if os.stat(v['filename']).st_size < 50:
1136                         compress_type = zipfile.ZIP_STORED
1137                     else:
1138                         compress_type = zipfile.ZIP_DEFLATED
1139                     zf.write(v['filename'], dest, compress_type)
1140             except:
1141                 pass
1142     finally:
1143         zf.close()
1144     
1145     output ('Writing archive %s successful.' % filename)
1146     if SILENT_MODE:
1147         print filename
1148
1149
1150 def make_inventory(inventory, subdir):
1151     document = getDOMImplementation().createDocument(
1152         None, INVENTORY_XML_ROOT, None)
1153
1154     # create summary entry
1155     s = document.createElement(INVENTORY_XML_SUMMARY)
1156     user = os.getenv('SUDO_USER', os.getenv('USER'))
1157     if user:
1158         s.setAttribute('user', user)
1159     s.setAttribute('date', time.strftime('%c'))
1160     s.setAttribute('hostname', platform.node())
1161     s.setAttribute('uname', ' '.join(platform.uname()))
1162     s.setAttribute('uptime', commands.getoutput(UPTIME))
1163     document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
1164
1165     map(lambda (k, v): inventory_entry(document, subdir, k, v),
1166         inventory.items())
1167     return document.toprettyxml()
1168
1169 def inventory_entry(document, subdir, k, v):
1170     try:
1171         el = document.createElement(INVENTORY_XML_ELEMENT)
1172         el.setAttribute('capability', v['cap'])
1173         el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
1174         el.setAttribute('md5sum', md5sum(v))
1175         document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
1176     except:
1177         pass
1178
1179
1180 def md5sum(d):
1181     m = md5.new()
1182     if d.has_key('filename'):
1183         f = open(d['filename'])
1184         data = f.read(1024)
1185         while len(data) > 0:
1186             m.update(data)
1187             data = f.read(1024)
1188         f.close()
1189     elif d.has_key('output'):
1190         m.update(d['output'].getvalue())
1191     return m.hexdigest()
1192
1193
1194 def construct_filename(k, v):
1195     if v.has_key('filename'):
1196         if v['filename'][0] == '/':
1197             return v['filename'][1:]
1198         else:
1199             return v['filename']
1200     s = k.replace(' ', '-')
1201     s = s.replace('--', '-')
1202     s = s.replace('/', '%')
1203     if s.find('.') == -1:
1204         s += '.out'
1205
1206     return s
1207
1208
1209 def update_capabilities():
1210     update_cap_size(CAP_HOST_CRASHDUMP_LOGS,
1211                     size_of_dir(HOST_CRASHDUMPS_DIR, HOST_CRASHDUMP_LOGS_RE))
1212     update_cap_size(CAP_HOST_CRASHDUMP_DUMPS,
1213                     size_of_dir(HOST_CRASHDUMPS_DIR, HOST_CRASHDUMP_LOGS_RE,
1214                                 True))
1215     update_cap_size(CAP_XAPI_DEBUG, size_of_dir(XAPI_DEBUG_DIR))
1216     update_cap_size(CAP_XENSERVER_LOGS, size_of_all(XENSERVER_LOGS))
1217
1218
1219 def update_cap_size(cap, size):
1220     update_cap(cap, MIN_SIZE, size)
1221     update_cap(cap, MAX_SIZE, size)
1222     update_cap(cap, CHECKED, size > 0)
1223
1224
1225 def update_cap(cap, k, v):
1226     global caps
1227     l = list(caps[cap])
1228     l[k] = v
1229     caps[cap] = tuple(l)
1230
1231
1232 def size_of_dir(d, pattern = None, negate = False):
1233     if os.path.isdir(d):
1234         return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
1235                            pattern, negate)
1236     else:
1237         return 0
1238
1239
1240 def size_of_all(files, pattern = None, negate = False):
1241     return sum([size_of(f, pattern, negate) for f in files])
1242
1243
1244 def matches(f, pattern, negate):
1245     if negate:
1246         return not matches(f, pattern, False)
1247     else:
1248         return pattern is None or pattern.match(f)
1249
1250
1251 def size_of(f, pattern, negate):
1252     if os.path.isfile(f) and matches(f, pattern, negate):
1253         return os.stat(f)[6]
1254     else:
1255         return size_of_dir(f, pattern, negate)
1256
1257
1258 def print_capabilities():
1259     document = getDOMImplementation().createDocument(
1260         "ns", CAP_XML_ROOT, None)
1261     map(lambda key: capability(document, key), caps.keys())
1262     print document.toprettyxml()
1263
1264 def capability(document, key):
1265     c = caps[key]
1266     el = document.createElement(CAP_XML_ELEMENT)
1267     el.setAttribute('key', c[KEY])
1268     el.setAttribute('pii', c[PII])
1269     el.setAttribute('min-size', str(c[MIN_SIZE]))
1270     el.setAttribute('max-size', str(c[MAX_SIZE]))
1271     el.setAttribute('min-time', str(c[MIN_TIME]))
1272     el.setAttribute('max-time', str(c[MAX_TIME]))
1273     el.setAttribute('content-type', c[MIME])
1274     el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
1275     document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
1276
1277
1278 def prettyDict(d):
1279     format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
1280     return '\n'.join([format % i for i in d.items()]) + '\n'
1281
1282
1283 def yes(prompt):
1284     yn = raw_input(prompt)
1285
1286     return len(yn) == 0 or yn.lower()[0] == 'y'
1287
1288
1289 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
1290
1291 def dp_list():
1292     command = [OVS_DPCTL, "dump-dps"]
1293     proc = Popen(command, bufsize=1, stdin=dev_null, stdout=PIPE, stderr=dev_null)
1294     (dps, err) = proc.communicate()
1295     return dps.splitlines()
1296
1297
1298 def disk_list():
1299     disks = []
1300     try:
1301         f = open('/proc/partitions')
1302         f.readline()
1303         f.readline()
1304         for line in f.readlines():
1305             (major, minor, blocks, name) = line.split()
1306             if int(major) < 254 and not partition_re.match(name):
1307                 disks.append(name)
1308         f.close()
1309     except:
1310         pass
1311     return disks
1312
1313
1314 class ProcOutput:
1315     debug = False
1316     def __init__(self, command, max_time, inst=None, filter=None):
1317         self.command = command
1318         self.max_time = max_time
1319         self.inst = inst
1320         self.running = False
1321         self.status = None
1322         self.timed_out = False
1323         self.failed = False
1324         self.timeout = int(time.time()) + self.max_time
1325         self.filter = filter
1326
1327     def __del__(self):
1328         self.terminate()
1329
1330     def run(self):
1331         self.timed_out = False
1332         try:
1333             if ProcOutput.debug:
1334                 output_ts("Starting '%s'" % ' '.join(self.command))
1335             self.proc = Popen(self.command, bufsize=1, stdin=dev_null, stdout=PIPE, stderr=dev_null)
1336             old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1337             fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1338             self.running = True
1339             self.failed = False
1340         except:
1341             output_ts("'%s' failed" % ' '.join(self.command))
1342             self.running = False
1343             self.failed = True
1344
1345     def terminate(self):
1346         if self.running:
1347             try:
1348                 os.kill(self.proc.pid, SIGTERM)
1349             except:
1350                 pass
1351             self.proc = None
1352             self.running = False
1353             self.status = SIGTERM
1354
1355     def read_line(self):
1356         assert self.running
1357         line = self.proc.stdout.readline()
1358         if line == '':
1359             # process exited
1360             self.status = self.proc.wait()
1361             self.proc = None
1362             self.running = False
1363         else:
1364             if self.filter:
1365                 line = self.filter(line)
1366             if self.inst:
1367                 self.inst.write(line)
1368
1369 def run_procs(procs):
1370     while True:
1371         pipes = []
1372         active_procs = []
1373
1374         for pp in procs:
1375             for p in pp:
1376                 if p.running:
1377                     active_procs.append(p)
1378                     pipes.append(p.proc.stdout)
1379                     break
1380                 elif p.status == None and not p.failed and not p.timed_out:
1381                     p.run()
1382                     if p.running:
1383                         active_procs.append(p)
1384                         pipes.append(p.proc.stdout)
1385                         break
1386
1387         if len(pipes) == 0:
1388             # all finished
1389             break
1390
1391         (i, o, x) = select(pipes, [], [], 1.0)
1392         now = int(time.time())
1393
1394         # handle process output
1395         for p in active_procs:
1396             if p.proc.stdout in i:
1397                 p.read_line()
1398
1399             # handle timeout
1400             if p.running and now > p.timeout:
1401                 output_ts("'%s' timed out" % ' '.join(p.command))
1402                 if p.inst:
1403                     p.inst.write("\n** timeout **\n")
1404                 p.timed_out = True
1405                 p.terminate()
1406
1407
1408 def pidof(name):
1409     pids = []
1410
1411     for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1412         try:
1413             if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1414                 pids.append(int(d))
1415         except:
1416             pass
1417         
1418     return pids
1419
1420
1421 def readKeyValueFile(filename, allowed_keys = None, strip_quotes = True, assert_quotes = True):
1422     """ Reads a KEY=Value style file (e.g. xensource-inventory). Returns a 
1423     dictionary of key/values in the file.  Not designed for use with large files
1424     as the file is read entirely into memory."""
1425
1426     f = open(filename, "r")
1427     lines = [x.strip("\n") for x in f.readlines()]
1428     f.close()
1429
1430     # remove lines contain
1431     if allowed_keys:
1432         lines = filter(lambda x: True in [x.startswith(y) for y in allowed_keys],
1433                        lines)
1434     
1435     defs = [ (l[:l.find("=")], l[(l.find("=") + 1):]) for l in lines ]
1436
1437     if strip_quotes:
1438         def quotestrip(x):
1439             if assert_quotes:
1440                 assert x.startswith("'") and x.endswith("'")
1441             return x.strip("'")
1442         defs = [ (a, quotestrip(b)) for (a,b) in defs ]
1443
1444     return dict(defs)
1445
1446
1447 class StringIOmtime(StringIO.StringIO):
1448     def __init__(self, buf = ''):
1449         StringIO.StringIO.__init__(self, buf)
1450         self.mtime = time.time()
1451
1452     def write(self, s):
1453         StringIO.StringIO.write(self, s)
1454         self.mtime = time.time()
1455
1456
1457 if __name__ == "__main__":
1458     try:
1459         sys.exit(main())
1460     except KeyboardInterrupt:
1461         print "\nInterrupted."
1462         sys.exit(3)