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