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