65569649a043413e5517503cd74717b04e16fa23
[sliver-openvswitch.git] / debian / ovs-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 # Copyright (c) 2010, 2011 Nicira Networks.
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 warnings
37 warnings.filterwarnings(action="ignore", category=DeprecationWarning)
38
39 import getopt
40 import re
41 import os
42 import StringIO
43 import sys
44 import tarfile
45 import time
46 import commands
47 import pprint
48 from xml.dom.minidom import parse, getDOMImplementation
49 import zipfile
50 from subprocess import Popen, PIPE
51 from select import select
52 from signal import SIGTERM, SIGUSR1
53 import md5
54 import platform
55 import fcntl
56 import glob
57 import urllib
58 import socket
59 import base64
60
61 sys.path.append('/usr/lib/python')
62 sys.path.append('/usr/lib64/python')
63
64 OS_RELEASE = platform.release()
65
66 #
67 # Files & directories
68 #
69
70 BUG_DIR = "/var/log/ovs-bugtool"
71 PLUGIN_DIR = "/etc/openvswitch/bugtool"
72 GRUB_CONFIG = '/boot/grub/menu.lst'
73 BOOT_KERNEL = '/boot/vmlinuz-' + OS_RELEASE
74 BOOT_INITRD = '/boot/initrd-' + OS_RELEASE + '.img'
75 PROC_PARTITIONS = '/proc/partitions'
76 FSTAB = '/etc/fstab'
77 PROC_MOUNTS = '/proc/mounts'
78 PROC_CPUINFO = '/proc/cpuinfo'
79 PROC_MEMINFO = '/proc/meminfo'
80 PROC_IOPORTS = '/proc/ioports'
81 PROC_INTERRUPTS = '/proc/interrupts'
82 PROC_SCSI = '/proc/scsi/scsi'
83 PROC_VERSION = '/proc/version'
84 PROC_MODULES = '/proc/modules'
85 PROC_DEVICES = '/proc/devices'
86 PROC_FILESYSTEMS = '/proc/filesystems'
87 PROC_CMDLINE = '/proc/cmdline'
88 PROC_CONFIG = '/proc/config.gz'
89 PROC_USB_DEV = '/proc/bus/usb/devices'
90 PROC_NET_SOFTNET_STAT = '/proc/net/softnet_stat'
91 MODPROBE_DIR = '/etc/modprobe.d'
92 RESOLV_CONF = '/etc/resolv.conf'
93 NSSWITCH_CONF = '/etc/nsswitch.conf'
94 NTP_CONF = '/etc/ntp.conf'
95 HOSTS = '/etc/hosts'
96 HOSTS_ALLOW = '/etc/hosts.allow'
97 HOSTS_DENY = '/etc/hosts.deny'
98 DHCP_LEASE_DIR = '/var/lib/dhcp3'
99 OPENVSWITCH_LOG_DIR = '/var/log/openvswitch'
100 OPENVSWITCH_DEFAULT_SWITCH = '/etc/default/openvswitch-switch'
101 OPENVSWITCH_DEFAULT_CONTROLLER = '/etc/default/openvswitch-controller'
102 OPENVSWITCH_CONF_DB = '/etc/openvswitch/conf.db'
103 OPENVSWITCH_VSWITCHD_PID = '/var/run/openvswitch/ovs-vswitchd.pid'
104 COLLECTD_LOGS_DIR = '/var/lib/collectd/rrd'
105 VAR_LOG_DIR = '/var/log/'
106 VAR_LOG_CORE_DIR = '/var/log/core'
107 X11_LOGS_DIR = VAR_LOG_DIR
108 X11_LOGS_RE = re.compile(r'.*/Xorg\..*$')
109 X11_AUTH_DIR = '/root/'
110 X11_AUTH_RE = re.compile(r'.*/\.((Xauthority)|(serverauth\.[0-9]*))$')
111 PAM_DIR = '/etc/pam.d'
112
113 #
114 # External programs
115 #
116
117 ARP = '/usr/sbin/arp'
118 CAT = '/bin/cat'
119 DF = '/bin/df'
120 DMESG = '/bin/dmesg'
121 DMIDECODE = '/usr/sbin/dmidecode'
122 FDISK = '/sbin/fdisk'
123 FIND = '/usr/bin/find'
124 IFCONFIG = '/sbin/ifconfig'
125 IPTABLES = '/sbin/iptables'
126 LOSETUP = '/sbin/losetup'
127 LS = '/bin/ls'
128 LSPCI = '/usr/bin/lspci'
129 MD5SUM = '/usr/bin/md5sum'
130 MODINFO = '/sbin/modinfo'
131 NETSTAT = '/bin/netstat'
132 OVS_DPCTL = '/usr/sbin/ovs-dpctl'
133 OVS_OFCTL = '/usr/sbin/ovs-ofctl'
134 OVS_VSCTL = '/usr/sbin/ovs-vsctl'
135 OVS_APPCTL = '/usr/sbin/ovs-appctl'
136 PS = '/bin/ps'
137 ROUTE = '/sbin/route'
138 SYSCTL = '/sbin/sysctl'
139 TC = '/sbin/tc'
140 UPTIME = '/usr/bin/uptime'
141 ZCAT = '/bin/zcat'
142
143 ETHTOOL = '/sbin/ethtool'
144 # ETHTOOL recently moved from /usr/sbin to /sbin in debian
145 if not os.path.isfile(ETHTOOL):
146     ETHTOOL = '/usr/sbin/ethtool'
147
148 #
149 # PII -- Personally identifiable information.  Of particular concern are
150 # things that would identify customers, or their network topology.
151 # Passwords are never to be included in any bug report, regardless of any PII
152 # declaration.
153 #
154 # NO            -- No PII will be in these entries.
155 # YES           -- PII will likely or certainly be in these entries.
156 # MAYBE         -- The user may wish to audit these entries for PII.
157 # IF_CUSTOMIZED -- If the files are unmodified, then they will contain no PII,
158 # but since we encourage customers to edit these files, PII may have been
159 # introduced by the customer.  This is used in particular for the networking
160 # scripts in dom0.
161 #
162
163 PII_NO            = 'no'
164 PII_YES           = 'yes'
165 PII_MAYBE         = 'maybe'
166 PII_IF_CUSTOMIZED = 'if_customized'
167 KEY      = 0
168 PII      = 1
169 MIN_SIZE = 2
170 MAX_SIZE = 3
171 MIN_TIME = 4
172 MAX_TIME = 5
173 MIME     = 6
174 CHECKED  = 7
175 HIDDEN   = 8
176
177 MIME_DATA = 'application/data'
178 MIME_TEXT = 'text/plain'
179
180 INVENTORY_XML_ROOT = "system-status-inventory"
181 INVENTORY_XML_SUMMARY = 'system-summary'
182 INVENTORY_XML_ELEMENT = 'inventory-entry'
183 CAP_XML_ROOT = "system-status-capabilities"
184 CAP_XML_ELEMENT = 'capability'
185
186
187 CAP_BLOBS                = 'blobs'
188 CAP_BOOT_LOADER          = 'boot-loader'
189 CAP_COLLECTD_LOGS        = 'collectd-logs'
190 CAP_DISK_INFO            = 'disk-info'
191 CAP_FIRSTBOOT            = 'firstboot'
192 CAP_HARDWARE_INFO        = 'hardware-info'
193 CAP_HIGH_AVAILABILITY    = 'high-availability'
194 CAP_HOST_CRASHDUMP_DUMPS = 'host-crashdump-dumps'
195 CAP_HOST_CRASHDUMP_LOGS  = 'host-crashdump-logs'
196 CAP_KERNEL_INFO          = 'kernel-info'
197 CAP_LOSETUP_A            = 'loopback-devices'
198 CAP_NETWORK_CONFIG       = 'network-config'
199 CAP_NETWORK_STATUS       = 'network-status'
200 CAP_OEM                  = 'oem'
201 CAP_PAM                  = 'pam'
202 CAP_PROCESS_LIST         = 'process-list'
203 CAP_PERSISTENT_STATS     = 'persistent-stats'
204 CAP_SYSTEM_LOGS          = 'system-logs'
205 CAP_SYSTEM_SERVICES      = 'system-services'
206 CAP_VNCTERM              = 'vncterm'
207 CAP_WLB                  = 'wlb'
208 CAP_X11_LOGS             = 'X11'
209 CAP_X11_AUTH             = 'X11-auth'
210
211 KB = 1024
212 MB = 1024 * 1024
213
214 caps = {}
215 cap_sizes = {}
216 unlimited_data = False
217 dbg = False
218
219 def cap(key, pii=PII_MAYBE, min_size=-1, max_size=-1, min_time=-1,
220         max_time=-1, mime=MIME_TEXT, checked=True, hidden=False):
221     caps[key] = (key, pii, min_size, max_size, min_time, max_time, mime,
222                  checked, hidden)
223     cap_sizes[key] = 0
224
225
226 cap(CAP_BLOBS,               PII_NO,                    max_size=5*MB)
227 cap(CAP_BOOT_LOADER,         PII_NO,                    max_size=3*KB,
228     max_time=5)
229 cap(CAP_COLLECTD_LOGS,       PII_MAYBE,                 max_size=50*MB,
230     max_time=5)
231 cap(CAP_DISK_INFO,           PII_MAYBE,                 max_size=25*KB,
232     max_time=20)
233 cap(CAP_FIRSTBOOT,           PII_YES,   min_size=60*KB, max_size=80*KB)
234 cap(CAP_HARDWARE_INFO,       PII_MAYBE,                 max_size=30*KB,
235     max_time=20)
236 cap(CAP_HIGH_AVAILABILITY,   PII_MAYBE,                 max_size=5*MB)
237 cap(CAP_HOST_CRASHDUMP_DUMPS,PII_YES, checked = False)
238 cap(CAP_HOST_CRASHDUMP_LOGS, PII_NO)
239 cap(CAP_KERNEL_INFO,         PII_MAYBE,                 max_size=120*KB,
240     max_time=5)
241 cap(CAP_LOSETUP_A,           PII_MAYBE,                 max_size=KB, max_time=5)
242 cap(CAP_NETWORK_CONFIG,      PII_IF_CUSTOMIZED,
243                                         min_size=0,     max_size=20*KB)
244 cap(CAP_NETWORK_STATUS,      PII_YES,                   max_size=19*KB,
245     max_time=30)
246 cap(CAP_PAM,                 PII_NO,                    max_size=30*KB)
247 cap(CAP_PERSISTENT_STATS,    PII_MAYBE,                 max_size=50*MB,
248     max_time=60)
249 cap(CAP_PROCESS_LIST,        PII_YES,                   max_size=30*KB,
250     max_time=20)
251 cap(CAP_SYSTEM_LOGS,         PII_MAYBE,                 max_size=50*MB,
252     max_time=5)
253 cap(CAP_SYSTEM_SERVICES,     PII_NO,                    max_size=5*KB,
254     max_time=20)
255 cap(CAP_VNCTERM,             PII_MAYBE, checked = False)
256 cap(CAP_WLB,                 PII_NO,                    max_size=3*MB,
257     max_time=20)
258 cap(CAP_X11_LOGS,            PII_NO,                    max_size=100*KB)
259 cap(CAP_X11_AUTH,            PII_NO,                    max_size=100*KB)
260
261 ANSWER_YES_TO_ALL = False
262 SILENT_MODE = False
263 entries = None
264 data = {}
265 dev_null = open('/dev/null', 'r+')
266
267 def output(x):
268     global SILENT_MODE
269     if not SILENT_MODE:
270         print x
271
272 def output_ts(x):
273     output("[%s]  %s" % (time.strftime("%x %X %Z"), x))
274
275 def cmd_output(cap, args, label = None, filter = None):
276     if cap in entries:
277         if not label:
278             if isinstance(args, list):
279                 a = [aa for aa in args]
280                 a[0] = os.path.basename(a[0])
281                 label = ' '.join(a)
282             else:
283                 label = args
284         data[label] = {'cap': cap, 'cmd_args': args, 'filter': filter}
285
286 def file_output(cap, path_list):
287     if cap in entries:
288         for p in path_list:
289             if os.path.exists(p):
290                 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
291                         cap_sizes[cap] < caps[cap][MAX_SIZE]:
292                     data[p] = {'cap': cap, 'filename': p}
293                     try:
294                         s = os.stat(p)
295                         cap_sizes[cap] += s.st_size
296                     except:
297                         pass
298                 else:
299                     output("Omitting %s, size constraint of %s exceeded" % (p, cap))
300
301 def tree_output(cap, path, pattern = None, negate = False):
302     if cap in entries:
303         if os.path.exists(path):
304             for f in os.listdir(path):
305                 fn = os.path.join(path, f)
306                 if os.path.isfile(fn) and matches(fn, pattern, negate):
307                     file_output(cap, [fn])
308                 elif os.path.isdir(fn):
309                     tree_output(cap, fn, pattern, negate)
310
311 def func_output(cap, label, func):
312     if cap in entries:
313         t = str(func).split()
314         data[label] = {'cap': cap, 'func': func}
315
316 def collect_data():
317     process_lists = {}
318
319     for (k, v) in data.items():
320         cap = v['cap']
321         if v.has_key('cmd_args'):
322             v['output'] = StringIOmtime()
323             if not process_lists.has_key(cap):
324                 process_lists[cap] = []
325             process_lists[cap].append(ProcOutput(v['cmd_args'], caps[cap][MAX_TIME], v['output'], v['filter']))
326         elif v.has_key('filename') and v['filename'].startswith('/proc/'):
327             # proc files must be read into memory
328             try:
329                 f = open(v['filename'], 'r')
330                 s = f.read()
331                 f.close()
332                 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
333                         cap_sizes[cap] < caps[cap][MAX_SIZE]:
334                     v['output'] = StringIOmtime(s)
335                     cap_sizes[cap] += len(s)
336                 else:
337                     output("Omitting %s, size constraint of %s exceeded" % (v['filename'], cap))
338             except:
339                 pass
340         elif v.has_key('func'):
341             try:
342                 s = v['func'](cap)
343             except Exception, e:
344                 s = str(e)
345             if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
346                     cap_sizes[cap] < caps[cap][MAX_SIZE]:
347                 v['output'] = StringIOmtime(s)
348                 cap_sizes[cap] += len(s)
349             else:
350                 output("Omitting %s, size constraint of %s exceeded" % (k, cap))
351
352     run_procs(process_lists.values())
353
354
355 def main(argv = None):
356     global ANSWER_YES_TO_ALL, SILENT_MODE
357     global entries, data, dbg
358
359     # we need access to privileged files, exit if we are not running as root
360     if os.getuid() != 0:
361         print >>sys.stderr, "Error: ovs-bugtool must be run as root"
362         return 1
363
364     output_file = None
365     output_type = 'tar.bz2'
366     output_fd = -1
367
368     if argv is None:
369         argv = sys.argv
370
371     try:
372         (options, params) = getopt.gnu_getopt(
373             argv, 'sy', ['capabilities', 'silent', 'yestoall', 'entries=',
374                          'output=', 'outfd=', 'outfile=', 'all', 'unlimited',
375                          'debug'])
376     except getopt.GetoptError, opterr:
377         print >>sys.stderr, opterr
378         return 2
379
380     try:
381         load_plugins(True)
382     except:
383         pass
384
385     entries = [e for e in caps.keys() if caps[e][CHECKED]]
386
387     for (k, v) in options:
388         if k == '--capabilities':
389             update_capabilities()
390             print_capabilities()
391             return 0
392
393         if k == '--output':
394             if  v in ['tar', 'tar.bz2', 'tar.gz', 'zip']:
395                 output_type = v
396             else:
397                 print >>sys.stderr, "Invalid output format '%s'" % v
398                 return 2
399
400         # "-s" or "--silent" means suppress output (except for the final
401         # output filename at the end)
402         if k in ['-s', '--silent']:
403             SILENT_MODE = True
404
405         if k == '--entries' and v != '':
406             entries = v.split(',')
407
408         # If the user runs the script with "-y" or "--yestoall" we don't ask
409         # all the really annoying questions.
410         if k in ['-y', '--yestoall']:
411             ANSWER_YES_TO_ALL = True
412
413         if k == '--outfd':
414             output_fd = int(v)
415             try:
416                 old = fcntl.fcntl(output_fd, fcntl.F_GETFD)
417                 fcntl.fcntl(output_fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
418             except:
419                 print >>sys.stderr, "Invalid output file descriptor", output_fd
420                 return 2
421
422         if k == '--outfile':
423             output_file = v
424
425         elif k == '--all':
426             entries = caps.keys()
427         elif k == '--unlimited':
428             unlimited_data = True
429         elif k == '--debug':
430             dbg = True
431             ProcOutput.debug = True
432
433     if len(params) != 1:
434         print >>sys.stderr, "Invalid additional arguments", str(params)
435         return 2
436
437     if output_fd != -1 and output_type != 'tar':
438         print >>sys.stderr, "Option '--outfd' only valid with '--output=tar'"
439         return 2
440
441     if output_fd != -1 and output_file is not None:
442         print >>sys.stderr, "Cannot set both '--outfd' and '--outfile'"
443         return 2
444
445     if ANSWER_YES_TO_ALL:
446         output("Warning: '--yestoall' argument provided, will not prompt for individual files.")
447
448     output('''
449 This application will collate dmesg output, details of the
450 hardware configuration of your machine, information about the build of
451 openvswitch that you are using, plus, if you allow it, various logs.
452
453 The collated information will be saved as a .%s for archiving or
454 sending to a Technical Support Representative.
455
456 The logs may contain private information, and if you are at all
457 worried about that, you should exit now, or you should explicitly
458 exclude those logs from the archive.
459
460 ''' % output_type)
461
462     # assemble potential data
463
464     file_output(CAP_BOOT_LOADER, [GRUB_CONFIG])
465     cmd_output(CAP_BOOT_LOADER, [LS, '-lR', '/boot'])
466     cmd_output(CAP_BOOT_LOADER, [MD5SUM, BOOT_KERNEL, BOOT_INITRD], label='vmlinuz-initrd.md5sum')
467
468     tree_output(CAP_COLLECTD_LOGS, COLLECTD_LOGS_DIR)
469     cmd_output(CAP_DISK_INFO, [FDISK, '-l'])
470     file_output(CAP_DISK_INFO, [PROC_PARTITIONS, PROC_MOUNTS])
471     file_output(CAP_DISK_INFO, [FSTAB])
472     cmd_output(CAP_DISK_INFO, [DF, '-alT'])
473     cmd_output(CAP_DISK_INFO, [DF, '-alTi'])
474     cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_host'])
475     cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_disk'])
476     cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/fc_transport'])
477     func_output(CAP_DISK_INFO, 'scsi-hosts', dump_scsi_hosts)
478
479
480     file_output(CAP_HARDWARE_INFO, [PROC_CPUINFO, PROC_MEMINFO, PROC_IOPORTS, PROC_INTERRUPTS])
481     cmd_output(CAP_HARDWARE_INFO, [DMIDECODE])
482     cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-n'])
483     cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-vv'])
484     file_output(CAP_HARDWARE_INFO, [PROC_USB_DEV, PROC_SCSI])
485     cmd_output(CAP_HARDWARE_INFO, [LS, '-lR', '/dev'])
486
487     file_output(CAP_KERNEL_INFO, [PROC_VERSION, PROC_MODULES, PROC_DEVICES,
488                                   PROC_FILESYSTEMS, PROC_CMDLINE])
489     cmd_output(CAP_KERNEL_INFO, [ZCAT, PROC_CONFIG], label='config')
490     cmd_output(CAP_KERNEL_INFO, [SYSCTL, '-A'])
491     tree_output(CAP_KERNEL_INFO, MODPROBE_DIR)
492     func_output(CAP_KERNEL_INFO, 'modinfo', module_info)
493
494     cmd_output(CAP_LOSETUP_A, [LOSETUP, '-a'])
495
496     file_output(CAP_NETWORK_CONFIG, [RESOLV_CONF, NSSWITCH_CONF, HOSTS])
497     file_output(CAP_NETWORK_CONFIG, [NTP_CONF, HOSTS_ALLOW, HOSTS_DENY])
498     file_output(CAP_NETWORK_CONFIG, [OPENVSWITCH_DEFAULT_SWITCH,
499         OPENVSWITCH_DEFAULT_CONTROLLER, OPENVSWITCH_CONF_DB])
500
501     cmd_output(CAP_NETWORK_STATUS, [IFCONFIG, '-a'])
502     cmd_output(CAP_NETWORK_STATUS, [ROUTE, '-n'])
503     cmd_output(CAP_NETWORK_STATUS, [ARP, '-n'])
504     cmd_output(CAP_NETWORK_STATUS, [NETSTAT, '-an'])
505     tree_output(CAP_NETWORK_STATUS, DHCP_LEASE_DIR)
506     cmd_output(CAP_NETWORK_STATUS, [IPTABLES, '-nL'])
507     for p in os.listdir('/sys/class/net/'):
508         try:
509             f = open('/sys/class/net/%s/type' % p, 'r')
510             t = f.readline()
511             f.close()
512             if int(t) == 1:
513                 # ARPHRD_ETHER
514                 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, p])
515                 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-S', p])
516                 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-k', p])
517                 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-i', p])
518                 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-c', p])
519                 cmd_output(CAP_NETWORK_STATUS,
520                            [TC, '-s', '-d', 'class', 'show', 'dev', p])
521         except:
522             pass
523     cmd_output(CAP_NETWORK_STATUS, [TC, '-s', 'qdisc'])
524     file_output(CAP_NETWORK_STATUS, [PROC_NET_SOFTNET_STAT])
525     tree_output(CAP_NETWORK_STATUS, OPENVSWITCH_LOG_DIR)
526     if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
527         cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show'])
528         for d in dp_list():
529             cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'show', d])
530             cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'dump-flows', d])
531             cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
532         try:
533             vspidfile = open(OPENVSWITCH_VSWITCHD_PID)
534             vspid = int(vspidfile.readline().strip())
535             vspidfile.close()
536             for b in bond_list(vspid):
537                 cmd_output(CAP_NETWORK_STATUS,
538                            [OVS_APPCTL, '-t', '/var/run/ovs-vswitchd.%s.ctl' % vspid, '-e' 'bond/show %s' % b],
539                            'ovs-appctl-bond-show-%s.out' % b)
540         except e:
541             pass
542
543     tree_output(CAP_PAM, PAM_DIR)
544
545     cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
546     func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
547
548     file_output(CAP_SYSTEM_LOGS,
549          [ VAR_LOG_DIR + x for x in
550            [ 'kern.log', 'daemon.log', 'user.log', 'syslog', 'messages',
551              'debug', 'dmesg', 'boot'] +
552            [ f % n for n in range(1, 20) \
553                  for f in ['kern.log.%d', 'kern.log.%d.gz',
554                            'daemon.log.%d', 'daemon.log.%d.gz',
555                            'user.log.%d', 'user.log.%d.gz',
556                            'messages.%d', 'messages.%d.gz']]])
557     if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
558         cmd_output(CAP_SYSTEM_LOGS, [DMESG])
559
560
561     tree_output(CAP_X11_LOGS, X11_LOGS_DIR, X11_LOGS_RE)
562     tree_output(CAP_X11_AUTH, X11_AUTH_DIR, X11_AUTH_RE)
563     tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
564
565
566     try:
567         load_plugins()
568     except:
569         pass
570
571     # permit the user to filter out data
572     for k in sorted(data.keys()):
573         if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % k):
574             del data[k]
575
576     # collect selected data now
577     output_ts('Running commands to collect data')
578     collect_data()
579
580     subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
581
582     # include inventory
583     data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
584
585     # create archive
586     if output_fd == -1:
587         if output_file is None:
588             dirname = BUG_DIR
589         else:
590             dirname = os.path.dirname(output_file)
591         if dirname and not os.path.exists(dirname):
592             try:
593                 os.makedirs(dirname)
594             except:
595                 pass
596
597     if output_fd == -1:
598         output_ts('Creating output file')
599
600     if output_type.startswith('tar'):
601         make_tar(subdir, output_type, output_fd, output_file)
602     else:
603         make_zip(subdir, output_file)
604
605     clean_tapdisk_logs()
606
607     if dbg:
608         print >>sys.stderr, "Category sizes (max, actual):\n"
609         for c in caps.keys():
610             print >>sys.stderr, "    %s (%d, %d)" % (c, caps[c][MAX_SIZE],
611                                                      cap_sizes[c])
612     return 0
613
614 def find_tapdisk_logs():
615     return glob.glob('/var/log/blktap/*.log*')
616
617 def generate_tapdisk_logs():
618     for pid in pidof('tapdisk'):
619         try:
620             os.kill(pid, SIGUSR1)
621             output_ts("Including logs for tapdisk process %d" % pid)
622         except :
623             pass
624     # give processes a second to write their logs
625     time.sleep(1)
626
627 def clean_tapdisk_logs():
628     for filename in find_tapdisk_logs():
629         try:
630             os.remove(filename)
631         except :
632             pass
633
634 def filter_db_pii(str, state):
635     if 'in_secret_table' not in state:
636         state['in_secret_table'] = False
637
638     if str.startswith('<table ') and 'name="secret"' in str:
639         state['in_secret_table'] = True
640     elif str.startswith('</table>'):
641         state['in_secret_table'] = False
642
643     if state['in_secret_table'] and str.startswith("<row"): # match only on DB rows
644         str = re.sub(r'(value=")[^"]+(")', r'\1REMOVED\2', str)
645     return str
646
647 def dump_scsi_hosts(cap):
648     output = ''
649     l = os.listdir('/sys/class/scsi_host')
650     l.sort()
651
652     for h in l:
653         procname = ''
654         try:
655                 f = open('/sys/class/scsi_host/%s/proc_name' % h)
656                 procname = f.readline().strip("\n")
657                 f.close()
658         except:
659                 pass
660         modelname = None
661         try:
662                 f = open('/sys/class/scsi_host/%s/model_name' % h)
663                 modelname = f.readline().strip("\n")
664                 f.close()
665         except:
666                 pass
667
668         output += "%s:\n" %h
669         output += "    %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
670
671     return output
672
673 def module_info(cap):
674     output = StringIO.StringIO()
675     modules = open(PROC_MODULES, 'r')
676     procs = []
677
678     for line in modules:
679         module = line.split()[0]
680         procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
681     modules.close()
682
683     run_procs([procs])
684
685     return output.getvalue()
686
687 def dp_list():
688     output = StringIO.StringIO()
689     procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
690
691     run_procs([procs])
692
693     if not procs[0].timed_out:
694         return output.getvalue().splitlines()
695     return []
696
697 def bond_list(pid):
698     output = StringIO.StringIO()
699     procs = [ProcOutput([OVS_APPCTL, '-t', '/var/run/ovs-vswitchd.%s.ctl' % pid, '-e' 'bond/list'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
700
701     run_procs([procs])
702
703     if not procs[0].timed_out:
704         bonds = output.getvalue().splitlines()[1:]
705         return [x.split('\t')[1] for x in bonds]
706     return []
707
708 def fd_usage(cap):
709     output = ''
710     fd_dict = {}
711     for d in [p for p in os.listdir('/proc') if p.isdigit()]:
712         try:
713             fh = open('/proc/'+d+'/cmdline')
714             name = fh.readline()
715             num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
716             if num_fds > 0:
717                 if not num_fds in fd_dict:
718                     fd_dict[num_fds] = []
719                 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
720         finally:
721             fh.close()
722     keys = fd_dict.keys()
723     keys.sort(lambda a, b: int(b) - int(a))
724     for k in keys:
725         output += "%s: %s\n" % (k, str(fd_dict[k]))
726     return output
727
728 def load_plugins(just_capabilities = False):
729     def getText(nodelist):
730         rc = ""
731         for node in nodelist:
732             if node.nodeType == node.TEXT_NODE:
733                 rc += node.data
734         return rc.encode()
735
736     def getBoolAttr(el, attr, default = False):
737         ret = default
738         val = el.getAttribute(attr).lower()
739         if val in ['true', 'false', 'yes', 'no']:
740             ret = val in ['true', 'yes']
741         return ret
742
743     for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
744         if not caps.has_key(dir):
745             if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
746                 continue
747             xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
748             assert xmldoc.documentElement.tagName == "capability"
749
750             pii, min_size, max_size, min_time, max_time, mime = \
751                  PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
752
753             if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
754                 pii = xmldoc.documentElement.getAttribute("pii")
755             if xmldoc.documentElement.getAttribute("min_size") != '':
756                 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
757             if xmldoc.documentElement.getAttribute("max_size") != '':
758                 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
759             if xmldoc.documentElement.getAttribute("min_time") != '':
760                 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
761             if xmldoc.documentElement.getAttribute("max_time") != '':
762                 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
763             if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
764                 mime = xmldoc.documentElement.getAttribute("mime")
765             checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
766             hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
767
768             cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
769
770         if just_capabilities:
771             continue
772
773         plugdir = os.path.join(PLUGIN_DIR, dir)
774         for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
775             xmldoc = parse(os.path.join(plugdir, file))
776             assert xmldoc.documentElement.tagName == "collect"
777
778             for el in xmldoc.documentElement.getElementsByTagName("*"):
779                 if el.tagName == "files":
780                     file_output(dir, getText(el.childNodes).split())
781                 elif el.tagName == "directory":
782                     pattern = el.getAttribute("pattern")
783                     if pattern == '': pattern = None
784                     negate = getBoolAttr(el, 'negate')
785                     tree_output(dir, getText(el.childNodes), pattern and re.compile(pattern) or None, negate)
786                 elif el.tagName == "command":
787                     label = el.getAttribute("label")
788                     if label == '': label = None
789                     cmd_output(dir, getText(el.childNodes), label)
790
791 def make_tar(subdir, suffix, output_fd, output_file):
792     global SILENT_MODE, data
793
794     mode = 'w'
795     if suffix == 'tar.bz2':
796         mode = 'w:bz2'
797     elif suffix == 'tar.gz':
798         mode = 'w:gz'
799
800     if output_fd == -1:
801         if output_file is None:
802             filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
803         else:
804             filename = output_file
805         tf = tarfile.open(filename, mode)
806     else:
807         tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
808
809     try:
810         for (k, v) in data.items():
811             try:
812                 tar_filename = os.path.join(subdir, construct_filename(k, v))
813                 ti = tarfile.TarInfo(tar_filename)
814
815                 ti.uname = 'root'
816                 ti.gname = 'root'
817
818                 if v.has_key('output'):
819                     ti.mtime = v['output'].mtime
820                     ti.size = len(v['output'].getvalue())
821                     v['output'].seek(0)
822                     tf.addfile(ti, v['output'])
823                 elif v.has_key('filename'):
824                     s = os.stat(v['filename'])
825                     ti.mtime = s.st_mtime
826                     ti.size = s.st_size
827                     tf.addfile(ti, file(v['filename']))
828             except:
829                 pass
830     finally:
831         tf.close()
832
833     if output_fd == -1:
834         output ('Writing tarball %s successful.' % filename)
835         if SILENT_MODE:
836             print filename
837
838
839 def make_zip(subdir, output_file):
840     global SILENT_MODE, data
841
842     if output_file is None:
843         filename = "%s/%s.zip" % (BUG_DIR, subdir)
844     else:
845         filename = output_file
846     zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
847
848     try:
849         for (k, v) in data.items():
850             try:
851                 dest = os.path.join(subdir, construct_filename(k, v))
852
853                 if v.has_key('output'):
854                     zf.writestr(dest, v['output'].getvalue())
855                 else:
856                     if os.stat(v['filename']).st_size < 50:
857                         compress_type = zipfile.ZIP_STORED
858                     else:
859                         compress_type = zipfile.ZIP_DEFLATED
860                     zf.write(v['filename'], dest, compress_type)
861             except:
862                 pass
863     finally:
864         zf.close()
865
866     output ('Writing archive %s successful.' % filename)
867     if SILENT_MODE:
868         print filename
869
870
871 def make_inventory(inventory, subdir):
872     document = getDOMImplementation().createDocument(
873         None, INVENTORY_XML_ROOT, None)
874
875     # create summary entry
876     s = document.createElement(INVENTORY_XML_SUMMARY)
877     user = os.getenv('SUDO_USER', os.getenv('USER'))
878     if user:
879         s.setAttribute('user', user)
880     s.setAttribute('date', time.strftime('%c'))
881     s.setAttribute('hostname', platform.node())
882     s.setAttribute('uname', ' '.join(platform.uname()))
883     s.setAttribute('uptime', commands.getoutput(UPTIME))
884     document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
885
886     map(lambda (k, v): inventory_entry(document, subdir, k, v),
887         inventory.items())
888     return document.toprettyxml()
889
890 def inventory_entry(document, subdir, k, v):
891     try:
892         el = document.createElement(INVENTORY_XML_ELEMENT)
893         el.setAttribute('capability', v['cap'])
894         el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
895         el.setAttribute('md5sum', md5sum(v))
896         document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
897     except:
898         pass
899
900
901 def md5sum(d):
902     m = md5.new()
903     if d.has_key('filename'):
904         f = open(d['filename'])
905         data = f.read(1024)
906         while len(data) > 0:
907             m.update(data)
908             data = f.read(1024)
909         f.close()
910     elif d.has_key('output'):
911         m.update(d['output'].getvalue())
912     return m.hexdigest()
913
914
915 def construct_filename(k, v):
916     if v.has_key('filename'):
917         if v['filename'][0] == '/':
918             return v['filename'][1:]
919         else:
920             return v['filename']
921     s = k.replace(' ', '-')
922     s = s.replace('--', '-')
923     s = s.replace('/', '%')
924     if s.find('.') == -1:
925         s += '.out'
926
927     return s
928
929 def update_capabilities():
930     pass
931
932 def update_cap_size(cap, size):
933     update_cap(cap, MIN_SIZE, size)
934     update_cap(cap, MAX_SIZE, size)
935     update_cap(cap, CHECKED, size > 0)
936
937
938 def update_cap(cap, k, v):
939     global caps
940     l = list(caps[cap])
941     l[k] = v
942     caps[cap] = tuple(l)
943
944
945 def size_of_dir(d, pattern = None, negate = False):
946     if os.path.isdir(d):
947         return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
948                            pattern, negate)
949     else:
950         return 0
951
952
953 def size_of_all(files, pattern = None, negate = False):
954     return sum([size_of(f, pattern, negate) for f in files])
955
956
957 def matches(f, pattern, negate):
958     if negate:
959         return not matches(f, pattern, False)
960     else:
961         return pattern is None or pattern.match(f)
962
963
964 def size_of(f, pattern, negate):
965     if os.path.isfile(f) and matches(f, pattern, negate):
966         return os.stat(f)[6]
967     else:
968         return size_of_dir(f, pattern, negate)
969
970
971 def print_capabilities():
972     document = getDOMImplementation().createDocument(
973         "ns", CAP_XML_ROOT, None)
974     map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
975     print document.toprettyxml()
976
977 def capability(document, key):
978     c = caps[key]
979     el = document.createElement(CAP_XML_ELEMENT)
980     el.setAttribute('key', c[KEY])
981     el.setAttribute('pii', c[PII])
982     el.setAttribute('min-size', str(c[MIN_SIZE]))
983     el.setAttribute('max-size', str(c[MAX_SIZE]))
984     el.setAttribute('min-time', str(c[MIN_TIME]))
985     el.setAttribute('max-time', str(c[MAX_TIME]))
986     el.setAttribute('content-type', c[MIME])
987     el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
988     document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
989
990
991 def prettyDict(d):
992     format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
993     return '\n'.join([format % i for i in d.items()]) + '\n'
994
995
996 def yes(prompt):
997     yn = raw_input(prompt)
998
999     return len(yn) == 0 or yn.lower()[0] == 'y'
1000
1001
1002 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
1003
1004 def disk_list():
1005     disks = []
1006     try:
1007         f = open('/proc/partitions')
1008         f.readline()
1009         f.readline()
1010         for line in f.readlines():
1011             (major, minor, blocks, name) = line.split()
1012             if int(major) < 254 and not partition_re.match(name):
1013                 disks.append(name)
1014         f.close()
1015     except:
1016         pass
1017     return disks
1018
1019
1020 class ProcOutput:
1021     debug = False
1022
1023     def __init__(self, command, max_time, inst=None, filter=None):
1024         self.command = command
1025         self.max_time = max_time
1026         self.inst = inst
1027         self.running = False
1028         self.status = None
1029         self.timed_out = False
1030         self.failed = False
1031         self.timeout = int(time.time()) + self.max_time
1032         self.filter = filter
1033         self.filter_state = {}
1034
1035     def __del__(self):
1036         self.terminate()
1037
1038     def cmdAsStr(self):
1039         return isinstance(self.command, list) and ' '.join(self.command) or self.command
1040
1041     def run(self):
1042         self.timed_out = False
1043         try:
1044             if ProcOutput.debug:
1045                 output_ts("Starting '%s'" % self.cmdAsStr())
1046             self.proc = Popen(self.command, bufsize=1, stdin=dev_null, stdout=PIPE, stderr=dev_null, shell=isinstance(self.command, str))
1047             old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1048             fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1049             self.running = True
1050             self.failed = False
1051         except:
1052             output_ts("'%s' failed" % self.cmdAsStr())
1053             self.running = False
1054             self.failed = True
1055
1056     def terminate(self):
1057         if self.running:
1058             try:
1059                 os.kill(self.proc.pid, SIGTERM)
1060             except:
1061                 pass
1062             self.proc = None
1063             self.running = False
1064             self.status = SIGTERM
1065
1066     def read_line(self):
1067         assert self.running
1068         line = self.proc.stdout.readline()
1069         if line == '':
1070             # process exited
1071             self.status = self.proc.wait()
1072             self.proc = None
1073             self.running = False
1074         else:
1075             if self.filter:
1076                 line = self.filter(line, self.filter_state)
1077             if self.inst:
1078                 self.inst.write(line)
1079
1080 def run_procs(procs):
1081     while True:
1082         pipes = []
1083         active_procs = []
1084
1085         for pp in procs:
1086             for p in pp:
1087                 if p.running:
1088                     active_procs.append(p)
1089                     pipes.append(p.proc.stdout)
1090                     break
1091                 elif p.status == None and not p.failed and not p.timed_out:
1092                     p.run()
1093                     if p.running:
1094                         active_procs.append(p)
1095                         pipes.append(p.proc.stdout)
1096                         break
1097
1098         if len(pipes) == 0:
1099             # all finished
1100             break
1101
1102         (i, o, x) = select(pipes, [], [], 1.0)
1103         now = int(time.time())
1104
1105         # handle process output
1106         for p in active_procs:
1107             if p.proc.stdout in i:
1108                 p.read_line()
1109
1110             # handle timeout
1111             if p.running and now > p.timeout:
1112                 output_ts("'%s' timed out" % p.cmdAsStr())
1113                 if p.inst:
1114                     p.inst.write("\n** timeout **\n")
1115                 p.timed_out = True
1116                 p.terminate()
1117
1118
1119 def pidof(name):
1120     pids = []
1121
1122     for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1123         try:
1124             if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1125                 pids.append(int(d))
1126         except:
1127             pass
1128
1129     return pids
1130
1131
1132 class StringIOmtime(StringIO.StringIO):
1133     def __init__(self, buf = ''):
1134         StringIO.StringIO.__init__(self, buf)
1135         self.mtime = time.time()
1136
1137     def write(self, s):
1138         StringIO.StringIO.write(self, s)
1139         self.mtime = time.time()
1140
1141
1142 if __name__ == "__main__":
1143     try:
1144         sys.exit(main())
1145     except KeyboardInterrupt:
1146         print "\nInterrupted."
1147         sys.exit(3)