vswitchd: Make the MAC entry aging time configurable.
[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 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_type = 'tar.bz2'
365     output_fd = -1
366
367     if argv is None:
368         argv = sys.argv
369
370     try:
371         (options, params) = getopt.gnu_getopt(
372             argv, 'sy', ['capabilities', 'silent', 'yestoall', 'entries=',
373                          'output=', 'outfd=', 'all', 'unlimited', 'debug'])
374     except getopt.GetoptError, opterr:
375         print >>sys.stderr, opterr
376         return 2
377
378     try:
379         load_plugins(True)
380     except:
381         pass
382
383     entries = [e for e in caps.keys() if caps[e][CHECKED]]
384
385     for (k, v) in options:
386         if k == '--capabilities':
387             update_capabilities()
388             print_capabilities()
389             return 0
390
391         if k == '--output':
392             if  v in ['tar', 'tar.bz2', 'zip']:
393                 output_type = v
394             else:
395                 print >>sys.stderr, "Invalid output format '%s'" % v
396                 return 2
397
398         # "-s" or "--silent" means suppress output (except for the final
399         # output filename at the end)
400         if k in ['-s', '--silent']:
401             SILENT_MODE = True
402
403         if k == '--entries' and v != '':
404             entries = v.split(',')
405
406         # If the user runs the script with "-y" or "--yestoall" we don't ask
407         # all the really annoying questions.
408         if k in ['-y', '--yestoall']:
409             ANSWER_YES_TO_ALL = True
410
411         if k == '--outfd':
412             output_fd = int(v)
413             try:
414                 old = fcntl.fcntl(output_fd, fcntl.F_GETFD)
415                 fcntl.fcntl(output_fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
416             except:
417                 print >>sys.stderr, "Invalid output file descriptor", output_fd
418                 return 2
419
420         elif k == '--all':
421             entries = caps.keys()
422         elif k == '--unlimited':
423             unlimited_data = True
424         elif k == '--debug':
425             dbg = True
426             ProcOutput.debug = True
427
428     if len(params) != 1:
429         print >>sys.stderr, "Invalid additional arguments", str(params)
430         return 2
431
432     if output_fd != -1 and output_type != 'tar':
433         print >>sys.stderr, "Option '--outfd' only valid with '--output=tar'"
434         return 2
435
436     if ANSWER_YES_TO_ALL:
437         output("Warning: '--yestoall' argument provided, will not prompt for individual files.")
438
439     output('''
440 This application will collate dmesg output, details of the
441 hardware configuration of your machine, information about the build of
442 openvswitch that you are using, plus, if you allow it, various logs.
443
444 The collated information will be saved as a .%s for archiving or
445 sending to a Technical Support Representative.
446
447 The logs may contain private information, and if you are at all
448 worried about that, you should exit now, or you should explicitly
449 exclude those logs from the archive.
450
451 ''' % output_type)
452
453     # assemble potential data
454
455     file_output(CAP_BOOT_LOADER, [GRUB_CONFIG])
456     cmd_output(CAP_BOOT_LOADER, [LS, '-lR', '/boot'])
457     cmd_output(CAP_BOOT_LOADER, [MD5SUM, BOOT_KERNEL, BOOT_INITRD], label='vmlinuz-initrd.md5sum')
458
459     tree_output(CAP_COLLECTD_LOGS, COLLECTD_LOGS_DIR)
460     cmd_output(CAP_DISK_INFO, [FDISK, '-l'])
461     file_output(CAP_DISK_INFO, [PROC_PARTITIONS, PROC_MOUNTS])
462     file_output(CAP_DISK_INFO, [FSTAB])
463     cmd_output(CAP_DISK_INFO, [DF, '-alT'])
464     cmd_output(CAP_DISK_INFO, [DF, '-alTi'])
465     cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_host'])
466     cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_disk'])
467     cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/fc_transport'])
468     func_output(CAP_DISK_INFO, 'scsi-hosts', dump_scsi_hosts)
469
470
471     file_output(CAP_HARDWARE_INFO, [PROC_CPUINFO, PROC_MEMINFO, PROC_IOPORTS, PROC_INTERRUPTS])
472     cmd_output(CAP_HARDWARE_INFO, [DMIDECODE])
473     cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-n'])
474     cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-vv'])
475     file_output(CAP_HARDWARE_INFO, [PROC_USB_DEV, PROC_SCSI])
476     cmd_output(CAP_HARDWARE_INFO, [LS, '-lR', '/dev'])
477
478     file_output(CAP_KERNEL_INFO, [PROC_VERSION, PROC_MODULES, PROC_DEVICES,
479                                   PROC_FILESYSTEMS, PROC_CMDLINE])
480     cmd_output(CAP_KERNEL_INFO, [ZCAT, PROC_CONFIG], label='config')
481     cmd_output(CAP_KERNEL_INFO, [SYSCTL, '-A'])
482     tree_output(CAP_KERNEL_INFO, MODPROBE_DIR)
483     func_output(CAP_KERNEL_INFO, 'modinfo', module_info)
484
485     cmd_output(CAP_LOSETUP_A, [LOSETUP, '-a'])
486
487     file_output(CAP_NETWORK_CONFIG, [RESOLV_CONF, NSSWITCH_CONF, HOSTS])
488     file_output(CAP_NETWORK_CONFIG, [NTP_CONF, HOSTS_ALLOW, HOSTS_DENY])
489     file_output(CAP_NETWORK_CONFIG, [OPENVSWITCH_DEFAULT_SWITCH,
490         OPENVSWITCH_DEFAULT_CONTROLLER, OPENVSWITCH_CONF_DB])
491
492     cmd_output(CAP_NETWORK_STATUS, [IFCONFIG, '-a'])
493     cmd_output(CAP_NETWORK_STATUS, [ROUTE, '-n'])
494     cmd_output(CAP_NETWORK_STATUS, [ARP, '-n'])
495     cmd_output(CAP_NETWORK_STATUS, [NETSTAT, '-an'])
496     tree_output(CAP_NETWORK_STATUS, DHCP_LEASE_DIR)
497     cmd_output(CAP_NETWORK_STATUS, [IPTABLES, '-nL'])
498     for p in os.listdir('/sys/class/net/'):
499         try:
500             f = open('/sys/class/net/%s/type' % p, 'r')
501             t = f.readline()
502             f.close()
503             if int(t) == 1:
504                 # ARPHRD_ETHER
505                 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, p])
506                 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-S', p])
507                 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-k', p])
508                 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-i', p])
509                 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-c', p])
510                 cmd_output(CAP_NETWORK_STATUS,
511                            [TC, '-s', '-d', 'class', 'show', 'dev', p])
512         except:
513             pass
514     cmd_output(CAP_NETWORK_STATUS, [TC, '-s', 'qdisc'])
515     file_output(CAP_NETWORK_STATUS, [PROC_NET_SOFTNET_STAT])
516     tree_output(CAP_NETWORK_STATUS, OPENVSWITCH_LOG_DIR)
517     if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
518         cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show'])
519         for d in dp_list():
520             cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'show', d])
521             cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'status', d])
522             cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'dump-flows', d])
523             cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
524         try:
525             vspidfile = open(OPENVSWITCH_VSWITCHD_PID)
526             vspid = int(vspidfile.readline().strip())
527             vspidfile.close()
528             for b in bond_list(vspid):
529                 cmd_output(CAP_NETWORK_STATUS,
530                            [OVS_APPCTL, '-t', '/var/run/ovs-vswitchd.%s.ctl' % vspid, '-e' 'bond/show %s' % b],
531                            'ovs-appctl-bond-show-%s.out' % b)
532         except e:
533             pass
534
535     tree_output(CAP_PAM, PAM_DIR)
536
537     cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
538     func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
539
540     file_output(CAP_SYSTEM_LOGS,
541          [ VAR_LOG_DIR + x for x in
542            [ 'kern.log', 'daemon.log', 'user.log', 'syslog', 'messages',
543              'debug', 'dmesg', 'boot'] +
544            [ f % n for n in range(1, 20) \
545                  for f in ['kern.log.%d', 'kern.log.%d.gz',
546                            'daemon.log.%d', 'daemon.log.%d.gz',
547                            'user.log.%d', 'user.log.%d.gz',
548                            'messages.%d', 'messages.%d.gz']]])
549     if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
550         cmd_output(CAP_SYSTEM_LOGS, [DMESG])
551
552
553     tree_output(CAP_X11_LOGS, X11_LOGS_DIR, X11_LOGS_RE)
554     tree_output(CAP_X11_AUTH, X11_AUTH_DIR, X11_AUTH_RE)
555     tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
556
557
558     try:
559         load_plugins()
560     except:
561         pass
562
563     # permit the user to filter out data
564     for k in sorted(data.keys()):
565         if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % k):
566             del data[k]
567
568     # collect selected data now
569     output_ts('Running commands to collect data')
570     collect_data()
571
572     subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
573
574     # include inventory
575     data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
576
577     # create archive
578     if output_fd == -1 and not os.path.exists(BUG_DIR):
579         try:
580             os.makedirs(BUG_DIR)
581         except:
582             pass
583
584     if output_fd == -1:
585         output_ts('Creating output file')
586
587     if output_type.startswith('tar'):
588         make_tar(subdir, output_type, output_fd)
589     else:
590         make_zip(subdir)
591
592     clean_tapdisk_logs()
593
594     if dbg:
595         print >>sys.stderr, "Category sizes (max, actual):\n"
596         for c in caps.keys():
597             print >>sys.stderr, "    %s (%d, %d)" % (c, caps[c][MAX_SIZE],
598                                                      cap_sizes[c])
599     return 0
600
601 def find_tapdisk_logs():
602     return glob.glob('/var/log/blktap/*.log*')
603
604 def generate_tapdisk_logs():
605     for pid in pidof('tapdisk'):
606         try:
607             os.kill(pid, SIGUSR1)
608             output_ts("Including logs for tapdisk process %d" % pid)
609         except :
610             pass
611     # give processes a second to write their logs
612     time.sleep(1)
613
614 def clean_tapdisk_logs():
615     for filename in find_tapdisk_logs():
616         try:
617             os.remove(filename)
618         except :
619             pass
620
621 def filter_db_pii(str, state):
622     if 'in_secret_table' not in state:
623         state['in_secret_table'] = False
624
625     if str.startswith('<table ') and 'name="secret"' in str:
626         state['in_secret_table'] = True
627     elif str.startswith('</table>'):
628         state['in_secret_table'] = False
629
630     if state['in_secret_table'] and str.startswith("<row"): # match only on DB rows
631         str = re.sub(r'(value=")[^"]+(")', r'\1REMOVED\2', str)
632     return str
633
634 def dump_scsi_hosts(cap):
635     output = ''
636     l = os.listdir('/sys/class/scsi_host')
637     l.sort()
638
639     for h in l:
640         procname = ''
641         try:
642                 f = open('/sys/class/scsi_host/%s/proc_name' % h)
643                 procname = f.readline().strip("\n")
644                 f.close()
645         except:
646                 pass
647         modelname = None
648         try:
649                 f = open('/sys/class/scsi_host/%s/model_name' % h)
650                 modelname = f.readline().strip("\n")
651                 f.close()
652         except:
653                 pass
654
655         output += "%s:\n" %h
656         output += "    %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
657
658     return output
659
660 def module_info(cap):
661     output = StringIO.StringIO()
662     modules = open(PROC_MODULES, 'r')
663     procs = []
664
665     for line in modules:
666         module = line.split()[0]
667         procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
668     modules.close()
669
670     run_procs([procs])
671
672     return output.getvalue()
673
674 def dp_list():
675     output = StringIO.StringIO()
676     procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
677
678     run_procs([procs])
679
680     if not procs[0].timed_out:
681         return output.getvalue().splitlines()
682     return []
683
684 def bond_list(pid):
685     output = StringIO.StringIO()
686     procs = [ProcOutput([OVS_APPCTL, '-t', '/var/run/ovs-vswitchd.%s.ctl' % pid, '-e' 'bond/list'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
687
688     run_procs([procs])
689
690     if not procs[0].timed_out:
691         bonds = output.getvalue().splitlines()[1:]
692         return [x.split('\t')[1] for x in bonds]
693     return []
694
695 def fd_usage(cap):
696     output = ''
697     fd_dict = {}
698     for d in [p for p in os.listdir('/proc') if p.isdigit()]:
699         try:
700             fh = open('/proc/'+d+'/cmdline')
701             name = fh.readline()
702             num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
703             if num_fds > 0:
704                 if not num_fds in fd_dict:
705                     fd_dict[num_fds] = []
706                 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
707         finally:
708             fh.close()
709     keys = fd_dict.keys()
710     keys.sort(lambda a, b: int(b) - int(a))
711     for k in keys:
712         output += "%s: %s\n" % (k, str(fd_dict[k]))
713     return output
714
715 def load_plugins(just_capabilities = False):
716     def getText(nodelist):
717         rc = ""
718         for node in nodelist:
719             if node.nodeType == node.TEXT_NODE:
720                 rc += node.data
721         return rc.encode()
722
723     def getBoolAttr(el, attr, default = False):
724         ret = default
725         val = el.getAttribute(attr).lower()
726         if val in ['true', 'false', 'yes', 'no']:
727             ret = val in ['true', 'yes']
728         return ret
729
730     for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
731         if not caps.has_key(dir):
732             if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
733                 continue
734             xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
735             assert xmldoc.documentElement.tagName == "capability"
736
737             pii, min_size, max_size, min_time, max_time, mime = \
738                  PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
739
740             if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
741                 pii = xmldoc.documentElement.getAttribute("pii")
742             if xmldoc.documentElement.getAttribute("min_size") != '':
743                 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
744             if xmldoc.documentElement.getAttribute("max_size") != '':
745                 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
746             if xmldoc.documentElement.getAttribute("min_time") != '':
747                 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
748             if xmldoc.documentElement.getAttribute("max_time") != '':
749                 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
750             if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
751                 mime = xmldoc.documentElement.getAttribute("mime")
752             checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
753             hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
754
755             cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
756
757         if just_capabilities:
758             continue
759
760         plugdir = os.path.join(PLUGIN_DIR, dir)
761         for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
762             xmldoc = parse(os.path.join(plugdir, file))
763             assert xmldoc.documentElement.tagName == "collect"
764
765             for el in xmldoc.documentElement.getElementsByTagName("*"):
766                 if el.tagName == "files":
767                     file_output(dir, getText(el.childNodes).split())
768                 elif el.tagName == "directory":
769                     pattern = el.getAttribute("pattern")
770                     if pattern == '': pattern = None
771                     negate = getBoolAttr(el, 'negate')
772                     tree_output(dir, getText(el.childNodes), pattern and re.compile(pattern) or None, negate)
773                 elif el.tagName == "command":
774                     label = el.getAttribute("label")
775                     if label == '': label = None
776                     cmd_output(dir, getText(el.childNodes), label)
777
778 def make_tar(subdir, suffix, output_fd):
779     global SILENT_MODE, data
780
781     mode = 'w'
782     if suffix == 'tar.bz2':
783         mode = 'w:bz2'
784     filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
785
786     if output_fd == -1:
787         tf = tarfile.open(filename, mode)
788     else:
789         tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
790
791     try:
792         for (k, v) in data.items():
793             try:
794                 tar_filename = os.path.join(subdir, construct_filename(k, v))
795                 ti = tarfile.TarInfo(tar_filename)
796
797                 ti.uname = 'root'
798                 ti.gname = 'root'
799
800                 if v.has_key('output'):
801                     ti.mtime = v['output'].mtime
802                     ti.size = len(v['output'].getvalue())
803                     v['output'].seek(0)
804                     tf.addfile(ti, v['output'])
805                 elif v.has_key('filename'):
806                     s = os.stat(v['filename'])
807                     ti.mtime = s.st_mtime
808                     ti.size = s.st_size
809                     tf.addfile(ti, file(v['filename']))
810             except:
811                 pass
812     finally:
813         tf.close()
814
815     if output_fd == -1:
816         output ('Writing tarball %s successful.' % filename)
817         if SILENT_MODE:
818             print filename
819
820
821 def make_zip(subdir):
822     global SILENT_MODE, data
823
824     filename = "%s/%s.zip" % (BUG_DIR, subdir)
825     zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
826
827     try:
828         for (k, v) in data.items():
829             try:
830                 dest = os.path.join(subdir, construct_filename(k, v))
831
832                 if v.has_key('output'):
833                     zf.writestr(dest, v['output'].getvalue())
834                 else:
835                     if os.stat(v['filename']).st_size < 50:
836                         compress_type = zipfile.ZIP_STORED
837                     else:
838                         compress_type = zipfile.ZIP_DEFLATED
839                     zf.write(v['filename'], dest, compress_type)
840             except:
841                 pass
842     finally:
843         zf.close()
844
845     output ('Writing archive %s successful.' % filename)
846     if SILENT_MODE:
847         print filename
848
849
850 def make_inventory(inventory, subdir):
851     document = getDOMImplementation().createDocument(
852         None, INVENTORY_XML_ROOT, None)
853
854     # create summary entry
855     s = document.createElement(INVENTORY_XML_SUMMARY)
856     user = os.getenv('SUDO_USER', os.getenv('USER'))
857     if user:
858         s.setAttribute('user', user)
859     s.setAttribute('date', time.strftime('%c'))
860     s.setAttribute('hostname', platform.node())
861     s.setAttribute('uname', ' '.join(platform.uname()))
862     s.setAttribute('uptime', commands.getoutput(UPTIME))
863     document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
864
865     map(lambda (k, v): inventory_entry(document, subdir, k, v),
866         inventory.items())
867     return document.toprettyxml()
868
869 def inventory_entry(document, subdir, k, v):
870     try:
871         el = document.createElement(INVENTORY_XML_ELEMENT)
872         el.setAttribute('capability', v['cap'])
873         el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
874         el.setAttribute('md5sum', md5sum(v))
875         document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
876     except:
877         pass
878
879
880 def md5sum(d):
881     m = md5.new()
882     if d.has_key('filename'):
883         f = open(d['filename'])
884         data = f.read(1024)
885         while len(data) > 0:
886             m.update(data)
887             data = f.read(1024)
888         f.close()
889     elif d.has_key('output'):
890         m.update(d['output'].getvalue())
891     return m.hexdigest()
892
893
894 def construct_filename(k, v):
895     if v.has_key('filename'):
896         if v['filename'][0] == '/':
897             return v['filename'][1:]
898         else:
899             return v['filename']
900     s = k.replace(' ', '-')
901     s = s.replace('--', '-')
902     s = s.replace('/', '%')
903     if s.find('.') == -1:
904         s += '.out'
905
906     return s
907
908 def update_capabilities():
909     pass
910
911 def update_cap_size(cap, size):
912     update_cap(cap, MIN_SIZE, size)
913     update_cap(cap, MAX_SIZE, size)
914     update_cap(cap, CHECKED, size > 0)
915
916
917 def update_cap(cap, k, v):
918     global caps
919     l = list(caps[cap])
920     l[k] = v
921     caps[cap] = tuple(l)
922
923
924 def size_of_dir(d, pattern = None, negate = False):
925     if os.path.isdir(d):
926         return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
927                            pattern, negate)
928     else:
929         return 0
930
931
932 def size_of_all(files, pattern = None, negate = False):
933     return sum([size_of(f, pattern, negate) for f in files])
934
935
936 def matches(f, pattern, negate):
937     if negate:
938         return not matches(f, pattern, False)
939     else:
940         return pattern is None or pattern.match(f)
941
942
943 def size_of(f, pattern, negate):
944     if os.path.isfile(f) and matches(f, pattern, negate):
945         return os.stat(f)[6]
946     else:
947         return size_of_dir(f, pattern, negate)
948
949
950 def print_capabilities():
951     document = getDOMImplementation().createDocument(
952         "ns", CAP_XML_ROOT, None)
953     map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
954     print document.toprettyxml()
955
956 def capability(document, key):
957     c = caps[key]
958     el = document.createElement(CAP_XML_ELEMENT)
959     el.setAttribute('key', c[KEY])
960     el.setAttribute('pii', c[PII])
961     el.setAttribute('min-size', str(c[MIN_SIZE]))
962     el.setAttribute('max-size', str(c[MAX_SIZE]))
963     el.setAttribute('min-time', str(c[MIN_TIME]))
964     el.setAttribute('max-time', str(c[MAX_TIME]))
965     el.setAttribute('content-type', c[MIME])
966     el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
967     document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
968
969
970 def prettyDict(d):
971     format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
972     return '\n'.join([format % i for i in d.items()]) + '\n'
973
974
975 def yes(prompt):
976     yn = raw_input(prompt)
977
978     return len(yn) == 0 or yn.lower()[0] == 'y'
979
980
981 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
982
983 def disk_list():
984     disks = []
985     try:
986         f = open('/proc/partitions')
987         f.readline()
988         f.readline()
989         for line in f.readlines():
990             (major, minor, blocks, name) = line.split()
991             if int(major) < 254 and not partition_re.match(name):
992                 disks.append(name)
993         f.close()
994     except:
995         pass
996     return disks
997
998
999 class ProcOutput:
1000     debug = False
1001
1002     def __init__(self, command, max_time, inst=None, filter=None):
1003         self.command = command
1004         self.max_time = max_time
1005         self.inst = inst
1006         self.running = False
1007         self.status = None
1008         self.timed_out = False
1009         self.failed = False
1010         self.timeout = int(time.time()) + self.max_time
1011         self.filter = filter
1012         self.filter_state = {}
1013
1014     def __del__(self):
1015         self.terminate()
1016
1017     def cmdAsStr(self):
1018         return isinstance(self.command, list) and ' '.join(self.command) or self.command
1019
1020     def run(self):
1021         self.timed_out = False
1022         try:
1023             if ProcOutput.debug:
1024                 output_ts("Starting '%s'" % self.cmdAsStr())
1025             self.proc = Popen(self.command, bufsize=1, stdin=dev_null, stdout=PIPE, stderr=dev_null, shell=isinstance(self.command, str))
1026             old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1027             fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1028             self.running = True
1029             self.failed = False
1030         except:
1031             output_ts("'%s' failed" % self.cmdAsStr())
1032             self.running = False
1033             self.failed = True
1034
1035     def terminate(self):
1036         if self.running:
1037             try:
1038                 os.kill(self.proc.pid, SIGTERM)
1039             except:
1040                 pass
1041             self.proc = None
1042             self.running = False
1043             self.status = SIGTERM
1044
1045     def read_line(self):
1046         assert self.running
1047         line = self.proc.stdout.readline()
1048         if line == '':
1049             # process exited
1050             self.status = self.proc.wait()
1051             self.proc = None
1052             self.running = False
1053         else:
1054             if self.filter:
1055                 line = self.filter(line, self.filter_state)
1056             if self.inst:
1057                 self.inst.write(line)
1058
1059 def run_procs(procs):
1060     while True:
1061         pipes = []
1062         active_procs = []
1063
1064         for pp in procs:
1065             for p in pp:
1066                 if p.running:
1067                     active_procs.append(p)
1068                     pipes.append(p.proc.stdout)
1069                     break
1070                 elif p.status == None and not p.failed and not p.timed_out:
1071                     p.run()
1072                     if p.running:
1073                         active_procs.append(p)
1074                         pipes.append(p.proc.stdout)
1075                         break
1076
1077         if len(pipes) == 0:
1078             # all finished
1079             break
1080
1081         (i, o, x) = select(pipes, [], [], 1.0)
1082         now = int(time.time())
1083
1084         # handle process output
1085         for p in active_procs:
1086             if p.proc.stdout in i:
1087                 p.read_line()
1088
1089             # handle timeout
1090             if p.running and now > p.timeout:
1091                 output_ts("'%s' timed out" % p.cmdAsStr())
1092                 if p.inst:
1093                     p.inst.write("\n** timeout **\n")
1094                 p.timed_out = True
1095                 p.terminate()
1096
1097
1098 def pidof(name):
1099     pids = []
1100
1101     for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1102         try:
1103             if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1104                 pids.append(int(d))
1105         except:
1106             pass
1107
1108     return pids
1109
1110
1111 class StringIOmtime(StringIO.StringIO):
1112     def __init__(self, buf = ''):
1113         StringIO.StringIO.__init__(self, buf)
1114         self.mtime = time.time()
1115
1116     def write(self, s):
1117         StringIO.StringIO.write(self, s)
1118         self.mtime = time.time()
1119
1120
1121 if __name__ == "__main__":
1122     try:
1123         sys.exit(main())
1124     except KeyboardInterrupt:
1125         print "\nInterrupted."
1126         sys.exit(3)