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