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