nicira-ext: Convert all "uintNN_t"s to "ovs_beNN"s.
[openvswitch] / 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                 cmd_output(CAP_NETWORK_STATUS,
502                            [TC, '-s', '-d', 'class', 'show', 'dev', p])
503         except:
504             pass
505     cmd_output(CAP_NETWORK_STATUS, [TC, '-s', 'qdisc'])
506     file_output(CAP_NETWORK_STATUS, [PROC_NET_SOFTNET_STAT])
507     tree_output(CAP_NETWORK_STATUS, OPENVSWITCH_CORE_DIR)
508     if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
509         cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show'])
510         for d in dp_list():
511             cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'show', d])
512             cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'status', d])
513             cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'dump-flows', d])
514             cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
515         try:
516             vspidfile = open(OPENVSWITCH_VSWITCHD_PID)
517             vspid = int(vspidfile.readline().strip())
518             vspidfile.close()
519             for b in bond_list(vspid):
520                 cmd_output(CAP_NETWORK_STATUS,
521                            [OVS_APPCTL, '-t', '/var/run/ovs-vswitchd.%s.ctl' % vspid, '-e' 'bond/show %s' % b],
522                            'ovs-appctl-bond-show-%s.out' % b)
523         except e:
524             pass
525
526     tree_output(CAP_PAM, PAM_DIR)
527
528     cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
529     func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
530
531     file_output(CAP_SYSTEM_LOGS,
532          [ VAR_LOG_DIR + x for x in
533            [ 'kern.log', 'daemon.log', 'user.log', 'syslog', 'messages',
534              'debug', 'dmesg', 'boot'] +
535            [ f % n for n in range(1, 20) \
536                  for f in ['kern.log.%d', 'kern.log.%d.gz',
537                            'daemon.log.%d', 'daemon.log.%d.gz',
538                            'user.log.%d', 'user.log.%d.gz',
539                            'messages.%d', 'messages.%d.gz']]])
540     if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
541         cmd_output(CAP_SYSTEM_LOGS, [DMESG])
542
543
544     tree_output(CAP_X11_LOGS, X11_LOGS_DIR, X11_LOGS_RE)
545     tree_output(CAP_X11_AUTH, X11_AUTH_DIR, X11_AUTH_RE)
546
547
548     try:
549         load_plugins()
550     except:
551         pass
552
553     # permit the user to filter out data
554     for k in sorted(data.keys()):
555         if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % k):
556             del data[k]
557
558     # collect selected data now
559     output_ts('Running commands to collect data')
560     collect_data()
561
562     subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
563
564     # include inventory
565     data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
566
567     # create archive
568     if output_fd == -1 and not os.path.exists(BUG_DIR):
569         try:
570             os.makedirs(BUG_DIR)
571         except:
572             pass
573
574     if output_fd == -1:
575         output_ts('Creating output file')
576
577     if output_type.startswith('tar'):
578         make_tar(subdir, output_type, output_fd)
579     else:
580         make_zip(subdir)
581
582     clean_tapdisk_logs()
583
584     if dbg:
585         print >>sys.stderr, "Category sizes (max, actual):\n"
586         for c in caps.keys():
587             print >>sys.stderr, "    %s (%d, %d)" % (c, caps[c][MAX_SIZE],
588                                                      cap_sizes[c])
589     return 0
590
591 def find_tapdisk_logs():
592     return glob.glob('/var/log/blktap/*.log*')
593
594 def generate_tapdisk_logs():
595     for pid in pidof('tapdisk'):
596         try:
597             os.kill(pid, SIGUSR1)
598             output_ts("Including logs for tapdisk process %d" % pid)
599         except :
600             pass
601     # give processes a second to write their logs
602     time.sleep(1)
603
604 def clean_tapdisk_logs():
605     for filename in find_tapdisk_logs():
606         try:
607             os.remove(filename)
608         except :
609             pass
610
611 def filter_db_pii(str, state):
612     if 'in_secret_table' not in state:
613         state['in_secret_table'] = False
614
615     if str.startswith('<table ') and 'name="secret"' in str:
616         state['in_secret_table'] = True
617     elif str.startswith('</table>'):
618         state['in_secret_table'] = False
619
620     if state['in_secret_table'] and str.startswith("<row"): # match only on DB rows
621         str = re.sub(r'(value=")[^"]+(")', r'\1REMOVED\2', str)
622     return str
623
624 def dump_scsi_hosts(cap):
625     output = ''
626     l = os.listdir('/sys/class/scsi_host')
627     l.sort()
628
629     for h in l:
630         procname = ''
631         try:
632                 f = open('/sys/class/scsi_host/%s/proc_name' % h)
633                 procname = f.readline().strip("\n")
634                 f.close()
635         except:
636                 pass
637         modelname = None
638         try:
639                 f = open('/sys/class/scsi_host/%s/model_name' % h)
640                 modelname = f.readline().strip("\n")
641                 f.close()
642         except:
643                 pass
644
645         output += "%s:\n" %h
646         output += "    %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
647
648     return output
649
650 def module_info(cap):
651     output = StringIO.StringIO()
652     modules = open(PROC_MODULES, 'r')
653     procs = []
654
655     for line in modules:
656         module = line.split()[0]
657         procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
658     modules.close()
659
660     run_procs([procs])
661
662     return output.getvalue()
663
664 def dp_list():
665     output = StringIO.StringIO()
666     procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
667
668     run_procs([procs])
669
670     if not procs[0].timed_out:
671         return output.getvalue().splitlines()
672     return []
673
674 def bond_list(pid):
675     output = StringIO.StringIO()
676     procs = [ProcOutput([OVS_APPCTL, '-t', '/var/run/ovs-vswitchd.%s.ctl' % pid, '-e' 'bond/list'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
677
678     run_procs([procs])
679
680     if not procs[0].timed_out:
681         bonds = output.getvalue().splitlines()[1:]
682         return [x.split('\t')[1] for x in bonds]
683     return []
684
685 def fd_usage(cap):
686     output = ''
687     fd_dict = {}
688     for d in [p for p in os.listdir('/proc') if p.isdigit()]:
689         try:
690             fh = open('/proc/'+d+'/cmdline')
691             name = fh.readline()
692             num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
693             if num_fds > 0:
694                 if not num_fds in fd_dict:
695                     fd_dict[num_fds] = []
696                 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
697         finally:
698             fh.close()
699     keys = fd_dict.keys()
700     keys.sort(lambda a, b: int(b) - int(a))
701     for k in keys:
702         output += "%s: %s\n" % (k, str(fd_dict[k]))
703     return output
704
705 def load_plugins(just_capabilities = False):
706     def getText(nodelist):
707         rc = ""
708         for node in nodelist:
709             if node.nodeType == node.TEXT_NODE:
710                 rc += node.data
711         return rc.encode()
712
713     def getBoolAttr(el, attr, default = False):
714         ret = default
715         val = el.getAttribute(attr).lower()
716         if val in ['true', 'false', 'yes', 'no']:
717             ret = val in ['true', 'yes']
718         return ret
719
720     for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
721         if not caps.has_key(dir):
722             if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
723                 continue
724             xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
725             assert xmldoc.documentElement.tagName == "capability"
726
727             pii, min_size, max_size, min_time, max_time, mime = \
728                  PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
729
730             if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
731                 pii = xmldoc.documentElement.getAttribute("pii")
732             if xmldoc.documentElement.getAttribute("min_size") != '':
733                 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
734             if xmldoc.documentElement.getAttribute("max_size") != '':
735                 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
736             if xmldoc.documentElement.getAttribute("min_time") != '':
737                 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
738             if xmldoc.documentElement.getAttribute("max_time") != '':
739                 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
740             if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
741                 mime = xmldoc.documentElement.getAttribute("mime")
742             checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
743             hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
744
745             cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
746
747         if just_capabilities:
748             continue
749
750         plugdir = os.path.join(PLUGIN_DIR, dir)
751         for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
752             xmldoc = parse(os.path.join(plugdir, file))
753             assert xmldoc.documentElement.tagName == "collect"
754
755             for el in xmldoc.documentElement.getElementsByTagName("*"):
756                 if el.tagName == "files":
757                     file_output(dir, getText(el.childNodes).split())
758                 elif el.tagName == "directory":
759                     pattern = el.getAttribute("pattern")
760                     if pattern == '': pattern = None
761                     negate = getBoolAttr(el, 'negate')
762                     tree_output(dir, getText(el.childNodes), pattern and re.compile(pattern) or None, negate)
763                 elif el.tagName == "command":
764                     label = el.getAttribute("label")
765                     if label == '': label = None
766                     cmd_output(dir, getText(el.childNodes), label)
767
768 def make_tar(subdir, suffix, output_fd):
769     global SILENT_MODE, data
770
771     mode = 'w'
772     if suffix == 'tar.bz2':
773         mode = 'w:bz2'
774     filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
775
776     if output_fd == -1:
777         tf = tarfile.open(filename, mode)
778     else:
779         tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
780
781     try:
782         for (k, v) in data.items():
783             try:
784                 tar_filename = os.path.join(subdir, construct_filename(k, v))
785                 ti = tarfile.TarInfo(tar_filename)
786
787                 ti.uname = 'root'
788                 ti.gname = 'root'
789
790                 if v.has_key('output'):
791                     ti.mtime = v['output'].mtime
792                     ti.size = len(v['output'].getvalue())
793                     v['output'].seek(0)
794                     tf.addfile(ti, v['output'])
795                 elif v.has_key('filename'):
796                     s = os.stat(v['filename'])
797                     ti.mtime = s.st_mtime
798                     ti.size = s.st_size
799                     tf.addfile(ti, file(v['filename']))
800             except:
801                 pass
802     finally:
803         tf.close()
804
805     if output_fd == -1:
806         output ('Writing tarball %s successful.' % filename)
807         if SILENT_MODE:
808             print filename
809
810
811 def make_zip(subdir):
812     global SILENT_MODE, data
813
814     filename = "%s/%s.zip" % (BUG_DIR, subdir)
815     zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
816
817     try:
818         for (k, v) in data.items():
819             try:
820                 dest = os.path.join(subdir, construct_filename(k, v))
821
822                 if v.has_key('output'):
823                     zf.writestr(dest, v['output'].getvalue())
824                 else:
825                     if os.stat(v['filename']).st_size < 50:
826                         compress_type = zipfile.ZIP_STORED
827                     else:
828                         compress_type = zipfile.ZIP_DEFLATED
829                     zf.write(v['filename'], dest, compress_type)
830             except:
831                 pass
832     finally:
833         zf.close()
834
835     output ('Writing archive %s successful.' % filename)
836     if SILENT_MODE:
837         print filename
838
839
840 def make_inventory(inventory, subdir):
841     document = getDOMImplementation().createDocument(
842         None, INVENTORY_XML_ROOT, None)
843
844     # create summary entry
845     s = document.createElement(INVENTORY_XML_SUMMARY)
846     user = os.getenv('SUDO_USER', os.getenv('USER'))
847     if user:
848         s.setAttribute('user', user)
849     s.setAttribute('date', time.strftime('%c'))
850     s.setAttribute('hostname', platform.node())
851     s.setAttribute('uname', ' '.join(platform.uname()))
852     s.setAttribute('uptime', commands.getoutput(UPTIME))
853     document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
854
855     map(lambda (k, v): inventory_entry(document, subdir, k, v),
856         inventory.items())
857     return document.toprettyxml()
858
859 def inventory_entry(document, subdir, k, v):
860     try:
861         el = document.createElement(INVENTORY_XML_ELEMENT)
862         el.setAttribute('capability', v['cap'])
863         el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
864         el.setAttribute('md5sum', md5sum(v))
865         document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
866     except:
867         pass
868
869
870 def md5sum(d):
871     m = md5.new()
872     if d.has_key('filename'):
873         f = open(d['filename'])
874         data = f.read(1024)
875         while len(data) > 0:
876             m.update(data)
877             data = f.read(1024)
878         f.close()
879     elif d.has_key('output'):
880         m.update(d['output'].getvalue())
881     return m.hexdigest()
882
883
884 def construct_filename(k, v):
885     if v.has_key('filename'):
886         if v['filename'][0] == '/':
887             return v['filename'][1:]
888         else:
889             return v['filename']
890     s = k.replace(' ', '-')
891     s = s.replace('--', '-')
892     s = s.replace('/', '%')
893     if s.find('.') == -1:
894         s += '.out'
895
896     return s
897
898 def update_capabilities():
899     pass
900
901 def update_cap_size(cap, size):
902     update_cap(cap, MIN_SIZE, size)
903     update_cap(cap, MAX_SIZE, size)
904     update_cap(cap, CHECKED, size > 0)
905
906
907 def update_cap(cap, k, v):
908     global caps
909     l = list(caps[cap])
910     l[k] = v
911     caps[cap] = tuple(l)
912
913
914 def size_of_dir(d, pattern = None, negate = False):
915     if os.path.isdir(d):
916         return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
917                            pattern, negate)
918     else:
919         return 0
920
921
922 def size_of_all(files, pattern = None, negate = False):
923     return sum([size_of(f, pattern, negate) for f in files])
924
925
926 def matches(f, pattern, negate):
927     if negate:
928         return not matches(f, pattern, False)
929     else:
930         return pattern is None or pattern.match(f)
931
932
933 def size_of(f, pattern, negate):
934     if os.path.isfile(f) and matches(f, pattern, negate):
935         return os.stat(f)[6]
936     else:
937         return size_of_dir(f, pattern, negate)
938
939
940 def print_capabilities():
941     document = getDOMImplementation().createDocument(
942         "ns", CAP_XML_ROOT, None)
943     map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
944     print document.toprettyxml()
945
946 def capability(document, key):
947     c = caps[key]
948     el = document.createElement(CAP_XML_ELEMENT)
949     el.setAttribute('key', c[KEY])
950     el.setAttribute('pii', c[PII])
951     el.setAttribute('min-size', str(c[MIN_SIZE]))
952     el.setAttribute('max-size', str(c[MAX_SIZE]))
953     el.setAttribute('min-time', str(c[MIN_TIME]))
954     el.setAttribute('max-time', str(c[MAX_TIME]))
955     el.setAttribute('content-type', c[MIME])
956     el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
957     document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
958
959
960 def prettyDict(d):
961     format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
962     return '\n'.join([format % i for i in d.items()]) + '\n'
963
964
965 def yes(prompt):
966     yn = raw_input(prompt)
967
968     return len(yn) == 0 or yn.lower()[0] == 'y'
969
970
971 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
972
973 def disk_list():
974     disks = []
975     try:
976         f = open('/proc/partitions')
977         f.readline()
978         f.readline()
979         for line in f.readlines():
980             (major, minor, blocks, name) = line.split()
981             if int(major) < 254 and not partition_re.match(name):
982                 disks.append(name)
983         f.close()
984     except:
985         pass
986     return disks
987
988
989 class ProcOutput:
990     debug = False
991
992     def __init__(self, command, max_time, inst=None, filter=None):
993         self.command = command
994         self.max_time = max_time
995         self.inst = inst
996         self.running = False
997         self.status = None
998         self.timed_out = False
999         self.failed = False
1000         self.timeout = int(time.time()) + self.max_time
1001         self.filter = filter
1002         self.filter_state = {}
1003
1004     def __del__(self):
1005         self.terminate()
1006
1007     def cmdAsStr(self):
1008         return isinstance(self.command, list) and ' '.join(self.command) or self.command
1009
1010     def run(self):
1011         self.timed_out = False
1012         try:
1013             if ProcOutput.debug:
1014                 output_ts("Starting '%s'" % self.cmdAsStr())
1015             self.proc = Popen(self.command, bufsize=1, stdin=dev_null, stdout=PIPE, stderr=dev_null, shell=isinstance(self.command, str))
1016             old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1017             fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1018             self.running = True
1019             self.failed = False
1020         except:
1021             output_ts("'%s' failed" % self.cmdAsStr())
1022             self.running = False
1023             self.failed = True
1024
1025     def terminate(self):
1026         if self.running:
1027             try:
1028                 os.kill(self.proc.pid, SIGTERM)
1029             except:
1030                 pass
1031             self.proc = None
1032             self.running = False
1033             self.status = SIGTERM
1034
1035     def read_line(self):
1036         assert self.running
1037         line = self.proc.stdout.readline()
1038         if line == '':
1039             # process exited
1040             self.status = self.proc.wait()
1041             self.proc = None
1042             self.running = False
1043         else:
1044             if self.filter:
1045                 line = self.filter(line, self.filter_state)
1046             if self.inst:
1047                 self.inst.write(line)
1048
1049 def run_procs(procs):
1050     while True:
1051         pipes = []
1052         active_procs = []
1053
1054         for pp in procs:
1055             for p in pp:
1056                 if p.running:
1057                     active_procs.append(p)
1058                     pipes.append(p.proc.stdout)
1059                     break
1060                 elif p.status == None and not p.failed and not p.timed_out:
1061                     p.run()
1062                     if p.running:
1063                         active_procs.append(p)
1064                         pipes.append(p.proc.stdout)
1065                         break
1066
1067         if len(pipes) == 0:
1068             # all finished
1069             break
1070
1071         (i, o, x) = select(pipes, [], [], 1.0)
1072         now = int(time.time())
1073
1074         # handle process output
1075         for p in active_procs:
1076             if p.proc.stdout in i:
1077                 p.read_line()
1078
1079             # handle timeout
1080             if p.running and now > p.timeout:
1081                 output_ts("'%s' timed out" % p.cmdAsStr())
1082                 if p.inst:
1083                     p.inst.write("\n** timeout **\n")
1084                 p.timed_out = True
1085                 p.terminate()
1086
1087
1088 def pidof(name):
1089     pids = []
1090
1091     for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1092         try:
1093             if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1094                 pids.append(int(d))
1095         except:
1096             pass
1097
1098     return pids
1099
1100
1101 class StringIOmtime(StringIO.StringIO):
1102     def __init__(self, buf = ''):
1103         StringIO.StringIO.__init__(self, buf)
1104         self.mtime = time.time()
1105
1106     def write(self, s):
1107         StringIO.StringIO.write(self, s)
1108         self.mtime = time.time()
1109
1110
1111 if __name__ == "__main__":
1112     try:
1113         sys.exit(main())
1114     except KeyboardInterrupt:
1115         print "\nInterrupted."
1116         sys.exit(3)