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