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