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